本练习文档配合专栏第 1-11 章使用。每学完一章,按对应练习动手操作一遍,效果最好。
前置条件:完成第 12 章的 3 节点集群搭建,或拥有一个可用的 K8s 集群。
13.1:核心概念总览
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:双容器共享卷
创建包含 writer 和 reader 两个容器的 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 仓库
- 在 GitHub 创建仓库
gitops-demo - 提交一个 Deployment + Service YAML
- 在 ArgoCD 中创建 Application 指向该仓库
- 点击 Sync,观察应用部署成功
13.11.3:Git Push 触发发布
- 修改 Git 仓库中 Deployment 的镜像 tag
- git commit && git push
- 观察 ArgoCD 从 OutOfSync → Syncing → Synced
- kubectl 验证集群中镜像已更新
13.11.4:漂移检测
- 确认 syncPolicy.automated.selfHeal: true
- kubectl scale 手动改副本数
- 观察 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>