Tìm hiểu về ConfigMap và Secret trong K8s
Xin chào mọi người, tiếp tục trong hành trình làm quen với Kubernetes, trong các bài trước mình đã trình bày về cách deploy ứng dụng lên môi trường k8s. Khi mới làm quen với K8s, nhiều người thường tập trung vào Pod, Deployment, Service, hay Ingress mà hay bỏ quên mất việc cấu hình ứng dụng. Đôi khi chúng ta cần đổi value của biến môi trường, thêm một biến, thay đổi mật khẩu database, bật log để check bug dễ hơn.
Dở khóc dở cười với config cứng
Một bạn H được làm việc với k8s ở môi trường staging. Hệ thống của team H đã chạy ổn định trên Kubernetes từ khá lâu, có CI/CD đầy đủ và image được build một lần để dùng cho nhiều môi trường. Tuy nhiên, mức log của ứng dụng lại được cấu hình cố định trong quá trình build image. Một hôm H được nhờ bật log chi tiết hơn để debug lỗi. Nhung trong code phần log_level lại đang được hard code là 'info', nên H đổi sang debug, push code lại, chờ CI/CD chạy lại rebuild image, deploy k8s,.... Lúc này H mới nhận ra rằng những thứ như log level hay password vốn không phải là logic xử lý của chương trình, nhưng vì được đặt trong code nên mỗi thay đổi nhỏ đều kéo theo một lần deploy nặng nề và tiềm ẩn rủi ro bảo mật. Từ một yêu cầu rất nhỏ, H bắt đầu tìm hiểu và biết được vì sao Kubernetes lại cần đến ConfigMap và Secret và việc cấu hình môi trường quan trọng như nào trong quá trình quản lý một ứng dụng trên k8s. Và việc hardcode nó gây ra phiền toái như nào.
ConfigMap là gì và vì sao nó giải quyết được vấn đề trên?
ConfigMap là nơi Kubernetes dùng để lưu các cấu hình của ứng dụng, tách rời khỏi code và Docker image.
Những “cấu hình” này thường là:
- Mức log (LOG_LEVEL)
- Port chạy ứng dụng
- Tên môi trường (dev, staging, prod)
- URL của service khác
- Feature flag (bật / tắt một tính năng)
Điểm quan trọng nhất cần nhớ:
ConfigMap không chứa logic, và cũng không chứa thông tin nhạy cảm.
Quay lại câu chuyện log bug
Trong câu chuyện trước, vấn đề không phải là Kubernetes hay CI/CD làm sai, mà là:
- LOG_LEVEL bị đóng cứng trong image
- Muốn đổi log → phải build lại image → redeploy
Nếu LOG_LEVEL nằm trong ConfigMap, thì:
- Image vẫn giữ nguyên
- Chỉ thay đổi ConfigMap
- Restart pod là xong
👉 Đây chính là lý do ConfigMap tồn tại.
Bạn có thể tưởng tượng Image như một cái máy pha cà phê.
- Máy pha cà phê = code + runtime
- Lượng đường, lượng sữa = config
Bạn không cần sản xuất lại cái máy chỉ để:
- Pha ngọt hơn
- Pha ít sữa hơn
ConfigMap chính là chỗ để bạn đổi “công thức”, ít nhiều đường/sữa, mà không đụng tới “cái máy”.
Lan man mãi về khái niệm ConfigMap mãi rồi, vậy cấu trúc một ConfigMap trông sẽ như nào?

