Skip to content

Tầng Dữ liệu

Tầng dữ liệu chạy trên node pool stateful trong cụm Kubernetes VNPAY Cloud. Tất cả workload đều dùng StorageClass csi-sc-vnpaycloud (Cinder CSI trên OpenStack) với allowVolumeExpansion: true, nghĩa là PVC có thể được mở rộng online mà không downtime.

Tổng quan Kiến trúc

Tóm tắt Component

ComponentLoạiInstancesImageNamespaceStorage
PostgreSQLStatefulSet (primary + replica)2 (1 primary + 1 replica)postgres:17-alpinenx-persistent20Gi mỗi pod
PgBouncerDeployment1ghcr.io/icoretech/pgbouncer-docker:1.24.1nx-persistent-
RedisStatefulSet (cluster mode)3 master, 0 replicaredis:8-alpinenx-broker5Gi mỗi pod
KafkaStatefulSet (KRaft + SASL)3 brokerapache/kafka:4.1.1nx-broker10Gi mỗi pod
TypesenseStatefulSet (cụm Raft)3typesense/typesense:30.1nx-search5Gi mỗi node
DebeziumDeployment1debezium/connect:3.0.0.Finalnx-search-

PostgreSQL

Kiến trúc: Primary + Replica với PgBouncer

PostgreSQL chạy dưới dạng hai StatefulSet tách biệt - một primary (đọc-ghi) và một replica (chỉ đọc, hot standby) - với một Deployment PgBouncer đứng trước để connection pooling. Replica stream WAL từ primary bằng streaming replication native của PostgreSQL.

Primary StatefulSet

Init container xử lý khởi tạo database lần đầu: tạo app user nx_seller_operator, database nx_seller_core, kích hoạt extension pg_stat_statementspgcrypto, và tạo repl_user cho streaming replication.

StatefulSet: nx-postgresql-primary
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nx-postgresql-primary
  namespace: nx-persistent
spec:
  serviceName: nx-postgresql-primary-headless
  replicas: 1
  template:
    spec:
      nodeSelector:
        node.kubernetes.io/pool: stateful
      securityContext:
        runAsNonRoot: true
        runAsUser: 70
        runAsGroup: 70
        fsGroup: 70
        seccompProfile:
          type: RuntimeDefault
      initContainers:
        - name: init-primary
          image: postgres:17-alpine
          # Creates: nx_seller_operator user, nx_seller_core DB,
          # pg_stat_statements + pgcrypto extensions, repl_user for replication,
          # pg_hba.conf entries for replication + SCRAM auth
      containers:
        - name: postgresql
          image: postgres:17-alpine
          ports:
            - containerPort: 5432
          args:
            - -c
            - wal_level=logical
            - -c
            - max_wal_senders=10
            - -c
            - max_replication_slots=10
            - -c
            - hot_standby=on
            - -c
            - shared_buffers=512MB
            - -c
            - effective_cache_size=1536MB
            - -c
            - work_mem=8MB
            - -c
            - maintenance_work_mem=128MB
            - -c
            - max_connections=100
            - -c
            - random_page_cost=1.1
            - -c
            - effective_io_concurrency=200
            - -c
            - log_statement=ddl
            - -c
            - log_min_duration_statement=1000
            - -c
            - password_encryption=scram-sha-256
            - -c
            - timezone=Asia/Ho_Chi_Minh
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              cpu: "2"
              memory: 4Gi
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: [ReadWriteOnce]
        storageClassName: csi-sc-vnpaycloud
        resources:
          requests:
            storage: 20Gi

Replica StatefulSet

Init container của replica dùng pg_basebackup từ primary lần đầu, sau đó vào chế độ hot standby. Nó stream WAL liên tục từ primary để độ trễ replication gần như bằng 0.

StatefulSet: nx-postgresql-replica
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nx-postgresql-replica
  namespace: nx-persistent
