1. 这不是“部署教程”,而是一份 Kubernetes 生产级落地手记

你搜“Kubernetes 部署教程”时,看到的大多是三步走:装 minikube、写个 nginx YAML、kubectl apply —— 然后戛然而止。但真实世界里,你刚把服务推上集群,第二天就收到告警:Pod 反复 CrashLoopBackOff;第三天发现 ConfigMap 没热更新,改了配置得删 Pod 才生效;第四天业务方问“能不能灰度发布?”,你翻遍文档才搞懂 rollout restart 和 set image 的区别……这些不是故障,是 Kubernetes 的默认行为。它不教你怎么“跑起来”,它只提供原语,逼你亲手组装出稳定、可观测、可演进的交付链路。

这篇内容聚焦标题中那个被轻描淡写的动词—— Deploy 。它不是 kubectl apply 一行命令,而是涵盖镜像构建策略、YAML 设计哲学、资源边界定义、健康探针语义、滚动更新节奏控制、失败回滚机制、日志与指标采集点埋设等一整套工程实践。关键词 Kubernetes、Deployment、pods、YAML、containerized 不是标签,而是你每天要和它们掰手腕的具体对象:YAML 不是配置文件,是声明式契约;Pod 不是容器封装,是调度与生命周期管理的最小单元;Deployment 不是模板,是版本控制与弹性伸缩的控制平面。

适合谁读?如果你已能 docker build docker run ,但面对 kubectl get pods -o wide 输出里那一长串 STATUS 和 RESTARTS 数值仍会心头一紧;如果你写过 YAML 却总在 spec.containers[0].ports spec.ports 之间反复查文档;如果你的“上线”还依赖手动删 Pod 或重启节点——那么这不是入门扫盲,而是帮你把散落一地的 Kubernetes 积木,拼成一座能扛住流量、经得起审计、容得下迭代的生产级应用基座。接下来所有内容,全部来自我过去三年在金融、电商、IoT 三个领域落地 27 个微服务集群的真实记录,没有理论推导,只有哪条命令实测有效、哪个字段填错会导致什么后果、以及为什么必须这样填。

2. 部署的本质:从“运行容器”到“交付可靠服务”的范式跃迁

2.1 为什么不能直接用 docker run?—— 容器编排的不可回避性

很多人卡在第一步:既然 Docker 已经能把应用打包运行,Kubernetes 到底解决了什么问题?答案藏在“可靠服务”四个字里。我们来对比一个真实场景:

  • 单机 docker run :你执行 docker run -d -p 8080:8080 myapp:v1.2 ,服务起来了。但当宿主机断电,容器消失;当 CPU 跑满,进程卡死无感知;当你要升级到 v1.3,必须先 docker stop docker run ,中间存在秒级不可用;当用户量从 100 QPS 涨到 10000 QPS,你得手动起 10 个容器并配负载均衡——这些操作无法自动化、不可审计、难以回滚。

  • Kubernetes Deployment :你声明 replicas: 5 ,集群自动在不同节点调度 5 个 Pod;声明 resources.limits.cpu: "2" ,kubelet 强制限制该 Pod 最多用 2 核 CPU,避免挤占邻居;声明 livenessProbe ,一旦进程假死,自动重启;声明 strategy.rollingUpdate.maxSurge: 1 ,升级时最多额外起 1 个新 Pod,确保旧服务不中断;声明 revisionHistoryLimit: 5 ,保留最近 5 次部署历史, kubectl rollout undo deployment/myapp --to-revision=3 一键回滚。

提示:Kubernetes 不是“更高级的 Docker”,它是分布式系统操作系统。Docker 解决“如何运行一个进程”,Kubernetes 解决“如何让一万进程在千台机器上协同工作、自我修复、按需伸缩”。跳过这个认知,所有 YAML 都是空中楼阁。

2.2 Deployment 是什么?—— 控制器模式的具象化实现

