第 13 章:Kubernetes 从入门到精通 · 实战

2 阅读6分钟

本练习文档配合专栏第 1-11 章使用。每学完一章,按对应练习动手操作一遍,效果最好。

前置条件:完成第 12 章的 3 节点集群搭建,或拥有一个可用的 K8s 集群。

13.1:核心概念总览

saas-multiagent-k8s-arch.png

13.1.1:认识你的集群

kubectl version --client
kubectl get nodes -o wide
kubectl api-resources | head -20

目标:确认 kubectl 能连接集群,了解集群节点数量、角色和版本。

13.2.2:理解声明式

分别用两种方式创建一个 Nginx Pod:

# 命令式(临时 Pod)
kubectl run nginx-imperative --image=nginx:alpine

# 声明式(YAML)
cat > nginx-declarative.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: nginx-declarative
spec:
  containers:
  - name: nginx
    image: nginx:alpine
EOF
kubectl apply -f nginx-declarative.yaml

然后删除:

kubectl delete pod nginx-imperative nginx-declarative

思考:声明式有什么好处?


13.2:Pod

13.2.1:双容器共享卷

创建包含 writerreader 两个容器的 Pod,reader 每秒打印 writer 写入的文件。

参考 YAML:

apiVersion: v1
kind: Pod
metadata:
  name: shared-vol
spec:
  containers:
  - name: writer
    image: busybox:1.31
    command: ["sh", "-c", "while true; do echo $(date) >> /data/log.txt; sleep 5; done"]
    volumeMounts:
    - name: data
      mountPath: /data
  - name: reader
    image: busybox:1.31
    command: ["sh", "-c", "while true; do cat /data/log.txt; sleep 5; done"]
    volumeMounts:
    - name: data
      mountPath: /data
  volumes:
  - name: data
    emptyDir: {}

验证:

kubectl logs shared-vol -c reader -f

13.2.2:探针失败自动重启

创建一个 livenessProbe 会失败的 Pod,观察 RESTARTS 增长:

apiVersion: v1
kind: Pod
metadata:
  name: probe-test
spec:
  containers:
  - name: app
    image: busybox:1.31
    command: ["sh", "-c", "touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600"]
    livenessProbe:
      exec:
        command: ["cat", "/tmp/healthy"]
      initialDelaySeconds: 5
      periodSeconds: 5
kubectl get pod probe-test -w
# 观察 RESTARTS 数字

13.2.3:Init 容器等待依赖

创建一个 Init 容器,主容器在 Init 成功前不启动:

apiVersion: v1
kind: Pod
metadata:
  name: init-demo
spec:
  initContainers:
  - name: wait
    image: busybox:1.31
    command: ["sh", "-c", "sleep 10; echo init done"]
  containers:
  - name: main
    image: nginx:alpine
kubectl describe pod init-demo | grep -A5 Init

13.3:Deployment

13.3.1:创建与扩缩容

kubectl create deployment web --image=nginx:alpine --replicas=3
kubectl get deploy,rs,pod -l app=web
kubectl scale deployment web --replicas=5
kubectl get rs

13.3.2:滚动更新与回滚

# 记录当前 revision
kubectl rollout history deployment/web

# 更新镜像
kubectl set image deployment/web nginx=nginx:1.27

# 观察滚动更新
kubectl rollout status deployment/web
kubectl get rs

# 回滚
kubectl rollout undo deployment/web
kubectl get rs

13.3.3:零停机发布

创建以下 Deployment,然后更新镜像,过程中访问 Service,验证不中断:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: zero-down
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: zero-down
  template:
    metadata:
      labels:
        app: zero-down
    spec:
      containers:
      - name: web
        image: nginx:alpine
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 3
          periodSeconds: 3
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 5"]

配合 Service:

apiVersion: v1
kind: Service
metadata:
  name: zero-down
spec:
  selector:
    app: zero-down
  ports:
  - port: 80
    targetPort: 80

压测:

# 另开一个终端循环请求
while true; do curl -s http://zero-down:80 | head -1; done

更新镜像,看是否有 502:

kubectl set image deployment/zero-down nginx=nginx:1.27

13.4:StatefulSet

13.4.1:部署一个 3 副本 StatefulSet

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web-sts
spec:
  serviceName: web-headless
  replicas: 3
  selector:
    matchLabels:
      app: web-sts
  template:
    metadata:
      labels:
        app: web-sts
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web-headless
spec:
  clusterIP: None
  selector:
    app: web-sts
  ports:
  - port: 80
kubectl apply -f web-sts.yaml
kubectl get pods
# 观察名字:web-sts-0, web-sts-1, web-sts-2
# 观察创建顺序:0 Ready 后才会创建 1

13.4.2:稳定 DNS 解析

# 进入一个 Pod
kubectl run tmp --rm -it --image=busybox:1.31 -- sh

# 在容器内解析
nslookup web-sts-0.web-headless.default.svc.cluster.local
nslookup web-sts-1.web-headless.default.svc.cluster.local
nslookup web-sts-2.web-headless.default.svc.cluster.local

13.4.3:删除 Pod 看名字是否保留

kubectl delete pod web-sts-1
kubectl get pods
# 新 Pod 仍然叫 web-sts-1,不是随机名

13.5:Service

13.5.1:创建 ClusterIP 和 NodePort

kubectl create deployment web-svc --image=nginx:alpine --replicas=3
kubectl expose deployment web-svc --port=80 --target-port=80 --type=ClusterIP
kubectl expose deployment web-svc --port=80 --target-port=80 --type=NodePort --name=web-svc-np
kubectl get svc
kubectl get endpoints web-svc

13.5.2:验证 Endpoints

