はじめに
今回は、Railsの基本スタックのインフラ構築をAWSで行うことに挑戦しました。アプリをAWS上でデプロイするにあたり、ECS(Elastic Container Service)を初めて利用してみました。Railsの基本スタックをAWSで簡単に構築すること、特にDockerとECSを用いたコンテナ化、SidekiqやElastiCacheの導入も含めて実践しました。この記事では、その過程や得られた学びを共有します。
デプロイの目的
- Railsの基本スタックをAWSで構築
- AWS上で、Railsアプリの本番環境を簡単にセットアップできるようになりたい。
- Sidekiqの本番利用を経験する
- これまでHerokuを使っていたため、AWSでのSidekiqのセットアップに挑戦。
- Dockerでのデプロイ
- Dockerで環境構築したアプリを効率的にデプロイできる環境を構築したい。
- ECSの利用経験を積む
- これまで使ったことのないECSを学びたかったため、今回の機会に挑戦してみました。
- ElastiCacheの利用
- ElastiCacheを利用したことがなかったため、試してみることにしました。
デプロイするアプリの構成
- フレームワーク: Rails 7
- Rubyバージョン: 3.2.4
- データベース: PostgreSQL
- バックグラウンドジョブ管理: Sidekiq
- キャッシュ: Redis
- サーバーレスコンテナ: ECS Fargate
アプリの機能
デプロイしたアプリは、シンプルな管理機能を持つもので、以下の機能を備えています。
- 管理者ログイン
- ユーザー一覧の表示
- メール送信
- Sidekiqダッシュボード
- Redisデータのビューア
このアプリを選んだ理由は以下のとおりです。
- バックグラウンド処理が視覚的に動作していることを確認したかった点
- Redisのデータを直接確認してみることが面白いと感じた点
- 基礎の基礎の機能は動かしたかった点
デプロイ方法
今回は、CloudFormationやTerraformを使用せずに、AWS CLIを活用してインフラを構築するスクリプトを作成しました。スクリプトはChatGPTと相談しながら、AWSリソースの構築をスクリプト化することで、手動操作を減らし、デプロイの手間を軽減しました。
詰まったポイント
- ECSのタスク概念
- タスクの管理方法に戸惑い、タスクが起動したままの状態や管理方法について理解するのに時間がかかりました。
- ロードバランサーとSSL設定
- 既存のドメインを利用してSSL化する際、ロードバランサーとRoute 53の設定につまづきました。
- リソース構築の順序
- AWSリソースの作成順序に何度か詰まりましたが、試行錯誤の末、無事にリソースを構築できました。
- ChatGPTでのスクリプト作成
- スクリプトのコード量が増えるにつれて意図しない修正が加わることが多く、エラーなく動作させるのに苦労しました。ただ、400行程度のコードを出力できるのには驚きました。
どの点も理解すれば大したことではありませんが、実際に取り組む中での試行錯誤が良い経験になりました
感想と次の挑戦
AWS CLIを使ってのリソース構築は初めての経験で、手間も多く大変でしたが、その分インフラの知識不足を実感しました。また、久しぶりにRailsを触る機会があり、やはり楽しく感じました。次は、Terraformなどの自動化ツールを使って、効率的にインフラを構築してみたいと思います。
デプロイスクリプトの紹介
以下は実際に使用したデプロイ用のスクリプトです。これを使うことで、VPCやサブネットの作成から、ECSサービスやALBの設定まで、手動操作なしで構築できるようにしました。
別途ALBとドメイン, SESのSMTPの設定が必要です。
deploy.sh
#!/bin/bash set -e REGION="ap-northeast-1" APP_NAME="sample-app" ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) VPC_CIDR="10.0.0.0/16" SUBNET1_CIDR="10.0.1.0/24" SUBNET2_CIDR="10.0.2.0/24" DB_USERNAME="mydbuser" DB_PASSWORD="mydbpassword" DB_NAME="sampledb" RDS_INSTANCE_CLASS="db.t3.micro" CACHE_NODE_TYPE="cache.t3.micro" ALB_NAME="${APP_NAME}-alb" TARGET_GROUP_NAME="${APP_NAME}-targets" SERVICE_NAME="${APP_NAME}-service" CLUSTER_NAME="${APP_NAME}-cluster" DB_INSTANCE_IDENTIFIER="${APP_NAME}-db" CACHE_CLUSTER_ID="${APP_NAME}-cache" DB_SUBNET_GROUP_NAME="${APP_NAME}-db-subnet-group" CACHE_SUBNET_GROUP_NAME="${APP_NAME}-cache-subnet-group" SECURITY_GROUP_NAME="${APP_NAME}-sg" TASK_DEFINITION_NAME="${APP_NAME}-task" TASK_EXECUTION_ROLE="arn:aws:iam::${ACCOUNT_ID}:role/ecsTaskExecutionRole" SECRETS_NAME="${APP_NAME}-env" DOMAIN_NAME="sample-app.example.com" SKIP_BUILD_IMAGE=false for arg in "$@" do case $arg in --skip-build-image) SKIP_BUILD_IMAGE=true shift ;; esac done echo "Starting deployment..." # Function to log and collect output output_log="deployment_output.log" function log { echo "$1" | tee -a $output_log } # Create or get ECR repository if ! aws ecr describe-repositories --repository-names ${APP_NAME} --region ${REGION} > /dev/null 2>&1; then aws ecr create-repository --repository-name ${APP_NAME} --region ${REGION} > /dev/null 2>&1 log "ECR repository ${APP_NAME} created." else log "ECR repository ${APP_NAME} already exists." fi # Authenticate Docker to the ECR registry aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com > /dev/null 2>&1 if [ "$SKIP_BUILD_IMAGE" = false ]; then # Build and push Docker image docker build --platform linux/amd64 . -t ${APP_NAME} docker tag ${APP_NAME}:latest ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${APP_NAME}:latest > /dev/null 2>&1 docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${APP_NAME}:latest > /dev/null 2>&1 log "Docker image for ${APP_NAME} built and pushed to ECR." else log "Skipping Docker image build and push." fi # Create or get VPC VPC_ID=$(aws ec2 describe-vpcs --filters "Name=cidr-block,Values=${VPC_CIDR}" --query 'Vpcs[0].VpcId' --output text --region ${REGION} 2>&1) if [ "$VPC_ID" = "None" ]; then VPC_ID=$(aws ec2 create-vpc --cidr-block ${VPC_CIDR} --query 'Vpc.VpcId' --output text --region ${REGION} 2>&1) log "VPC created with ID: ${VPC_ID}." else log "VPC already exists with ID: ${VPC_ID}." fi # Create or get subnets SUBNET_ID1=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=${VPC_ID}" "Name=cidr-block,Values=${SUBNET1_CIDR}" --query 'Subnets[0].SubnetId' --output text --region ${REGION} 2>&1) if [ "$SUBNET_ID1" = "None" ]; then SUBNET_ID1=$(aws ec2 create-subnet --vpc-id ${VPC_ID} --cidr-block ${SUBNET1_CIDR} --availability-zone ${REGION}a --query 'Subnet.SubnetId' --output text --region ${REGION} 2>&1) log "Subnet created with ID: ${SUBNET_ID1}." else log "Subnet already exists with ID: ${SUBNET_ID1}." fi aws ec2 modify-subnet-attribute --subnet-id ${SUBNET_ID1} --map-public-ip-on-launch SUBNET_ID2=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=${VPC_ID}" "Name=cidr-block,Values=${SUBNET2_CIDR}" --query 'Subnets[0].SubnetId' --output text --region ${REGION} 2>&1) if [ "$SUBNET_ID2" = "None" ]; then SUBNET_ID2=$(aws ec2 create-subnet --vpc-id ${VPC_ID} --cidr-block ${SUBNET2_CIDR} --availability-zone ${REGION}c --query 'Subnet.SubnetId' --output text --region ${REGION} 2>&1) log "Subnet created with ID: ${SUBNET_ID2}." else log "Subnet already exists with ID: ${SUBNET_ID2}." fi aws ec2 modify-subnet-attribute --subnet-id ${SUBNET_ID2} --map-public-ip-on-launch # Create and attach internet gateway IGW_ID=$(aws ec2 describe-internet-gateways --filters "Name=attachment.vpc-id,Values=${VPC_ID}" --query 'InternetGateways[0].InternetGatewayId' --output text --region ${REGION} 2>&1) if [ "$IGW_ID" = "None" ]; then IGW_ID=$(aws ec2 create-internet-gateway --query 'InternetGateway.InternetGatewayId' --output text --region ${REGION} 2>&1) aws ec2 attach-internet-gateway --vpc-id ${VPC_ID} --internet-gateway-id ${IGW_ID} --region ${REGION} > /dev/null 2>&1 log "Internet gateway created and attached with ID: ${IGW_ID}." else log "Internet gateway already exists with ID: ${IGW_ID}." fi # Create or get route table ROUTE_TABLE_ID=$(aws ec2 describe-route-tables --filters "Name=vpc-id,Values=${VPC_ID}" --query 'RouteTables[0].RouteTableId' --output text --region ${REGION} 2>&1) if [ "$ROUTE_TABLE_ID" = "None" ]; then ROUTE_TABLE_ID=$(aws ec2 create-route-table --vpc-id ${VPC_ID} --query 'RouteTable.RouteTableId' --output text --region ${REGION} 2>&1) log "Route table created with ID: ${ROUTE_TABLE_ID}." else log "Route table already exists with ID: ${ROUTE_TABLE_ID}." fi # Create route if ! aws ec2 describe-route-tables --route-table-ids ${ROUTE_TABLE_ID} --query 'RouteTables[0].Routes[?DestinationCidrBlock == `0.0.0.0/0`]' --output text --region ${REGION} 2>&1 | grep -q "0.0.0.0/0"; then aws ec2 create-route --route-table-id ${ROUTE_TABLE_ID} --destination-cidr-block 0.0.0.0/0 --gateway-id ${IGW_ID} --region ${REGION} > /dev/null 2>&1 log "Route created in route table ${ROUTE_TABLE_ID}." else log "Route already exists in route table ${ROUTE_TABLE_ID}." fi # Associate route table with subnets aws ec2 associate-route-table --subnet-id ${SUBNET_ID1} --route-table-id ${ROUTE_TABLE_ID} --region ${REGION} > /dev/null 2>&1 aws ec2 associate-route-table --subnet-id ${SUBNET_ID2} --route-table-id ${ROUTE_TABLE_ID} --region ${REGION} > /dev/null 2>&1 log "Route table associated with subnets." # Create or get security group SECURITY_GROUP_ID=$(aws ec2 describe-security-groups --filters "Name=group-name,Values=${SECURITY_GROUP_NAME}" --query 'SecurityGroups[0].GroupId' --output text --region ${REGION} 2>&1) if [ "$SECURITY_GROUP_ID" = "None" ]; then SECURITY_GROUP_ID=$(aws ec2 create-security-group --group-name ${SECURITY_GROUP_NAME} --description "Security group for ${APP_NAME}" --vpc-id ${VPC_ID} --query 'GroupId' --output text --region ${REGION} 2>&1) log "Security group created with ID: ${SECURITY_GROUP_ID}." else log "Security group already exists with ID: ${SECURITY_GROUP_ID}." fi # Authorize security group ingress function authorize_security_group_ingress { local group_id=$1 local protocol=$2 local port=$3 local source_group_id=$4 if ! aws ec2 describe-security-groups --group-ids $group_id --query "SecurityGroups[0].IpPermissions[?IpProtocol == '$protocol' && FromPort == \`$port\` && ToPort == \`$port\` && UserIdGroupPairs[0].GroupId == '$source_group_id']" --region $REGION 2>&1 | grep -q "$source_group_id"; then aws ec2 authorize-security-group-ingress --group-id $group_id --protocol $protocol --port $port --source-group $source_group_id --region $REGION > /dev/null 2>&1 log "Ingress rule added to security group $group_id: $protocol $port from $source_group_id." else log "Ingress rule already exists in security group $group_id: $protocol $port from $source_group_id." fi } function authorize_security_group_ingress_cidr { local group_id=$1 local protocol=$2 local port=$3 local cidr=$4 if ! aws ec2 describe-security-groups --group-ids $group_id --query "SecurityGroups[0].IpPermissions[?IpProtocol == '$protocol' && FromPort == \`$port\` && ToPort == \`$port\` && IpRanges[0].CidrIp == '$cidr']" --region $REGION 2>&1 | grep -q "$cidr"; then aws ec2 authorize-security-group-ingress --group-id $group_id --protocol $protocol --port $port --cidr $cidr --region $REGION > /dev/null 2>&1 log "Ingress rule added to security group $group_id: $protocol $port from $cidr." else log "Ingress rule already exists in security group $group_id: $protocol $port from $cidr." fi } authorize_security_group_ingress_cidr ${SECURITY_GROUP_ID} "tcp" 80 "0.0.0.0/0" authorize_security_group_ingress ${SECURITY_GROUP_ID} "tcp" 5432 ${SECURITY_GROUP_ID} authorize_security_group_ingress ${SECURITY_GROUP_ID} "tcp" 6379 ${SECURITY_GROUP_ID} authorize_security_group_ingress_cidr ${SECURITY_GROUP_ID} "tcp" 3000 "0.0.0.0/0" log "Security group ingress rules authorized." # Create or get RDS subnet group and instance if ! aws rds describe-db-subnet-groups --db-subnet-group-name ${DB_SUBNET_GROUP_NAME} --region ${REGION} > /dev/null 2>&1; then aws rds create-db-subnet-group --db-subnet-group-name ${DB_SUBNET_GROUP_NAME} --db-subnet-group-description "Subnet group for ${APP_NAME}" --subnet-ids ${SUBNET_ID1} ${SUBNET_ID2} --region ${REGION} > /dev/null 2>&1 log "RDS subnet group created." else log "RDS subnet group already exists." fi if ! aws rds describe-db-instances --db-instance-identifier ${DB_INSTANCE_IDENTIFIER} --region ${REGION} > /dev/null 2>&1; then aws rds create-db-instance --db-instance-identifier ${DB_INSTANCE_IDENTIFIER} --db-instance-class ${RDS_INSTANCE_CLASS} --engine postgres --master-username ${DB_USERNAME} --master-user-password ${DB_PASSWORD} --allocated-storage 20 --vpc-security-group-ids ${SECURITY_GROUP_ID} --db-subnet-group-name ${DB_SUBNET_GROUP_NAME} --region ${REGION} > /dev/null 2>&1 aws rds wait db-instance-available --db-instance-identifier ${DB_INSTANCE_IDENTIFIER} --region ${REGION} > /dev/null 2>&1 log "RDS instance created and available." else log "RDS instance already exists." fi # Create or get ElastiCache subnet group and cluster if ! aws elasticache describe-cache-subnet-groups --cache-subnet-group-name ${CACHE_SUBNET_GROUP_NAME} --region ${REGION} > /dev/null 2>&1; then aws elasticache create-cache-subnet-group --cache-subnet-group-name ${CACHE_SUBNET_GROUP_NAME} --cache-subnet-group-description "Subnet group for ${APP_NAME}" --subnet-ids ${SUBNET_ID1} ${SUBNET_ID2} --region ${REGION} > /dev/null 2>&1 log "ElastiCache subnet group created." else log "ElastiCache subnet group already exists." fi if ! aws elasticache describe-cache-clusters --cache-cluster-id ${CACHE_CLUSTER_ID} --region ${REGION} > /dev/null 2>&1; then aws elasticache create-cache-cluster --cache-cluster-id ${CACHE_CLUSTER_ID} --engine redis --cache-node-type ${CACHE_NODE_TYPE} --num-cache-nodes 1 --security-group-ids ${SECURITY_GROUP_ID} --cache-subnet-group-name ${CACHE_SUBNET_GROUP_NAME} --region ${REGION} > /dev/null 2>&1 aws elasticache wait cache-cluster-available --cache-cluster-id ${CACHE_CLUSTER_ID} --region ${REGION} > /dev/null 2>&1 log "ElastiCache cluster created and available." else log "ElastiCache cluster already exists." fi # Get RDS and ElastiCache endpoints RDS_ENDPOINT=$(aws rds describe-db-instances --db-instance-identifier ${DB_INSTANCE_IDENTIFIER} --query 'DBInstances[0].Endpoint.Address' --output text --region ${REGION} 2>&1) CACHE_ENDPOINT=$(aws elasticache describe-cache-clusters --cache-cluster-id ${CACHE_CLUSTER_ID} --show-cache-node-info --query 'CacheClusters[0].CacheNodes[0].Endpoint.Address' --output text --region ${REGION} 2>&1) # Write DB information to .env.deploy echo "Writing DB information to .env.deploy" cat > .env.deploy <<EOL POSTGRES_HOST=${RDS_ENDPOINT} POSTGRES_USER=${DB_USERNAME} POSTGRES_PASSWORD=${DB_PASSWORD} POSTGRES_DB=${DB_NAME} REDIS_URL=redis://${CACHE_ENDPOINT}:6379/0 DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@${RDS_ENDPOINT}/${DB_NAME} EOL # Combine .env.production and .env.deploy echo "Combining .env.production and .env.deploy" cat .env.production .env.deploy > .env.full # Create or get ECS cluster if ! aws ecs describe-clusters --clusters ${CLUSTER_NAME} --region ${REGION} --query 'clusters[0].status' --output text 2>&1 | grep -q "ACTIVE"; then aws ecs create-cluster --cluster-name ${CLUSTER_NAME} --region ${REGION} > /dev/null 2>&1 log "ECS cluster created with name: ${CLUSTER_NAME}." else log "ECS cluster already exists with name: ${CLUSTER_NAME}." fi # Create or update Secrets Manager secret ENV_VARIABLES=$(cat .env.full | jq -Rs 'split("\n") | map(select(length > 0)) | map(split("=")) | map({name: .[0], value: .[1]})') if ! aws secretsmanager describe-secret --secret-id ${SECRETS_NAME} --region ${REGION} > /dev/null 2>&1; then aws secretsmanager create-secret --name ${SECRETS_NAME} --secret-string "$(echo $ENV_VARIABLES | jq -c '.')" --region ${REGION} > /dev/null 2>&1 log "Secrets Manager secret created." else aws secretsmanager update-secret --secret-id ${SECRETS_NAME} --secret-string "$(echo $ENV_VARIABLES | jq -c '.')" --region ${REGION} > /dev/null 2>&1 log "Secrets Manager secret updated." fi # Create log group if it does not exist if ! aws logs describe-log-groups --log-group-name-prefix /ecs/${TASK_DEFINITION_NAME} --region ${REGION} | grep -q ${TASK_DEFINITION_NAME}; then aws logs create-log-group --log-group-name /ecs/${TASK_DEFINITION_NAME} --region ${REGION} > /dev/null 2>&1 log "Log group /ecs/${TASK_DEFINITION_NAME} created." else log "Log group /ecs/${TASK_DEFINITION_NAME} already exists." fi # Create log group about sidekiq if it does not exist if ! aws logs describe-log-groups --log-group-name-prefix /ecs/${TASK_DEFINITION_NAME}-sidekiq --region ${REGION} | grep -q ${TASK_DEFINITION_NAME}-sidekiq; then aws logs create-log-group --log-group-name /ecs/${TASK_DEFINITION_NAME}-sidekiq --region ${REGION} > /dev/null 2>&1 log "Log group /ecs/${TASK_DEFINITION_NAME}-sidekiq created." else log "Log group /ecs/${TASK_DEFINITION_NAME}-sidekiq already exists." fi # Register ECS task definition ENV_VARS=$(cat .env.full | jq -R -s -c 'split("\n") | map(select(length > 0)) | map(split("=")) | map({"name": .[0], "value": .[1]})') TASK_DEFINITION=$(cat <<EOF { "family": "${TASK_DEFINITION_NAME}", "networkMode": "awsvpc", "containerDefinitions": [ { "name": "${APP_NAME}-container", "image": "${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${APP_NAME}:latest", "essential": true, "memory": 256, "cpu": 128, "portMappings": [ { "containerPort": 3000, "hostPort": 3000 } ], "environment": $ENV_VARS, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/${TASK_DEFINITION_NAME}", "awslogs-region": "${REGION}", "awslogs-stream-prefix": "${APP_NAME}" } } }, { "name": "${APP_NAME}-sidekiq", "image": "${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${APP_NAME}:latest", "essential": true, "memory": 256, "cpu": 128, "command": ["sidekiq"], "environment": $ENV_VARS, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/${TASK_DEFINITION_NAME}-sidekiq", "awslogs-region": "${REGION}", "awslogs-stream-prefix": "${APP_NAME}-sidekiq" } } } ], "requiresCompatibilities": [ "FARGATE" ], "cpu": "256", "memory": "512", "executionRoleArn": "${TASK_EXECUTION_ROLE}", "taskRoleArn": "${TASK_EXECUTION_ROLE}" } EOF ) echo "${TASK_DEFINITION}" > task-definition.json aws ecs register-task-definition --cli-input-json file://task-definition.json --region ${REGION} > /dev/null 2>&1 log "ECS task definition registered." # Create or get ALB ALB_ARN=$(aws elbv2 describe-load-balancers --names ${ALB_NAME} --query 'LoadBalancers[0].LoadBalancerArn' --output text --region ${REGION} 2>/dev/null || echo "None") if [ "$ALB_ARN" = "None" ]; then ALB_ARN=$(aws elbv2 create-load-balancer \ --name ${ALB_NAME} \ --subnets ${SUBNET_ID1} ${SUBNET_ID2} \ --security-groups ${SECURITY_GROUP_ID} \ --region ${REGION} \ --query 'LoadBalancers[0].LoadBalancerArn' \ --output text) log "ALB created with ARN: ${ALB_ARN}." else log "ALB already exists with ARN: ${ALB_ARN}." fi # Create or get Target Group TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --names ${TARGET_GROUP_NAME} --query 'TargetGroups[0].TargetGroupArn' --output text --region ${REGION} 2>/dev/null || echo "None") if [ "$TARGET_GROUP_ARN" = "None" ]; then TARGET_GROUP_ARN=$(aws elbv2 create-target-group \ --name ${TARGET_GROUP_NAME} \ --protocol HTTP \ --port 3000 \ --vpc-id ${VPC_ID} \ --target-type ip \ --query 'TargetGroups[0].TargetGroupArn' \ --output text \ --region ${REGION}) log "Target group created with ARN: ${TARGET_GROUP_ARN}." else log "Target group already exists with ARN: ${TARGET_GROUP_ARN}." fi # SSL化 # Request a new SSL certificate using ACM CERTIFICATE_ARN=$(aws acm request-certificate \ --domain-name ${DOMAIN_NAME} \ --validation-method DNS \ --query 'CertificateArn' \ --output text \ --region ${REGION}) log "SSL certificate requested with ARN: ${CERTIFICATE_ARN}." # Wait until the certificate is issued aws acm wait certificate-validated --certificate-arn ${CERTIFICATE_ARN} --region ${REGION} log "SSL certificate issued." # Create HTTPS listener on ALB HTTPS_LISTENER_ARN=$(aws elbv2 describe-listeners --load-balancer-arn ${ALB_ARN} --query "Listeners[?Port==\`443\`].ListenerArn" --output text --region ${REGION}) if [ -z "$HTTPS_LISTENER_ARN" ]; then # Create HTTPS listener if it doesn't exist HTTPS_LISTENER_ARN=$(aws elbv2 create-listener \ --load-balancer-arn ${ALB_ARN} \ --protocol HTTPS \ --port 443 \ --certificates CertificateArn=${CERTIFICATE_ARN} \ --default-actions Type=forward,TargetGroupArn=${TARGET_GROUP_ARN} \ --query 'Listeners[0].ListenerArn' \ --output text \ --region ${REGION}) log "HTTPS listener created with ARN: ${HTTPS_LISTENER_ARN}." else log "HTTPS listener already exists with ARN: ${HTTPS_LISTENER_ARN}." fi authorize_security_group_ingress_cidr ${SECURITY_GROUP_ID} "tcp" 443 "0.0.0.0/0" log "Security group updated to allow HTTPS traffic." # Create or get HTTP listener on ALB (for redirect to HTTPS) HTTP_LISTENER_ARN=$(aws elbv2 describe-listeners --load-balancer-arn ${ALB_ARN} --query "Listeners[?Port==\`80\`].ListenerArn" --output text --region ${REGION}) if [ -z "$HTTP_LISTENER_ARN" ]; then HTTP_LISTENER_ARN=$(aws elbv2 create-listener \ --load-balancer-arn ${ALB_ARN} \ --protocol HTTP \ --port 80 \ --default-actions Type=redirect,RedirectConfig="{Protocol=HTTPS,Port=443,StatusCode=HTTP_301}" \ --query 'Listeners[0].ListenerArn' \ --output text \ --region ${REGION}) log "HTTP listener created with ARN: ${HTTP_LISTENER_ARN} and redirects to HTTPS." else log "HTTP listener already exists with ARN: ${HTTP_LISTENER_ARN}." fi # Create or update ECS service SERVICE_STATUS=$(aws ecs describe-services --cluster ${CLUSTER_NAME} --services ${SERVICE_NAME} --region ${REGION} --query 'services[0].status' --output text 2>&1 || true) if [[ "$SERVICE_STATUS" == "None" || "$SERVICE_STATUS" == "MISSING" || -z "$SERVICE_STATUS" ]]; then if ! aws ecs create-service --cluster ${CLUSTER_NAME} --service-name ${SERVICE_NAME} --task-definition ${TASK_DEFINITION_NAME} --desired-count 1 --launch-type FARGATE --network-configuration "awsvpcConfiguration={subnets=[${SUBNET_ID1},${SUBNET_ID2}],securityGroups=[${SECURITY_GROUP_ID}],assignPublicIp=ENABLED}" --load-balancers "targetGroupArn=${TARGET_GROUP_ARN},containerName=${APP_NAME}-container,containerPort=3000" --region ${REGION} > /dev/null 2>&1; then log "Failed to create ECS service." exit 1 else log "ECS service created." fi else if ! aws ecs update-service --cluster ${CLUSTER_NAME} --service ${SERVICE_NAME} --task-definition ${TASK_DEFINITION_NAME} --desired-count 1 --region ${REGION} > /dev/null 2>&1; then log "Failed to update ECS service." exit 1 else log "ECS service updated." fi fi ALB_DNS_NAME=$(aws elbv2 describe-load-balancers \ --names ${ALB_NAME} \ --query 'LoadBalancers[0].DNSName' \ --output text \ --region ${REGION}) log "Deployment of ${APP_NAME} completed successfully!" log "You can access the application at: https://${ALB_DNS_NAME}"
.env.production.sample
AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= RAILS_MASTER_KEY= RAILS_ENV=production SECRET_KEY_BASE= SMTP_IAM_USER_NAME= SMTP_USERNAME= SMTP_PASSWORD=