Config trên Kubernetes: Secret, restart, rotation, feature flag, drift Trung cấp
Thời gian đọc ước tính: 7 phút
Trang này trả lời: config đi vào Pod thế nào, khi đổi thì cần làm gì để nó thật sự được áp dụng, và cách đổi secret, flag mà không gây sự cố.
ConfigMap và Secret vào Pod
Phần tiêu đề “ConfigMap và Secret vào Pod”spec: containers: - name: api image: registry.example.com/payment-api@sha256:... # cùng digest mọi môi trường envFrom: - configMapRef: name: payment-api-config # DATABASE_POOL_MAX, PAYMENT_API_BASE_URL, LOG_LEVEL env: - name: PAYMENT_API_SECRET_KEY valueFrom: secretKeyRef: name: payment-api-secrets key: PAYMENT_API_SECRET_KEYNhững điều docs Kubernetes nói mà hay bị bỏ qua:
| Sự thật | Hệ quả |
|---|---|
| ConfigMap “does not provide secrecy or encryption”; tối đa 1 MiB | Không để secret trong ConfigMap |
| Giá trị trong Secret là base64, “obscures them but does NOT provide any useful level of confidentiality” | base64 không phải mã hoá |
| Mặc định Secret lưu không mã hoá trong etcd | Phải bật encryption at rest, RBAC least-privilege |
| Ai được tạo Pod trong namespace thì đọc được mọi Secret của namespace đó | Phân namespace theo ranh giới quyền |
| ConfigMap dùng làm env var “are not updated automatically and require a pod restart” | Sửa ConfigMap xong, Pod đang chạy vẫn dùng giá trị cũ |
Volume mount được cập nhật dần (kubelet sync + cache); mount bằng subPath thì không |
File thì đổi được khi chạy, nhưng app phải tự đọc lại |
Sửa ConfigMap không tự rollout
Phần tiêu đề “Sửa ConfigMap không tự rollout”Deployment chỉ rollout khi Pod template (.spec.template) đổi. Sửa ConfigMap không đổi template, nên không tự rollout.
Đang tải diagram…
Cách làm phổ biến để template đổi theo config:
- Helm: annotation chứa checksum của config.
- Kustomize:
configMapGeneratorthêm hash vào tên ConfigMap. - Thủ công:
kubectl rollout restartsau khi sửa.
Kubernetes không tự rollback. Pod mới exit vì config sai thì Deployment báo ProgressDeadlineExceeded sau progressDeadlineSeconds (mặc định 600) và dừng ở đó.
Với maxUnavailable mặc định 25%, một phần Pod cũ có thể đã bị gỡ trước khi Pod mới fail. Cấu hình readiness đúng cách ở Health probes.
Restart hay hot reload?
Phần tiêu đề “Restart hay hot reload?”Restart khi config đổi (mặc định)
- Config bất biến trong suốt đời process; code không cần xử lý “giá trị đổi giữa request”.
- Rollout đi qua readiness và graceful shutdown; config sai thì Pod mới không ready, Pod cũ vẫn chạy.
- Đổi config có lịch sử như một lần deploy.
Hot reload trong process
- Mọi thay đổi phải qua pool, client, cache đã khởi tạo từ giá trị cũ; dễ có trạng thái nửa cũ nửa mới.
- Config sai được nạp vào Pod đang phục vụ, không có readiness nào chặn.
- Chỉ đáng làm cho một nhóm nhỏ giá trị: log level, secret xoay vòng, flag.
Default: restart. Giá trị cần đổi khi đang chạy thường là feature flag, nên đưa vào flag service thay vì tự viết hot reload. Ngoại lệ chính đáng là secret rotation.
Secret rotation: chừa sẵn móc
Phần tiêu đề “Secret rotation: chừa sẵn móc”Secret sẽ phải đổi: định kỳ, hoặc khi bị lộ. Đọc secret một lần lúc start thì cách duy nhất để đổi là restart. Điều đó ổn nếu quy trình có giai đoạn chuyển tiếp:
- Tạo credential mới ở provider, giữ credential cũ còn hiệu lực.
- Cập nhật Secret, rollout restart. Pod mới dùng key mới, Pod cũ vẫn dùng key cũ hợp lệ.
- Khi mọi Pod đã chạy key mới, revoke key cũ.
Với key do mình kiểm tra (webhook signing secret, JWT signing key), app phải chấp nhận hai giá trị cùng lúc trong giai đoạn chuyển:
WEBHOOK_SIGNING_SECRETS=new,old, thử lần lượt.- Hoặc JWT có
kidđể chọn key.
Thiếu móc này, rotation biến thành sự cố có kế hoạch.
Muốn đổi không restart: mount Secret thành file (không subPath), đọc lại khi file đổi hoặc khi nhận 401 từ provider. Xử lý khi secret bị lộ xem Secure coding.
Feature flag: config đổi theo runtime
Phần tiêu đề “Feature flag: config đổi theo runtime”Pete Hodgson (“Feature Toggles” trên martinfowler.com) chia flag thành bốn loại, khác nhau ở tuổi thọ và độ động:
| Loại | Dùng cho | Tuổi thọ | Quyết định |
|---|---|---|---|
| Release | Merge code chưa xong vào main, bật khi sẵn sàng | Ngắn, “transitionary by nature” | Gần như tĩnh |
| Experiment | A/B test | Đủ lâu để có số liệu | Theo từng request/user |
| Ops | Kill switch, giảm tải tính năng phụ | Thường ngắn; một số kill switch sống lâu | Phải đổi rất nhanh |
| Permissioning | Tính năng cho gói premium, beta user | Có thể rất dài | Theo từng request |
Đang tải diagram…
Bài gốc nói flag là “inventory which comes with a carrying cost”. Trong thực tế:
- Mỗi flag có owner và ngày hết hạn khi tạo. Release flag quá hạn là ticket, không phải trang trí.
- Có thể đặt giới hạn số flag đang tồn tại, như bài gốc gợi ý, để ép việc dọn dẹp.
- Kill switch (ops flag sống lâu) là ngoại lệ có chủ đích: tắt recommendation khi DB quá tải. Kiểm tra định kỳ rằng tắt nó vẫn chạy được.
- Flag đọc lúc runtime nên có giá trị fallback khi flag service không trả lời. Fallback của tính năng mới là
false.
Env var FEATURE_X_ENABLED vẫn hợp lý cho release flag tĩnh. Khi cần bật theo user hoặc tắt trong vài giây, chuyển sang flag service.
Config drift và dev/prod parity
Phần tiêu đề “Config drift và dev/prod parity”12-factor nêu ba khoảng cách giữa dev và prod: thời gian, con người, công cụ. Với config, drift thường trông như sau:
- Staging có key mà production thiếu. Phát hiện lúc deploy production.
- Ai đó
kubectl editConfigMap production lúc 2h sáng, không ai ghi lại. Lần apply manifest sau ghi đè mất. - Local dùng SQLite, production dùng PostgreSQL. 12-factor gọi thẳng: “resists the urge to use different backing services”.
Cách chặn:
- Config (trừ secret) nằm trong git, apply qua CI hoặc GitOps. Không sửa tay trên cluster.
- Schema trong
loadConfiglà nguồn sự thật duy nhất. CI kiểm tra mỗi môi trường có đủ key của schema. - Local chạy cùng loại DB, broker, cache với production (container), dù nhỏ hơn.
- Đổi tên hay xoá key theo kiểu expand–contract: đọc cả key mới và cũ trong một release, cập nhật mọi môi trường, rồi mới bỏ key cũ.
Tiếp theo: Lỗi hay gặp.