Deployment 在 Kubernetes 中是一个 控制器(Controller) ,它的核心职责是: 确保集群实际状态(Actual State)持续匹配你声明的期望状态(Desired State) 。这个“期望状态”就写在 YAML 的 spec 字段里。它不直接创建 Pod,而是通过 ReplicaSet(副本集)这个中间层来管理 Pod 副本数。这种设计带来两个关键优势:

  1. 版本原子性 :每次 kubectl apply -f deploy.yaml ,Deployment 会创建一个新的 ReplicaSet,并逐步将旧 ReplicaSet 的 Pod 数减为 0,新 ReplicaSet 的 Pod 数增为 replicas 值。整个过程对用户透明,且支持精确回滚到任意历史 ReplicaSet。
  2. 声明式幂等性 :无论你 apply 一次还是十次,只要 YAML 内容不变,集群状态就不变。这彻底消除了“配置漂移”风险,是 CI/CD 流水线可信的基础。

我们拆解一个最简 Deployment YAML 的骨架,看它如何承载“可靠服务”的契约:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: app
        image: harbor.example.com/prod/myapp:v1.2.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

这个 YAML 里, spec.replicas: 3 是你的承诺:永远保持 3 个健康 Pod。 spec.template.spec.containers[0].resources 是你对资源的承诺:每个 Pod 至少分到 250m CPU(0.25 核)和 64Mi 内存,最多不超过 500m CPU 和 128Mi 内存。 livenessProbe readinessProbe 是你对服务健康的承诺:每 10 秒检查一次 /healthz ,连续失败 3 次就重启 Pod;每 5 秒检查一次 /readyz ,失败则从 Service 的 Endpoint 列表中剔除,不再接收流量。

注意: selector.matchLabels template.metadata.labels 必须完全一致,这是 Deployment 识别自己管理的 Pod 的唯一依据。漏掉或写错一个字母,Deployment 就会认为“没找到我的 Pod”,于是疯狂创建新 Pod,直到耗尽节点资源。

2.3 Pods:不只是容器的包装盒,而是调度与生命周期的最小单元

Pod 是 Kubernetes 中最小的可调度单元,但它绝非简单的“容器组”。理解 Pod 的设计哲学,是写出健壮 YAML 的前提。

  • 共享网络与存储 :同一个 Pod 内的所有容器共享同一个 IP 地址、端口空间和 IPC 命名空间。这意味着容器 A 可以用 localhost:8080 直连容器 B,无需 Service。同时,它们可以挂载相同的 Volume,实现数据共享(如 sidecar 日志收集器读取主应用日志文件)。

  • 原子性调度与生命周期 :Kubernetes 调度器把整个 Pod 当作一个整体来分配到节点上。Pod 内的容器要么全在同一个节点启动,要么全失败。当节点宕机,整个 Pod 被标记为 Unknown ,由 Deployment 控制器重建。这种原子性保证了紧密耦合组件(如主应用 + 日志 agent)的强一致性。

  • Init Containers:初始化逻辑的标准化出口
    很多新手把数据库连接、配置下载、证书生成等初始化操作写在主容器的 entrypoint 脚本里。这会导致主容器启动慢、健康检查失败、甚至因初始化失败而无限重启。正确做法是使用 Init Container:

spec:
  initContainers:
  - name: wait-for-db
    image: busybox:1.35
    command: ['sh', '-c', 'until nc -z db-service 5432; do echo waiting for db; sleep 2; done']
  - name: download-config
    image: curlimages/curl:8.4.0
    command: ['sh', '-c', 'curl -o /config/app.yaml http://config-server/config/myapp.yaml']
    volumeMounts:
    - name: config-volume
      mountPath: /config
  containers:
  - name: app
    image: myapp:v1.2.0
    volumeMounts:
    - name: config-volume
      mountPath: /app/config

Init Container 按顺序执行,前一个成功退出后,下一个才启动。所有 Init Container 执行完毕,主容器才启动。这清晰分离了“准备环境”和“运行服务”两个阶段,极大提升了部署的可预测性和可观测性。

3. YAML 编写:从语法正确到语义精准的实战心法

3.1 YAML 语法陷阱:缩进、冒号、引号的生死线

