ConfigMap và Secret: Tách Cấu Hình Khỏi Image

K
Kai··4 min read

Một image tốt là một image bất biếndùng được mọi nơi: cùng một image nginx hay cùng một image app của bạn phải chạy được ở máy dev, ở staging, ở production. Điều đó chỉ khả thi nếu cấu hình (URL database, cờ tính năng, mật khẩu) không nằm trong image mà được tiêm vào lúc chạy. Kubernetes cho hai công cụ: ConfigMap cho cấu hình thường, Secret cho dữ liệu nhạy cảm.

Vì sao không nhét cấu hình vào image

Nếu bạn build mật khẩu DB hay URL backend thẳng vào image, bạn sẽ phải build lại image cho mỗi môi trường — đánh mất ý nghĩa của container. Tệ hơn, mật khẩu nằm trong image là rủi ro bảo mật (ai pull image cũng đọc được). Nguyên tắc (theo tinh thần 12-factor app): tách cấu hình ra khỏi code. Trong Kubernetes, ConfigMap và Secret là nơi chứa cấu hình tách rời đó.

   image (bất biến)  +  ConfigMap/Secret (theo môi trường)  =  pod chạy
        nginx:1.27          APP_MODE=production
                            DB_PASSWORD=...

ConfigMap: cấu hình không nhạy cảm

kubectl create configmap app-config \
  --from-literal=APP_COLOR=blue \
  --from-literal=APP_MODE=production
configmap/app-config created

ConfigMap chỉ là một túi cặp key=value. Ngoài --from-literal, bạn tạo được từ file (--from-file=config.properties) hoặc khai báo bằng YAML — cách hợp để lưu vào Git.

Secret: dữ liệu nhạy cảm

kubectl create secret generic app-secret \
  --from-literal=DB_PASSWORD='s3cr3t-p@ss'
secret/app-secret created

Secret nhìn giống ConfigMap, nhưng dành riêng cho mật khẩu, token, khoá. Kubernetes đối xử với Secret cẩn trọng hơn (không in giá trị ra log mặc định, có thể giới hạn truy cập). Nhưng có một điều bạn nhất định phải biết.

Cảnh báo: Secret chỉ là base64, KHÔNG phải mã hóa

Đây là hiểu lầm phổ biến và nguy hiểm nhất về Kubernetes. Xem giá trị Secret vừa tạo:

kubectl get secret app-secret -o jsonpath='{.data.DB_PASSWORD}'
czNjcjN0LXBAc3M=

Trông như "đã được bảo vệ". Nhưng base64 là mã hoá thuận nghịch ai cũng giải được, không phải mật mã:

kubectl get secret app-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
s3cr3t-p@ss

Mật khẩu hiện nguyên hình. Vậy nên:

  • Đừng commit Secret YAML vào Git như thể nó an toàn — base64 không che được gì.
  • Mặc định Secret lưu trong etcd ở dạng không mã hoá. Production cần bật encryption at rest cho etcd, và/hoặc dùng giải pháp ngoài như Sealed Secrets, External Secrets Operator, hay vault của nhà cung cấp cloud.
  • Giá trị của "Secret" so với "ConfigMap" nằm ở cách Kubernetes đối xử và phân quyền (RBAC, không log), chứ bản thân nó không mã hoá nội dung.

Nhớ kỹ điều này thì bạn tránh được một lỗ hổng kinh điển.

Inject vào pod: hai cách

ConfigMap và Secret vô dụng nếu pod không đọc được. Có hai cách tiêm: thành biến môi trường, hoặc mount thành file. Pod sau dùng cả hai:

apiVersion: v1
kind: Pod
metadata:
  name: config-demo
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "echo COLOR=$APP_COLOR MODE=$APP_MODE; cat /etc/secret/DB_PASSWORD; sleep 3600"]
      env:                                  # cách 1: ConfigMap → biến môi trường
        - name: APP_COLOR
          valueFrom:
            configMapKeyRef: { name: app-config, key: APP_COLOR }
        - name: APP_MODE
          valueFrom:
            configMapKeyRef: { name: app-config, key: APP_MODE }
      volumeMounts:                         # cách 2: Secret → file
        - name: secret-vol
          mountPath: /etc/secret
          readOnly: true
  volumes:
    - name: secret-vol
      secret:
        secretName: app-secret
kubectl apply -f pod-config.yaml
kubectl logs config-demo
COLOR=blue MODE=production
s3cr3t-p@ss

Thành công cả hai đường:

  • Biến môi trường (env.valueFrom.configMapKeyRef): giá trị ConfigMap thành $APP_COLOR, $APP_MODE trong container. Hợp cho cấu hình đơn giản. (Tiêm cả ConfigMap một lần bằng envFrom.)
  • Mount thành file (volumes.secret + volumeMounts): mỗi key của Secret thành một file trong /etc/secret/. Hợp cho file cấu hình, chứng chỉ TLS, hoặc khi không muốn secret lộ qua biến môi trường (env dễ bị in ra log hơn).

Khi nào dùng cách nào? Cấu hình ngắn gọn → env. File cấu hình/chứng chỉ, hoặc secret cần kín đáo hơn → mount file. Một điểm tiện của mount: nếu cập nhật ConfigMap, file mount sẽ tự cập nhật theo (env thì không — phải dựng lại pod).

Tổng kết

Tách cấu hình khỏi image để một image chạy được mọi môi trường — đó là việc của ConfigMap (cấu hình thường) và Secret (dữ liệu nhạy cảm). Cả hai inject vào pod theo hai cách: biến môi trường (configMapKeyRef/secretKeyRef, hoặc envFrom) hoặc mount thành file (qua volumes). Điều tối quan trọng: Secret chỉ là base64, không phải mã hoá — ai có quyền đọc là thấy giá trị; production phải bật encryption at rest cho etcd và/hoặc dùng giải pháp secret chuyên dụng, và đừng commit Secret YAML như thể nó an toàn.

Cấu hình đã tách rời, nhưng dữ liệu trong pod vẫn bay hơi cùng pod — pod chết là mất sạch. Bài 8: Volumes, PV và PVC — cách lưu trữ dữ liệu bền vững, sống lâu hơn vòng đời pod.