spec:
  serviceName: nx-postgresql-replica-headless
  replicas: 1
  template:
    spec:
      initContainers:
        - name: init-replica
          image: postgres:17-alpine
          # pg_basebackup from primary on first run, standby.signal on restart
      containers:
        - name: postgresql
          image: postgres:17-alpine
          args:
            - -c
            - hot_standby=on
            - -c
            - hot_standby_feedback=on
            - -c
            - shared_buffers=512MB
            - -c
            - max_connections=100
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 2Gi
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        storageClassName: csi-sc-vnpaycloud
        resources:
          requests:
            storage: 20Gi

PgBouncer Connection Pooling

PgBouncer chạy dưới dạng Deployment đứng trước primary. Các backend service kết nối tới nx-pgbouncer:5432 (ánh xạ tới port 6432 nội bộ của PgBouncer). Nó chạy ở chế độ transaction pooling với max_client_conn là 200 và default_pool_size là 20.

Cấu hình PgBouncer (từ ConfigMap)
yaml
# PgBouncer configuration (from ConfigMap)
[databases]
nx_seller_core = host=nx-postgresql-primary.nx-persistent.svc.cluster.local port=5432 dbname=nx_seller_core

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
pool_mode = transaction
max_client_conn = 200
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
max_db_connections = 50

Init container sinh file userlist.txt từ các Kubernetes Secret (app user + superuser). Service expose port 5432 ra bên ngoài, ánh xạ tới 6432 nội bộ - nên các backend service dùng cùng port như khi kết nối trực tiếp PostgreSQL.

Services

ServiceDNSPortMục đích
nx-pgbouncernx-pgbouncer.nx-persistent.svc.cluster.local5432Kết nối pooled tới primary (dùng bởi tất cả backend service)
nx-postgresql-primarynx-postgresql-primary.nx-persistent.svc.cluster.local5432Primary trực tiếp (dùng bởi PgBouncer, port-forward)
nx-postgresql-replicanx-postgresql-replica.nx-persistent.svc.cluster.local5432Replica trực tiếp (chỉ đọc, dùng cho read splitting sau này)
nx-postgresql-primary-headless(pod DNS)5432DNS ổn định cho StatefulSet
nx-postgresql-replica-headless(pod DNS)5432DNS ổn định cho StatefulSet

Đường dẫn Kết nối Mặc định

Các backend service kết nối tới nx-pgbouncer.nx-persistent.svc.cluster.local:5432 (pooled). Điều này được cấu hình trong shared-config.yaml dưới dạng APP_ENV_POSTGRES_HOST. Các service primary/replica trực tiếp tồn tại cho truy cập admin và read/write splitting trong tương lai.

Users & Secrets

UserMục đíchSecret
postgresSuperusernx-postgresql-superuser-secret
nx_seller_operatorApp user (CREATEDB)nx-postgresql-app-secret
repl_userStreaming replicationnx-postgresql-replication-secret

Database Schemas

BANA dùng 14 schema trong database nx_seller_core:

SchemaServices
publicidentity, commerce (bảng dùng chung)
helpdeskhelpdesk
pricingpricing
allocationcommerce (stock allocation)
inventoryinventory
financefinance
ledgerledger
paymentpayment
salesale
invoiceinvoice
identityidentity
taxtaxation
licensinglicensing
outreachoutreach

Port-Forward cho Truy cập Local

bash
# From infrastructure/deployments/staging/ directory:
./kc port-forward svc/nx-postgresql-primary 5432:5432 -n nx-persistent

# From project root:
infrastructure/deployments/staging/kc port-forward svc/nx-postgresql-primary 5432:5432 -n nx-persistent

# Then connect locally:
psql -h localhost -U nx_seller_operator -d nx_seller_core

Redis

Cluster Mode (3 Master, 0 Replica)

