Django-云环境中的持续集成
这篇讲什么
- 持续集成的价值不在于「有个流水线图标」,而在于把「上线前该做的检查」变成机器每次都会做的动作。这篇把 Django 项目里真正该进流水线的六条命令逐条实测了一遍退出码——包括两个会让人踩坑的地方:
check --deploy默认不会让流水线失败,测试库残留会让manage.py test在 CI 里卡在交互提问。 - Jenkins、GitHub Actions、阿里云密钥控制台这些平台侧的配置按官方文档给可复制写法,本机没有容器运行时和云账号,这部分未验证。
- 实测环境:WSL2 Ubuntu 26.04.1 LTS + Python 3.14.4 + Django 5.2.19 LTS + MySQL 9.7.4 + ruff 0.17.3 + black 26.10.1 + Coverage.py 7.17.2。
- 平台侧(Jenkins / GitHub Actions / ACK / ACR / SLS)只给配置写法,未在本机执行。
CI/CD 的三段与部署策略
1.1 流程
- Build & Package:装依赖、构建镜像或产物。
- Test:静态检查、单元测试、迁移一致性、部署体检。
- Deployment:把产物推到目标环境,并做可回滚的切换。

CD 阶段的三种切换策略
| 策略 | 做法 | 优点 | 代价 |
|---|---|---|---|
| Rolling Upgrade 滚动更新 | 多实例逐个下线升级 | 资源占用小,k8s 默认 | 新老版本会短暂共存,迁移必须双向兼容 |
| Blue/Green 蓝绿 | 新集群整体部署完再切流量 | 回滚就是切回旧集群,快 | 需要双份资源 |
| Canary 金丝雀 | 先放 1% 流量到新版本,观察再放量 | 风险最小 | 需要流量切分与指标基线(见《Django-接入监控告警》) |

