How to get host ip and port from inside ECS container
I have a situation where i need to know the host machine IP and assigned ports of my ECS instance, and that is because i am using a clustering application that requires to know port and IP’s of neighbors (Vertx), let me explain it.
I have several ports exposed as part of the Vertx framework in the same container, all vertx services together compose a cluster, so… every time a new container with a vertx application rises i need to expose somewhere this information. In order to do that i am creating a service registry with Hashicorp Consul.
Consul is a Concensus algoritm implementation and also an DNS service mostly used as service registry. Read more about Consul here and the RAFT consensus algorithm here.
To do the service registry we need a DNS record/ip + an assigned IP, something like:
service1:3701
192.168.00.25:234
When i am working in my development machine it is simple to me, i just expose the specific Host Machine IP to my container in an environment variable (example HOST_MACHINE_IP) and assign an static port to the container. That is something i cannot do in Amazon, because i don’t know the EC2 instance where my service is going to be running and to be scalable i need to assign ports dynamically.
Where do i get the damn machine IP?
Some of those stuff you are able to look into in the AWS console after the creation of the instances/services or the reboot, however you need to have that info at the very moment a service is created.
EC2/ECS Instance Metadata
EC2 instance metadata API provided by Amazon provides some of this information, the service is available right after the EC2 instance creation is complete.
To get the default network interface just invoke this url:
Take a look at the metadata API, documentation in here for more available data as hostname, public dns, private dns, ami id, etc.
We already have the public ip for our service, now we need the assigned ports to our containers.
Dynamic port assignation
As i’ve shown in the “vertx services” diagram, on each container the exposed ports are 5701 and 15701, these are not the port that will be assigned in the host machine, ECS will assign an port between 49153 to 65535 and do the port forwarding to the container.
In ECS to indicate a port to be dynamically assigned is just as easy as set a “0” in the “Host Port” field of task definition.
As a result an available port (not used yet) in the port range is bind to the container-exposed port.
In the image we see the exposed port by our docker container (5701 and 15701), however that port is available by accessing it trough 192.168.100.1:49153 and 192.168.100.1:49154 addresses.
Those are our service registries, that the vertx service will be on change of self register in the Consul server.
We already know the instance IP inside the docker container, what we are missing is the instance assigned ports, here is where it comes to the scene the ECS Metadata.
ECS metadata is provided by the ECS Agent installed on every cluster instance it is also available as a REST service in port 51678, you could CURL it from the ECS instance:
Or from the ECS container trough the docker network bridge ip (172.17.0.1):
http://172.17.0.1:51678/v1/metadata
The ECS agent brings information like cluster it belong, instance ARN, agent version, etc
{
"Cluster": "default",
"ContainerInstanceArn": "<container_instance_ARN>",
"Version": "Amazon ECS Agent - v1.14.3 (15de319)"
}
And the most important, information about our running tasks:
{
"Arn": "arn:aws:ecs:us-east-1:<aws_account_id>:task/e01d58a8-151b-40e8-bc01-22647b9ecfec",
"Containers": [
{
"DockerId": "79c796ed2a7f864f485c76f83f3165488097279d296a7c05bd5201a1c69b2920",
"DockerName": "ecs-nginx-efs-2-nginx-9ac0808dd0afa495f001",
"Name": "nginx"
}
],
"DesiredStatus": "RUNNING",
"Family": "nginx-efs",
"KnownStatus": "RUNNING",
"Version": "2"
}
You can read more about ECS agent and provided metadata in here.
At this point is where everything become complex, because to know the assigned ports of our container we need to know the task id of our container also the docker id and with that information query the ECS Agent metadata.
Internet is a magic place and i’ve found a python script that does everything of that for me:
https://gist.github.com/chris-smith-zocdoc/126db78651046c67ac66dbd87393b1dc
This script searches for the docker container id in a socket (/proc/1/cpuset), with that container id searches in the metadata for the Task ARN, with the task ARN search for the containers in the task, in the container reads the networkBindings sections and for each network binding (port assignation) exposes each as an environment variable in the format “PORT_{TCP|UDP}_{CONTAINER_PORT}, example PORT_TCP_5701=49153.
Finally i can have exposed as environment variables my ip and ports in a shell script startup.sh and read them from my vertx application :
export HOST_MACHINE_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4 2>/dev/null)
result=$(python3.4 /ecs_get_port.py)
eval “$result”
echo “Host machine ip is: $HOST_MACHINE_IP”
echo “Host assigned port is: $PORT_TCP_5701”
echo “Message bus port is: $PORT_TCP_15701”
I will not talk about Consul, or vertx in this article, in next Post i will talk about UserData, instance creation metadata and cloud-init.