DAY 02 —— 部署 Pod 的几种方式(全流程实操)

举报
yd_268505900 发表于 2026/11/06 11:37:17 2026/11/06
【摘要】 DAY 02 —— 部署 Pod 的几种方式(全流程实操)


【学习目标】
1. 分清"裸 Pod / Deployment / DaemonSet / StatefulSet / Job / 静态 Pod"
2. 每种方式都能独立写出 YAML 并跑起来、验证、清理
3. 掌握探针、资源限制、调度约束、滚动更新与回滚
4. 建立"生产不裸跑 Pod"的正确直觉


----------------------------------------------------------------
零、先记住这张选型表
----------------------------------------------------------------
需求场景                                用哪个
--------------------------------------  ----------------------
一次性调试、验证镜像                      kubectl run(裸 Pod)
无状态 Web/API,要滚动更新、要扩缩容      Deployment  ★最常用
每个节点必须跑一个(日志/监控/CNI agent) DaemonSet
有状态、需稳定域名和独立存储(DB/MQ)      StatefulSet
跑完就结束的批处理                        Job
定时任务(每天备份、每小时对账)           CronJob
kubelet 直接管理的系统组件                静态 Pod(/etc/kubernetes/manifests)
需要固定 Pod IP、单实例且要保留 IP        裸 Pod / StatefulSet + Headless


----------------------------------------------------------------
一、方式 1:kubectl run(命令式,最快,仅调试)
----------------------------------------------------------------
# 最简
kubectl run web --image=nginx:1.27

# 带端口、副本(注意:1.18+ 的 run 不再支持 --replicas,副本用 scale 或 deploy)
kubectl run web --image=nginx:1.27 --port=80

# 交互式临时 Pod,退出即删(排障神器)
kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- sh
# 容器内测试集群 DNS:
#   nslookup kubernetes.default.svc.cluster.local
#   wget -qO- http://web

# 生成 YAML 而不真正创建(--dry-run=client -o yaml)
kubectl run web --image=nginx:1.27 --port=80 \
  --dry-run=client -o yaml > web-pod.yaml

查看与清理:
kubectl get pod web -o wide
kubectl describe pod web
kubectl delete pod web

要点:
- 裸 Pod 没有控制器,删了不重建、节点故障不迁移
- --dry-run=client -o yaml 是写 YAML 的官方"脚手架"技巧,后面每种资源都能这么干


----------------------------------------------------------------
二、方式 2:直接 apply 一个 Pod YAML(声明式基础)
----------------------------------------------------------------
cat > pod-nginx.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
    tier: frontend
spec:
  containers:
  - name: nginx
    image: nginx:1.27
    ports:
    - name: http
      containerPort: 80
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 256Mi
    env:
    - name: TZ
      value: Asia/Shanghai
EOF

kubectl apply -f pod-nginx.yaml
kubectl get pod nginx -o yaml          # 看 apiserver 补全的字段(status 等)
kubectl get pod nginx -o jsonpath='{.status.podIP}{"\n"}'
kubectl delete -f pod-nginx.yaml

YAML 四段结构(所有 K8s 对象通用):
apiVersion / kind / metadata(name、namespace、labels、annotations)/ spec(期望状态)
你只写 spec,status 由系统写。


----------------------------------------------------------------
三、方式 3:Deployment(生产标准,必须掌握)
----------------------------------------------------------------
3.1 最小可用 Deployment
----------------------------------------------------------------
cat > deploy-web.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1          # 最多超出期望副本 1 个
      maxUnavailable: 0    # 升级期间不允许不可用(滚动不中断)
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web
        image: nginx:1.27
        ports:
        - containerPort: 80
        resources:
          requests: {cpu: 100m, memory: 128Mi}
          limits:   {cpu: 500m, memory: 256Mi}
EOF

kubectl apply -f deploy-web.yaml
kubectl rollout status deploy/web
kubectl get rs,po -l app=web -o wide

层级关系(务必理解):
Deployment ──管理──> ReplicaSet ──管理──> Pod
每次改镜像模板 → 新建一个 RS → 新 RS 扩、老 RS 缩 → 老 RS 保留(用于回滚)

