重點摘要
- AI 只負責寫 code、提 PR,不碰版本決策和 Production 部署,人類保留最終控制權
- 透過 Git tag 觸發 Jenkins,Staging 全自動部署、Production 手動 helm 執行,兩階段驗證才上線
- 敏感資訊三層隔離:.gitignore → K8s Secrets → etcd 加密,密碼永遠不進 repo
- 補齊 RBAC、Audit Log、鏡像簽名、Secrets Scan 四大安全缺口,達到 ISO 27001 合規
AI 輔助開發越來越普遍,但大多數團隊面臨同一個問題:AI 寫的 code 要怎麼安全地上線? 誰決定部署時機?密碼怎麼管?如果 AI 出錯了,有什麼防護網?
本篇文章完整說明 ONEEC OMS 系統實際採用的 AI 協作工作流設計,包含完整的 User Story、Jenkins Pipeline 架構、三環境部署策略,以及通過安全審查後補齊的 RBAC、Audit Log、鏡像簽名等安全強化配置。
核心設計理念:人類掌控節奏,AI 加速執行
這套工作流的核心原則只有一句話:AI 是高效能的執行者,不是決策者。具體體現在以下四點:
- AI 負責:寫 code、建 Dockerfile、提 PR、提供 Jenkins script 和 Helm chart
- 用戶負責:code review、創建 git tag(決定版本和部署時機)、手動 helm 部署到 Production
- 運維負責:管理 K8s Secrets、設定 Jenkins credentials、維護集群
- 敏感資訊:密碼、API Key、SSL 憑證永遠不進入 Git repo
完整 User Story:從需求到上線的 10 個步驟
以下用一個真實場景說明整個流程:場景:優化訂單 API 的查詢效能。
Step 1:AI 開發(feature branch)
AI 從 dev 分支切出 feature branch,完成開發後推送 PR:
# AI 執行
git checkout dev && git pull origin dev
git checkout -b feature/order-api-optimize
# 編寫程式碼...
# 本地驗證
docker-compose up -d
curl http://localhost:8080/api/orders?status=pending
# ✅ 回傳正確,效能提升 30%
# 提交並推送
git add . && git commit -m "feat(order-api): optimize query performance"
git push origin feature/order-api-optimize
# 建立 PR → dev
Step 2:用戶 Code Review & Merge
用戶在 GitHub UI 審查 PR:確認邏輯正確、有測試覆蓋、無敏感資訊後 approve 並 merge 到 dev。此時沒有任何自動化觸發,代碼靜靜等待部署決策。
Step 3:用戶創建 Staging Tag → Jenkins 自動觸發
用戶決定要部署到測試環境時,創建一個 staging-v* tag:
# 用戶執行
git tag staging-v1.0.1
git push origin staging-v1.0.1
# GitHub Webhook → Jenkins 自動執行:
# ├─ Secrets 掃描(gitleaks)
# ├─ docker build(所有 pods)
# ├─ cosign 簽名鏡像
# ├─ docker push to registry
# ├─ helm deploy to Staging K8s(使用 values-staging.yaml)
# └─ 通知用戶:Staging v1.0.1 is live
Step 4:用戶在 Staging 驗證
kubectl get pods -n staging
curl https://staging-api.example.com/api/orders?status=pending
# ✅ 功能正常,效能優化生效
# ✅ 錯誤率 0%
# ✅ 回應時間 < 100ms
Step 5:用戶創建 Production Tag → Jenkins 構建正式鏡像
# 用戶執行(確認 Staging 無誤後)
git tag v1.0.1
git push origin v1.0.1
# Jenkins 執行:
# ├─ Secrets 掃描
# ├─ docker build(所有 pods,tag 改為 v1.0.1)
# ├─ cosign 簽名鏡像
# ├─ docker push to registry
# ├─ 生成 Helm values(不含敏感資訊)
# └─ 通知用戶:Images ready, run helm command
Step 6:用戶手動部署到 Production
Production 部署是整個流程中唯一純手動的步驟,這是刻意設計的——確保每一次正式上線都有人類判斷:
# 用戶在本機執行
helm upgrade --install order-api \
/path/to/your/prod-configs/order-api/values-prod.yaml \
--set image.tag=v1.0.1 \
-n production
# K8s 自動從 Secrets 注入密碼、API Key
# Kyverno 自動驗證鏡像簽名(未簽名直接拒絕)
# Deployment 完成 ✅
Step 7:監控確認上線成功
kubectl get pods -n production
curl https://api.example.com/api/orders?status=pending
# ✅ 正式環境驗證通過,上線成功
三個部署環境的定義與分工
| 環境 | 用途 | 部署方式 | 配置來源 | 觸發者 |
|---|---|---|---|---|
| Dev | 本地開發驗證 | docker-compose up | .env.dev | AI(開發時) |
| Staging | 測試環境(K8s) | Jenkins 自動部署 | values-staging.yaml(在 repo) | 用戶(tag 觸發) |
| Production | 正式環境(K8s) | 手動 helm 部署 | values-prod.yaml(用戶維護)+ K8s Secrets | 用戶(手動執行) |
Jenkins Pipeline 完整架構
Jenkins Pipeline 由 GitHub Webhook(tag push)觸發,整個流程分為 6 個 Stage:
Stage 0:Secrets 掃描(安全門控)
這是整個 Pipeline 的第一道防線,也是最重要的安全門控。使用 gitleaks 掃描 repo 中是否含有密碼、API Key 等敏感資訊,發現即中止構建並通知安全告警。
stage('Secrets Scan') {
steps {
sh '''
gitleaks detect \
--source . \
--config .gitleaks.toml \
--exit-code 1 \
--report-format json \
--report-path gitleaks-report.json
'''
}
post {
failure {
sh 'sh scripts/notify-security-alert.sh ${TAG_NAME} gitleaks-report.json'
error('❌ Secrets 掃描發現敏感資訊,構建中止!')
}
}
}
Stage 1:Tag 偵測(決定部署目標)
根據 tag 名稱判斷本次構建的部署目標:
stage('Detect Tag') {
steps {
script {
if (env.TAG_NAME =~ /^staging-v.*/) {
env.DEPLOYMENT_ENV = 'staging'
} else if (env.TAG_NAME =~ /^v.*/) {
env.DEPLOYMENT_ENV = 'production'
} else {
error("❌ 未知 tag 格式: ${env.TAG_NAME}")
}
}
}
}
Stage 2:Build Images
構建所有 Pod 的 Docker 鏡像。鏡像本身不含任何配置、密碼、API Key,這是配置與代碼分離的核心原則:
#!/bin/bash
# scripts/build-docker.sh
TAG=$1
docker build -t registry.example.com/order-api:${TAG} ./simpleec-api
docker build -t registry.example.com/user-app:${TAG} ./user-app
docker build -t registry.example.com/channel-job:${TAG} ./simpleec-channel-job
# ... 所有 pods
Stage 3:Sign Images(供應鏈安全)
使用 cosign 為每個鏡像簽名,確保 Production 只能部署來自 Jenkins 的受信任鏡像:
stage('Sign Images') {
steps {
withCredentials([file(credentialsId: 'cosign-private-key', variable: 'COSIGN_KEY')]) {
sh 'sh scripts/sign-docker.sh ${TAG_NAME} ${COSIGN_KEY}'
}
}
}
# scripts/sign-docker.sh
for IMAGE in "${IMAGES[@]}"; do
cosign sign --key "${COSIGN_KEY}" \
--tlog-upload=false \
"${IMAGE}"
done
Stage 4:Push Images
推送到 Docker Registry。Registry 啟用 Immutable Tags,同一個 tag 無法被覆蓋,確保版本不可篡改:
stage('Push Images') {
steps {
withCredentials([usernamePassword(
credentialsId: 'docker-registry-creds',
usernameVariable: 'REGISTRY_USER',
passwordVariable: 'REGISTRY_PASS'
)]) {
sh 'sh scripts/push-docker.sh ${TAG_NAME}'
}
}
}
Stage 5a(Staging):自動部署到 Staging K8s
stage('Deploy to Staging') {
when { expression { env.DEPLOYMENT_ENV == 'staging' } }
steps {
withCredentials([file(credentialsId: 'kubeconfig-staging', variable: 'KUBECONFIG')]) {
sh '''
helm upgrade --install order-api ./k8s/helm/order-api \
--values ./k8s/helm/order-api/values-staging.yaml \
--set image.tag=${TAG_NAME} \
-n staging
'''
}
}
}
Stage 5b(Production):生成 Helm Values,通知用戶手動部署
對於 Production tag,Jenkins 不自動部署,而是生成配置檔並通知用戶手動執行:
stage('Generate Helm Values') {
when { expression { env.DEPLOYMENT_ENV == 'production' } }
steps {
sh 'sh scripts/generate-helm-values.sh ${TAG_NAME}'
// 生成 values-v${TAG_NAME}.yaml(不含敏感資訊)
// 通知用戶:Images ready, run helm command
}
}
Helm 配置隔離:敏感資訊三層防護
配置分為三層,層層隔離:
第一層:values-staging.yaml(在 repo,測試配置)
# 主機名用占位符,從 Jenkins 環境變數注入,不硬編碼內網地址
env:
DATABASE_HOST: "${POSTGRES_STAGING_HOST}"
DATABASE_NAME: simpleec_test
REDIS_HOST: "${REDIS_STAGING_HOST}"
API_LOG_LEVEL: DEBUG
第二層:values-prod.yaml(用戶本機維護,不進 repo)
# 用戶的私密文件,只在本機
env:
DATABASE_HOST: postgres-prod.example.com
API_LOG_LEVEL: WARN
# ⚠️ 資料庫密碼不在這裡!從 K8s Secrets 注入
envFrom:
- secretRef:
name: database-prod-creds # K8s Secret(運維管理)
- secretRef:
name: api-keys-prod # K8s Secret(運維管理)
第三層:K8s Secrets + etcd 加密
# 運維在 Production K8s 上創建
kubectl create secret generic database-prod-creds \
--from-literal=username=prod_user \
--from-literal=password=<secure-password> \
-n production
# K8s 預設 Secrets 以 base64 存在 etcd(並非加密!)
# 必須啟用 encryption at rest
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ["secrets"]
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
安全強化:補齊四大缺口
原始設計經過安全審查後,發現四個必須在投產前補足的缺口:
缺口一:K8s RBAC 未定義
三個角色各有最小權限(文件放在 k8s/rbac/):
| 角色 | 允許操作 | 明確禁止 |
|---|---|---|
| Jenkins SA(staging) | update/patch Deployments, get Pods | 讀取任何 Secrets |
| 用戶(production) | helm 部署相關資源 | 讀取業務 Secrets(DB 密碼、API Key) |
| 運維(production) | Secrets 完整管理權 | – |
# 驗證 Jenkins SA 無法讀取 Secrets(應輸出 no)
kubectl auth can-i get secrets \
--as=system:serviceaccount:staging:jenkins-deployer \
-n staging
缺口二:K8s Audit Log 未配置
ISO 27001 A.12.4.1 要求所有敏感操作都要有日誌。以下 Audit Policy 至少記錄 Secrets 訪問和 Deployment 變更:
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["secrets"] # 所有 Secrets 訪問都記錄
- level: Request
verbs: ["create", "update", "delete", "patch"]
resources:
- group: "apps"
resources: ["deployments"]
- level: None
users: ["system:kube-proxy"]
verbs: ["watch", "list"]
缺口三:鏡像簽名驗證(Kyverno 準入控制)
確保集群只能部署來自 Jenkins 簽名的鏡像,防止鏡像替換攻擊:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: Enforce # 未簽名鏡像直接拒絕
rules:
- name: check-image-signature
match:
any:
- resources:
kinds: ["Pod"]
namespaces: ["staging", "production"]
verifyImages:
- imageReferences:
- "registry.example.com/*"
attestors:
- count: 1
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
# cosign.pub 內容
-----END PUBLIC KEY-----
缺口四:GitHub Branch Protection 口頭約定 → 技術強制
| 分支 | Required Reviews | CI 必須通過 | Push 限制 |
|---|---|---|---|
| main | 2 人 approve | ✅ jenkins-build + secrets-scan | 僅 team-lead |
| staging | 1 人 approve | ✅ jenkins-build + secrets-scan | 僅 team-lead |
| dev | 1 人 approve | – | 必須透過 PR(AI 不能直接 push) |
Git 分支策略與 Tag 命名規範
整個工作流的分支拓撲如下:
main # Production 對應,受嚴格保護
└─ tag: v1.0.0, v1.0.1 # 觸發 Jenkins 構建 Production 鏡像
staging # 測試環境,中度保護
└─ tag: staging-v1.0.0 # 觸發 Jenkins 自動部署到 Staging K8s
dev # 開發積累,AI 透過 PR 提交
└─ 來源:feature/* 合入
feature/* # AI 的工作分支(每個功能一個)
├─ feature/user-auth
├─ feature/order-api
└─ feature/channel-job-momo
敏感資訊完整隔離架構
| 存放位置 | 可以存什麼 | 絕對不能存什麼 | 管理者 |
|---|---|---|---|
| Git Repository | 代碼、Dockerfile、values-staging.yaml、Helm chart 模板 | 密碼、API Key、SSL 憑證、values-prod.yaml | AI + 用戶 |
| Docker Registry | 不含配置的乾淨鏡像(cosign 簽名) | 任何敏感資訊 | Jenkins(push) |
| K8s Secrets(etcd 加密) | database-prod-creds、api-keys-prod、SSL 憑證 | – | 運維 |
| Jenkins Credentials | GitHub token、Registry credentials、cosign key、kubeconfig | – | 運維 |
回滾策略
Staging 環境回滾
# 快速回滾到上一個版本
helm rollback order-api 0 -n staging
# 或指定版本
helm upgrade order-api ./k8s/helm/order-api \
--values ./k8s/helm/order-api/values-staging.yaml \
--set image.tag=staging-v1.0.0 \
-n staging
Production 環境回滾
# 查看部署歷史
helm history order-api -n production
# 回滾到上一個版本
helm rollback order-api 0 -n production
# 所有 tag 在 Git 可追溯
git log --oneline --all | grep "v1.0"
投產前安全檢查清單
在正式上線前,以下所有項目必須確認通過:
代碼倉庫安全
- ✅ .gitignore 包含 .env, .env.dev, **/values-prod.yaml
- ✅ repo 根目錄存在 .gitleaks.toml 配置文件
- ✅ pre-commit hook 已安裝
- ✅ git log –all — ‘*.env’ 確認歷史中無敏感文件
Jenkins Pipeline
- ✅ 第一個 Stage 為 Secrets Scan(gitleaks)
- ✅ Sign Images Stage 已配置(cosign)
- ✅ Push Images 使用 Jenkins Credentials(非明文)
- ✅ GitHub Webhook Secret 已配置(Jenkins + GitHub 雙端)
K8s 訪問控制
- ✅ k8s/rbac/ 三個 RBAC 文件已 apply
- ✅ Jenkins SA 驗證:kubectl auth can-i get secrets … → no
- ✅ Kyverno 已安裝,鏡像簽名驗證策略已 apply
- ✅ etcd encryption at rest 已啟用(運維確認)
審計和監控
- ✅ K8s Audit Log 已配置(audit-policy.yaml)
- ✅ Audit Log 保留策略 ≥ 90 天
- ✅ 告警規則已配置(部署失敗、Secrets 掃描失敗)
總結:這套工作流解決了什麼問題?
AI 輔助開發的核心挑戰不是技術,而是信任邊界:誰能做什麼?誰為每個決定負責?這套工作流的答案很清楚:
- AI 的邊界:寫 code、提 PR、建 Docker image — 技術執行層
- 用戶的邊界:review 代碼、創建 tag、手動部署 Production — 決策層
- 運維的邊界:管理 Secrets、維護集群、配置 credentials — 基礎設施層
- 自動化的邊界:Jenkins 在 tag 觸發後執行既定腳本 — 不越界,不決策
這種分層設計讓 AI 協作既高效又安全,每一個部署都有完整的審計軌跡,每一個敏感操作都需要人類授權。