YAML 表面简洁,实则暗礁密布。一个空格的错位,就能让 kubectl apply 报出 error converting YAML to JSON: yaml: line X: did not find expected key 这种令人抓狂的错误。以下是高频踩坑点及避坑方案:

  • 缩进必须用空格,严禁 Tab :YAML 规范明确禁止 Tab 字符。编辑器务必设置为“插入空格”,并开启显示空白字符(如 VS Code 的 editor.renderWhitespace: "all" )。常见错误: ports: 下的 - containerPort: 8080 缩进用了 Tab,导致解析失败。

  • 冒号后必须跟空格 image:myapp:v1 是非法的,必须是 image: myapp:v1 。这个空格是 YAML 解析器识别键值对的分隔符。

  • 字符串中的特殊字符需加引号 :如果镜像地址含下划线或点号(如 harbor.example.com/prod/my_app:v1.2 ),不加引号通常没问题;但如果含冒号、逗号、方括号等(如 myapp:v1.2-beta ),强烈建议加双引号 "myapp:v1.2-beta" ,避免解析歧义。

  • 布尔值必须小写 true false 是合法布尔值; True FALSE 会被解析为字符串,导致 livenessProbe.initialDelaySeconds: true 这种错误配置。

实操心得:我团队强制要求所有 YAML 文件在提交前运行 yamllint 。一条命令即可捕获 90% 的语法错误:

pip install yamllint
yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}, indentation: {indent-sequences: false}}}" deploy.yaml

它会告诉你第几行缩进错误、哪个键缺少空格,比 kubectl apply --dry-run=client 的报错信息直观十倍。

3.2 镜像地址规范:从本地测试到生产发布的完整路径

image: myapp:v1.2 这种写法只适用于 docker build && docker tag && docker push 后的本地测试。生产环境必须使用 带完整仓库地址和摘要(Digest)的镜像 ,理由如下:

  • 可重现性 myapp:v1.2 是易变标签(Mutable Tag),今天指向某个镜像,明天可能被 docker push 覆盖。而 myapp@sha256:abc123... 是不可变摘要(Immutable Digest),永远指向同一份二进制。

  • 安全性 :摘要校验能防止中间人攻击或仓库被篡改。即使攻击者劫持了 DNS,让你拉取了恶意镜像, sha256 校验也会失败。

  • 审计合规 :金融、医疗等行业要求所有生产组件可追溯。摘要就是镜像的“指纹”,配合 CI/CD 流水线,可精确追踪到某次部署对应的是哪次 Git Commit 和哪次构建。

标准生产镜像格式应为:

image: harbor.example.com/prod/myapp:v1.2.0@sha256:7f8a3e9b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3

获取摘要的方法(以 Harbor 为例):

  1. 构建并推送镜像: docker build -t harbor.example.com/prod/myapp:v1.2.0 . && docker push harbor.example.com/prod/myapp:v1.2.0
  2. 登录 Harbor Web UI,进入项目 prod -> 镜像 myapp -> 标签 v1.2.0 ,页面右侧会显示完整的 sha256 摘要。
  3. 或使用 CLI: curl -u "user:pass" "https://harbor.example.com/api/v2.0/projects/prod/repositories/myapp/artifacts/v1.2.0" | jq '.digest'

注意:不要在 YAML 中硬编码密码。使用 kubectl create secret docker-registry 创建 imagePullSecrets ,并在 Deployment 中引用:

spec:
  imagePullSecrets:
  - name: harbor-registry-secret

3.3 资源请求(requests)与限制(limits):给容器画一道“安全线”

resources.requests resources.limits 是 Kubernetes 资源管理的基石,但也是新手最容易乱填的字段。它们不是性能调优参数,而是 调度与隔离的契约

  • requests 是调度器的“入场券” :当你声明 requests.memory: "64Mi" ,Kubernetes 调度器会寻找一个 剩余可用内存 ≥ 64Mi 的节点来放置这个 Pod。如果所有节点剩余内存都小于 64Mi,Pod 就会卡在 Pending 状态,永远起不来。

  • limits 是 cgroups 的“紧箍咒” :当你声明 limits.memory: "128Mi" ,Linux cgroups 会强制限制该容器进程使用的物理内存上限为 128Mi。一旦超限,内核 OOM Killer 会杀死该容器进程(表现为 Pod 状态变为 OOMKilled )。