Redis chạy dưới dạng cluster 3 node với 16384 hash slot phân phối qua 3 master. Không có replica được cấu hình (staging không cần dự phòng cho từng master). Cluster cung cấp sharding dữ liệu qua các node.

StatefulSet

StatefulSet: nx-redis
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nx-redis
  namespace: nx-broker
spec:
  serviceName: nx-redis-headless
  replicas: 3
  template:
    spec:
      nodeSelector:
        node.kubernetes.io/pool: stateful
      securityContext:
        runAsNonRoot: true
        runAsUser: 999
        runAsGroup: 999
        fsGroup: 999
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: redis
          image: redis:8-alpine
          command:
            - redis-server
            - --requirepass
            - $(REDIS_PASSWORD)
            - --masterauth
            - $(REDIS_PASSWORD)
            - --cluster-enabled
            - "yes"
            - --cluster-config-file
            - /data/nodes.conf
            - --cluster-node-timeout
            - "5000"
            - --appendonly
            - "yes"
            - --appendfsync
            - everysec
            - --maxmemory
            - 256mb
            - --maxmemory-policy
            - noeviction
          ports:
            - containerPort: 6379
              name: redis
            - containerPort: 16379
              name: cluster-bus
          env:
            - name: REDIS_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: nx-redis-secret
                  key: REDIS_PASSWORD
          resources:
            requests:
              cpu: 100m
              memory: 384Mi
            limits:
              cpu: "1"
              memory: 512Mi
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: [ReadWriteOnce]
        storageClassName: csi-sc-vnpaycloud
        resources:
          requests:
            storage: 5Gi

Khởi tạo Cluster

Sau khi cả 3 pod đang chạy, một Job một lần tạo cluster:

Job: nx-redis-cluster-init
yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: nx-redis-cluster-init
  namespace: nx-broker
spec:
  ttlSecondsAfterFinished: 600
  backoffLimit: 3
  template:
    spec:
      restartPolicy: OnFailure
      nodeSelector:
        node.kubernetes.io/pool: default
      securityContext:
        runAsNonRoot: true
        runAsUser: 999
        runAsGroup: 999
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: cluster-init
          image: redis:8-alpine
          command:
            - sh
            - -c
            - |
              set -e
              export REDISCLI_AUTH="$REDIS_PASSWORD"
              HEADLESS="nx-redis-headless.nx-broker.svc.cluster.local"
              NODES=""
              for i in 0 1 2; do
                HOST="nx-redis-${i}.${HEADLESS}"
                until [ "$(redis-cli -h "$HOST" ping 2>/dev/null)" = "PONG" ]; do sleep 2; done
                NODES="${NODES} ${HOST}:6379"
              done
              echo "yes" | redis-cli --cluster create ${NODES} --cluster-replicas 0

PodSecurity Restricted

Init job phải có runAsNonRoot: true, seccompProfile: RuntimeDefault, và capabilities.drop: [ALL] để thỏa mãn PodSecurity Standard restricted được enforce trên tất cả namespace.

Services

ServiceDNSPortMục đích
nx-redis-headlessnx-redis-headless.nx-broker.svc.cluster.local6379, 16379Headless - DNS cấp pod cho các node cluster
nx-redisnx-redis.nx-broker.svc.cluster.local6379, 16379ClusterIP - load-balanced (discovery ban đầu)

Địa chỉ pod riêng lẻ được các backend service dùng:

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

Cấu hình Kết nối Backend

Các backend service kết nối ở cluster mode qua shared-config.yaml:

Cache
yaml
# Cache
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"

# BullMQ
APP_ENV_BULLMQ_REDIS_MODE: cluster
APP_ENV_BULLMQ_REDIS_CLUSTER_NODES: "nx-redis-0....:6379,nx-redis-1....:6379,nx-redis-2....:6379"

# WebSocket
APP_ENV_WEBSOCKET_REDIS_MODE: cluster
APP_ENV_WEBSOCKET_REDIS_CLUSTER_NODES: "nx-redis-0....:6379,nx-redis-1....:6379,nx-redis-2....:6379"

