To expand on the answer provided by dfrdmn:
While this answer works well in most cases, it has a couple small potential problems.
AWS ELB Network Load Balancers
First, if you are using an ELB network load balancer, this method won't work with its HTTP health checks because the load balancer sends the IP address of the load balancer in the HTTP host header. From the AWS docs:
The HTTP host header in the health check request contains the IP address of the load balancer node and the listener port, not the IP address of the target and the health check port. If you are mapping incoming requests by host header, you must ensure that health checks match any HTTP host header. Another option is to add a separate HTTP service on a different port and configure the target group to use that port for health checks instead. Alternatively, consider using TCP health checks.
So, adding your instance (target group) IP to your ALLOWED_HOSTS
will not work. As stated, you could use TCP health checks, or you could use the middleware approach described in another answer.
Metadata endpoint is throttled
Second, because the metadata endpoint limits number of concurrent connections and throttles requests, you may encounter issues in some cases.
Your Django settings.py
file is executed for every process and any time processes need to restart. This is important if your webserver is configured to use multiple processes, such as when using gunicorn workers, as is commonly configured to properly take full advantage of system CPU resources.
This means that, with enough processes, your settings.py
file will be executed many times, sending many concurrent requests to the metadata endpoint and your processes could fail to start. Further, on subsequent process restarts, the throttling will exacerbate the throttling problem. In some circumstances, this can cause your application to grind to a halt or have fewer processes running than intended.
To get around this, you could do a few things:
- Obtain the IP address before starting your server and set the IP address as an environment variable, then read the environment variable to add it to your allowed hosts.
$ export ALLOWED_HOST_EC2_PRIVATE_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4)
$ gunicorn -w 10 ... myapp:app
# settings.py
ALLOWED_HOSTS = ['myhost.tld', ]
if os.getenv('ALLOWED_HOST_EC2_PRIVATE_IP'):
ALLOWED_HOSTS.append(os.environ['ALLOWED_HOST_EC2_PRIVATE_IP'])
You may yet still encounter throttling issues with the metadata endpoint if many applications or other services utilize the instance's metadata at the same time.
- For services running in containers on ECS you can use the container metadata file
You can do this safely within the settings.py
because there is no throttling or rate limit for accessing this file. This also avoids your application potentially interfering with other services that need the instance's metadata endpoint.
# settings.py
import os
import json
ALLOWED_HOSTS = ['myhost.tld', ]
if os.getenv('ECS_CONTAINER_METADATA_FILE'):
metadata_file_path = os.environ['ECS_CONTAINER_METADATA_FILE']
with open(metadata_file_path) as f:
metadata = json.load(f)
private_ip = metadata["HostPrivateIPv4Address"]
ALLOWED_HOSTS.append(private_ip)
You could also combine the first approach with the metadata file, in your container's ENTRYPOINT.
#!/usr/bin/env bash
# docker-entrypoint.sh
export ALLOWED_HOST_EC2_PRIVATE_IP=$(jq -r .HostPrivateIPv4Address $ECS_CONTAINER_METADATA_FILE)
exec "$@"
FROM myapplication
COPY docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["gunicorn", "whatever"]