Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 21 — Kubernetes ConfigMap & Secret

Đã đọc 0/14 mục · còn khoảng 10 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Ở các bài trước, bạn đã biết cách đóng gói ứng dụng thành Pod, phơi bày nó ra ngoài bằng Service. Nhưng có một câu hỏi cực kỳ thực tế mà mọi DevOps Engineer đều gặp trong tuần làm việc đầu tiên: "Ứng dụng của tôi cần biết địa chỉ database, cần mật khẩu Redis, cần API key của cổng thanh toán — tôi nhét mấy thứ này vào đâu?"

Câu trả lời sai (mà rất nhiều team Việt Nam vẫn đang làm) là: hardcode thẳng vào Docker image, hoặc nhét vào file .env rồi copy vào image lúc build. Cách này biến config thành một phần của artifact — nghĩa là mỗi lần đổi mật khẩu database, bạn phải build lại image, push lại registry, deploy lại. Tệ hơn, mật khẩu nằm chình ình trong lịch sử image, ai docker history cũng thấy.

Kubernetes tách bạch hai khái niệm này bằng hai object: ConfigMap (cho config không nhạy cảm) và Secret (cho dữ liệu nhạy cảm như mật khẩu, token, chứng chỉ TLS). Đây chính là ứng dụng thực tế của nguyên tắc "config tách khỏi code" trong triết lý Twelve-Factor App — cùng một image chạy được ở dev, staging, production, chỉ khác nhau ở lớp config được "tiêm" vào lúc runtime.

Nắm vững ConfigMap và Secret là ranh giới giữa một người "biết chạy kubectl apply" và một DevOps Engineer thực thụ. Đây cũng là chủ đề gần như chắc chắn xuất hiện trong mọi buổi phỏng vấn Kubernetes tại Việt Nam.

Khái niệm cốt lõi

ConfigMap — nơi chứa config không nhạy cảm

ConfigMap là một object lưu dữ liệu dạng key-value dưới dạng plain text. Dùng nó cho những thứ như: URL của service khác, log level, feature flag, tên môi trường, hay cả một file cấu hình hoàn chỉnh (ví dụ nginx.conf).

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
  namespace: production
data:
  APP_ENV: "production"
  LOG_LEVEL: "info"
  DB_HOST: "postgres.production.svc.cluster.local"
  DB_PORT: "5432"
  MAX_CONNECTIONS: "100"

Lưu ý: mọi giá trị trong data đều là chuỗi. Con số 5432 phải để trong ngoặc kép, nếu không YAML sẽ hiểu là số nguyên và Kubernetes sẽ báo lỗi. Đây là lỗi kinh điển của người mới.

ConfigMap còn có trường binaryData (cho dữ liệu nhị phân, base64) nhưng 99% trường hợp bạn chỉ dùng data.

Secret — nơi chứa dữ liệu nhạy cảm

Secret trông rất giống ConfigMap về cấu trúc, nhưng giá trị được lưu dưới dạng base64. Điều tối quan trọng cần khắc cốt ghi tâm: base64 KHÔNG phải là mã hóa (encryption), nó chỉ là encoding. Bất kỳ ai có echo <chuỗi> | base64 -d đều đọc ngược ra được. Base64 chỉ giúp lưu dữ liệu nhị phân (như file chứng chỉ) một cách an toàn về mặt định dạng, chứ không bảo vệ về mặt bảo mật.

apiVersion: v1
kind: Secret
metadata:
  name: api-secret
  namespace: production
type: Opaque
stringData:
  DB_PASSWORD: "Sup3rS3cr3t!2026"
  JWT_SECRET: "aVeryLongRandomStringHere"

Mẹo quan trọng: dùng stringData thay vì data, Kubernetes sẽ tự động base64-encode giúp bạn khi lưu. Nếu dùng data, bạn phải tự encode trước — dễ sai và khó đọc.

Trường type phổ biến:

  • Opaque: kiểu mặc định, dữ liệu tùy ý (mật khẩu, token).
  • kubernetes.io/dockerconfigjson: credential để pull image từ private registry.
  • kubernetes.io/tls: chứng chỉ TLS (tls.crt và tls.key).

Hai cách "tiêm" config vào Pod

Cả ConfigMap lẫn Secret đều có thể được đưa vào container theo hai cách, và việc chọn đúng cách rất quan trọng:

Cách 1 — Environment variables: Biến từng key thành biến môi trường. Đơn giản, phù hợp với vài giá trị lẻ. Nhược điểm: env var không tự cập nhật khi ConfigMap thay đổi (phải restart Pod), và biến môi trường dễ bị lộ qua log hay kubectl describe pod.

Cách 2 — Volume mount: Mount ConfigMap/Secret thành các file trong một thư mục. Phù hợp khi bạn có cả một file config (như application.yml, nginx.conf) hoặc chứng chỉ. Ưu điểm lớn: khi ConfigMap thay đổi, file trong volume tự động cập nhật (sau khoảng 1 phút) mà không cần restart Pod — dù ứng dụng của bạn phải tự đọc lại file.

