简体   繁体   English

Django ALLOWED_HOSTS 与 ELB HealthCheck

[英]Django ALLOWED_HOSTS with ELB HealthCheck

I have a django application deployed on Elastic Beanstalk.我在 Elastic Beanstalk 上部署了一个 django 应用程序。 The HealthCheck for my app keeps failing because the IP of the ELB HealthCheck is not included in my ALLOWED_HOSTS settings variable.我的应用程序的 HealthCheck 一直失败,因为 ELB HealthCheck 的 IP 未包含在我的ALLOWED_HOSTS设置变量中。

How can I modify ALLOWED_HOSTS to make the HealthCheck pass?如何修改ALLOWED_HOSTS以使 HealthCheck 通过? I would just pass in the explicit IP address, but I believe that this changes, so whenever the IP changes the check would fail again until I add the new IP.我只会传入显式 IP 地址,但我相信这会发生变化,因此每当 IP 更改时,检查都会再次失败,直到我添加新 IP。

Your EC2 instance can query metadata about itself, including its IP address which is available at: http://169.254.169.254/latest/meta-data/local-ipv4 .您的 EC2 实例可以查询有关其自身的元数据,包括其 IP 地址,该地址位于: http : //169.254.169.254/latest/meta-data/local-ipv4

You can test this by ssh-ing into your EC2 instance and running:您可以通过 ssh-ing 到您的 EC2 实例并运行来测试:

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

So, in your configuration, you can do something like:因此,在您的配置中,您可以执行以下操作:

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

Obviously you can replace requests with urllib if you don't want the dependency.显然,如果您不想要依赖requests ,您可以用urllib替换requests

Here is another solution using Django Middleware.这是使用 Django 中间件的另一个解决方案。

Django's django.middleware.common.CommonMiddleware calls request.get_host() , which validates the request with ALLOWED_HOSTS . Django 的django.middleware.common.CommonMiddleware调用request.get_host() ,它使用ALLOWED_HOSTS验证请求。 If you simply want to check that the application is running, you can create a middleware like this.如果您只是想检查应用程序是否正在运行,您可以创建这样的中间件。

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)

And put your HealthCheckMiddleware in front of CommonMiddleware in settings.py并把你的HealthCheckMiddleware前面CommonMiddlewaresettings.py

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

And your application will always respond to path /health with ok as long as your app is running regardless of any configurations.只要您的应用程序在运行,无论任何配置如何,您的应用程序都将始终以ok响应路径/health

The other solution doesn't answer the question because it doesn't take into consideration all the various tools AWS has (ELB, etc).另一个解决方案没有回答这个问题,因为它没有考虑 AWS 拥有的所有各种工具(ELB 等)。 What we ended up doing (since we use nginx + uwsgi ) is we set the header to something valid when the user sends a request.我们最终做的(因为我们使用nginx + uwsgi )是我们在用户发送请求时将标头设置为有效的内容。

As documented on this page:如本页所述:

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

We put our nginx configuration as below:我们把我们的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;
}

The key here is to put a valid value for $my_host as per your ALLOWED_HOSTS .这里的关键是根据您的ALLOWED_HOSTS$my_host设置一个有效值。

Unfortunately there is no "perfect" solution without increasing overhead.不幸的是,没有不增加开销的“完美”解决方案。 Some configurations will require you to add IP addresses all the time to the ALLOWED_HOSTS but this solution would generally work with the least over head.某些配置将要求您始终将 IP 地址添加到ALLOWED_HOSTS但此解决方案通常会以最少的ALLOWED_HOSTS工作。

To expand on the answer provided by dfrdmn :要扩展dfrdmn 提供的答案

While this answer works well in most cases, it has a couple small potential problems.虽然这个答案在大多数情况下都很有效,但它有一些潜在的小问题。

AWS ELB Network Load Balancers AWS ELB 网络负载均衡器

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.首先,如果您使用的是 ELB网络负载均衡器,则此方法不适用于其 HTTP 运行状况检查,因为负载均衡在 HTTP 主机标头中发送负载均衡器的 IP 地址。 From the AWS docs :AWS 文档

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.健康检查请求中的 HTTP 主机头包含负载均衡器节点的 IP 地址和侦听器端口,而不是目标的 IP 地址和健康检查端口。 If you are mapping incoming requests by host header, you must ensure that health checks match any HTTP host header.如果您按主机标头映射传入请求,则必须确保运行状况检查与任何 HTTP 主机标头匹配。 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.另一种选择是在不同的端口上添加单独的 HTTP 服务,并将目标组配置为使用该端口进行健康检查。 Alternatively, consider using TCP health checks.或者,考虑使用 TCP 运行状况检查。

So, adding your instance (target group) IP to your ALLOWED_HOSTS will not work.因此,将您的实例(目标组)IP 添加到您的ALLOWED_HOSTS将不起作用。 As stated, you could use TCP health checks, or you could use the middleware approach described in another answer.如上所述,您可以使用 TCP 运行状况检查,或者您可以使用另一个答案中描述的中间件方法。

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.您的 Django settings.py文件为每个进程执行,并且任何时候进程需要重新启动。 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.如果您的网络服务器配置为使用多个进程,例如使用gunicorn workers 时,这很重要,因为通常配置为正确利用系统 CPU 资源。

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.这意味着,如果有足够多的进程,您的settings.py文件将被执行多次,向元数据端点发送许多并发请求,并且您的进程可能无法启动。 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:为了解决这个问题,你可以做一些事情:

  1. 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.启动服务器之前获取 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'])

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

  1. For services running in containers on ECS you can use the container metadata file对于在 ECS 上的容器中运行的服务,您可以使用容器元数据文件

You can do this safely within the settings.py because there is no throttling or rate limit for accessing this file.您可以在settings.py安全地执行此操作,因为访问此文件没有节流或速率限制。 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.您还可以在容器的 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"]

Solution解决方案

The solution that worked for me was to simply install the django-ebhealthcheck library.对我有用的解决方案是简单地安装django-ebhealthcheck库。 After installing it, just add ebhealthcheck.apps.EBHealthCheckConfig to your INSTALLED_APPS.安装后,只需将 ebhealthcheck.apps.EBHealthCheckConfig 添加到您的 INSTALLED_APPS。

From django-ebhealthcheck GitHub:来自 django-ebhealthcheck GitHub:

By default, Elastic Beanstalk's health check system uses the public IP of each load balanced instance as the request's host header when making a request.默认情况下,Elastic Beanstalk 的健康检查系统在发出请求时使用每个负载均衡实例的公共 IP 作为请求的主机头。 Unless added to ALLOWED_HOSTS, this causes Django to return a 400 Bad Request and a failed health check.除非添加到 ALLOWED_HOSTS,否则这会导致 Django 返回 400 Bad Request 和失败的健康检查。

This app dynamically adds your instance's public IP address to Django's ALLOWED_HOSTS setting to permit health checks to succeed.此应用程序将您实例的公共 IP 地址动态添加到 Django 的 ALLOWED_HOSTS 设置中,以允许运行状况检查成功。 This happens upon application start.这发生在应用程序启动时。

Version 2.0.0 and higher supports IMDSv2. 2.0.0 及更高版本支持 IMDSv2。 If you are using v1 and cannot upgrade, use version 1 of this library instead (pip install django-ebhealthcheck<2.0.0).如果您使用的是 v1 且无法升级,请改用此库的第 1 版(pip install django-ebhealthcheck<2.0.0)。

Installation安装

  1. pip install django-ebhealthcheck

  2. Add ebhealthcheck.apps.EBHealthCheckConfig to your INSTALLED_APPS:将 ebhealthcheck.apps.EBHealthCheckConfig 添加到您的 INSTALLED_APPS:

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

I like Watt Imsuri 's answer above, and I think I'm going to use this approach in all my projects so I turned it into a pip package for Django.我喜欢上面Watt Imsuri回答,我想我会在我所有的项目中使用这种方法,所以我把它变成了 Django 的 pip 包。

pip install django-easy-health-check

Add the health check middleware to Django settings before django.middleware.common.CommonMiddleware :django.middleware.common.CommonMiddleware之前将健康检查中间件添加到 Django 设置中:

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

By default, the health check url will be available at "example.com/healthcheck/".默认情况下,健康检查 URL 将在“example.com/healthcheck/”中提供。

You can also customize and overwrite the default settings by including the following in your project's settings.py:您还可以通过在项目的 settings.py 中包含以下内容来自定义和覆盖默认设置:

DJANGO_EASY_HEALTH_CHECK = {
    "PATH": "/healthcheck/",
    "RETURN_STATUS_CODE": 200,
    "RETURN_BYTE_DATA": "",
    "RETURN_HEADERS": None
}

In production, you may also want to set the following Django settings:在生产中,您可能还想设置以下 Django 设置:

ALLOWED_HOSTS = ["example.com"]
SECURE_SSL_REDIRECT = True
SECURE_REDIRECT_EXEMPT = [r'^healthcheck/$']

For more info, check out the git .有关更多信息,请查看git

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM