AI Infra 训练营
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • CI/CD 实战(MySQL+Go+Vue)
  • HPA/Ingress/Hubble 实战
  • 面试速查 + 真实踩坑
  • Day 0 · 新手接管 Runbook
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • CI/CD 实战(MySQL+Go+Vue)
  • HPA/Ingress/Hubble 实战
  • 面试速查 + 真实踩坑
  • Day 0 · 新手接管 Runbook
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
  • Day 0 · 环境与硬件

    • Day 0 新手现场接管 Runbook:先看懂,再动手
    • Day 0:5 节点裸 Ubuntu → K8s 装机基线
  • Week 1:K8s 内核 + 周边基础设施

    • Day 1:3 CP HA 集群 + CNI 选型 + DNS 调优
    • Day 2:控制面 deep dive + etcd 内核 + chaos drill
    • Day 3:CRD + Operator (kubebuilder 从 0 写)
    • Day 4:Longhorn 存储 + Cilium 二探(Hubble / NetworkPolicy / L7)
    • Day 5:PVC 在线扩容 + K8s 安全基线(RBAC / PSA / Secret 加密 / Kyverno)
    • Day 6:调度策略 + Prometheus / Loki 观测栈
    • Day 7:Harbor 私有镜像 + ArgoCD GitOps + Cilium WireGuard
  • Week 2:制品 + GitOps + AI Infra + 综合

    • Day 8:k3s 单节点 + NVIDIA Device Plugin + vLLM 跑 Qwen2.5-3B
    • Day 8(attempt 1):跨 WAN GPU 加入主集群(失败复盘)
    • Day 8:AlertManager 真接入 + PrometheusRule 实战
    • Day 8:集群内 CI 闭环 — Gitea + Jenkins + Kaniko
    • Day 9:Triton 多框架推理 + DCGM 跨集群可观测 + vLLM 实测
    • Day 10:MIG 硬切片 + AWQ 量化 + HPA Custom Metrics
    • Day 11:AI 业务端到端 —— chainlit + GitOps + 跨 WAN vLLM
    • Day 12:灾难恢复 + 生产事故注入
    • Day 13:LLM Operator + 多集群联邦 + Ambient Mesh + RAG
    • Day 14:CKA / CKS 真题演练 + 14 天技术栈横向汇总

Day 14:CKA / CKS 真题演练 + 14 天技术栈横向汇总

走到第 14 天,集群已经是一个能装、能跑、能挂、能救的完整平台:3 CP HA + 2 worker + 1 个跨 WAN GPU 节点,Cilium / Longhorn / Harbor / ArgoCD / Prometheus / Loki / Hubble / vLLM / Kyverno / 自研 Operator 全部就位。

这一天不再装新东西。把 CKA / CKS 真题按 Day 0-13 的能力切片走一遍,每题给场景 + 解法 + 卡点,再把 14 天踩过的真坑横向汇总。

整篇分三段:

  • CKA 真题段:12 题,每题对应一个 Day 的实操
  • CKS 真题段:8 题,全部对应 Day 5(安全)+ Day 12(灾备)+ 准入控制
  • 全程踩坑横向汇总:5 类反复出现的真坑泛化

14 天技术栈一张表

主集群 5 节点 K8s 1.30(10.0.24.0/24)+ 跨 WAN 1 个 GPU 节点 k3s(A800-40G):

