让你的CI/CD飞起来:轻松集成Docker的保姆级教程


嘿,朋友们!今天我们来聊一个能让你开发部署流程瞬间“高大上”起来的技术组合:CI/CD + Docker。是不是听起来就觉得很酷?别急,这篇文章会用最简单直白的方式,带你一步步把 Docker 集成到你的 CI/CD 流程中,让你的代码从提交到上线的全过程,如丝般顺滑。

为啥要在 CI/CD 里用 Docker?它到底香在哪?

在咱们开始动手之前,先得搞明白一个问题:为啥要多此一举,把 Docker 这个东西加到本来就能跑的 CI/CD 流程里呢?答案很简单,因为它能解决很多让人头疼的问题。

  • 环境一致性,告别“在我这儿明明是好的”:这绝对是每个程序员都听过或者说过的话。Docker 通过容器化技术,将你的应用和它所需要的所有依赖、配置、库文件全部打包在一起。 这样一来,无论是在开发人员的笔记本上,还是在测试服务器、生产服务器上,环境都一模一样,彻底告别因为环境不同导致的各种奇葩 Bug。

  • 更快的构建和部署:Docker 镜像是分层的,在 CI/CD 流程中,只有代码或者依赖发生变化的那一层需要重新构建,这大大加快了构建速度。部署的时候,也只是启动一个容器的事儿,比传统的部署方式快了不知道多少倍。

  • 轻松实现弹性伸缩:需要扩容?多启动几个 Docker 容器就行了。需要回滚?把旧版本的镜像重新跑起来就完事了。就是这么简单粗暴。

  • 流程更清晰,职责更分明:开发人员只需要关注自己的代码和 Dockerfile,运维人员则更专注于容器的编排和管理,大家各司其职,合作起来也更愉快。

实战开始:三步走,把 Docker 集成到 CI/CD

好了,理论说完了,咱们卷起袖子加油干!这里我们以一个简单的 Python Web 应用为例,看看如何把它通过 Docker 集成到 GitLab CI/CD 流程中。

第一步:编写一个简单的 Dockerfile

Dockerfile 就像一个食谱,告诉 Docker 如何一步步地制作我们应用的可口“大餐”——也就是 Docker 镜像。

在你的项目根目录下,创建一个名为 Dockerfile 的文件,内容如下:

# 1. 选择一个包含 Python 3.8 的官方基础镜像
FROM python:3.8-slim-buster

# 2. 在容器内创建一个工作目录
WORKDIR /app

# 3. 将项目依赖文件拷贝到工作目录
COPY requirements.txt requirements.txt

# 4. 安装项目依赖
RUN pip install -r requirements.txt

# 5. 将项目所有文件拷贝到工作目录
COPY . .

# 6. 暴露容器的 5000 端口,让外部可以访问
EXPOSE 5000

# 7. 容器启动时运行的命令
CMD ["python", "app.py"]

代码解析:

  • FROM python:3.8-slim-buster:我们的应用是基于 Python 3.8 的,所以我们选择一个官方的、精简的 Python 3.8 镜像作为基础。
  • WORKDIR /app:在容器里创建一个 /app 目录,并把它设置为我们之后所有操作的工作目录。
  • COPY requirements.txt requirements.txtRUN pip install -r requirements.txt:这是 Docker 构建缓存的经典用法。我们先把依赖文件 requirements.txt 拷进去并安装,这样只要这个文件不变,下次构建时就可以直接使用缓存,大大加快了构建速度。
  • COPY . .:把当前目录下的所有文件(也就是我们的项目代码)都拷贝到容器的 /app 目录。
  • EXPOSE 5000:声明这个容器会使用 5000 端口。
  • CMD ["python", "app.py"]:当容器启动后,默认执行 python app.py 这个命令来运行我们的 Web 应用。

第二步:配置 CI/CD 流水线 (.gitlab-ci.yml)

现在,我们需要告诉 CI/CD 系统,当代码提交后,应该执行哪些操作。在 GitLab CI/CD 中,我们通过在项目根目录下创建一个 .gitlab-ci.yml 文件来实现。

# 定义流水线包含的阶段
stages:
  - build
  - test
  - release
  - deploy

# 构建阶段:构建 Docker 镜像并推送到镜像仓库
build_image:
  stage: build
  image: docker:20.10.16 # 使用官方的 Docker 镜像作为执行环境
  services:
    - docker:20.10.16-dind # 启动一个 "Docker in Docker" 服务
  script:
    - echo "开始构建 Docker 镜像..."
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $CI_REGISTRY_IMAGE:latest .
    - docker push $CI_REGISTRY_IMAGE:latest
    - echo "镜像推送成功!"

