上海一家 私募 的 风控 主管 批准 了 你 的 L3 manifest,但 在 部署 步 上 停 住:「开发者 把 一行 修复 合 入 main,这 时 镜像 从 哪 来?谁 打 tag?谁 扫描?谁 推 到 Aliyun ACR?谁 对 feed-dev 跑 kubectl apply?明天 生产 上 在 1.0.1 里 发现 bug,谁 把 它 翻 回 1.0.0,要 多久?」L1 的 wheel 在。L2 的 镜像 能 构 出来。L3 的 manifest 能 手动 apply。这 些 都 不 会 自己 跑。capstone 把 三 课 合 进 一 条 端到端 流水线 收 模块:输入 是 3.6.4 L4 的 capstone(开发者 笔记本 上 的 三 个 Python 脚本),输出 是 同 一 份 工件 通过 CI 部署 到 Kubernetes namespace,并 演练 一 条 一 行 命令 的 回滚 路径。
第 1 步——把 3.6.4 capstone 重构 为 Python 包(L1)
把 L1 纪律 套 在 3.6.4 L4 的 三 个 文件 上。建 src/feed_handler/。把 producer 主体 挪 进 producer.py 的 def main(): ...。把 consumer 主体(3.6.4 L4 的 feed_handler.py)挪 进 consumer.py。把 VWAP 监控(3.6.4 L4 的 vwap_monitor.py)挪 进 monitor.py。__init__.py 写 __version__ = "1.0.0"。__main__.py 写 L1 的 20 行 分发器。再 加 一 个 _config.py(见 第 2 步)。包 布局:
src/feed_handler/
__init__.py # __version__ = "1.0.0"
__main__.py # 分发器
_config.py # 12 因素 配置 层
producer.py # producer.main()
consumer.py # consumer.main()
monitor.py # monitor.main()
第 2 步——12 因素 配置 层
建 src/feed_handler/_config.py,每 个 运行时 配置 值 都 从 环境 变量 读。必 需 值 用 os.environ['<KEY>'](不 用 .get——缺 值 就 在 启动 时 直接 KeyError 崩溃);可选 值 用 os.environ.get('<KEY>', '<默认值>'):
"""12-factor configuration: every runtime config value comes from an env var.
Required config: read with os.environ['<KEY>'] so a missing value is a loud crash
at startup, not a silent default. Optional config: read with os.environ.get(...,
default). The same image runs unchanged across dev / staging / prod with only
the orchestrator-injected env vars differing.
"""
import os
KAFKA_BOOTSTRAP_SERVERS = os.environ["KAFKA_BOOTSTRAP_SERVERS"]
TOPIC = os.environ["TOPIC"]
PG_DSN = os.environ["PG_DSN"]
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
VWAP_WINDOW_NS = int(os.environ.get("VWAP_WINDOW_NS", 60 * 1_000_000_000))
纪律 规则 说 一次:必 需 配置 来自 必 需 的 env var(不 设 默认;让 它 大声 失败);可选 配置 设 默认;同 一份 镜像 跨 dev / staging / prod 不变,只 有 编排器 注入 的 env var 不一样。L1 的 producer.py / consumer.py / monitor.py 从 feed_handler._config import,绝不 从 内联 字符串 字面量——这样 镜像 是 同 一份 工件,env var 是 唯一 的 环境 差异。
第 3 步——pyproject.toml + uv.lock(L1 应用)
按 L1 写 pyproject.toml:hatchling、四 段、三 个 钉死 依赖 加 用于 L4 日志 形态 的 structlog == 24.1.0,以及 [tool.hatch.version] path = "src/feed_handler/__init__.py"。跑 uv lock 产 出 uv.lock。两 份 是 capstone 的 交付物 1 与 2。
第 4 步——Containerfile(L2 应用)
按 L2 写 多 阶段 Containerfile:builder 阶段 装 build-essential librdkafka-dev libpq-dev;runtime 阶段 装 librdkafka1 libpq5 加 useradd appuser 加 USER appuser 加 HEALTHCHECK 加 ENTRYPOINT ["python", "-m", "feed_handler"] 加 CMD ["consumer"]。按 L2 写 .dockerignore。交付物 3 与 4。
第 5 步——docker-compose.yml + .env.example(L3 应用)
按 L3 写 五 服务 compose 文件——kafka + timescaledb + feed-handler-producer + feed-handler-consumer + feed-handler-monitor——加 env_file: .env、命名 网络 feed-net、命名 卷 kafka-data + ts-data。写 .env.example(提交 进 git 的 模板)含 POSTGRES_PASSWORD、KAFKA_BOOTSTRAP_SERVERS、TOPIC、PG_DSN 的 占位 值。本地 验证:docker compose up -d && docker compose ps && docker compose logs -f feed-handler-consumer && docker compose down -v。交付物 5 与 6。
第 6 步——Kubernetes manifest 在 manifests/(L3 应用)
写 八 个 YAML:namespace.yaml、kafka-statefulset.yaml、kafka-service.yaml、timescaledb-statefulset.yaml、timescaledb-service.yaml、feed-handler-deployments.yaml、services.yaml、config.yaml、secrets.yaml。每 个 Deployment 模板 必须 写 resources.requests、resources.limits、readinessProbe、livenessProbe。每 个 Deployment 带 prometheus.io/scrape: 'true' 注解 作为 指向 3.6.6 的 forward-pointer。consumer Deployment 完整 形态:
apiVersion: apps/v1
kind: Deployment
metadata:
name: feed-handler-consumer
namespace: feed-dev
labels:
app: feed-handler
component: consumer
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
selector:
matchLabels:
app: feed-handler
component: consumer
template:
metadata:
labels:
app: feed-handler
component: consumer
annotations:
prometheus.io/scrape: 'true'
prometheus.io/port: '8080'
spec:
containers:
- name: consumer
image: <registry>/quant/feed-handler:1.0.0
command: ['python', '-m', 'feed_handler', 'consumer']
envFrom:
- configMapRef:
name: feed-handler-config
- secretRef:
name: feed-handler-secrets
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
cpu: 1
memory: 2Gi
readinessProbe:
exec:
command: ['python', '-c', 'import feed_handler']
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
livenessProbe:
exec:
command: ['python', '-c', 'import feed_handler']
initialDelaySeconds: 30
periodSeconds: 60
failureThreshold: 5
交付物 7(manifests/ 下 八 个 YAML 文件)。
第 7 步——CI 流水线
写 .github/workflows/build-deploy.yml,六 个 job 按 依赖 顺序:
name: build-deploy
on:
push:
branches: [main]
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- run: uv sync --frozen --extra dev
- run: uv run ruff check .
- run: uv run mypy src/
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- run: uv sync --frozen --extra dev
- run: uv run pytest -v
build-wheel:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- run: uv build
- uses: actions/upload-artifact@v4
with:
name: wheel
path: dist/*.whl
build-image:
needs: build-wheel
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: version
run: |
VERSION=$(python -c "import re; print(re.search(r'__version__ = \"(.+)\"', open('src/feed_handler/__init__.py').read()).group(1))")
echo "value=${VERSION}" >> "$GITHUB_OUTPUT"
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ${{ vars.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- uses: docker/build-push-action@v5
with:
tags: '${{ vars.REGISTRY }}/quant/feed-handler:${{ github.sha }},${{ vars.REGISTRY }}/quant/feed-handler:${{ steps.version.outputs.value }}'
push: true
platforms: linux/amd64
scan-image:
needs: build-image
runs-on: ubuntu-latest
steps:
- uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ vars.REGISTRY }}/quant/feed-handler:${{ github.sha }}'
severity: 'HIGH,CRITICAL'
exit-code: '1'
deploy-dev:
needs: scan-image
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > $HOME/.kube/config
- run: kubectl set image deployment/feed-handler-producer producer=${{ vars.REGISTRY }}/quant/feed-handler:${{ github.sha }} -n feed-dev
- run: kubectl set image deployment/feed-handler-consumer consumer=${{ vars.REGISTRY }}/quant/feed-handler:${{ github.sha }} -n feed-dev
- run: kubectl set image deployment/feed-handler-monitor monitor=${{ vars.REGISTRY }}/quant/feed-handler:${{ github.sha }} -n feed-dev
- run: kubectl rollout status deployment/feed-handler-consumer -n feed-dev --timeout=5m
六 个 job 名——lint、test、build-wheel、build-image、scan-image、deploy-dev——通过 needs: 串 起。deploy-dev 闸 if: github.ref == 'refs/heads/main' 保证 只 有 main 合 入 才 部署。kubectl set image 通过 改 镜像 tag 触发 滚动 更新——纯 image 更新 比 kubectl apply 更 幂等。最后 一行 kubectl rollout status ... --timeout=5m 阻塞 CI job 直到 滚动 完成 或 超时——pod 起 不 来 就 让 deploy 大声 失败。规则:CI 构建 一次、扫描 一次、部署 一次;staging 与 prod 部署 走 手动 审批 或 ArgoCD GitOps——forward-pointer,点 到 为止。
中文 工程 现场 翻译:上面 的 .github/workflows/build-deploy.yml 是 参考 语法;国内 量化 firm 实际 CI 平台 多 是 自建 GitLab CI Runner、Jenkins、阿里云 流水线、腾讯云 CODING、华为云 CodeArts。GitLab CI 语法 几乎 一致——把 jobs: 换成 stages:、把 needs: 换成 stage: 依赖、把 uses: 换成 image:,其它 命令 行 同。把 <registry> 换成 registry.example.cn/quant/feed-handler 或 registry.cn-hangzhou.aliyuncs.com/<account>/feed-handler。集群 kubectl set image 用 阿里云 ACK / 腾讯云 TKE 内部 kubeconfig。
交付物 6:.github/workflows/build-deploy.yml。
第 8 步——六 CI job 命令 行
每 job 一 行 标准 命令,写 进 runbook:
lint→uv run ruff check . && uv run mypy src/test→uv sync --frozen && uv run pytest -vbuild-wheel→uv buildbuild-image→docker buildx build --tag <registry>/quant/feed-handler:${SHA} --tag <registry>/quant/feed-handler:${VERSION} --push --platform linux/amd64 .scan-image→trivy image --severity HIGH,CRITICAL --exit-code 1 <registry>/quant/feed-handler:${SHA}deploy-dev→kubectl set image deployment/feed-handler-consumer consumer=<registry>/quant/feed-handler:${SHA} -n feed-dev && kubectl rollout status deployment/feed-handler-consumer -n feed-dev --timeout=5m
第 9 步——端到端 验证 + 回滚 演练
十二 步 序列,按 这个 顺序:
kind create cluster --name feed-dev
kind load docker-image <registry>/quant/feed-handler:1.0.0 --name feed-dev
kubectl create namespace feed-dev
kubectl apply -f manifests/ -n feed-dev
kubectl get pods -n feed-dev -w
kubectl exec -n feed-dev kafka-0 -- kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group feedhandler-warehouse
kubectl logs -f deployment/feed-handler-consumer -n feed-dev
sed -i 's/1.0.0/1.0.1/' src/feed_handler/__init__.py
docker buildx build --tag <registry>/quant/feed-handler:1.0.1 --push .
kind load docker-image <registry>/quant/feed-handler:1.0.1 --name feed-dev
kubectl set image deployment/feed-handler-consumer consumer=<registry>/quant/feed-handler:1.0.1 -n feed-dev
kubectl rollout status deployment/feed-handler-consumer -n feed-dev
kubectl rollout undo deployment/feed-handler-consumer -n feed-dev
kubectl rollout history deployment/feed-handler-consumer -n feed-dev
第 1–7 步 验证 初次 部署。第 8–10 步 切 新 版本、构建、推 出、滚 上 去。第 11 步 用 kubectl rollout undo 一行 回滚。第 12 步 在 kubectl rollout history 里 看 到 两 个 修订,回滚 可 审计。
双 配置 层
三 条 规则 把 12 因素 模式 锚 在 交付物 上:
non-secret config: Kubernetes ConfigMap feed-handler-config → envFrom: [configMapRef]credentials: Kubernetes Secret feed-handler-secrets → envFrom: [secretRef](YAML 里 的 base64 是 编码、不 是 加密;生产 用 集群 级 KMS 做 静态 加密)runtime read: src/feed_handler/_config.py reads via os.environ['<KEY>'] for required values (no default, fail loud) and os.environ.get('<KEY>', '<default>') for optional values
合 一 句话:同 一 份 镜像 跨 dev / staging / prod 不变,只 有 env var 不一样。
七 项 权威 capstone 交付物
按 这个 顺序:
pyproject.toml+uv.lock(L1)src/feed_handler/{__init__,__main__,producer,consumer,monitor,_config}.py(L1 + L4)Containerfile+.dockerignore(L2)docker-compose.yml+.env.example(L3)manifests/*.yaml(八 个 文件:namespace.yaml、kafka-statefulset.yaml、kafka-service.yaml、timescaledb-statefulset.yaml、timescaledb-service.yaml、feed-handler-deployments.yaml、services.yaml、config.yaml、secrets.yaml)(L3).github/workflows/build-deploy.yml(L4 CI)README.md(runbook)
跨 模块 纪律 总结
pyproject.toml 是 真理源(L1);wheel 是 可部署 工件(L1);锁定文件 是 可重现性 印章(L1);Containerfile 是 受 评审 的 多 阶段 非 root 代码,七 条 L2 纪律 都 在(L2);镜像 用 semver + commit SHA 打 tag 且 CI 里 扫描(L2);compose 本地、Kubernetes 测试 + 生产(L3);每 个 prod pod 设 资源 请求 与 限制(L3);有状态 服务 用 StatefulSet + 稳定 PVC(L3);非 secret 配置 进 ConfigMap、凭据 进 Secret 并 牢 记 「base64 是 编码,不 是 加密」(L3);六 job 流水线 CI 构建 一次、扫描 一次、部署 一次(本课);回滚 是 kubectl rollout undo 一 行 命令(本课)。
练习
Exercise
对 真 实 本地 kind 集群 完整 构建 并 部署 capstone。从 3.6.4 L4 capstone(三 个 Python 脚本 producer.py / feed_handler.py / vwap_monitor.py + migrations/001_create_ticks_raw.sql)以及 本 模块 的 L1 + L2 + L3 形态 出发,(a) 按 L1 重构 为 包 布局 src/feed_handler/{__init__,__main__,producer,consumer,monitor,_config}.py;__init__.py 携带 __version__ = '1.0.0';按 本课 写 _config.py 读 四 个 必 需 env var 与 两 个 可选 env var。(b) 写 pyproject.toml 并 跑 uv lock。(c) 按 L2 写 Containerfile + .dockerignore。(d) 按 L3 写 docker-compose.yml + .env.example;本地 用 docker compose up -d && docker compose ps 验证 五 服务 healthy。(e) 按 七 交付物 清单 写 manifests/(八 个 YAML 文件);每 个 Deployment 模板 必须 设 resources.requests + resources.limits + readinessProbe + livenessProbe;每 个 Deployment 必须 带 prometheus.io/scrape: 'true' 注解 作为 指向 3.6.6 的 forward-pointer。(f) 按 本课 写 .github/workflows/build-deploy.yml,六 个 job 按 依赖 顺序。(g) 写 README.md runbook 覆盖:前置 工具(docker、kind、kubectl、uv、trivy)、构建 + 扫描 + 推 镜像 命令、kind load docker-image、kubectl apply -f manifests/、验证 命令、回滚 命令。(h) 通过 kind create cluster --name feed-dev 建 集群。(i) 本地 跑 CI 流水线(用 act,或 手动 重 跑 每 个 job)确认 六 个 job 全 过。(j) kubectl get pods -n feed-dev -w 看 五 个 pod 都 Running。(k) 通过 kubectl exec -n feed-dev kafka-0 -- kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group feedhandler-warehouse 验证 LAG 在 稳态 接近 零。(l) kubectl logs -f deployment/feed-handler-consumer -n feed-dev 看 结构化 JSON 日志 流。(m) 演练 回滚:把 __version__ 升 到 '1.0.1' 加 一 行 行为 变更,重 跑 CI(或 手动 重 构),用 kubectl set image deployment/feed-handler-consumer consumer=<registry>/quant/feed-handler:1.0.1 -n feed-dev apply 新 镜像,kubectl rollout status 看 滚动 更新,再 跑 kubectl rollout undo deployment/feed-handler-consumer -n feed-dev,确认 kubectl rollout history deployment/feed-handler-consumer -n feed-dev 显示 两 个 修订。(n) 用 一 句话 写 出 如果 这 部署 到 测试 或 生产(不是 kind 集群)会 有 哪些 变化(提示:托管 StorageClass、registry endpoint、ArgoCD GitOps、sealed-secrets 或 external-secrets-operator 管 凭据、network policy、RBAC、部署 前 手动 审批 闸)。
提示
deploy-dev 成功 但 pod 一直 ImagePullBackOff,说明 集群 拉 不 到 registry。kind 上 要 kind load docker-image 而 不 是 真 拉 registry;测试 / 生产 上 要 imagePullSecrets: 引用 一 个 含 registry 凭据 的 Secret。提示
kubectl rollout undo 只 翻 回 同 一 个 镜像,说明 你 在 两 次 构建 之间 忘 了 升 __version__,CI 用 ${{ github.sha }} 把 两 个 都 打 上 了 同 tag——rollout history 记 了 两 个 修订,但 pod template 实际 没 变。必备 组件 回顾
本课 交付物 对 合约 的 映射:
- Fenced
```python块——src/feed_handler/_config.py,四 个 必 需 env var(os.environ['KAFKA_BOOTSTRAP_SERVERS']/os.environ['TOPIC']/os.environ['PG_DSN'],以及 producer 端 独有 的PRODUCER_RATE_HZ),两 个 可选 env var(os.environ.get('LOG_LEVEL', 'INFO')、int(os.environ.get('VWAP_WINDOW_NS', 60 * 1_000_000_000)))。 - Fenced
```yaml块——.github/workflows/build-deploy.yml,六 个 job(lint、test、build-wheel、build-image、scan-image、deploy-dev)通过needs:串 起,if: github.ref == 'refs/heads/main'闸,三 条kubectl set image调用,最后kubectl rollout status ... --timeout=5m。 - Fenced
```yaml块——consumerDeploymentmanifest:replicas: 3、RollingUpdate策略、prometheus.io/scrape: 'true'+prometheus.io/port: '8080'注解(3.6.6 forward-pointer)、envFrom: [configMapRef, secretRef]、resources.requests: {cpu: 100m, memory: 512Mi}、resources.limits: {cpu: 1, memory: 2Gi}、readinessProbe+livenessProbe。 - Fenced
```bash块——十二 步 验证 + 回滚 演练,顺序:kind create cluster --name feed-dev、kind load docker-image、kubectl create namespace feed-dev、kubectl apply -f manifests/、kubectl get pods -n feed-dev -w、kubectl exec ... kafka-consumer-groups --describe --group feedhandler-warehouse、kubectl logs -f、版本 bump + 重 构、kubectl set image、kubectl rollout status、kubectl rollout undo、kubectl rollout history。 - Inline-code 列表 七 项 capstone 交付物:
pyproject.toml + uv.lock、src/feed_handler/{__init__,__main__,producer,consumer,monitor,_config}.py、Containerfile + .dockerignore、docker-compose.yml + .env.example、manifests/*.yaml(八 个 YAML 文件)、.github/workflows/build-deploy.yml、README.md。 - Inline-code 列表 六 CI job:
lint→uv run ruff check . && uv run mypy src/;test→uv sync --frozen && uv run pytest -v;build-wheel→uv build;build-image→docker buildx build --tag ...:${SHA} --tag ...:${VERSION} --push --platform linux/amd64 .;scan-image→trivy image --severity HIGH,CRITICAL --exit-code 1 ...:${SHA};deploy-dev→kubectl set image ... && kubectl rollout status ... --timeout=5m。 - Inline-code 列表 双 配置 层 规则:
non-secret config: Kubernetes ConfigMap feed-handler-config → envFrom: [configMapRef];credentials: Kubernetes Secret feed-handler-secrets → envFrom: [secretRef](base64 是 编码、不 是 加密);runtime read: src/feed_handler/_config.py reads via os.environ['<KEY>'] for required values and os.environ.get('<KEY>', '<default>') for optional values。 - 上面 的 练习 加 两 个 渐进式 Hint。
中国 区 锚点
国内 量化 firm 把 这 套 形态 部署 到 自建 集群(Rancher / KubeSphere)或 公有云 阿里云 ACK、腾讯云 TKE、华为云 CCE 上 跑 同 一 个 沪深300 ETF(510300)合成 流。build-image job 推 到 内部 自建 Harbor(registry.example.cn/quant/feed-handler)或 公有 云 阿里云 ACR(registry.cn-hangzhou.aliyuncs.com/<account>/feed-handler);deploy-dev job 应用 到 feed-dev namespace,与 测试 namespace 同 形(差 在 一 个 手动 审批 闸)。沪深300 ETF(510300)与 上证 50ETF(510050)是 stream 的 modal 锚点;CFFEX 股指 期货 IF / IC / IH 走 平行 的 namespace 但 manifest 形状 相同。私募 与 公募 的 工程 团队 把 这 套 CI 模板 跑 在 内部 GitLab Runner 或 阿里云 流水线 / 腾讯云 CODING 上,Trivy 报告 进 内部 漏洞 看板;T+1 结算 周期 与 涨跌停 板 风控 在 应用 层 处理 不 影响 CI 形态。SSE 上证、SZSE 深证 的 行情 与 50ETF / 300ETF 的 入库 走 同 一 套 Deployment 形态。
国内 量化 firm 的 CI 现场 还 有 几 点 工程 细节 值得 在 capstone 里 锚 住。第 一,凭据 注入:自建 GitLab Runner 通过 Kubernetes executor 跑 时,registry 凭据 与 kubeconfig 通过 自建 Vault 取 出 注入 进 容器,不 走 GitHub Actions 的 secrets 形态——但 kubectl set image 命令 行 完全 一致。第 二,镜像 加速:阿里云 ACR EE 版 与 腾讯云 TCR EE 版 都 支持 跨 区域 复制,灰 度 发布 时 把 镜像 同步 到 业务 所 在 区 的 registry 再 跑 deploy-dev。第 三,沪深300 ETF(300ETF)与 上证 50ETF(50ETF)的 合成 流 在 ConfigMap 的 TOPIC 字段 切 换;私募 的 自营 业务 与 资管 业务 通过 namespace 隔离,分别 跑 feed-quant-dev 与 feed-am-dev,CI 流水线 的 if: 闸 按 分支 名 走 不同 namespace。第 四,回滚 演练 在 国内 头部 私募 也 是 PR 必 跑 项,rollout history 进 内部 变更 单 系统 供 风控 审计。第 五,CFFEX 股指 期货 IF / IC / IH 合约 换 月 时 的 流水线 不 改 形态——主力 合约 字段 在 ConfigMap 切 换、CI 上 触发 一 次 kubectl rollout restart、按 同 一 套 readiness 探针 评估 就绪。涨跌停 板 与 T+1 结算 在 应用 层 处理,与 CI 流水线 无关;本 capstone 的 形态 在 SSE 与 SZSE 两 个 交易 所 的 行情 入库 上 完全 通用。第 六,国产 基础 设施 上 跑 这 套 CI 与 国际 形态 几乎 一致:把 GitHub Actions 的 uses: actions/checkout@v4 换 成 GitLab 的 image: 与 script:、把 docker/build-push-action@v5 换 成 script: docker buildx build --push ... 就 完成 移植。
阅读 清单
GitHub Actions / GitLab CI 中文 文档 社区 翻译;Jenkins 中文 文档;阿里云 云效 流水线 文档 help.aliyun.com/product/150040.html;腾讯云 CODING DevOps 文档 cloud.tencent.com/document/product/1115;华为云 CodeArts 文档;ArgoCD 中文 摘要 社区 翻译;KubeSphere 流水线 文档;极客 时间《持续交付实战》;《DevOps 实践指南》中文版;阿里 资深 工程师《云原生 DevOps 实战》。一条 额外 注释:国内 量化 firm 的 CI/CD 平台 一般 由 DevOps / 平台 团队 维护;本课 教 quant developer 写 .gitlab-ci.yml(或 GitHub Actions YAML 翻译版)直接 提交,平台 团队 负责 Runner 与 Cluster 的 接入。部署 默认 是 push-based kubectl apply from CI;ArgoCD / Flux 已 在 部分 头部 私募 与 券商 自营 落地,但 仍 是 名词 性 引用,不 是 本课 worked example 形态。私募 量化 行业 内 流水线 的 上手 节奏 通常 是:先 跑 通 lint 与 test 两 个 job 接 上 内部 Runner、再 让 build-wheel 与 build-image 推 到 内部 Harbor、最后 让 scan-image 与 deploy-dev 接 上 阿里云 ACK / 腾讯云 TKE 的 kubeconfig 与 ServiceAccount。每 一 步 都 需要 平台 团队 在 凭据 与 RBAC 上 配 合,但 流水线 文件 本身 是 quant 写 进 git 的 受 评审 代码——按 3.6.2 的 PR 流程 走 评审、合 入、再 部署。沪深300 ETF(300ETF)与 上证 50ETF(50ETF)的 行情 入库 是 capstone 验证 的 主 锚 标的,CFFEX 期指 IF / IC / IH 是 拓展 锚 标的,均 走 同 一 份 capstone 形态 与 同 一 套 回滚 路径。
通往 3.6.6 的 桥
下 一 模块(3.6.6 可观测性 与 系统 设计)会 把 Prometheus 抓取 注解 接 到 本课 产出 的 Deployment 上,围绕 3.6.4 L4 的 结构化 JSON 日志 行 搭建 consumer lag 与 部署 滚动 dashboard,并 在 本 流水线 现在 记录 的 rollout history 之上 加 告警 规则。capstone 兑现 本 模块 的 承诺:3.6.4 L4 那份 跑 在 开发者 笔记本 ./run.sh 之下 的 工件,现在 通过 CI 流水线 部署 到 Kubernetes namespace,并 有 一 行 命令 的 回滚 路径 经过 演练。