层组件关键事实
Control Plane(Day 0-1)3 CP + per-node HAProxy + etcd raftVIP 漂不动只能 per-node;etcd 单点损毁 2 分钟恢复(实测)
CNI / DNS(Day 1)Cilium 1.16 eBPF + Hubble + node-local-dns私有 IDC 必须 VXLAN Always;DNS 9 跳解析链
存储(Day 4)Longhorn 5 节点 634GB 池,3-replicaVolumeSnapshot 秒级 + PVC 在线扩容
安全(Day 5)RBAC + PSA restricted + Kyverno + Secret aescbcetcd 直查从明文到密文实测
调度(Day 6)HPA + KEDA + topologySpreadConstraintsmetrics-server 自签集群要 --kubelet-insecure-tls
CI/CD(Day 7-8)Harbor + Jenkins + Kaniko + ArgoCDGitOps selfHeal + prune;Kaniko 无 docker daemon
GPU / 推理(Day 8-10)NVIDIA Device Plugin + DCGM + vLLMMIG 3×2g.10gb;Qwen2.5-3B 30 并发 2901 tok/s P99 1.3s
Operator(Day 11/13)kubebuilder v4:SimpleApp + LLMService CRDReconcile 三件套 + Finalizer + OwnerReference
Mesh / 联邦(Day 11/13)Cilium ClusterMesh 设计 + SSH tunnel 实操跨 WAN VXLAN UDP 4789 不通走 tunnel
观测(Day 6/9)kube-prometheus-stack + Loki + AlertManager28 target up;DCGM 跨 WAN 19 个 GPU metric
灾备(Day 12)etcd snapshot + OOM / Cilium / cert SOP5 类生产事故 SOP 实测注入恢复

Part 1:CKA 真题演练(12 题)

CKA 限时 2 小时 17 题,平均每题 < 8 分钟,所有命令必须能盲打。每题给场景 + 解法 + 卡点。

Q1(Day 1)kubeadm join token 丢了

新 worker 准备加入,但当时 kubeadm init 输出的 join 命令已经过期或丢失。

# 在任一 cp 上重新生成(包含 token + ca-cert-hash)
kubeadm token create --print-join-command

# token 默认 TTL 24h。要永久 token(不推荐生产):
kubeadm token create --ttl 0 --print-join-command

卡点:默认 token 24 小时过期,老 token 已失效;不要去 kubeadm token list 然后手拼命令,--print-join-command 一行出全部。

Q2(Day 0)节点 NotReady,runtime is down

# 1. kubelet 是否在跑
systemctl status kubelet
journalctl -u kubelet --since '5min ago' | tail -50

# 2. containerd 是否 healthy
systemctl status containerd
crictl info | jq '.status'

# 3. cgroup driver 一致性(最常见根因)
grep SystemdCgroup /etc/containerd/config.toml   # 应为 true
grep cgroupDriver /var/lib/kubelet/config.yaml   # 应为 systemd

# 4. 重启
systemctl restart containerd kubelet

卡点:kubelet 和 containerd 的 cgroup driver 不一致是 90% 的根因(一个 systemd,一个 cgroupfs)。Pod 会启动但行为异常(OOM 触发不对、limit 不生效)。

Q3(Day 1)节点优雅下线

kubectl cordon <node>                                       # 阻止新 Pod
kubectl drain <node> \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --timeout=300s                                            # evict 现有
# 维护操作(重启 / 升级内核 / 换盘)
kubectl uncordon <node>                                     # 恢复调度

卡点:忘 --ignore-daemonsets,drain 会卡在 DaemonSet(kube-proxy / cilium-agent)。--delete-emptydir-data 必须显式给,否则有 emptyDir 的 Pod drain 不动。

Q4(Day 0/1)集群升级 1.30 → 1.31

每台 cp 先升(apt unhold kubeadm → apt install kubeadm=1.31.* → kubeadm upgrade apply v1.31.0 → apt install kubelet kubectl → restart kubelet → hold 回来),然后每台 worker drain → kubeadm upgrade node → 升 kubelet → uncordon。

# cp 上的关键三行
apt-mark unhold kubeadm && apt-get install -y kubeadm=1.31.0-*
kubeadm upgrade apply v1.31.0
apt-get install -y kubelet=1.31.0-* kubectl=1.31.0-* && systemctl restart kubelet

卡点:K8s 一次只能 +1 minor。1.30 → 1.32 必须经过 1.31,否则 apiserver 起不来。apt-mark hold 是 Day 0 装的时候就该做的(防 unattended-upgrades 自动跨版本升)。

Q5(Day 6)Pod Pending:5 Insufficient cpu

kubectl get pod <p> -o jsonpath='{.spec.containers[*].resources.requests}'
kubectl describe nodes | grep -A5 'Allocated resources'