Một ConfigMap thực chất chỉ là một object YAML rất đơn giản.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: debug
APP_ENV: production
APP_PORT: "3000"
Ở đây:
- name: app-config → tên ConfigMap
- data → danh sách các cặp key – value
- Tất cả value đều là string (kể cả số), đó là lý do mà port là số mà lại để trong dấu nháy, app sẽ tự parse sau
Đưa ConfigMap vào Kubernetes như thế nào?
Cách đơn giản nhất là dùng kubectl apply:
kubectl apply -f app-config.yaml
Sau đó bạn có thể dùng các lệnh sau để kiểm tra:
kubectl get configmap
kubectl describe configmap app-config
Lúc này ConfigMap đã tồn tại trong cluster, nhưng ứng dụng vẫn chưa dùng nó đâu nhé.
Làm sao để ứng dụng dùng được ConfigMap?
Mình sẽ chỉ các bạn 2 cách để dùng được ConfigMap đã định nghĩa vào trong ứng dụng của các bạn
Cách 1: Inject ConfigMap thành biến môi trường
Trong Deployment, chỗ dùng env, bạn sửa đoạn code như sau:
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
Khi container chạy:
Kubernetes sẽ tạo các biến môi trường:
- LOG_LEVEL
- APP_ENV
- APP_PORT
Trong code Node.js, bạn chỉ cần:
const logLevel = process.env.LOG_LEVEL;
Cách 2: Mount ConfigMap thành file
Cách này hay dùng khi:
- Config dài (JSON, YAML)
- App legacy đọc config từ file
Ví dụ: Trong Deployment
...
spec:
template:
spec:
containers:
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: app-config
...
Khi container chạy:
- /etc/config/LOG_LEVEL
- /etc/config/APP_ENV
👉 Mỗi key trở thành một file, và nội dung file là value. Trong code bạn có thể dùng kĩ thuật đọc file để lấy value ra dùng.
Vậy trong câu chuyện log bug, ConfigMap giúp gì?
Quay lại tình huống thực tế:
Trước:
- Đổi log → build image → redeploy
Sau khi dùng ConfigMap:
- Sửa LOG_LEVEL trong ConfigMap
- Restart pod
- Không build lại image
Điều này đặc biệt quan trọng khi:
- Production/Staging đang có sự cố
- Cần phản ứng nhanh
- Không muốn rủi ro từ việc build image mới
Khi nào NÊN dùng ConfigMap?
Mọi người nên dùng ConfigMap cho:
- Thứ có thể thay đổi theo môi trường
- Thứ có thể thay đổi theo thời điểm vận hành
- Thứ không nhạy cảm
Ví dụ:
- LOG_LEVEL
- APP_ENV
- SERVICE_URL
- FEATURE_FLAG , ....
Khi nào KHÔNG nên dùng ConfigMap?
Không nên dùng ConfigMap cho:
- Password
- API key
- Token
- Private key...
👉 Những thứ đó phải dùng Secret, sẽ nói ở phần tiếp theo.
Với file chứa ConfigMap như vậy thì có thể push lên repo của chúng ta, tuy nhiên phải đảm bảo một điều là nó không chứa bất cứ thông tin "nhạy cảm" nào
Secret là gì? Chúng khác gì với ConfigMap?
Nếu ConfigMap dùng cho cấu hình 'thông thường', thì:
Secret là nơi Kubernetes dùng để lưu thông tin nhạy cảm như mật khẩu, token, key, certificate.
Có một điểm quan trọng cần nhớ rằng, Secret KHÔNG phải là két sắt, nó ko tự động mã hóa hay bảo vệ tuyệt đối dữ liệu cho bạn Trong 1 file yaml, giá trị của secret thường trông như này
data:
DB_PASSWORD: c3VwZXJfc2VjcmV0
Mọi người đừng hiểu nhầm đây là encrypt (mã hóa bảo mật), đây chỉ là base64 encode (đổi định dạng) , ai đọc được thì có thể decode được ngay.
echo c3VwZXJfc2VjcmV0 | base64 -d
# super_secret
Cấu trúc một file secret đơn giản như sau:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
DB_USER: YWRtaW4=
DB_PASSWORD: c3VwZXJfc2VjcmV0
Trong đó:
- kind: Secret → khai báo đây là Secret
- type: Opaque → loại Secret phổ biến nhất
- data → các cặp key–value (value phải base64)
👉 Kubernetes bắt buộc value phải là base64, để tránh lỗi encoding.
OK, vừa rồi là file yaml chứa cấu hình secret, lúc này thì k8s chưa có thông tin về Secret đâu nhé. Chúng ta sẽ cần tạo object Secret cho k8s nắm giữ.
Cách 1: Tạo bằng file YAML
kubectl apply -f db-secret.yaml
Cách 2: Tạo trực tiếp bằng kubectl (dành cho ai không thích dùng file, mà có ít env)
kubectl create secret generic db-secret \
--from-literal=DB_PASSWORD=super_secret
Kubernetes sẽ tự:
- Encode base64
- Tạo Secret trong cluster
👉 Cách này tránh lộ secret trong file.
Vậy là K8S đã có Secret rồi, vậy trong ứng dụng của chúng ta, muốn dùng những secret này thì làm như nào. Việc này cũng đơn giản tương tự như dùng ConfigMap:
Cách 1: Inject Secret thành biến môi trường
Trong Deployment:
env:
- name: LOG_LEVEL
valueFrom:
secretRef:
name: db-secret
key: DB_PASSWORD
Khi này , trong 1 app Node.js:
const dbPassword = process.env.DB_PASSWORD // super_secret;
Cách 2: Mount thành file (tương tự như ConfigMap thôi)
Trong Deployment:
volumeMounts:
- name: secret-volume
mountPath: /etc/secret
volumes:
- name: secret-volume
secret:
secretName: db-secret
Khi container chạy, bên trong sẽ có:
/etc/secret/
├── DB_USER
└── DB_PASSWORD
"Secret", các bạn đọc tới đây thì có thể cảm thấy rõ ràng rằng: sao 2 cái khái niệm ConfigMap với Secret cứ thấy giống giống nhau, đều để lưu key-value, tại sao không để sử dụng 1 cái thôi cho nhanh?
Thoạt nhìn thì cảm giác này hoàn toàn hợp lý. Cả ConfigMap và Secret đều là key–value, đều được mount vào Pod, đều có thể đọc bằng env hoặc file. Nếu chỉ nhìn ở bề mặt kỹ thuật, câu hỏi “tại sao không gộp làm một cho đơn giản?” là một câu hỏi rất tự nhiên, đặc biệt đối với những ai mà mới tìm hiểu, mới làm việc với k8s như mình
Nhưng lý do Kubernetes tách ra hai khái niệm riêng biệt không nằm ở chuyện “lưu dữ liệu kiểu gì”, mà nằm ở cách dữ liệu đó được đối xử trong quá trình vận hành hệ thống.
ConfigMap được thiết kế với giả định rằng: dữ liệu bên trong có thể bị nhìn thấy. Không sao cả. Nó chứa những thứ như log level, môi trường chạy, port, URL của service khác. Những giá trị này nếu bị đọc, bị log, hay bị commit nhầm thì thường chỉ gây bất tiện, chứ không gây ra sự cố nghiêm trọng về bảo mật. Secret thì ngược lại. Ngay từ tên gọi, Kubernetes đã muốn nói rõ: đây là dữ liệu nhạy cảm. Không phải vì Kubernetes có một cơ chế mã hóa thần kỳ nào cho Secret, mà vì việc tách riêng này tạo ra một “ranh giới tư duy” rất quan trọng. Khi một giá trị được đặt vào Secret, toàn bộ hệ thống — từ con người, quy trình cho tới tooling — đều phải đối xử với nó cẩn thận hơn.
Ví dụ rất thực tế: trong nhiều cluster, quyền đọc ConfigMap được mở khá rộng để dev, QA, hoặc các tool có thể dễ dàng debug. Nhưng quyền đọc Secret thường bị giới hạn chặt hơn. Nếu chỉ có một loại object duy nhất để lưu tất cả, thì hoặc là bạn mở quyền quá rộng cho dữ liệu nhạy cảm, hoặc là bạn siết quyền đến mức việc vận hành bình thường cũng trở nên khó khăn.
Ngoài ra, việc tách ConfigMap và Secret còn giúp giảm những “tai nạn” rất đời. Một dev có thể thoải mái log config từ ConfigMap để kiểm tra, nhưng khi thấy chữ “Secret”, họ sẽ tự nhiên chùn tay hơn trước khi in ra log hay commit lên Git. Đây không phải là vấn đề kỹ thuật, mà là vấn đề hành vi con người — thứ mà Kubernetes phải tính đến khi thiết kế.
Nói cách khác, ConfigMap và Secret trông giống nhau vì chúng cùng giải quyết bài toán “đưa dữ liệu vào container”, nhưng chúng tồn tại riêng rẽ để giải quyết hai mức độ rủi ro hoàn toàn khác nhau. Kubernetes không tách chúng ra để làm mọi thứ phức tạp hơn, mà để buộc chúng ta phải suy nghĩ: giá trị này có được phép lộ không? Nếu lộ thì hậu quả là gì? Và việc hiểu rõ và tổ chức dữ liệu riêng biệt như vậy cũng thể hiện phần nào việc chúng ta am hiểu về project của chúng ta: cái gì được lộ, cái gì không.
Trên đây mới chỉ là những tìm hiểu cơ bản để có thể bắt tay vào thực hiện những bước cấu hình cơ bản khi mới làm việc với k8s. Dĩ nhiên là nâng cao hơn sẽ còn những kĩ thuật hay ho và thú vị hơn chờ chúng ta tìm hiểu. 💥💥💥
Tài liệu tham khảo
https://kubernetes.io/docs/concepts/configuration/secret/
https://kubernetes.io/docs/concepts/configuration/configmap/
https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/
All rights reserved