Profile Groups & Production Patterns — gom profile, validate, blue/green
Profile groups (Boot 2.4+) gom nhiều profile thành một nhóm logic để kích hoạt bằng một tên. Bài này đào sâu cơ chế groups, multi-document YAML, mô phỏng inheritance qua include, và pattern production: secret management, blue/green canary, validate active profile khi startup.
TL;DR: Profile groups (spring.profiles.group) giải quyết bài toán "activate 5 profile rời mỗi deploy" bằng cách gom chúng dưới một tên logic: production = prod + prod-db + prod-cache + prod-tracing. Kích hoạt production, Boot tự include cả nhóm. Multi-document YAML (--- + on-profile) cho phép nhiều profile document trong một file. Inheritance mô phỏng qua spring.profiles.include hoặc thứ tự active. Production có 3 safeguard quan trọng: không hardcode secret (dùng env var ${VAR}), validate active profile khi startup (ApplicationReadyEvent), và blue/green canary qua pod env var. Bài này là tiếp nối trực tiếp của Profile — activation cơ bản: đã biết profile là gì, bài này đào sâu cách tổ chức và vận hành.
1. Vấn đề groups giải quyết
1.1 Anti-pattern: activate 5 profile rời
Không có groups, production deploy trông như này:
# Khi deploy production — 5 profile phai active
SPRING_PROFILES_ACTIVE=prod,prod-db,prod-cache,prod-tracing,prod-monitoring
Hệ quả thực tế khi làm theo cách này:
- Dễ bỏ sót: operator quên
prod-tracing→ không có observability. - Khó review: README có nhiều dòng
SPRING_PROFILES_ACTIVEdài, không nhất quán giữa team. - Brittle: thêm profile mới (
prod-audit) → phải update mọi nơi đã hardcode chuỗi 5 profile.
1.2 Cơ chế groups
Profile groups (Boot 2.4+) là mapping trong config: group-name -> [profile1, profile2, ...]. Khi Boot thấy group-name trong danh sách active, nó expand thành toàn bộ profile của group đó trước khi tiến hành load config và register bean.
Khai báo group trong application.yml:
spring:
profiles:
group:
production: prod, prod-db, prod-cache, prod-tracing
staging: staging, staging-db, staging-cache
development: dev, dev-db
Activate production:
java -jar app.jar --spring.profiles.active=production
Boot tự include 4 profile con. File config tách theo concern:
application-prod.yml # Spring common prod config
application-prod-db.yml # DataSource, connection pool
application-prod-cache.yml # Redis config
application-prod-tracing.yml # Micrometer, Zipkin, sampling
Mỗi file nhỏ, single concern, dễ review. Khi application-prod.yml phình to 200 dòng, group là cách clean.
Groups tốt hơn vì chỉ một nơi định nghĩa thành phần của deployment profile. Thêm concern mới (vd prod-audit) chỉ cần thêm vào group definition — không sửa Kubernetes manifest, không sửa README, không sửa CI pipeline.
1.3 Group lồng và multi-group
Group có thể bao gồm group khác không? Không trực tiếp — Boot không resolve group transitive. Nhưng profile con có thể dùng spring.profiles.include để kéo thêm profile:
# application-prod.yml — prod tự include prod-base
spring:
profiles:
include: prod-base, prod-monitoring
Activate prod → Boot include prod-base và prod-monitoring theo. Cách này mô phỏng hierachy profile.
Multi-group cho phép deploy vào nhiều region từ một codebase:
spring:
profiles:
group:
asia-east: prod-base, prod-asia-east, prod-monitoring
eu-west: prod-base, prod-eu-west, prod-monitoring
us-east: prod-base, prod-us-east, prod-monitoring
3 region — mỗi region kéo prod-base (DataSource pool, JPA config) và region-specific (DB URL, S3 endpoint). K8s manifest chỉ cần:
env:
- name: SPRING_PROFILES_ACTIVE
value: "asia-east" # hoac eu-west, us-east
2. Multi-document YAML
2.1 Cú pháp và cơ chế
Multi-document YAML (Boot 2.4+) đặt nhiều document trong một file, ngăn cách bằng ---. Mỗi document có thể gắn với profile qua spring.config.activate.on-profile:
# application.yml — single file, 3 document
spring:
application:
name: order-service
datasource:
hikari:
maximum-pool-size: 10 # default
---
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:postgresql://localhost:5432/dev
hikari:
maximum-pool-size: 20
logging:
level:
com.olhub: DEBUG
---
spring:
config:
activate:
on-profile: prod
datasource:
url: ${PROD_DB_URL}
hikari:
maximum-pool-size: 50 # override default
logging:
level:
com.olhub: INFO
Cơ chế Boot xử lý multi-document:
- Document 1 (không có
on-profile): apply làm base cho mọi profile. - Mỗi document sau: Boot kiểm tra
on-profile— nếu match active profile thì merge với base, ghi đè key trùng. - Thứ tự merge theo vị trí document trong file — document sau override document trước.
Đi theo đúng một key qua ba document, với prod đang active:

