Here's an architecture diagram from JHipster on how your architecture would look like once generated.
The first service to start is the registry as all others depend on it, then each other service can start independently. As they each boot up they will register themselves with the registry. This registration contains their IP address, port and the service name. Now whenever a service needs to know how to communicate to another service it can ask the registry where it's located.
As the registry is required for everything else in the stack, it's recommended to deploy it as a standalone on EC2.
This is where you'll encounter the first issue when using ECS (or other container orchestration services). When the service registers within an ECS task, it doesn't actually know it's own IP. The default is it will just check it's IP on the network, however that won't work as it will be the IP within the container.
The other option is to set the property: eureka.instance.ip-address
.
This will only work if we can figure out what IP to put here. Since we're using ECS we can't know the IP until the task has started.
AWS provides us with an API that we can query to figure out what the ECS task's IP is, and we can do that each time our docker container starts using an Entrypoint
in our Dockerfile
. The location of the API is already available on the path of any ECS task as ECS_CONTAINER_METADATA_URI
:
Here's our entrypoint.sh:
export ECS_INSTANCE_IP_TASK=$(curl --retry 5 -connect-timeout 3 -s ${ECS_CONTAINER_METADATA_URI})
export ECS_INSTANCE_IP_ADDRESS=$(echo ${ECS_INSTANCE_IP_TASK} | python3 -c "import sys, json; print(json.load(sys.stdin)['Networks'][0]['IPv4Addresses'][0])")
echo "ECS Instance IP response: " ${ECS_INSTANCE_IP_ADDRESS}
echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP}
exec java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} "${HOME}/app.jar" "$@"
We now edit our property file with the following:
eureka:
instance:
preferIpAddress: true
ip-address: ${ECS_INSTANCE_IP_ADDRESS:localhost}
nonSecurePort: ${server.port}
This will set the IP to either the IP address from the entrypoint if the variable exists or to localhost if not. This is so local development isn't broken.
Now when our microservice starts within ECS it will query for its IP and set it on the path prior to booting. This will allow them to correctly register themselves.
Comments