3.2 暴露服务:Service
----------------------------------------------------------------
cat > svc-web.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web          # 关键:靠 label 选中 Pod
  ports:
  - port: 80          # Service 端口
    targetPort: 80    # 容器端口
  type: ClusterIP
EOF
kubectl apply -f svc-web.yaml
kubectl get svc web
kubectl get endpointslices -l kubernetes.io/service-name=web   # 看后端 Pod IP

三种类型:
- ClusterIP(默认):仅集群内可访问,集群内用 web.default.svc.cluster.local
- NodePort:每节点开 30000-32767 端口,curl <节点IP>:<nodePort>
- LoadBalancer:云上自动建 SLB;裸机可配 MetalLB

集群内验证(用临时 Pod 当客户端):
kubectl run curl --rm -it --image=curlimages/curl --restart=Never -- \
  curl -s -o /dev/null -w '%{http_code}\n' http://web

3.3 扩缩容
----------------------------------------------------------------
kubectl scale deploy web --replicas=5
kubectl get po -l app=web -w        # 观察实时变化,Ctrl+C 退出
kubectl autoscale deploy web --min=2 --max=10 --cpu-percent=70   # HPA(需 metrics-server)
kubectl get hpa

3.4 滚动更新与回滚(重点实操)
----------------------------------------------------------------
# 改镜像(触发滚动更新)
kubectl set image deploy/web web=nginx:1.28
kubectl rollout status deploy/web
kubectl rollout history deploy/web
kubectl get rs -l app=web          # 能看到新旧两个 RS

# 模拟坏版本,观察 maxUnavailable=0 的效果
kubectl set image deploy/web web=nginx:9.9.9
kubectl get po -l app=web -w       # 新 Pod ImagePullBackOff,旧 Pod 还在服务
kubectl rollout status deploy/web  # 会卡住并提示
kubectl rollout undo deploy/web    # 回滚
kubectl rollout status deploy/web

# 回到指定版本
kubectl rollout history deploy/web --revision=2
kubectl rollout undo deploy/web --to-revision=2

# 暂停/恢复(批量改多个字段时避免多次滚动)
kubectl rollout pause deploy/web ... ; kubectl rollout resume deploy/web

# 修改资源限制(会重建 Pod,注意 requests 变化会重新调度)
kubectl set resources deploy/web -c web --limits=cpu=1,memory=512Mi

3.5 探针:让 K8s 知道你的应用到底活没活
----------------------------------------------------------------
cat > deploy-probe.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels: {app: api}
  template:
    metadata:
      labels: {app: api}
    spec:
      containers:
      - name: api
        image: nginx:1.27
        ports: [{containerPort: 80}]
        readinessProbe:            # 就绪:失败则从 Service 后端摘除,不重启
          httpGet: {path: /, port: 80}
          initialDelaySeconds: 3
          periodSeconds: 5
          failureThreshold: 3
        livenessProbe:             # 存活:失败则重启容器
          httpGet: {path: /, port: 80}
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 2
          failureThreshold: 3
        startupProbe:              # 启动:启动慢的应用用,成功后 liveness 才开始计时
          tcpSocket: {port: 80}
          failureThreshold: 30
          periodSeconds: 2
        lifecycle:
          preStop:                 # 优雅下线:先从 Endpoints 摘除,再等连接排空
            exec:
              command: ["sh","-c","sleep 5"]
      terminationGracePeriodSeconds: 30
EOF

三种探针取舍:
- 只配 readiness:优雅发布、不过早接流量
- liveness:绝不能配成"依赖数据库挂了就重启我",会导致雪崩
- startupProbe:给 JVM/大模型服务留足启动时间,避免被 liveness 误杀

验证:
kubectl describe pod <pod> | grep -A3 -E 'Readiness|Liveness'
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[*].ready}'

3.6 配置注入:ConfigMap / Secret
----------------------------------------------------------------
kubectl create configmap app-cfg --from-literal=APP_ENV=prod \
  --from-file=nginx.conf
kubectl create secret generic db-secret \
  --from-literal=MYSQL_ROOT_PASSWORD='S3cr3t!'