# 测试阶段:运行单元测试
run_tests:
  stage: test
  image: $CI_REGISTRY_IMAGE:latest # 使用我们上一步构建的镜像
  script:
    - echo "开始运行单元测试..."
    - python -m pytest
    - echo "测试通过!"

# 发布阶段:给镜像打上版本标签
release_image:
  stage: release
  image: docker:20.10.16
  services:
    - docker:20.10.16-dind
  script:
    - echo "开始打版本标签..."
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker pull $CI_REGISTRY_IMAGE:latest
    - docker tag $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - echo "版本标签推送成功!"
  only:
    - main # 只在 main 分支执行

# 部署阶段:将镜像部署到服务器
deploy_to_server:
  stage: deploy
  image: alpine # 使用一个轻量的镜像
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
  script:
    - echo "开始部署到服务器..."
    - ssh -o StrictHostKeyChecking=no user@your-server.com "
        docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY &&
        docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA &&
        docker stop my-app || true &&
        docker rm my-app || true &&
        docker run -d --name my-app -p 80:5000 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      "
    - echo "部署成功!"
  only:
    - main # 只在 main 分支执行

代码解析:

  • stages:定义了整个 CI/CD 流程分为四个阶段:构建、测试、发布、部署。
  • build_image 作业:
    • 使用 docker:20.10.16 镜像和 docker:dind 服务来提供一个可以在 CI/CD 环境中运行 Docker 命令的环境。
    • 通过 docker login 登录到 GitLab 自带的镜像仓库($CI_REGISTRY 等是 GitLab 预设的变量)。
    • docker builddocker push 就是我们熟悉的构建和推送镜像的命令。
  • run_tests 作业:
    • 直接使用上一步构建好的应用镜像作为运行环境,这保证了测试环境和最终运行环境的绝对一致!
    • 运行 pytest 来执行单元测试。
  • release_image 作业:
    • 当代码合并到 main 分支后,这个作业会给 latest 镜像打上一个唯一的提交哈希($CI_COMMIT_SHA)作为版本标签,这样我们就可以追溯每个镜像对应的代码版本。
  • deploy_to_server 作业:
    • 这也是一个只在 main 分支运行的作业。
    • 通过 SSH 连接到你的生产服务器。这里需要提前在 GitLab CI/CD 的变量设置中配置好服务器的 SSH 私钥($SSH_PRIVATE_KEY)。
    • 在服务器上,拉取刚刚打好版本标签的镜像,然后停掉旧的容器,启动新的容器,完成部署。

第三步:准备你的应用代码和测试

现在,万事俱备,只欠东风。我们来准备一个简单的 Python Flask 应用和测试用例。

app.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, Dockerized CI/CD!'

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

requirements.txt

Flask==2.0.1
pytest==6.2.4

test_app.py

from app import app

def test_hello():
    client = app.test_client()
    response = client.get('/')
    assert response.status_code == 200
    assert b"Hello, Dockerized CI/CD!" in response.data

案例:一次完整的 CI/CD 流程

现在,当你把这三个文件(app.py, requirements.txt, test_app.py)以及前面写的 Dockerfile.gitlab-ci.yml 一起提交到你的 GitLab 仓库时,神奇的事情就发生了:

  1. 提交代码:你修改了 app.py 里的返回文字,并推送到一个功能分支。
  2. 触发流水线:GitLab CI/CD 检测到代码提交,立刻开始执行 .gitlab-ci.yml 中定义的流水线。
  3. 构建 & 测试build_image 作业开始执行,它构建了一个新的 Docker 镜像。紧接着,run_tests 作业启动,使用这个新镜像运行 pytest,确保你的修改没有引入 Bug。
  4. 合并到主分支:代码评审通过后,你将功能分支合并到 main 分支。
  5. 发布 & 部署:这次合并触发了 main 分支的流水线。除了构建和测试,release_image 作业会给镜像打上版本号,然后 deploy_to_server 作业会自动把这个新版本的镜像部署到你的服务器上。

整个过程无需你手动干预,几分钟之内,你的代码就从本地的编辑器,安全、可靠地到达了生产环境,为你的用户提供服务。

总结

看,集成 Docker 到 CI/CD 流程其实并不复杂。核心思想就是:

  1. Dockerfile 定义你的应用环境。
  2. 在 CI/CD 流水线中,先构建这个 Dockerfile 生成镜像。
  3. 后续的测试、部署等所有步骤,都直接使用这个镜像来完成。

这样一套组合拳下来,不仅能极大地提升你的开发和部署效率,还能让你的应用更加健壮和可靠。还在等什么?赶紧动手试试吧!


  目录