三种解法:减 Pod request(最快)/ 加节点 / PriorityClass + preemption 抢占。

卡点:kubectl top 看实际使用,describe nodes 的 Allocated 才是已 request。Pending 的根因永远是 request 不是实际使用。

Q6(Day 6)Pod 跨 zone 均匀分布

spec:
  template:
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule         # 严格,不行就 Pending
        labelSelector:
          matchLabels: { app: my-app }

卡点:ScheduleAnyway 是软约束,要严格用 DoNotSchedule。labelSelector 必须跟 Pod label 匹配,否则约束全没生效(skew 永远 0)。

Q7(Day 8/10)Pod 必须跑在 GPU 节点上

spec:
  template:
    spec:
      nodeSelector:
        accelerator: gpu
      tolerations:
      - key: nvidia.com/gpu
        operator: Exists
        effect: NoSchedule
      containers:
      - name: app
        image: vllm/vllm-openai:latest
        resources:
          limits:
            nvidia.com/gpu: 1

卡点:nodeSelector + toleration 缺一不可。漏 toleration 会 Pending,漏 nodeSelector 会跑到 CPU 节点。

Q8(Day 6)HPA TARGETS unknown

# 99% 是 metrics-server 没装或证书问题
kubectl get pod -n kube-system | grep metrics-server
kubectl logs -n kube-system <metrics-server>
# 常见:x509: cannot validate certificate

# 修:加 --kubelet-insecure-tls
kubectl patch deploy metrics-server -n kube-system --type=json \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

卡点:自签集群 metrics-server 默认验证 kubelet cert 会失败。--kubelet-insecure-tls 是 kubeadm 集群的标准配方。

Q9(Day 5)namespace CPU 总额限制

apiVersion: v1
kind: ResourceQuota
metadata: { name: cpu-limit, namespace: my-ns }
spec:
  hard:
    requests.cpu: "5"
    limits.cpu: "10"
---
apiVersion: v1
kind: LimitRange
metadata: { name: default-limits, namespace: my-ns }
spec:
  limits:
  - type: Container
    default:        { cpu: "200m", memory: "256Mi" }
    defaultRequest: { cpu: "100m", memory: "128Mi" }

卡点:只设 ResourceQuota 不行 —— Pod 没显式写 request 会被 Quota 拒绝。LimitRange 补默认值,两个一起用才完整。

Q10(Day 1/4)Service connection refused

按下面顺序查:

kubectl get endpoints <svc>           # 空 → selector 不匹配 / Pod readiness 没过
kubectl get networkpolicy -n <ns>     # 有没有把流量拦了
sudo ipvsadm -L -n | grep <svc-ip>    # kube-proxy 是否生成了规则
kubectl logs -n kube-system <kube-proxy>

卡点:80% 的 Service 不通是 Endpoint 为空(selector / label 不匹配 或 readiness 没过)。先查 kubectl get endpoints 再查别的。

Q11(Day 4)NetworkPolicy deny-all 后 DNS 也挂了

# deny-all 之后必须显式 allow DNS egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-dns, namespace: my-ns }
spec:
  podSelector: {}
  policyTypes: [Egress]
  egress:
  - to:
    - namespaceSelector:
        matchLabels: { kubernetes.io/metadata.name: kube-system }
      podSelector:
        matchLabels: { k8s-app: kube-dns }
    ports:
    - { protocol: UDP, port: 53 }
    - { protocol: TCP, port: 53 }

卡点:default deny + 白名单的模式必须首先放 DNS,否则 Pod 连解析都做不到,所有请求 fail。CKS 必考。

Q12(Day 4/5)PVC Pending

kubectl describe pvc <pvc>                              # 看 events
kubectl get sc                                          # SC 存在 + default?
kubectl get pod -n longhorn-system | grep -E 'manager|csi'
kubectl get nodes.longhorn.io -n longhorn-system        # node Schedulable?

卡点:Longhorn 节点的 Schedulable: false 是最隐蔽的(节点磁盘空间不够 / replica 数对不上 / disk tag 不匹配)。kubectl describe pvc 的 event 是入口。