kubectl get endpoints web-svc
kubectl scale deployment web-svc --replicas=5
kubectl get endpoints web-svc
# 应该变成 5 个 IP

13.5.3:selector 不匹配导致 Service 不通

apiVersion: v1
kind: Service
metadata:
  name: wrong-svc
spec:
  selector:
    app: not-exist   # 故意写错
  ports:
  - port: 80
kubectl apply -f wrong-svc.yaml
kubectl get endpoints wrong-svc
# 输出 <none>
# 这就是 Service 不通最常见的根因

13.6:Ingress

13.6.1:安装 Ingress Controller

# 安装 nginx-ingress
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.0/deploy/static/provider/cloud/deploy.yaml

kubectl get pods -n ingress-nginx -w

13.6.2:创建多路径 Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-ingress
spec:
  rules:
  - host: demo.local
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-svc
            port:
              number: 8080
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-svc
            port:
              number: 80

验证:

kubectl get ingress
# 查看 ADDRESS 列

如果本地测试,可在 /etc/hosts 添加:

<Ingress Controller IP> demo.local

13.7:ConfigMap & Secret

13.7.1:ConfigMap 三种注入方式

kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=debug \
  --from-literal=DB_HOST=mysql

创建 Pod 测试环境变量注入:

apiVersion: v1
kind: Pod
metadata:
  name: env-test
spec:
  containers:
  - name: app
    image: busybox:1.31
    command: ["sh", "-c", "env | grep -E 'LOG_LEVEL|DB_HOST'; sleep 3600"]
    envFrom:
    - configMapRef:
        name: app-config

练习 7.2:Secret 与 Base64 真相

kubectl create secret generic db-pass --from-literal=password=123456
kubectl get secret db-pass -o yaml
# 用 base64 解码
kubectl get secret db-pass -o jsonpath='{.data.password}' | base64 -d

思考:为什么说 Secret 只是编码,不是加密?生产环境应该怎么保护 Secret?


13.8:Volume

13.8.1:EmptyDir 生命周期

创建第 2 章练习 2.1 的 Pod,删除后重建,验证日志是否清空。

13.8.2:PVC 动态供应

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
kubectl apply -f pvc.yaml
kubectl get pvc test-pvc
# 输出 Bound 表示成功
kubectl get pv
# 自动创建的 PV

13.8.3:Pod 挂载 PVC

apiVersion: v1
kind: Pod
metadata:
  name: pvc-user
spec:
  containers:
  - name: app
    image: busybox:1.31
    command: ["sh", "-c", "echo hello > /data/a.txt; sleep 3600"]
    volumeMounts:
    - name: data
      mountPath: /data
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: test-pvc

删除 Pod 再重建,验证 /data/a.txt 还在。


13.9:HPA

13.9.1:安装 Metrics Server

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl top nodes
kubectl top pods

如果报错 x509,加上 --kubelet-insecure-tls 参数(仅测试环境)。

13.9.2:创建 HPA

kubectl create deployment hpa-demo --image=nginx:alpine --replicas=1
kubectl autoscale deployment hpa-demo --min=1 --max=5 --cpu-percent=50
kubectl get hpa

13.9.3:制造负载触发扩容

# 进入 Pod 制造 CPU 负载
kubectl exec -it deploy/hpa-demo -- sh
# 容器内执行
while true; do echo 1 > /dev/null; done

观察 HPA:

kubectl get hpa -w
kubectl get pods -w

13.10:Helm

13.10.1:安装 nginx 并自定义 values

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install my-nginx bitnami/nginx --set service.type=NodePort
helm list
kubectl get svc my-nginx

13.10.2:升级与回滚

helm upgrade my-nginx bitnami/nginx --set replicaCount=3
helm history my-nginx
helm rollback my-nginx 1

13.10.3:打包自己的 Chart

helm create mychart
# 修改 templates/deployment.yaml 和 values.yaml
helm package mychart
helm install demo ./mychart-0.1.0.tgz

13.11:ArgoCD

13.11.1:安装 ArgoCD

参考第 11 章内容,用 Helm 安装 ArgoCD,记录 NodePort 和 admin 初始密码。

13.11.2:创建 GitOps 仓库

  1. 在 GitHub 创建仓库 gitops-demo
  2. 提交一个 Deployment + Service YAML
  3. 在 ArgoCD 中创建 Application 指向该仓库
  4. 点击 Sync,观察应用部署成功

13.11.3:Git Push 触发发布

  1. 修改 Git 仓库中 Deployment 的镜像 tag
  2. git commit && git push
  3. 观察 ArgoCD 从 OutOfSync → Syncing → Synced
  4. kubectl 验证集群中镜像已更新

13.11.4:漂移检测

  1. 确认 syncPolicy.automated.selfHeal: true
  2. kubectl scale 手动改副本数
  3. 观察 ArgoCD 是否自动改回 Git 中声明的副本数

附录:推荐的 kubectl 命令速查

# 资源查看
kubectl get pods,svc,deploy -n <ns>
kubectl get pods -o wide -n <ns>
kubectl get events --sort-by=.lastTimestamp -n <ns>

# 详情与日志
kubectl describe pod <pod> -n <ns>
kubectl logs <pod> -n <ns>
kubectl logs <pod> -n <ns> --previous
kubectl exec -it <pod> -n <ns> -- /bin/sh

# 调试网络
kubectl get endpoints <svc> -n <ns>
kubectl run tmp --rm -it --image=busybox:1.31 -- nslookup <svc>.<ns>.svc.cluster.local

# 编辑与回滚
kubectl edit deployment <name> -n <ns>
kubectl rollout status deployment <name> -n <ns>
kubectl rollout undo deployment <name> -n <ns>