Redis Cluster vs. Logical Databases

Chế độ Redis Cluster không hỗ trợ SELECT \<db\> (logical database). Tất cả dữ liệu nằm ở DB 0, được phân vùng theo hash slot qua 3 master. Tiền tố key (ví dụ: cache:, bullmq:, ws:) được dùng thay cho các database riêng biệt.


Kafka

KRaft StatefulSet với SASL/SCRAM-SHA-512

Apache Kafka 4.1.1 chạy ở chế độ KRaft (không ZooKeeper) với 3 node kết hợp broker+controller. Backend service xác thực qua SASL/SCRAM-SHA-512 trên listener CLIENT.

Listeners

ListenerPortProtocolMục đích
INTERNAL29092PLAINTEXTGiao tiếp inter-broker (bảo mật bằng NetworkPolicy)
CLIENT9092SASL_PLAINTEXT (SCRAM-SHA-512)Backend service kết nối vào đây
CONTROLLER9093PLAINTEXTĐồng thuận quorum KRaft

StatefulSet

Init container sinh server.properties động (đặt node.id từ ordinal của pod), xóa lost+found khỏi PVC ext4 (Kafka không chấp nhận), và chạy kafka-storage.sh format lần đầu khởi động.

StatefulSet: nx-kafka
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nx-kafka
  namespace: nx-broker
spec:
  serviceName: nx-kafka-headless
  replicas: 3
  podManagementPolicy: Parallel
  template:
    spec:
      nodeSelector:
        node.kubernetes.io/pool: stateful
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      initContainers:
        - name: kafka-init-config
          image: apache/kafka:4.1.1
          # Generates server.properties, removes lost+found,
          # runs kafka-storage.sh format on first run
      containers:
        - name: kafka
          image: apache/kafka:4.1.1
          command:
            - /opt/kafka/bin/kafka-server-start.sh
            - /config/server.properties
          env:
            - name: KAFKA_OPTS
              value: "-Djava.security.auth.login.config=/etc/kafka-jaas/kafka_server_jaas.conf"
          ports:
            - containerPort: 29092
              name: internal
            - containerPort: 9092
              name: client
            - containerPort: 9093
              name: controller
          resources:
            requests:
              cpu: 500m
              memory: 1280Mi
            limits:
              cpu: "1.5"
              memory: 2Gi
          volumeMounts:
            - name: data
              mountPath: /var/lib/kafka/data
            - name: config
              mountPath: /config
            - name: jaas
              mountPath: /etc/kafka-jaas
              readOnly: true
      volumes:
        - name: config
          emptyDir: { sizeLimit: 8Mi }
        - name: jaas
          secret:
            secretName: nx-kafka-jaas
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: [ReadWriteOnce]
        storageClassName: csi-sc-vnpaycloud
        resources:
          requests:
            storage: 10Gi

Cấu hình Chính (từ server.properties được sinh ra)

properties
# Replication
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2
default.replication.factor=3
min.insync.replicas=2
num.partitions=3

# Retention
log.retention.hours=168          # 7 days
log.retention.bytes=1073741824   # 1 GB per partition
log.segment.bytes=1073741824

# Auto-create topics
auto.create.topics.enable=false

Khởi tạo User SASL/SCRAM

Một Job một lần tạo user SCRAM-SHA-512 nx.staging trên listener INTERNAL (vốn là PLAINTEXT và không yêu cầu SASL):

Job: nx-kafka-scram-init
yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: nx-kafka-scram-init
  namespace: nx-broker
