#!/bin/bash # EN: Deploy GoodGo Platform production services to Kubernetes production environment # VI: Trien khai cac service production cua GoodGo Platform len moi truong production Kubernetes # # Prerequisites: # - kubectl configured with production cluster access (KUBECONFIG env var) # - Docker images pushed to Docker Hub (goodgo/*:) # - Secrets created via kubectl or sealed-secrets (see secrets.yaml.example) # # Usage: # export KUBECONFIG=/path/to/kubeconfig # ./scripts/deploy/deploy-prod.sh # ./scripts/deploy/deploy-prod.sh --service iam-service # Deploy single service # ./scripts/deploy/deploy-prod.sh --dry-run # Dry run mode # ./scripts/deploy/deploy-prod.sh --rollback iam-service # Rollback service # ./scripts/deploy/deploy-prod.sh --migrate # Run DB migrations before deploy set -euo pipefail # EN: Color output helpers # VI: Ham ho tro mau output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' NC='\033[0m' # No Color SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" DEPLOY_DIR="${ROOT_DIR}/deployments/production/kubernetes" NAMESPACE="production" DRY_RUN="" SINGLE_SERVICE="" ROLLBACK_SERVICE="" RUN_MIGRATIONS=false # EN: Production services list # VI: Danh sach service production PROD_SERVICES=( "iam-service" "merchant-service" "catalog-service" "order-service" "fnb-engine" "inventory-service" "wallet-service" "booking-service" ) # EN: Parse arguments # VI: Phan tich tham so while [[ $# -gt 0 ]]; do case $1 in --dry-run) DRY_RUN="--dry-run=client" echo -e "${YELLOW}[DRY RUN] No changes will be applied${NC}" shift ;; --service) SINGLE_SERVICE="$2" shift 2 ;; --rollback) ROLLBACK_SERVICE="$2" shift 2 ;; --migrate) RUN_MIGRATIONS=true shift ;; --help|-h) echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --dry-run Preview changes without applying" echo " --service Deploy a single service" echo " --rollback Rollback a service to previous revision" echo " --migrate Run EF Core database migrations before deploy" echo " -h, --help Show this help" echo "" echo "Services: ${PROD_SERVICES[*]}" exit 0 ;; *) echo -e "${RED}Unknown option: $1${NC}" echo "Use --help for usage information" exit 1 ;; esac done # ============================================================================= # EN: Pre-flight checks # VI: Kiem tra truoc khi deploy # ============================================================================= echo -e "${MAGENTA}=== GoodGo Platform - Production Deployment ===${NC}" echo "" # EN: Require explicit confirmation for production # VI: Yeu cau xac nhan ro rang cho production if [ -z "$DRY_RUN" ] && [ -z "$ROLLBACK_SERVICE" ]; then echo -e "${RED}WARNING: You are about to deploy to PRODUCTION!${NC}" echo -e "${YELLOW}Namespace: ${NAMESPACE}${NC}" if [ -n "$SINGLE_SERVICE" ]; then echo -e "${YELLOW}Service: ${SINGLE_SERVICE}${NC}" else echo -e "${YELLOW}Services: ALL (${#PROD_SERVICES[@]} services)${NC}" fi echo "" read -p "Type 'yes' to confirm: " confirm if [ "$confirm" != "yes" ]; then echo -e "${YELLOW}Deployment cancelled${NC}" exit 0 fi fi # EN: Verify KUBECONFIG # VI: Xac minh KUBECONFIG if [ -z "${KUBECONFIG:-}" ]; then echo -e "${RED}KUBECONFIG environment variable not set${NC}" echo "Usage: export KUBECONFIG=/path/to/kubeconfig && $0" exit 1 fi # EN: Verify kubectl connectivity # VI: Xac minh ket noi kubectl echo -e "${BLUE}Verifying kubectl connectivity...${NC}" if ! kubectl cluster-info &>/dev/null; then echo -e "${RED}Cannot connect to Kubernetes cluster. Check KUBECONFIG.${NC}" exit 1 fi # EN: Verify namespace exists # VI: Xac minh namespace ton tai if ! kubectl get namespace "${NAMESPACE}" &>/dev/null; then echo -e "${YELLOW}Namespace '${NAMESPACE}' does not exist. Creating...${NC}" kubectl apply -f "${DEPLOY_DIR}/namespace.yaml" ${DRY_RUN} fi # EN: Verify manifests directory # VI: Xac minh thu muc manifests if [ ! -d "${DEPLOY_DIR}" ]; then echo -e "${RED}Deployment directory not found: ${DEPLOY_DIR}${NC}" exit 1 fi echo -e "${GREEN}Pre-flight checks passed${NC}" echo "" # ============================================================================= # EN: Handle rollback # VI: Xu ly rollback # ============================================================================= if [ -n "$ROLLBACK_SERVICE" ]; then echo -e "${YELLOW}Rolling back ${ROLLBACK_SERVICE}...${NC}" # EN: Show rollout history # VI: Hien thi lich su rollout echo -e "${BLUE}Rollout history:${NC}" kubectl rollout history "deployment/${ROLLBACK_SERVICE}" -n "${NAMESPACE}" 2>/dev/null || { echo -e "${RED}Deployment '${ROLLBACK_SERVICE}' not found in namespace '${NAMESPACE}'${NC}" exit 1 } echo "" read -p "Rollback to previous revision? (yes/no): " confirm_rollback if [ "$confirm_rollback" = "yes" ]; then kubectl rollout undo "deployment/${ROLLBACK_SERVICE}" -n "${NAMESPACE}" echo -e "${BLUE}Waiting for rollback to complete...${NC}" kubectl rollout status "deployment/${ROLLBACK_SERVICE}" -n "${NAMESPACE}" --timeout=180s echo -e "${GREEN}Rollback of ${ROLLBACK_SERVICE} completed${NC}" else echo -e "${YELLOW}Rollback cancelled${NC}" fi exit 0 fi # ============================================================================= # EN: Run database migrations (optional) # VI: Chay database migrations (tuy chon) # ============================================================================= if [ "$RUN_MIGRATIONS" = true ]; then echo -e "${BLUE}[0/6] Running database migrations...${NC}" # EN: Check dotnet-ef tool # VI: Kiem tra dotnet-ef tool if ! command -v dotnet &>/dev/null; then echo -e "${RED}dotnet SDK not found. Install .NET 10 SDK for migrations.${NC}" exit 1 fi dotnet tool install --global dotnet-ef 2>/dev/null || true declare -A MIGRATION_MAP=( ["iam-service"]="services/iam-service-net/src/IamService.Infrastructure/IamService.Infrastructure.csproj|services/iam-service-net/src/IamService.API/IamService.API.csproj|NEON_IAM_DATABASE_URL_PRODUCTION" ["merchant-service"]="services/merchant-service-net/src/MerchantService.Infrastructure/MerchantService.Infrastructure.csproj|services/merchant-service-net/src/MerchantService.API/MerchantService.API.csproj|NEON_MERCHANT_DATABASE_URL_PRODUCTION" ["order-service"]="services/order-service-net/src/OrderService.Infrastructure/OrderService.Infrastructure.csproj|services/order-service-net/src/OrderService.API/OrderService.API.csproj|NEON_ORDER_DATABASE_URL_PRODUCTION" ["fnb-engine"]="services/fnb-engine-net/src/FnbEngine.Infrastructure/FnbEngine.Infrastructure.csproj|services/fnb-engine-net/src/FnbEngine.API/FnbEngine.API.csproj|NEON_FNB_DATABASE_URL_PRODUCTION" ["inventory-service"]="services/inventory-service-net/src/InventoryService.Infrastructure/InventoryService.Infrastructure.csproj|services/inventory-service-net/src/InventoryService.API/InventoryService.API.csproj|NEON_INVENTORY_DATABASE_URL_PRODUCTION" ["wallet-service"]="services/wallet-service-net/src/WalletService.Infrastructure/WalletService.Infrastructure.csproj|services/wallet-service-net/src/WalletService.API/WalletService.API.csproj|NEON_WALLET_DATABASE_URL_PRODUCTION" ["catalog-service"]="services/catalog-service-net/src/CatalogService.Infrastructure/CatalogService.Infrastructure.csproj|services/catalog-service-net/src/CatalogService.API/CatalogService.API.csproj|NEON_CATALOG_DATABASE_URL_PRODUCTION" ["booking-service"]="services/booking-service-net/src/BookingService.Infrastructure/BookingService.Infrastructure.csproj|services/booking-service-net/src/BookingService.API/BookingService.API.csproj|NEON_BOOKING_DATABASE_URL_PRODUCTION" ) for svc in "${PROD_SERVICES[@]}"; do if [ -n "$SINGLE_SERVICE" ] && [ "$SINGLE_SERVICE" != "$svc" ]; then continue fi IFS='|' read -r infra_proj startup_proj db_env_var <<< "${MIGRATION_MAP[$svc]}" infra_path="${ROOT_DIR}/${infra_proj}" startup_path="${ROOT_DIR}/${startup_proj}" if [ -f "$infra_path" ] && [ -f "$startup_path" ]; then echo -e "${BLUE} Migrating ${svc}...${NC}" if [ -n "${!db_env_var:-}" ]; then ConnectionStrings__DefaultConnection="${!db_env_var}" \ dotnet ef database update \ --project "$infra_path" \ --startup-project "$startup_path" 2>&1 || { echo -e "${RED} Migration failed for ${svc}${NC}" read -p "Continue deployment? (yes/no): " continue_deploy if [ "$continue_deploy" != "yes" ]; then exit 1 fi } echo -e "${GREEN} ${svc} migrated${NC}" else echo -e "${YELLOW} Skipping ${svc} - env var ${db_env_var} not set${NC}" fi else echo -e "${YELLOW} Skipping ${svc} - project files not found${NC}" fi done echo "" fi # ============================================================================= # EN: Step 1 - Apply namespace # VI: Buoc 1 - Tao namespace # ============================================================================= echo -e "${BLUE}[1/6] Applying namespace...${NC}" kubectl apply -f "${DEPLOY_DIR}/namespace.yaml" ${DRY_RUN} # ============================================================================= # EN: Step 2 - Apply shared configuration # VI: Buoc 2 - Ap dung cau hinh chung # ============================================================================= echo -e "${BLUE}[2/6] Applying configuration...${NC}" kubectl apply -f "${DEPLOY_DIR}/configmap.yaml" ${DRY_RUN} # ============================================================================= # EN: Step 3 - Deploy infrastructure (Redis) # VI: Buoc 3 - Trien khai ha tang (Redis) # ============================================================================= echo -e "${BLUE}[3/6] Deploying infrastructure...${NC}" if [ -z "$SINGLE_SERVICE" ] || [ "$SINGLE_SERVICE" = "redis" ]; then kubectl apply -f "${DEPLOY_DIR}/redis.yaml" ${DRY_RUN} echo -e "${GREEN} redis deployed${NC}" fi # ============================================================================= # EN: Step 4 - Deploy backend services # VI: Buoc 4 - Trien khai cac service backend # ============================================================================= echo -e "${BLUE}[4/6] Deploying services...${NC}" for svc in "${PROD_SERVICES[@]}"; do if [ -z "$SINGLE_SERVICE" ] || [ "$SINGLE_SERVICE" = "$svc" ]; then if [ -f "${DEPLOY_DIR}/${svc}.yaml" ]; then kubectl apply -f "${DEPLOY_DIR}/${svc}.yaml" ${DRY_RUN} echo -e "${GREEN} ${svc} deployed${NC}" else echo -e "${YELLOW} ${svc}.yaml not found, skipping${NC}" fi fi done # ============================================================================= # EN: Step 5 - Apply ingress routing # VI: Buoc 5 - Ap dung dinh tuyen ingress # ============================================================================= echo -e "${BLUE}[5/6] Applying ingress routing...${NC}" if [ -z "$SINGLE_SERVICE" ]; then kubectl apply -f "${DEPLOY_DIR}/ingress.yaml" ${DRY_RUN} fi # ============================================================================= # EN: Step 6 - Wait for rollouts and verify # VI: Buoc 6 - Cho rollout va xac minh # ============================================================================= echo "" echo -e "${BLUE}[6/6] Waiting for rollouts to complete...${NC}" if [ -z "$DRY_RUN" ]; then FAILED=0 for svc in "${PROD_SERVICES[@]}"; do if [ -z "$SINGLE_SERVICE" ] || [ "$SINGLE_SERVICE" = "$svc" ]; then echo -n " ${svc}: " if kubectl rollout status "deployment/${svc}" -n "${NAMESPACE}" --timeout=180s 2>/dev/null; then echo -e "${GREEN}ready${NC}" else echo -e "${RED}FAILED${NC}" FAILED=$((FAILED + 1)) fi fi done echo "" if [ $FAILED -gt 0 ]; then echo -e "${RED}WARNING: ${FAILED} service(s) did not complete rollout${NC}" echo -e "${YELLOW}Troubleshooting:${NC}" echo -e " kubectl get pods -n ${NAMESPACE}" echo -e " kubectl describe pod -n ${NAMESPACE}" echo -e " kubectl logs -n ${NAMESPACE} -l app= --tail=50" echo "" echo -e "${YELLOW}To rollback a failed service:${NC}" echo -e " $0 --rollback " exit 1 fi # EN: Print deployment summary # VI: In tom tat deployment echo -e "${GREEN}=== Production Deployment Summary ===${NC}" echo "" echo -e "${BLUE}Pods:${NC}" kubectl get pods -n "${NAMESPACE}" -o wide --sort-by='.metadata.name' 2>/dev/null echo "" echo -e "${BLUE}Services:${NC}" kubectl get svc -n "${NAMESPACE}" 2>/dev/null echo "" echo -e "${BLUE}HPAs:${NC}" kubectl get hpa -n "${NAMESPACE}" 2>/dev/null echo "" echo -e "${BLUE}Ingress:${NC}" kubectl get ingress -n "${NAMESPACE}" 2>/dev/null fi echo "" echo -e "${GREEN}=== Production deployment completed ===${NC}" echo -e "${BLUE}API: https://api.goodgo.vn${NC}" echo -e "${BLUE}POS: https://pos.goodgo.vn${NC}" echo "" echo -e "Verify: kubectl get pods -n ${NAMESPACE}" echo -e "Logs: kubectl logs -n ${NAMESPACE} -l app= -f" echo -e "Rollback: $0 --rollback "