CI/CD 自动部署
概述
传统 PLC 部署流程: 工程师现场插 U 盘 → IDE 打开工程 → 点编译 → 点下载. 这套流程在小型项目可以, 多站点 / 多版本 / 多工艺的大规模工厂完全不可接受: 版本追溯难 / 人为失误多 / 回滚慢 / 合规审计缺失.
DarraRT 设计了完整的CI/CD 自动部署链路, 把 PLC 项目纳入软件工程体系:
- Git 版本控制 + Code Review
- 流水线自动编译 / 测试 / 签名 / 打包
- 远程推送到设备仓库
- 灰度 / 蓝绿部署
- 一键回滚 + 完整审计
适用场景
| 场景 | 产线数 | 频率 | 是否必需 |
|---|---|---|---|
| 单机试产 | 1 | 偶尔 | 否 |
| 中等工厂 | 5-20 | 每月 | 推荐 |
| 大型集团 | 50+ | 周 / 日 | 必需 |
| 云化 PLC 即服务 | 1000+ | 连续 | 必需 |
架构图
┌── 开发者 ───┐
│ IDE 编辑 │
│ Git commit │
└──────┬──────┘
│ push
▼
┌─────────────────────────────────────────┐
│ Git 仓库 (GitLab / Gitea / GitHub) │
│ 触发 CI │
└──────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ CI Runner (GitLab Runner / Jenkins) │
│ 1. dotnet build (Darra.PLC.Compiler) │
│ 2. 单元测试 (FB 回归) │
│ 3. SIL 覆盖率检查 │
│ 4. 代码签名 │
│ 5. 产出 .darraproj.signed │
└──────┬──────────────────────────────────┘
│ upload
▼
┌─────────────────────────────────────────┐
│ 制品仓库 (S3 / Nexus / 私有 NAS) │
│ artifacts/line1/1.2.3/proj.signed │
└──────┬──────────────────────────────────┘
│ 触发 CD
▼
┌─────────────────────────────────────────┐
│ Fleet Manager (调度器) │
│ - 蓝绿 / 灰度策略 │
│ - 目标设备列表 │
└──────┬──────────────────────────────────┘
│ POST /api/deploy/push
▼
┌─────────────────────────────────────────┐
│ Service (设备端) │
│ 1. 接收 + 验证签名 │
│ 2. 冷启动新版本 │
│ 3. 健康探针 OK → 切流 │
│ 4. 旧版本保留作回滚快照 │
└─────────────────────────────────────────┘
前置条件
- DarraRT IDE ≥ 2.4 支持项目保存为 Git 友好的文本格式 (YAML + SCL)
Darra.PLC.CompilerCLI 已安装到 CI Runner (dotnet tool install -g Darra.PLC.Compiler)- 代码签名证书 (建议用硬件 HSM 保管)
- 设备端 Service 启用
/api/deploy/*端点 + mTLS
Git 工作流
分支策略
main ─────●────────●────────●───────► 生产
\ \ \
release/* ●─────────────────────●───► 版本发布
\
develop ●────●────●────●────●────●───●───► 集成
\ \ \
feature/* ● ● ● 日常特性
feature/xxx: 单一特性, 小步提交develop: 所有特性汇总, 部署到测试线release/*: 版本冻结 → 测试 → 合回 mainmain: 生产部署分支, 受保护, 只接受 PR
提交规范
<type>(<scope>): <subject>
<body>
Co-Authored-By: Claude <noreply@anthropic.com>
类型: feat / fix / refactor / perf / docs / test / ci
scope: plc / hmi / ecat / service
示例: feat(plc): 新增 FB_KalmanFilter 支持一维 Kalman
CI 步骤
示例: GitLab CI
# .gitlab-ci.yml
stages: [build, test, sign, publish]
variables:
VERSION: ${CI_COMMIT_TAG:-${CI_COMMIT_SHORT_SHA}}
build:
stage: build
image: mcr.microsoft.com/dotnet/sdk:8.0
before_script:
- dotnet tool install -g Darra.PLC.Compiler
script:
- darra-plcc build ./project -o ./out/proj.darraproj
- dotnet build ./src/Services/Darra.PLC.Service
-c Release -o ./out/service
artifacts:
paths: [out/]
expire_in: 7 days
unit_test:
stage: test
script:
- dotnet test ./tests/Darra.PLC.Tests
--logger "trx;LogFileName=unit.trx"
--collect:"XPlat Code Coverage"
- darra-plcc test ./project --fb-regression
artifacts:
reports: { junit: ./TestResults/unit.trx }
when: always
sil_check:
stage: test
script:
- darra-plcc sil-scan ./project --min-level SIL2
- darra-plcc coverage --fb "FB_Emergency*" --require 100
sign:
stage: sign
script:
- darra-pkg sign out/proj.darraproj
--cert ./certs/signing.pfx
--pass ${SIGN_PASS}
--tsa http://tsa.digicert.com
- mv out/proj.darraproj out/proj-${VERSION}.signed.darraproj
artifacts:
paths: [out/*.signed.darraproj]
publish:
stage: publish
only: [tags]
script:
- aws s3 cp out/proj-${VERSION}.signed.darraproj
s3://darrart-artifacts/line1/${VERSION}/
- curl -X POST https://fleet.acme.com/api/releases
-H "X-API-Key: ${FLEET_TOKEN}"
-d "{\"version\":\"${VERSION}\",\"line\":\"line1\"}"
SIL 等级检查
安全相关 FB (按 IEC 61508 / 61511) 必须做覆盖率检查:
darra-plcc sil-scan ./project --min-level SIL2
# 产出:
# ✓ FB_EStop (SIL3 标注, 测试覆盖率 100%)
# ✓ FB_LightCurtain (SIL2 标注, 覆盖率 97%)
# ✗ FB_DoorInterlock (SIL2 标注, 覆盖率 72%) — FAIL
签名
darra-pkg sign proj.darraproj \
--cert signing.pfx \
--pass $SIGN_PASS \
--tsa http://timestamp.digicert.com \
--algo sha256-rsa-pss
生成的文件签名 + 时间戳双重保护, 设备端用内置 CA 验证。
CD 步骤
推送到设备
POST /api/deploy/push HTTP/1.1
Host: device-01.acme.lan
Content-Type: multipart/form-data
Authorization: Bearer <ops_token>
X-Request-ID: <uuid>
--boundary
Content-Disposition: form-data; name="file"; filename="proj.signed.darraproj"
<binary>
--boundary
Content-Disposition: form-data; name="manifest"
{
"version": "1.2.3",
"strategy": "blue_green",
"rollback_keep": 3,
"health_check_url": "/api/health",
"activate_after_success": true
}
--boundary--
蓝绿部署
初始状态:
Active = 1.2.2 (blue) -- 正在运行, I/O 激活
Standby = 空 (green)
部署:
1. 上传 1.2.3 到 green slot
2. 冷启动 green 加载 1.2.3 (不出 I/O, 只跑仿真)
3. 健康探针: 扫描周期正常, 无异常, 连续 30s
4. 切换: green 接管 I/O, blue 转 standby
5. 观察 5 min, 无问题 → 删除 blue 1.2.2, 保留作回滚快照
如果第 3 步失败:
丢弃 green, blue 继续跑, 工程师看日志找原因
Fleet Manager 调度
Fleet Manager 管理多设备分发:
# fleet.yaml
fleets:
- name: line1-west
devices: [dev-01, dev-02, dev-03]
strategy: canary
canary_ratio: 0.33 # 先发 1 台
bake_time_s: 600 # 观察 10 min
auto_rollout: true # OK 则继续推
- name: line2-critical
devices: [dev-10, dev-11]
strategy: manual_approve # 必须人工确认
notify:
- type: feishu
webhook: ${FEISHU_URL}
触发:
darra-fleet deploy line1-west --version 1.2.3
回滚
# 查快照
darra-fleet snapshots dev-01
# 1.2.3 (current)
# 1.2.2 (2026-04-18T10:00Z)
# 1.2.1 (2026-04-15T14:00Z)
# 一键回滚
darra-fleet rollback dev-01 --to 1.2.2
内部流程:
- 上一版本快照已在 Standby slot
- 切换 Active → Standby (秒级)
- 如果快照损坏, 重新从制品仓下载
多工位分发 (Ansible)
# deploy.yml
- hosts: plc_line1
tasks:
- name: 下发新版本
uri:
url: "https://{{ inventory_hostname }}:18443/api/deploy/push"
method: POST
body_format: form-multipart
body:
file: "{{ lookup('file', 'proj-1.2.3.signed.darraproj') }}"
manifest: |
{"version":"1.2.3","strategy":"blue_green"}
status_code: 200
register: result
- name: 等待健康
uri:
url: "https://{{ inventory_hostname }}:18443/api/health"
status_code: 200
retries: 30
delay: 2
审计
所有部署事件落审计表:
CREATE TABLE t_deploy_audit (
id BIGSERIAL PRIMARY KEY,
device_id TEXT NOT NULL,
version TEXT NOT NULL,
action TEXT NOT NULL, -- deploy | rollback | approve
operator TEXT NOT NULL,
ts TIMESTAMPTZ NOT NULL DEFAULT now(),
request_id UUID,
result TEXT NOT NULL, -- success | fail | cancelled
error_msg TEXT,
diff JSONB -- 版本差异摘要
);
报告: 每周给质量主管邮件, 列出全部变更 + 谁批的。
排错
| 现象 | 原因 | 处理 |
|---|---|---|
| CI 编译超时 | PLC 工程太大 | 分库并行 / 缓存 NuGet |
| 签名失败 | 证书过期 | 续期, CI 环境变量更新 |
| 推送 401 | 设备 token 错 | Fleet Manager 重发 |
| 健康探针失败 | 新版本 bug | 看 service 日志, 回滚 |
| 双活冲突 | 蓝绿切换时 I/O 被两实例驱动 | 检查 Active/Standby 锁 |
| 回滚失败 | 快照被清了 | 从制品仓重新下, rollback_keep >= 3 |
高级技巧
- 版本命名语义化:
major.minor.patch[-<branch>-<build>],1.2.3-feature-AB-42 - 热更 vs 冷更: 非关键改动用热更 (运行时重载), 结构性改动用冷启动
- DR 演练: 每月故意部署坏版本, 验证自动回滚触发
- 金丝雀指标: 部署后 5 min 内扫描周期 / 报警数异常则自动回滚
- GitOps: 设备端 Service 订阅 Git 仓库, 看到新 tag 自动拉取