Chú ý luật ở bước 3: cái thắng là document đứng sau trong file, không phải document có profile cụ thể hơn. Đảo vị trí hai document là đảo luôn kết quả.
2.2 on-profile hỗ trợ expression
on-profile nhận cùng expression syntax với @Profile:
---
spring:
config:
activate:
on-profile: "prod & monitoring" # chi apply khi ca 2 profile active
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
2.3 Khi nào dùng multi-document vs separate files
| Tình huống | Approach |
|---|---|
| File ngắn (dưới 100 dòng), 2-3 profile | Multi-document — một file, thấy tổng quan dễ |
| File dài, 5+ profile, nhiều team chỉnh | Separate files — ít merge conflict |
| Dùng profile groups | Separate files — group reference file riêng |
| Quick prototype | Multi-document — không tạo nhiều file |
Multi-document YAML dùng spring.config.activate.on-profile là cú pháp Boot 2.4+. Boot 2.3 trở về dùng spring.profiles (deprecated): spring: profiles: dev. Nếu upgrade từ Boot 2.3, cần migrate sang cú pháp mới.
3. Mô phỏng inheritance
Boot không có cơ chế "profile A extends profile B" trực tiếp. Có 2 pattern mô phỏng:
3.1 Common base + override qua thứ tự active
application.yml # base cho moi profile
application-prod-base.yml # base prod (DataSource pool, JPA)
application-prod.yml # prod-specific override
application-prod-canary.yml # canary deployment override
# Active theo thu tu: base, prod-base, prod, prod-canary
SPRING_PROFILES_ACTIVE=prod-base,prod,prod-canary
Boot merge theo thứ tự — profile sau ghi đè profile trước. prod-canary có thể override một số property của prod (vd sampling.probability: 1.0 để trace mọi request trong canary pod).
3.2 spring.profiles.include — dependency tường minh
# application-prod.yml
spring:
profiles:
include: prod-base, prod-monitoring
datasource:
url: ${PROD_DB_URL}
Activate prod → Boot tự include prod-base và prod-monitoring. Đây là cách viết explicit dependency giữa profile — người đọc file thấy ngay "prod cần prod-base và prod-monitoring".
Khác biệt với group:
| Group | Include | |
|---|---|---|
| Định nghĩa ở | application.yml base | File profile con (application-prod.yml) |
| Ai quyết include | Người vận hành (operator) khi set active | Author của profile |
| Flexibility | Operator customize group | Fixed bởi author |
4. Production patterns
4.1 Secret management — không hardcode credential
Anti-pattern: credential nằm trong YAML commit lên Git:
# application-prod.yml — NGUY HIEM
spring:
datasource:
password: prod-secret-123 # hardcode, lo secret khi repo bi access
Ngay cả private repo cũng có rủi ro: repo bị clone, contributor rogue, log CI lộ. Pattern an toàn là tham chiếu env var:
# application-prod.yml — an toan
spring:
datasource:
url: ${PROD_DB_URL} # tu env var, mandatory
username: ${PROD_DB_USER}
password: ${DB_PASSWORD} # fail-fast neu missing (khong co default)
Khi Spring không tìm thấy biến env ${PROD_DB_URL} và không có giá trị default, app từ chối khởi động với IllegalArgumentException: Could not resolve placeholder. Đây là fail-fast có chủ đích.
Kubernetes Secret mount vào pod:
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: db-password
- name: PROD_DB_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: db-url
Với HashiCorp Vault, thêm spring-cloud-starter-vault-config — Boot tự fetch secret từ Vault path khi khởi động:
spring:
cloud:
vault:
host: vault.internal
token: ${VAULT_TOKEN}
kv:
enabled: true
backend: secret
default-context: order-service
4.2 Validate active profile tại startup — fail-fast
Typo trong profile name là silent bug: SPRING_PROFILES_ACTIVE=prdo khiến Boot chạy với 0 profile active — không load application-prod.yml, không register bean prod. App khởi động thành công nhưng dùng sai config.
Pattern phòng thủ: validate tại ApplicationReadyEvent — sau khi container fully started, trước khi accept traffic:
@Component
public class StartupValidator {
@EventListener(ApplicationReadyEvent.class)
public void validate(ApplicationReadyEvent event) {
Environment env = event.getApplicationContext().getEnvironment();
String[] active = env.getActiveProfiles();
// Phai co it nhat 1 trong cac profile hop le
Profiles allowed = Profiles.of("dev", "test", "staging", "prod");
if (!env.acceptsProfiles(allowed)) {
throw new IllegalStateException(
"Active profiles must include one of: dev/test/staging/prod. Got: " +
Arrays.toString(active)
);
}
// Neu prod, validate critical property
if (env.acceptsProfiles(Profiles.of("prod"))) {
requireProperty(env, "spring.datasource.url");
requireProperty(env, "spring.datasource.password");
}
}
private void requireProperty(Environment env, String key) {
if (!StringUtils.hasText(env.getProperty(key))) {
throw new IllegalStateException("Missing required property: " + key);
}
}
}
Tại sao dùng ApplicationReadyEvent chứ không phải @PostConstruct hay ContextRefreshedEvent? Vì ApplicationReadyEvent fire sau khi toàn bộ container đã refresh xong và bean lifecycle hoàn tất — đây là điểm chắc chắn nhất. @PostConstruct chạy trong pha tạo bean — lúc đó có thể chưa đủ context.
Ngoài validator thủ công, Spring Boot Actuator cung cấp /actuator/env để debug runtime — liệt kê active profile và mọi property source. Không cần restart để kiểm tra cấu hình đang chạy.
4.3 Blue/green và canary deployment
Blue/green deployment chạy 2 phiên bản app song song và chuyển traffic sang phiên bản mới. Profile giúp phân biệt config giữa 2 phiên bản:
# application-prod-v2.yml — override config cho phien ban moi
spring:
datasource:
hikari:
maximum-pool-size: 60 # tang pool cho v2
management:
tracing:
sampling:
probability: 1.0 # trace 100% request o canary de debug
Kubernetes: 2 Deployment chia traffic:
# Deployment chinh (blue) - 90% pod
spec:
replicas: 9
template:
spec:
containers:
- name: app
env:
- name: SPRING_PROFILES_ACTIVE
value: "production" # group: prod + prod-db + prod-cache + prod-tracing
# Deployment canary (green) - 10% pod
spec:
replicas: 1
template:
spec:
containers:
- name: app
env:
- name: SPRING_PROFILES_ACTIVE
value: "production,prod-v2" # them prod-v2 override
Canary pod active 5 profile: prod, prod-db, prod-cache, prod-tracing (từ group production) và prod-v2 (trực tiếp). application-prod-v2.yml merge sau, override key cần thiết. Khi canary ổn định, dần tăng replicas canary và giảm main.
4.4 Failure runbook
Mode 1 — Profile typo, silent fallback:
- Triệu chứng: startup log hiện
No active profile set, falling back to 1 default profile. - Diagnose:
/actuator/envcheckspring.profiles.active. - Remediate:
StartupValidator(section 4.2) bắt được ngay lúc deploy.
Mode 2 — Profile conflict: 2 profile define cùng key:
- Triệu chứng: config X có 2 giá trị khác nhau giữa 2 profile đều active.
- Diagnose:
/actuator/configpropsliệt kê property source ưu tiên. - Remediate: thứ tự profile trong
spring.profiles.activecó chủ đích — profile sau ghi đè trước. Document priority rõ ràng trong README.
Mode 3 — Secret rotate, app vẫn dùng credential cũ:
- Nguyên nhân: K8s Secret update không trigger pod restart.
- Remediate: rolling restart pod sau khi rotate secret. Spring Cloud Config có
@RefreshScopecho runtime refresh không restart.
5. Pitfall
❌ Nhầm 1 — Tưởng group resolve transitive:
spring:
profiles:
group:
production: prod, prod-db
full-prod: production, prod-tracing # SAI
Boot không expand production khi xử lý group full-prod — nó treat production như tên profile literal, không phải group. Kết quả full-prod active 2 profile: production (literal string, không có file application-production.yml) và prod-tracing.
Cách đúng: liệt kê đầy đủ trong group:
spring:
profiles:
group:
full-prod: prod, prod-db, prod-tracing
❌ Nhầm 2 — Secret trong YAML commit lên Git:
# application-prod.yml
spring:
datasource:
password: actual-password # git history ghi nho mai mai
Xóa khỏi file không đủ — git log vẫn giữ. Dùng ${ENV_VAR} reference và rotate secret sau.
❌ Nhầm 3 — Không validate profile, bug ngầm production:
App active wrong profile → dùng config dev trong prod (pool size thấp, log level DEBUG, DB localhost). Silent bug cho đến khi hết connection hoặc lộ data qua verbose log.
Luôn có StartupValidator tối thiểu check profile name hợp lệ.
❌ Nhầm 4 — on-profile cú pháp Boot 2.3 lẫn Boot 2.4:
# Boot 2.3 syntax — deprecated
---
spring:
profiles: dev # deprecated
datasource:
url: jdbc:postgresql://localhost/dev
# Boot 2.4+ syntax — dung
---
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:postgresql://localhost/dev
Lẫn lộn cú pháp → Boot 2.4+ log warning "spring.profiles is no longer supported in document-specific sections" và có thể không apply document.
6. Cơ chế bên dưới — cách Boot expand group
Khi Spring Boot khởi động, ProfilesParser (trong spring-boot module) xử lý spring.profiles.group trước khi Environment được finalize. Thứ tự:
- Boot đọc
spring.profiles.activetừ env var / JVM property /application.yml. - Với mỗi profile name trong danh sách active, Boot tra
spring.profiles.groupmap — nếu có mapping thì thêm (không replace) các profile con vào danh sách active. - Step 2 lặp lại cho các profile mới vừa thêm vào (nhưng không transitive cho group khác).
- Sau khi expand xong, Boot load
application-{profile}.ymlcho toàn bộ active profiles theo thứ tự ưu tiên.
Điểm quan trọng: expansion thêm vào, không thay thế.

