跳轉到

K8s Web Container Logs/Batch Permission Denied Root Cause

概念概覽

根本原因

核心知識

根本原因

init-all.sh 在 Jenkins pipeline 中以 su -s /bin/bash www-data 執行,但其中跑的 PHP 用 Monolog 寫 log 時,會自動 mkdir 當天的日期子目錄(如 logs/batch/2026-04-10/)。問題在於 supervisord 是以 root 跑,整個 PHP-FPM master 也是 root,因此 Monolog auto-mkdir 出來的目錄 owner 是 root:root 755,後續 cron 以 www-data 跑時無法在該目錄下建檔。

drwxr-xr-x. 2 root root 16384 Apr 10 12:01 2026-04-10  ← 問題就是這個

多層修正方案

Layer 1:Dockerfile(預建目錄 + 正確 owner)

RUN mkdir -p /var/www/html/Gamania/logs/syncdb /var/www/html/Gamania/logs/batch
RUN chown -R 1000:1000 /var/www/html/
這只能確保 image 內初始狀態正確,但 init-all.sh 執行後仍會把日期子目錄建成 root。

Layer 2:docker-entrypoint.sh(容器啟動後 chown)

# 啟動前將 logs/ 擁有者統一為 www-data
chown -R 1000:1000 /var/www/html/Gamania/logs
supervisord -n -c /etc/supervisord.conf
只在容器啟動時執行一次,若 init-all.sh 之後才建出新日期目錄,則無效。

Layer 3:Jenkins Pipeline(init 之後補 chown,根治關鍵)

kubectl exec --context ${ctx} -i $POD_NAME -n ps -- \
  su -s /bin/bash www-data -c 'bash /var/www/html/env/scripts/init-all.sh ${params.restartSite}'
kubectl exec --context ${ctx} -i $POD_NAME -n ps -- \
  chown -R 1000:1000 /var/www/html/Gamania/logs
這是根治手段:init-all.sh 不管建了什麼 root:root 目錄,最後一律 chown 還給 www-data。

偵錯工具

  • kubectl exec ... -- ls -la /var/www/html/Gamania/logs/batch/ 確認日期目錄 owner
  • kubectl exec ... -- ps aux 確認誰在跑 PHP(root/www-data)
  • kubectl get pod ... -o jsonpath='{.spec.containers[0].volumeMounts}' 確認有無 EFS mount 干擾
  • kubectl rollout restart 後立即查 ls,確認新 pod 初始狀態

經驗教訓

  • Monolog 的 auto-mkdir 行為會繼承呼叫者的 uid,即使 su 成 www-data,若上層 PHP-FPM master 是 root,建出的目錄仍可能是 root

  • docker-entrypoint.sh 的 chown 只在啟動瞬間生效,init script 跑完後建的目錄不受保護

  • 根治方式:在 Jenkins pipeline 跑完 init-all.sh 之後,必須補一道 chown -R 1000:1000 /var/www/html/Gamania/logs

  • 相同版號的 ECR image build 不會強制更新,需要用不同 tag 才能讓 k8s 拉新 image

  • 偵錯時要先確認 volumeMounts,排除 EFS 等外部 mount 干擾

常見陷阱

  • 只改 Dockerfile 不改 Jenkins pipeline,init-all.sh 跑完後仍會留下 root:root 日期目錄

  • 以為 su -s /bin/bash www-data 跑 init 就沒事,但 PHP 內部 Monolog mkdir 仍可能是 root

  • rollout restart 後 pod name 換了但 image 沒換,問題依舊

  • 相同版號 tag push 到 ECR 不代表 k8s 會拉新 image(imagePullPolicy 問題)

最佳實踐

  • Dockerfile 預建 logs 子目錄並 chown,確保 image 初始狀態正確

  • entrypoint.sh 在 supervisord 啟動前補 chown logs/,覆蓋 openapi 生成等 root 操作

  • Jenkins init stage 跑完腳本後補 chown,這是唯一能覆蓋 Monolog 日期目錄的時機

  • 查 permission 問題時,先用 ls -la 確認目錄 owner,再用 ps aux 確認 process uid

相關概念

來源 Sessions

日期 Session 貢獻摘要

| 2026-04-14 | 2915f12c-2e40-49a6-9734-a50edf42bc3a | 完整追蹤並解決 K8s PHP Web container 中 logs/batch// 目錄被 root 建出、www-data cron 無法寫入的根因,以及多層修正方案。 |


本概念頁面由 Semi-Brain Wiki 系統自動維護

最後更新: 2026-04-14