Linux运维18 min read

Kubernetes 运维入门:从零搭建生产级集群

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 环境就能遇到和练习。

分享:

相关文章

评论区