ConfigMap, config ngoài image

Tách config khỏi container image → cùng image chạy ở dev/staging/prod với config khác nhau.

Tạo ConfigMap

# Từ literal
kubectl create configmap app-config \
  --from-literal=DB_HOST=postgres \
  --from-literal=LOG_LEVEL=info

# Từ file
kubectl create configmap nginx-config --from-file=nginx.conf

# Từ thư mục
kubectl create configmap configs --from-file=./config-dir/
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  DB_HOST: postgres
  LOG_LEVEL: info
  config.yaml: |
    server:
      port: 8080
      timeout: 30s    

Inject qua environment variable

spec:
  containers:
    - name: app
      image: my-app:1.2.0
      env:
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DB_HOST
      # Hoặc inject tất cả keys
      envFrom:
        - configMapRef:
            name: app-config

Inject qua volume

spec:
  containers:
    - name: app
      volumeMounts:
        - name: config
          mountPath: /etc/config
          readOnly: true
  volumes:
    - name: config
      configMap:
        name: app-config

Kết quả: /etc/config/DB_HOST, /etc/config/LOG_LEVEL, /etc/config/config.yaml, mỗi key thành file.

subPath, mount file đơn lẻ

volumeMounts:
  - name: config
    mountPath: /app/config.yaml # Chỉ mount 1 file
    subPath: config.yaml # Key trong ConfigMap

Cảnh báo: subPath không nhận hot reload khi ConfigMap thay đổi.


Secret, “config nhạy cảm”

Secret giống ConfigMap nhưng cho dữ liệu nhạy cảm: password, API key, TLS cert.

Tạo Secret

kubectl create secret generic db-creds \
  --from-literal=DB_PASSWORD=s3cret \
  --from-literal=DB_USER=admin
apiVersion: v1
kind: Secret
metadata:
  name: db-creds
type: Opaque
data:
  DB_PASSWORD: czNjcmV0 # Base64 encoded, KHÔNG phải mã hoá
  DB_USER: YWRtaW4=
stringData: # Plaintext (K8s tự encode khi apply)
  API_KEY: my-api-key-123

Inject giống ConfigMap

env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: db-creds
        key: DB_PASSWORD

# Hoặc volume mount
volumes:
  - name: creds
    secret:
      secretName: db-creds

Secret KHÔNG mã hoá

Quan trọng: Secret chỉ là base64 encoded, không encrypt. Ai có quyền get secret trong namespace → đọc được plaintext.

# Decode secret
kubectl get secret db-creds -o jsonpath='{.data.DB_PASSWORD}' | base64 -d

Bảo vệ thực sự cần:

  1. Encryption at rest: cấu hình kube-apiserver encrypt Secret trong etcd (EncryptionConfiguration).
  2. RBAC: giới hạn ai được get/list secret.
  3. Sealed Secrets: encrypt secret trong Git, chỉ cluster decrypt được.
  4. External Secrets Operator: sync secret từ Vault, AWS Secrets Manager, GCP Secret Manager.

Env vs Volume, khi nào dùng gì

Khía cạnhEnv variableVolume mount
Hot reload❌ Cần restart pod✅ Tự cập nhật (trừ subPath)
Xem trong log⚠️ Dễ leak (env, kubectl describe)Ít rủi ro hơn
File config phức tạp❌ Chỉ key-value✅ File bất kỳ
Legacy app đọc env✅ Tương thíchCần app đọc file

Best practice cho secret: dùng volume mount thay env. Env dễ leak qua docker inspect, process listing, crash dump.


Hot reload

Khi ConfigMap/Secret thay đổi:

  • Volume mount (không subPath): kubelet tự cập nhật file trong pod, thời gian tuỳ sync period (mặc định ~1 phút). App cần watch file hoặc reload khi file đổi.
  • Env variable: không cập nhật cho đến khi pod restart.
  • subPath mount: không cập nhật, kubelet chỉ mount lúc pod start.
