Kubernetes 运维入门:从零搭建生产级集群
前言
如果你已经在用 Docker 跑容器,迟早会遇到这些问题:容器挂了谁来重启?流量怎么分发到多个容器?滚动更新怎么做?某台机器宕机了容器怎么迁移?这些问题的答案就是 Kubernetes。
K8s 的学习曲线确实陡,但核心概念其实不多。这篇文章不走"百科全书"路线,而是按一个运维工程师上手 K8s 的真实路径来组织:先理解核心概念 → 搭一个集群 → 部署一个应用 → 排查一个问题。
一、核心概念:只讲你必须知道的
1.1 Pod — 最小调度单元
Pod 是 K8s 中最小的可部署单元。一个 Pod 可以包含一个或多个容器,这些容器共享网络和存储命名空间。
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
restartPolicy: Always
为什么不直接调度容器? 因为有些场景需要多个容器紧密协作(如主容器 + sidecar 日志收集),它们需要共享网络栈和存储卷。Pod 就是把这些容器打包在一起的"盒子"。
关键概念:requests 和 limits
requests:调度器据此决定 Pod 放到哪个节点(节点剩余资源 >= requests)limits:运行时硬限制,超过会被限流(CPU)或杀死(内存 OOM)
# 查看 Pod 资源使用
kubectl top pod nginx-pod
NAME CPU(cores) MEMORY(bytes)
nginx-pod 2m 15Mi
1.2 Deployment — 声明式管理
你几乎不会直接创建 Pod。Pod 是易逝的——节点故障、升级、扩缩容都会销毁重建 Pod。Deployment 管理一组相同的 Pod,确保始终有指定数量的副本在运行。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3 # 期望3个副本
selector:
matchLabels:
app: web-app
strategy:
type: RollingUpdate # 滚动更新
rollingUpdate:
maxSurge: 1 # 更新时最多多出1个
maxUnavailable: 0 # 更新时不允许减少(零停机)
template: # Pod 模板
metadata:
labels:
app: web-app
spec:
containers:
- name: web
image: registry.example.com/web:v2.1.0
ports:
- containerPort: 8080
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-secret
key: host
- name: APP_ENV
value: "production"
readinessProbe: # 就绪探针
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe: # 存活探针
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
lifecycle:
preStop: # 优雅停止
exec:
command: ["sh", "-c", "curl -X POST http://localhost:8080/shutdown && sleep 5"]
terminationGracePeriodSeconds: 30
探针详解:
| 探针 | 作用 | 失败行为 |
|---|---|---|
livenessProbe |
容器是否活着 | 重启容器 |
readinessProbe |
容器是否就绪能接流量 | 从 Service Endpoints 中移除 |
startupProbe |
容器是否已启动(优先于上面两个) | 重启容器 |
关键理解:readinessProbe 失败不会重启容器,只是暂时不往这个 Pod 转发流量。livenessProbe 失败会重启容器。对于启动慢的应用(如 Java),用 startupProbe 避免 livenessProbe 在启动阶段误杀。
1.3 Service — 稳定的访问入口
Pod 的 IP 是不固定的,Service 提供一个稳定的虚拟 IP 和 DNS 名字,并将流量负载均衡到后端 Pod。
apiVersion: v1
kind: Service
metadata:
name: web-app-svc
spec:
selector:
app: web-app # 匹配 Pod 标签
ports:
- port: 80 # Service 端口
targetPort: 8080 # Pod 端口
type: ClusterIP # 集群内部访问
Service 类型:
| 类型 | 说明 | 访问范围 |
|---|---|---|
ClusterIP |
默认,集群内部虚拟 IP | 集群内 |
NodePort |
在每个节点开放端口(30000-32767) | 集群外(节点IP:端口) |
LoadBalancer |
云厂商负载均衡器 | 公网 |
Headless |
不分配 ClusterIP,DNS 直接返回 Pod IP | 有状态服务 |
# Service 对应的 DNS 记录
# <service-name>.<namespace>.svc.cluster.local
# 如:web-app-svc.default.svc.cluster.local
# 集群内访问
curl http://web-app-svc.default.svc.cluster.local:80
# 或简写(同一 namespace)
curl http://web-app-svc:80
1.4 Ingress — HTTP 七层路由
Service 是四层(TCP/UDP),Ingress 是七层(HTTP/HTTPS),可以根据域名和路径路由到不同 Service。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: tls-secret
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app-svc
port:
number: 80
- path: /static
pathType: Prefix
backend:
service:
name: static-svc
port:
number: 80
1.5 ConfigMap 与 Secret — 配置管理
# ConfigMap - 非敏感配置
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
app.properties: |
server.port=8080
log.level=info
cache.ttl=300
DB_NAME: "mydb"
---
# Secret - 敏感信息(base64编码,不是加密!)
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
host: bXlkYi5leGFtcGxlLmNvbQ== # echo -n 'mydb.example.com' | base64
password: cGFzc3dvcmQxMjM= # echo -n 'password123' | base64
重要安全提示:Secret 默认只是 base64 编码,不是加密。生产环境要启用 encryption at rest 或使用外部密钥管理(Vault、Sealed Secrets)。
二、kubeadm 搭建生产集群
2.1 环境准备
三台机器(2核4G起步):
| 角色 | 主机名 | IP |
|---|---|---|
| Master | k8s-master | 192.168.1.10 |
| Worker 1 | k8s-worker1 | 192.168.1.11 |
| Worker 2 | k8s-worker2 | 192.168.1.12 |
所有节点执行:
# 1. 关闭 swap(K8s 要求)
swapoff -a
sed -i '/swap/d' /etc/fstab
# 2. 加载内核模块
cat > /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
# 3. 内核参数
cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system
# 4. 安装容器运行时(containerd)
cat > /etc/apt/sources.list.d/docker.list <<EOF
deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable
EOF
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
apt update && apt install -y containerd.io
# 生成默认配置
containerd config default > /etc/containerd/config.toml
# 修改 SystemdCgroup 为 true
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# 配置镜像加速
sed -i 's|configs = \[\]|configs = [{\n plugin = "io.containerd.grpc.v1.cri"\n path = "/etc/containerd/certs.d"\n}]|' /etc/containerd/config.toml
mkdir -p /etc/containerd/certs.d/docker.io
echo 'server = "https://registry-1.docker.io"' > /etc/containerd/certs.d/docker.io/hosts.toml
systemctl enable --now containerd
# 5. 安装 kubeadm, kubelet, kubectl
# 添加 K8s 源(国内镜像)
cat > /etc/apt/sources.list.d/kubernetes.list <<EOF
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
apt update
apt install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
2.2 初始化 Master 节点
# 在 k8s-master 上执行
kubeadm init \
--apiserver-advertise-address=192.168.1.10 \
--pod-network-cidr=10.244.0.0/16 \
--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers \
--kubernetes-version=v1.30.0
# 输出中会有 join 命令,记下来:
# kubeadm join 192.168.1.10:6443 --token xxxxxx --discovery-token-ca-cert-hash sha256:xxxxxx
# 配置 kubectl
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
# 验证
kubectl get nodes
# 此时 master 节点状态为 NotReady,因为还没装网络插件
2.3 安装网络插件(Calico)
# 下载 Calico YAML
curl -fsSL https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml -o calico.yaml
# 如果用了自定义 pod CIDR,修改 CALICO_IPV4POOL_CIDR
# 默认就是 192.168.0.0/16,如果 init 时用了 10.244.0.0/16 需要改
sed -i 's/192.168.0.0\/16/10.244.0.0\/16/' calico.yaml
kubectl apply -f calico.yaml
# 等待 Calico Pod 就绪
kubectl get pods -n kube-system -w
# 直到 calico-node 全部 Running
# 再次检查节点状态
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# k8s-master Ready control-plane 10m v1.30.0
2.4 加入 Worker 节点
# 在每个 worker 节点执行 init 输出的 join 命令
kubeadm join 192.168.1.10:6443 \
--token xxxxxx \
--discovery-token-ca-cert-hash sha256:xxxxxx
# 回到 master 验证
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# k8s-master Ready control-plane 15m v1.30.0
# k8s-worker1 Ready <none> 2m v1.30.0
# k8s-worker2 Ready <none> 2m v1.30.0
如果 join token 过期了:
# 在 master 上重新生成
kubeadm token create --print-join-command
2.5 安装 Ingress Controller
# 安装 NGINX Ingress Controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/baremetal/deploy.yaml
# 验证
kubectl get pods -n ingress-nginx -w
# 裸金属环境需要用 NodePort 或 MetalLB 暴露 Ingress
kubectl get svc -n ingress-nginx
# ingress-nginx-controller NodePort 10.96.45.123 <none> 80:31234/TCP,443:31235/TCP
三、部署一个完整应用
3.1 创建命名空间
kubectl create namespace production
3.2 部署配置
# web-app.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
namespace: production
data:
APP_ENV: "production"
DB_POOL_SIZE: "20"
---
apiVersion: v1
kind: Secret
metadata:
name: web-secret
namespace: production
type: Opaque
stringData: # 用 stringData 不用手动 base64
DB_PASSWORD: "S3cr3tP@ss"
REDIS_PASSWORD: "r3d1sP@ss"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: web-config
- secretRef:
name: web-secret
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: web-html
---
apiVersion: v1
kind: Service
metadata:
name: web-svc
namespace: production
spec:
selector:
app: web-app
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
namespace: production
spec:
ingressClassName: nginx
rules:
- host: app.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 80
kubectl apply -f web-app.yaml
# 查看
kubectl get all -n production
kubectl get ingress -n production
3.3 持久化存储
裸金属环境没有云盘,用 NFS 或 local-volume:
# StorageClass - NFS
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
provisioner: nfs.csi.k8s.io
parameters:
server: 192.168.1.100
share: /data/nfs
reclaimPolicy: Retain
volumeBindingMode: Immediate
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
namespace: production
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-storage
resources:
requests:
storage: 10Gi
# 在 Deployment 中挂载
spec:
containers:
- name: app
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: data-pvc
四、运维操作速查
4.1 日常操作
# 查看集群状态
kubectl get nodes -o wide
kubectl cluster-info
# 查看 Pod(所有命名空间)
kubectl get pods -A
kubectl get pods -n production -o wide
# 查看 Pod 详情(事件、调度信息)
kubectl describe pod <pod-name> -n production
# 查看 Pod 日志
kubectl logs <pod-name> -n production
kubectl logs <pod-name> -n production --tail=100
kubectl logs <pod-name> -n production -f # 实时跟踪
kubectl logs <pod-name> -n production --previous # 上一个容器的日志(崩溃排查)
# 进入 Pod
kubectl exec -it <pod-name> -n production -- /bin/sh
kubectl exec -it <pod-name> -n production -- /bin/bash
# 端口转发(本地调试)
kubectl port-forward svc/web-svc 8080:80 -n production
# 然后本地访问 http://localhost:8080
# 临时 Pod 调试网络
kubectl run debug --image=nicolaka/netshoot -it --rm -- bash
# 在 Pod 内测试集群内服务连通性
curl http://web-svc.production.svc.cluster.local
4.2 扩缩容
# 手动扩缩容
kubectl scale deployment web-app --replicas=5 -n production
# 水平自动扩缩容(HPA)
kubectl autoscale deployment web-app \
--min=3 --max=10 \
--cpu-percent=70 \
-n production
# 查看 HPA
kubectl get hpa -n production
4.3 滚动更新与回滚
# 更新镜像
kubectl set image deployment/web-app web=nginx:1.26 -n production
# 查看滚动状态
kubectl rollout status deployment/web-app -n production
# 查看历史版本
kubectl rollout history deployment/web-app -n production
# 回滚到上一版本
kubectl rollout undo deployment/web-app -n production
# 回滚到指定版本
kubectl rollout undo deployment/web-app --to-revision=2 -n production
4.4 节点维护
# 标记节点不可调度(维护模式)
kubectl cordon k8s-worker1
# 驱逐节点上的 Pod
kubectl drain k8s-worker1 --ignore-daemonsets --delete-emptydir-data
# 维护完成后恢复
kubectl uncordon k8s-worker1
4.5 清理资源
# 删除指定资源
kubectl delete -f web-app.yaml
kubectl delete deployment web-app -n production
# 强制删除卡住的 Pod
kubectl delete pod <pod-name> -n production --force --grace-period=0
# 清理已完成 Pod(Completed 状态)
kubectl delete pods --field-selector=status.phase=Succeeded -A
五、故障排查实战
场景一:Pod 一直 Pending
kubectl describe pod <pod-name> -n production
# 查看 Events 部分
常见原因:
- Insufficient cpu/memory:节点资源不够。降低 requests 或加节点。
- node(s) had volume node affinity conflict:PV 和 Pod 不在同一个 zone/节点。
- 0/3 nodes are available: 3 node(s) had untolerated taint:节点有污点,Pod 没有配置 toleration。
场景二:Pod 一直 ImagePullBackOff
kubectl describe pod <pod-name> -n production
# Failed to pull image "xxx": rpc error: code = Unknown
排查:
# 1. 检查镜像名拼写
# 2. 检查镜像仓库是否可访问
kubectl run test --image=<image-name> --restart=Never -it --rm
# 3. 检查 imagePullSecrets(私有仓库)
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=user \
--docker-password=pass \
--docker-email=user@example.com \
-n production
# 在 Deployment 中引用
# spec.template.spec.imagePullSecrets:
# - name: regcred
场景三:Pod 频繁重启(CrashLoopBackOff)
# 查看退出原因
kubectl logs <pod-name> -n production --previous
kubectl describe pod <pod-name> -n production | grep -A5 "Last State"
# 常见原因:
# 1. 应用启动崩溃 → 看日志
# 2. livenessProbe 太激进 → 加大 initialDelaySeconds
# 3. OOMKilled → 加大 memory limits
# 4. 配置错误 → 检查 ConfigMap/Secret
场景四:Service 无法访问
# 1. 检查 Endpoints 是否有 Pod
kubectl get endpoints web-svc -n production
# 如果 ENDPOINTS 为空,说明 selector 没匹配到 Pod
# 2. 检查 selector
kubectl get pods -n production --show-labels
kubectl get svc web-svc -n production -o yaml | grep -A5 selector
# 3. 检查 Pod readinessProbe 是否通过
kubectl get pods -n production
# READY 列 0/1 说明 readinessProbe 没通过
# 4. DNS 解析问题
kubectl run dns-test --image=busybox -it --rm -- nslookup web-svc.production.svc.cluster.local
场景五:节点 NotReady
# 查看节点详情
kubectl describe node k8s-worker1
# 常见原因:
# 1. kubelet 挂了 → systemctl status kubelet
# 2. 磁盘满了 → df -h
# 3. 内存不足 → free -m
# 4. 容器运行时挂了 → systemctl status containerd
# 5. 网络插件问题 → 检查 calico-node Pod
六、生产环境建议
6.1 资源管理
# 每个容器都必须设置 resources
resources:
requests:
cpu: 100m # 不设 requests = 可能调度到资源不足的节点
memory: 128Mi
limits:
cpu: 500m # 不设 limits = 可能吃光节点资源
memory: 256Mi # CPU limit 超过会限流,内存 limit 超过会 OOMKill
# 安装 metrics-server 查看资源使用
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl top nodes
kubectl top pods -A
6.2 安全加固
# Pod Security Standards
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
# SecurityContext
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
6.3 备份与灾难恢复
# 备份 etcd(K8s 的核心数据存储)
ETCDCTL_API=3 etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
snapshot save /backup/etcd-$(date +%Y%m%d).db
# 验证备份
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
snapshot status /backup/etcd-20260707.db --write-out=table
6.4 命名规范
| 资源 | 命名规范 | 示例 |
|---|---|---|
| Namespace | 环境名 | production / staging |
| Deployment | 应用名 | web-app / user-service |
| Service | 应用名 + 后缀 | web-app-svc |
| ConfigMap | 应用名 + 后缀 | web-app-config |
| Secret | 应用名 + 后缀 | web-app-secret |
| Ingress | 域名 | api-example-com |
| PVC | 用途 | web-app-data |
写在最后
Kubernetes 的学习曲线确实陡,但核心就是几个概念:Pod 是运行单元,Deployment 管理 Pod 副本,Service 提供访问入口,Ingress 做七层路由,ConfigMap/Secret 管配置,PVC 管存储。理解了这些,80% 的日常运维就够用了。
进阶方向:
- Helm — K8s 的包管理器,管理复杂应用的 YAML 模板
- ArgoCD — GitOps 持续部署,把 Git 仓库作为声明式配置的唯一来源
- Service Mesh (Istio/Linkerd) — 微服务间流量管理、可观测性、安全
- Kube-Prometheus-Stack — 监控告警全套方案
建议先用 kubeadm 搭一个三节点集群练手,把应用的部署、扩缩容、更新回滚、故障排查全走一遍。等这些操作变成肌肉记忆,再深入更高级的主题。不要一开始就上生产级的多 master 高可用集群——复杂度会指数级上升,而且大部分问题在单 master 环境就能遇到和练习。

