ConfigMap và Secret trong Kubernetes: Tách cấu hình khỏi image
1. Tổng quan
Khi đóng gói ứng dụng thành container image, bạn không nên “nướng” cứng các giá trị như database URL, API key vào image. Bài này giúp bạn hiểu rõ ConfigMap và Secret trong Kubernetes — hai object dùng để tách cấu hình ra khỏi image, cách tạo, cách nạp vào Pod qua biến môi trường hoặc volume, và cách tích hợp với ứng dụng Spring Boot. Đọc xong, bạn tự tin cấu hình một Deployment đọc config động mà không cần build lại image mỗi lần đổi giá trị.
2. ConfigMap là gì
ConfigMap là object lưu dữ liệu cấu hình dạng key-value, không mã hóa, dùng cho dữ liệu không nhạy cảm: tên service, feature flag, file properties…
Dữ liệu trong ConfigMap tồn tại độc lập với Pod — bạn có thể sửa ConfigMap mà không cần rebuild image, chỉ cần restart Pod (hoặc dùng cơ chế reload nếu ứng dụng hỗ trợ).
3. Tạo ConfigMap
3.1. Từ file YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config
data:
APP_ENV: "production"
LOG_LEVEL: "INFO"
application.properties: |
server.port=8080
spring.jpa.hibernate.ddl-auto=validate
Ở đây data chứa hai loại: key đơn giản (APP_ENV, LOG_LEVEL) và một key chứa cả nội dung file (application.properties). Áp dụng bằng:
kubectl apply -f order-service-config.yaml
3.2. Từ literal hoặc file có sẵn
kubectl create configmap order-service-config \
--from-literal=APP_ENV=production \
--from-file=application.properties=./config/application.properties
Lệnh này tạo ConfigMap trực tiếp từ command line mà không cần viết YAML, tiện khi script CI/CD.
4. Sử dụng ConfigMap trong Pod
4.1. Nạp dưới dạng biến môi trường
apiVersion: v1
kind: Pod
metadata:
name: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
envFrom:
- configMapRef:
name: order-service-config
envFrom nạp toàn bộ key trong ConfigMap thành biến môi trường cùng tên. Container đọc APP_ENV, LOG_LEVEL y như biến môi trường thường.
Nếu chỉ muốn chọn một key cụ thể:
env:
- name: APP_ENVIRONMENT
valueFrom:
configMapKeyRef:
name: order-service-config
key: APP_ENV
Cách này cho phép đổi tên biến môi trường trong container (APP_ENVIRONMENT) khác với key gốc trong ConfigMap (APP_ENV).
4.2. Nạp dưới dạng volume
volumeMounts:
- name: config-volume
mountPath: /app/config
volumes:
- name: config-volume
configMap:
name: order-service-config
Mỗi key trong ConfigMap trở thành một file trong /app/config (ví dụ /app/config/application.properties). Cách này phù hợp khi ứng dụng cần đọc cả file cấu hình thay vì từng biến môi trường riêng lẻ.
Lưu ý: Khi mount dạng volume, Kubernetes sẽ tự động cập nhật file khi ConfigMap thay đổi (có độ trễ vài chục giây đến vài phút tùy kubelet sync period). Với biến môi trường (
env/envFrom), Pod không tự reload — bạn phải restart Pod.
5. Secret là gì và khác biệt với ConfigMap
Secret có cấu trúc giống ConfigMap nhưng dành cho dữ liệu nhạy cảm: mật khẩu, token, private key. Khác biệt chính:
| Tiêu chí | ConfigMap | Secret |
|---|---|---|
| Mã hóa | Không | Base64-encode (không phải mã hóa thật) |
| Lưu trong etcd | Plain text | Có thể bật encryption at rest |
Hiển thị qua kubectl get |
Hiện giá trị | Ẩn giá trị (chỉ thấy base64) |
| Mục đích | Config thường | Dữ liệu nhạy cảm |
Lưu ý: Secret chỉ encode base64, không mã hóa mặc định. Ai có quyền
get secrettrong namespace đều decode được. Muốn bảo mật thật sự, bạn cần bật [encryption at rest] ở etcd hoặc dùng external secret manager (Vault, AWS Secrets Manager) tích hợp qua External Secrets Operator.
6. Tạo Secret
6.1. Từ YAML (phải tự encode base64)
echo -n "P@ssw0rd123" | base64
# UEBzc3cwcmQxMjM=
apiVersion: v1
kind: Secret
metadata:
name: order-service-secret
type: Opaque
data:
DB_PASSWORD: UEBzc3cwcmQxMjM=
Trường data yêu cầu giá trị đã base64-encode sẵn. Nếu không muốn tự encode, dùng stringData:
stringData:
DB_PASSWORD: "P@ssw0rd123"
Kubernetes sẽ tự encode khi lưu. Dùng stringData cho dễ đọc khi viết manifest thủ công.
6.2. Từ command line
kubectl create secret generic order-service-secret \
--from-literal=DB_PASSWORD=P@ssw0rd123 \
--from-literal=DB_USERNAME=order_app
7. Sử dụng Secret trong Pod
Cách nạp hoàn toàn tương tự ConfigMap:
envFrom:
- secretRef:
name: order-service-secret
hoặc mount volume:
volumeMounts:
- name: secret-volume
mountPath: /app/secret
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: order-service-secret
Mount dạng volume cho Secret nên luôn đặt readOnly: true và hạn chế quyền truy cập file bằng defaultMode nếu cần:
secret:
secretName: order-service-secret
defaultMode: 0400
8. Tích hợp với Spring Boot
Spring Boot đọc biến môi trường theo chuẩn relaxed binding, nên bạn không cần code gì đặc biệt — chỉ cần đặt tên biến đúng quy ước.
envFrom:
- configMapRef:
name: order-service-config
- secretRef:
name: order-service-secret
env:
- name: SPRING_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: order-service-secret
key: DB_USERNAME
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: order-service-secret
key: DB_PASSWORD
Spring Boot tự map SPRING_DATASOURCE_USERNAME → spring.datasource.username. Trong code, bạn chỉ cần @ConfigurationProperties bình thường:
@ConfigurationProperties(prefix = "spring.datasource")
public record DataSourceProperties(String url, String username, String password) {
}
@Service
public class PaymentService {
private final DataSourceProperties dataSourceProperties;
public PaymentService(DataSourceProperties dataSourceProperties) {
this.dataSourceProperties = dataSourceProperties;
}
public String describeConnection() {
return "Connecting as %s".formatted(dataSourceProperties.username());
}
}
PaymentService không biết gì về Kubernetes — nó chỉ nhận giá trị đã được Spring Boot bind sẵn từ biến môi trường do ConfigMap/Secret cung cấp.
8.1. Test không phụ thuộc cluster
Bạn test logic bằng cách set biến môi trường giả lập qua @TestPropertySource, không cần Kubernetes thật:
@SpringBootTest
@TestPropertySource(properties = {
"spring.datasource.username=order_app",
"spring.datasource.password=test-pass"
})
class PaymentServiceTest {
@Autowired
private PaymentService paymentService;
@Test
void shouldDescribeConnectionUsingBoundProperties() {
assertThat(paymentService.describeConnection())
.isEqualTo("Connecting as order_app");
}
}
Test này xác nhận DataSourceProperties được bind đúng và PaymentService dùng đúng giá trị — tách biệt hoàn toàn với việc giá trị đó đến từ ConfigMap hay Secret thật trên cluster.
PaymentServiceTest > shouldDescribeConnectionUsingBoundProperties() PASSED
9. Cập nhật động không cần restart Pod
Nếu mount ConfigMap/Secret dưới dạng volume, file sẽ tự cập nhật nhưng ứng dụng Spring Boot không tự đọc lại trừ khi bạn:
- Dùng Spring Cloud Kubernetes (
spring-cloud-starter-kubernetes-client-config) để polling ConfigMap và refresh@RefreshScopebean. - Hoặc tự implement file-watcher đọc lại file mount.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
spring.cloud.kubernetes.config.enabled=true
spring.cloud.kubernetes.reload.enabled=true
spring.cloud.kubernetes.reload.mode=polling
Cấu hình này bật tính năng Spring Cloud Kubernetes tự poll ConfigMap theo chu kỳ và refresh bean đánh dấu @RefreshScope, không cần restart Pod thủ công.
Lưu ý: Polling liên tục tốn tài nguyên API server. Với cluster lớn, cân nhắc dùng
spring.cloud.kubernetes.reload.mode=event(watch API) thay vì polling.
10. Lỗi thường gặp và khi nào không nên dùng
- Để mật khẩu trong ConfigMap: ConfigMap không mã hóa,
kubectl get configmap -o yamlhiện rõ giá trị. Luôn dùng Secret cho dữ liệu nhạy cảm. - Quên base64-encode khi viết
datatrong Secret YAML: dẫn đến lỗiillegal base64 data. DùngstringDatanếu không muốn tự encode. - Đổi ConfigMap nhưng Pod không nhận giá trị mới: do nạp qua
env/envFromkhông tự reload — phảikubectl rollout restart deployment. - Mount Secret volume nhưng quên
readOnly: true: container có thể ghi đè file, gây nhầm lẫn khi debug. - Dùng Secret như một kho bí mật lâu dài: Secret trong Kubernetes phù hợp cho cấu hình runtime, không thay thế được secret manager chuyên dụng (Vault, AWS Secrets Manager) khi cần audit log, rotation tự động, hoặc versioning chặt chẽ.
- ConfigMap/Secret quá lớn: Kubernetes giới hạn kích thước object khoảng 1 MiB (do giới hạn etcd). Nếu cần file config lớn, cân nhắc dùng volume mount từ PersistentVolume hoặc init container tải từ object storage.
11. Kết luận
ConfigMap và Secret giúp bạn tách cấu hình khỏi image, triển khai cùng một image cho nhiều môi trường (dev, staging, production) chỉ bằng cách đổi object cấu hình. ConfigMap dùng cho dữ liệu công khai, Secret dùng cho dữ liệu nhạy cảm — nhưng nhớ rằng Secret mặc định chỉ encode chứ không mã hóa thật sự. Với Spring Boot, bạn tận dụng relaxed binding để nạp giá trị qua biến môi trường mà không cần thay đổi code nghiệp vụ, và có thể bật Spring Cloud Kubernetes nếu cần reload cấu hình động mà không restart Pod.