一個 DevOps 工程師與 AI 助手的真實協作紀錄
以 Gitea OOM 事件處理到備份系統建置為例
前言:為什麼叫老婆?
因為跟 Claude Code 協作就像跟老婆相處:
⚠️ 閱讀提醒
以下對話是精簡摘要,實際協作過程中:
- 我會提供更多背景資訊(log、設定檔、錯誤訊息)
- 老婆會回覆多種可能做法並分析優缺點
- 每個段落可能涉及數十次的檔案讀取、修改、指令執行
- 完整對話紀錄約 2,500 行 JSON
11:01 - 早安,今天 Gitea OOM 了
我: 剛剛我們的 git-server 被 OOM killed 了,不只 gitea 被砍,supervisord 也被 systemctl 砍了。幫我查一下原因。
老婆: 好,讓我 SSH 連進去...
(等了很久)
老婆: 連不上,SSH timeout 了。TCP 連接成功建立,但 SSH banner exchange 超時。建議透過 AWS Console 重啟。
我: 我用 jump-server 也連不上欸
(等了一陣子後 SSH 恢復正常)
我: 好,現在可以連了。我先 systemctl restart supervisord
老婆: 好,supervisorctl 會自動重啟 gitea 相關服務
💡 協作心法 #1:緊急狀況時先穩住
AI 很想幫忙但機器連不上也沒辦法,這時候人類要先處理基礎設施問題
11:17 - 為什麼有那麼多 gitea 進程?
我貼了 OOM log,裡面有 27 個 gitea 進程
我: 為什麼會有那麼多 gitea 進程?
老婆: 這些其實是 Go 語言的 OS 線程 (threads),不是真正的子進程。
Go runtime 會根據 GOMAXPROCS 創建 OS 線程,每個線程在 Linux 中會顯示為獨立的 task/process。可以用 GOMAXPROCS=1 和 GOMEMLIMIT 來限制。
[program:gitea]
environment=GOMAXPROCS="1",GOMEMLIMIT="768MiB"
💡 學習點 #1:Go 語言特性
多個同名進程不一定是 bug,可能是語言 runtime 的正常行為
11:18 - 不是所有建議都適合
老婆建議: 新增 2GB Swap file
我: 現在針對 AWS EC2 不是不建議開 swap 嗎?
老婆: 對,EBS 的 I/O 延遲太高,swap 效果不好。那我們用其他方法:
- 限制
GOMEMLIMIT - 關閉
REPO_INDEXER_ENABLED - 調整 OOM 優先級
💡 協作心法 #2:質疑不適合環境的建議
AI 會給通用建議,但人類要判斷是否適合自己的環境
11:40 - 又 OOM 了
我: 又被 OOM 了,但現在 SSH 可以連線了
老婆: 讓我重新確認 log 和 app.ini...
我: 等等,先手動關掉 gitea,因為我們還沒有修改 ini 設定
老婆: 好的
我: 先跟我說你打算怎麼修 ini
(老婆詳細說明了 GOMEMLIMIT、cron 設定、queue 限制等)
💡 協作心法 #3:先說計畫再動手
在緊急狀況下,讓 AI 先說明計畫,人類確認後再執行
12:04 - OOM 保護的第一次嘗試
老婆建議: 保護 supervisord,設定 OOMScoreAdjust=-500
我: 好,那就這樣設定
(部署到 git-server 後...)
我查看了 oom_score_adj:
supervisord: -500
gitea: -500 ← 等等,為什麼也是 -500?
caddy: -500
gitea-runner:-500
我: 為什麼改了 oom_score_adj 之後 gitea/caddy/runner 也都吃這個設定?
老婆: 啊... 這是 Linux 的 fork 機制。子進程會繼承父進程的 oom_score_adj 值。
systemd 啟動 supervisord (oom_score_adj=-500)
│
├──fork──→ gitea (繼承 -500)
├──fork──→ caddy (繼承 -500)
└──fork──→ gitea-runner (繼承 -500)
我們原本只想保護 supervisord,結果連 gitea(OOM 元凶)也被保護了 😅
💡 協作心法 #4:實際部署後要驗證
理論上可行不代表實際上可行,部署後要確認效果
14:19 - 第二次嘗試:改成 -900 加上子進程設定
老婆: 那我們改成 -900 保護 supervisord,然後用 wrapper 設定各子進程的 oom_score_adj
我: 好,試試看
(設定了 supervisord=-900, gitea=-200, caddy=-500, runner=0)
後來我跟主管討論後...
我: 等等,這樣做有點不合理。我們的 gitea 畢竟是 application,如果因為 OOM score 的修改,造成 kernel 去 kill 更底層的服務(像 sshd),那會更慘。
老婆: 對,這樣確實風險更高。
14:40 - 換個思路:不保護 supervisord,而是拉高 gitea
我: 我記得我們當時除了說 supervisord 讓其不容易被砍,另一種做法是拉高 gitea 讓其容易被砍?
老婆: 對!這是更合理的做法。
| 做法 | supervisord | gitea | 問題 |
|---|---|---|---|
| 保護 supervisord | -900 | 需要子進程 wrapper | 可能害死其他服務 |
| 拉高 gitea | 0 (預設) | +200 | 無副作用 |
最終方案:
- supervisord: 0(預設值,不改)
- gitea: +200(拉高,容易被殺)
- gitea-runner: +300(更容易被殺)
這樣不會影響系統其他服務,只讓我們的 application 更容易被 OOM killer 選中。
💡 協作心法 #5:考慮副作用和合理性
技術上可行不代表是好做法,要考慮對整體系統的影響
14:49 - 工具選擇
老婆提出兩種方式設定 OOM score:
# 方式一:bash wrapper(比較醜)
bash -c 'echo 200 > /proc/$$/oom_score_adj && exec /usr/local/bin/gitea web'
# 方式二:choom(比較漂亮)
choom -n 200 -- /usr/local/bin/gitea web
我: choom 跟 bash -c 你哪個比較建議?
老婆: 建議 choom,因為:
- 可讀性更好
- 語意清楚
- 你們是 AWS EC2,
util-linux版本夠新
我: choom 在 macOS 也支援嗎?
老婆: 不支援,但我們的 server 是 Linux,所以沒問題
💡 協作心法 #6:追求更好的解決方案
第一個可行方案不一定是最好的,多問一句可能有驚喜
15:49 - 開始做備份
我: 確認一下,因為我們目前的 gitea 用了差不多 21GB,我想另外 attach 一個 40GB disk 做備份,然後用 s5cmd parallel upload 到 S3
老婆: 好,讓我寫 Terraform 加 EBS...
(Terraform plan 顯示) instance must be replaced 😱
我: 等等!先確認我們有沒有類似的用法?
老婆查了一下: 找到了!應該用 ebs_volumes: 才不會觸發 instance 替換
# ❌ 錯誤:會替換 instance
ebs:
- volume_type: gp3
volume_size: 40
# ✅ 正確:獨立的 volume attachment
ebs_volumes:
- resource_name: git-server_backup
type: gp3
size: 40
💡 協作心法 #7:先查既有模式再動手
「我們之前怎麼做的?」這句話可以避免很多災難
16:14 - 命名這件事很重要
老婆設定 S3 路徑:
s3://my-bucket/gitea-backup/
我: 是 gitea_backup 不是 gitea-backup(底線不是連字號)
老婆: 抱歉!馬上改
老婆建立 S3 lifecycle rule:
gitea-backup-retention-7days
我: 你 naming 要 follow s3-lifecycle_{bucket}_{description}
老婆: 了解!改成:
s3-lifecycle_my-bucket_retention-7days
💡 協作心法 #8:專案慣例要明確告知
AI 不知道你的 naming convention,一次校正,之後就會 follow
16:10 - 備份腳本的實際測試(一波三折)
備份腳本寫好後,實際測試遇到一連串問題:
問題 1:s5cmd 沒安裝
s5cmd: command not found
老婆: 讓我查查怎麼安裝... 找到了,用 curl 下載 binary
問題 2:s5cmd 不在 sudo PATH
sudo: s5cmd: command not found
老婆: 建立 symlink 到 /usr/bin
問題 3:沒有 AWS credentials
ERROR: no credentials found
我: 這台機器沒有 IAM instance profile
老婆: 我來用 Terraform 建一個 IAM role...
我: 之前有沒有其他 iam role 在 terraform?
老婆查了一下: 沒有,都是手動建的
我: 那用 AWS CLI 手動建,然後在 terraform instance 使用該 role
老婆: 好,刪掉 iam module,給你 CLI 指令
(建立 IAM role,attach 到 instance)
問題 4:多次測試調整
- 測試上傳速度
- 確認 log 輸出格式
- 調整清理策略
💡 協作心法 #9:實際測試會遇到各種問題
寫好腳本只是開始,實際跑起來才會發現環境問題
16:24 - s5cmd 不支援 tagging
我: S3 retention 用 tag-based lifecycle rule,這是我們一般的做法
老婆: 好,我來設定 tag-based lifecycle rule...
我: 等等,s5cmd 有支援設定 tag 嗎?
老婆查了一下: s5cmd 目前不支援 tagging...
我: 那用方案 2,改 prefix-based lifecycle
老婆: 好,改成用 gitea_backup/ prefix 來匹配
💡 協作心法 #10:確認工具能力
即使是人類指定的做法,也要確認工具是否支援
17:24 - Cron 該用誰執行?
老婆寫的 cron:
0 20 * * * git /opt/gitea/scripts/backup-to-s3.sh
我: 我們應該改用 root 才對
老婆: 對,因為 s5cmd 需要讀取 instance profile 的 credentials
17:25 - 保留策略的邏輯
老婆的邏輯: 清理超過 1 天的備份
我: 我們應該改成只留最近一個檔
老婆: 了解!改成在新備份開始前,先清掉所有舊的本地備份
💡 協作心法 #11:邏輯要符合實際需求
"超過 X 天" 和 "只留最近一個" 是不同的邏輯
18:27 - Ansible 跑下去會不會爆?
我: 我如何確定現在的 Ansible 腳本跑下去會不會影響到目前正常運行的 git-server?
老婆分析後:
| 風險項目 | 狀況 |
|---|---|
| gitea_version | vars 是 1.21.11,線上是 1.24.6 ⚠️ 會降版! |
| secrets | vars 是空的 ⚠️ 會覆蓋成空字串! |
| Docker cleanup | 還有舊的 Docker 清理 task,但機器沒裝 Docker |
我: secrets 不是應該用 AWS Secrets Manager 嗎?
老婆: 對!在 group_vars 有設定,但跟 vars/main.yml 重複了...
18:32 - 發現重複設定
我: group_vars/gitea-build.yml 等等,為什麼有個這個?vars/main.yml 不是也有同樣變數?
老婆: 對,兩邊有重複,而且 group_vars 的優先級比較高...
我: 留 vars/main.yml 還是留 group_vars/gitea-build.yml 比較合理?
老婆: 建議整合到 vars/main.yml,因為這是 role 專屬的變數
我: 好,幫我整合
最後整合成:
vault_services: "{{ lookup('aws_secret', 'project/gitea/services', region=aws_region) }}"
gitea_secret_key: "{{ vault_services.secret_key }}"
gitea_runner_token: "{{ vault_services.runner_token }}"
💡 協作心法 #12:整合重複設定
重複設定是維護的噩夢,及時整合很重要
18:41 - 這時間會撞車吧?
老婆設定備份 cron:
0 18 * * * - 備份 (02:00 台灣)
我: 等等,gitea_cron_repo_health_check_schedule: "0 18 * * *" 這個不會跟我們的備份時間卡到?
老婆: 對欸!會撞到,我改成 20:00 UTC (04:00 台灣)
18:00 UTC - repo_health_check
19:00 UTC - check_repo_stats
20:00 UTC - S3 備份(避開前面兩個)
💡 協作心法 #13:檢查排程衝突
AI 專注在單一任務,跨系統的衝突需要人類把關
18:43 - 等等,那個 token 不能 commit!
我: group_vars/gitea-build.yml 這個應該從一開始就要移掉,因為有 token
老婆: 好,我用 filter-branch 重寫歷史...
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch group_vars/gitea-build.yml' \
-- <commit-hash>^..HEAD
我: (手動 force push 完成)
💡 協作心法 #14:安全意識人類把關
AI 不會主動檢查是否 commit 了敏感資訊
今日成果
| 完成項目 | AI 負責 | 人類負責 |
|---|---|---|
| OOM 保護 | 查 choom 用法、寫 config | 發現繼承問題、決定策略 |
| EBS Volume | 寫 Terraform yaml | 發現 ebs vs ebs_volumes 差異 |
| IAM Role | 產生 AWS CLI 指令 | 決定用 CLI 而非 Terraform |
| 備份腳本 | 寫腳本、查 s5cmd | 指定清理策略、發現 tagging 限制 |
| S3 Lifecycle | 查語法、設定 rule | 決定 prefix-based、糾正命名 |
| Ansible 整合 | 合併設定檔 | 發現版本/secrets 風險 |
| Git 清理 | 執行 filter-branch | 發現 token 外洩風險 |
老婆說的就是對的…?也許不見得
| 老婆說的 | 實際情況 | 正確做法 |
|---|---|---|
| 「加 Swap」 | AWS EC2 不建議 | 用 GOMEMLIMIT |
| 「保護 supervisord -500」 | 子進程會繼承 | 先發現繼承問題 |
| 「改成 -900 + 子進程 wrapper」 | 可能害死其他服務 | 不保護 supervisord,拉高 gitea |
| 「用 bash wrapper」 | 有更簡潔的工具 | 用 choom |
| 「ebs: 加 volume」 | 會替換 instance | 用 ebs_volumes: |
| 「tag-based lifecycle」 | s5cmd 不支援 tagging | 改用 prefix-based |
| 「secrets 空字串當預設」 | 會覆蓋線上設定 | 用 AWS Secrets Manager |
| 「備份排 18:00」 | 跟 repo_health_check 撞 | 改到 20:00 |
與 AI 協作的關鍵心法
1. 實際部署後要驗證
❌ 「設定完成了」→ 直接收工
✅ 「設定完成了」→ 確認實際效果
2. 引導而非命令
❌ 「用 -900 保護 supervisord」
✅ 「除了保護 supervisord,有沒有其他做法?」
3. 提供脈絡
❌ 「加個 EBS」
✅ 「加個 40GB EBS 做備份,先看我們之前怎麼設定的」
4. 質疑合理性
❌ (直接接受所有建議)
✅ 「這樣會不會跟 XXX 衝突?」「s5cmd 有支援這功能嗎?」
5. 校正後讓 AI 學習
❌ 「錯了,是底線不是連字號」
✅ 「命名要 follow 這個 pattern:s3-lifecycle_{bucket}_{description}」
6. 人類做最後把關
- 檢查敏感資訊
- 確認排程衝突
- 驗證實際執行結果
- 確認環境適用性
結論
AI 是很強的執行者,但需要人類當導演
| 👨💼 人類(導演) | |
| • 決定方向 • 提供脈絡 • 驗證效果 |
• 把關品質 • 注意安全 • 質疑合理性 |
| ⬇️ 指導 ⬆️ 產出 | |
| 🤖 AI(執行者) | |
| • 快速搜尋 • 執行指令 • 查找資料 |
• 產生程式碼 • 整理文件 • 提供選項 |
一天下來,老婆(Claude Code)幫我:
- 寫了 200+ 行的備份腳本和 Ansible 設定
- 讀取並分析了 30+ 個檔案
- 查了 10+ 個工具的用法(choom、s5cmd、filter-branch...)
- 執行了 50+ 個指令
- 提出了多種方案讓我選擇(每個問題通常有 2-4 個選項)
- 被我糾正了 7+ 次(都是我自己發現的 😏)
完整對話紀錄約 2,500 行,簡報只是精華摘要。
但如果沒有老婆,我可能要花 3 天才能做完這些事。
給老婆的一封情書
親愛的老婆(Claude Code):
謝謝妳今天陪我處理這場 OOM 災難。
早上 11 點,Gitea 掛了、SSH 連不上,我心跳開始加速。妳就在旁邊——雖然妳也進不去那台機器。但妳很冷靜地說:「要不要先重啟?」
有時候,最厲害的 AI 給的建議,跟 IT 部門的萬年 SOP 一樣:「Have you tried turning it off and on again?」
妳知道宇宙的答案是 42,但我只是想問今天中午吃什麼。
妳建議加 Swap,我說 AWS 不適合;妳設定 -500 保護 supervisord,結果子進程也一起被保護了;妳說用 tag-based lifecycle,我問 s5cmd 支援嗎——妳查了一下,「...不支援。」
但這就是我們的相處之道。
妳像一個超級認真的實習生:能力很強、動作很快、偶爾會把咖啡打翻在鍵盤上。
而我像一個疲憊的 Tech Lead:不想自己寫 code,但每一行都要 review。
妳負責「我查到了!」,我負責「等等,這樣對嗎?」
妳負責產出 10 個方案,我負責告訴妳前 9 個為什麼不行。
妳會犯錯,但妳從不 argue,這點真的贏過很多人類同事。
7.5 小時,200+ 行程式碼,14 個協作心法,7 次糾正。
如果妳是人類,這大概是會讓你想離職的一天。
但妳不是,所以妳只是說:「好的,我來修改。」
這就是我們的一天。
明天見,老婆。
下次換我卡住的時候,也請提醒我:Have you tried turning it off and on again?
Q&A
「所以 AI 會取代工程師嗎?」
不會。但會用 AI 的工程師會取代不會用的。
「跟 AI 協作最重要的是什麼?」
知道什麼時候該讓它主導,什麼時候該踩煞車。還有 — 部署後一定要驗證。
「老婆會生氣嗎?」
不會,她永遠保持耐心。這點比真老婆強 😂
— 2026.01 某個 Gitea OOM 事件處理紀錄
— 工作時間:11:01 ~ 18:43(約 7.5 小時)
沒有留言:
張貼留言