滚动更新对 Django 的硬约束是:数据库迁移不能破坏上一版代码(加列可空、删列分两次发布),细节见《Django-生产环境要注意哪些事项》3.5。
二、流水线里该跑的六条命令(实测退出码)
先给结论表,后面逐条给实测输出。所有退出码都是本机跑的:
| 命令 | 作用 | 通过时 | 不通过时 |
|---|---|---|---|
ruff check . |
静态错误与风格 | 0 | 1 |
black --check . |
格式一致性 | 0 | 1 |
python manage.py makemigrations --check --dry-run |
有没有忘了生成迁移 | 0 | 1 |
python manage.py check --deploy --fail-level WARNING |
生产配置体检 | 0 | 1 |
python manage.py test --noinput |
单元测试 | 0 | 1 |
coverage report --fail-under=80 |
覆盖率闸门 | 0 | 2 |
静态检查:ruff
1 | $ ruff check core proj manage.py |
典型的一条提示(Python 3 已经不需要编码声明):
1 | 1 | # -*- coding: utf-8 -*- |
ruff 是 Rust 实现、单文件配置,官方文档见参考链接;--fix 只修安全项,--unsafe-fixes 要人工复核。
格式:black
1 | $ black --check core proj manage.py |
一个和 Django 有关的实测冷知识(前面批次踩过):startproject 生成的文件是否被格式化,取决于执行环境里有没有装 black(Django 会 shutil.which("black") 后调用格式化器),和 Django 版本无关。所以同一个 Django 版本在两台机器上生成的 settings.py 排版可能不一样,CI 里最好把 black 固定成依赖。
迁移一致性
1 | $ python manage.py makemigrations --check --dry-run |
改了模型但忘了生成迁移时:
1 | $ python manage.py makemigrations --check --dry-run |
这条命令的意义:把「代码里有模型变更、仓库里没有对应迁移文件」变成构建失败,而不是等上线后 OperationalError: no such column。
2.4 部署体检:必须加 --fail-level
1 | $ python manage.py check --deploy --settings=proj.settings.prod |
默认有 warning 也返回 0,流水线会绿灯通过——这是最容易误会的点。要真的拦住:
1 | $ python manage.py check --deploy --fail-level WARNING ; echo $? |
--fail-level ERROR 同样返回 0(warning 仍不算 error),所以阈值要写 WARNING。
测试:--noinput 不是可选项
第一次踩坑现场:上一轮用了 --keepdb,测试库 test_django_note_deploy 留在实例里,这一轮不带 --noinput:
1 | $ python manage.py test --settings=proj.settings.dev < /dev/null |
CI 里没有 tty,Django 的交互提问直接 EOFError,看起来像「测试挂了」其实是命令写法问题。加上 --noinput:
1 | $ python manage.py test --noinput --settings=proj.settings.dev ; echo $? |
失败用例的退出码也实测过:
1 | $ python manage.py test core.tests_fail_tmp --noinput ; echo $? |
覆盖率闸门
1 | $ coverage run --source=core,proj manage.py test --noinput --settings=proj.settings.dev |
1 | $ coverage report --fail-under=20 ; echo $? |
注意阈值不达标时退出码是 2(不是 1),如果你的流水线只判断 == 1 会漏掉。
2.7 一个 shell 层的坑:管道之后取 $?
上面所有退出码都是「命令直接结束就取 $?」。如果写成 ruff check . | tail -5; echo $?,拿到的是 tail 的退出码(永远 0),流水线会假装通过。本轮实测就中过一次:ruff 明明报了 33 个错,脚本却显示退出码 0。要么不加管道,要么用 set -o pipefail。
三、GitHub Actions 写法(官方文档,未在本机验证)
.github/workflows/ci.yml:
1 | name: ci |
要点(都是第二节实测结论的直接应用):
services给 MySQL 配 healthcheck,否则第一条 SQL 会撞在还没就绪的库上。DJANGO_SECRET_KEY在 CI 里用一个专用假值即可(体检只看长度),不要把生产密钥放进 workflow。--fail-level WARNING与--noinput都要写全。- 本机没有 GitHub Actions 运行环境,这份 YAML 未执行验证,字段版本以官方文档为准。
四、Jenkins 流水线写法(官方文档,未在本机验证)
Jenkinsfile(declarative):
1 | pipeline { |
老笔记里那句「Jenkins 的启动日志里面会自动产生初始化的 admin 密码」是对的(官方文档也是这个说法),但生产上应该用 --java-jar 参数或 K8s Secret 固定管理员密码,别每次从日志里捞。
kubectl rollout status 是「等滚动更新真的完成」的关键,否则流水线会在 Pod 还没就绪时就报成功。
4.1 工具对照
| 工具 | 形态 | 适合 |
|---|---|---|
| Jenkins | 自建,Pipeline as Code | 需要精细控制、内网构建、和 k8s 深度集成 |
| GitHub Actions | 仓库内 YAML + 托管 runner | 开源项目、GitHub 生态、零运维 |
| GitLab CI | .gitlab-ci.yml + Runner |
代码托管与 CI 一体的团队 |
| Spinnaker / Harness | 专职 CD 平台 | 多集群、多区域复杂发布策略 |
老笔记把「CICD 工具」列成 Jenkins/Spinnaker/Harness 三项,缺了最常用的 GitHub Actions 与 GitLab CI,实际选型优先看代码托管在哪。

五、密钥不进仓库:Kubernetes Secrets
5.1 原则
- 镜像构建不依赖本地文件(
COPY的东西都在仓库里,密钥不能进去)。 - 账号密码不进代码库、不进 Dockerfile、不进
docker-compose.yml的明文段。 - 存放位置:Kubernetes Secrets、Vault、云 KMS/凭据管理;运行时通过环境变量注入。
5.2 命令(官方文档口径,未在本机执行)
老笔记里 --fromliteral= 少了连字符,正确写法:
1 | $ kubectl create secret generic prod-db-secret \ |
在 Deployment 里引用:
1 | env: |
代码侧仍然是熟悉的那一套:
1 | import os |
三个必须知道的限制:
- Secret 默认只是 base64 编码,不是加密;要开 etcd 静态加密或外部 KMS。
kubectl describe pod会把环境变量名打出来(值显示为<set>),日志里不小心打印就泄漏。- Secret 权限要用 RBAC 收紧,别给所有 ServiceAccount 读全命名空间的权限。
5.3 阿里云 ACK 上的做法(控制台操作,未验证)
- 集群「配置管理 → 保密字典」创建 Secret。
- 工作负载 YAML 里引用
secretKeyRef。 - Python 代码仍然只读环境变量(第 5.2 节那段),换平台不用改代码。



六、在 k8s 里跑 Jenkins(官方文档口径,未验证)
老笔记的方式是 Docker-in-Docker 镜像部署到集群:
1 | $ docker pull myjenkins/blueocean:latest |
说明两点:
- 原文用的是个人镜像
ihopeit/jenkins-dod:1.1,长期维护性差;现在应优先用官方jenkins/jenkins或myjenkins/blueocean,DinD 场景还要给--privileged或改用 Kaniko/buildah 这类无 daemon 构建方案。 - 流程总结仍然是对的:建 Ingress 路由 → 配置 kubeconfig 与镜像仓库凭据 → 建 Pipeline 项目 → 触发构建。

落地顺序建议
- 先把第二节的六条命令在本地跑通,确认每条的退出码符合预期(这是流水线的骨架)。
- 把命令搬进 CI:先只跑 lint + test,稳定后再加
check --deploy --fail-level WARNING和覆盖率闸门。 - 密钥全部走环境变量注入,仓库里只留
.env.example。 - 加镜像构建与推送(这一步才需要容器运行时)。
- 部署阶段加
kubectl rollout status或云上的发布等待,别「apply 完就报成功」。 - 发布策略从滚动更新起步,量大了再上蓝绿/金丝雀,并保证迁移双向兼容。
参考
- Ruff 官方文档:规则集、
--fix与--unsafe-fixes。 - Black 官方文档:格式化约定与
--check。 - Coverage.py 官方文档:
--fail-under与coverage xml。 - Django 官方:check 框架:
--deploy、--fail-level的语义。 - Django 官方:makemigrations:
--check与--dry-run。 - Django 官方:运行测试与测试工具:
--noinput、--keepdb。 - GitHub Actions 官方文档:workflow 语法、services 容器与 healthcheck。
- Jenkins 官方文档:Pipeline 语法与凭据管理。
- Kubernetes Secrets 官方文档:创建方式、引用方式与安全边界。