CI 掛掉時,打開主控台日誌、用肉眼追原因這件事,其實很耗精神。
本文會透過實際程式碼,解說如何把 偵測 Jenkins 失敗 → n8n 回收日誌 → Claude 摘要原因 →
通知到 Slack 這整套流程完全自動化。
重點是 徹底分工。Jenkins 端只負責丟出「失敗這件事」以及「日誌在哪裡」。
日誌回收、AI 調查、通知全部交給 n8n。
後半段會出現一些 Kubernetes 特有的內容(例如改寫成內部 service),所以先分享一下筆者的環境。
如果換成其他環境,理解後也能套用到地端 VM 或雲端代管環境。
https://jenkins.example.info,從內部可透過jenkins-app.jenkins.svc.cluster.local:8080 存取。claude 指令)的自製映像檔。curl(預設使用 busybox 的 wget)。CLAUDE_CODE_OAUTH_TOKEN(Claude CLI 驗證)Kubernetes / Vault 不是必需的。只要滿足以下 3 點即可:
「n8n 能連到 Jenkins 的內部端點」、「pod 內能執行claude」、「能安全地傳遞 Jenkins 的認證資訊」,
其餘架構都可以。
分工原則如下:
這樣切分之後,即使 AI 解析邏輯改了,Jenkins 端也完全不用動。
在各個 pipeline 的 post { failure { } } 中,只要呼叫共用函式 1 行即可。
post {
failure {
notifyN8nFailure()
}
}
內容放在 Shared Library(vars/notifyN8nFailure.groovy)裡。設計重點有 3 個。
這個函式是從 failure {} 裡呼叫,也就是「建置已經失敗之後的後處理」。
如果這裡再拋例外,可能會把後處理本身弄壞,所以 任何失敗都直接吞掉,只輸出 warn 日誌。
通知採取 best-effort 方針(就算沒送到,建置結果也照樣以 FAILURE 結束)。
def call(Map args = [:]) {
def urlCredId = args.urlCredentialId ?: 'n8n-failure-webhook-url'
def tokenCredId = args.tokenCredentialId ?: 'n8n-failure-webhook-token'
try {
def payload = buildPayload(args.extra instanceof Map ? args.extra : [:])
def payloadFile = '.n8n-failure-payload.json'
writeFile file: payloadFile, text: payload
// ...(送出流程)
} catch (err) {
// 不要因為失敗通知本身失敗,就破壞建置後處理。
echo "[WARN] 傳送 n8n 失敗通知時發生錯誤,已忽略: ${err}"
}
}
Webhook URL / 認證 token 都從 Jenkins Credentials 取得。
這裡 不要用 Groovy 字串插值("${...}")直接塞進 curl 指令。
因為一旦插值,有時會繞過遮罩機制,讓明文出現在日誌中。
應該用 withCredentials 綁定成環境變數,再在 shell 裡以 $N8N_WEBHOOK_URL 的方式存取。
withCredentials([string(credentialsId: urlCredId, variable: 'N8N_WEBHOOK_URL')]) {
sh """#!/bin/bash
set -uo pipefail
URL="\${N8N_WEBHOOK_URL:-}"
if [ -z "\$URL" ]; then
echo "[WARN] n8n-failure-webhook-url 為空,略過失敗通知。" >&2
exit 0
fi
TOKEN="\${N8N_WEBHOOK_TOKEN:-}"
AUTH_HEADER=()
if [ -n "\$TOKEN" ]; then
AUTH_HEADER=(-H "X-N8N-Webhook-Token: \$TOKEN")
fi
HTTP=\$(curl -sS -o /dev/null -w '%{http_code}' \\
--connect-timeout 10 --max-time 30 \\
-X POST \\
-H 'Content-Type: application/json; charset=utf-8' \\
"\${AUTH_HEADER[@]}" \\
--data @${payloadFile} \\
"\$URL" || echo 000)
# 非 2xx 也只記 warn,繼續往下跑
"""
}
URL 是必填、token 是選填,這個非對稱設計也很重要。
即使沒有 token credential 也希望通知能繼續,所以 token 會另外嘗試取得;如果沒有,就不加 header 直接送出。
Payload 只包含失敗的中繼資訊。不直接傳送主控台日誌本體。
改為傳 consoleUrl(BUILD_URL + consoleText),回收交給 n8n。
private String buildPayload(Map extra) {
def cb = currentBuild
def data = [
job : env.JOB_NAME ?: '',
build : env.BUILD_NUMBER ?: '',
buildUrl : env.BUILD_URL ?: '',
consoleUrl : env.BUILD_URL ? "${env.BUILD_URL}consoleText" : '',
branch : env.BRANCH_NAME ?: (env.GIT_BRANCH ?: ''),
node : env.NODE_NAME ?: '',
jenkinsUrl : env.JENKINS_URL ?: '',
result : (cb?.currentResult ?: 'FAILURE'),
durationMs : (cb?.duration ?: 0),
]
if (extra) { data.extra = extra }
return groovy.json.JsonOutput.toJson(data)
}
送出的 JSON 會長這樣:
{
"job": "GitHub/n8n-i18n-japanese-pr-review",
"build": "67",
"buildUrl": "https://jenkins.example.info/job/.../67/",
"consoleUrl": "https://jenkins.example.info/job/.../67/consoleText",
"branch": "main",
"result": "FAILURE",
"durationMs": 12345
}
Credential ID種類是否必需用途n8n-failure-webhook-urlSecret text必需n8n Webhook 的正式 URLn8n-failure-webhook-tokenSecret text選填Header Auth token(X-N8N-Webhook-Token)如果 URL 尚未註冊或為空,就會跳過通知,因此可以分階段導入。
n8n 的工作流程由 4 個節點組成,依序來看。
用 POST /webhook/jenkins-failure 接收來自 Jenkins 的 JSON。
可以選擇性設定 Header Auth,並與 Jenkins 端的 n8n-failure-webhook-token 一致,降低外部直接呼叫的風險。
這一步雖然不起眼,但很重要。收到的 consoleUrl 不能直接拿來用,而是要:
公開 URL(例如在 Cloudflare Access 之下)很多時候無法從 n8n pod 直接存取。
因此這裡會只取出 consoleUrl 的路徑,再改指向內部 service
jenkins-app.jenkins.svc.cluster.local:8080。
如此一來可以繞過 Cloudflare,同時因為 host 固定為內部位址,也能降低 SSRF 風險。
在 n8n pod 內透過 Execute Command 節點一次完成。
wget --header="Authorization: Basic <base64(user:token)>" \
http://jenkins-app.jenkins.svc.cluster.local:8080/.../consoleText \
-O - \
| tail -n 400 | head -c 24000 \
| claude -p "請用中文簡潔摘要這個 Jenkins 建置失敗的原因,並附上根本原因、對應階段與處理方式"
實際環境中有兩個常見坑:
curl → 改用 busybox 的 wget。busybox 的 wget 不支援 --user/--password,--header="Authorization: Basic <base64>" 傳入。tail -n 400 | head -c 24000 只擷取尾端再交給 claude。Claude CLI 的認證由 pod 的環境變數 CLAUDE_CODE_OAUTH_TOKEN(來自 Vault)提供。
Jenkins 的 API 認證資訊也透過 pod env 傳入,而不是放在 n8n 的 credential store 裡,
因此 n8n 端不需要另外新增 credential(只有 Slack 會沿用既有 credential)。
把 Claude 的摘要發到 Slack 頻道。這裡共用既有的 Slack API credential。
如果 Claude 回傳太長,可能會碰到 Slack 字數上限,因此必要時可在 prompt 端限制輸出長度。
我實際故意把 CI 弄失敗,完成了 end-to-end 驗證。
對於因 ssh: connect to host github.com port 22: Connection timed out 而失敗的建置,
Claude 成功整理出根本原因、對應階段、處理方式,並且發到 Slack。
導入過程中踩到/需要注意的點如下:
retryOnFail / onError。如果要面對暫時性故障,可以再補上。透過「Jenkins 只負責丟出失敗事件,回收、AI 調查、通知都交給 n8n」這個切分方式,
就能把 CI 失敗的初步調查整個自動化。Jenkins 端只要 1 行 Shared Library,
AI 解析邏輯也被收斂在 n8n 內,因此後續迭代調整會很方便。
同樣的架構也可以把 Slack 換成 Discord 或 Teams,也可以把 Claude 換成其他 CLI 解析工具。
如果你也常常在 CI 掛掉時用肉眼追日誌,建議先從「把失敗事件集中到單一入口」開始,效果通常會很明顯。
從零開始也能學會!一起挑戰看看吧
我也有寫 note ↓
原文出處:https://qiita.com/jqit-yukiono/items/61985c6743b89aa6924b