Tasks running on Amazon EC2 Container Service (Amazon ECS) can take advantage of awsvpc mode for container networking. This mode allocates an elastic networking interface to each running task, providing a dynamic private IP address and internal DNS name. This simplifies container networking management and operations, allowing tasks to run with full networking features on AWS.
Amazon ECS recommends using the awsvpc network mode unless you have a specific need to use a different network mode.
Below is a diagram of the AWSVPC mode for EC2 launch type:
In the task definition enter the following parameter for network mode
{
...
"containerDefinitions": [
...
],
...
"networkMode": "awsvpc",
...
}
To run a task in AWSVPC networking mode provide a network configuration structure as follows:
{
"awsvpcConfiguration": {
"subnets": ["string", ...],
"securityGroups": ["string", ...],
"assignPublicIp": "ENABLED"|"DISABLED"
}
}
There is a default limit to the number of network interfaces that can be attached to an Amazon EC2 instance. Amazon ECS supports launching container instances with increased ENI density using supported Amazon EC2 instance types. When you use these instance types and opt in to the ‘awsvpcTrunking’ account setting, additional ENIs are available on newly launched container instances. For details see here
For comprehensive considerations see here.
One can leverage the new “ECS exec” feature to access containers and check the network configuration. For details including prerequisites for using this feature see blog post
Note: The executables you want to run in the interactive shell session must be available in the container image!
source ~/.bashrc
cd ~/environment/ecsworkshop/content/networking_sd/ecs_networking/setup/
export TASK_FILE=ecs-networking-demo-awsvpc-mode.json
envsubst < ${TASK_FILE}.template > ${TASK_FILE}
export TASK_DEF=$(aws ecs register-task-definition --cli-input-json file://${TASK_FILE} --query 'taskDefinition.taskDefinitionArn' --output text)
export TASK_ARN=$(aws ecs run-task --cluster ${ClusterName} --task-definition ${TASK_DEF} \
--network-configuration "awsvpcConfiguration={subnets=[${PrivateSubnetOne},${PrivateSubnetTwo}],securityGroups=[${ContainerSecurityGroup}],assignPublicIp=DISABLED}" \
--enable-execute-command --launch-type EC2 --query 'tasks[0].taskArn' --output text)
aws ecs describe-tasks --cluster ${ClusterName} --task ${TASK_ARN}
# sleep to let the container start
sleep 30
aws ecs execute-command --cluster ${ClusterName} --task ${TASK_ARN} --container nginx --command "/bin/sh" --interactive
Inside the container run the following commands. Note that the container has acquired an IP of the VPC CIDR 10.0.0.0/16 for “eth0”:
ip a sh
ip link sh
ip r sh
curl localhost:80
# to leave the interactive session type exit
Sample outputs for awsvpc network mode of a task running a nginx:alpine container which contains the required net-tools package required for running “ip” commands using ECS exec:
$ aws ecs execute-command --cluster staging --task 374eb66626904a238001bc6301e3cbea --container nginx --command "/bin/sh" --interactive
The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.
Starting session with SessionId: ecs-execute-command-09a0ac6bc7bdf6020
/ # ip a sh
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
...
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP qlen 1000
link/ether 06:d0:20:80:ed:6a brd ff:ff:ff:ff:ff:ff
inet 10.0.1.121/24 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::4d0:20ff:fe80:ed6a/64 scope link
valid_lft forever preferred_lft forever
/ # ip l sh
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
...
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP qlen 1000
link/ether 06:d0:20:80:ed:6a brd ff:ff:ff:ff:ff:ff
/ # ip r sh
default via 10.0.1.1 dev eth1
10.0.1.0/24 dev eth1 scope link src 10.0.1.121
...
/ # curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
…
Another approach is to access the ECS EC2 instance running your task as a priviledged Linux user to observe some details:
CONT_INST_ID=$(aws ecs list-container-instances --cluster ${ClusterName} --query 'containerInstanceArns[]' --output text)
EC2_INST_ID=$(aws ecs describe-container-instances --cluster ${ClusterName} --container-instances ${CONT_INST_ID} --query 'containerInstances[0].ec2InstanceId' --output text)
aws ssm start-session --target ${EC2_INST_ID}
and to run the following commands inside the instance:
sudo -i
docker ps
CONT_ID=$(docker ps --format "{{.ID}} {{.Image}}" | grep nginx:alpine | awk '{print $1}')
PID=$(docker inspect -f '{{.State.Pid}}' $CONT_ID)
ip a sh eth0
nsenter -t $PID -n ip a show
CONT_IP=$(nsenter -t $PID -n ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)
curl ${CONT_IP}:80
# to leave the interactive session type exit twice
Sample outputs for awsvpc network mode of a task running a nginx container.
Note: eth0 of the EC2 instance and eth0 of the container are different and belong to the same VPC/network CIDR:
[root@ip-xxx ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8dce1c4c8611 nginx "/docker-entrypoint.…" 7 days ago Up 7 days ecs-web-1-web-e0ddf8998e99d4dc0300
[root@ip-xxx ~]# PID=$(docker inspect -f '{{.State.Pid}}' 8dce1c4c8611)
[root@ip-10-0-100-21 ~]# ip a sh eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
link/ether 02:3c:af:b8:6d:52 brd ff:ff:ff:ff:ff:ff
inet 10.0.100.21/24 brd 10.0.100.255 scope global dynamic eth0
valid_lft 2333sec preferred_lft 2333sec
inet6 fe80::3c:afff:feb8:6d52/64 scope link
valid_lft forever preferred_lft forever
[root@ip-xxx ~]# nsenter -t $PID -n ip a show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
3: ecs-eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 0a:58:a9:fe:ac:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 169.254.172.2/22 scope global ecs-eth0
valid_lft forever preferred_lft forever
4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 02:15:66:37:08:ee brd ff:ff:ff:ff:ff:ff
inet 10.0.100.154/24 scope global eth0
valid_lft forever preferred_lft forever
[root@ip-xxx ~]# curl 10.0.100.154:80
<!DOCTYPE html>
<html>
...
<h1>Welcome to nginx!</h1>
...
</html>
Cleanup the task:
aws ecs stop-task --cluster ${ClusterName} --task ${TASK_ARN}