跳到主要内容

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.Compiler CLI 已安装到 CI Runner (dotnet tool install -g Darra.PLC.Compiler)
  • 代码签名证书 (建议用硬件 HSM 保管)
  • 设备端 Service 启用 /api/deploy/* 端点 + mTLS

Git 工作流

分支策略

main      ─────●────────●────────●───────►  生产
\ \ \
release/* ●─────────────────────●───► 版本发布
\
develop ●────●────●────●────●────●───●───► 集成
\ \ \
feature/* ● ● ● 日常特性
  • feature/xxx: 单一特性, 小步提交
  • develop: 所有特性汇总, 部署到测试线
  • release/*: 版本冻结 → 测试 → 合回 main
  • main: 生产部署分支, 受保护, 只接受 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

内部流程:

  1. 上一版本快照已在 Standby slot
  2. 切换 Active → Standby (秒级)
  3. 如果快照损坏, 重新从制品仓下载

多工位分发 (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

高级技巧

  1. 版本命名语义化: major.minor.patch[-<branch>-<build>], 1.2.3-feature-AB-42
  2. 热更 vs 冷更: 非关键改动用热更 (运行时重载), 结构性改动用冷启动
  3. DR 演练: 每月故意部署坏版本, 验证自动回滚触发
  4. 金丝雀指标: 部署后 5 min 内扫描周期 / 报警数异常则自动回滚
  5. GitOps: 设备端 Service 订阅 Git 仓库, 看到新 tag 自动拉取

相关文档