传统部署里,应用假定自己运行在一台长期存活的机器上:本地磁盘可写、进程不会无故消失、IP 固定、启动顺序可控。K8s 里这些假设全部不成立——Pod 随时可能被驱逐、重建后 IP 变化、多个副本并行存在。
因此应用设计要遵守几条基本约束:
这几条决定了后面所有实践的方向。
镜像设计有三条经验值得坚持。
第一,用多阶段构建控制体积。编译工具链留在构建阶段,运行阶段只保留产物和运行时,镜像小意味着拉取快、攻击面小、扩缩容更迅速。
第二,以非 root 用户运行。在 Dockerfile 中创建普通用户并切换,配合 K8s 的 securityContext,可以显著降低容器逃逸后的影响面。
第三,固定基础镜像版本。避免使用 latest,用 digest 或明确版本号锁定,保证构建可复现。
镜像推送后,建议加上不可变标签(如 git commit SHA),让"线上跑的到底是哪份代码"永远可追溯。
K8s 提供多种工作负载,选错类型会带来大量额外运维成本。
类型 | 适用场景 | 关键特征 |
|---|---|---|
Deployment | 无状态 Web/API 服务 | 副本可随意替换,滚动更新 |
StatefulSet | 数据库、消息队列、有状态中间件 | 稳定网络标识、有序启停、持久卷绑定 |
DaemonSet | 日志采集、节点监控、网络插件 | 每个节点一份 |
Job / CronJob | 批处理、定时任务 | 运行至完成 |
判断标准很简单:进程重启后是否还需要知道"我是谁、我的数据在哪"。需要,就用 StatefulSet;不需要,Deployment 足矣。很多团队把定时任务硬塞进 Deployment 里用 sleep 循环,结果日志混乱、并发失控,其实 CronJob 才是正解。
配置外置是云原生的基本要求。K8s 用 ConfigMap 存非敏感配置、Secret 存敏感信息,但有几个坑要避开。
不要把 Secret 当成加密存储。K8s 中的 Secret 默认只是 base64 编码,不是加密。生产环境应启用 etcd 加密、配合外部密钥管理(如 Vault、云厂商 KMS),并严格控制 RBAC 权限。
配置变更的触发问题。以环境变量方式注入的配置,Pod 不会自动感知变化;以卷挂载的 ConfigMap 会更新文件,但应用未必重新加载。最稳妥的做法是:配置变更时更新 Deployment 的注解(如配置哈希),触发一次滚动发布。
下面是一个精简的 Deployment 骨架,展示配置注入、资源限制与探针的组合方式:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
spec:
replicas: 3
selector:
matchLabels: { app: order-api }
template:
metadata:
labels: { app: order-api }
spec:
containers:
- name: app
image: registry.example.com/order-api:1.8.3
ports: [{ containerPort: 8080 }]
envFrom:
- configMapRef: { name: order-api-config }
- secretRef: { name: order-api-secret }
resources:
requests: { cpu: "200m", memory: "256Mi" }
limits: { cpu: "1", memory: "512Mi" }
readinessProbe:
httpGet: { path: /healthz/ready, port: 8080 }
initialDelaySeconds: 5
livenessProbe:
httpGet: { path: /healthz/live, port: 8080 }
initialDelaySeconds: 15这段 YAML 里的每一个字段,背后都对应一个设计决策,下面分别展开。
这是最容易被做错的一块。两个探针语义完全不同:
常见错误是把依赖检查塞进存活探针——数据库抖一下,所有 Pod 一起被重启,直接把小故障放大成雪崩。正确做法是:存活探针只检查进程自身是否健康,依赖可用性交给就绪探针或业务层的熔断降级。
另外,启动慢的应用应配置 startupProbe,避免存活探针在启动期误杀容器。
不写资源声明是生产事故的高发原因。调度器依据 requests 决定 Pod 落在哪个节点,limits 则约束运行时的资源上限。
建议以压测得到的 P95 用量作为 requests,留出合理 buffer 作为 limits;内存的 request 与 limit 可以设为相等,避免节点内存超卖导致的连锁驱逐。CPU 属于可压缩资源,limit 可以适度放宽。
同时启用 HorizontalPodAutoscaler,基于 CPU/内存或自定义指标扩缩容。但要注意:HPA 生效的前提是应用能水平扩展、启动足够快、且下游能承受扩容后的并发。
Pod IP 会变,Service 提供稳定的虚拟 IP 和 DNS 名称,是服务间调用的标准入口。几个实践要点:
<服务名>.<命名空间>.svc.cluster.local,跨命名空间调用时写全域名,避免解析歧义。服务间通信还要在应用层做超时、重试与熔断。K8s 的网络是"尽力而为",Service 只做四层转发,不做重试,这些必须由应用或服务网格承担。
Pod 是易失的,kubectl logs 只能看到当前容器,容器重启后日志就丢了。因此:
/metrics,关注 RED(Rate、Error、Duration)与 USE(Utilization、Saturation、Errors)指标;同时接入 K8s 层面的指标(重启次数、就绪状态、资源使用)。可观测性不是上线后补的,而是应用设计阶段就要预留的接口。
K8s 原生支持滚动更新,通过 maxSurge 和 maxUnavailable 控制节奏。但滚动更新只是"逐步替换",不具备流量切分和快速回滚的精细能力。
无论哪种策略,都要保证版本可回滚:镜像不可变、数据库变更向后兼容、配置有版本。kubectl rollout undo 只回滚工作负载,回滚不了数据,这一点必须提前设计。
安全要贯穿整条链路:
安全不是上线前的一次检查,而是流水线中的一道道门禁。
把上述内容串起来,一个新应用上 K8s 的推荐路径是:
Kubernetes 的价值不在于"能跑容器",而在于用声明式模型把应用的部署、扩缩、自愈、发布变成可自动化、可审计的流程。要做到这一点,应用本身必须配合:无状态化、配置外置、健康可探测、资源可度量、失败可恢复。
代码只是冰山一角,真正的功夫在设计决策里。把上面这些约束内化成团队的应用规范,K8s 才会从"复杂的基础设施"变成"可靠的应用运行底座"。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。