将 Django 部署到 AWS Fargate 时如何将本地 ip 添加到 ALLOWED_HOSTS

When deploying Django into AWS Fargate how do you add the local ip into ALLOWED_HOSTS

我正在测试将我的 Django 应用程序部署到 AWS 的 Fargate 服务中。

一切似乎 运行,但我收到运行状况检查错误,因为 Application Load Balancer 使用主机的本地 IP 向我的 Django 应用程序发送请求。这在日志中给我一个允许的主机错误。

Invalid HTTP_HOST header: '172.31.86.159:8000'. You may need to add '172.31.86.159' to ALLOWED_HOSTS

我曾尝试在任务启动时获取本地 ip 并将其附加到我的 ALLOWED_HOSTS,但这在 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)

有没有办法获取 ENI IP 地址以便我可以将其附加到 ALLOWED_HOSTS?

现在可行,并且与文档一致,但我不知道这是最好的方法还是有更好的方法。

我的容器在awsvpc网络模式下是运行。

https://aws.amazon.com/blogs/compute/under-the-hood-task-networking-for-amazon-ecs/

...the ECS agent creates an additional "pause" container for each task before starting the containers in the task definition. It then sets up the network namespace of the pause container by executing the previously mentioned CNI plugins. It also starts the rest of the containers in the task so that they share their network stack of the pause container. (emphasis mine)

我假设

so that they share their network stack of the pause container

意味着我们真的只需要暂停容器的 IPv4 地址。在我的 non-exhaustive 测试中,ECS 元中似乎总是 Container[0]http://169.254.170.2/v2/metadata

根据这些假设,这确实有效,尽管我不知道这样做有多明智:

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)

当然,如果我们传入ECS任务定义中必须设置的容器名称,我们也可以这样做:

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)

这些代码片段中的任何一个都会出现在 Django 的生产设置中。

是否有我缺少的更好的方法?同样,这是为了允许 Application Load Balancer 运行状况检查。使用 ECS (Fargate) 时,ALB 发送主机 header 作为容器的本地 IP。

在fargate中,AWS容器代理注入了一个环境变量:${ECS_CONTAINER_METADATA_URI}

这包含 URL 到元数据端点,所以现在您可以

curl ${ECS_CONTAINER_METADATA_URI}

输出类似于

{  
   "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"
         ]
      }
   ]
}

在键 Networks 下你会发现 IPv4Address

将其代入 python,您将得到

METADATA_URI = os.environ['ECS_CONTAINER_METADATA_URI']
container_metadata = requests.get(METADATA_URI).json()
ALLOWED_HOSTS.append(container_metadata['Networks'][0]['IPv4Addresses'][0])

另一种解决方案是创建一个中间件来绕过 ALLOWED_HOSTS 仅针对您的健康检查端点的检查,例如

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")