spring profiles và externalized config

- Published on
- /10 mins read/
Một trong những rủi ro bảo mật nghiêm trọng nhất mà các đội ngũ phát triển hay mắc phải là lưu trữ mật khẩu cơ sở dữ liệu, API keys hoặc chứng chỉ bảo mật trực tiếp vào các file
application-prod.ymlrồi commit lên Git repository. Theo nguyên tắc 'Factor III: Config' của phương pháp luận 12-Factor App kinh điển: Mã nguồn phần mềm và Cấu hình môi trường phải được phân tách tuyệt đối. Một artifact đóng gói (Docker Container Image) phải có khả năng chạy xuyên suốt từ Local, Dev, Staging cho tới Production mà không cần rebuild lại một dòng code nào.
Trong hệ sinh thái Spring Boot, Spring Profiles là cơ chế nền tảng giúp kích hoạt có chọn lọc các Bean và nạp các tập thuộc tính tương ứng theo từng ngữ cảnh triển khai. Tuy nhiên, kể từ Spring Boot 2.4+, cơ chế nạp cấu hình đã trải qua một cuộc tái cấu trúc toàn diện với sự xuất hiện của spring.config.import, làm thay đổi hoàn toàn cách chúng ta tổ chức đa môi trường.
Bài viết này phân tích chi tiết Thứ tự ưu tiên nạp cấu hình (Property Source Precedence) trong Spring Boot và cung cấp Bản thiết kế quản trị cấu hình Cloud-Native chuẩn production kết hợp cùng Kubernetes và HashiCorp Vault.
# nguyên lý 12-factor: phân tách tuyệt đối mã nguồn & cấu hình
# tiêu chuẩn vàng của 12-factor config
- Kiểm tra tính độc lập: Nếu ứng dụng của bạn được biến thành mã nguồn mở (Open Source) ngay ngày mai trên GitHub, bạn có phải lo lắng về việc lộ bất kỳ thông tin đăng nhập nào không? Nếu câu trả lời là CÓ, kiến trúc cấu hình của bạn đã thất bại.
- Không phân biệt artifact theo môi trường: Không bao giờ tạo file
app-dev.jarhayapp-prod.jar. Phải là cùng một file.jarnhận cấu hình động từ môi trường bên ngoài khi khởi động.
# thứ tự ưu tiên 17 tầng cấu hình trong Spring Boot (property source hierarchy)
Khi cùng một thuộc tính (ví dụ: database.url) được khai báo ở nhiều nơi: trong file YAML, biến môi trường hệ điều hành, hay tham số dòng lệnh — Spring Boot sẽ phân giải giá trị nào?
Spring Boot áp dụng một thứ tự ưu tiên cực kỳ chặt chẽ (Giá trị ở tầng trên sẽ ghi đè hoàn toàn giá trị ở tầng dưới):
Quy tắc của Solution Architect: Hãy tận dụng tính năng này để xây dựng cấu hình an toàn:
application.ymlđóng gói bên trong file Jar: Chỉ chứa các giá trị mặc định an toàn cho môi trường Local / Test.- Môi trường Production: Ghi đè bằng Biến Môi Trường (Environment Variables) hoặc nạp từ máy chủ quản lý bí mật bên ngoài mà không bao giờ sửa file đóng gói trong Jar!
# cuộc cách mạng Spring Boot 2.4+: cú pháp Spring.config.import
Trước bản 2.4, lập trình viên thường dùng cú pháp phân tách --- kết hợp spring.profiles trong một file YAML duy nhất. Cú pháp cũ này đã bị Deprecate vì gây ra nhiều hành vi ghi đè khó đoán (Non-deterministic behavior).
Từ Spring Boot 2.4+, cơ chế nạp cấu hình mới sử dụng thuộc tính spring.config.import linh hoạt và tường minh hơn rất nhiều:
# application.yml chuẩn hiện đại
spring:
application:
name: payment-core-service
# Kích hoạt profile mặc định nếu không có biến môi trường
profiles:
active: local
# Cấu hình dùng chung cho mọi môi trường
jpa:
open-in-view: false
hibernate:
ddl-auto: validate
---
# Khối cấu hình cho Profile LOCAL
spring:
config:
activate:
on-profile: local
datasource:
url: jdbc:h2:mem:testdb
username: sa
password:
---
# Khối cấu hình cho Profile PRODUCTION
spring:
config:
activate:
on-profile: prod
# Nạp cấu hình từ file mount ngoài Kubernetes và HashiCorp Vault
import:
- 'configtree:/etc/config/app/'
- 'vault://secret/payment-service'# sức mạnh của configtree
Khai báo configtree:/etc/config/app/ cho phép Spring Boot tự động quét toàn bộ thư mục được Kubernetes ConfigMap mount vào:
- Tên file sẽ biến thành tên key của cấu hình (ví dụ: file
/etc/config/app/database/url). - Nội dung bên trong file sẽ biến thành giá trị của thuộc tính tương ứng!
# kiểm soát khởi tạo Bean: @profile vs @conditionalonproperty
Để kích hoạt hoặc vô hiệu hóa các Bean theo môi trường, Spring cung cấp hai cơ chế với triết lý hoàn toàn khác biệt:
# so sánh kiến trúc
| Tiêu chuẩn | @Profile | @ConditionalOnProperty | | :------------------------ | :--------------------------------------------------------------- | :----------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | Mục đích | Bật/tắt component theo Môi trường vĩ mô (Local, Test, Prod) | Bật/tắt component theo Tính năng cụ thể (Feature Toggles) | | Biểu thức hỗ trợ | Hỗ trợ logic cơ bản: @Profile("dev | test"), @Profile("prod & !dr") | Hỗ trợ kiểm tra giá trị: havingValue = "true", matchIfMissing = false | | Khả năng Testability | Kém: Phải dùng @ActiveProfiles("test") trên toàn bộ test suite | Rất cao: Chỉ cần override thuộc tính trong test bằng @TestPropertySource | | Khuyến nghị kiến trúc | Dùng cho hạ tầng cốt lõi (Datasource, Cloud Storage Provider) | Khuyến nghị dùng cho 90% logic nghiệp vụ và tích hợp 3rd party |
# triển khai mock service chuẩn enterprise với feature flag
public interface PaymentGatewayClient {
PaymentResult charge(BigDecimal amount);
}
// Bean Mock chỉ chạy khi bật cờ feature flag
@Service
@ConditionalOnProperty(prefix = "features.payment", name = "use-sandbox", havingValue = "true", matchIfMissing = true)
public class MockSandboxPaymentClient implements PaymentGatewayClient {
@Override
public PaymentResult charge(BigDecimal amount) {
return new PaymentResult("SANDBOX_SUCCESS_TX_123");
}
}
// Bean Production thật chỉ kích hoạt khi tắt sandbox
@Service
@ConditionalOnProperty(prefix = "features.payment", name = "use-sandbox", havingValue = "false")
public class LiveVisaPaymentClient implements PaymentGatewayClient {
@Override
public PaymentResult charge(BigDecimal amount) {
// Gọi API Visa thật qua mTLS
return callVisaApi(amount);
}
}# kiến trúc bảo mật: quản trị bí mật với hashicorp Vault & Kubernetes secrets
Trong môi trường ngân hàng và tài chính, không một ai (kể cả Lead Developer) được phép biết mật khẩu Database Production.
# lợi ích bảo mật vượt trội
- Dynamic Secrets: HashiCorp Vault tự động sinh ra một cặp tài khoản Database tạm thời cho mỗi Pod với thời hạn sống (TTL) 1 giờ. Nếu Pod chết, tài khoản đó tự động bị xóa khỏi PostgreSQL.
- Zero-Trust & Audit Trail: Mọi lượt truy vấn lấy mật khẩu đều được ghi vết (Audit Log) chính xác tới từng tên Pod và IP.
# thay đổi cấu hình không cần khởi động lại: @refreshscope
Khi cần thay đổi các tham số nhạy cảm (như ngưỡng Rate Limit, thời gian Timeout của đối tác) trên Production, việc Restart hàng trăm container là một rủi ro lớn.
Sử dụng Spring Cloud Bus kết hợp @RefreshScope:
@Component
@RefreshScope // Tự động reload giá trị khi nhận được sự kiện RefreshEvent
@ConfigurationProperties(prefix = "business.thresholds")
public class DynamicThresholdProperties {
private BigDecimal maxTransferLimit = new BigDecimal("50000000");
// Getter & Setter
public BigDecimal getMaxTransferLimit() { return maxTransferLimit; }
public void setMaxTransferLimit(BigDecimal limit) { this.maxTransferLimit = limit; }
}Khi cấu hình thay đổi trên Git hoặc Vault, quản trị viên chỉ cần bắn một webhook tới Actuator Endpoint:
curl -X POST https://api.company.com/actuator/busrefreshSpring Cloud Bus sẽ phát tán sự kiện qua Kafka/RabbitMQ tới toàn bộ các Pods đang chạy. Mọi Bean được đánh dấu @RefreshScope sẽ tự động hủy instance cũ và nạp lại cấu hình mới trong bộ nhớ RAM mà không gây rớt bất kỳ kết nối nào.
# bảng kiểm tra sẵn sàng vận hành (architect's checklist)
- 1. Quét Secret Bằng Git Hooks: Đã cài đặt công cụ quét secret (như
gitleakshoặctrufflehog) trong pre-commit hook để chặn đứng việc commit credentials lên Git chưa? - 2. Không Dùng File application-prod.yml Trong Jar: Môi trường Production phải nhận cấu hình thông qua biến môi trường OS hoặc Secret Store bên ngoài.
- 3. Strict Validation Khi Khởi Động: Đã sử dụng
@ConfigurationPropertieskết hợp Jakarta Bean Validation (@Validated,@NotNull) để ứng dụng lập tức dừng khởi động (Fail-Fast) nếu thiếu cấu hình quan trọng chưa? - 4. Giới Hạn Quyền Actuator Refresh: Endpoint
/actuator/refreshvà/actuator/envbắt buộc phải được bảo vệ bằng Spring Security, chỉ mở cho mạng nội bộ hoặc tài khoản admin. - 5. Profile Naming Standardization: Chuẩn hóa quy ước đặt tên profile trong toàn bộ tổ chức (ví dụ:
local,dev,staging,prod) để tránh tình trạng nơi thì dùngproduction, nơi thì dùnglive.
# lời kết
Quản trị cấu hình không đơn thuần là việc tạo ra vài file .yml và chuyển đổi profile qua lại. Nó là một kỷ luật kiến trúc mang tính sống còn đối với sự an toàn và tính linh hoạt của doanh nghiệp.
Làm chủ thứ tự nạp cấu hình của Spring Boot, tuân thủ nguyên tắc 12-Factor và tích hợp cùng hạ tầng quản lý bí mật hiện đại (Kubernetes / Vault) chính là nền tảng vững chắc để bạn tự tin vận hành hàng trăm microservices trên môi trường đám mây mà không bao giờ phải lo sợ trước những sự cố rò rỉ dữ liệu hay xung đột cấu hình.
Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!
Happy coding 😎 👍🏻 🚀 🔥.
On this page
- # nguyên lý 12-factor: phân tách tuyệt đối mã nguồn & cấu hình
- # tiêu chuẩn vàng của 12-factor config
- # thứ tự ưu tiên 17 tầng cấu hình trong Spring Boot (property source hierarchy)
- # cuộc cách mạng Spring Boot 2.4+: cú pháp Spring.config.import
- # sức mạnh của configtree
- # kiểm soát khởi tạo Bean: @profile vs @conditionalonproperty
- # so sánh kiến trúc
- # triển khai mock service chuẩn enterprise với feature flag
- # kiến trúc bảo mật: quản trị bí mật với hashicorp Vault & Kubernetes secrets
- # lợi ích bảo mật vượt trội
- # thay đổi cấu hình không cần khởi động lại: @refreshscope
- # bảng kiểm tra sẵn sàng vận hành (architect's checklist)
- # lời kết