PVC 扩容后 df 没变大:

kubectl get pvc <pvc> -o yaml | grep -A2 conditions
# FileSystemResizePending → 重启 Pod 让 kubelet 触发 resize2fs
kubectl delete pod <pod-using-pvc>

Part 2:CKS 真题演练(8 题)

Q13(Day 5)只读 SA:get / list pods + configmaps

apiVersion: v1
kind: ServiceAccount
metadata: { name: only-view, namespace: my-ns }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: pod-cm-reader, namespace: my-ns }
rules:
- apiGroups: [""]
  resources: ["pods", "configmaps"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: only-view-binding, namespace: my-ns }
subjects:
- kind: ServiceAccount
  name: only-view
  namespace: my-ns
roleRef:
  kind: Role
  name: pod-cm-reader
  apiGroup: rbac.authorization.k8s.io

验证:

kubectl auth can-i list pods -n my-ns \
  --as=system:serviceaccount:my-ns:only-view
# yes

kubectl auth can-i delete pods -n my-ns \
  --as=system:serviceaccount:my-ns:only-view
# no

卡点:Role 是 namespace 级,ClusterRole 是集群级。题目「在 my-ns 只读」必须用 Role + RoleBinding,写 ClusterRole + ClusterRoleBinding 等于权限放大。

Q14(Day 5)PSA restricted 让 Pod 合规

namespace 标 pod-security.kubernetes.io/enforce: restricted 后 Pod 起不来,补齐 5 项:

spec:
  securityContext:                       # Pod 级
    runAsNonRoot: true
    runAsUser: 65534
    seccompProfile: { type: RuntimeDefault }
  containers:
  - securityContext:                     # 容器级
      allowPrivilegeEscalation: false
      runAsNonRoot: true
      readOnlyRootFilesystem: true
      capabilities: { drop: [ALL] }

卡点:5 项必须全配齐,缺一个就拒。seccompProfile 是 v1.25+ 引入的 restricted 硬要求,老题库经常漏。

Q15(Day 5)Secret at-rest 加密

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: [secrets]
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: <base64 32-byte 密钥>      # head -c 32 /dev/urandom | base64
  - identity: {}                            # 兜底读旧明文

五步:三 cp 都放配置文件 → apiserver static pod manifest 加 --encryption-provider-config + volume mount → 滚动重启三 cp → rewrite 历史 Secret → 直查 etcd 验证。

# 4. rewrite 历史 Secret(关键,否则只对新写的生效)
kubectl get secrets -A -o json | kubectl replace -f -

# 5. 直查 etcd 看到 "k8s:enc:aescbc:v1:key1:" 前缀就对
etcdctl get /registry/secrets/default/my-secret --print-value-only | hexdump -C | head

卡点:

  • providers 列表里 aescbc 在前 identity 在后,否则新写的 Secret 不加密。
  • 步骤 4 必须做,否则历史 Secret 仍是明文,加密只对新写的生效。
  • 三 cp 滚动重启时,第一个重启的 cp 加密了新 Secret,其他 cp 还在用 identity 读 —— 这就是 identity 兜底为什么必须留。

Q16(Day 5)Kyverno:禁用 image:latest

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: { name: disallow-latest-tag }
spec:
  validationFailureAction: Enforce
  rules:
  - name: require-image-tag
    match:
      any:
      - resources: { kinds: [Pod] }
    validate:
      message: "Image must have an explicit tag (not :latest, not empty)"
      pattern:
        spec:
          containers:
          - image: "!*:latest & *:*"

卡点:!*:latest & *:* 是 Kyverno anchor 语法 —— 第一段排除 :latest,第二段要求必须有 :tag。只写第一段会让 nginx(无 tag)通过。

Q17(Day 11/12)apiserver cert 即将过期

kubeadm certs check-expiration         # 看每张 cert 到期日
kubeadm certs renew all                # 续所有
systemctl restart kubelet              # 让 static pod 重启
# 或在每 cp 上:
kill $(pgrep kube-apiserver)           # static pod 自重启