spec:
  template:
    spec:
      containers:
        - name: scram-init
          image: apache/kafka:4.1.1
          command:
            - sh
            - -c
            - |
              BOOTSTRAP="nx-kafka-0.nx-kafka-headless.nx-broker.svc.cluster.local:29092"
              # Wait for Kafka, then:
              /opt/kafka/bin/kafka-configs.sh \
                --bootstrap-server "$BOOTSTRAP" \
                --alter \
                --add-config "SCRAM-SHA-512=[iterations=8192,password=${KAFKA_SASL_PASSWORD}]" \
                --entity-type users \
                --entity-name nx.staging

Cấu hình JAAS được lưu trong Secret nx-kafka-jaas và mount tại /etc/kafka-jaas/kafka_server_jaas.conf. Nó được load qua JVM argument KAFKA_OPTS.

Services

ServiceDNSPortsGhi chú
nx-kafka-headlessnx-kafka-headless.nx-broker.svc.cluster.local29092, 9092, 9093Headless, publishNotReadyAddresses: true (quan trọng cho KRaft bootstrap)
nx-kafkanx-kafka.nx-broker.svc.cluster.local29092, 9092ClusterIP - load-balanced

publishNotReadyAddresses

Headless service phải có publishNotReadyAddresses: true. Không có nó, các pod không thể discover nhau trong giai đoạn bootstrap KRaft ban đầu vì chúng chưa "ready".

Cấu hình Kết nối Backend

From shared-config.yaml
yaml
# From shared-config.yaml
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
# Password in nx-shared-secret: APP_ENV_KAFKA_SASL_PASSWORD

Typesense

Cụm Raft 3 node

Typesense chạy dưới dạng cụm Raft 3 node để đạt tính sẵn sàng cao. Ghi đi qua leader được bầu rồi nhân bản sang các follower; đọc phục vụ bởi node bất kỳ. Cụm chịu được 1 node chết (quorum = 2/3).

Typesense giữ toàn bộ dữ liệu trong RAMnhân bản đầy đủ sang mọi node - nó không shard, nên mỗi node phải đủ RAM chứa toàn bộ index. Index là phái sinh: nếu mất sạch thì re-index lại từ PostgreSQL qua CDC, nên cụm phục vụ tính sẵn sàng, không phải độ bền dữ liệu.

Thuộc tínhGiá trị
Số node3 (1 leader + 2 follower)
Chịu lỗi1 node (quorum 2/3)
Cổng API8108
Cổng peering (Raft)8107 - chỉ nội bộ, không publish
Khám phá peerfile nodes, mỗi node một dòng <peering-host>:8107:8108
Phục hồitự lành (leader stream lại cho node trống) hoặc re-index từ PostgreSQL

Khám phá peer dùng file nodes mount từ ConfigMap (nx-typesense-nodes) liệt kê FQDN của mọi pod. Service quản trị là headless (clusterIP: None, publishNotReadyAddresses: true) để mỗi pod có DNS ổn định cho peering Raft trước khi Ready. --reset-peers-on-error giúp cụm tự lành khi IP pod thay đổi. Các pod trải trên nhiều host bằng pod anti-affinity required (cần ≥3 node stateful).

StatefulSet: nx-typesense (3 replica)
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nx-typesense
  namespace: nx-search
spec:
  serviceName: nx-typesense          # headless governing service
  replicas: 3
  podManagementPolicy: Parallel
  template:
    spec:
      nodeSelector:
        node.kubernetes.io/pool: stateful
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app.kubernetes.io/name: typesense
              topologyKey: kubernetes.io/hostname
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: typesense
          image: typesense/typesense:30.1
          args:
            - --data-dir=/data
            - --api-port=8108
            - --peering-port=8107
            - --nodes=/etc/typesense/nodes
            - --api-key=$(TYPESENSE_API_KEY)
            - --enable-cors
            - --reset-peers-on-error
          ports:
            - containerPort: 8108
              name: http
            - containerPort: 8107
              name: peering
          env:
            - name: TYPESENSE_API_KEY
              valueFrom:
                secretKeyRef:
                  name: nx-typesense-secret
                  key: TYPESENSE_API_KEY
          resources:
            requests:
              cpu: 200m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 2Gi
          volumeMounts:
            - name: data
              mountPath: /data
            - name: nodes
              mountPath: /etc/typesense
              readOnly: true
      volumes:
        - name: nodes
          configMap:
            name: nx-typesense-nodes
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: [ReadWriteOnce]
        storageClassName: csi-sc-vnpaycloud
        resources:
          requests:
            storage: 5Gi

ConfigMap nx-typesense-nodes chứa danh sách peer Raft (một dòng, ngăn cách bằng dấu phẩy):

nx-typesense-0.nx-typesense.nx-search.svc.cluster.local:8107:8108,nx-typesense-1.nx-typesense.nx-search.svc.cluster.local:8107:8108,nx-typesense-2.nx-typesense.nx-search.svc.cluster.local:8107:8108

Service

ServiceDNSCổngGhi chú
nx-typesense (headless)nx-typesense-{0,1,2}.nx-typesense.nx-search.svc.cluster.local8108 (http), 8107 (peering)Quản trị StatefulSet; cấp DNS ổn định cho từng pod

Cấu hình client

Client liệt kê cả ba FQDN pod để client Typesense tự cân bằng tải và failover:

bash
APP_ENV_TYPESENSE_NODES="http:nx-typesense-0.nx-typesense.nx-search.svc.cluster.local:8108,http:nx-typesense-1.nx-typesense.nx-search.svc.cluster.local:8108,http:nx-typesense-2.nx-typesense.nx-search.svc.cluster.local:8108"

Develop (Docker Compose)

Develop phản chiếu cùng topology dưới dạng cụm Docker Compose 3 node (nx-typesense-1/2/3, cổng host 38118/38128/38138, peering 8107, dùng chung file nodes mount theo đường dẫn tuyệt đối, image typesense/typesense:31.0.rc3). Chạy trên một host nên chỉ có nhân bản Raft và rolling restart, không có HA khi mất host. Kiểm tra quorum: curl -s localhost:38118/debug -H 'x-typesense-api-key: <key>' trên từng node → đúng một {"state":1} (leader) + hai {"state":4} (follower).


Debezium

CDC Connector

Debezium chạy dưới dạng instance Kafka Connect trong nx-search, stream sự kiện change data capture (CDC) từ PostgreSQL đến các Kafka topic. Điều này cho phép cập nhật search index tới Typesense theo thời gian thực.

Deployment

Thuộc tínhGiá trị
Imagedebezium/connect:3.0.0.Final
Namespacenx-search
Replicas1
Port8083

Job nx-debezium-init đăng ký PostgreSQL source connector sau khi instance Debezium sẵn sàng.

Service

ServiceDNSPort
nx-debeziumnx-debezium.nx-search.svc.cluster.local8083

Network Policies

Tất cả namespace đều có default-deny-all (cả ingress và egress) với các rule allow rõ ràng.

Mặc định Deny

Áp dụng cho: nx-backend, nx-app, nx-persistent, nx-broker, nx-search, nx-internal.

NetworkPolicy: default-deny-all
yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

Rule Allow

PolicyNamespaceHướngFrom/ToPorts
allow-dnsCả 6 namespaceEgresskube-system53 (UDP+TCP)
allow-egress-to-postgresqlnx-backendEgressnx-persistent5432
allow-egress-to-brokernx-backendEgressnx-broker6379, 16379, 9092, 29092
allow-egress-to-searchnx-backendEgressnx-search8108
allow-egress-to-backendnx-backendEgressnx-backend3000 (inter-service)
allow-egress-to-s3nx-backendEgressExternal (0.0.0.0/0 excl. private)443 (chỉ commerce, ledger)
allow-from-backendnx-brokerIngressnx-backend + nx-broker6379, 16379, 9092, 29092, 9093
allow-broker-internalnx-brokerEgressnx-broker6379, 16379, 9092, 29092, 9093
allow-pg-replicationnx-persistentCả haiPod PG trong nx-persistent5432
allow-pgbouncer-to-primarynx-persistentEgressPgBouncer → PG primary5432
allow-pgbouncer-from-backendnx-persistentIngressnx-backend → PgBouncer6432
allow-from-backendnx-persistentIngressnx-backend5432, 6432
allow-from-backendnx-searchIngressnx-backend8108
allow-search-internalnx-searchCả haiTrong nx-search8083, 8108
egress-nx-searchnx-searchEgressnx-persistent, nx-broker5432, 9092