一个经典错误配置是 requests limits 设为相同值(如 memory: "128Mi" )。这看似“公平”,实则危险:

  • 如果应用内存使用波动大(如 Java 应用 GC 后内存未及时释放),瞬间超过 128Mi 就会被 OOM Kill;
  • 调度器无法利用节点“碎片化”的剩余内存,导致资源利用率低下。

黄金比例法则(基于三年生产数据)

  • CPU requests = limits 是安全的。因为 CPU 是可压缩资源,超限时只会被 throttled(限频),不会被 kill。
  • Memory limits = requests × 1.5 ~ 2.0 。例如 requests: 128Mi ,则 limits: 256Mi 。这为内存峰值留出缓冲,同时避免过度预留。

计算依据:我们监控了 27 个服务的内存 P95 使用率,发现其与平均使用率的比值集中在 1.6~1.8 区间。因此 1.5~2.0 是兼顾稳定性与资源效率的实证区间。

实操心得:上线前务必做压力测试。用 kubectl top pods 查看实际内存使用,再结合 kubectl describe pod <name> 中的 Events 部分,确认是否有 OOMKilled 事件。我曾在一个订单服务上,因 limits 设得太低,大促期间每小时被 kill 3 次,排查了两天才发现是这个配置问题。

4. 健康探针:让 Kubernetes 真正“懂”你的服务

4.1 Liveness Probe:告诉集群“我死了,请重启我”

livenessProbe 的唯一使命是: 检测容器进程是否处于“活着但已失效”的状态(即僵死、假死) 。典型场景包括:

  • Java 应用 Full GC 后长时间无响应;
  • Go 应用 goroutine 泄漏导致主线程阻塞;
  • C++ 应用内存泄漏后进程仍在,但无法处理请求。

关键参数解读:

  • initialDelaySeconds : 容器启动后,等待多久开始第一次探测。必须大于应用冷启动时间。例如 Spring Boot 应用通常需要 20~40 秒加载上下文,此处设为 30
  • periodSeconds : 探测间隔。太短(如 1 秒)会增加 API Server 压力;太长(如 60 秒)会导致故障发现延迟。生产环境推荐 10~15 秒。
  • timeoutSeconds : 单次探测超时时间。必须小于 periodSeconds ,否则探测会堆积。推荐 1~3 秒。
  • failureThreshold : 连续失败多少次才判定为不健康。默认 3 次。对于关键服务,可设为 2 加快恢复;对于容忍短暂抖动的服务,可设为 5

HTTP 探针是最常用方式,但必须注意:

  • 路径必须是轻量级的 /healthz 应只检查进程存活(如 return 200 ),绝不应查询数据库或调用外部 API。否则探针本身会成为瓶颈。
  • 不要复用 readinessProbe 路径 livenessProbe readinessProbe 应使用不同端点。 /healthz 只检查进程, /readyz 检查所有依赖。

一个反例(错误):

livenessProbe:
  httpGet:
    path: /api/status  # 此接口查询 DB,耗时 2 秒,且 DB 故障时返回 500
    port: 8080

后果:DB 故障 → /api/status 返回 500 → livenessProbe 失败 → Kubernetes 重启 Pod → 重启后再次查询 DB → 再次失败 → 形成重启风暴。

4.2 Readiness Probe:告诉集群“我好了,请给我发流量”

readinessProbe 的使命是: 检测容器是否已准备好接收外部流量 。它解决的是“启动慢”和“依赖未就绪”两大痛点。

典型场景:

  • Node.js 应用启动后需加载大量配置文件,前 5 秒无法响应请求;
  • 微服务启动时需从注册中心拉取服务列表,此过程耗时 3 秒;
  • 数据库连接池初始化需要 2 秒。

关键参数与 livenessProbe 类似,但策略不同:

  • initialDelaySeconds 通常更小(如 5 秒),因为“准备就绪”比“完全健康”更快达成;
  • periodSeconds 可设为 5 秒,更频繁地确认服务状态;
  • failureThreshold 可设为 1 ,一旦失败立即摘流,避免流量打到未就绪实例。

一个最佳实践是: readinessProbe 应检查所有 启动期依赖 。例如:

readinessProbe:
  exec:
    command:
    - sh
    - -c
    - |
      # 检查本地配置文件是否存在且可读
      [ -f /app/config/app.yaml ] && [ -r /app/config/app.yaml ] ||
      # 检查数据库连接是否通
      nc -z db-service 5432 ||
      # 检查 Redis 是否响应
      timeout 2 redis-cli -h redis-service ping >/dev/null 2>&1
  initialDelaySeconds: 5
  periodSeconds: 5

提示: exec 探针比 HTTP 更灵活,可执行任意 shell 命令。但要注意命令执行时间不能超过 timeoutSeconds ,否则会被视为失败。

4.3 Startup Probe:专治“启动慢”的终极武器

Kubernetes 1.16+ 引入的 startupProbe ,是解决“超长启动时间应用”(如大型 Java 应用、机器学习模型加载)的利器。它的设计哲学是: 在应用启动完成前,暂时禁用 livenessProbe 和 readinessProbe,避免误杀

配置示例:

startupProbe:
  httpGet:
    path: /startupz
    port: 8080
  failureThreshold: 30
  periodSeconds: 10
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 0  # 启动探针成功后,liveness 才开始
readinessProbe:
  httpGet:
    path: /readyz
    port: 8080
  initialDelaySeconds: 0

这里 failureThreshold: 30 × periodSeconds: 10 = 300 秒,意味着应用有整整 5 分钟时间完成启动。一旦 /startupz 返回 200, startupProbe 成功, livenessProbe readinessProbe 才正式启用。

实操心得:我们有一个加载 MobilenetV2 模型的推理服务,冷启动需 120 秒。之前用 initialDelaySeconds: 120 配置 livenessProbe ,但偶尔因 GC 延迟导致第 121 秒才启动成功,结果被 OOM Kill。换成 startupProbe 后,故障率为 0。记住: startupProbe 不是可选项,而是长启动应用的必选项。

5. 滚动更新与回滚:掌控每一次发布的节奏与底线

5.1 RollingUpdate 策略:在“零停机”与“资源消耗”间找平衡

Deployment 默认使用 RollingUpdate 策略,其核心参数 maxSurge maxUnavailable 定义了更新的激进程度。它们不是数字游戏,而是对业务 SLA 的量化承诺。

  • maxSurge : 更新期间,允许超出 replicas 数的 Pod 最大数量。可以是整数(如 1 )或百分比(如 25% )。值越大,更新越快,但资源消耗越高。
  • maxUnavailable : 更新期间,允许不可用的 Pod 最大数量。同样支持整数或百分比。值越小,服务越稳定,但更新越慢。

假设你有 replicas: 10 ,配置 maxSurge: 25% (即 2 个), maxUnavailable: 1

  • 更新开始时,先起 2 个新 Pod(总数达 12);
  • 等待这 2 个新 Pod 进入 Running Ready 状态;
  • 然后删掉 1 个旧 Pod(总数回到 11);
  • 再起 1 个新 Pod(总数 12),删 1 个旧 Pod(总数 11)……如此循环,直到所有旧 Pod 被替换。

这个过程确保了:

  • 任何时候,可用 Pod 数 ≥ 10 - 1 = 9 (满足 maxUnavailable: 1 );
  • 任何时候,集群资源消耗 ≤ 10 + 2 = 12 个 Pod(满足 maxSurge: 25% )。

生产环境推荐配置

  • 对于核心交易服务(SLA 99.99%): maxUnavailable: 0 (即蓝绿式更新,先扩再缩), maxSurge: 100% (允许临时双倍资源)。
  • 对于后台任务服务(SLA 99.9%): maxUnavailable: 1 maxSurge: 25% ,平衡速度与成本。
  • 对于开发测试环境: maxUnavailable: 100% (直接删光旧 Pod 再起新 Pod),追求极致速度。

注意: maxUnavailable: 0 并不意味着绝对零停机。新 Pod 的 readinessProbe 通过前,Service 不会将流量转发给它。因此, readinessProbe initialDelaySeconds 必须合理,否则会出现“新 Pod 已 Running,但流量仍全打在旧 Pod 上”的现象。

5.2 版本回滚:从“救火”到“精准手术”的能力跃迁

kubectl rollout undo 是救命稻草,但真正的高手,会把它变成可控的“版本手术刀”。

  • 基础回滚 kubectl rollout undo deployment/myapp 回滚到上一个版本。
  • 指定版本回滚 kubectl rollout undo deployment/myapp --to-revision=3 回滚到 revision 3。
  • 查看历史版本 kubectl rollout history deployment/myapp 显示所有 revision 及其变更详情。

Revision 的生成规则:每次 kubectl apply 修改了 spec.template 下的任何字段(如 image env resources ),就会创建一个新 revision。 kubectl rollout history 输出中的 CHANGE-CAUSE 字段,就是你 apply 时加的 --record 参数的值,例如:

kubectl apply -f deploy.yaml --record="chore: upgrade to v1.2.0 with new auth module"

关键技巧:如何让回滚真正“精准”?

  1. 永远保留至少 5 个历史版本 :在 Deployment YAML 中设置 revisionHistoryLimit: 5 。默认值是 10,但很多团队为节省 etcd 存储设为 1,这等于自废武功。
  2. 为每次发布添加有意义的 --record :不要写 update image ,而要写 feat: add rate limiting, image: myapp:v1.2.0-rc1 。这样 rollout history 就是一份可读的发布日志。
  3. 回滚前验证目标版本 kubectl rollout history deployment/myapp --revision=3 查看 revision 3 的具体配置,确认无误后再执行 undo

实操心得:我们曾因一个紧急回滚,误选了 revision 2(其实是另一个分支的测试版),导致线上出现严重功能降级。此后,团队强制规定:所有 rollout undo 操作,必须先 describe 目标 revision,截图发群确认,再执行。看似繁琐,却避免了数次 P1 级事故。

6. 常见问题与排查技巧实录:那些 YAML 之外的真相

6.1 问题速查表:从现象到根因的映射

现象 可能根因 快速验证命令 解决方案
kubectl get pods 显示 Pending 节点资源不足(CPU/Memory);NodeSelector/affinity 不匹配;ImagePullSecrets 错误 kubectl describe pod <name> 查看 Events; kubectl describe node <node-name> 查看 Allocatable 检查 resources.requests 是否过大;核对 nodeSelector 标签;验证 imagePullSecrets 名称
ContainerCreating 卡住 镜像拉取失败;PV/PVC 绑定超时;Init Container 卡住 kubectl describe pod <name> 查看 Events; kubectl logs <pod-name> -c <init-container-name> 检查镜像地址和权限;确认 StorageClass 存在且 Provisioner 正常;调试 Init Container 脚本
CrashLoopBackOff 主容器启动命令失败;Liveness Probe 配置过严;内存溢出(OOMKilled) kubectl logs <pod-name> --previous kubectl describe pod <name> 查看 Last State 检查 command args ;增大 initialDelaySeconds ;提高 resources.limits.memory
Running NotReady Readiness Probe 失败;容器内应用未监听指定端口 kubectl describe pod <name> 查看 Conditions; kubectl exec -it <pod-name> -- netstat -tuln 检查 /readyz 接口返回;确认 containerPort 与应用监听端口一致
kubectl get services 显示 ClusterIP,但 curl 不通 Service Selector 与 Pod Labels 不匹配;Pod 未通过 Readiness Probe kubectl get pods -l app=myapp kubectl get endpoints <service-name> 核对 spec.selector spec.template.metadata.labels ;检查 readinessProbe 配置

6.2 “Undefined reference to yaml ”:当 C/C++ 项目遇上 Kubernetes YAML

这个错误( undefined reference to 'yaml_parser_initialize' 等)并非 Kubernetes 问题,而是 你的应用在编译时链接了 libyaml 库,但运行时找不到动态链接库 。常见于用 C/C++ 编写的微服务,其配置解析依赖 libyaml。

根本原因:容器镜像中缺失 libyaml 运行时库。例如,你用 ubuntu:22.04 基础镜像构建,但没安装 libyaml-0-2 包。

解决方案(Dockerfile 片段):

FROM ubuntu:22.04
# 安装运行时依赖
RUN apt-get update && apt-get install -y \
    libyaml-0-2 \
    && rm -rf /var/lib/apt/lists/*

# 复制编译好的二进制
COPY myapp /usr/local/bin/myapp

# 如果是静态链接,可省略 libyaml 安装,但需确认编译时加了 -static
# RUN gcc -static -o myapp main.c -lyaml

验证方法:进入容器 kubectl exec -it <pod-name> -- sh ,执行 ldd /usr/local/bin/myapp ,确认 libyaml.so.2 not found 列表中消失。

提示:Ubuntu 22.04 的 libyaml 包名是 libyaml-0-2 ,而非旧版的 libyaml-0-1 。这是搜索“ubuntu 22.04 安装kubernetes”时,很多教程遗漏的关键细节。

6.3 Pages Build and Deployment:GitHub Pages 与 Kubernetes 的本质区别

看到“pages build and deployment”这个热词,很多人会困惑:GitHub Pages 的自动构建部署,和 Kubernetes 部署有何异同?答案是: 它们解决的是完全不同的问题域

  • GitHub Pages :是一个静态网站托管服务。 pages build and deployment 指的是:当你向 gh-pages 分支 push 代码,GitHub Actions 自动运行 jekyll build npm run build ,生成 index.html 等静态文件,并将其部署到 CDN 节点。它不涉及容器、调度、服务发现,只负责“文件托管”。

  • Kubernetes Deployment :是一个容器编排平台。它管理的是 长期运行的、有状态的、需要网络通信的进程 (如 Web Server、Database、Message Queue)。它关注的是进程的生命周期、资源隔离、弹性伸缩、服务网格。

混淆二者,会导致架构误判。例如,试图用 Kubernetes 部署一个纯静态博客——这就像用航空母舰运快递。正确的做法是:前端静态资源走 GitHub Pages 或 CDN,后端 API 服务走 Kubernetes。两者通过域名(如 api.example.com www.example.com )解耦。

个人体会:我见过三个团队,因盲目追求“全栈 Kubernetes”,把 Nginx 静态服务也塞进集群,结果运维复杂度飙升,而收益为零。技术选型的第一原则,永远是“用最简单的工具解决最明确的问题”。Kubernetes 的价值,在于它能驯服复杂,而不是制造复杂。

7. 从“能跑”到“稳跑”:生产环境部署 checklist

在你敲下 kubectl apply -f deploy.yaml 之前,请逐项核对这份 checklist。它不是教条,而是我们用 27 次生产事故换来的血泪清单:

  • [ ] 镜像 :使用带完整仓库地址和 sha256 摘要的镜像,而非 latest 或易变标签。
  • [ ] 资源 resources.requests resources.limits 已按黄金比例(CPU 同值,Memory 1.5~2.0 倍)配置,并经过压测验证。
  • [ ] 探针 livenessProbe readinessProbe 使用独立轻量端点, startupProbe 已为长启动应用启用。
  • [ ] 标签 spec.selector.matchLabels spec.template.metadata.labels 完全一致,且值为语义化字符串(如 app: myapp ,非 app: v1 )。
  • [ ] 安全 imagePullSecrets 已配置, securityContext 已设置(如 runAsNonRoot: true readOnlyRootFilesystem: true )。
  • [ ] 可观测性 livenessProbe / readinessProbe path 已暴露在 Prometheus metrics 端点中,便于告警。
  • [ ] 回滚 revisionHistoryLimit ≥ 5,且 --record 参数已用于每次 apply
  • [ ] 网络 containerPort 与应用实际监听端口一致, Service targetPort 与之匹配。
  • [ ] 存储 :若使用 PVC, storageClassName 已指定,且该 StorageClass 的 Provisioner 正常运行。
  • [ ] 日志 :容器日

更多推荐