Sau expand production, active profile là [production, prod, prod-db, prod-cache, prod-tracing] — tên group production vẫn còn trong danh sách. Đó là lý do bạn thấy nó xuất hiện trong log "The following profiles are active" và trong /actuator/env; Boot cũng thử load application-production.yml (thường không tồn tại, bỏ qua im lặng).
7. Liên hệ các bài khác
- Profile — activation, @Profile trên bean, expression: bài trước giải thích profile là gì và cơ chế activation cơ bản. Bài này xây trên nền đó với groups và production patterns.
- Logging — SLF4J, log levels, config: module 06 bao gồm logging — profile thường dùng để set log level khác nhau theo môi trường (
DEBUGcho dev,INFOcho prod). Hiểu profile groups trước khi config logging per-profile. - Spring Boot AutoConfiguration — cơ chế bên dưới:
@ConditionalOnProfilelà một trong các@Conditionalautoconfiguration dùng để chỉ apply@Configurationclass khi profile match — liên hệ trực tiếp với@Profiletrong bài này. - Bean lifecycle phases:
ApplicationReadyEvent(dùng trongStartupValidator) fire ở pha cuối lifecycle — hiểu lifecycle giúp chọn đúng event listener point.
Tóm tắt
- Profile groups (
spring.profiles.group): gom nhiều profile thành một tên logic. Activate tên group → Boot expand thành danh sách đầy đủ. Giải quyết bài toán activate 5 profile rời và tránh bỏ sót profile khi deploy. - Multi-document YAML (
---+spring.config.activate.on-profile): nhiều profile document trong một file. Document đầu là base; document sau merge + override khi profile match. Phù hợp cho file ngắn, 2-3 profile; dùng separate files khi file dài hoặc nhiều team chỉnh. - Inheritance mô phỏng qua
spring.profiles.include(explicit dependency trong file profile) hoặc thứ tự cascade activation. - Secret management: không hardcode trong YAML. Dùng
${ENV_VAR}reference — app fail-fast nếu biến thiếu. Mount K8s Secret vào pod env var. - Startup validation:
@EventListener(ApplicationReadyEvent.class)kiểm tra profile hợp lệ + critical property — bắt typo profile và thiếu config tại deploy time, không phải runtime. - Blue/green canary: 2 K8s Deployment khác nhau ở
SPRING_PROFILES_ACTIVE— canary pod thêm profile override config. Profile group tách biệt concern rõ ràng. - Groups không transitive: Boot không expand group lồng trong group khác.
Tự kiểm tra
- Q1Tại sao dùng profile groups thay vì liệt kê thẳng
SPRING_PROFILES_ACTIVE=prod,prod-db,prod-cache,prod-tracing? Khi nào groups không đủ? - Q2Cho đoạn YAML dưới đây — Boot merge document 1 và document 2 ra sao khi
prodactive? Giá trị cuối củahikari.maximum-pool-sizelà bao nhiêu?
Document 1:datasource.hikari.maximum-pool-size: 10(no profile). Document 2:datasource.hikari.maximum-pool-size: 50(on-profile: prod). - Q3Tại sao cần
StartupValidatorvalidate active profile tạiApplicationReadyEvent? Tại sao không đủ nếu chỉ kiểm tra log? - Q4Bạn cần deploy app ở 3 region:
asia-east,eu-west,us-east. Mỗi region có DB URL riêng; common config (security, JPA pool) giống nhau. Thiết kế profile layout dùng groups thế nào? - Q5Blue/green deployment dùng 2 K8s Deployment:
order-service(9 replica,productiongroup) vàorder-service-canary(1 replica,production,prod-v2). Tại sao canary pod active 5 profile, không phải 2? Cơ chế nào giải thích?
Bài tiếp theo: Logging — SLF4J, levels, config
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên