Spring Core & Boot/Profile Groups & Production Patterns — gom profile, validate, blue/green
38/41
Bài 38 / 41~13 phútConfig, Profiles & LoggingMiễn phí lượt xem

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_ACTIVE dà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.

Tại sao groups tốt hơn chuỗi profile rời

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-baseprod-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:

  1. Document 1 (không có on-profile): apply làm base cho mọi profile.
  2. Mỗi document sau: Boot kiểm tra on-profile — nếu match active profile thì merge với base, ghi đè key trùng.
  3. 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:

Giá trị maximum-pool-size sau từng document khi profile prod active: base cho 10, document dev bị bỏ qua nguyên khối nên vẫn 10, document prod trùng key nên ghi đè thành 50

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ốngApproach
File ngắn (dưới 100 dòng), 2-3 profileMulti-document — một file, thấy tổng quan dễ
File dài, 5+ profile, nhiều team chỉnhSeparate files — ít merge conflict
Dùng profile groupsSeparate files — group reference file riêng
Quick prototypeMulti-document — không tạo nhiều file
Boot 2.3 tro ve truoc

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-baseprod-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:

GroupInclude
Định nghĩa ởapplication.yml baseFile profile con (application-prod.yml)
Ai quyết includeNgười vận hành (operator) khi set activeAuthor của profile
FlexibilityOperator customize groupFixed 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 xongbean 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.

Ket hop voi Spring Boot Actuator

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/env check spring.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/configprops liệt kê property source ưu tiên.
  • Remediate: thứ tự profile trong spring.profiles.active có 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ó @RefreshScope cho 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ự:

  1. Boot đọc spring.profiles.active từ env var / JVM property / application.yml.
  2. Với mỗi profile name trong danh sách active, Boot tra spring.profiles.group map — nếu có mapping thì thêm (không replace) các profile con vào danh sách active.
  3. 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).
  4. Sau khi expand xong, Boot load application-{profile}.yml cho 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ế.

Danh sách active trước và sau expand: production được tra trong group map rồi bốn profile con được thêm vào, tên group vẫn nằm nguyên trong danh sách nên Boot cũng thử load file cấu hình theo tên đó

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 (DEBUG cho dev, INFO cho prod). Hiểu profile groups trước khi config logging per-profile.
  • Spring Boot AutoConfiguration — cơ chế bên dưới: @ConditionalOnProfile là một trong các @Conditional autoconfiguration dùng để chỉ apply @Configuration class khi profile match — liên hệ trực tiếp với @Profile trong bài này.
  • Bean lifecycle phases: ApplicationReadyEvent (dùng trong StartupValidator) 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

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Tạ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 đủ?
  2. Q2
    Cho đoạn YAML dưới đây — Boot merge document 1 và document 2 ra sao khi prod active? Giá trị cuối của hikari.maximum-pool-size là bao nhiêu?

    Document 1: datasource.hikari.maximum-pool-size: 10 (no profile). Document 2: datasource.hikari.maximum-pool-size: 50 (on-profile: prod).
  3. Q3
    Tại sao cần StartupValidator validate active profile tại ApplicationReadyEvent? Tại sao không đủ nếu chỉ kiểm tra log?
  4. Q4
    Bạ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?
  5. Q5
    Blue/green deployment dùng 2 K8s Deployment: order-service (9 replica, production group) 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

Đặt 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

Bài tiếp theo

Logging cơ bản — SLF4J facade, Logback, log level và hierarchy