一、为什么要在Kubernetes上搞CI/CD?
想象一下,你开发了一个很棒的应用。传统上,你要发布新功能,可能需要经历:在自己电脑上打包 -> 上传到服务器 -> 手动执行一堆命令重启服务 -> 祈祷一切正常。这个过程不仅慢,还容易出错,尤其是在服务越来越多、更新越来越频繁的时候。
Kubernetes(我们常叫它K8s)就像一个超级智能的机器人管家,它帮你管理成百上千个应用容器(可以想象成一个个打包好的、标准化的应用盒子)。而CI/CD(持续集成/持续部署)则是一套自动化流水线,目标是从你写完代码到应用上线,全程自动化,又快又稳。
把这两者结合起来,就像是给这位机器人管家配上了一套全自动的“代码到上线”生产线。你提交代码,流水线自动测试、打包、并指挥K8s管家安全地更新应用,全程无需人工干预。这不仅能让你每天发布多次,还能大大减少半夜被报警叫起来处理线上问题的痛苦。
二、核心蓝图:一条典型的K8s CI/CD流水线长什么样?
一条完整的、基于K8s的CI/CD流水线,通常包含以下几个核心阶段,它们像流水线一样环环相扣:
- 代码提交与触发:开发者将代码推送到Git仓库(如GitLab、GitHub)。
- 持续集成(CI):流水线被自动触发,拉取代码,进行代码质量检查、单元测试、编译构建,最终生成一个可部署的容器镜像,并推送到镜像仓库(如Docker Hub、Harbor)。
- 持续部署(CD):流水线从镜像仓库拉取新版本的镜像,然后通过更新K8s的配置文件(通常是YAML文件),指挥K8s集群安全地滚动更新你的应用。
- 验证与回滚:更新后,自动进行健康检查或简单的集成测试。如果发现问题,可以快速、自动地回滚到上一个稳定版本。
这个流程的核心思想是:一切皆代码。不仅应用代码是代码,应用的构建方式(Dockerfile)、部署配置(K8s YAML)、甚至流水线本身(Jenkinsfile, .gitlab-ci.yml)都是代码,可以被版本管理、评审和复用。
三、手把手实战:用GitLab CI搭建一条完整流水线
下面,我将用一个完整的示例,展示如何使用 GitLab CI 这一技术栈,为一个简单的Web应用构建一条部署到K8s的流水线。
技术栈声明: 本篇所有示例均基于 GitLab CI + Docker + Kubernetes 技术栈。
假设我们有一个用Python Flask写的“Hello World”应用。
第一步:准备应用代码和Dockerfile
我们的项目目录结构如下:
my-app/
├── app.py # Flask应用代码
├── requirements.txt # Python依赖
├── Dockerfile # 构建镜像的说明书
└── .gitlab-ci.yml # GitLab CI流水线定义文件
app.py 内容:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, Kubernetes CI/CD!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Dockerfile 内容:
# 使用官方Python轻量级镜像作为基础
FROM python:3.9-slim
# 设置工作目录,后续命令都在这个目录下执行
WORKDIR /app
# 将依赖文件复制到容器中
COPY requirements.txt .
# 安装Python依赖,使用清华镜像源加速
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
# 将应用代码复制到容器中
COPY . .
# 声明容器运行时监听的端口
EXPOSE 5000
# 定义容器启动时执行的命令
CMD ["python", "app.py"]
第二步:定义GitLab CI流水线(.gitlab-ci.yml)
这是流水线的“总指挥脚本”。我们定义四个阶段:构建、测试、打包-推送镜像、部署到K8s。
# 定义流水线的各个阶段,按顺序执行
stages:
- build
- test
- build-push-image
- deploy
# 一些全局变量,比如镜像的标签名,用提交ID的前8位,确保唯一性
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA # 使用GitLab容器仓库和提交哈希作为标签
K8S_NAMESPACE: "my-app-prod" # 定义K8s中的命名空间
# 第一阶段:构建
build-job:
stage: build
image: python:3.9-slim # 使用Python镜像来运行这个Job
script:
- echo "开始安装依赖并检查环境..."
- pip install -r requirements.txt
only:
- main # 只有推送到main分支才触发
# 第二阶段:测试(这里以简单的单元测试为例)
test-job:
stage: test
image: python:3.9-slim
script:
- pip install -r requirements.txt
- python -m pytest test_app.py --verbose # 假设我们有一个test_app.py的测试文件
only:
- main
# 第三阶段:构建Docker镜像并推送到GitLab内置的容器仓库
build-image:
stage: build-push-image
image: docker:latest # 这个Job本身需要在一个有Docker环境的容器里运行
services:
- docker:dind # 使用Docker-in-Docker服务,让这个Job可以执行docker命令
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
script:
- echo "登录到GitLab容器仓库..."
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- echo "构建Docker镜像..."
- docker build -t $IMAGE_TAG .
- echo "将镜像推送到仓库..."
- docker push $IMAGE_TAG
only:
- main
# 第四阶段:部署到Kubernetes集群
deploy-to-k8s:
stage: deploy
image: bitnami/kubectl:latest # 使用包含kubectl命令的镜像
script:
- echo "配置kubectl,连接到目标K8s集群..."
# 这里通常需要配置kubeconfig文件,内容来自CI/CD变量,避免密钥硬编码
- mkdir -p ~/.kube
- echo "$KUBE_CONFIG" > ~/.kube/config # KUBE_CONFIG是一个在GitLab UI中设置的、包含集群访问凭证的变量
- chmod 600 ~/.kube/config
- echo "更新Kubernetes部署清单中的镜像标签..."
# 使用sed命令动态替换deployment.yaml文件中的镜像标签为本次构建的镜像
- sed -i "s|IMAGE_PLACEHOLDER|$IMAGE_TAG|g" k8s/deployment.yaml
- echo "应用部署配置到命名空间 $K8S_NAMESPACE ..."
- kubectl apply -f k8s/ -n $K8S_NAMESPACE
- echo "等待部署完成..."
- kubectl rollout status deployment/my-app-deployment -n $K8S_NAMESPACE --timeout=120s
- echo "部署成功!"
only:
- main
# 部署通常需要手动点击确认,增加安全阀
when: manual
第三步:准备Kubernetes部署配置文件
在项目根目录创建 k8s/ 文件夹,里面存放K8s的YAML文件。
k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
replicas: 2 # 我们希望运行2个实例,确保高可用
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: IMAGE_PLACEHOLDER # 这个占位符会被CI流水线中的sed命令替换成真实的镜像地址
ports:
- containerPort: 5000
# 添加健康检查,这是生产环境最佳实践
livenessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80 # 集群内其他服务访问的端口
targetPort: 5000 # 容器内应用实际监听的端口
type: ClusterIP # 类型为ClusterIP,仅在集群内部可访问
现在,当你将代码推送到GitLab的main分支时,这条流水线就会自动运行:构建环境 -> 运行测试 -> 打包成镜像 ->(等待你手动确认后)部署到Kubernetes集群。K8s会优雅地启动新版本的Pod,并等它们通过健康检查后,再替换掉旧版本的Pod,实现零停机更新。
四、必须掌握的最佳实践与避坑指南
光搭起来还不够,要让它健壮、安全、高效,你需要关注以下几点:
1. 镜像标签策略
不要总是用 latest 标签。像示例中一样,使用提交ID、构建时间戳或语义化版本(如 v1.2.3)作为标签。这能让你精确知道线上跑的是哪次代码提交,回滚时也一目了然。
2. 完善的健康检查(探针)
示例中在Deployment里配置了 livenessProbe(存活探针)和 readinessProbe(就绪探针)。这是K8s保障应用健康的“生命监护仪”。存活探针失败,K8s会重启容器;就绪探针失败,K8s会暂时将Pod从服务负载均衡中移除,直到它恢复健康。这能有效避免新版应用有Bug时,把流量导过去导致全线崩溃。
3. 密钥管理 绝对不要 把密码、API密钥、K8s配置文件等敏感信息直接写在代码或CI脚本里!应该使用CI/CD平台(如GitLab的CI/CD Variables、GitHub Secrets)或专业的密钥管理工具(如HashiCorp Vault、AWS Secrets Manager)来存储,并在流水线中以环境变量的方式注入。
4. 采用蓝绿部署或金丝雀发布 直接滚动更新是基础。对于更重要的应用,可以采用更高级的策略。
- 蓝绿部署:准备两套完全一样的环境(蓝环境和绿环境)。当前流量在蓝环境,你把新版本部署到绿环境并测试无误后,一次性将流量从蓝环境切换到绿环境。切换快,回滚也快(直接切回蓝环境即可)。
- 金丝雀发布:像矿工用金丝雀探毒一样,先让一小部分(比如5%)的用户流量访问新版本,观察监控和错误率。如果一切正常,再逐步扩大新版本流量比例,直至完全替换。这可以最大限度降低新版本故障的影响范围。在K8s中,可以通过Service Mesh(如Istio)或Ingress Controller(如Nginx Ingress)的流量切分功能轻松实现。
5. 一切皆代码与代码审查 不仅应用代码要Review,Dockerfile、K8s YAML、CI配置文件都要纳入版本库,并经过同事的代码审查。这能保证部署流程的可追溯性和团队知识共享。
五、应用场景、优缺点与总结
应用场景:
- 微服务架构:这是K8s CI/CD的主战场,成百上千个服务需要自动化、标准化的发布流程。
- 快速迭代的互联网产品:需要每天甚至每天多次发布新功能或修复。
- 需要高可用和弹性伸缩的业务:CI/CD配合K8s的自动化运维能力,能轻松应对流量波动。
技术优点:
- 高度自动化:解放人力,减少人为失误。
- 快速反馈:开发能立即知道代码集成和部署是否成功。
- 部署一致且可靠:从开发到生产,环境高度统一。
- 轻松回滚:版本化的镜像和配置使得回滚操作像部署一样简单。
注意事项与挑战:
- 学习曲线陡峭:需要同时掌握Docker、K8s、CI/CD工具和云原生生态的知识。
- 初始复杂度高:搭建和维护一套稳定的K8s集群及配套的CI/CD流水线需要投入。
- 成本:可能需要云上K8s服务或自建集群的运维成本,以及镜像仓库等服务的费用。
- 安全:自动化流水线如果被攻破,攻击者将获得巨大的破坏力,因此密钥管理和权限控制至关重要。
文章总结: 构建基于Kubernetes的CI/CD流水线,是现代软件交付的“高速公路”。它通过将构建、测试、部署全过程自动化、代码化,并与K8s强大的容器编排能力深度集成,实现了发布流程的标准化、快速化和可靠化。核心在于遵循“一切皆代码”的理念,并落实镜像标签、健康检查、密钥管理、高级部署策略等最佳实践。虽然入门有一定门槛,但它为团队带来的开发效率提升、系统稳定性保障和业务敏捷性,使其成为云原生时代不可或缺的基石能力。从一条简单的流水线开始,逐步实践和优化,你就能驾驭这套强大的自动化引擎。
评论