Configure Amazon ECS Service Connect
The following code example shows how to:
Create the VPC infrastructure
Set up logging
Create the ECS cluster
Configure IAM roles
Create the service with Service Connect
Verify the deployment
Clean up resources
- Bash
-
- Amazon CLI with Bash script
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the Sample developer tutorials
repository. #!/bin/bash # ECS Service Connect Tutorial Script v4 - Modified to use Default VPC # This script creates an ECS cluster with Service Connect and deploys an nginx service # Uses the default VPC to avoid VPC limits set -e # Exit on any error # Configuration SCRIPT_NAME="ECS Service Connect Tutorial" LOG_FILE="ecs-service-connect-tutorial-v4-default-vpc.log" REGION=${AWS_DEFAULT_REGION:-${AWS_REGION:-$(aws configure get region 2>/dev/null)}} if [ -z "$REGION" ]; then echo "ERROR: No AWS region configured." echo "Set one with: aws configure set region us-east-1" exit 1 fi ENV_PREFIX="tutorial" CLUSTER_NAME="${ENV_PREFIX}-cluster" NAMESPACE_NAME="service-connect" # Generate random suffix for unique resource names RANDOM_SUFFIX=$(openssl rand -hex 6) # Arrays to track created resources for cleanup declare -a CREATED_RESOURCES=() # Logging function log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } # Error handling function handle_error() { log "ERROR: Script failed at line $1" log "Attempting to clean up resources..." cleanup_resources exit 1 } # Set up error handling trap 'handle_error $LINENO' ERR # Function to add resource to tracking array track_resource() { CREATED_RESOURCES+=("$1") log "Tracking resource: $1" } # Function to check if command output contains actual errors check_for_errors() { local output="$1" local command_name="$2" # Check for specific AWS CLI error patterns, not just any occurrence of "error" if echo "$output" | grep -qi "An error occurred\|InvalidParameterException\|AccessDenied\|ValidationException\|ResourceNotFoundException"; then log "ERROR in $command_name: $output" return 1 fi return 0 } # Function to get AWS account ID get_account_id() { ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) log "Using AWS Account ID: $ACCOUNT_ID" } # Function to wait for resources to be ready wait_for_resource() { local resource_type="$1" local resource_id="$2" case "$resource_type" in "cluster") log "Waiting for cluster $resource_id to be active..." local attempt=1 local max_attempts=30 while [ $attempt -le $max_attempts ]; do local status=$(aws ecs describe-clusters --clusters "$resource_id" --query 'clusters[0].status' --output text) if [ "$status" = "ACTIVE" ]; then log "Cluster is now active" return 0 fi log "Cluster status: $status (attempt $attempt/$max_attempts)" sleep 10 ((attempt++)) done log "ERROR: Cluster did not become active within expected time" return 1 ;; "service") log "Waiting for service $resource_id to be stable..." aws ecs wait services-stable --cluster "$CLUSTER_NAME" --services "$resource_id" ;; "nat-gateway") log "Waiting for NAT Gateway $resource_id to be available..." aws ec2 wait nat-gateway-available --nat-gateway-ids "$resource_id" ;; esac } # Function to use default VPC infrastructure setup_default_vpc_infrastructure() { log "Using default VPC infrastructure..." # Get default VPC VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" --query 'Vpcs[0].VpcId' --output text) if [[ "$VPC_ID" == "None" || -z "$VPC_ID" ]]; then log "ERROR: No default VPC found. Please create a default VPC first." exit 1 fi log "Using default VPC: $VPC_ID" # Get default subnets SUBNETS=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=default-for-az,Values=true" --query 'Subnets[].SubnetId' --output text) SUBNET_ARRAY=($SUBNETS) if [ ${#SUBNET_ARRAY[@]} -lt 2 ]; then log "ERROR: Need at least 2 subnets for ECS Service Connect. Found: ${#SUBNET_ARRAY[@]}" exit 1 fi PUBLIC_SUBNET1=${SUBNET_ARRAY[0]} PUBLIC_SUBNET2=${SUBNET_ARRAY[1]} log "Using subnets: $PUBLIC_SUBNET1, $PUBLIC_SUBNET2" # Create security group for ECS tasks SG_OUTPUT=$(aws ec2 create-security-group \ --group-name "${ENV_PREFIX}-ecs-sg-${RANDOM_SUFFIX}" \ --description "Security group for ECS Service Connect tutorial" \ --vpc-id "$VPC_ID" 2>&1) check_for_errors "$SG_OUTPUT" "create-security-group" SECURITY_GROUP_ID=$(echo "$SG_OUTPUT" | grep -o '"GroupId": "[^"]*"' | cut -d'"' -f4) track_resource "SG:$SECURITY_GROUP_ID" log "Created security group: $SECURITY_GROUP_ID" # Add inbound rules to security group aws ec2 authorize-security-group-ingress \ --group-id "$SECURITY_GROUP_ID" \ --protocol tcp \ --port 80 \ --cidr 0.0.0.0/0 >/dev/null 2>&1 || true aws ec2 authorize-security-group-ingress \ --group-id "$SECURITY_GROUP_ID" \ --protocol tcp \ --port 443 \ --cidr 0.0.0.0/0 >/dev/null 2>&1 || true log "Default VPC infrastructure setup completed" } # Function to create CloudWatch log groups create_log_groups() { log "Creating CloudWatch log groups..." # Create log group for nginx container aws logs create-log-group --log-group-name "/ecs/service-connect-nginx" 2>&1 | grep -v "ResourceAlreadyExistsException" || { if [ ${PIPESTATUS[0]} -eq 0 ]; then log "Log group /ecs/service-connect-nginx created" track_resource "LOG_GROUP:/ecs/service-connect-nginx" else log "Log group /ecs/service-connect-nginx already exists" fi } # Create log group for service connect proxy aws logs create-log-group --log-group-name "/ecs/service-connect-proxy" 2>&1 | grep -v "ResourceAlreadyExistsException" || { if [ ${PIPESTATUS[0]} -eq 0 ]; then log "Log group /ecs/service-connect-proxy created" track_resource "LOG_GROUP:/ecs/service-connect-proxy" else log "Log group /ecs/service-connect-proxy already exists" fi } } # Function to create ECS cluster with Service Connect create_ecs_cluster() { log "Creating ECS cluster with Service Connect..." CLUSTER_OUTPUT=$(aws ecs create-cluster \ --cluster-name "$CLUSTER_NAME" \ --service-connect-defaults namespace="$NAMESPACE_NAME" \ --tags key=Environment,value=tutorial 2>&1) check_for_errors "$CLUSTER_OUTPUT" "create-cluster" track_resource "CLUSTER:$CLUSTER_NAME" log "Created ECS cluster: $CLUSTER_NAME" wait_for_resource "cluster" "$CLUSTER_NAME" # Track the Service Connect namespace that gets created # Wait a moment for the namespace to be created sleep 5 NAMESPACE_ID=$(aws servicediscovery list-namespaces \ --filters Name=TYPE,Values=HTTP \ --query "Namespaces[?Name=='$NAMESPACE_NAME'].Id" --output text 2>/dev/null || echo "") if [[ -n "$NAMESPACE_ID" && "$NAMESPACE_ID" != "None" ]]; then track_resource "NAMESPACE:$NAMESPACE_ID" log "Service Connect namespace created: $NAMESPACE_ID" fi } # Function to create IAM roles create_iam_roles() { log "Creating IAM roles..." # Check if ecsTaskExecutionRole exists if aws iam get-role --role-name ecsTaskExecutionRole >/dev/null 2>&1; then log "IAM role ecsTaskExecutionRole exists" else log "Creating ecsTaskExecutionRole..." aws iam create-role \ --role-name ecsTaskExecutionRole \ --assume-role-policy-document '{ "Version":"2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "ecs-tasks.amazonaws.com"}, "Action": "sts:AssumeRole" }] }' >/dev/null 2>&1 aws iam attach-role-policy \ --role-name ecsTaskExecutionRole \ --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy >/dev/null 2>&1 track_resource "ROLE:ecsTaskExecutionRole" log "Created ecsTaskExecutionRole" sleep 10 fi # Check if ecsTaskRole exists, create if not if aws iam get-role --role-name ecsTaskRole >/dev/null 2>&1; then log "IAM role ecsTaskRole exists" else log "IAM role ecsTaskRole does not exist, will create it" # Create trust policy for ECS tasks cat > /tmp/ecs-task-trust-policy.json << EOF { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF aws iam create-role \ --role-name ecsTaskRole \ --assume-role-policy-document file:///tmp/ecs-task-trust-policy.json >/dev/null track_resource "IAM_ROLE:ecsTaskRole" log "Created ecsTaskRole" # Wait for role to be available sleep 10 fi } # Function to create task definition create_task_definition() { log "Creating task definition..." # Create task definition JSON cat > /tmp/task-definition.json << EOF { "family": "service-connect-nginx", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "executionRoleArn": "arn:aws:iam::${ACCOUNT_ID}:role/ecsTaskExecutionRole", "taskRoleArn": "arn:aws:iam::${ACCOUNT_ID}:role/ecsTaskRole", "containerDefinitions": [ { "name": "nginx", "image": "public.ecr.aws/docker/library/nginx:latest", "portMappings": [ { "containerPort": 80, "protocol": "tcp", "name": "nginx-port" } ], "essential": true, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/service-connect-nginx", "awslogs-region": "${REGION}", "awslogs-stream-prefix": "ecs" } } } ] } EOF TASK_DEF_OUTPUT=$(aws ecs register-task-definition --cli-input-json file:///tmp/task-definition.json 2>&1) check_for_errors "$TASK_DEF_OUTPUT" "register-task-definition" TASK_DEF_ARN=$(echo "$TASK_DEF_OUTPUT" | grep -o '"taskDefinitionArn": "[^"]*"' | cut -d'"' -f4) track_resource "TASK_DEF:service-connect-nginx" log "Created task definition: $TASK_DEF_ARN" # Clean up temporary file rm -f /tmp/task-definition.json } # Function to create ECS service with Service Connect create_ecs_service() { log "Creating ECS service with Service Connect..." # Create service definition JSON cat > /tmp/service-definition.json << EOF { "serviceName": "service-connect-nginx-service", "cluster": "${CLUSTER_NAME}", "taskDefinition": "service-connect-nginx", "desiredCount": 1, "launchType": "FARGATE", "networkConfiguration": { "awsvpcConfiguration": { "subnets": ["${PUBLIC_SUBNET1}", "${PUBLIC_SUBNET2}"], "securityGroups": ["${SECURITY_GROUP_ID}"], "assignPublicIp": "ENABLED" } }, "serviceConnectConfiguration": { "enabled": true, "namespace": "${NAMESPACE_NAME}", "services": [ { "portName": "nginx-port", "discoveryName": "nginx", "clientAliases": [ { "port": 80, "dnsName": "nginx" } ] } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/service-connect-proxy", "awslogs-region": "${REGION}", "awslogs-stream-prefix": "ecs-service-connect" } } }, "tags": [ { "key": "Environment", "value": "tutorial" } ] } EOF SERVICE_OUTPUT=$(aws ecs create-service --cli-input-json file:///tmp/service-definition.json 2>&1) check_for_errors "$SERVICE_OUTPUT" "create-service" track_resource "SERVICE:service-connect-nginx-service" log "Created ECS service: service-connect-nginx-service" wait_for_resource "service" "service-connect-nginx-service" # Clean up temporary file rm -f /tmp/service-definition.json } # Function to verify deployment verify_deployment() { log "Verifying deployment..." # Check service status SERVICE_STATUS=$(aws ecs describe-services \ --cluster "$CLUSTER_NAME" \ --services "service-connect-nginx-service" \ --query 'services[0].status' --output text) log "Service status: $SERVICE_STATUS" # Check running tasks RUNNING_COUNT=$(aws ecs describe-services \ --cluster "$CLUSTER_NAME" \ --services "service-connect-nginx-service" \ --query 'services[0].runningCount' --output text) log "Running tasks: $RUNNING_COUNT" # Get task ARN TASK_ARN=$(aws ecs list-tasks \ --cluster "$CLUSTER_NAME" \ --service-name "service-connect-nginx-service" \ --query 'taskArns[0]' --output text) if [[ "$TASK_ARN" != "None" && -n "$TASK_ARN" ]]; then log "Task ARN: $TASK_ARN" # Try to get task IP address TASK_IP=$(aws ecs describe-tasks \ --cluster "$CLUSTER_NAME" \ --tasks "$TASK_ARN" \ --query 'tasks[0].attachments[0].details[?name==`privateIPv4Address`].value' \ --output text 2>/dev/null || echo "") if [[ -n "$TASK_IP" && "$TASK_IP" != "None" ]]; then log "Task IP address: $TASK_IP" else log "Could not retrieve task IP address" fi fi # Check Service Connect namespace NAMESPACE_STATUS=$(aws servicediscovery list-namespaces \ --filters Name=TYPE,Values=HTTP \ --query "Namespaces[?Name=='$NAMESPACE_NAME'].Id" --output text 2>/dev/null || echo "") if [[ -n "$NAMESPACE_STATUS" && "$NAMESPACE_STATUS" != "None" ]]; then log "Service Connect namespace '$NAMESPACE_NAME' is active" else log "Service Connect namespace '$NAMESPACE_NAME' not found or not active" fi # Display Service Connect configuration log "Service Connect configuration:" aws ecs describe-services \ --cluster "$CLUSTER_NAME" \ --services "service-connect-nginx-service" \ --query 'services[0].serviceConnectConfiguration' 2>/dev/null || true } # Function to display created resources display_resources() { echo "" echo "===========================================" echo "CREATED RESOURCES" echo "===========================================" for resource in "${CREATED_RESOURCES[@]}"; do echo "- $resource" done echo "===========================================" echo "" } # Function to cleanup resources cleanup_resources() { log "Starting cleanup process..." # Delete resources in reverse order of creation for ((i=${#CREATED_RESOURCES[@]}-1; i>=0; i--)); do resource="${CREATED_RESOURCES[i]}" resource_type=$(echo "$resource" | cut -d':' -f1) resource_id=$(echo "$resource" | cut -d':' -f2) log "Cleaning up $resource_type: $resource_id" case "$resource_type" in "SERVICE") aws ecs update-service --cluster "$CLUSTER_NAME" --service "$resource_id" --desired-count 0 2>&1 | grep -qi "error" && log "Warning: Failed to scale down service $resource_id" aws ecs wait services-stable --cluster "$CLUSTER_NAME" --services "$resource_id" 2>/dev/null || true aws ecs delete-service --cluster "$CLUSTER_NAME" --service "$resource_id" --force 2>&1 | grep -qi "error" && log "Warning: Failed to delete service $resource_id" ;; "TASK_DEF") TASK_DEF_ARNS=$(aws ecs list-task-definitions --family-prefix "$resource_id" --query 'taskDefinitionArns' --output text 2>/dev/null) for arn in $TASK_DEF_ARNS; do aws ecs deregister-task-definition --task-definition "$arn" >/dev/null 2>&1 || true done ;; "ROLE") aws iam detach-role-policy --role-name "$resource_id" --policy-arn "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" 2>/dev/null || true aws iam delete-role --role-name "$resource_id" 2>&1 | grep -qi "error" && log "Warning: Failed to delete role $resource_id" ;; "IAM_ROLE") aws iam detach-role-policy --role-name "$resource_id" --policy-arn "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" 2>/dev/null || true aws iam delete-role --role-name "$resource_id" 2>&1 | grep -qi "error" && log "Warning: Failed to delete role $resource_id" ;; "CLUSTER") aws ecs delete-cluster --cluster "$resource_id" 2>&1 | grep -qi "error" && log "Warning: Failed to delete cluster $resource_id" ;; "SG") for attempt in 1 2 3 4 5; do if aws ec2 delete-security-group --group-id "$resource_id" 2>/dev/null; then break fi log "Security group $resource_id still has dependencies, retrying in 30s ($attempt/5)..." sleep 30 done ;; "LOG_GROUP") aws logs delete-log-group --log-group-name "$resource_id" 2>&1 | grep -qi "error" && log "Warning: Failed to delete log group $resource_id" ;; "NAMESPACE") # First, delete any services in the namespace NAMESPACE_SERVICES=$(aws servicediscovery list-services \ --filters Name=NAMESPACE_ID,Values="$resource_id" \ --query 'Services[].Id' --output text 2>/dev/null || echo "") if [[ -n "$NAMESPACE_SERVICES" && "$NAMESPACE_SERVICES" != "None" ]]; then for service_id in $NAMESPACE_SERVICES; do aws servicediscovery delete-service --id "$service_id" >/dev/null 2>&1 || true sleep 2 done fi # Then delete the namespace aws servicediscovery delete-namespace --id "$resource_id" >/dev/null 2>&1 || true ;; esac sleep 2 # Brief pause between deletions done # Clean up temporary files rm -f /tmp/ecs-task-trust-policy.json rm -f /tmp/task-definition.json rm -f /tmp/service-definition.json log "Cleanup completed" } # Main execution main() { log "Starting $SCRIPT_NAME v4 (Default VPC)" log "Region: $REGION" log "Log file: $LOG_FILE" # Get AWS account ID get_account_id # Setup infrastructure using default VPC setup_default_vpc_infrastructure # Create CloudWatch log groups create_log_groups # Create ECS cluster create_ecs_cluster # Create IAM roles create_iam_roles # Create task definition create_task_definition # Create ECS service create_ecs_service # Verify deployment verify_deployment log "Tutorial completed successfully!" # Display created resources display_resources # Ask user if they want to clean up echo "" echo "===========================================" echo "CLEANUP CONFIRMATION" echo "===========================================" echo "Do you want to clean up all created resources? (y/n): " read -r CLEANUP_CHOICE if [[ "$CLEANUP_CHOICE" =~ ^[Yy]$ ]]; then cleanup_resources log "All resources have been cleaned up" else log "Resources left intact. You can clean them up later by running the cleanup function." echo "" echo "To clean up resources later, you can use the AWS CLI commands or the AWS Management Console." echo "Remember to delete resources in the correct order to avoid dependency issues." fi } # Make script executable and run chmod +x "$0" main "$@"-
For API details, see the following topics in Amazon CLI Command Reference.
-
For a complete list of Amazon SDK developer guides and code examples, see Create Amazon EC2 resources using an Amazon SDK. This topic also includes information about getting started and details about previous SDK versions.
Build and manage a resilient service
Create a basic VPC