一、为什么要在Kubernetes上搞CI/CD?

想象一下,你开发了一个很棒的应用。传统上,你要发布新功能,可能需要经历:在自己电脑上打包 -> 上传到服务器 -> 手动执行一堆命令重启服务 -> 祈祷一切正常。这个过程不仅慢,还容易出错,尤其是在服务越来越多、更新越来越频繁的时候。

Kubernetes(我们常叫它K8s)就像一个超级智能的机器人管家,它帮你管理成百上千个应用容器(可以想象成一个个打包好的、标准化的应用盒子)。而CI/CD(持续集成/持续部署)则是一套自动化流水线,目标是从你写完代码到应用上线,全程自动化,又快又稳。

把这两者结合起来,就像是给这位机器人管家配上了一套全自动的“代码到上线”生产线。你提交代码,流水线自动测试、打包、并指挥K8s管家安全地更新应用,全程无需人工干预。这不仅能让你每天发布多次,还能大大减少半夜被报警叫起来处理线上问题的痛苦。

二、核心蓝图:一条典型的K8s CI/CD流水线长什么样?

一条完整的、基于K8s的CI/CD流水线,通常包含以下几个核心阶段,它们像流水线一样环环相扣:

  1. 代码提交与触发:开发者将代码推送到Git仓库(如GitLab、GitHub)。
  2. 持续集成(CI):流水线被自动触发,拉取代码,进行代码质量检查、单元测试、编译构建,最终生成一个可部署的容器镜像,并推送到镜像仓库(如Docker Hub、Harbor)。
  3. 持续部署(CD):流水线从镜像仓库拉取新版本的镜像,然后通过更新K8s的配置文件(通常是YAML文件),指挥K8s集群安全地滚动更新你的应用。
  4. 验证与回滚:更新后,自动进行健康检查或简单的集成测试。如果发现问题,可以快速、自动地回滚到上一个稳定版本。

这个流程的核心思想是:一切皆代码。不仅应用代码是代码,应用的构建方式(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强大的容器编排能力深度集成,实现了发布流程的标准化、快速化和可靠化。核心在于遵循“一切皆代码”的理念,并落实镜像标签、健康检查、密钥管理、高级部署策略等最佳实践。虽然入门有一定门槛,但它为团队带来的开发效率提升、系统稳定性保障和业务敏捷性,使其成为云原生时代不可或缺的基石能力。从一条简单的流水线开始,逐步实践和优化,你就能驾驭这套强大的自动化引擎。