Files
pos-system/microservices/scripts/deploy/deploy-prod.sh
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

351 lines
14 KiB
Bash
Executable File

#!/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/*:<commit-sha>)
# - 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 <name> Deploy a single service"
echo " --rollback <name> 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 <pod-name> -n ${NAMESPACE}"
echo -e " kubectl logs -n ${NAMESPACE} -l app=<service-name> --tail=50"
echo ""
echo -e "${YELLOW}To rollback a failed service:${NC}"
echo -e " $0 --rollback <service-name>"
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=<service-name> -f"
echo -e "Rollback: $0 --rollback <service-name>"