Bức tranh bảo mật thật của Secret

Đây là phần nhiều người hiểu sai. Mặc định, Secret được lưu trong etcd (cơ sở dữ liệu của cluster) ở dạng base64, không mã hóa. Muốn thực sự an toàn, bạn cần:

  • Bật Encryption at Rest cho etcd (mã hóa dữ liệu Secret trong etcd).
  • Siết RBAC để giới hạn ai được get/list Secret.
  • Cân nhắc giải pháp bên ngoài như HashiCorp Vault hoặc cloud KMS (sẽ học ở Bài 40).
Trong phạm vi bài này, ta tập trung vào cách tạo và dùng Secret đúng chuẩn Kubernetes native.

Tình huống thực tế

Ví dụ 1 — Tiki tách config theo môi trường

Giả sử đội backend của một sàn thương mại điện tử lớn như Tiki vận hành dịch vụ order-service. Trước đây, mỗi môi trường (dev, staging, production) có một Docker image riêng vì config được build thẳng vào image. Kết quả: một image "chạy tốt ở staging" nhưng khi lên production lại lỗi vì ai đó quên đổi biến, và tệ hơn — image staging vô tình chứa credential production.

Sau khi chuẩn hóa, họ giữ một image duy nhất order-service:1.4.2. Mỗi namespace (staging, production) có một ConfigMap order-config và một Secret order-secret riêng. Cùng một image, deploy lên staging thì lấy config staging, lên production thì lấy config production. Bài toán "chạy được ở máy tôi mà không chạy trên production" gần như biến mất, vì artifact được kiểm thử ở staging chính xác là artifact chạy production.

Bài học: Tách config khỏi image giúp đảm bảo immutability của artifact — thứ bạn test chính là thứ bạn ship. Config khác biệt nằm ở lớp Kubernetes, không nằm trong image.

Ví dụ 2 — Startup fintech ở TP.HCM và cú lộ API key

Một startup fintech khoảng 20 người ở Quận 1 tích hợp cổng thanh toán VNPay. Bạn dev đưa VNPAY_SECRET_KEY vào một ConfigMap (chứ không phải Secret) để "cho nhanh". ConfigMap đó được commit lên Git repo cấu hình dùng cho GitOps. Vài tuần sau, khi họ chuyển repo config từ private sang public để chia sẻ mẫu với đối tác, secret key thanh toán bị lộ ra internet. Hậu quả là phải quay (rotate) toàn bộ credential khẩn cấp lúc 2 giờ sáng.

Sau sự cố, họ áp ba quy tắc cứng: (1) mọi thứ nhạy cảm bắt buộc là Secret, không bao giờ là ConfigMap; (2) không commit Secret thô lên Git — dùng Sealed Secrets (mã hóa Secret trước khi đưa vào Git, chỉ controller trong cluster mới giải mã được); (3) bật Encryption at Rest cho etcd.

Bài học: Ranh giới ConfigMap/Secret không phải chuyện kỹ thuật thuần túy — nó là ranh giới bảo mật. Đặt sai chỗ một lần có thể trả giá bằng cả đêm rotate credential và niềm tin của đối tác.

Ví dụ 3 — Team platform cập nhật feature flag không cần rebuild

Một công ty SaaS ở Đông Nam Á có dịch vụ frontend đọc feature flag từ file features.json được mount từ ConfigMap dưới dạng volume. Khi họ muốn bật tính năng "thanh toán trả góp" cho 10% người dùng, họ chỉ cần kubectl edit configmap để sửa giá trị flag. Sau khoảng một phút, file trong Pod tự cập nhật, ứng dụng (có cơ chế watch file) đọc lại và bật tính năng — không rebuild image, không restart Pod, không downtime.

Nhưng họ cũng học được một điều đau: nếu config được tiêm qua env var thay vì volume, thay đổi sẽ không có hiệu lực cho tới khi Pod restart. Có lần một bạn sửa ConfigMap dạng env var rồi ngồi đợi mãi không thấy đổi, tưởng bug, mất nửa buổi debug.

Bài học: Volume mount cập nhật động, env var thì không. Chọn cách tiêm phù hợp với nhu cầu "cần đổi nóng" của bạn.

Hướng dẫn từng bước

Ta sẽ deploy một ứng dụng web đọc config từ ConfigMap và mật khẩu từ Secret.

Bước 1 — Tạo ConfigMap. Cách khai báo (imperative), nhanh cho lúc thử:

kubectl create configmap api-config \
  --from-literal=APP_ENV=production \
  --from-literal=LOG_LEVEL=info \
  -n production

Hoặc tạo từ file YAML (declarative, khuyến nghị cho production để lưu vào Git):

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
  namespace: production
data:
  APP_ENV: "production"
  LOG_LEVEL: "info"

Bước 2 — Tạo Secret bằng stringData để khỏi tự encode:

apiVersion: v1
kind: Secret
metadata:
  name: api-secret
  namespace: production
type: Opaque
stringData:
  DB_PASSWORD: "Sup3rS3cr3t!2026"

