后端服务容器化部署从 Docker 到 K8s 指南

chinaz
chinaz 正式会员超兽战士
发布于 2026-10-07 09:56 ·1 浏览 ·0 回复

后端服务容器化部署的正确路径是三步走:Docker 把服务打成标准化镜像 → docker-compose 在单机验证完整依赖 → Kubernetes 接管编排、扩缩容和滚动发布。跳过 Docker 直接写 YAML 上 K8s,是把「镜像怎么做、进程怎么退出、配置怎么注入」这些坑一次性堆到生产环境,返工成本最高。

为什么不能跳过 Docker 直接用 K8s?

结论:K8s 只负责「调度和编排镜像」,它不解决镜像本身的质量问题,所以镜像层必须先做扎实。

K8s 的 Deployment 里所有字段都是围绕「一个符合容器规范的镜像」设计的:探针探的是镜像里的进程,资源限制管的是镜像里的进程,滚动更新换的也是镜像 tag。如果镜像里还写着 RUN nohup java -jar app.jar &、日志写本地文件、用 root 用户启动,这些问题在 K8s 上会被放大——Pod 反复重启、日志丢失、权限报错,而且排查成本远高于本地。

单机验证阶段用 docker-compose up -d 起服务 + 数据库 + Redis,跑通一次完整链路和一版灰度流量,再写 K8s YAML,顺序不要倒过来。

Docker 镜像怎么做才适合上 K8s?

结论:用多阶段构建 + 非 root 用户 + 固定 tag,镜像体积能压到 20MB 级别,启动时间从秒级降到百毫秒级。

以 Go 服务为例:

FROM golang:1.22-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/server

FROM alpine:3.19
RUN apk add --no-cache tzdata && adduser -D -u 10001 appuser
ENV TZ=Asia/Shanghai
COPY --from=build /out/app /app
USER 10001
EXPOSE 8080
ENTRYPOINT ["/app"]

三个硬性要求:一是基础镜像带 tzdata 并设 TZ,否则容器内时间是 UTC,日志和定时任务全部错 8 小时;二是创建 uid 10001 的非 root 用户,K8s 的 securityContext.runAsNonRoot: true 才不会拒绝启动;三是镜像 tag 用 git commit sha 而不是 latest,latest 在滚动更新时因为 imagePullPolicy: Always 会导致无法确定回滚版本。

构建命令:docker build -t registry.example.com/order-api:$(git rev-parse --short HEAD) .,推送后再在 Deployment 里引用这个 tag。

从 docker run 到 Deployment:资源限制和探针怎么设?

结论:requests 按服务常驻内存的 1.2 倍设,limits 按 2 倍设;探针三个参数(initialDelaySeconds、periodSeconds、failureThreshold)必须按实际启动耗时调。

一个可直接改用的片段:

resources:
  requests: { cpu: "100m", memory: "128Mi" }
  limits:   { cpu: "500m", memory: "512Mi" }
livenessProbe:
  httpGet: { path: /healthz, port: 8080 }
  initialDelaySeconds: 10
  periodSeconds: 10
  failureThreshold: 3
readinessProbe:
  httpGet: { path: /readyz, port: 8080 }
  initialDelaySeconds: 5
  periodSeconds: 5

注意 CPU limit 会触发 CFS 节流,Java 服务建议只设 requests 不设 CPU limits,并用 -XX:MaxRAMPercentage=75.0 让 JVM 感知容器内存上限;Go 服务设 limits 没问题。liveness 探针不要指向依赖数据库的接口,否则数据库抖动会导致全部 Pod 被重启。

Deployment、Service、Ingress 的关系是什么?

结论:Deployment 管 Pod 副本,Service 提供集群内固定虚拟 IP 和负载均衡,Ingress 负责把集群外流量按域名和路径转发进来,三者缺一不可。

调用链是:外部请求 → Ingress Controller(如 nginx-ingress)→ Service(ClusterIP)→ 选中的 Pod。Service 的 selector 必须和 Deployment 的 spec.template.metadata.labels 完全一致,标签写错是最常见的「Service 找不到后端」原因。集群内服务互调用 Service 名即可,例如 http://order-api.default.svc.cluster.local:8080。

部署命令就是一行:kubectl apply -f deploy/ -n prod,用 kubectl get pods -o wide -n prod 确认状态为 Running 且就绪探针通过(READY 列显示 1/1)。

配置和密钥怎么注入?

结论:非敏感配置用 ConfigMap,密码和密钥用 Secret,并且以「挂载文件」方式注入而不是环境变量。

环境变量方式注入的 ConfigMap 更新后不会自动生效,必须 kubectl rollout restart deployment/order-api -n prod 才能重启加载;挂载成文件则 K8s 会自动同步更新内容(有约 1 分钟延迟),配合应用内的配置热加载更省事。Secret 用 kubectl create secret generic db-secret --from-literal=password=xxx 创建,不要写进 Git 仓库的 YAML 明文里。

优雅退出和滚动更新怎么做?

结论:设置 terminationGracePeriodSeconds: 30 加 preStop 延迟 5 秒,能消除滚动更新期间 5xx 报错。

Pod 被删除时,K8s 会先摘掉 Endpoints 再发 SIGTERM。由于 kube-proxy 规则同步有延迟,应用如果在收到 SIGTERM 后立刻退出,仍会有几秒请求打到已关闭的进程上。做法是:

lifecycle:
  preStop:
    exec: { command: ["sleep", "5"] }
terminationGracePeriodSeconds: 30

应用侧要监听 SIGTERM,先停止接收新请求,等存量请求处理完再退出。

滚动更新默认策略是 maxSurge: 25%、maxUnavailable: 25%,3 副本时约等于先起 1 个新 Pod、停 1 个旧 Pod。出问题回滚一条命令:kubectl rollout undo deployment/order-api -n prod;查看历史版本用 kubectl rollout history deployment/order-api -n prod。

最后一条经验:容器日志一律写 stdout/stderr,不要写文件,用 kubectl logs -f --tail=200 deploy/order-api -n prod 采集,否则日志系统接不上。

整体路径就是:镜像做小做规范 → compose 验证 → Deployment 配好资源与探针 → Service/Ingress 打通流量 → ConfigMap/Secret 管配置 → 优雅退出 + 滚动更新兜住发布风险。这六步走完,容器化部署才算是真正落地。

版权声明:本文来自 GJ站长论坛《后端服务容器化部署从 Docker 到 K8s 指南》
原文链接:https://www.gj0.com/thread-218.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~