卡点:renew all 续的是 kubeadm 管理的那些 cert(apiserver / controller-manager / scheduler / etcd / front-proxy)。kubelet cert 不在里面 —— kubelet 用 RotateCertificates: true 自动续,要在 /var/lib/kubelet/config.yaml 里开。

Q18(Day 12)etcd 单节点数据全毁

剩 2/3 quorum 时 5 步:

# 1. 在活的 cp 上移除死成员
etcdctl member remove <dead-id>

# 2. 死节点清 data dir
mv /var/lib/etcd /var/lib/etcd-broken-backup

# 3. add learner
etcdctl member add <cp-name> --peer-urls=https://<cp-ip>:2380 --learner

# 4. 改 etcd manifest:--initial-cluster-state=existing + 完整三节点列表
# 5. mv manifest 回原位,kubelet 拉起 etcd,同步完自动 promote voter

卡点:必须先 remove 再 add,否则同名同 ID 冲突。--learner 是 etcd 3.4+ 的安全机制(同步完才有投票权,避免 quorum 计算错误)。实测含同步 2 分钟。

Q19(Day 12)整个 cluster 死了,只剩 snapshot

# 1. 在 cp-1 restore(单节点新集群)
ETCDCTL_API=3 etcdctl snapshot restore snapshot.db \
  --name k8s-cp-1 \
  --initial-cluster k8s-cp-1=https://10.0.24.31:2380 \
  --initial-cluster-token new-token \
  --initial-advertise-peer-urls https://10.0.24.31:2380 \
  --data-dir /var/lib/etcd-new

# 2. mv /var/lib/etcd-new → /var/lib/etcd
# 3. 改 etcd manifest:--initial-cluster-state=new(注意是 new,不是 existing)
# 4. kubelet 拉起单节点 etcd
# 5. cp-2 / cp-3 kubeadm reset → 重新 join

卡点:从 snapshot 起的是新 cluster(token 都换了),--initial-cluster-state=new 不是 existing。其他 cp 必须 reset 后 rejoin,不能直接 start。

Q20(Day 4/5)NetworkPolicy 多租户隔离

team-a 的 Pod 只能访问 team-a 的 Service + DNS:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: team-a-isolation, namespace: team-a }
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]
  ingress:
  - from:
    - podSelector: {}                       # 同 ns
  egress:
  - to:
    - podSelector: {}                       # 同 ns
  - to:                                     # DNS
    - namespaceSelector:
        matchLabels: { kubernetes.io/metadata.name: kube-system }
      podSelector:
        matchLabels: { k8s-app: kube-dns }
    ports: [{ protocol: UDP, port: 53 }]

卡点:podSelector: {} 是「所有 Pod in this namespace」,不是全集群。多个 egress rule 之间是 OR 关系。


Part 3:全程踩坑横向汇总

14 天里反复出现的 5 类坑,泛化到「面试讲故事」可以套的程度。

坑 1:IDC source-IP filter 系列

私有 IDC 网关 / 交换机做 source-IP 白名单 + MAC-IP 静态绑定,跟 K8s 网络抽象冲突。

Day现象根因修复
Day 1.Bkeepalived VIP 10.0.24.100 其他节点 100% 丢包网关只放分配给本机的 IPper-node HAProxy 替代 VIP
Day 1.ECalico IPIPCrossSubnet 跨节点 Pod ping 全丢,tcpdump 看到包源 IP 是裸 Pod IP网关看到 src 不在白名单直接 dropCalico encap 切 VXLAN Always
Day 9GPU 节点跨 WAN 加入主集群 Cilium VXLAN UDP 4789 不通公网防火墙阻 UDP改 SSH tunnel + Service + Endpoints

通用规律:私有 IDC 默认就用 overlay 封装(VXLAN),别想着省那 50B overhead。源 IP 必须是节点 IP,IDC 才放行。

坑 2:etcd ghost member

kubeadm join --control-plane 失败后,etcd 里残留半成品 member。

etcdserver: can only promote a learner member which is in sync with leader

etcdctl member list:
| 85047be2f29d3191 | unstarted |          | https://10.0.24.29:2380 |   ← ghost
| 953b8e085f0fea77 | started   | k8s-cp-3 | ...                       |

修复:

etcdctl member remove 85047be2f29d3191
# 然后才能重新 join

通用规律:kubeadm 任何半失败都要看 etcd member list 有没有残留。kubeadm reset 不清 etcd member。

坑 3:BIRD / iptables / proto bird 残留路由

CNI 切换(Calico → Cilium)后跨节点带宽掉到 26 Mbps,根因是 Calico BIRD 学到的路由没清。

ip route | grep 'proto bird'
# 10.244.111.192  via 10.0.24.31  dev eth0  proto bird   ← 残留

ip route flush proto bird

CNI 切换的完整 cleanup checklist:

  • /etc/cni/net.d/* 配置
  • /opt/cni/bin/* 二进制
  • iptables 自定义链(iptables-save | grep cali)
  • ip route flush proto bird(Calico)
  • 接口:vxlan.calico / tunl0 / cali* veth / cilium*

漏任何一项都让新 CNI 半坏不坏。

坑 4:sshd_config.d override + remote heredoc 引号

两个看似无关其实同根的 SSH 坑:

4.1 Day 0 改 /etc/ssh/sshd_config 后 PubkeyAuthentication 仍然 no —— Ubuntu 22 的 /etc/ssh/sshd_config.d/*.conf override 主配置。

修复:

for f in /etc/ssh/sshd_config.d/*.conf; do
  [ -f "$f" ] && sed -i 's/^PubkeyAuthentication.*/PubkeyAuthentication yes/' "$f"
done
sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
sshd -t && systemctl reload sshd

4.2 远程 heredoc 用双引号,$(hostname) 被本地 shell 提前展开:

# 错的:本地 eval 后远端跑空命令
ssh root@$ip "bash -s" <<EOF
echo $(hostname)        # 本地 hostname 被插入
EOF

# 对的:单引号包 EOF,远端真正展开
ssh root@$ip 'bash -s' <<'EOF'
echo $(hostname)        # 远端 hostname
EOF

