Estoy probando la implementación de mi aplicación Django en el servicio Fargate de AWS.
Todo parece funcionar, pero recibo errores de Health Check ya que Application Load Balancer envía solicitudes a mi aplicación Django usando la IP local del host. Esto me da un error de host permitido en los registros.
Encabezado HTTP_HOST no válido: '172.31.86.159:8000'. Es posible que deba agregar '172.31.86.159' a ALLOWED_HOSTS
Intenté obtener la IP local en el momento de inicio de la tarea y agregarla a mi ALLOWED_HOSTS, pero esto falla en Fargate:
import requests EC2_PRIVATE_IP = None try: EC2_PRIVATE_IP = requests.get('http://169.254.169.254/latest/meta-data/local-ipv4', timeout = 0.01).text except requests.exceptions.RequestException: pass if EC2_PRIVATE_IP: ALLOWED_HOSTS.append(EC2_PRIVATE_IP)¿Hay alguna forma de obtener la dirección IP de ENI para poder agregarla a ALLOWED_HOSTS?
Ahora bien, esto funciona y se alinea con la documentación, pero no sé si es la MEJOR manera o si hay una MEJOR MANERA.
Mis contenedores se ejecutan en el modo de red awsvpc .
https://aws.amazon.com/blogs/compute/under-the-hood-task-networking-for-amazon-ecs/
...el agente de ECS crea un contenedor de "pausa" adicional para cada tarea antes de iniciar los contenedores en la definición de la tarea. Luego configura el espacio de nombres de red del contenedor de pausa ejecutando los complementos CNI mencionados anteriormente. También inicia el resto de contenedores en la tarea para que compartan su pila de red del contenedor de pausa. (énfasis mío)
asumo el
para que compartan su pila de red del contenedor de pausa
Significa que realmente solo necesitamos la dirección IPv4 del contenedor de pausa. En mis pruebas no exhaustivas, parece que esto siempre es Container[0] en el metadato de ECS: http://169.254.170.2/v2/metadata
Con esas suposiciones en juego, esto funciona, aunque no sé qué tan inteligente es hacerlo:
import requests EC2_PRIVATE_IP = None METADATA_URI = os.environ.get('ECS_CONTAINER_METADATA_URI', 'http://169.254.170.2/v2/metadata') try: resp = requests.get(METADATA_URI) data = resp.json() # print(data) container_meta = data['Containers'][0] EC2_PRIVATE_IP = container_meta['Networks'][0]['IPv4Addresses'][0] except: # silently fail as we may not be in an ECS environment pass if EC2_PRIVATE_IP: # Be sure your ALLOWED_HOSTS is a list NOT a tuple # or .append() will fail ALLOWED_HOSTS.append(EC2_PRIVATE_IP)Por supuesto, si pasamos el nombre del contenedor que debemos establecer en la definición de la tarea de ECS, también podríamos hacer esto:
import os import requests EC2_PRIVATE_IP = None METADATA_URI = os.environ.get('ECS_CONTAINER_METADATA_URI', 'http://169.254.170.2/v2/metadata') try: resp = requests.get(METADATA_URI) data = resp.json() # print(data) container_name = os.environ.get('DOCKER_CONTAINER_NAME', None) search_results = [x for x in data['Containers'] if x['Name'] == container_name] if len(search_results) > 0: container_meta = search_results[0] else: # Fall back to the pause container container_meta = data['Containers'][0] EC2_PRIVATE_IP = container_meta['Networks'][0]['IPv4Addresses'][0] except: # silently fail as we may not be in an ECS environment pass if EC2_PRIVATE_IP: # Be sure your ALLOWED_HOSTS is a list NOT a tuple # or .append() will fail ALLOWED_HOSTS.append(EC2_PRIVATE_IP)Cualquiera de estos fragmentos de código entraría en la configuración de producción de Django.
¿Hay una mejor manera de hacer esto que me estoy perdiendo? Nuevamente, esto es para permitir las verificaciones de estado del balanceador de carga de aplicaciones. Cuando se usa ECS (Fargate), el ALB envía el encabezado del host como la IP local del contenedor.
En fargate, hay una variable de entorno inyectada por el agente de contenedor de AWS: ${ECS_CONTAINER_METADATA_URI}
Esto contiene la URL del punto final de metadatos, por lo que ahora puede hacer
curl ${ECS_CONTAINER_METADATA_URI}La salida se ve algo como
{ "DockerId":"redact", "Name":"redact", "DockerName":"ecs-redact", "Image":"redact", "ImageID":"redact", "Labels":{ }, "DesiredStatus":"RUNNING", "KnownStatus":"RUNNING", "Limits":{ }, "CreatedAt":"2019-04-16T22:39:57.040286277Z", "StartedAt":"2019-04-16T22:39:57.29386087Z", "Type":"NORMAL", "Networks":[ { "NetworkMode":"awsvpc", "IPv4Addresses":[ "172.30.1.115" ] } ] } Debajo de la clave Networks encontrará IPv4Address
Poniendo esto en python, obtienes
METADATA_URI = os.environ['ECS_CONTAINER_METADATA_URI'] container_metadata = requests.get(METADATA_URI).json() ALLOWED_HOSTS.append(container_metadata['Networks'][0]['IPv4Addresses'][0]) Una solución alternativa a esto es crear un middleware que omita la verificación ALLOWED_HOSTS solo para su punto final de verificación de estado, por ejemplo
from django.http import HttpResponse from django.utils.deprecation import MiddlewareMixin class HealthEndpointMiddleware(MiddlewareMixin): def process_request(self, request): if request.META["PATH_INFO"] == "/health/": return HttpResponse("OK")