# 在 Deployment 里使用
#   envFrom:
#   - configMapRef: {name: app-cfg}
#   - secretRef:    {name: db-secret}
#   或整目录挂载:
#   volumeMounts: [{name: cfg, mountPath: /etc/nginx/conf.d}]
#   volumes: [{name: cfg, configMap: {name: app-cfg}}]

注意:Secret 默认只是 base64,不是加密。生产要开 etcd 静态加密(EncryptionConfiguration)
+ RBAC 限制 + 外接 KMS。不要把 Secret 提交到 Git。

3.7 生产必备:Pod 反亲和 + 中断预算
----------------------------------------------------------------
# 让 3 个副本尽量分散到不同节点(节点故障不至于全挂)
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels: {app: web}

# PDB:节点维护时至少保留 2 个可用
cat > pdb-web.yaml <<'EOF'
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: {name: web-pdb}
spec:
  minAvailable: 2
  selector:
    matchLabels: {app: web}
EOF
kubectl apply -f pdb-web.yaml


----------------------------------------------------------------
四、方式 4:DaemonSet(每节点一个)
----------------------------------------------------------------
典型用途:日志采集(filebeat)、监控(node-exporter)、CNI、安全 agent。

cat > ds-agent.yaml <<'EOF'
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-agent
  namespace: kube-system
spec:
  selector:
    matchLabels: {app: node-agent}
  updateStrategy:
    type: RollingUpdate
    rollingUpdate: {maxUnavailable: 1}
  template:
    metadata:
      labels: {app: node-agent}
    spec:
      tolerations:                      # 允许跑到有污点的 master 上
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule
      containers:
      - name: agent
        image: busybox:1.36
        command: ["sh","-c","while true; do date; sleep 60; done"]
        resources:
          requests: {cpu: 20m, memory: 32Mi}
        volumeMounts:
        - name: varlog
          mountPath: /var/log
          readOnly: true
      volumes:
      - name: varlog
        hostPath: {path: /var/log}
EOF

kubectl apply -f ds-agent.yaml
kubectl get ds -n kube-system node-agent
kubectl get po -n kube-system -l app=node-agent -o wide   # 数量 = 节点数

要点:不给 replicas(跟随节点数自动增减);常用 hostPath / hostNetwork;
需要容忍 master 污点才能覆盖全节点。


----------------------------------------------------------------
五、方式 5:StatefulSet(有状态)
----------------------------------------------------------------
适用:MySQL 主从、Redis Cluster、Kafka、ZooKeeper、Elasticsearch。

三个"稳定":
1) 稳定网络标识:pod-0.svc-name.ns.svc.cluster.local(需 Headless Service)
2) 稳定存储:每个副本一个 PVC(volumeClaimTemplates),删 Pod 不删盘
3) 稳定顺序:0→N-1 依次创建,N-1→0 逆序删除

cat > sts-demo.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: redis
spec:
  clusterIP: None                # Headless
  selector: {app: redis}
  ports: [{port: 6379, name: redis}]
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis
  replicas: 3
  selector:
    matchLabels: {app: redis}
  template:
    metadata:
      labels: {app: redis}
    spec:
      containers:
      - name: redis
        image: redis:7.2
        ports: [{containerPort: 6379, name: redis}]
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests: {storage: 1Gi}
      # storageClassName: standard   # 按集群实际 SC 填写
EOF

kubectl apply -f sts-demo.yaml
kubectl get po -l app=redis -w          # 观察 0→1→2 顺序创建
kubectl get pvc                          # data-redis-0/1/2
kubectl delete po redis-0
kubectl get pvc                          # 盘还在,重新挂回

集群内解析验证:
kubectl run dns --rm -it --image=busybox:1.36 --restart=Never -- \
  nslookup redis-0.redis.default.svc.cluster.local


----------------------------------------------------------------
六、方式 6:Job 与 CronJob(批处理)
----------------------------------------------------------------
6.1 Job:跑完即止,成功即 Completed
----------------------------------------------------------------
cat > job-pi.yaml <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  completions: 3          # 总共要成功 3 次
  parallelism: 2          # 同时跑 2 个
  backoffLimit: 4         # 失败重试上限
  activeDeadlineSeconds: 300
  ttlSecondsAfterFinished: 600   # 完成后 10 分钟自动清理
  template:
    spec:
      restartPolicy: Never        # Job 必须 Never 或 OnFailure
      containers:
      - name: pi
        image: perl:5.38
        command: ["perl","-Mbignum=bpi","-wle","print bpi(2000)"]