通用规律:SSH 配置改完必看 .d/*.conf,远程脚本永远用 <<'EOF' 单引号。

坑 5:containerd v3 schema 字段名

2024 后 Docker 官方源装的 containerd 是 2.x,config schema 升到 v3:

  • v1/v2:sandbox_image = 'registry.k8s.io/pause:3.9'
  • v3:sandbox = 'registry.k8s.io/pause:3.10.1'

照搬老教程 sed 改 sandbox_image 完全没匹配。config_path 在 v3 里是 '' 默认值,只能改值不能 append(append 会 duplicate key,containerd 起不来)。

# 看实际字段名再改
containerd config dump | grep -E "pause|sandbox"
sed -i "s|config_path = ''|config_path = '/etc/containerd/certs.d'|" /etc/containerd/config.toml

通用规律:装 containerd 后先 dump 看真实 schema,不要按记忆 sed。


面试常见题(覆盖 14 天最高频)

Q1:你接手一台裸机准备装 K8s,前 30 分钟做什么?

四步:

  1. 探虚拟化(systemd-detect-virt,LXC 直接换机器)+ cgroup 版本
  2. SSH 免密 + 关密码登录 + 关 unattended-upgrades(防自动跨 minor 升 kubelet 炸集群)
  3. 装 chrony 同步时间(etcd raft 对时差 > 1s 敏感)
  4. hostname + /etc/hosts + 关无用服务(multipathd / iscsid / apport)

Q2:K8s HA 控制面方案怎么选?

3 种:

  • VIP + keepalived / kube-vip ARP:小厂 IDC 几乎不让漂(source-IP filter)
  • 外部 LB:云上简单,私有要多 1 台
  • per-node HAProxy:localhost:16443 转 3 CP,kubespray / talos / k0s 默认

实战:私有 IDC ip addr add VIP 后其他节点 Destination Host Unreachable(L3 失败,不是 L2 ARP)= 网关白名单 block,只能 per-node HAProxy。

Q3:跨节点 Pod 不通你怎么排查?

5 步:

  1. kubectl get pods -o wide 看 IP 分配
  2. kubectl exec pod1 -- ping <podN-ip> 看连通性
  3. ip route | grep <pod-cidr> 看 host 路由表
  4. tcpdump -i eth0 host <对端节点> 看真实包到没到 + 检查源 IP
  5. 关键线索:源 IP 是裸 Pod IP(没封装)→ CNI encap 配错 → 切 VXLAN

故事:Calico IPIPCrossSubnet 在私有 IDC 被 source-IP filter drop,tcpdump 看到包源 IP 没封装是关键。

Q4:node-local-dns 为什么生产必装?

4 个收益:

  1. 延迟:本地 hairpin < 0.5ms vs kube-proxy DNAT 2-5ms(5-10×)
  2. conntrack 压力:NOTRACK 绕过 conntrack,大集群关键
  3. CoreDNS QPS 降 50-90%:大部分 query 节点本地命中
  4. 故障容忍:CoreDNS 全挂 cache 内 name 还能解析

部署坑:forward 上游必须指 kube-dns-upstream Service,不能指 kube-dns —— 否则 node-local-dns 自己 forward 给自己(都 bind 了 10.96.0.10),死循环 timeout。

Q5:etcd 灾备你怎么做?2 分钟恢复怎么测的?

snapshot save cron 每 4 小时 + 异地同步。

单节点损毁(剩 2/3 quorum,实测 2 分钟):member remove → 清 data dir → member add --learner → 改 manifest --initial-cluster-state=existing → 拉起,etcd 自动 promote voter。

整集群死从 snapshot 救:etcdctl snapshot restore + --initial-cluster-state=new(不是 existing),其他 cp kubeadm reset 后重 join。

Q6:Secret at-rest 加密怎么真生效?

4 步:

  1. 写 EncryptionConfiguration(aescbc 在前,identity 兜底读旧明文)
  2. 三 cp 放配置 + apiserver static pod 加 --encryption-provider-config + volume mount
  3. 滚动重启三 cp
  4. kubectl get secrets -A -o json | kubectl replace -f - rewrite 历史 Secret,否则只对新写的生效

验证:etcdctl get /registry/secrets/... 看到 k8s:enc:aescbc:v1:key1: 前缀。

Q7:HPA 该用什么指标?vLLM 推理服务怎么扩?

普通应用:CPU / memory util。

LLM 推理:

  • 不能用 CPU util:永远低,GPU 才是瓶颈
  • 不能用 GPU util:永远 80%+,扩到 max 还是高
  • 应该用 vllm:num_requests_waiting(队列深度,KEDA 接 Prometheus scaledobject)

Q8:CNI 切换需要清哪些残留?

K8s 层:Installation CR / DaemonSet / Deployment / Service / RBAC / CRD / Namespace(注意 finalizer,可能要 kubectl replace --raw .../finalize 强删)。

节点层(容易漏):

  • /etc/cni/net.d/* 配置
  • /opt/cni/bin/* 二进制
  • iptables 自定义链
  • ip route flush proto bird(Calico 学到的路由)
  • 网络接口(vxlan.calico / tunl0 / cali* veth)

故事:装 Cilium 后跨节点带宽只有 Calico 的 20%,查路由表发现 BIRD proto 路由跟 Cilium 路由共存导致包走错下一跳,ip route flush proto bird 后反超 Calico 14%。


延伸

  • CKA / CKS 真实考试:限时 2h,17-20 题,可查 kubernetes.io 文档但来不及现搜。把上面 20 题的 yaml 盲打练到 < 1 分钟。
  • vimrc / kubectl alias:考前必备 k=kubectl + kn=kubectl -n + set ts=2 sw=2 et for yaml。
  • 进一步:CKA 之后看 kubectl debug / ephemeral container / Gateway API(替代 Ingress)。CKS 之后看 falco runtime detection / OPA Gatekeeper(Kyverno 替代品对比)/ supply chain(cosign + SLSA)。
在 GitHub 上编辑此页
Prev
Day 13:LLM Operator + 多集群联邦 + Ambient Mesh + RAG