# Force restart pod khi config thay đổi
kubectl rollout restart deploy/my-app

# Hoặc dùng hash annotation (Helm pattern)
# Template: checksum/config: {{ include (print .Template.BasePath "/configmap.yaml") . | sha256sum }}

Downward API

Inject metadata về chính pod vào container:

env:
  - name: POD_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.name
  - name: POD_NAMESPACE
    valueFrom:
      fieldRef:
        fieldPath: metadata.namespace
  - name: POD_IP
    valueFrom:
      fieldRef:
        fieldPath: status.podIP
  - name: NODE_NAME
    valueFrom:
      fieldRef:
        fieldPath: spec.nodeName
  - name: CPU_REQUEST
    valueFrom:
      resourceFieldRef:
        containerName: app
        resource: requests.cpu
  - name: MEM_LIMIT
    valueFrom:
      resourceFieldRef:
        containerName: app
        resource: limits.memory

Hoặc qua volume:

volumes:
  - name: pod-info
    downwardAPI:
      items:
        - path: labels
          fieldRef:
            fieldPath: metadata.labels
        - path: annotations
          fieldRef:
            fieldPath: metadata.annotations

Use case: logging (thêm pod name/namespace vào structured log), app tự detect resource limits (JVM -XX:MaxRAMPercentage).


Immutable ConfigMap/Secret

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v2
immutable: true # Không cho sửa sau khi tạo

Lợi ích: giảm load lên API server (không watch changes), tránh sửa nhầm config production. Đổi config = tạo ConfigMap mới + update Deployment reference.


Lab: đổi config mà pod không nhận

Khi sửa ConfigMap mà app không đổi hành vi, kiểm tra app lấy config bằng env hay volume:

kubectl -n app get deploy api -o yaml | grep -A20 -E 'envFrom|configMapRef|volumeMounts'
kubectl -n app get configmap app-config -o yaml
kubectl -n app exec deploy/api -- printenv | grep APP_
kubectl -n app exec deploy/api -- ls -l /etc/app-config

Env var chỉ được inject lúc container start, nên đổi ConfigMap không tự đổi env trong container đang chạy. Volume mount có thể cập nhật sau một khoảng delay, nhưng nếu dùng subPath thì cũng không hot reload.

Với config production quan trọng, cách ít mơ hồ nhất là version config name (app-config-v3) rồi rollout Deployment có chủ ý.


Điều cần giữ khi vận hành Kubernetes

  • ConfigMap cho config không nhạy cảm. Secret cho password/key/cert, nhưng chỉ base64, không encrypt mặc định.
  • Volume mount tốt hơn env: hot reload, ít leak, hỗ trợ file phức tạp.
  • subPath không hot reload.
  • Downward API inject pod metadata vào container.
  • Secret cần bảo vệ thêm: encryption at rest, RBAC, hoặc External Secrets Operator.

Câu hỏi hay gặp

ConfigMap thay đổi nhưng app không nhận config mới?

Trả lời: Kiểm tra: (1) dùng env → cần restart pod; (2) dùng subPath → cũng cần restart; (3) dùng volume mount → file cập nhật sau ~1 phút, nhưng app phải đọc lại file (watch hoặc periodic reload). Không có magic reload.

Secret trong Git repo, cách xử lý?

Trả lời: Không commit Secret YAML plaintext vào Git. Dùng: (1) Sealed Secrets, encrypt bằng cluster public key, chỉ cluster decrypt; (2) External Secrets Operator, sync từ Vault/AWS/GCP; (3) SOPS, encrypt file YAML bằng KMS key.

ConfigMap/Secret size limit?

Trả lời: Mỗi ConfigMap/Secret tối đa 1 MiB (etcd limit). Config lớn hơn → chia nhỏ hoặc dùng init container download từ S3/GCS.


Bài tiếp theo (Giai đoạn IV): Volume, PVC và stateful workload, lưu trữ bền vững trên Kubernetes.