嘿,朋友们!今天我们来聊一个能让你开发部署流程瞬间“高大上”起来的技术组合: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.txt和RUN 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 build和docker 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 仓库时,神奇的事情就发生了:
- 提交代码:你修改了
app.py里的返回文字,并推送到一个功能分支。 - 触发流水线:GitLab CI/CD 检测到代码提交,立刻开始执行
.gitlab-ci.yml中定义的流水线。 - 构建 & 测试:
build_image作业开始执行,它构建了一个新的 Docker 镜像。紧接着,run_tests作业启动,使用这个新镜像运行pytest,确保你的修改没有引入 Bug。 - 合并到主分支:代码评审通过后,你将功能分支合并到
main分支。 - 发布 & 部署:这次合并触发了
main分支的流水线。除了构建和测试,release_image作业会给镜像打上版本号,然后deploy_to_server作业会自动把这个新版本的镜像部署到你的服务器上。
整个过程无需你手动干预,几分钟之内,你的代码就从本地的编辑器,安全、可靠地到达了生产环境,为你的用户提供服务。
总结
看,集成 Docker 到 CI/CD 流程其实并不复杂。核心思想就是:
- 用
Dockerfile定义你的应用环境。 - 在 CI/CD 流水线中,先构建这个
Dockerfile生成镜像。 - 后续的测试、部署等所有步骤,都直接使用这个镜像来完成。
这样一套组合拳下来,不仅能极大地提升你的开发和部署效率,还能让你的应用更加健壮和可靠。还在等什么?赶紧动手试试吧!