EOF
kubectl apply -f job-pi.yaml
kubectl get job,pod -l job-name=pi -w
kubectl logs job/pi

【重要】Job 的 Pod 模板里 restartPolicy 只能是 Never / OnFailure,
写成 Always 会被 apiserver 直接拒绝。

6.2 CronJob:定时
----------------------------------------------------------------
cat > cj-backup.yaml <<'EOF'
apiVersion: batch/v1
kind: CronJob
metadata:
  name: backup
spec:
  schedule: "0 2 * * *"              # 每天 02:00(集群内是 UTC,注意时区)
  timeZone: "Asia/Shanghai"          # 1.27+ 支持,低版本需给容器设 TZ
  concurrencyPolicy: Forbid          # 上一次没跑完就跳过本次
  startingDeadlineSeconds: 300
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: backup
            image: busybox:1.36
            command: ["sh","-c","echo backup at $(date); sleep 10"]
EOF
kubectl apply -f cj-backup.yaml
kubectl get cj

# 立刻手动触发一次(不用等到 2 点)
kubectl create job --from=cronjob/backup backup-manual
kubectl get po -l job-name=backup-manual
kubectl logs -f job/backup-manual

坑:CronJob 的 schedule 按 controller-manager 时区(一般 UTC),
表达式别写错;concurrencyPolicy 默认 Allow,会叠加跑。


----------------------------------------------------------------
七、方式 7:静态 Pod(kubelet 直管)
----------------------------------------------------------------
定义:YAML 放在节点的 /etc/kubernetes/manifests/,kubelet 直接读取并启动,
不经过 apiserver 调度(但 apiserver 里能看到一个 mirror pod)。

# 在节点上(需要 root)
mkdir -p /etc/kubernetes/manifests
cat > /etc/kubernetes/manifests/static-nginx.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: static-nginx
  namespace: kube-system
spec:
  containers:
  - name: nginx
    image: nginx:1.27
    ports: [{containerPort: 80, hostPort: 8080}]
  hostNetwork: true
EOF
# 删除文件即停止该 Pod
mv /etc/kubernetes/manifests/static-nginx.yaml /tmp/

典型用户:kube-apiserver、etcd、kube-controller-manager、kube-scheduler
(kubeadm 装的集群,这四个就在 master 的 manifests 目录里)。
注意:改了静态 Pod 的 YAML,kubelet 会检测到并重建;改坏了 apiserver 起不来,
只能登机器修 —— 生产改之前先备份,并想好回滚。


----------------------------------------------------------------
八、调度约束:把 Pod 放到你想放的地方
----------------------------------------------------------------
1) nodeSelector(最简单)
      nodeSelector:
        disktype: ssd

2) 节点亲和(软硬兼备)
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:   # 硬性
            nodeSelectorTerms:
            - matchExpressions:
              - {key: zone, operator: In, values: [az-a, az-b]}
          preferredDuringSchedulingIgnoredDuringExecution:  # 软性加权
          - weight: 80
            preference:
              matchExpressions:
              - {key: gpu, operator: In, values: ["true"]}

3) Pod 亲和/反亲和(把同类打散或聚拢)
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels: {app: web}
            topologyKey: kubernetes.io/hostname

4) 污点与容忍
# 给节点打污点
kubectl taint nodes node1 dedicated=gpu:NoSchedule
# Pod 加容忍才能上去
      tolerations:
      - key: dedicated
        operator: Equal
        value: gpu
        effect: NoSchedule
# 取消污点
kubectl taint nodes node1 dedicated=gpu:NoSchedule-

5) 常用排错
kubectl get pod <p> -o wide            # 落在哪个节点
kubectl describe pod <p> | tail -20    # FailedScheduling 会写明原因
kubectl describe node <n> | grep -E 'Taints|Allocated'