Sao lưu & Khôi phục

ServicePhương phápTần suấtRetentionKhôi phục
PostgreSQLStreaming replication (replica)WAL liên tụcReplica onlinePromote replica
RedisAOF persistence (appendonly yes, appendfsync everysec)Liên tụcTrên đĩaKhởi động lại từ AOF
KafkaLog retentionTự động7 ngày / 1GB mỗi partitionReplay từ log được giữ lại
TypesenseRe-index từ PostgreSQLTheo yêu cầuN/ARe-index

Chiến lược Dữ liệu Staging

Dữ liệu staging có thể được tạo lại từ migration seed. Bộ đôi primary+replica cung cấp sẵn sàng đọc và warm standby, nhưng không có backup off-cluster lên S3 ở mức staging. Đối với backup cấp production (CNPG với barman, WAL archiving tới S3, PITR), xem mục Production bên dưới.


Mở rộng Volume

Tất cả PVC dùng StorageClass csi-sc-vnpaycloudallowVolumeExpansion: true.

Cho StatefulSet (Redis, Kafka, Typesense, PostgreSQL)

bash
# Example: expand Redis PVC from 5Gi to 10Gi
# From infrastructure/deployments/staging/ directory:
./kc patch pvc data-nx-redis-0 -n nx-broker -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
./kc patch pvc data-nx-redis-1 -n nx-broker -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
./kc patch pvc data-nx-redis-2 -n nx-broker -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'

# From project root:
infrastructure/deployments/staging/kc patch pvc data-nx-redis-0 -n nx-broker -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'

Driver Cinder CSI hỗ trợ mở rộng online - không cần restart pod.


Tóm tắt Tài nguyên

Staging (stateful node pool)

WorkloadPodsCPU reqCPU limMem reqMem limStorage
PG Primary1500m21Gi4Gi20Gi
PG Replica1250m1512Mi2Gi20Gi
PgBouncer1100m500m256Mi512Mi-
Redis (x3)3300m31.125Gi1.5Gi15Gi
Kafka (x3)31.54.53.75Gi6Gi30Gi
Typesense (x3)3600m31.5Gi6Gi15Gi
Debezium1250m1512Mi2Gi-
Tổng133.5158.67Gi22Gi100Gi

Production (Dự kiến)

Chưa triển khai

Tầng dữ liệu production đã được lên kế hoạch nhưng chưa triển khai. Mục sau mô tả kiến trúc mục tiêu. Manifest thực tế sẽ được tạo khi hạ tầng production được provisioned.

ComponentSetup Production Dự kiến
PostgreSQLCloudNativePG operator (1 primary + 2 replica), PgBouncer sidecar, WAL archiving liên tục tới S3, retention 30 ngày, PITR
RedisRedis Sentinel hoặc Cluster với replica (1 master + 2 replica mỗi shard)
KafkaStrimzi operator (3 broker, rack-aware, CRD KafkaTopic/KafkaUser khai báo, Cruise Control)
TypesenseCluster Raft 3 node với bầu leader tự động
FailoverTự động: <30s cho PG (CNPG), <15s cho Redis (Sentinel), sẵn có cho Kafka (ISR)
BackupsWAL liên tục + base hàng ngày tới S3 (PG), AOF + RDB tới S3 (Redis), log retention + replication (Kafka)

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.