feat: Phase 2 close-out — multi-branch management, production K8s, revenue dashboard UI, responsive POS
Backend:
- Multi-branch shop management: SetDefaultShop, TransferShop commands, GetMerchantShops paginated query
- Shop aggregate: IsDefault field, SetAsDefault/ClearDefault/TransferOwnership behavior methods
- 2 new domain events: ShopSetAsDefaultDomainEvent, ShopTransferredDomainEvent
Frontend:
- Revenue Dashboard (MudChart line/donut/bar, 4 KPI cards, top products table)
- Staff Performance (sortable table, color-coded completion rates, CSV export)
- Customer QR Menu page (/menu/{ShopId}, mobile-first, Vietnamese labels)
- QR Code Generator admin page (batch generate, print-all, per-table QR)
- Responsive POS layout (collapsible sidebar, slide-out order drawer, touch-friendly CSS)
- ResponsiveOrderPanel component (desktop inline / tablet drawer / mobile overlay)
Infrastructure:
- Production K8s manifests: 8 services (3 replicas, 512Mi-1Gi, HPA min3/max10), Redis with persistence
- Production ingress: api.goodgo.vn, cert-manager TLS, rate-limit middleware
- Deploy script: pre-flight checks, dry-run, single-service deploy, rollback support
- CI/CD: deploy-production.yml with environment approval, commit SHA tags
- Prometheus full scrape config (11 targets), docker-compose observability stack
- Production deployment checklist (80+ items)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1241,31 +1241,87 @@ services:
|
||||
# restart: unless-stopped
|
||||
|
||||
# Prometheus - Metrics Collection
|
||||
# prometheus:
|
||||
# image: prom/prometheus:latest
|
||||
# container_name: prometheus-local
|
||||
# ports:
|
||||
# - "9090:9090"
|
||||
# volumes:
|
||||
# - ../../infra/observability/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||
# - prometheus_data:/prometheus
|
||||
# networks:
|
||||
# - microservices-network
|
||||
# restart: unless-stopped
|
||||
prometheus:
|
||||
image: prom/prometheus:v2.51.0
|
||||
container_name: prometheus-local
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--storage.tsdb.retention.time=15d'
|
||||
- '--web.console.libraries=/etc/prometheus/console_libraries'
|
||||
- '--web.console.templates=/etc/prometheus/consoles'
|
||||
- '--web.enable-lifecycle'
|
||||
ports:
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ../../infra/observability/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||
- ../../infra/observability/prometheus/rules:/etc/prometheus/rules:ro
|
||||
- ../../infra/observability/prometheus/alert-rules.yml:/etc/prometheus/alert-rules.yml:ro
|
||||
- prometheus_data:/prometheus
|
||||
networks:
|
||||
- microservices-network
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:9090/-/healthy || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# Grafana - Metrics Visualization
|
||||
# grafana:
|
||||
# image: grafana/grafana:latest
|
||||
# container_name: grafana-local
|
||||
# ports:
|
||||
# - "3001:3000"
|
||||
# environment:
|
||||
# - GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
# volumes:
|
||||
# - grafana_data:/var/lib/grafana
|
||||
# networks:
|
||||
# - microservices-network
|
||||
# restart: unless-stopped
|
||||
# Grafana - Metrics Visualization & Dashboards
|
||||
grafana:
|
||||
image: grafana/grafana:10.3.1
|
||||
container_name: grafana-local
|
||||
ports:
|
||||
- "3002:3000"
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_USER=admin
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
- GF_USERS_ALLOW_SIGN_UP=false
|
||||
- GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/etc/grafana/provisioning/dashboards/goodgo-overview.json
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
- ../../infra/observability/grafana/datasources:/etc/grafana/provisioning/datasources:ro
|
||||
- ../../infra/observability/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
|
||||
networks:
|
||||
- microservices-network
|
||||
depends_on:
|
||||
prometheus:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# Loki - Log Aggregation
|
||||
loki:
|
||||
image: grafana/loki:2.9.4
|
||||
container_name: loki-local
|
||||
ports:
|
||||
- "3100:3100"
|
||||
command: -config.file=/etc/loki/loki-config.yml
|
||||
volumes:
|
||||
- ../../infra/observability/loki/loki-config.yml:/etc/loki/loki-config.yml:ro
|
||||
- loki_data:/loki
|
||||
networks:
|
||||
- microservices-network
|
||||
restart: unless-stopped
|
||||
|
||||
# Promtail - Log Collector (ships container logs to Loki)
|
||||
promtail:
|
||||
image: grafana/promtail:2.9.4
|
||||
container_name: promtail-local
|
||||
volumes:
|
||||
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ../../infra/observability/promtail/promtail-config.yml:/etc/promtail/promtail-config.yml:ro
|
||||
command: -config.file=/etc/promtail/promtail-config.yml
|
||||
networks:
|
||||
- microservices-network
|
||||
depends_on:
|
||||
- loki
|
||||
restart: unless-stopped
|
||||
|
||||
# ===========================================================================
|
||||
# FRONTEND APPS
|
||||
@@ -1340,6 +1396,12 @@ volumes:
|
||||
driver: local
|
||||
rabbitmq_data:
|
||||
driver: local
|
||||
prometheus_data:
|
||||
driver: local
|
||||
grafana_data:
|
||||
driver: local
|
||||
loki_data:
|
||||
driver: local
|
||||
# =============================================================================
|
||||
# NETWORKS
|
||||
# =============================================================================
|
||||
|
||||
136
deployments/production/kubernetes/booking-service.yaml
Normal file
136
deployments/production/kubernetes/booking-service.yaml
Normal file
@@ -0,0 +1,136 @@
|
||||
# EN: Booking Service - Booking & Reservation System
|
||||
# VI: Booking Service - He thong Dat cho & Dat lich
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: booking-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: booking-service
|
||||
environment: production
|
||||
platform: goodgo
|
||||
tier: backend
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: booking-service
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: booking-service
|
||||
environment: production
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- booking-service
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- name: booking-service
|
||||
image: goodgo/booking-service-net:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: goodgo-config
|
||||
- secretRef:
|
||||
name: goodgo-secrets
|
||||
env:
|
||||
- name: ConnectionStrings__DefaultConnection
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: BOOKING_DATABASE_URL
|
||||
- name: IamService__ServiceName
|
||||
value: "booking-service"
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
failureThreshold: 12
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: booking-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: booking-service
|
||||
environment: production
|
||||
spec:
|
||||
selector:
|
||||
app: booking-service
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: booking-service-hpa
|
||||
namespace: production
|
||||
labels:
|
||||
app: booking-service
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: booking-service
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
136
deployments/production/kubernetes/catalog-service.yaml
Normal file
136
deployments/production/kubernetes/catalog-service.yaml
Normal file
@@ -0,0 +1,136 @@
|
||||
# EN: Catalog Service - Polymorphic Product & Category Management
|
||||
# VI: Catalog Service - Quan ly San pham & Danh muc da hinh
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: catalog-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: catalog-service
|
||||
environment: production
|
||||
platform: goodgo
|
||||
tier: backend
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: catalog-service
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: catalog-service
|
||||
environment: production
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- catalog-service
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- name: catalog-service
|
||||
image: goodgo/catalog-service-net:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: goodgo-config
|
||||
- secretRef:
|
||||
name: goodgo-secrets
|
||||
env:
|
||||
- name: ConnectionStrings__DefaultConnection
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: CATALOG_DATABASE_URL
|
||||
- name: IamService__ServiceName
|
||||
value: "catalog-service"
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
failureThreshold: 12
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: catalog-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: catalog-service
|
||||
environment: production
|
||||
spec:
|
||||
selector:
|
||||
app: catalog-service
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: catalog-service-hpa
|
||||
namespace: production
|
||||
labels:
|
||||
app: catalog-service
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: catalog-service
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
58
deployments/production/kubernetes/configmap.yaml
Normal file
58
deployments/production/kubernetes/configmap.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
# EN: Shared configuration for all GoodGo production services
|
||||
# VI: Cau hinh chung cho tat ca cac service production cua GoodGo
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: goodgo-config
|
||||
namespace: production
|
||||
labels:
|
||||
environment: production
|
||||
platform: goodgo
|
||||
data:
|
||||
# EN: ASP.NET Core Configuration
|
||||
# VI: Cau hinh ASP.NET Core
|
||||
ASPNETCORE_ENVIRONMENT: "Production"
|
||||
ASPNETCORE_URLS: "http://+:8080"
|
||||
|
||||
# EN: JWT Configuration (shared across all services)
|
||||
# VI: Cau hinh JWT (dung chung cho tat ca services)
|
||||
Jwt__Authority: "http://iam-service:8080"
|
||||
Jwt__Audience: "goodgo-api"
|
||||
Jwt__RequireHttpsMetadata: "true"
|
||||
|
||||
# EN: Service Discovery URLs (K8s DNS: {service-name}.production.svc.cluster.local)
|
||||
# VI: URL tim kiem service (K8s DNS: {service-name}.production.svc.cluster.local)
|
||||
IamService__BaseUrl: "http://iam-service:8080"
|
||||
MerchantService__BaseUrl: "http://merchant-service:8080"
|
||||
CatalogService__BaseUrl: "http://catalog-service:8080"
|
||||
OrderService__BaseUrl: "http://order-service:8080"
|
||||
InventoryService__BaseUrl: "http://inventory-service:8080"
|
||||
WalletService__BaseUrl: "http://wallet-service:8080"
|
||||
StorageService__BaseUrl: "http://storage-service:8080"
|
||||
FnbEngine__BaseUrl: "http://fnb-engine:8080"
|
||||
BookingService__BaseUrl: "http://booking-service:8080"
|
||||
|
||||
# EN: Redis Configuration
|
||||
# VI: Cau hinh Redis
|
||||
Redis__Host: "redis"
|
||||
Redis__Port: "6379"
|
||||
Redis__Database: "0"
|
||||
|
||||
# EN: CORS Configuration
|
||||
# VI: Cau hinh CORS
|
||||
Cors__AllowedOrigins: "https://pos.goodgo.vn,https://goodgo.vn,https://admin.goodgo.vn"
|
||||
|
||||
# EN: Logging (stricter for production)
|
||||
# VI: Ghi log (nghiem ngat hon cho production)
|
||||
Serilog__MinimumLevel__Default: "Warning"
|
||||
Serilog__MinimumLevel__Override__Microsoft: "Error"
|
||||
Serilog__MinimumLevel__Override__System: "Error"
|
||||
|
||||
# EN: Feature Flags
|
||||
# VI: Tinh nang bat/tat
|
||||
Features__SwaggerEnabled: "false"
|
||||
Features__DetailedErrors: "false"
|
||||
|
||||
# EN: API Version
|
||||
# VI: Phien ban API
|
||||
API_VERSION: "v1"
|
||||
143
deployments/production/kubernetes/fnb-engine.yaml
Normal file
143
deployments/production/kubernetes/fnb-engine.yaml
Normal file
@@ -0,0 +1,143 @@
|
||||
# EN: FnB Engine - Table, Session, Kitchen & Reservation Management (SignalR for Kitchen Display)
|
||||
# VI: FnB Engine - Quan ly Ban, Phien, Bep & Dat ban (SignalR cho Man hinh bep)
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: fnb-engine
|
||||
namespace: production
|
||||
labels:
|
||||
app: fnb-engine
|
||||
environment: production
|
||||
platform: goodgo
|
||||
tier: backend
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: fnb-engine
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: fnb-engine
|
||||
environment: production
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- fnb-engine
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- name: fnb-engine
|
||||
image: goodgo/fnb-engine-net:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: goodgo-config
|
||||
- secretRef:
|
||||
name: goodgo-secrets
|
||||
env:
|
||||
- name: ConnectionStrings__DefaultConnection
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: FNB_DATABASE_URL
|
||||
- name: IamService__ServiceName
|
||||
value: "fnb-engine"
|
||||
# EN: Redis for SignalR backplane (Kitchen Display)
|
||||
# VI: Redis cho SignalR backplane (Man hinh bep)
|
||||
- name: ConnectionStrings__Redis
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: ConnectionStrings__Redis
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
failureThreshold: 12
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: fnb-engine
|
||||
namespace: production
|
||||
labels:
|
||||
app: fnb-engine
|
||||
environment: production
|
||||
spec:
|
||||
selector:
|
||||
app: fnb-engine
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: fnb-engine-hpa
|
||||
namespace: production
|
||||
labels:
|
||||
app: fnb-engine
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: fnb-engine
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
@@ -1,15 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: iam-service-config
|
||||
namespace: production
|
||||
data:
|
||||
NODE_ENV: "production"
|
||||
PORT: "5001"
|
||||
API_VERSION: "v1"
|
||||
CORS_ORIGIN: "https://goodgo.vn"
|
||||
LOG_LEVEL: "warn"
|
||||
SERVICE_NAME: "iam-service"
|
||||
TRACING_ENABLED: "true"
|
||||
# Note: DATABASE_URL is stored in secrets (iam-service-secrets)
|
||||
# DATABASE_URL should point to Neon production branch
|
||||
@@ -1,29 +1,72 @@
|
||||
# EN: IAM Service - Identity & Access Management (Duende IdentityServer, JWT, RBAC, MFA)
|
||||
# VI: IAM Service - Quan ly Danh tinh & Truy cap (Duende IdentityServer, JWT, RBAC, MFA)
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: iam-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: iam-service
|
||||
environment: production
|
||||
platform: goodgo
|
||||
tier: backend
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: iam-service
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: iam-service
|
||||
environment: production
|
||||
spec:
|
||||
# EN: Pod anti-affinity to spread replicas across nodes
|
||||
# VI: Anti-affinity de phan bo replica tren nhieu node
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- iam-service
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- name: iam-service
|
||||
image: goodgo/iam-service:latest
|
||||
image: goodgo/iam-service-net:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5001
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: iam-service-config
|
||||
name: goodgo-config
|
||||
- secretRef:
|
||||
name: iam-service-secrets
|
||||
name: goodgo-secrets
|
||||
env:
|
||||
# EN: Override service-specific database URL
|
||||
# VI: Override URL database rieng cho service
|
||||
- name: ConnectionStrings__DefaultConnection
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: IAM_DATABASE_URL
|
||||
- name: IamService__ServiceName
|
||||
value: "iam-service"
|
||||
- name: IdentityServer__IssuerUri
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: IdentityServer__IssuerUri
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
@@ -34,7 +77,7 @@ spec:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 5001
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
@@ -42,33 +85,44 @@ spec:
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 5001
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
failureThreshold: 12
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: iam-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: iam-service
|
||||
environment: production
|
||||
spec:
|
||||
selector:
|
||||
app: iam-service
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5001
|
||||
targetPort: 5001
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: iam-service-hpa
|
||||
namespace: production
|
||||
labels:
|
||||
app: iam-service
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
|
||||
@@ -1,74 +1,289 @@
|
||||
# EN: Traefik Ingress for GoodGo Production - API Gateway routing
|
||||
# VI: Traefik Ingress cho GoodGo Production - Dinh tuyen API Gateway
|
||||
#
|
||||
# Routes match infra/traefik/dynamic/routes.yml for consistency
|
||||
# Host: api.goodgo.vn (API), pos.goodgo.vn (POS Frontend)
|
||||
|
||||
# =============================================================================
|
||||
# API Ingress - Backend services
|
||||
# =============================================================================
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api-ingress
|
||||
namespace: production
|
||||
labels:
|
||||
environment: production
|
||||
platform: goodgo
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/rule-type: PathPrefix
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
# EN: Traefik Ingress class
|
||||
# VI: Ingress class cua Traefik
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
# EN: Rate limiting and security middlewares
|
||||
# VI: Middleware gioi han toc do va bao mat
|
||||
traefik.ingress.kubernetes.io/router.middlewares: production-cors@kubernetescrd,production-secure-headers@kubernetescrd,production-rate-limit@kubernetescrd
|
||||
# EN: cert-manager TLS (production issuer)
|
||||
# VI: TLS bang cert-manager (issuer production)
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- api.goodgo.vn
|
||||
secretName: api-tls-cert
|
||||
secretName: api-production-tls
|
||||
rules:
|
||||
- host: api.goodgo.vn
|
||||
http:
|
||||
paths:
|
||||
# ===== IAM Service =====
|
||||
- path: /api/v1/auth
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 5001
|
||||
number: 8080
|
||||
- path: /api/v1/users
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 5001
|
||||
number: 8080
|
||||
- path: /api/v1/identity
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 5001
|
||||
number: 8080
|
||||
- path: /api/v1/access
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 5001
|
||||
number: 8080
|
||||
- path: /api/v1/governance
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 5001
|
||||
number: 8080
|
||||
- path: /api/v1/rbac
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 5001
|
||||
number: 8080
|
||||
- path: /api/v1/mfa
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 5001
|
||||
number: 8080
|
||||
- path: /api/v1/sessions
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 5001
|
||||
number: 8080
|
||||
# EN: IdentityServer OIDC endpoints
|
||||
# VI: IdentityServer OIDC endpoints
|
||||
- path: /connect
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 8080
|
||||
- path: /.well-known
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: iam-service
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
# ===== Merchant Service =====
|
||||
- path: /api/v1/merchants
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: merchant-service
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api/v1/shops
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: merchant-service
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api/v1/subscriptions
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: merchant-service
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
# ===== Order Service =====
|
||||
- path: /api/v1/orders
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: order-service
|
||||
port:
|
||||
number: 8080
|
||||
# EN: POS/KDS SignalR Hub (WebSocket)
|
||||
# VI: POS/KDS SignalR Hub (WebSocket)
|
||||
- path: /hubs/pos
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: order-service
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
# ===== FnB Engine =====
|
||||
- path: /api/v1/kitchen
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: fnb-engine
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api/v1/fnb
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: fnb-engine
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api/v1/tables
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: fnb-engine
|
||||
port:
|
||||
number: 8080
|
||||
# EN: Kitchen Display SignalR Hub
|
||||
# VI: SignalR Hub Man hinh bep
|
||||
- path: /hubs/kitchen
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: fnb-engine
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
# ===== Inventory Service =====
|
||||
- path: /api/v1/inventory
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: inventory-service
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api/v1/stock
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: inventory-service
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
# ===== Wallet Service =====
|
||||
- path: /api/v1/wallets
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: wallet-service
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api/v1/points
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: wallet-service
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api/v1/payments
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: wallet-service
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
# ===== Catalog Service =====
|
||||
- path: /api/v1/products
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: catalog-service
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api/v1/categories
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: catalog-service
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
# ===== Booking Service =====
|
||||
- path: /api/v1/bookings
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: booking-service
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api/v1/reservations
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: booking-service
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
---
|
||||
# =============================================================================
|
||||
# POS Frontend Ingress
|
||||
# =============================================================================
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: pos-web-ingress
|
||||
namespace: production
|
||||
labels:
|
||||
environment: production
|
||||
platform: goodgo
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- pos.goodgo.vn
|
||||
secretName: pos-production-tls
|
||||
rules:
|
||||
- host: pos.goodgo.vn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: pos-web
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
136
deployments/production/kubernetes/inventory-service.yaml
Normal file
136
deployments/production/kubernetes/inventory-service.yaml
Normal file
@@ -0,0 +1,136 @@
|
||||
# EN: Inventory Service - Stock Management & Deduction (Retail + FnB)
|
||||
# VI: Inventory Service - Quan ly Ton kho & Tru kho (Retail + FnB)
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: inventory-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: inventory-service
|
||||
environment: production
|
||||
platform: goodgo
|
||||
tier: backend
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: inventory-service
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: inventory-service
|
||||
environment: production
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- inventory-service
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- name: inventory-service
|
||||
image: goodgo/inventory-service-net:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: goodgo-config
|
||||
- secretRef:
|
||||
name: goodgo-secrets
|
||||
env:
|
||||
- name: ConnectionStrings__DefaultConnection
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: INVENTORY_DATABASE_URL
|
||||
- name: IamService__ServiceName
|
||||
value: "inventory-service"
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
failureThreshold: 12
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: inventory-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: inventory-service
|
||||
environment: production
|
||||
spec:
|
||||
selector:
|
||||
app: inventory-service
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: inventory-service-hpa
|
||||
namespace: production
|
||||
labels:
|
||||
app: inventory-service
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: inventory-service
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
136
deployments/production/kubernetes/merchant-service.yaml
Normal file
136
deployments/production/kubernetes/merchant-service.yaml
Normal file
@@ -0,0 +1,136 @@
|
||||
# EN: Merchant Service - Merchant & Shop Management
|
||||
# VI: Merchant Service - Quan ly Merchant & Shop
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: merchant-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: merchant-service
|
||||
environment: production
|
||||
platform: goodgo
|
||||
tier: backend
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: merchant-service
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: merchant-service
|
||||
environment: production
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- merchant-service
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- name: merchant-service
|
||||
image: goodgo/merchant-service-net:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: goodgo-config
|
||||
- secretRef:
|
||||
name: goodgo-secrets
|
||||
env:
|
||||
- name: ConnectionStrings__DefaultConnection
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: MERCHANT_DATABASE_URL
|
||||
- name: IamService__ServiceName
|
||||
value: "merchant-service"
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
failureThreshold: 12
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: merchant-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: merchant-service
|
||||
environment: production
|
||||
spec:
|
||||
selector:
|
||||
app: merchant-service
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: merchant-service-hpa
|
||||
namespace: production
|
||||
labels:
|
||||
app: merchant-service
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: merchant-service
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
10
deployments/production/kubernetes/namespace.yaml
Normal file
10
deployments/production/kubernetes/namespace.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
# EN: Production namespace for GoodGo Platform
|
||||
# VI: Namespace production cho GoodGo Platform
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: production
|
||||
labels:
|
||||
environment: production
|
||||
platform: goodgo
|
||||
managed-by: kubectl
|
||||
144
deployments/production/kubernetes/order-service.yaml
Normal file
144
deployments/production/kubernetes/order-service.yaml
Normal file
@@ -0,0 +1,144 @@
|
||||
# EN: Order Service - Order Processing & POS API (SignalR WebSocket for POS/KDS)
|
||||
# VI: Order Service - Xu ly Order & POS API (SignalR WebSocket cho POS/KDS)
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: order-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: order-service
|
||||
environment: production
|
||||
platform: goodgo
|
||||
tier: backend
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: order-service
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: order-service
|
||||
environment: production
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- order-service
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- name: order-service
|
||||
image: goodgo/order-service-net:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: goodgo-config
|
||||
- secretRef:
|
||||
name: goodgo-secrets
|
||||
env:
|
||||
- name: ConnectionStrings__DefaultConnection
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: ORDER_DATABASE_URL
|
||||
- name: IamService__ServiceName
|
||||
value: "order-service"
|
||||
# EN: Inter-service communication
|
||||
# VI: Giao tiep giua cac service
|
||||
- name: CatalogService__BaseUrl
|
||||
value: "http://catalog-service:8080"
|
||||
- name: InventoryService__BaseUrl
|
||||
value: "http://inventory-service:8080"
|
||||
- name: WalletService__BaseUrl
|
||||
value: "http://wallet-service:8080"
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
failureThreshold: 12
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: order-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: order-service
|
||||
environment: production
|
||||
spec:
|
||||
selector:
|
||||
app: order-service
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: order-service-hpa
|
||||
namespace: production
|
||||
labels:
|
||||
app: order-service
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: order-service
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
122
deployments/production/kubernetes/redis.yaml
Normal file
122
deployments/production/kubernetes/redis.yaml
Normal file
@@ -0,0 +1,122 @@
|
||||
# EN: Redis - Cache & SignalR Backplane for production
|
||||
# VI: Redis - Cache & SignalR Backplane cho production
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: production
|
||||
labels:
|
||||
app: redis
|
||||
environment: production
|
||||
platform: goodgo
|
||||
tier: infrastructure
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
environment: production
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7-alpine
|
||||
command:
|
||||
- redis-server
|
||||
- "--requirepass"
|
||||
- "$(REDIS_PASSWORD)"
|
||||
- "--maxmemory"
|
||||
- "512mb"
|
||||
- "--maxmemory-policy"
|
||||
- "allkeys-lru"
|
||||
- "--appendonly"
|
||||
- "yes"
|
||||
- "--appendfsync"
|
||||
- "everysec"
|
||||
- "--save"
|
||||
- "900 1"
|
||||
- "--save"
|
||||
- "300 10"
|
||||
- "--save"
|
||||
- "60 10000"
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: Redis__Password
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- redis-cli
|
||||
- -a
|
||||
- "$(REDIS_PASSWORD)"
|
||||
- ping
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- redis-cli
|
||||
- -a
|
||||
- "$(REDIS_PASSWORD)"
|
||||
- ping
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
volumeMounts:
|
||||
- name: redis-data
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: redis-data
|
||||
persistentVolumeClaim:
|
||||
claimName: redis-pvc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: production
|
||||
labels:
|
||||
app: redis
|
||||
environment: production
|
||||
spec:
|
||||
selector:
|
||||
app: redis
|
||||
ports:
|
||||
- name: redis
|
||||
protocol: TCP
|
||||
port: 6379
|
||||
targetPort: 6379
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: redis-pvc
|
||||
namespace: production
|
||||
labels:
|
||||
app: redis
|
||||
environment: production
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
@@ -1,34 +1,64 @@
|
||||
# Kubernetes Secrets Template for Production
|
||||
# DO NOT commit actual secrets to Git
|
||||
# Use this as a template to create secrets
|
||||
|
||||
# Create secret using kubectl:
|
||||
# kubectl create secret generic iam-service-secrets \
|
||||
# --from-literal=database-url='postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=jwt-secret='your-production-jwt-secret-min-32-chars' \
|
||||
# --from-literal=jwt-refresh-secret='your-production-refresh-secret-min-32-chars' \
|
||||
# --from-literal=redis-password='' \
|
||||
# EN: Kubernetes Secrets Template for GoodGo Production
|
||||
# VI: Template Secrets Kubernetes cho GoodGo Production
|
||||
#
|
||||
# DO NOT commit actual secrets to Git.
|
||||
# Use this as a template to create secrets via kubectl or sealed-secrets.
|
||||
#
|
||||
# =============================================================================
|
||||
# Option 1: Create secrets using kubectl (manual)
|
||||
# =============================================================================
|
||||
#
|
||||
# kubectl create secret generic goodgo-secrets \
|
||||
# --from-literal=Jwt__Secret='your-production-jwt-secret-min-64-chars-strong-random' \
|
||||
# --from-literal=Jwt__RefreshSecret='your-production-refresh-secret-min-64-chars-strong-random' \
|
||||
# --from-literal=IdentityServer__IssuerUri='https://api.goodgo.vn' \
|
||||
# --from-literal=IAM_DATABASE_URL='postgresql://user:pass@ep-xxx.region.neon.tech/iam_production?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=MERCHANT_DATABASE_URL='postgresql://user:pass@ep-xxx.region.neon.tech/merchant_production?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=ORDER_DATABASE_URL='postgresql://user:pass@ep-xxx.region.neon.tech/order_production?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=FNB_DATABASE_URL='postgresql://user:pass@ep-xxx.region.neon.tech/fnb_production?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=INVENTORY_DATABASE_URL='postgresql://user:pass@ep-xxx.region.neon.tech/inventory_production?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=WALLET_DATABASE_URL='postgresql://user:pass@ep-xxx.region.neon.tech/wallet_production?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=CATALOG_DATABASE_URL='postgresql://user:pass@ep-xxx.region.neon.tech/catalog_production?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=BOOKING_DATABASE_URL='postgresql://user:pass@ep-xxx.region.neon.tech/booking_production?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=Redis__Password='your-strong-redis-password' \
|
||||
# --from-literal=ConnectionStrings__Redis='redis:6379,password=your-strong-redis-password,abortConnect=false' \
|
||||
# --from-literal=Storage__MinIO__Endpoint='minio.goodgo.vn' \
|
||||
# --from-literal=Storage__MinIO__AccessKey='your-minio-access-key' \
|
||||
# --from-literal=Storage__MinIO__SecretKey='your-minio-secret-key' \
|
||||
# --from-literal=RabbitMQ__Host='rabbitmq' \
|
||||
# --from-literal=RabbitMQ__Username='goodgo' \
|
||||
# --from-literal=RabbitMQ__Password='your-strong-rabbitmq-password' \
|
||||
# -n production
|
||||
|
||||
# Or use GitHub Secrets in CI/CD:
|
||||
# - NEON_DATABASE_URL_PRODUCTION
|
||||
# - JWT_SECRET_PRODUCTION
|
||||
# - JWT_REFRESH_SECRET_PRODUCTION
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: iam-service-secrets
|
||||
namespace: production
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Neon Database URL (Production branch)
|
||||
# Format: postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
database-url: "postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true"
|
||||
|
||||
# JWT Secrets (use strong random strings, min 32 characters)
|
||||
jwt-secret: "your-production-jwt-secret-min-32-chars"
|
||||
jwt-refresh-secret: "your-production-refresh-secret-min-32-chars"
|
||||
|
||||
# Redis (if password protected)
|
||||
redis-password: ""
|
||||
#
|
||||
# =============================================================================
|
||||
# Option 2: Use GitHub Secrets in CI/CD (for automated deployments)
|
||||
# =============================================================================
|
||||
#
|
||||
# Required GitHub Secrets:
|
||||
# - KUBECONFIG_PRODUCTION (base64 encoded kubeconfig)
|
||||
# - DOCKER_USERNAME / DOCKER_PASSWORD
|
||||
# - NEON_IAM_DATABASE_URL_PRODUCTION
|
||||
# - NEON_MERCHANT_DATABASE_URL_PRODUCTION
|
||||
# - NEON_ORDER_DATABASE_URL_PRODUCTION
|
||||
# - NEON_FNB_DATABASE_URL_PRODUCTION
|
||||
# - NEON_INVENTORY_DATABASE_URL_PRODUCTION
|
||||
# - NEON_WALLET_DATABASE_URL_PRODUCTION
|
||||
# - NEON_CATALOG_DATABASE_URL_PRODUCTION
|
||||
# - NEON_BOOKING_DATABASE_URL_PRODUCTION
|
||||
# - JWT_SECRET_PRODUCTION
|
||||
# - JWT_REFRESH_SECRET_PRODUCTION
|
||||
# - REDIS_PASSWORD_PRODUCTION
|
||||
# - MINIO_ACCESS_KEY_PRODUCTION
|
||||
# - MINIO_SECRET_KEY_PRODUCTION
|
||||
# - RABBITMQ_PASSWORD_PRODUCTION
|
||||
#
|
||||
# =============================================================================
|
||||
# Option 3: Use sealed-secrets or external-secrets operator (RECOMMENDED for production)
|
||||
# =============================================================================
|
||||
#
|
||||
# Install sealed-secrets controller:
|
||||
# kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.5/controller.yaml
|
||||
#
|
||||
# Create sealed secret:
|
||||
# kubeseal --format yaml < secret.yaml > sealed-secret.yaml
|
||||
# kubectl apply -f sealed-secret.yaml
|
||||
|
||||
136
deployments/production/kubernetes/wallet-service.yaml
Normal file
136
deployments/production/kubernetes/wallet-service.yaml
Normal file
@@ -0,0 +1,136 @@
|
||||
# EN: Wallet Service - Wallet & Payment Management
|
||||
# VI: Wallet Service - Quan ly Vi & Thanh toan
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: wallet-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: wallet-service
|
||||
environment: production
|
||||
platform: goodgo
|
||||
tier: backend
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: wallet-service
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: wallet-service
|
||||
environment: production
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- wallet-service
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- name: wallet-service
|
||||
image: goodgo/wallet-service-net:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: goodgo-config
|
||||
- secretRef:
|
||||
name: goodgo-secrets
|
||||
env:
|
||||
- name: ConnectionStrings__DefaultConnection
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: goodgo-secrets
|
||||
key: WALLET_DATABASE_URL
|
||||
- name: IamService__ServiceName
|
||||
value: "wallet-service"
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
failureThreshold: 12
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: wallet-service
|
||||
namespace: production
|
||||
labels:
|
||||
app: wallet-service
|
||||
environment: production
|
||||
spec:
|
||||
selector:
|
||||
app: wallet-service
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: wallet-service-hpa
|
||||
namespace: production
|
||||
labels:
|
||||
app: wallet-service
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: wallet-service
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
Reference in New Issue
Block a user