先做再看
本篇是第 09 篇实践 Lab 的参考答案.
强烈建议先独立尝试每个 Lab,遇到卡点翻对应理论篇(01-08),无法解决再查阅.
Lab 1 - Pod 基础
1.1 基础 Pod
apiVersion: v1 kind: Pod metadata: name: nginx-basic namespace: lab spec: containers: - name: nginx image: nginx:1.25-alpine ports: - containerPort: 80
|
$ kubectl apply -f nginx-basic.yaml $ kubectl get pod nginx-basic $ kubectl exec nginx-basic -- curl -s localhost
|
1.2 多容器 Pod + emptyDir 共享
apiVersion: v1 kind: Pod metadata: name: sidecar-demo namespace: lab spec: volumes: - name: shared-log emptyDir: {}
containers: - name: nginx image: nginx:1.25-alpine volumeMounts: - name: shared-log mountPath: /usr/share/nginx/html
- name: writer image: busybox:1.36 command: ["/bin/sh", "-c"] args: - while true; do date >> /logs/log.txt; sleep 1; done volumeMounts: - name: shared-log mountPath: /logs
|
$ kubectl apply -f sidecar-demo.yaml $ sleep 5 $ kubectl exec sidecar-demo -c nginx -- cat /usr/share/nginx/html/log.txt
|
1.3 探针配置
apiVersion: v1 kind: Pod metadata: name: probe-demo namespace: lab spec: containers: - name: nginx image: nginx:1.25-alpine ports: - containerPort: 80 livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 3 periodSeconds: 5 readinessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 3 periodSeconds: 5
|
$ kubectl apply -f probe-demo.yaml $ kubectl get pod probe-demo
|
关键理解:liveness 失败 → K8s 重启容器;readiness 失败 → Pod 从 Endpoints 移除(不接收流量),但容器不重启.
1.4 Init Container
apiVersion: v1 kind: Pod metadata: name: init-demo namespace: lab spec: volumes: - name: workdir emptyDir: {}
initContainers: - name: init-download image: busybox:1.36 command: ["sh", "-c", "echo 'Hello from Init Container' > /work/message.txt"] volumeMounts: - name: workdir mountPath: /work
containers: - name: app image: busybox:1.36 command: ["sh", "-c", "cat /data/message.txt && sleep 3600"] volumeMounts: - name: workdir mountPath: /data
|
$ kubectl apply -f init-demo.yaml $ kubectl logs init-demo $ kubectl get pod init-demo
|
1.5 资源限制与 OOMKilled
apiVersion: v1 kind: Pod metadata: name: oom-demo namespace: lab spec: containers: - name: stress image: polinux/stress command: ["stress", "--vm", "1", "--vm-bytes", "200M"] resources: requests: memory: 64Mi limits: memory: 128Mi
|
$ kubectl apply -f oom-demo.yaml $ kubectl get pod oom-demo -w
|
Lab 2 - 工作负载控制器
2.1 Deployment 基础
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy namespace: lab spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.24 ports: - containerPort: 80
|
$ kubectl apply -f nginx-deploy.yaml $ kubectl get pods -l app=nginx
$ kubectl delete pod <pod-name> $ kubectl get pods -l app=nginx
|
2.2 滚动更新 + 观察
$ kubectl get pods -l app=nginx -w
$ kubectl set image deployment/nginx-deploy nginx=nginx:1.25
|
2.3 调整更新策略
$ kubectl patch deployment nginx-deploy -p ' spec: strategy: rollingUpdate: maxSurge: 0 maxUnavailable: 1'
$ kubectl set image deployment/nginx-deploy nginx=nginx:1.26
|
2.4 回滚
$ kubectl rollout history deployment/nginx-deploy REVISION CHANGE-CAUSE 1 <none> 2 <none> 3 <none>
$ kubectl rollout undo deployment/nginx-deploy --to-revision=1 $ kubectl get deployment nginx-deploy -o jsonpath='{.spec.template.spec.containers[0].image}'
|
2.5 StatefulSet 有序性
apiVersion: v1 kind: Service metadata: name: sts-svc namespace: lab spec: clusterIP: None selector: app: sts-demo ports: - port: 80 --- apiVersion: apps/v1 kind: StatefulSet metadata: name: sts-demo namespace: lab spec: serviceName: sts-svc replicas: 3 selector: matchLabels: app: sts-demo template: metadata: labels: app: sts-demo spec: containers: - name: busybox image: busybox:1.36 command: ["sleep", "3600"]
|
$ kubectl apply -f sts-demo.yaml $ kubectl get pods -w -l app=sts-demo
$ kubectl scale statefulset sts-demo --replicas=1
|
Lab 3 - Service 与服务发现
3.1 ClusterIP + 负载均衡验证
apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: lab spec: selector: app: nginx ports: - port: 80 targetPort: 80
|
$ kubectl apply -f nginx-svc.yaml
$ kubectl run tmp --image=curlimages/curl -n lab -it --rm -- sh
/ $ for i in $(seq 1 10); do curl -s nginx-svc | grep "Server name"; done
/ $ for i in $(seq 1 6); do curl -s http://nginx-svc -o /dev/null -w "%{remote_ip}\n"; done
|
3.2 DNS 发现
/ $ nslookup nginx-svc Server: 10.43.0.10 Address 1: 10.43.0.10 kube-dns.kube-system.svc.cluster.local Name: nginx-svc Address 1: 10.43.xxx.xxx ← ClusterIP
/ $ nslookup nginx-svc.lab.svc.cluster.local
|
3.3 Endpoints 联动
$ kubectl get endpoints nginx-svc -n lab NAME ENDPOINTS nginx-svc 10.42.0.5:80,10.42.1.3:80,10.42.2.4:80
$ kubectl patch deployment nginx-deploy -n lab -p ' spec: template: spec: containers: - name: nginx readinessProbe: httpGet: path: /nonexistent port: 80 periodSeconds: 2'
$ kubectl get endpoints nginx-svc -n lab NAME ENDPOINTS nginx-svc <none> ← 空!所有 Pod 都 readiness 失败
$ kubectl patch deployment nginx-deploy -n lab --type=json \ -p '[{"op": "remove", "path": "/spec/template/spec/containers/0/readinessProbe"}]'
|
3.4 Headless Service
$ kubectl run tmp2 --image=busybox:1.36 -n lab -it --rm -- sh
/ $ nslookup sts-svc.lab.svc.cluster.local
/ $ nslookup sts-demo-0.sts-svc.lab.svc.cluster.local
|
Lab 4 - Ingress 七层路由
4.1 域名路由
apiVersion: apps/v1 kind: Deployment metadata: name: app-a namespace: lab spec: replicas: 1 selector: matchLabels: app: app-a template: metadata: labels: app: app-a spec: containers: - name: echo image: hashicorp/http-echo args: ["-text=Hello from App A"] ports: - containerPort: 5678 --- apiVersion: v1 kind: Service metadata: name: app-a-svc namespace: lab spec: selector: app: app-a ports: - port: 80 targetPort: 5678 ---
--- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: host-routing namespace: lab spec: rules: - host: a.lab.local http: paths: - path: / pathType: Prefix backend: service: name: app-a-svc port: number: 80 - host: b.lab.local http: paths: - path: / pathType: Prefix backend: service: name: app-b-svc port: number: 80
|
$ echo "127.0.0.1 a.lab.local b.lab.local" | sudo tee -a /etc/hosts
$ curl http://a.lab.local Hello from App A
$ curl http://b.lab.local Hello from App B
|
4.2 TLS 自签名证书
$ openssl req -x509 -nodes -days 365 \ -newkey rsa:2048 \ -keyout tls.key \ -out tls.crt \ -subj "/CN=a.lab.local"
$ kubectl create secret tls lab-tls -n lab \ --cert=tls.crt --key=tls.key
|
spec: tls: - hosts: - a.lab.local secretName: lab-tls rules: - host: a.lab.local ...
|
$ curl -k https://a.lab.local Hello from App A
|
Lab 5 - 配置管理
5.1 ConfigMap 环境变量
apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: lab data: APP_ENV: "dev" LOG_LEVEL: "debug" --- apiVersion: v1 kind: Pod metadata: name: config-env-demo namespace: lab spec: containers: - name: app image: busybox:1.36 command: ["sleep", "3600"] envFrom: - configMapRef: name: app-config
|
$ kubectl exec config-env-demo -n lab -- env | grep -E "APP_ENV|LOG_LEVEL" APP_ENV=dev LOG_LEVEL=debug
|
5.2 热更新验证
$ kubectl patch configmap app-config -n lab -p '{"data":{"LOG_LEVEL":"info"}}'
$ kubectl exec config-env-demo -n lab -- env | grep LOG_LEVEL LOG_LEVEL=debug ← 没变!(环境变量不热更新)
|
结论:环境变量注入永不热更新;Volume 挂载(不用 subPath)约 30-60s 后自动更新文件内容.
Lab 6 - 持久化存储
6.1 emptyDir 生命周期
apiVersion: v1 kind: Pod metadata: name: emptydir-demo namespace: lab spec: volumes: - name: data emptyDir: {} containers: - name: writer image: busybox:1.36 command: ["sh", "-c", "echo 'persistent?' > /data/test.txt && sleep 3600"] volumeMounts: - name: data mountPath: /data
|
$ kubectl apply -f emptydir-demo.yaml $ kubectl exec emptydir-demo -n lab -- cat /data/test.txt persistent?
$ kubectl delete pod emptydir-demo -n lab $ kubectl apply -f emptydir-demo.yaml $ kubectl exec emptydir-demo -n lab -- cat /data/test.txt persistent? ← 注意:这是新写入的,不是旧数据!因为 init command 又执行了一遍
|
6.2 PVC 持久化
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: lab-pvc namespace: lab spec: accessModes: - ReadWriteOnce storageClassName: local-path resources: requests: storage: 1Gi --- apiVersion: v1 kind: Pod metadata: name: pvc-demo namespace: lab spec: volumes: - name: data persistentVolumeClaim: claimName: lab-pvc containers: - name: writer image: busybox:1.36 command: ["sleep", "3600"] volumeMounts: - name: data mountPath: /data
|
$ kubectl apply -f pvc-demo.yaml $ kubectl exec pvc-demo -n lab -- sh -c "echo 'I survive pod deletion' > /data/proof.txt" $ kubectl delete pod pvc-demo -n lab
$ kubectl apply -f pvc-demo.yaml $ kubectl exec pvc-demo -n lab -- cat /data/proof.txt I survive pod deletion ← 数据保留!PVC 独立于 Pod 生命周期
|
Lab 7 - 综合实战(核心思路)
Lab 7 较为复杂,这里给出架构设计和关键 YAML 片段.完整实现需要你根据前面 Lab 的经验自行组装.
架构图
┌─────────────┐ │ Ingress │ │ app.lab.local│ └──────┬──────┘ │ ┌────────────┼────────────┐ │ /api │ / │ ↓ ↓ │ ┌──────────┐ ┌──────────┐ │ │ api-svc │ │front-svc │ │ │ (CIP) │ │ (CIP) │ │ └────┬─────┘ └──────────┘ │ │ │ ↓ │ ┌────────────────┐ │ │ api Deployment │ │ │ (2 replicas) │ │ │ + HPA │ │ └───┬────────┬───┘ │ │ │ │ ↓ ↓ │ ┌───────────┐ ┌───────────┐ │ │ pg-svc │ │ redis-svc │ │ │(Headless) │ │(Headless) │ │ └─────┬─────┘ └─────┬─────┘ │ │ │ │ ↓ ↓ │ ┌───────────┐ ┌───────────┐ │ │PostgreSQL │ │ Redis │ │ │StatefulSet│ │StatefulSet│ │ │ + PVC │ │ + PVC │ │ └───────────┘ └───────────┘
|
关键 YAML 片段
apiVersion: v1 kind: Secret metadata: name: pg-credentials namespace: lab stringData: POSTGRES_USER: admin POSTGRES_PASSWORD: "lab-secret-123" POSTGRES_DB: labdb
---
apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres namespace: lab spec: serviceName: pg-svc replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:16-alpine ports: - containerPort: 5432 envFrom: - secretRef: name: pg-credentials volumeMounts: - name: pgdata mountPath: /var/lib/postgresql/data volumeClaimTemplates: - metadata: name: pgdata spec: accessModes: ["ReadWriteOnce"] storageClassName: local-path resources: requests: storage: 2Gi
---
apiVersion: apps/v1 kind: Deployment metadata: name: api namespace: lab spec: replicas: 2 selector: matchLabels: app: api template: metadata: labels: app: api spec: containers: - name: api image: hashicorp/http-echo args: ["-text=API OK - connected to pg-svc and redis-svc"] ports: - containerPort: 5678 env: - name: DB_HOST valueFrom: configMapKeyRef: name: api-config key: DB_HOST - name: DB_PASSWORD valueFrom: secretKeyRef: name: pg-credentials key: POSTGRES_PASSWORD resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi
---
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: api-hpa namespace: lab spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: api minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60
|
验证步骤
$ kubectl get all -n lab
$ echo "127.0.0.1 app.lab.local" | sudo tee -a /etc/hosts
$ curl http://app.lab.local/api API OK - connected to pg-svc and redis-svc
$ curl http://app.lab.local/
$ kubectl delete pod postgres-0 -n lab $ kubectl get pod -w -n lab
$ kubectl delete pod -l app=api -n lab $ kubectl get pod -l app=api -n lab
|
小结
快速回顾
- Lab 1:Pod 的 YAML 结构,多容器共享 Volume,探针行为差异,OOMKilled 触发条件
- Lab 2:Deployment 自愈能力,滚动更新可视化观察,回滚操作,StatefulSet 严格有序
- Lab 3:Service DNS 解析验证,Endpoints 随 readiness 联动,Headless 逐 Pod DNS
- Lab 4:Ingress 域名/路径路由配置,TLS 证书创建和挂载
- Lab 5:环境变量不热更新,Volume 文件自动更新,subPath 的限制
- Lab 6:emptyDir 数据随 Pod 消失,PVC 数据跨 Pod 生命周期保留
- Lab 7:完整微服务编排--组件间通过 Service 名通信,ConfigMap/Secret 传递配置,StatefulSet+PVC 保障数据持久化
恭喜完成
如果你独立完成了全部 7 个 Lab,说明你已经具备了在生产环境中部署和管理 K8s 应用的基础能力.下一步建议:
- 尝试用 Helm 打包 Lab 7 的完整应用
- 在真实的云平台(如 TKE,EKS)上重复 Lab 7
- 深入学习你感兴趣的进阶主题(如 Operator 开发,Service Mesh)