Cấu hình
Tổng quan
Cấu hình di chuyển từ file .env của Docker Compose sang Kubernetes ConfigMap và Secret. Trên staging, secrets được tạo thủ công qua create-secrets.sh. Trên production, Sealed Secrets cung cấp mã hóa an toàn để lưu trong Git.
Tổ chức ConfigMap
Mỗi service có ConfigMap riêng với cấu hình không nhạy cảm. ConfigMap được tổ chức theo từng overlay (staging vs production) qua Kustomize.
ConfigMap Dùng chung
Các giá trị được chia sẻ qua tất cả backend service. Lưu ý khác biệt namespace cho service discovery và truy cập tầng dữ liệu.
apiVersion: v1
kind: ConfigMap
metadata:
name: nx-shared-config
namespace: nx-backend
data:
NODE_ENV: staging
# PostgreSQL via PgBouncer — nx-persistent namespace
APP_ENV_POSTGRES_HOST: nx-pgbouncer.nx-persistent.svc.cluster.local
APP_ENV_POSTGRES_PORT: "5432"
APP_ENV_POSTGRES_USERNAME: nx_seller_operator
APP_ENV_POSTGRES_DATABASE: nx_seller_core
# Redis — cluster mode, 3 nodes (nx-broker namespace)
APP_ENV_CACHE_REDIS_MODE: cluster
APP_ENV_CACHE_REDIS_CLUSTER_NODES: "nx-redis-0.nx-redis-headless.nx-broker.svc.cluster.local:6379,nx-redis-1.nx-redis-headless.nx-broker.svc.cluster.local:6379,nx-redis-2.nx-redis-headless.nx-broker.svc.cluster.local:6379"
# Kafka — CLIENT listener with SASL/SCRAM-SHA-512 (nx-broker namespace)
APP_ENV_KAFKA_BROKERS: nx-kafka-0.nx-kafka-headless.nx-broker.svc.cluster.local:9092,nx-kafka-1.nx-kafka-headless.nx-broker.svc.cluster.local:9092,nx-kafka-2.nx-kafka-headless.nx-broker.svc.cluster.local:9092
APP_ENV_KAFKA_SASL_ENABLE: "true"
APP_ENV_KAFKA_SASL_MECHANISM: SCRAM-SHA-512
APP_ENV_KAFKA_SASL_USERNAME: nx.staging
# APP_ENV_KAFKA_SASL_PASSWORD is in nx-shared-secret
# Service discovery — nx-backend namespace
APP_ENV_IDENTITY_SERVICE_BASE_URL: http://nx-identity.nx-backend.svc.cluster.local:3000/v1/api/identity
APP_ENV_COMMERCE_SERVICE_BASE_URL: http://nx-commerce.nx-backend.svc.cluster.local:3000/v1/api/commerce
APP_ENV_PRICING_SERVICE_BASE_URL: http://nx-pricing.nx-backend.svc.cluster.local:3000/v1/api/pricing
# Domains — staging
APP_DOMAIN: sgw.staging.bana.com.vn
HOOK_DOMAIN: hook.staging.bana.com.vnapiVersion: v1
kind: ConfigMap
metadata:
name: nx-shared-config
namespace: nx-backend
data:
NODE_ENV: production
LOG_LEVEL: info
# Database — nx-persistent namespace
DB_HOST: nx-postgresql.nx-persistent.svc.cluster.local
DB_PORT: "5432"
DB_NAME: nx_seller
# Redis — nx-broker namespace
REDIS_HOST: nx-redis.nx-broker.svc.cluster.local
REDIS_PORT: "6379"
# Kafka — nx-broker headless service DNS
KAFKA_BROKERS: nx-kafka-0.nx-kafka-headless.nx-broker.svc.cluster.local:29092,nx-kafka-1.nx-kafka-headless.nx-broker.svc.cluster.local:29092,nx-kafka-2.nx-kafka-headless.nx-broker.svc.cluster.local:29092
# Typesense — nx-search namespace
TYPESENSE_HOST: nx-typesense.nx-search.svc.cluster.local
TYPESENSE_PORT: "8108"
# Service discovery — nx-backend namespace
IDENTITY_URL: http://nx-identity.nx-backend.svc.cluster.local:3000
COMMERCE_URL: http://nx-commerce.nx-backend.svc.cluster.local:3000
SALE_URL: http://nx-sale.nx-backend.svc.cluster.local:3000
SIGNAL_URL: http://nx-signal.nx-backend.svc.cluster.local:3000
PAYMENT_URL: http://nx-payment-api.nx-backend.svc.cluster.local:3000
# Domains — production (TBD)
APP_DOMAIN: TBD
HOOK_DOMAIN: TBD
# OpenTelemetry — production only
OTEL_EXPORTER_OTLP_ENDPOINT: http://nx-otel-collector.nx-watcher.svc.cluster.local:4317
OTEL_SERVICE_NAME: banaKhác biệt ConfigMap Staging vs Production
| Key | Staging | Production |
|---|---|---|
NODE_ENV | staging | production |
APP_ENV_POSTGRES_HOST | nx-pgbouncer.nx-persistent... (PgBouncer) | nx-pgbouncer.nx-persistent... (PgBouncer) |
APP_ENV_CACHE_REDIS_MODE | cluster | cluster |
APP_ENV_KAFKA_SASL_USERNAME | nx.staging | nx.production |
APP_DOMAIN | sgw.staging.bana.com.vn | TBD |
HOOK_DOMAIN | hook.staging.bana.com.vn | TBD |
OTEL_EXPORTER_OTLP_ENDPOINT | Chưa đặt | http://nx-otel-collector.nx-watcher.svc.cluster.local:4317 |
TIP
Các URL service discovery (IDENTITY_URL, COMMERCE_URL, v.v.) giống nhau ở mọi môi trường vì chúng dùng DNS nội bộ cluster trong nx-backend. Chỉ các domain public-facing khác nhau.
ConfigMap theo Service
identity-specific config
# identity-specific config
apiVersion: v1
kind: ConfigMap
metadata:
name: nx-identity-config
namespace: nx-backend
data:
APP_NAME: identity
APP_PORT: "3000"
APP_BASE_PATH: /v1/api/identity
APP_ROLE: issuer
# JWKS settings
JWKS_ROTATION_INTERVAL: "86400"
TOKEN_ACCESS_EXPIRY: "3600"
TOKEN_REFRESH_EXPIRY: "604800"payment-api specific config
# payment-api specific config
apiVersion: v1
kind: ConfigMap
metadata:
name: nx-payment-api-config
namespace: nx-backend
data:
APP_NAME: payment
APP_PORT: "3000"
APP_BASE_PATH: /v1/api/payment
APP_MODE: apipayment-worker specific config
# payment-worker specific config
apiVersion: v1
kind: ConfigMap
metadata:
name: nx-payment-worker-config
namespace: nx-backend
data:
APP_NAME: payment
APP_ENV_MQ_PAY_MODE: workerÁnh xạ DNS từ Docker Compose sang K8s
Docker Compose (.env) | K8s ConfigMap | Ghi chú |
|---|---|---|
DB_HOST=nx-postgresql | APP_ENV_POSTGRES_HOST=nx-pgbouncer.nx-persistent.svc.cluster.local | PgBouncer pooler, cross-namespace cần FQDN |
REDIS_HOST=nx-redis | APP_ENV_CACHE_REDIS_CLUSTER_NODES=nx-redis-{0,1,2}.nx-redis-headless.nx-broker... | Cluster mode, 3 pod StatefulSet trong nx-broker |
KAFKA_BROKERS=nx-kafka-1:29092,... | APP_ENV_KAFKA_BROKERS=nx-kafka-0.nx-kafka-headless.nx-broker...:9092,... | CLIENT listener port 9092 với SASL trong nx-broker |
IDENTITY_URL=http://dev-nx-identity:3000 | APP_ENV_IDENTITY_SERVICE_BASE_URL=http://nx-identity.nx-backend.svc.cluster.local:3000/v1/api/identity | Cùng namespace (nx-backend), dạng ngắn cũng được |
TIP
Trong cùng một namespace, tên DNS ngắn hoạt động: nx-identity:3000. Cross-namespace cần dạng đầy đủ \<svc\>.\<ns\>.svc.cluster.local. Tất cả backend service đều ở nx-backend; frontend service nằm ở nx-app. Các data service được chia qua nx-persistent, nx-broker, và nx-search.
Quản lý Secrets
Staging: Tạo Thủ công qua create-secrets.sh
Trên staging, secrets được tạo thủ công bằng script create-secrets.sh trong infrastructure/deployments/staging/manifests/02-secrets/. File YAML template định nghĩa cấu trúc; script hỏi giá trị thực và tạo K8s secret trực tiếp.
# Run from the staging manifests directory
cd infrastructure/deployments/staging/manifests/02-secrets/
./create-secrets.shProduction: Sealed Secrets
Sealed Secrets mã hóa secrets phía client để có thể lưu trong Git (chỉ production).
Workflow (Production)
# 1. Create a regular secret YAML (DO NOT commit this)
cat <<EOF > secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: nx-identity-secret
namespace: nx-backend
type: Opaque
stringData:
DB_PASSWORD: "actual-password"
JWT_SECRET: "actual-jwt-secret"
JWKS_PRIVATE_KEY: "actual-private-key"
EOF
# 2. Seal it (encrypts with cluster's public key)
kubeseal --format yaml < secret.yaml > sealed-secret.yaml
# 3. Commit the sealed version
git add sealed-secret.yaml
rm secret.yaml # Never commit the plain versionTổ chức Secret
| Secret | Namespace | Nội dung |
|---|---|---|
nx-shared-secret | nx-backend | APP_ENV_POSTGRES_PASSWORD, APP_ENV_CACHE_REDIS_PASSWORD, APP_ENV_KAFKA_SASL_PASSWORD |
nx-identity-secret | nx-backend | ES256 keys, mail credentials, SMS API keys |
nx-payment-secret | nx-backend | Payment DB & Redis credentials |
nx-commerce-secret | nx-backend | S3 credentials, VNPAY TVAN API key, Typesense key |
nx-sale-secret | nx-backend | Basic auth credentials |
nx-ledger-secret | nx-backend | Encryption key, S3 credentials |
nx-postgresql-superuser-secret | nx-persistent | Postgres superuser credentials |
nx-postgresql-app-secret | nx-persistent | App user (nx_seller_operator) credentials |
nx-postgresql-replication-secret | nx-persistent | Streaming replication credentials |
nx-redis-secret | nx-broker | REDIS_PASSWORD |
nx-kafka-jaas | nx-broker | Cấu hình JAAS Kafka SASL/SCRAM |
nx-kafka-scram-secret | nx-broker | SCRAM username/password |
nx-typesense-secret | nx-search | TYPESENSE_API_KEY |
staging-bana-tls | nx-internal | TLS cert và key |
bcr-registry | nx-backend / nx-app / nx-internal | Container registry pull credentials |
INFO
bcr-registry là imagePullSecret cần thiết để pull image từ private registry bcr.bana.com.vn. Nó phải được tạo trong nx-backend, nx-app, và nx-internal và được tham chiếu trong tất cả Deployment spec hoặc ServiceAccount mặc định của namespace.
Cấu hình Pod
Pod tham chiếu cả config dùng chung và config theo service:
Pods reference both shared and service-specific configs:
spec:
imagePullSecrets:
- name: bcr-registry
containers:
- name: identity
envFrom:
# Shared config (DB_HOST, REDIS_HOST, etc.)
- configMapRef:
name: nx-shared-config
# Service-specific config
- configMapRef:
name: nx-identity-config
# Shared secrets (DB_PASSWORD, REDIS_PASSWORD)
- secretRef:
name: nx-shared-secret
# Service-specific secrets
- secretRef:
name: nx-identity-secret
env:
# Downward API for Snowflake ID
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.namePhân cấp Cấu hình
Pod Environment
├── nx-shared-config (ConfigMap) — shared across all services
├── nx-<service>-config (ConfigMap) — service-specific overrides
├── nx-shared-secret (Secret) — shared credentials
└── nx-<service>-secret (Secret) — service-specific credentialsCác entry sau override entry trước khi key trùng. Điều này cho phép:
nx-shared-configđặtDB_HOSTcho tất cả servicenx-identity-configcó thể override bất kỳ key dùng chung nào nếu cần
Tham khảo Namespace
| Namespace | Mục đích | Phạm vi ConfigMap/Secret |
|---|---|---|
nx-internal | nginx-ingress, Traefik, cert-manager, API Portal | TLS certs, cấu hình gateway, registry creds |
nx-backend | Backend services | Backend configs, backend secrets, registry creds |
nx-app | Frontend apps | Frontend configs, registry creds |
nx-persistent | PostgreSQL | Database credentials |
nx-broker | Redis, Kafka | Broker credentials |
nx-search | Typesense | Search credentials |
nx-watcher | Observability stack | Cấu hình giám sát |
Xác thực Môi trường
Mỗi service xác thực môi trường khi startup bằng Zod schema (pattern IGNIS). Nếu thiếu biến bắt buộc, pod fail nhanh với lỗi rõ ràng trong log.
// packages/identity/src/config/environment.ts
const envSchema = z.object({
APP_ENV_POSTGRES_HOST: z.string(),
APP_ENV_POSTGRES_PORT: z.coerce.number(),
APP_ENV_POSTGRES_DATABASE: z.string(),
APP_ENV_POSTGRES_USERNAME: z.string(),
// ... all required vars
});Đây là cùng validation dùng trong Docker Compose — không cần thay đổi.