K8s 容器云平台服务部署流程:从 Dockerfile 打包镜像到上线
最近在公司内部 K8s 容器云平台上部署服务,最大的感受是:平台页面上的“创建应用”只是最后一步,真正决定部署是否顺利的,是前面这条链路:代码能稳定构建 → Dockerfile 写得正确 → 镜像能在本地跑起来 → 镜像能推到仓库 → K8s 能拉取镜像并通过探针验证服务健康。
这篇文章按一次真实上线的顺序来讲,并以中国电信云 CeakE 容器服务 CKE 的控制台为例展示具体操作。不同平台的页面名称可能不同,但只要底层是 Kubernetes,工作负载、Service、Ingress、探针和滚动更新等概念都能一一对应。
一、部署前先确认什么
先把需要的信息列清楚,后面就不会在平台页面里边填边猜。
| 准备项 | 需要确认什么 | 常见示例 |
|---|---|---|
| 服务启动方式 | 本地如何启动,监听哪个端口 | 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:
|
|
如果项目目录里有 .git、node_modules、target、日志、临时文件,都会影响构建速度,甚至把不该进入镜像的文件带进去。因此项目根目录建议放一个 .dockerignore:
|
|
.dockerignore 的目标不是“少传几个文件”这么简单,它直接影响三件事:构建速度、镜像安全、缓存命中率。
2. 一个通用的镜像构建流程
无论是 Go、Java、Node.js 还是 Python 服务,打包镜像都可以按这条路径走:
先确认项目不用 Docker 也能构建成功:
|
|
这一步很重要。Dockerfile 不是用来掩盖项目本身构建问题的。项目在本地基础构建都不稳定,放进 Docker 后只会更难排查。
3. Go 服务 Dockerfile 示例
Go 服务适合用多阶段构建:第一阶段负责编译,第二阶段只保留运行需要的二进制文件。
|
|
这个 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 |
不把源码、编译缓存、测试文件带到最终镜像 |
构建命令:
|
|
本地运行验证:
|
|
如果本地 curl 都不通,先不要推仓库,也不要上平台。优先检查:
- 应用实际监听端口是不是
8080; - 应用是否监听
0.0.0.0,而不是只监听127.0.0.1; - 启动命令是否正确;
- 健康检查路径是否真实存在;
- 必需环境变量是否已经传入。
4. Java Spring Boot 服务 Dockerfile 示例
Spring Boot 服务也建议多阶段构建。构建阶段使用 JDK,运行阶段只使用 JRE。
|
|
如果服务需要 JVM 参数,不建议把所有参数写死在 Dockerfile 里。可以留给平台环境变量控制:
|
|
平台里再配置:
|
|
这里要注意:JAVA_OPTS 本身不是 Kubernetes 标准字段,它只是你的启动脚本或 ENTRYPOINT 读取的环境变量。写了环境变量但启动命令没使用,它就不会生效。
5. Node.js 服务 Dockerfile 示例
Node.js 服务重点是依赖缓存和不要把开发依赖带进运行镜像。
|
|
如果你用的是前端静态站点,最终镜像可以不是 Node,而是 Nginx:
|
|
6. 镜像 Tag 怎么命名
生产环境不建议只用 latest。latest 看起来方便,但它不可追踪:今天的 latest 和昨天的 latest 可能不是同一个镜像。
更实用的 Tag 规则:
|
|
示例:
|
|
我更推荐至少保留 Git 提交信息:
|
|
这样平台回滚时,你能明确知道回到哪一次提交,而不是猜某个 Tag 当时指向什么。
如果公司平台支持使用镜像 Digest,生产环境还可以把 image 写成不可变形式:
|
|
Tag 适合人看,Digest 更适合机器保证不可变。
7. 构建架构要和集群节点一致
如果你在 Mac M 系列、ARM 开发机或某些 CI Runner 上构建镜像,而公司 K8s 节点是 x86,那么需要特别关注镜像架构。
查看本地机器架构:
|
|
明确构建 linux/amd64 镜像:
|
|
如果平台 Pod 报错类似 exec format error,优先怀疑镜像架构和节点架构不匹配。
8. 登录并推送镜像仓库
构建完成后,先确认本地有镜像:
|
|
登录公司镜像仓库:
|
|
如果镜像 Tag 还不是仓库地址,需要重新打 Tag:
|
|
推送:
|
|
推送成功后,容器云平台才能从仓库拉到这个镜像。注意:K8s 集群不会使用你本机 Docker 里的镜像。除非是特殊的本地集群,生产平台只会从镜像仓库拉取。
三、在容器云平台创建工作负载
镜像准备好后,才进入平台部署阶段。下面结合控制台截图走一遍完整流程。
1. 进入集群并检查资源状态
在“容器 → 集群列表”中找到目标集群。进入控制台前先确认集群状态为“运行中”,并核对集群类型、版本、节点数和所属租户,避免把服务发布到错误的环境。
进入集群后的“概览”页会集中展示 Deployment、StatefulSet、DaemonSet、Job、CronJob 和 Pod 数量,以及节点健康状态、CPU、内存和磁盘使用情况。
发布前不要只看“节点健康”,还要同时检查资源水位和事件列表。截图里 CPU、内存都已经有一定占用;如果 requests 配得过大,Pod 可能因为剩余可分配资源不足而一直处于 Pending。事件列表中的 Warning 也应在发布前确认是否与目标项目或网络入口有关。
2. 先选对工作负载类型
左侧“工作负载”菜单并不只有 Deployment。平台把常见控制器和 Pod 分开列出,选择时可以按运行模式判断:
| 控制台入口 | Kubernetes 对象 | 适用场景 |
|---|---|---|
| 无状态负载 | Deployment |
Web、API 等可横向扩容、实例可替换的服务 |
| 守护进程集 | DaemonSet |
每个节点运行一个实例,如日志采集、监控 Agent |
| 有状态负载 | StatefulSet |
需要稳定网络标识、顺序发布或持久存储的服务 |
| 普通任务 | Job |
数据迁移、一次性计算等执行完成即退出的任务 |
| 定时任务 | CronJob |
按 Cron 表达式周期执行的任务 |
| 容器组 | Pod |
查看实际运行实例和排错,一般不直接创建长期业务 Pod |
Deployment 列表中的“容器组(运行/期望)”、副本数和状态可以快速判断发布是否完成。比如 0/1 且状态为 Updating,说明新 Pod 还没有达到期望状态。
Pod 页面尤其适合排错:它同时展示状态、就绪容器数、重启次数、Pod IP 和所在节点。截图中的 CrashLoopBackOff 和异常高的重启次数就是明确的故障信号,此时应优先查看容器日志、上一轮日志和 Pod 事件,而不是先改 Service 或 Route。
3. 按四步向导创建 Deployment
在“无状态负载”页面点击“新建无状态工作负载”,控制台会依次进入基本信息、容器配置、服务配置和高级配置四个步骤。
第一步:基本信息
填写名称、项目(对应 Namespace)、实例数量和描述。测试环境可以先用 1 个实例验证;生产环境通常至少 2 个实例,并结合滚动更新策略保证发布期间仍有可用 Pod。
第二步:容器配置
容器镜像既可以从平台镜像仓库选择,也可以直接输入完整镜像地址。需要重点核对镜像 Tag、拉取策略以及 vCPU/内存的请求和限制:
- 使用不可变 Tag 时可以选
IfNotPresent;截图中的默认值是Always,它适合同一 Tag 可能被覆盖的场景,但生产发布仍建议使用唯一 Tag; - 不建议把 CPU、内存请求与限制都保持为“无限制”,至少要设置合理的 requests,生产环境再根据压测和监控设置 limits;
- 容器名称是 Pod 内的标识,不等于 Deployment 名称;一个 Pod 也可以配置多个容器。
向下滚动可以配置环境变量、启动命令、运行参数、工作目录、健康检查和存储卷。平台把 Kubernetes 的三类探针分别显示为存活检查(Liveness)、就绪检查(Readiness)和启动检查(Startup):
- Readiness 决定 Pod 是否加入 Service Endpoint,业务服务通常应该配置;
- Liveness 用于判断是否需要重启容器,不要把偶发抖动的外部依赖放进检查逻辑;
- 启动慢的应用可使用 Startup Probe,避免还在启动时就被 Liveness 反复重启。
第三步:服务配置
这里可以绑定已有 Service,也可以直接新建。若当前工作负载暂时不需要被访问,可以先跳过,后续再从“网络 → 服务”中单独创建。
新建 Service 时要填写访问类型、服务端口、容器端口和协议。对普通内部服务优先使用 ClusterIP;NodePort 会在节点上开放端口;LoadBalancer 是否可用取决于云平台和负载均衡配置。最关键的是把 Service 正确绑定到工作负载标签,否则 Service 不会产生 Endpoint。
第四步:高级配置
高级配置上半部分包括 DNS 策略、额外 Hosts/网络,以及升级策略。常规场景保持 ClusterFirst;升级方式使用 RollingUpdate 时,需要结合副本数设置最大扩容 Pod 数(maxSurge)和最大不可用 Pod 数(maxUnavailable)。单副本服务若允许 maxUnavailable: 1,发布过程中会短暂不可用。
高级配置下半部分可以设置节点亲和性、工作负载亲和/反亲和、污点容忍、标签、注解、应用监控和私有镜像密钥。只有在确实需要指定节点、跨节点分散副本或访问私有仓库时再修改;不清楚含义时,先保留平台默认的调度配置。
四步表单最终仍会生成 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 长这样:
|
|
这里最值得关注的是三件事:
image必须是平台节点能访问的完整镜像地址;containerPort必须和应用真实监听端口一致;readinessProbe不通过时,Pod 即使启动了也不会接收流量。
健康检查接口要轻量、稳定,最好只检查进程是否能处理请求,以及必要的本地依赖是否正常。不要把一堆下游系统都强绑定进去,否则某个非核心依赖抖动,会导致自己的 Pod 被平台摘流量甚至重启。
四、配置服务入口
Deployment 只负责让 Pod 跑起来。要让其他服务或用户访问它,还需要 Service 和 Ingress。
1. 创建 Service
“网络 → 服务”页面会显示访问方式、Cluster IP 或外部 IP,以及“服务端口 → 容器端口/协议”的映射关系。列表中的 ClusterIP、NodePort 和 LoadBalancer 对应三种不同暴露范围,不能只根据端口号判断服务是否能从公网访问。
新建 Service 时先选择项目和名称,再选择访问类型并配置端口:
| 表单字段 | Kubernetes 字段 | 说明 |
|---|---|---|
| 服务端口 | spec.ports[].port |
客户端访问 Service 使用的端口 |
| 容器端口 | spec.ports[].targetPort |
流量最终转发到 Pod 的端口 |
| 访问类型 | spec.type |
ClusterIP、NodePort 或 LoadBalancer |
| 绑定工作负载/标签 | spec.selector |
必须能匹配目标 Pod 的 labels |
| Session Hold | sessionAffinity |
只有业务确实需要会话粘性时才开启 |
集群内访问一般用 Service:
|
|
这表示集群内其他服务可以通过类似下面的地址访问:
|
|
如果需要外部 HTTP/HTTPS 访问,通常配置 Ingress、Route 或公司网关规则:
|
|
2. 创建 Route(Ingress)
该平台把 Ingress 放在“网络 → 路由”中。路由列表展示名称、绑定的 Service、项目和类型;一个路由可以包含多条后端规则。
新建路由时依次填写路由名称、项目、Ingress 类型、协议、域名、路径匹配方式、目标 Service 和目标端口。截图中的类型为 nginx、匹配方式为 Prefix,它们分别对应 Ingress Controller 和 pathType: Prefix。如果选择 HTTPS,还需要按平台要求配置 TLS 证书或 Secret。
平台页面里可能不会叫 Ingress,而是叫“域名”“路由”“七层负载均衡”或“应用入口”。无论名称如何,排错时都沿着 Route/Ingress → Service → Endpoint → Pod 这条链路逐层检查。
五、发布、验证和回滚
如果平台支持直接应用 YAML,可以这样发布:
|
|
常用验证命令:
|
|
如果新版本有问题,先暂停扩散,再回滚:
|
|
平台控制台一般也会提供“版本历史”“回滚”“重新部署”按钮。无论用页面还是命令,本质都是修改 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 |
排错时建议按这个顺序看:
七、上线前检查清单
| 检查项 | 通过标准 |
|---|---|
| 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 或网关承接外部流量。
真正建议养成的习惯有四个:
- 镜像先本地跑通,再推仓库;
- 每次发布使用唯一 Tag,不依赖
latest; - 配置和密钥交给平台注入,不写死进镜像;
- 上线前准备好健康检查、日志查看和回滚路径。
只要这条链路稳定,平台部署就会从“填表碰运气”变成“标准化交付流程”。


















