CI/CD完整流程:Docker + Jenkins + Kubernetes 发表于 2024年03月11日 更新于 2026年03月11日
CI/CD完整流程:Docker + Jenkins + Kubernetes 一、通俗易懂的比喻解释 1.1 用盖房子来比喻 Docker 镜像 = 房子设计图纸
镜像就是一张图纸,上面写着:需要什么材料、怎么搭建
比如 nginx:alpine 镜像 = “一个装了 Nginx 的 Linux 系统的设计图”
镜像是只读的,不会变
Docker 容器 = 根据图纸盖出来的真实房子
容器就是按照镜像(图纸)实际运行起来的程序
一张图纸可以盖很多房子(一个镜像可以启动多个容器)
容器是活的,可以进去操作
1 2 docker run nginx:alpine
1.2 完整流程:从本地到线上 场景 1:你在本地玩 Docker + Jenkins 1 2 3 4 你的电脑(本地) ├── Docker Desktop(盖房子的工地) │ └── Jenkins 容器(一个房子,里面住着 Jenkins) │ └── 访问 http://localhost:8080 就能进这个房子
这时候发生了什么?
你下载了 Jenkins 镜像(图纸)
Docker 根据图纸盖了一个 Jenkins 容器(房子)
这个房子开了一扇门:8080 端口
你通过 localhost:8080 进入这个房子
1 2 3 docker run -p 8080:8080 jenkins/jenkins
重点 :这个 Jenkins 只在你电脑上,别人访问不了!
场景 2:真实线上环境 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 公司的服务器集群(线上) ├── 服务器 1 │ ├── Docker(工地) │ │ └── Jenkins 容器(管家房子) │ └── K8s Master(总指挥) │ ├── 服务器 2 │ ├── Docker(工地) │ └── K8s Node(工人) │ └── 你的前端容器 A(房子 A) │ ├── 服务器 3 │ ├── Docker(工地) │ └── K8s Node(工人) │ └── 你的前端容器 B(房子 B) │ └── 服务器 4 ├── Docker(工地) └── K8s Node(工人) └── 你的前端容器 C(房子 C)
1.3 完整部署流程(用你的项目举例) 第 1 步:你提交代码 1 2 3 git add . git commit -m "修复了登录 bug" git push origin main
第 2 步:Jenkins 自动开始干活(在服务器 1) Jenkins 就像一个管家,住在服务器 1 的 Docker 容器里。
1 2 3 4 5 6 7 1. 拉取你的代码 2. npm install(安装依赖) 3. npm run build(打包,生成 dist 文件夹) 4. docker build -t uhr-form:v1.0.5 . 5. docker push uhr-form:v1.0.5 6. kubectl apply -f k8s.yaml
第 3 步:K8s 开始调度(总指挥) K8s Master(总指挥)看到新图纸来了:
1 2 3 4 5 6 7 8 spec: replicas: 3 template: spec: containers: - name: uhr-form image: uhr-form:v1.0.5
K8s 总指挥开始分配任务:
“服务器 2,你去盖一个房子”
“服务器 3,你也盖一个”
“服务器 4,你也盖一个”
第 4 步:各个服务器开始盖房子 1 2 3 4 5 6 服务器 2: 1. docker pull uhr-form:v1.0.5 # 下载图纸 2. docker run uhr-form:v1.0.5 # 盖房子(启动容器) 3. 房子盖好了,开门营业(监听 80 端口) 服务器 3、4 同样操作
第 5 步:用户访问 1 2 3 4 5 6 7 8 9 10 用户访问 http://your-domain.com ↓ K8s Service(前台接待) ↓ 负载均衡(随机选一个房子) ↓ ┌─────┼─────┐ ↓ ↓ ↓ 房子A 房子B 房子C (容器A)(容器B)(容器C)
1.4 关键理解 Docker 的角色 Docker 就是”盖房子的工具”,在每台服务器上都有。
本地:你电脑上的 Docker Desktop
线上:每台服务器上都装了 Docker
Jenkins 的角色 Jenkins 是”自动化管家”,负责:
监听代码变化
自动打包
制作 Docker 镜像(图纸)
通知 K8s 部署
Jenkins 本身也是一个 Docker 容器 ,住在某台服务器上。
K8s 的角色 K8s 是”总指挥 + 物业管理”,负责:
决定在哪台服务器盖房子
监控房子是否正常(容器健康检查)
房子塌了自动重建(自动重启)
流量分配(负载均衡)
为什么本地和线上不一样?
本地
线上
只有你一台电脑
有很多台服务器
手动 docker run
K8s 自动调度
只有你能访问
全世界都能访问
容器挂了要手动重启
K8s 自动重启
1.5 总结一句话
Docker :盖房子的工具(本地和线上都用)
Jenkins :自动化管家(监听代码变化,自动打包部署)
K8s :物业公司(管理多台服务器上的多个容器)
本地 :你自己玩,手动盖房子线上 :Jenkins 自动打包 → K8s 自动在多台服务器盖房子 → 用户访问
重要 :Docker 容器跑在 K8s 上,K8s 调度和管理 Docker 容器。
二、三者的关系与定位 2.1 整体架构图 1 2 3 4 5 6 7 8 9 开发阶段 构建阶段 部署阶段 ↓ ↓ ↓ 代码提交 → Jenkins (CI/CD) → Docker镜像 → K8s集群 | | | |-- 拉取代码 |-- 构建镜像 |-- 容器编排 |-- 运行测试 |-- 推送仓库 |-- 自动扩缩容 |-- 构建应用 | |-- 服务发现 |-- 自动部署 |-- 负载均衡 |-- 滚动更新
2.2 各自的定位 Docker - 容器化技术
打包应用及依赖,保证环境一致性
解决”在我机器上能跑”的问题
层级:应用层
Jenkins - CI/CD 工具
自动化构建、测试、部署流程
串联整个开发到部署的流程
层级:流程编排层
Kubernetes - 容器编排平台
管理和调度大规模容器集群
提供高可用、自动扩缩容等能力
层级:基础设施层
三、完整的 CI/CD 流程 3.1 流程图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 1. 开发提交代码到 Git ↓ 2. Git Webhook 触发 Jenkins ↓ 3. Jenkins 拉取最新代码 ↓ 4. Jenkins 安装依赖并运行测试 ↓ 5. Jenkins 构建应用 ↓ 6. Jenkins 构建 Docker 镜像 ↓ 7. Jenkins 推送镜像到镜像仓库 ↓ 8. Jenkins 更新 K8s Deployment ↓ 9. K8s 执行滚动更新 ↓ 10. K8s 健康检查 ↓ 11. 部署完成,发送通知
3.2 Jenkins Pipeline 完整示例 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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 pipeline { agent any environment { DOCKER_REGISTRY = 'registry.example.com' DOCKER_CREDENTIALS = 'docker-hub-credentials' IMAGE_NAME = 'myapp' IMAGE_TAG = "${env.BUILD_NUMBER}" K8S_NAMESPACE = 'production' K8S_DEPLOYMENT = 'myapp-deployment' KUBECONFIG_CREDENTIALS = 'kubeconfig' } stages { stage('Checkout' ) { steps { echo '📥 拉取代码...' checkout scm } } stage('Install Dependencies' ) { steps { echo '📦 安装依赖...' sh 'npm ci' } } stage('Lint & Test' ) { parallel { stage('Lint' ) { steps { echo '🔍 代码检查...' sh 'npm run lint' } } stage('Unit Test' ) { steps { echo '🧪 单元测试...' sh 'npm run test:unit' } } stage('E2E Test' ) { steps { echo '🎯 E2E测试...' sh 'npm run test:e2e' } } } } stage('Build Application' ) { steps { echo '🔨 构建应用...' sh 'npm run build' } } stage('Build Docker Image' ) { steps { echo '🐳 构建 Docker 镜像...' script { def fullImageName = "${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" def latestImageName = "${DOCKER_REGISTRY}/${IMAGE_NAME}:latest" sh "docker build -t ${fullImageName} ." sh "docker tag ${fullImageName} ${latestImageName}" } } } stage('Push Docker Image' ) { steps { echo '📤 推送 Docker 镜像...' script { docker.withRegistry("https://${DOCKER_REGISTRY}" , DOCKER_CREDENTIALS) { sh "docker push ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" sh "docker push ${DOCKER_REGISTRY}/${IMAGE_NAME}:latest" } } } } stage('Deploy to K8s' ) { steps { echo '🚀 部署到 Kubernetes...' script { withKubeConfig([credentialsId: KUBECONFIG_CREDENTIALS]) { sh """ kubectl set image deployment/${K8S_DEPLOYMENT} \ ${IMAGE_NAME}=${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \ -n ${K8S_NAMESPACE} \ --record """ sh """ kubectl rollout status deployment/${K8S_DEPLOYMENT} \ -n ${K8S_NAMESPACE} \ --timeout=5m """ } } } } stage('Verify Deployment' ) { steps { echo '✅ 验证部署...' script { withKubeConfig([credentialsId: KUBECONFIG_CREDENTIALS]) { sh """ kubectl get pods -n ${K8S_NAMESPACE} \ -l app=${IMAGE_NAME} \ -o wide """ sleep 10 sh 'curl -f http://myapp.example.com/health || exit 1' } } } } } post { success { echo '🎉 部署成功!' dingtalk( robot: 'jenkins-bot' , type: 'MARKDOWN' , title: '部署成功' , text: [ "### 🎉 部署成功" , "- **项目**: ${env.JOB_NAME}" , "- **版本**: ${IMAGE_TAG}" , "- **环境**: ${K8S_NAMESPACE}" , "- **镜像**: ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" , "- [查看详情](${env.BUILD_URL})" ] ) } failure { echo '❌ 部署失败!' script { withKubeConfig([credentialsId: KUBECONFIG_CREDENTIALS]) { sh """ kubectl rollout undo deployment/${K8S_DEPLOYMENT} \ -n ${K8S_NAMESPACE} """ } } dingtalk( robot: 'jenkins-bot' , type: 'MARKDOWN' , title: '部署失败' , text: [ "### ❌ 部署失败(已自动回滚)" , "- **项目**: ${env.JOB_NAME}" , "- **版本**: ${IMAGE_TAG}" , "- [查看日志](${env.BUILD_URL}console)" ] ) } always { sh "docker rmi ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} || true" sh "docker rmi ${DOCKER_REGISTRY}/${IMAGE_NAME}:latest || true" cleanWs() } } }
四、Kubernetes 配置文件 4.1 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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 apiVersion: apps/v1 kind: Deployment metadata: name: myapp-deployment namespace: production labels: app: myapp version: v1 spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: myapp template: metadata: labels: app: myapp version: v1 spec: imagePullSecrets: - name: registry-secret initContainers: - name: init-db image: busybox:1.28 command: ['sh' , '-c' , 'until nc -z db-service 3306; do echo waiting for db; sleep 2; done;' ] containers: - name: myapp image: registry.example.com/myapp:latest imagePullPolicy: Always ports: - containerPort: 3000 name: http protocol: TCP env: - name: NODE_ENV value: "production" - name: PORT value: "3000" - name: DATABASE_URL valueFrom: configMapKeyRef: name: myapp-config key: DATABASE_URL - name: DB_PASSWORD valueFrom: secretKeyRef: name: myapp-secret key: password resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 volumeMounts: - name: config mountPath: /app/config readOnly: true - name: logs mountPath: /app/logs volumes: - name: config configMap: name: myapp-config - name: logs persistentVolumeClaim: claimName: myapp-logs-pvc
4.2 Service 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: Service metadata: name: myapp-service namespace: production spec: type: ClusterIP selector: app: myapp ports: - protocol: TCP port: 80 targetPort: 3000 sessionAffinity: ClientIP
4.3 Ingress 配置 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 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp-ingress namespace: production annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/rate-limit: "100" spec: tls: - hosts: - myapp.example.com secretName: myapp-tls rules: - host: myapp.example.com http: paths: - path: / pathType: Prefix backend: service: name: myapp-service port: number: 80
4.4 ConfigMap 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: v1 kind: ConfigMap metadata: name: myapp-config namespace: production data: DATABASE_URL: "mysql://db-service:3306/myapp" REDIS_URL: "redis://redis-service:6379" LOG_LEVEL: "info" app.conf: | server { listen 80; server_name example.com; location / { proxy_pass http://localhost:3000; } }
4.5 Secret 配置 1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: Secret metadata: name: myapp-secret namespace: production type: Opaque data: username: YWRtaW4= password: cGFzc3dvcmQ= db-password: ZGJwYXNzd29yZA==
4.6 HPA 自动扩缩容 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 apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: myapp-hpa namespace: production spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp-deployment minReplicas: 3 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 15 scaleUp: stabilizationWindowSeconds: 0 policies: - type: Percent value: 100 periodSeconds: 15
五、Dockerfile 最佳实践 5.1 多阶段构建 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 FROM node:16 AS builderWORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build FROM node:16 -alpineWORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./ RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 USER nodejsEXPOSE 3000 HEALTHCHECK --interval=30s --timeout =3s --start-period=40s \ CMD node healthcheck.js || exit 1 CMD ["node" , "dist/main.js" ]
5.2 .dockerignore 文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # .dockerignore node_modules npm-debug.log .git .gitignore .env .env.local .DS_Store *.md .vscode .idea coverage .nyc_output dist build *.log
六、常用 K8s 命令 6.1 Pod 操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 kubectl get pods -n production kubectl get pods -n production -o wide kubectl get pods -n production -w kubectl describe pod myapp-pod -n production kubectl logs myapp-pod -n production kubectl logs -f myapp-pod -n production kubectl logs myapp-pod -n production --previous kubectl exec -it myapp-pod -n production -- bash kubectl exec -it myapp-pod -n production -- sh kubectl delete pod myapp-pod -n production
6.2 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 kubectl get deployments -n production kubectl describe deployment myapp-deployment -n production kubectl scale deployment myapp-deployment --replicas=5 -n production kubectl set image deployment/myapp-deployment \ myapp=myapp:v2 -n production --record kubectl rollout status deployment/myapp-deployment -n production kubectl rollout history deployment/myapp-deployment -n production kubectl rollout undo deployment/myapp-deployment -n production kubectl rollout undo deployment/myapp-deployment \ --to-revision=2 -n production kubectl rollout pause deployment/myapp-deployment -n production kubectl rollout resume deployment/myapp-deployment -n production
6.3 Service 操作 1 2 3 4 5 6 7 kubectl get services -n production kubectl get svc -n production kubectl describe service myapp-service -n production kubectl get endpoints -n production
6.4 配置操作 1 2 3 4 5 6 7 8 9 10 kubectl get configmap -n production kubectl describe configmap myapp-config -n production kubectl create configmap myapp-config --from-file=config.yaml -n production kubectl get secret -n production kubectl describe secret myapp-secret -n production kubectl create secret generic myapp-secret \ --from-literal=password=mypassword -n production
6.5 应用配置 1 2 3 4 5 6 7 8 9 10 11 kubectl apply -f k8s/ kubectl apply -f k8s/deployment.yaml kubectl delete -f k8s/ kubectl delete deployment myapp-deployment -n production kubectl get deployment myapp-deployment -n production -o yaml kubectl get deployment myapp-deployment -n production -o json
6.6 调试命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 kubectl port-forward pod/myapp-pod 8080:3000 -n production kubectl port-forward service/myapp-service 8080:80 -n production kubectl get events -n production --sort-by='.lastTimestamp' kubectl top pods -n production kubectl top nodes kubectl cluster-info kubectl get nodes kubectl describe node node-name
七、面试高频问题 7.1 Docker、Jenkins、K8s 三者如何配合? 答:
Docker 负责应用容器化,保证环境一致性
Jenkins 负责 CI/CD 流程自动化,串联整个流程
K8s 负责容器编排和管理,实现高可用和自动扩缩容
工作流程:
开发提交代码 → Git
Jenkins 自动触发构建
Jenkins 使用 Docker 构建镜像
Jenkins 推送镜像到仓库
Jenkins 通知 K8s 更新部署
K8s 拉取新镜像并滚动更新
7.2 K8s 滚动更新原理? 答:
创建新版本 Pod
等待新 Pod 就绪(readinessProbe)
将流量切换到新 Pod
删除旧版本 Pod
重复以上步骤,直到所有 Pod 更新完成
配置参数:
maxSurge: 最多可以多出几个 Pod(默认 25%)
maxUnavailable: 最多几个 Pod 不可用(默认 25%)
7.3 如何保证 K8s 应用的高可用? 答:
多副本部署 :replicas >= 3
健康检查 :配置 livenessProbe 和 readinessProbe
资源限制 :设置 requests 和 limits
反亲和性 :Pod 分散到不同节点
HPA 自动扩缩容 :应对流量波动
PDB(Pod Disruption Budget) :限制同时不可用的 Pod 数量
滚动更新 :零停机部署
7.4 CI/CD 流程中如何保证质量? 答:
代码检查 :ESLint、Prettier 等
单元测试 :Jest、Mocha 等
集成测试 :API 测试、E2E 测试
代码覆盖率 :要求达到一定比例
安全扫描 :镜像漏洞扫描
性能测试 :压力测试、负载测试
灰度发布 :先发布到部分用户
自动回滚 :失败时自动回滚到上一版本
7.5 如何优化 CI/CD 流程? 答:
并行构建 :测试、检查等并行执行
缓存依赖 :npm cache、Docker layer cache
增量构建 :只构建变更的部分
多阶段构建 :减小镜像体积
使用更快的 Agent :SSD、更多 CPU
清理旧资源 :定期清理旧镜像、旧构建
分支策略 :不同分支不同流程
八、总结 完整的 CI/CD 流程需要 Docker、Jenkins、Kubernetes 三者紧密配合:
Docker 提供容器化能力,保证环境一致性
Jenkins 提供自动化能力,串联整个流程
Kubernetes 提供编排能力,实现高可用和弹性伸缩
通过这套体系,可以实现:
✅ 代码提交后自动构建、测试、部署
✅ 零停机滚动更新
✅ 自动扩缩容应对流量波动
✅ 失败自动回滚
✅ 完整的监控和日志
这是现代化应用部署的标准实践。