Áp dụng: kubectl apply -f secret.yaml

Bước 3 — Tiêm vào Pod qua env var. Dùng envFrom để nạp toàn bộ, và valueFrom cho từng giá trị lẻ:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels: { app: api }
  template:
    metadata:
      labels: { app: api }
    spec:
      containers:
      - name: api
        image: mycompany/api:1.4.2
        envFrom:
        - configMapRef:
            name: api-config
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: api-secret
              key: DB_PASSWORD

Sau đó bên trong container, ứng dụng đọc APP_ENV, LOG_LEVEL, DB_PASSWORD như biến môi trường bình thường.

Bước 4 — (Tùy chọn) Tiêm qua volume mount nếu cần cả file config:

        volumeMounts:
        - name: config-volume
          mountPath: /etc/app/config
          readOnly: true
      volumes:
      - name: config-volume
        configMap:
          name: api-config

Mỗi key trong ConfigMap sẽ thành một file (ví dụ /etc/app/config/APP_ENV). Với Secret, thay configMap bằng secret và đặt thêm defaultMode: 0400 để giới hạn quyền đọc file.

Bước 5 — Kiểm tra. Xem Pod đã nhận config chưa:

kubectl exec -it deploy/api -n production -- env | grep APP_ENV
kubectl exec -it deploy/api -n production -- cat /etc/app/config/APP_ENV

Lỗi thường gặp & mẹo

1. Nhét dữ liệu nhạy cảm vào ConfigMap. Sai lầm nghiêm trọng nhất. Quy tắc: cứ là mật khẩu, token, key, chứng chỉ thì phải Secret. Không có ngoại lệ.

2. Tưởng base64 là bảo mật. kubectl get secret api-secret -o yaml cho ra base64, decode một dòng là ra plaintext. Muốn an toàn thật phải bật Encryption at Rest và siết RBAC.

3. Sửa ConfigMap nhưng Pod không cập nhật. Nếu tiêm qua env var, thay đổi chỉ có hiệu lực sau khi Pod restart. Mẹo: thêm annotation checksum của ConfigMap vào Pod template (kubectl rollout restart deploy/api để buộc rollout), hoặc dùng công cụ như Reloader để tự restart Pod khi ConfigMap/Secret đổi.

4. Quên -n namespace. ConfigMap và Secret là namespaced — Pod chỉ thấy được ConfigMap/Secret trong cùng namespace. Tạo ConfigMap ở default rồi mong Pod ở production dùng được là không xảy ra.

5. Giá trị số không để trong ngoặc kép. DB_PORT: 5432 sẽ lỗi vì ConfigMap chỉ nhận string. Phải viết DB_PORT: "5432".

6. Pod không khởi động vì thiếu key. Nếu dùng secretKeyRef trỏ tới key không tồn tại, Pod sẽ kẹt ở trạng thái CreateContainerConfigError. Kiểm tra bằng kubectl describe pod.

Mẹo vàng: Với Secret dùng cho GitOps, đừng bao giờ commit Secret thô. Dùng Sealed Secrets (Bitnami) hoặc External Secrets Operator để giữ Git sạch mà vẫn khai báo được.

Bài tập thực hành

  • Tạo một ConfigMap tên web-config chứa WELCOME_MSG="Xin chào" và THEME="dark", một Secret web-secret chứa API_KEY. Deploy một Pod nginx và tiêm cả hai vào (ConfigMap qua envFrom, Secret qua valueFrom). Dùng kubectl exec ... -- env để xác nhận.
  • Mount web-config dưới dạng volume vào /etc/config. Vào trong Pod kiểm tra: mỗi key có phải một file riêng không? Nội dung file là gì?
  • Sửa giá trị THEME trong ConfigMap. Quan sát: với cách env var, giá trị trong Pod có đổi không? Với cách volume, sau khoảng 1 phút file có tự cập nhật không? Ghi lại khác biệt.
  • (Nâng cao) Chạy kubectl get secret web-secret -o yaml, lấy giá trị base64 của API_KEY và decode ngược lại bằng base64 -d. Tự rút ra vì sao Secret mặc định chưa thực sự an toàn.

Tóm tắt

  • ConfigMap chứa config không nhạy cảm (plain text); Secret chứa dữ liệu nhạy cảm (base64-encoded, không phải mã hóa).
  • Base64 không phải bảo mật. Muốn an toàn thật: bật Encryption at Rest cho etcd, siết RBAC, dùng Sealed Secrets/Vault khi làm GitOps.
  • Hai cách tiêm: env var (đơn giản, nhưng không tự cập nhật, phải restart Pod) và volume mount (hợp cho file, tự cập nhật động).
  • Dùng stringData cho Secret để khỏi tự encode; nhớ để giá trị số trong ngoặc kép ở ConfigMap.
  • ConfigMap/Secret là namespaced — Pod chỉ dùng được trong cùng namespace.
  • Tách config khỏi image giúp một image chạy được mọi môi trường, đúng tinh thần Twelve-Factor App — nền tảng của mọi quy trình deploy chuyên nghiệp.
Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi