【问题标题】:Django ALLOWED_HOSTS with ELB HealthCheck带有 ELB HealthCheck 的 Django ALLOWED_HOSTS
【发布时间】:2015-02-27 11:48:23
【问题描述】:

我在 Elastic Beanstalk 上部署了一个 django 应用程序。我的应用程序的 HealthCheck 一直失败,因为 ELB HealthCheck 的 IP 未包含在我的 ALLOWED_HOSTS 设置变量中。

如何修改ALLOWED_HOSTS 以使 HealthCheck 通过?我只会传入显式 IP 地址,但我相信这会发生变化,因此每当 IP 更改时,检查都会再次失败,直到我添加新 IP。

【问题讨论】:

  • 运气好了吗?我也面临同样的问题
  • 我只是硬编码了 IP 地址。反正只要我有网站运行就一直工作

标签: django amazon-web-services amazon-ec2 amazon-elb amazon-elastic-beanstalk


【解决方案1】:

解决方案

对我有用的解决方案是简单地安装 django-ebhealthcheck 库。安装后,只需将ebhealthcheck.apps.EBHealthCheckConfig 添加到您的 INSTALLED_APPS。

来自 django-ebhealthcheck GitHub:

默认情况下,Elastic Beanstalk 的健康检查系统使用公共 IP 每个负载均衡的实例作为请求的主机头时 提出要求。除非添加到 ALLOWED_HOSTS,否则这会导致 Django 返回 400 Bad Request 和失败的健康检查。

此应用将您实例的公共 IP 地址动态添加到 Django 的 ALLOWED_HOSTS 设置允许健康检查成功。 这发生在应用程序启动时。

2.0.0 及更高版本支持 IMDSv2。如果您使用的是 v1 并且 无法升级,请改用此库的版本 1(pip install django-ebhealthcheck


安装

  1. pip install django-ebhealthcheck

  2. ebhealthcheck.apps.EBHealthCheckConfig 添加到您的 INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    'ebhealthcheck.apps.EBHealthCheckConfig',
    ...
]

【讨论】:

    【解决方案2】:

    扩展the answer provided by dfrdmn

    虽然这个答案在大多数情况下效果很好,但它有几个潜在的小问题。

    AWS ELB 网络负载均衡器

    首先,如果您使用的是 ELB 网络 负载均衡器,此方法将无法使用其 HTTP 健康检查,因为负载均衡器会发送 负载均衡器的 IP 地址 em> 在 HTTP 主机标头中。来自the AWS docs

    健康检查请求中的HTTP主机头包含负载均衡节点的IP地址和监听端口,而不是目标的IP地址和健康检查端口。如果您按主机标头映射传入请求,则必须确保运行状况检查与任何 HTTP 主机标头匹配。另一种选择是在不同的端口上添加单独的 HTTP 服务,并将目标组配置为使用该端口进行健康检查。或者,考虑使用 TCP 健康检查。

    因此,将您的实例(目标组)IP 添加到您的ALLOWED_HOSTS 将不起作用。如前所述,您可以使用 TCP 健康检查,也可以使用另一个答案中描述的中间件方法。

    元数据端点被限制

    其次,由于元数据端点限制并发连接数和throttles请求数,在某些情况下您可能会遇到问题。

    您的 Django settings.py 文件为每个 进程执行,并且任何时候进程都需要重新启动。如果您的网络服务器配置为使用多个进程(例如使用 gunicorn workers 时),这一点很重要,因为通常配置为正确充分利用系统 CPU 资源。

    这意味着,如果有足够的进程,您的settings.py 文件将被执行多次,向元数据端点发送许多并发请求,您的进程可能无法启动。此外,在后续进程重新启动时,节流将加剧节流问题。在某些情况下,这可能会导致您的应用程序停止运行或运行的进程少于预期。

    要解决这个问题,您可以做一些事情:

    1. 在启动服务器之前获取 IP 地址并将 IP 地址设置为环境变量,然后读取环境变量以将其添加到允许的主机中。
    $ 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'])
    

    如果许多应用程序或其他服务同时使用实例的元数据,您可能仍会遇到元数据端点的限制问题。

    1. 对于在 ECS 上的容器中运行的服务,您可以使用 container metadata file

    您可以在settings.py 中安全地执行此操作,因为访问此文件没有限制或速率限制。这也避免了您的应用程序可能干扰需要实例元数据端点的其他服务。

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

    您还可以在容器的 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"]
    

    【讨论】:

      【解决方案3】:

      这是另一个使用 Django 中间件的解决方案。

      Django 的django.middleware.common.CommonMiddleware 调用request.get_host(),它使用ALLOWED_HOSTS 验证请求。如果您只是想检查应用程序是否正在运行,您可以创建这样的中间件。

      from django.http import HttpResponse
      
      
      class HealthCheckMiddleware:
          def __init__(self, get_response):
              self.get_response = get_response
      
          def __call__(self, request):
              if request.path == '/health':
                  return HttpResponse('ok')
              return self.get_response(request)
      

      然后把你的HealthCheckMiddleware放在CommonMiddleware前面settings.py

      MIDDLEWARE = [
          'yourdjangoapp.middleware.HealthCheckMiddleware',
          ......
          'django.middleware.common.CommonMiddleware',
          ......
      ]
      

      只要您的应用程序在运行,无论任何配置如何,您的应用程序都会始终使用ok 响应路径/health

      【讨论】:

      • 这对我来说似乎是一个很好的解决方案!
      • 这很好用。也适用于 Route53 http 健康检查。可以考虑添加对预期由 AWS HTTP 运行状况检查发送的用户代理字符串的检查。
      【解决方案4】:

      另一个解决方案没有回答这个问题,因为它没有考虑 AWS 拥有的所有各种工具(ELB 等)。我们最终要做的(因为我们使用nginx + uwsgi)是我们将标头设置为在用户发送请求时有效。

      如本页所述:

      https://opbeat.com/blog/posts/the-new-django-allowed_hosts-with-elb-uwsgi-and-nginx/

      我们将nginx 配置如下:

      set $my_host $host;
      if ($host ~ "\d+\.\d+\.\d+\.\d+") {
        set $my_host "example.com";
      }
      
      location / {
        uwsgi_pass unix:///tmp/mysite.com.sock;
        uwsgi_param HTTP_HOST $my_host;
        include uwsgi_params;
      }
      

      这里的关键是根据您的ALLOWED_HOSTS$my_host 设置一个有效值。

      不幸的是,没有不增加开销的“完美”解决方案。某些配置会要求您始终将 IP 地址添加到 ALLOWED_HOSTS,但此解决方案通常会以最少的开销工作。

      【讨论】:

      • 这实际上与允许 * 不一样吗?
      • @Kevin 有点像,但它完全不一样。它仍然带来相同的风险,尽管有所降低。此配置将防止发送带有 IP 地址以外的任何内容的错误主机标头。但是,如果攻击者使用流氓 IP 地址而不是流氓 DNS 名称来欺骗标头,主机标头检查所阻止的攻击向量仍然容易受到攻击。因此,不应该因此而推荐这种方法。更不用说,它会破坏依赖主机头的功能。
      【解决方案5】:

      您的 EC2 实例可以查询 metadata 自身的信息,包括其 IP 地址,该地址位于:http://169.254.169.254/latest/meta-data/local-ipv4

      您可以通过 ssh-ing 到您的 EC2 实例并运行来对此进行测试:

      curl http://169.254.169.254/latest/meta-data/local-ipv4
      

      因此,在您的配置中,您可以执行以下操作:

      import requests
      
      ALLOWED_HOSTS = ['.yourdomain.com', ]
      try:
          EC2_IP = requests.get('http://169.254.169.254/latest/meta-data/local-ipv4').text
          ALLOWED_HOSTS.append(EC2_IP)
      except requests.exceptions.RequestException:
          pass
      

      如果您不想要依赖项,显然可以将requests 替换为urllib

      【讨论】:

      • 这在这种情况下不起作用,因为用户试图获取 ELB 的 IP 地址,而不是他们正在运行的服务器。上述代码代码没有获取到ELB的IP地址。
      • 它确实有效,因为当 ELB 运行健康检查时,IP 是服务器的 IP,而不是 ELB
      • 请注意,使用 ELB network 负载平衡器时,HTTP 运行状况检查不适用于此方法,因为 HTTP 主机标头 contains the IP address of the load balancerThe 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
      猜你喜欢
      • 2016-06-21
      • 1970-01-01
      • 1970-01-01
      • 2015-04-11
      • 2016-08-30
      • 2016-06-06
      • 2018-04-24
      • 2018-02-17
      • 2017-01-20
      相关资源
      最近更新 更多