全部文章 笔记与思考

K8s 容器云平台服务部署流程:从 Dockerfile 打包镜像到上线

以公司内部 Kubernetes 容器云平台为背景,梳理从服务准备、Dockerfile 编写、镜像构建与推送,到创建工作负载、配置服务入口、发布验证和常见排错的完整流程。

K8s 容器云平台服务部署流程:从 Dockerfile 打包镜像到上线

最近在公司内部 K8s 容器云平台上部署服务,最大的感受是:平台页面上的“创建应用”只是最后一步,真正决定部署是否顺利的,是前面这条链路:代码能稳定构建 → Dockerfile 写得正确 → 镜像能在本地跑起来 → 镜像能推到仓库 → K8s 能拉取镜像并通过探针验证服务健康。

K8s 容器云部署流程

这篇文章按一次真实上线的顺序来讲,并以中国电信云 CeakE 容器服务 CKE 的控制台为例展示具体操作。不同平台的页面名称可能不同,但只要底层是 Kubernetes,工作负载、Service、Ingress、探针和滚动更新等概念都能一一对应。

flowchart LR A[代码仓库] --> B[编写 Dockerfile] B --> C[本地构建镜像] C --> D[本地运行验证] D --> E[推送镜像仓库] E --> F[容器云平台创建工作负载] F --> G[配置 Service 和 Ingress] G --> H[发布验证和回滚预案]

一、部署前先确认什么

先把需要的信息列清楚,后面就不会在平台页面里边填边猜。

准备项 需要确认什么 常见示例
服务启动方式 本地如何启动,监听哪个端口 java -jar app.jar、./server、npm start,端口 8080
Dockerfile 是否在项目根目录,是否能完整构建运行环境 Dockerfile、.dockerignore
镜像仓库 仓库地址、项目名、账号权限 registry.example.com/team/order-api
平台命名空间 服务要部署到哪个项目 / Namespace team-dev、team-test、team-prod
配置来源 哪些是环境变量,哪些是密钥 APP_ENV、数据库地址、Token
网络入口 只集群内访问,还是需要外部域名 ClusterIP、Ingress、Route、网关
资源规格 CPU、内存、磁盘、实例数 requests: 200m/256Mi,limits: 1/1Gi
健康检查 平台用哪个接口判断服务可用 /healthz、/actuator/health

容器云平台通常会把 Kubernetes 的对象包装成更好填的表单。它们之间大概是这个关系:

平台页面里的名字 Kubernetes 对象 主要作用
应用 / 工作负载 Deployment 管理 Pod 副本、滚动更新和回滚
实例 / 容器组 Pod 真正运行容器的最小单元
服务 Service 给一组 Pod 提供稳定的集群内访问地址
路由 / 域名 / 网关 Ingress 或平台自定义入口 把外部 HTTP/HTTPS 流量转进集群
配置 ConfigMap 注入非敏感配置
密钥 Secret 注入密码、Token、证书等敏感信息
镜像拉取凭据 imagePullSecret 让节点有权限从私有镜像仓库拉镜像

一句话:平台最终还是把你填的内容转成 Kubernetes 资源,只是帮你隐藏了一部分 YAML。

二、重点:根据 Dockerfile 打包镜像

Dockerfile 是镜像的构建说明书。K8s 不会拿你的源代码直接运行,它只会根据 Deployment 里的 image 字段去镜像仓库拉一个镜像,然后把这个镜像启动成容器。

所以不要把“部署失败”一上来就归因于平台。很多问题其实发生在镜像阶段:

  • 镜像里没有复制到正确的产物;
  • 容器启动命令写错;
  • 服务监听了 127.0.0.1,导致 Pod 外部访问不到;
  • 本地能访问的配置文件没有放进镜像,也没有通过环境变量注入;
  • 镜像架构不对,比如在 ARM 机器上构建了 linux/arm64,但集群节点是 linux/amd64;
  • 私有仓库权限或 Tag 写错,导致 ImagePullBackOff。

1. 先理解 Dockerfile 常用指令

指令 作用 注意点
FROM 指定基础镜像 尽量使用明确版本,不要只写 latest
WORKDIR 设置容器内工作目录 后续 COPY、RUN、CMD 都会基于它执行
COPY 把构建上下文里的文件复制进镜像 优先用 COPY,除非确实需要 ADD 的额外能力
RUN 构建镜像时执行命令 常用于安装依赖、编译代码、清理缓存
ARG 构建期变量 构建完成后默认不会保留为运行时环境变量
ENV 运行期环境变量默认值 不要把密码写进 ENV
EXPOSE 声明容器监听端口 只是元数据,不等于真正开放端口
USER 指定运行用户 生产环境建议不要用 root 跑应用
ENTRYPOINT 固定启动入口 适合固定主进程
CMD 默认启动参数或命令 可以被 docker run 后面的参数覆盖

镜像构建还有一个容易被忽略的概念:构建上下文。下面命令最后的 . 不是装饰,它表示把当前目录作为构建上下文发给 Docker:

1
docker build -t registry.example.com/team/order-api:1.0.0 .

如果项目目录里有 .git、node_modules、target、日志、临时文件,都会影响构建速度,甚至把不该进入镜像的文件带进去。因此项目根目录建议放一个 .dockerignore:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.git
.github
.idea
.vscode
node_modules
target
dist
build
*.log
.env
.env.*
README.md

.dockerignore 的目标不是“少传几个文件”这么简单,它直接影响三件事:构建速度、镜像安全、缓存命中率。

2. 一个通用的镜像构建流程

无论是 Go、Java、Node.js 还是 Python 服务,打包镜像都可以按这条路径走:

flowchart TD A[确认本地构建命令] --> B[编写 .dockerignore] B --> C[编写 Dockerfile] C --> D[docker build 构建镜像] D --> E[docker run 本地验证] E --> F[打 Tag] F --> G[docker login] G --> H[docker push 推送仓库] H --> I[平台使用该镜像部署]

先确认项目不用 Docker 也能构建成功:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Java 示例
./mvnw clean package -DskipTests

# Go 示例
go test ./...
go build -o server ./cmd/server

# Node.js 示例
npm ci
npm run build

这一步很重要。Dockerfile 不是用来掩盖项目本身构建问题的。项目在本地基础构建都不稳定,放进 Docker 后只会更难排查。

3. Go 服务 Dockerfile 示例

Go 服务适合用多阶段构建:第一阶段负责编译,第二阶段只保留运行需要的二进制文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# syntax=docker/dockerfile:1

FROM golang:1.23-alpine AS builder
WORKDIR /src

RUN apk add --no-cache git ca-certificates

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/server ./cmd/server

FROM alpine:3.20
WORKDIR /app

RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /out/server /app/server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

USER app
EXPOSE 8080
ENTRYPOINT ["/app/server"]

这个 Dockerfile 里有几个关键点:

写法 为什么这样做
COPY go.mod go.sum ./ 后先 go mod download 依赖文件不变时可以复用缓存,加快后续构建
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 构建适合 Linux x86 节点运行的静态二进制
第二阶段使用 alpine 镜像体积更小,运行层更干净
创建 app 用户并 USER app 避免容器内进程以 root 身份运行
只复制 /out/server 不把源码、编译缓存、测试文件带到最终镜像

构建命令:

1
docker build -t registry.example.com/team/order-api:1.0.0 .

本地运行验证:

1
2
3
4
5
6
docker run --rm --name order-api \
  -p 8080:8080 \
  -e APP_ENV=local \
  registry.example.com/team/order-api:1.0.0

curl http://localhost:8080/healthz

如果本地 curl 都不通,先不要推仓库,也不要上平台。优先检查:

  • 应用实际监听端口是不是 8080;
  • 应用是否监听 0.0.0.0,而不是只监听 127.0.0.1;
  • 启动命令是否正确;
  • 健康检查路径是否真实存在;
  • 必需环境变量是否已经传入。

4. Java Spring Boot 服务 Dockerfile 示例

Spring Boot 服务也建议多阶段构建。构建阶段使用 JDK,运行阶段只使用 JRE。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# syntax=docker/dockerfile:1

FROM eclipse-temurin:21-jdk AS builder
WORKDIR /src

COPY mvnw pom.xml ./
COPY .mvn .mvn
RUN ./mvnw -q -DskipTests dependency:go-offline

COPY src src
RUN ./mvnw -q -DskipTests package

FROM eclipse-temurin:21-jre
WORKDIR /app

RUN useradd -r -u 10001 appuser
COPY --from=builder /src/target/*.jar /app/app.jar

USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

如果服务需要 JVM 参数,不建议把所有参数写死在 Dockerfile 里。可以留给平台环境变量控制:

1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]

平台里再配置:

1
2
JAVA_OPTS=-Xms512m -Xmx512m -XX:+UseG1GC
SPRING_PROFILES_ACTIVE=prod

这里要注意:JAVA_OPTS 本身不是 Kubernetes 标准字段,它只是你的启动脚本或 ENTRYPOINT 读取的环境变量。写了环境变量但启动命令没使用,它就不会生效。

5. Node.js 服务 Dockerfile 示例

Node.js 服务重点是依赖缓存和不要把开发依赖带进运行镜像。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# syntax=docker/dockerfile:1

FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
RUN npm prune --omit=dev

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production

RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

USER app
EXPOSE 3000
CMD ["node", "dist/server.js"]

如果你用的是前端静态站点,最终镜像可以不是 Node,而是 Nginx:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80

6. 镜像 Tag 怎么命名

生产环境不建议只用 latest。latest 看起来方便,但它不可追踪:今天的 latest 和昨天的 latest 可能不是同一个镜像。

更实用的 Tag 规则:

1
2
3
registry.example.com/<团队>/<服务名>:<版本号>
registry.example.com/<团队>/<服务名>:<Git提交短SHA>
registry.example.com/<团队>/<服务名>:<环境>-<构建号>

示例:

1
2
3
docker build -t registry.example.com/pay/order-api:1.4.2 .
docker build -t registry.example.com/pay/order-api:git-a18c3f2 .
docker build -t registry.example.com/pay/order-api:test-20260903-001 .

我更推荐至少保留 Git 提交信息:

1
2
3
4
5
GIT_SHA=$(git rev-parse --short HEAD)
IMAGE=registry.example.com/pay/order-api:${GIT_SHA}

docker build -t ${IMAGE} .
docker push ${IMAGE}

这样平台回滚时,你能明确知道回到哪一次提交,而不是猜某个 Tag 当时指向什么。

如果公司平台支持使用镜像 Digest,生产环境还可以把 image 写成不可变形式:

1
registry.example.com/pay/order-api@sha256:xxxxxxxxxxxxxxxx

Tag 适合人看,Digest 更适合机器保证不可变。

7. 构建架构要和集群节点一致

如果你在 Mac M 系列、ARM 开发机或某些 CI Runner 上构建镜像,而公司 K8s 节点是 x86,那么需要特别关注镜像架构。

查看本地机器架构:

1
2
docker version
docker info

明确构建 linux/amd64 镜像:

1
2
3
4
docker buildx build \
  --platform linux/amd64 \
  -t registry.example.com/team/order-api:git-a18c3f2 \
  --push .

如果平台 Pod 报错类似 exec format error,优先怀疑镜像架构和节点架构不匹配。

8. 登录并推送镜像仓库

构建完成后,先确认本地有镜像:

1
docker images | grep order-api

登录公司镜像仓库:

1
docker login registry.example.com

如果镜像 Tag 还不是仓库地址,需要重新打 Tag:

1
docker tag order-api:local registry.example.com/pay/order-api:git-a18c3f2

推送:

1
docker push registry.example.com/pay/order-api:git-a18c3f2

推送成功后,容器云平台才能从仓库拉到这个镜像。注意:K8s 集群不会使用你本机 Docker 里的镜像。除非是特殊的本地集群,生产平台只会从镜像仓库拉取。

三、在容器云平台创建工作负载

镜像准备好后,才进入平台部署阶段。下面结合控制台截图走一遍完整流程。

1. 进入集群并检查资源状态

在“容器 → 集群列表”中找到目标集群。进入控制台前先确认集群状态为“运行中”,并核对集群类型、版本、节点数和所属租户,避免把服务发布到错误的环境。

CeakE CKE 集群列表

进入集群后的“概览”页会集中展示 Deployment、StatefulSet、DaemonSet、Job、CronJob 和 Pod 数量,以及节点健康状态、CPU、内存和磁盘使用情况。

CeakE CKE 集群资源概览

发布前不要只看“节点健康”,还要同时检查资源水位和事件列表。截图里 CPU、内存都已经有一定占用;如果 requests 配得过大,Pod 可能因为剩余可分配资源不足而一直处于 Pending。事件列表中的 Warning 也应在发布前确认是否与目标项目或网络入口有关。

2. 先选对工作负载类型

左侧“工作负载”菜单并不只有 Deployment。平台把常见控制器和 Pod 分开列出,选择时可以按运行模式判断:

控制台入口 Kubernetes 对象 适用场景
无状态负载 Deployment Web、API 等可横向扩容、实例可替换的服务
守护进程集 DaemonSet 每个节点运行一个实例,如日志采集、监控 Agent
有状态负载 StatefulSet 需要稳定网络标识、顺序发布或持久存储的服务
普通任务 Job 数据迁移、一次性计算等执行完成即退出的任务
定时任务 CronJob 按 Cron 表达式周期执行的任务
容器组 Pod 查看实际运行实例和排错,一般不直接创建长期业务 Pod

无状态负载 Deployment 列表

Deployment 列表中的“容器组(运行/期望)”、副本数和状态可以快速判断发布是否完成。比如 0/1 且状态为 Updating,说明新 Pod 还没有达到期望状态。

守护进程集 DaemonSet 列表

有状态负载 StatefulSet 列表

普通任务 Job 列表

定时任务 CronJob 列表

容器组 Pod 列表

Pod 页面尤其适合排错:它同时展示状态、就绪容器数、重启次数、Pod IP 和所在节点。截图中的 CrashLoopBackOff 和异常高的重启次数就是明确的故障信号,此时应优先查看容器日志、上一轮日志和 Pod 事件,而不是先改 Service 或 Route。

3. 按四步向导创建 Deployment

在“无状态负载”页面点击“新建无状态工作负载”,控制台会依次进入基本信息、容器配置、服务配置和高级配置四个步骤。

第一步:基本信息

新建 Deployment 的基本信息

填写名称、项目(对应 Namespace)、实例数量和描述。测试环境可以先用 1 个实例验证;生产环境通常至少 2 个实例,并结合滚动更新策略保证发布期间仍有可用 Pod。

第二步:容器配置

Deployment 容器镜像与资源配置

容器镜像既可以从平台镜像仓库选择,也可以直接输入完整镜像地址。需要重点核对镜像 Tag、拉取策略以及 vCPU/内存的请求和限制:

  • 使用不可变 Tag 时可以选 IfNotPresent;截图中的默认值是 Always,它适合同一 Tag 可能被覆盖的场景,但生产发布仍建议使用唯一 Tag;
  • 不建议把 CPU、内存请求与限制都保持为“无限制”,至少要设置合理的 requests,生产环境再根据压测和监控设置 limits;
  • 容器名称是 Pod 内的标识,不等于 Deployment 名称;一个 Pod 也可以配置多个容器。

Deployment 环境变量、探针和存储配置

向下滚动可以配置环境变量、启动命令、运行参数、工作目录、健康检查和存储卷。平台把 Kubernetes 的三类探针分别显示为存活检查(Liveness)、就绪检查(Readiness)和启动检查(Startup):

  • Readiness 决定 Pod 是否加入 Service Endpoint,业务服务通常应该配置;
  • Liveness 用于判断是否需要重启容器,不要把偶发抖动的外部依赖放进检查逻辑;
  • 启动慢的应用可使用 Startup Probe,避免还在启动时就被 Liveness 反复重启。

第三步:服务配置

Deployment 向导中的服务配置

这里可以绑定已有 Service,也可以直接新建。若当前工作负载暂时不需要被访问,可以先跳过,后续再从“网络 → 服务”中单独创建。

Deployment 向导中新建 Service

新建 Service 时要填写访问类型、服务端口、容器端口和协议。对普通内部服务优先使用 ClusterIP;NodePort 会在节点上开放端口;LoadBalancer 是否可用取决于云平台和负载均衡配置。最关键的是把 Service 正确绑定到工作负载标签,否则 Service 不会产生 Endpoint。

第四步:高级配置

Deployment 网络与滚动更新配置

高级配置上半部分包括 DNS 策略、额外 Hosts/网络,以及升级策略。常规场景保持 ClusterFirst;升级方式使用 RollingUpdate 时,需要结合副本数设置最大扩容 Pod 数(maxSurge)和最大不可用 Pod 数(maxUnavailable)。单副本服务若允许 maxUnavailable: 1,发布过程中会短暂不可用。

Deployment 调度、标签和镜像密钥配置

高级配置下半部分可以设置节点亲和性、工作负载亲和/反亲和、污点容忍、标签、注解、应用监控和私有镜像密钥。只有在确实需要指定节点、跨节点分散副本或访问私有仓库时再修改;不清楚含义时,先保留平台默认的调度配置。

四步表单最终仍会生成 Kubernetes Deployment。填写时可以用下面这张表快速对照:

配置项 推荐填法 说明
应用名称 order-api 尽量和服务名一致
命名空间 team-test / team-prod 区分环境和团队
镜像地址 registry.example.com/pay/order-api:git-a18c3f2 使用刚推送的镜像
副本数 测试 1,生产 2+ 生产至少两个副本才有滚动更新意义
容器端口 8080 必须和应用实际监听端口一致
环境变量 平台表单或 ConfigMap 普通配置不要写死进镜像
密钥 Secret 密码、Token、证书不要写入 Dockerfile
CPU/内存 先小后调 requests 保证调度,limits 控制上限
健康检查 /healthz 或业务健康接口 Readiness 决定是否接流量
镜像拉取凭据 平台仓库凭据 私有仓库必填

如果用 YAML 表达,一个典型 Deployment 长这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-api
  namespace: team-test
  labels:
    app: order-api
spec:
  replicas: 2
  revisionHistoryLimit: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels:
      app: order-api
  template:
    metadata:
      labels:
        app: order-api
    spec:
      imagePullSecrets:
        - name: registry-secret
      containers:
        - name: order-api
          image: registry.example.com/pay/order-api:git-a18c3f2
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          env:
            - name: APP_ENV
              value: "test"
          envFrom:
            - configMapRef:
                name: order-api-config
            - secretRef:
                name: order-api-secret
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "1"
              memory: "1Gi"
          readinessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 10
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 30
            periodSeconds: 20
            timeoutSeconds: 2
            failureThreshold: 3

这里最值得关注的是三件事:

  1. image 必须是平台节点能访问的完整镜像地址;
  2. containerPort 必须和应用真实监听端口一致;
  3. readinessProbe 不通过时,Pod 即使启动了也不会接收流量。

健康检查接口要轻量、稳定,最好只检查进程是否能处理请求,以及必要的本地依赖是否正常。不要把一堆下游系统都强绑定进去,否则某个非核心依赖抖动,会导致自己的 Pod 被平台摘流量甚至重启。

四、配置服务入口

Deployment 只负责让 Pod 跑起来。要让其他服务或用户访问它,还需要 Service 和 Ingress。

flowchart LR U[用户或上游服务] --> I[Ingress 或网关] I --> S[Service] S --> P1[Pod 1] S --> P2[Pod 2] S --> P3[Pod 3]

1. 创建 Service

“网络 → 服务”页面会显示访问方式、Cluster IP 或外部 IP,以及“服务端口 → 容器端口/协议”的映射关系。列表中的 ClusterIP、NodePort 和 LoadBalancer 对应三种不同暴露范围,不能只根据端口号判断服务是否能从公网访问。

Service 列表与端口映射

新建 Service 表单

新建 Service 时先选择项目和名称,再选择访问类型并配置端口:

表单字段 Kubernetes 字段 说明
服务端口 spec.ports[].port 客户端访问 Service 使用的端口
容器端口 spec.ports[].targetPort 流量最终转发到 Pod 的端口
访问类型 spec.type ClusterIP、NodePort 或 LoadBalancer
绑定工作负载/标签 spec.selector 必须能匹配目标 Pod 的 labels
Session Hold sessionAffinity 只有业务确实需要会话粘性时才开启

集群内访问一般用 Service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
  name: order-api
  namespace: team-test
spec:
  type: ClusterIP
  selector:
    app: order-api
  ports:
    - name: http
      port: 80
      targetPort: http

这表示集群内其他服务可以通过类似下面的地址访问:

1
http://order-api.team-test.svc.cluster.local

如果需要外部 HTTP/HTTPS 访问,通常配置 Ingress、Route 或公司网关规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-api
  namespace: team-test
spec:
  ingressClassName: nginx
  rules:
    - host: order-api-test.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: order-api
                port:
                  number: 80

2. 创建 Route(Ingress)

该平台把 Ingress 放在“网络 → 路由”中。路由列表展示名称、绑定的 Service、项目和类型;一个路由可以包含多条后端规则。

Route 列表

新建 Route 表单

新建路由时依次填写路由名称、项目、Ingress 类型、协议、域名、路径匹配方式、目标 Service 和目标端口。截图中的类型为 nginx、匹配方式为 Prefix,它们分别对应 Ingress Controller 和 pathType: Prefix。如果选择 HTTPS,还需要按平台要求配置 TLS 证书或 Secret。

平台页面里可能不会叫 Ingress,而是叫“域名”“路由”“七层负载均衡”或“应用入口”。无论名称如何,排错时都沿着 Route/Ingress → Service → Endpoint → Pod 这条链路逐层检查。

五、发布、验证和回滚

如果平台支持直接应用 YAML,可以这样发布:

1
2
3
kubectl apply -f k8s/
kubectl rollout status deployment/order-api -n team-test
kubectl get pods -n team-test -l app=order-api -o wide

常用验证命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 看 Pod 是否 Ready
kubectl get pods -n team-test -l app=order-api

# 看 Deployment 发布状态
kubectl describe deployment order-api -n team-test

# 看某个 Pod 事件
kubectl describe pod <pod-name> -n team-test

# 看日志
kubectl logs -f deployment/order-api -n team-test

# 临时端口转发验证服务
kubectl port-forward svc/order-api 8080:80 -n team-test
curl http://localhost:8080/healthz

如果新版本有问题,先暂停扩散,再回滚:

1
2
3
kubectl rollout history deployment/order-api -n team-test
kubectl rollout undo deployment/order-api -n team-test
kubectl rollout status deployment/order-api -n team-test

平台控制台一般也会提供“版本历史”“回滚”“重新部署”按钮。无论用页面还是命令,本质都是修改 Deployment 的 Pod Template,让控制器重新创建一批 Pod。

六、常见问题速查

现象 可能原因 排查方向
ImagePullBackOff 镜像地址错、Tag 不存在、仓库无权限 查镜像仓库、imagePullSecret、Pod 事件
ErrImagePull 节点拉镜像失败 kubectl describe pod 看具体错误
CrashLoopBackOff 容器启动后反复退出 看 kubectl logs --previous,检查启动命令和环境变量
Running 但 0/1 Ready Readiness Probe 不通过 检查健康检查路径、端口、超时设置
外部访问 404 Ingress 路径或域名规则不匹配 查 Ingress 规则、网关转发路径
外部访问 502/503 Service 没有可用 Endpoint 查 Pod Ready 状态、Service selector
连接数据库失败 Secret、网络策略、白名单、DNS 配置问题 查环境变量、凭据、网络连通性
OOMKilled 内存超过 limit 提高 limit,分析内存峰值,调整 JVM/运行时参数
exec format error 镜像架构和节点架构不一致 用 docker buildx --platform linux/amd64 重构建
修改镜像后平台没更新 Tag 没变且拉取策略未强制拉取 使用新 Tag,或调整 imagePullPolicy

排错时建议按这个顺序看:

flowchart TD A[Pod 是否创建] -->|没有| B[看 Deployment 事件] A -->|已创建| C[镜像是否拉取成功] C -->|失败| D[查镜像地址和仓库权限] C -->|成功| E[容器是否启动] E -->|失败| F[查启动命令和日志] E -->|成功| G[Pod 是否 Ready] G -->|失败| H[查 Readiness Probe] G -->|成功| I[Service 是否有 Endpoint] I -->|没有| J[查 selector 和 label] I -->|有| K[查 Ingress 或网关规则]

七、上线前检查清单

检查项 通过标准
Dockerfile 可重复构建 换一台机器或 CI 也能 docker build 成功
镜像本地可运行 docker run 后健康检查通过
镜像 Tag 可追踪 Tag 包含版本号、构建号或 Git SHA
镜像已推送仓库 平台能看到或拉取该镜像
配置未写死进镜像 环境差异通过 ConfigMap / Secret 注入
密码未进入 Dockerfile 不在 ENV、源码、镜像层里保存敏感信息
资源限制已配置 设置合理的 requests 和 limits
探针已配置 Readiness 和 Liveness 路径真实可用
Service selector 正确 Service 能找到 Pod Endpoint
入口规则已验证 域名、路径、证书、网关转发正常
回滚方式明确 知道上一版镜像 Tag 或 Deployment revision
日志和监控可用 上线后能看日志、指标和告警

总结

在公司 K8s 容器云平台部署服务,可以把流程压缩成一句话:

先用 Dockerfile 把服务变成可运行、可追踪、可推送的镜像,再让 K8s 用 Deployment 管理副本,用 Service 稳定访问,用 Ingress 或网关承接外部流量。

真正建议养成的习惯有四个:

  1. 镜像先本地跑通,再推仓库;
  2. 每次发布使用唯一 Tag,不依赖 latest;
  3. 配置和密钥交给平台注入,不写死进镜像;
  4. 上线前准备好健康检查、日志查看和回滚路径。

只要这条链路稳定,平台部署就会从“填表碰运气”变成“标准化交付流程”。