----------------------------------------------------------------
九、一个"像样"的完整部署(把上面串起来)
----------------------------------------------------------------
cat > web-full.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels: {app: web}
spec:
  replicas: 3
  revisionHistoryLimit: 5
  selector:
    matchLabels: {app: web}
  strategy:
    type: RollingUpdate
    rollingUpdate: {maxSurge: 1, maxUnavailable: 0}
  template:
    metadata:
      labels: {app: web}
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 101
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels: {app: web}
      containers:
      - name: web
        image: nginx:1.27
        ports: [{containerPort: 80, name: http}]
        envFrom:
        - configMapRef: {name: app-cfg}
        resources:
          requests: {cpu: 100m, memory: 128Mi}
          limits:   {cpu: 500m, memory: 256Mi}
        readinessProbe:
          httpGet: {path: /, port: http}
          initialDelaySeconds: 3
          periodSeconds: 5
        livenessProbe:
          httpGet: {path: /, port: http}
          initialDelaySeconds: 15
          periodSeconds: 10
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: false
          capabilities:
            drop: ["ALL"]
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector: {app: web}
  ports:
  - {name: http, port: 80, targetPort: http}
  type: ClusterIP
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web
spec:
  minAvailable: 2
  selector:
    matchLabels: {app: web}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
spec:
  ingressClassName: nginx
  rules:
  - host: web.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web
            port: {number: 80}
EOF

kubectl create configmap app-cfg --from-literal=TZ=Asia/Shanghai
kubectl apply -f web-full.yaml
kubectl get deploy,rs,po,svc,ingress,pdb -l app=web
kubectl rollout status deploy/web
kubectl port-forward svc/web 8080:80
# 另一终端:curl -I localhost:8080
kubectl delete -f web-full.yaml


----------------------------------------------------------------
十、排障速查(部署阶段的 90% 问题)
----------------------------------------------------------------
症状                         先看什么 / 常见原因
---------------------------  -------------------------------------------
Pending                      describe → FailedScheduling:资源不足、污点、
                             亲和性不满足、PVC 未绑定、镜像太大
ContainerCreating 久          拉镜像中(大镜像/网络慢)、挂载卷失败、
                             CNI 异常 → describe 看 Events
ImagePullBackOff/ErrImagePull 镜像名/标签错、私有仓库需 imagePullSecrets、
                             containerd 未配 registry mirror
CrashLoopBackOff             容器自身启动即退出 → logs --previous、
                             检查 command/args、配置、权限
Running 但 not Ready          readinessProbe 失败 → exec 进容器 curl 探针路径
                             describe 看 "Readiness probe failed"
OOMKilled                    limits.memory 太小,或内存泄漏 → kubectl top /
                             describe 看 Last State
Evicted                      节点磁盘/内存压力 → describe node conditions
Service 访问不通              selector 与 Pod labels 不匹配、targetPort 错、
                             EndpointSlice 为空、NetworkPolicy 拦了
滚动更新卡住                  rollout status 看卡在哪个 Pod;必要时
                             rollout undo 快速止血

三条黄金命令,按顺序打:
kubectl get pod -o wide
kubectl describe pod <name>        # 只看最后 Events 段
kubectl logs <name> [--previous] [-c 容器名]


----------------------------------------------------------------
十一、Day 02 自检清单
----------------------------------------------------------------
[ ] 能用 kubectl run --dry-run=client -o yaml 生成脚手架
[ ] 手写一个带 requests/limits + 三种探针的 Deployment
[ ] 完成一次 set image → 观察滚动 → undo 回滚
[ ] 用 NodePort 或 port-forward 真访问到服务
[ ] 起一个 DaemonSet,确认 Pod 数 = 节点数
[ ] 起一个 StatefulSet,验证删除 Pod 后 PVC 仍在、名字仍是 -0/-1/-2
[ ] 起一个 CronJob 并手动触发一次
[ ] 能解释 Pending / ImagePullBackOff / CrashLoopBackOff 的区别与查法

下一篇预告:Day 03 —— Service 与 Ingress 网络模型、CoreDNS 解析链路、
NetworkPolicy 隔离;Day 04 —— 存储 PV/PVC/StorageClass;Day 05 ——
配置与密钥、RBAC 与安全基线;Day 06 —— 监控日志与故障演练。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。