Docker+K8s 集成部署实战
在微服务和云原生普及的今天,Docker 的容器标准化能力与 Kubernetes 的大规模编排能力,已成为企业级部署的黄金组合。但从本地开发到生产落地,很多开发者会卡在 "集成配置""资源优化""故障排查" 等关键环节。本文结合电商订单系统的部署升级经验,从核心概念、集成要点到生产实战,一步步拆解 Docker 与 K8s 的协同逻辑,同时分享 5 个高频踩坑案例及解决方案。
一、Docker 与 K8s 的核心关系:不是替代,而是互补
Docker 和 K8s 的定位截然不同,却构成了容器化部署的完整链路,理解二者的分工是集成的基础。
1.1 核心定位与职责划分
| 工具 | 核心能力 | 核心价值 |
|---|---|---|
| Docker | 容器打包、单机运行 | 将应用及依赖打包为标准化镜像,确保"一次构建,到处运行",解决环境一致性问题 |
| K8s | 集群编排、自动化管理 | 对大规模容器进行部署、扩缩容、自愈、网络调度,解决分布式环境下的运维复杂性问题 |
注意:K8s 1.24+ 版本已移除内置的 dockershim 组件,不再直接对接 Docker,但可通过 Docker 内置的 containerd 运行时实现集成------这是当前生产环境的主流方案,无需额外安装独立的 containerd。
1.2 关键概念对应关系
| Docker 概念 | K8s 对应概念 | 核心作用说明 |
|---|---|---|
| 镜像(Image) | 镜像(Image) | K8s 通过镜像拉取策略(Always/IfNotPresent/Never)从仓库拉取镜像创建容器 |
| 容器(Container) | Pod 内的容器 | K8s 最小调度单元是 Pod,一个 Pod 可包含 1~N 个容器,共享网络和存储资源 |
| Docker Compose | Deployment/StatefulSet | Compose 适用于单机小规模编排,K8s 控制器实现集群级高可用编排 |
| Docker Network | CNI 插件 + Service | Docker 仅支持单机网络互通,K8s 通过 CNI(如 Calico)实现跨节点 Pod 通信,Service 提供固定访问入口 |
二、Docker 与 K8s 集成的 4 个核心落地要点
集成的关键在于 "标准化" 与 "适配性" ------Docker 镜像需满足 K8s 的拉取要求,K8s 配置需兼容 Docker 的运行特性,以下 4 个要点是落地必经之路。
2.1 镜像标准化:K8s 拉取成功的前提
镜像的规范程度直接影响部署稳定性,生产环境需重点关注 3 点:
(1)镜像命名规范
必须遵循 [仓库地址]:[端口]/[命名空间]/[镜像名]:[标签] 格式,示例:
harbor.example.com/apps/order-service:v1.2.3-8f7d6a5
- 禁止使用
latest标签:避免版本漂移导致的部署不一致,建议用"版本号 + Git Commit ID"命名,便于追溯问题。 - 私有仓库前缀不可少:明确指定私有仓库地址(如 Harbor),避免与公仓镜像混淆。
(2)镜像优化技巧
通过多阶段构建减小体积,降低拉取时间和存储成本,以 Go 语言应用为例:
dockerfile
# 构建阶段:使用完整编译环境
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go mod download && CGO_ENABLED=0 GOOS=linux go build -o order-service .
# 运行阶段:使用轻量基础镜像
FROM alpine:3.18
RUN apk --no-cache add ca-certificates # 解决HTTPS证书问题
WORKDIR /app
COPY --from=builder /app/order-service .
USER nobody # 非root用户运行,降低安全风险
EXPOSE 8080
CMD ["./order-service"]
优化效果:镜像体积可从 2G+ 压缩至 80M 以内,同时移除冗余依赖,提升安全性。
(3)私有仓库集成配置
K8s 拉取私有仓库镜像需配置认证信息,步骤如下:
- 创建镜像拉取密钥:
bash
kubectl create secret docker-registry harbor-secret \
--docker-server=harbor.example.com \
--docker-username=admin \
--docker-password=123456
- 在 Deployment 中引用密钥:
yaml
spec:
template:
spec:
imagePullSecrets:
- name: harbor-secret # 与创建的Secret名称一致
2.2 容器运行时集成(K8s 1.24+ 重点)
通过 Docker 内置的 containerd 对接 K8s,无需额外部署独立运行时,步骤如下:
- 安装 Docker 后,生成 containerd 配置文件:
bash
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
- 修改配置文件,适配 K8s 要求:
-
将 Sandbox 镜像改为国内源(避免拉取失败):
sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9" -
启用 CRI 接口(K8s 与 containerd 通信的标准接口)
-
- 重启 containerd 并设置开机自启:
bash
systemctl restart containerd && systemctl enable containerd
- K8s 集群初始化时指定运行时:
bash
kubeadm init --image-repository registry.aliyuncs.com/google_containers \
--cri-socket unix:///run/containerd/containerd.sock \
--pod-network-cidr=10.244.0.0/16
2.3 资源配置:Docker 限制与 K8s 配置的映射
Docker 的资源限制(如内存、CPU)需通过 K8s 的 resources 字段精准映射,避免容器超限被 Kill 或资源浪费:
| Docker 限制方式 | K8s 对应配置 | 说明 |
|---|---|---|
docker run --memory 1G |
limits.memory: 1Gi |
容器最大可用内存,超限可能被 OOM Kill |
docker run --cpus 1 |
limits.cpu: 1000m |
容器最大可用 CPU(1000m=1 核) |
docker run --restart always |
K8s 自愈机制 + replicas | Deployment 通过 replicas 指定副本数,故障时自动重启并重新调度 |
生产环境配置示例(订单服务):
yaml
resources:
requests: # 最小资源需求(K8s调度依据)
cpu: 500m # 0.5核
memory: 512Mi
limits: # 最大资源限制
cpu: 1000m # 1核
memory: 1Gi
requests:确保 Pod 被调度到资源充足的节点,避免资源竞争;limits:防止单个 Pod 占用过多资源,影响其他服务。
2.4 网络集成:从 Docker 单机网络到 K8s 跨节点通信
Docker 的单机网络无法满足分布式部署需求,K8s 通过 CNI 插件和 Service 实现网络互通:
(1)CNI 插件选择
生产环境优先选择 Calico(支持网络策略、跨节点通信稳定),安装命令:
bash
kubectl apply -f https://docs.projectcalico.org/v3.25/manifests/calico.yaml
安装后,每个 Pod 会被分配独立 IP,跨节点 Pod 可直接通信,无需额外配置端口映射。
(2)服务暴露:Ingress 替代 NodePort
Docker 的 -p 8080:80 端口映射,在 K8s 中需通过 Service+Ingress 实现,生产环境禁止使用 NodePort(端口冲突风险高):
① 创建 ClusterIP 类型 Service(集群内访问):
yaml
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 8080
targetPort: 8080
type: ClusterIP
② 通过 Ingress 暴露对外访问入口(以 NGINX Ingress 为例):
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: order.example.com # 自定义域名
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-service
port:
number: 8080
tls: # 启用HTTPS
- hosts:
- order.example.com
secretName: order-tls-secret # 存储证书的Secret名称
Ingress 支持域名路由、路径转发、TLS 加密,是生产环境对外暴露服务的标准方案。
三、生产环境实战:电商订单系统从 Docker Compose 迁移到 K8s
以电商订单系统为例,分享从单机 Docker Compose 到 K8s 集群部署的完整迁移过程及思考。
3.1 迁移背景与目标
(1)原始架构痛点
- 3 台物理机,用 Docker Compose 部署订单服务、库存服务、MySQL(单机)、Redis(单机);
- 核心问题:容器故障需手动重启、扩缩容效率低、主机目录挂载导致数据易丢失、端口冲突频繁。
(2)迁移目标
- 高可用:服务可用性从 99.5% 提升至 99.99%,支持故障自愈;
- 自动化:支持一键扩缩容、滚动更新、版本回滚;
- 可追溯:镜像版本与代码提交绑定,部署过程可审计;
- 资源优化:提升节点 CPU/内存利用率,降低硬件成本。
3.2 核心迁移步骤
(1)镜像重构:从 "能用" 到 "生产级"
原 Dockerfile 基于 Ubuntu,包含冗余工具(如 vim、gcc),镜像体积达 2.3G,重构后:
- 采用多阶段构建,体积压缩至 78M;
- 敏感配置(如 MySQL 密码)从镜像中移除,通过 K8s Secret 挂载;
- 镜像标签改为
v1.2.3-8f7d6a5(版本号 + Git Commit ID)。
(2)存储方案升级:从 hostPath 到 PVC
原架构用 -v /data/logs:/app/logs 挂载主机目录,节点故障后日志丢失,优化方案:
- 部署 NFS 分布式存储,提供共享存储能力;
- 创建 PersistentVolume(PV)和 PersistentVolumeClaim(PVC):
yaml
# PV配置
apiVersion: v1
kind: PersistentVolume
metadata:
name: order-logs-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
nfs:
server: 192.168.1.100
path: /nfs/order/logs
# PVC配置
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: order-service-logs-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
- 在 Deployment 中引用 PVC:
yaml
volumeMounts:
- name: logs
mountPath: /app/logs
volumes:
- name: logs
persistentVolumeClaim:
claimName: order-service-logs-pvc
(3)部署文件编写:Docker Compose 转 K8s 资源
原 Docker Compose 配置(订单服务):
yaml
version: '3'
services:
order-service:
image: order-service:latest
ports:
- "8080:8080"
volumes:
- ./config:/app/config
- /data/logs:/app/logs
restart: always
environment:
- MYSQL_HOST=192.168.1.100
- REDIS_HOST=192.168.1.101
重构为 K8s Deployment+Service+ConfigMap+Secret,完整配置如下:
yaml
# 配置文件存储(ConfigMap)
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config
data:
MYSQL_HOST: "mysql-service" # 用Service名替代固定IP,实现服务发现
REDIS_HOST: "redis-service"
LOG_LEVEL: "info"
---
# 敏感信息存储(Secret)
apiVersion: v1
kind: Secret
metadata:
name: order-service-secret
type: Opaque
data:
MYSQL_PASSWORD: "cGFzc3dvcmQxMjM=" # base64加密(echo -n "password123" | base64)
---
# 部署控制器(Deployment)
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3 # 3副本确保高可用
selector:
matchLabels:
app: order-service
strategy:
rollingUpdate: # 滚动更新策略
maxSurge: 1 # 最多可额外创建1个Pod
maxUnavailable: 1 # 最多允许1个Pod不可用
template:
metadata:
labels:
app: order-service
spec:
imagePullSecrets:
- name: harbor-secret
containers:
- name: order-service
image: harbor.example.com/apps/order-service:v1.2.3-8f7d6a5
ports:
- containerPort: 8080
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
env:
- name: MYSQL_HOST
valueFrom:
configMapKeyRef:
name: order-service-config
key: MYSQL_HOST
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: order-service-secret
key: MYSQL_PASSWORD
volumeMounts:
- name: logs
mountPath: /app/logs
readinessProbe: # 就绪探针,确保Pod就绪后才接收流量
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
volumes:
- name: logs
persistentVolumeClaim:
claimName: order-service-logs-pvc
---
# 服务访问入口(Service)
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 8080
targetPort: 8080
type: ClusterIP
(4)滚动更新与回滚配置
生产环境部署新版本时,需避免服务中断,K8s 滚动更新机制可实现零停机部署:
- 触发更新(修改镜像版本):
bash
kubectl set image deployment/order-service order-service=harbor.example.com/apps/order-service:v1.2.4-9a3b7c2
- 查看更新状态:
bash
kubectl rollout status deployment/order-service
- 若新版本出现问题,快速回滚:
bash
# 回滚到上一版本
kubectl rollout undo deployment/order-service
# 回滚到指定版本(先查看历史版本)
kubectl rollout history deployment/order-service
kubectl rollout undo deployment/order-service --to-revision=2
3.3 生产踩坑与解决方案(高频 5 例)
| 坑点 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| 1. 镜像拉取认证错误 | kubectl describe pod 显示 Failed to pull image: unauthorized: authentication required |
imagePullSecrets 配置在 Deployment 的 spec 层级,而非 spec.template.spec 层级 | 确保 imagePullSecrets 配置在 Pod 模板中,且 Secret 名称与创建的一致 |
| 2. Pod 被 OOM Kill | Pod 状态显示 OOMKilled,事件提示 MemoryLimitExceeded | limits.memory 配置过小,requests 配置过低 | 1. 压测调整合理资源配置;2. 启用 VPA 自动调整: yaml<br>apiVersion: autoscaling.k8s.io/v1<br>kind: VerticalPodAutoscaler<br>metadata:<br> name: order-service-vpa<br>spec:<br> targetRef:<br> apiVersion: apps/v1<br> kind: Deployment<br> name: order-service<br> updatePolicy:<br> updateMode: Auto<br> resourcePolicy:<br> containerPolicies:<br> - containerName: '*'<br> minAllowed:<br> cpu: 500m<br> memory: 512Mi<br> maxAllowed:<br> cpu: 2000m<br> memory: 4Gi<br> |
| 3. Ingress 路由 404 | 访问 order.example.com 提示 404,Pod 和 Service 状态正常 | pathType 配置错误,或 rewrite-target 注解未正确设置 | 1. 明确 pathType: Prefix;2. 配置路径重写注解: ```yaml annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - host: order.example.com http: paths: - path: /api(/ |
| 4. 滚动更新服务不可用 | 更新过程中部分用户访问提示"连接超时" | 未配置就绪探针,新 Pod 未完全启动就接收流量 | 添加就绪探针(Readiness Probe),确保 Pod 就绪后才加入负载均衡池 |
| 5. PVC 挂载失败 | Pod 处于 Pending 状态,提示 PersistentVolumeClaim is not bound | PV 与 PVC 的 accessModes 不匹配,或 NFS 未授权 K8s 节点访问 | 1. 确保 PV/PVC accessModes 一致;2. 配置 NFS 授权: /nfs/order/logs 192.168.1.0/24(rw,sync,no_root_squash) |
总结
- 排版优化核心:通过分级标题、表格、代码块格式化、重点内容加粗,让文档结构更清晰,关键信息更易查找;
- 格式规范:统一代码块语法高亮、标准化配置示例格式,符合技术文档的阅读习惯;
- 信息整合:将分散的踩坑案例整理为表格,对比展示问题、原因和解决方案,提升实用性。