ConfigMap và Secret đã được nạp đủ vào container, kubectl exec ... env in ra đúng từng biến, vậy mà Laravel chạy trên web vẫn không đọc được chúng. Đây là lỗi rất hay gặp khi đưa một image Laravel lên Kubernetes, và thủ phạm thường là clear_env, một tham số của PHP-FPM mặc định xoá sạch biến môi trường trước khi chạy code PHP. Tài liệu này giải thích clear_env hoạt động thế nào, cách phân biệt nó với các lỗi cấu hình khác, cách sửa, cái giá về bảo mật, và vài cái bẫy khác khi chạy image Laravel all-in-one trên Kubernetes.
1. Triệu chứng: biến có trong container nhưng Laravel không thấy
Giả sử bạn dùng image kiểu all-in-one như trong bài Docker hoá Laravel: một image all-in-one cho Website AI: một container chạy supervisord, supervisord khởi động nginx và PHP-FPM. Deployment nạp biến bằng envFrom từ ConfigMap và Secret, đúng như bài ConfigMap và Secret: biến môi trường Laravel trên Kubernetes. Mọi thứ trông đúng:
- Pod lên được, nghĩa là initContainer đã chạy xong
php artisan migrate --force: lệnhphpkết nối được MySQL bằng thông tin trong ConfigMap và Secret. - Vào container gõ
envthấy đủDB_HOST,DB_DATABASE,APP_URL. - Nhưng request qua trình duyệt thì Laravel hành xử như thể không có những biến đó.
Điểm mấu chốt: "cùng một image, cùng một bộ biến, chạy bằng lệnh php thì đúng, chạy qua web thì sai". Lệnh CLI và web đi qua hai đường khác nhau, và chỉ đường web đi qua PHP-FPM.
2. clear_env là gì
Theo tài liệu cấu hình FPM trên php.net, clear_env quyết định có xoá môi trường của các worker FPM hay không, trước khi thêm các biến khai báo bằng env[...] trong cấu hình pool. Giá trị mặc định là yes. File www.conf của nhiều bản phân phối ghi rõ điều đó trong chú thích, còn dòng tắt nó thì bị comment sẵn:
; Clear environment in FPM workers
; Prevents arbitrary environment variables from reaching FPM worker processes
; by clearing the environment in workers before env vars specified in this
; pool configuration are added.
; Setting to "no" will make all environment variables available to PHP code
; via getenv(), $_ENV and $_SERVER.
; Default Value: yes
;clear_env = noĐi theo đường đi của một biến môi trường trong container sẽ thấy vì sao CLI đúng còn web sai:
kubelet ── envFrom (ConfigMap, Secret) ──► tiến trình chính của container: supervisord
│ (tiến trình con thừa hưởng env)
┌──────────────────────────────┼─────────────────────────────┐
▼ ▼ ▼
nginx php-fpm (master) queue:work
│ CÓ đủ biến
│ clear_env = yes
▼
php-fpm worker: env BỊ XOÁ
← mọi request web chạy ở đây
kubectl exec / initContainer ──► php artisan ... CÓ đủ biến (không qua FPM)Thiết kế này có lý do: trên máy chủ dùng chung, môi trường của tiến trình cha có thể chứa đủ thứ không liên quan, không nên lọt vào code PHP của từng site. Image PHP chính thức trên Docker Hub đã đặt sẵn clear_env = no, vì trong container, biến môi trường chính là kênh cấu hình. Còn image tự dựng từ Alpine hay Debian và cài PHP bằng trình quản lý gói (apk, apt) sẽ giữ nguyên mặc định yes của bản phân phối.
Vì sao Laravel không báo lỗi mà lặng lẽ dùng giá trị khác
Nếu Laravel không thấy biến nào, nó hỏng ngay và dễ nhận ra. Trường hợp khó hơn là khi Dockerfile có dòng:
RUN cp .env.example .envLaravel đọc file .env bằng thư viện phpdotenv ở chế độ không ghi đè: biến nào đã có trong môi trường thì giữ nguyên, file .env chỉ lấp chỗ trống. Với CLI, môi trường có đủ biến thật nên .env gần như vô tác dụng. Với worker FPM, môi trường đã bị clear_env xoá trắng, nên toàn bộ cấu hình lấy từ .env.example nằm sẵn trong image: APP_ENV=local, DB_HOST=127.0.0.1, APP_URL của môi trường phát triển. Ứng dụng không báo "thiếu biến", nó chỉ kết nối nhầm chỗ, sinh link nhầm domain. Đây là kiểu lỗi tốn thời gian nhất: mọi thứ có vẻ "gần đúng".
3. Điều tra clear_env theo từng lớp
So sánh từng lớp, từ ngoài vào trong, cho tới khi tìm ra lớp làm mất biến:
# Lớp 1: container có biến không?
kubectl exec -n app deploy/app -- env | grep -E '^(APP_URL|DB_HOST)='
# Lớp 2: PHP chạy bằng CLI có thấy không?
kubectl exec -n app deploy/app -- php -r 'var_dump(getenv("DB_HOST"));'
# Lớp 3: PHP-FPM đang thật sự dùng cấu hình nào?
kubectl exec -n app deploy/app -- php-fpm -tt 2>&1 | grep -E 'clear_env|include'
# tên binary tuỳ bản phân phối: php-fpm, php-fpm82, php-fpm8.2...Lớp 4 là nhìn từ phía web: một route tạm thời trả về getenv('DB_HOST'), hoặc mục Environment trong phpinfo(). Nếu lớp 1 và 2 có giá trị còn lớp 4 rỗng, gần như chắc chắn là clear_env. Xoá các route kiểu này ngay sau khi điều tra xong: chúng in ra đúng những thứ không được phép lộ.
Một dấu hiệu nhận biết rẻ hơn: đổi APP_DEBUG trong ConfigMap, restart pod, rồi gây một lỗi trên web. Nếu trang lỗi không đổi theo, biến môi trường không tới được PHP-FPM.
4. Sửa: clear_env = no
Bản sửa chỉ một dòng trong file pool, kèm việc bỏ file .env khỏi image:
# .docker/www.conf
-;clear_env = no
+clear_env = no
# Dockerfile
-RUN cp .env.example .envBuild lại image, deploy lại, web bắt đầu thấy đúng biến từ ConfigMap, Secret. Bỏ .env khỏi image quan trọng không kém dòng clear_env: nếu một biến thiếu thật thì Laravel hỏng rõ ràng thay vì âm thầm lấy giá trị mẫu. Lưu ý thay đổi ConfigMap không tự đi vào container đang chạy; phải tạo lại pod (ví dụ kubectl rollout restart deploy/app -n app) thì biến mới có hiệu lực.
Chắc chắn file cấu hình của bạn được nạp
Một lỗi cùng họ: Dockerfile chép www.conf vào /etc/php-fpm.d/www.conf, nhưng PHP-FPM của bản phân phối chỉ đọc thư mục khác, ví dụ trên Alpine là include=/etc/php82/php-fpm.d/*.conf. File nằm sai chỗ, không có thông báo lỗi nào, FPM vẫn chạy bằng www.conf mặc định của gói, và mọi chỉnh sửa của bạn (kể cả clear_env) không có tác dụng. Sau khi chép cấu hình vào image, luôn kiểm tra cấu hình hiệu lực bằng php-fpm -tt (với nginx là nginx -T), đừng tin vào việc lệnh COPY chạy thành công.
5. Cân nhắc bảo mật khi đặt clear_env = no
Tắt clear_env nghĩa là mọi biến của container, kể cả DB_PASSWORD, APP_KEY, khoá object storage, đều nằm trong getenv(), $_ENV và $_SERVER của mọi request. Hệ quả:
phpinfo()để quên ở đâu đó sẽ in toàn bộ bí mật.- Trang lỗi khi bật
APP_DEBUGcó thể hiển thị biến môi trường. Nếu phải bật debug để điều tra, hãy tắt lại ngay khi xong; website thật luôn đểAPP_DEBUG=false. - Một lỗ hổng cho phép đọc biến hoặc chạy lệnh tuỳ ý trong PHP sẽ lấy được ngay mọi bí mật của site.
Các phương án và đánh đổi:
| Cách làm | Ưu điểm | Nhược điểm |
|---|---|---|
clear_env = no | Đơn giản, thêm biến mới không phải sửa image | Mọi biến của container đều lộ cho code PHP |
Giữ clear_env = yes, khai báo từng biến env[DB_HOST] = $DB_HOST trong pool | Chỉ biến được liệt kê mới tới PHP | Danh sách dài, thêm biến phải build lại image, quên một dòng là lỗi lặp lại |
Chạy php artisan config:cache lúc container khởi động | Worker FPM đọc file cache, không cần env | Gọi env() ngoài thư mục config/ trả về null; phải chạy đúng thời điểm, đúng filesystem |
Trong container chỉ chạy một ứng dụng, biến môi trường vốn được dựng riêng cho ứng dụng đó, nên clear_env = no là lựa chọn chấp nhận được. Điều cần siết hơn là bí mật nằm sai chỗ: mật khẩu trong ConfigMap, file Secret commit vào Git.
6. Các bẫy khác khi Docker hoá Laravel cho Kubernetes
config:cachelúc build image. Lúcdocker buildchưa có ConfigMap hay Secret nào. Chạyphp artisan config:cache(hoặcoptimize) trong Dockerfile sẽ đóng băng giá trị mặc định vàobootstrap/cache/config.php, và Laravel bỏ qua biến môi trường từ đó về sau. Tài liệu Laravel nói rõ hệ quả này trong mục Configuration Caching.config:cachetrong initContainer. Nghe hợp lý nhưng vô ích: initContainer có filesystem riêng, file cache nó tạo không sang được container chính, trừ khi hai bên dùng chung một volume.- File
.envnằm trong image, như mục 2. Nó che lỗi thiếu biến và dễ mang theo khoá thật nếu.env.examplechứa giá trị không phải mẫu. - Log không ra stdout. Chuyển log truy cập và log lỗi của nginx ra
/dev/stdoutvà/dev/stderr, nếu khôngkubectl logschỉ thấy log của supervisord. - User không thống nhất. nginx, PHP-FPM và quyền sở hữu thư mục phải cùng một user (ví dụ cùng là
nginxthay vì nửanobody, nửanginx), vàchownmã nguồn tương ứng để quyền ghistoragenhất quán. - Kiến trúc CPU. Dùng
FROM --platform=linux/amd64(hoặc build đa kiến trúc) để image build trên máy ARM vẫn chạy được trên node amd64.
7. Checklist khi biến môi trường "biến mất"
- Container có biến không (
env)? - PHP CLI có thấy không (
php -r 'var_dump(getenv(...))')? - File pool của bạn có thật sự được nạp (
php-fpm -tt)? clear_env đang là gì? - Có file
.envhoặcbootstrap/cache/config.phpnằm sẵn trong image không? - Đã tạo lại pod sau khi sửa ConfigMap chưa?
- Đã xoá route debug, tắt
APP_DEBUGsau khi điều tra chưa?
Lỗi clear_env khó tìm vì nằm ở chỗ giao nhau của ba thứ: cách Kubernetes truyền biến, cách PHP-FPM sinh worker và cách Laravel đọc .env. Mỗi thứ đều đúng theo tài liệu riêng. Phương pháp mới là bài học chính: khi một giá trị "biến mất", kiểm tra từng lớp mà nó phải đi qua, và đừng để giá trị mặc định nào che đi lỗi thiếu cấu hình. Cách Laravel dùng FPM pool trên Cloud VPS có trong bài Tầng Minion: chạy nhiều phiên bản PHP song song, còn đường vào cluster qua HTTPS nằm ở bài ingress-nginx và cert-manager: HTTPS tự động trên Kubernetes.
Bài tiếp theo
Giám sát Kubernetes với kube-prometheus và Grafana. Website đã chạy, giờ cần nhìn thấy cluster: Prometheus Operator, node-exporter, kube-state-metrics, Grafana, và bảo vệ chúng bằng basic auth qua Ingress.
