问题描述
我们正尝试通过使用Django Channels 2,AWS和Nginx + daphne在我们的网站上配置实时聊天。我们的设置可以在本地运行良好,但是在部署到生产环境时会遇到问题。
我们的生产环境由两个使用Elastic Container Service(Fargate)部署到AWS的Docker容器组成。前面运行的容器是一个Nginx配置,它充当代理服务器来提供静态文件。第二个容器运行我们的API / Django站点。代理在端口8000上运行,并将传入的请求转发到在端口9000上运行的API / Django容器。我还将注意到,我们正在使用terraform配置我们的AWS环境。
我引用了完成相似设置的多篇文章。例如: https://medium.com/@elspanishgeek/how-to-deploy-django-channels-2-x-on-aws-elastic-beanstalk-8621771d4ff0
但是此设置使用的是我们未使用的Elastic Beanstalk部署。
代理Dockerfile:
FROM Nginxinc/Nginx-unprivileged:1-alpine
LABEL maintainer='CodeDank'
copY ./default.conf.tpl /etc/Nginx/default.conf.tpl
copY ./uwsgi_params /etc/Nginx/uwsgi_params
ENV LISTEN_PORT=8000
ENV APP_HOST=app
ENV APP_PORT=9000
USER root
RUN mkdir -p /vol/static
RUN chmod 755 /vol/static
RUN touch /etc/Nginx/conf.d/default.conf
RUN chown Nginx:Nginx /etc/Nginx/conf.d/default.conf
copY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
USER Nginx
CMD ["/entrypoint.sh"]
API /站点Dockerfile:
FROM python:3.7-alpine3.11
LABEL maintainer="CodeDank"
ENV PYTHONUNBUFFERED 1
ENV PATH="/scripts:${PATH}"
RUN pip install --upgrade pip
copY ./requirements.txt /requirements.txt
RUN apk add --update --no-cache postgresql-client jpeg-dev
RUN apk add --update --no-cache --virtual .tmp-build-deps \
gcc libc-dev linux-headers postgresql-dev \
musl-dev zlib zlib-dev
RUN apk add --update --no-cache libressl-dev musl-dev libffi-dev
RUN apk add --update --no-cache g++ freetype-dev jpeg-dev
RUN pip install -r /requirements.txt
RUN apk del .tmp-build-deps
RUN mkdir /app
workdir /app
copY ./app /app
copY ./scripts /scripts
RUN chmod +x /scripts/*
RUN mkdir -p /vol/web/media
RUN mkdir -p /vol/web/static
RUN adduser -D user
RUN chown -R user:user /vol/
RUN chmod -R 755 /vol/web
USER user
CMD ["entrypoint.sh"]
(入口点脚本如下所示)
我们创建了一个AWS Elasticache Redis服务器,用作Django通道的CHANNEL_LAYERS后端。 “ REdis_HOSTNAME”环境变量是Redis服务器的端点地址。
# Channels Settings
Asgi_APPLICATION = "app.routing.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer","CONfig": {
"hosts": [
(os.environ.get('REdis_HOSTNAME'),6379)
],},}
import os
import django
from channels.routing import get_default_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE','app.settings')
django.setup()
application = get_default_application()
在Channels文档之后,我们尝试配置daphne以在我们的项目中运行asgi应用程序。理想情况下,我们希望此设置使Nginx代理服务器将所有websocket请求转发到运行在端口9001上的daphne服务器。我们的所有websocket端点都将包含/ ws /,因此Nginx代理配置已如下定义。
default.conf.tpl:
upstream channels-backend {
server localhost:9001;
}
server {
listen ${LISTEN_PORT};
location /static {
alias /vol/static;
}
location / {
uwsgi_pass ${APP_HOST}:${APP_PORT};
include /etc/Nginx/uwsgi_params;
client_max_body_size 4G;
}
location /ws/ {
proxy_pass http://channels-backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
代理入口点脚本:
#!/bin/sh
set -e
envsubst '${LISTEN_PORT},${APP_HOST},${APP_PORT}' < /etc/Nginx/default.conf.tpl > /etc/Nginx/conf.d/default.conf
Nginx -g 'daemon off;'
API /站点入口点脚本:
#!/bin/sh
set -e
python manage.py collectstatic --noinput
python manage.py wait_for_db
python manage.py migrate
uwsgi --socket :9000 --workers 4 --master --enable-threads --module app.wsgi
daphne -b 0.0.0.0 -p 9001 app.asgi:application
尝试连接到我们网站上的Websocket时,返回502错误。
Error during WebSocket handshake: Unexpected response code: 502.
我怀疑daphne服务器未按预期运行,或者未使用Nginx服务器正确配置。在API入口点脚本中,daphne命令是否可以按当前状态运行?或者,是否缺少使daphne在Nginx代理后面运行所需的所有内容?我最初的想法是daphne命令不能在入口点脚本中的uwsgi命令之后运行。但是,我不确定该命令是否还需要放置在其他位置才能运行daphne进程。
代理的cloudwatch日志不是非常详细,但是尝试连接到网站上的Websocket时,我收到此错误消息。
[error] 8#8: *53700 connect() Failed (111: Connection refused) while connecting to upstream,client: 10.1.1.190,server:,request: "GET /ws/chat/djagno/ HTTP/1.1",upstream: "http://127.0.0.1:9001/ws/chat/djagno/",host: "mycustomdomain.net"
我已经看到解决此问题的其他方法,其中不包括使用Nginx代理将Websocket流量定向到daphne。也许我们的方法不是最好的解决方案?我们欢迎其他配置。
任何反馈将不胜感激。谢谢!
解决方法
我想到的一件事是,您是否正在扩展nginx容器?为了使websocket正常工作,您可能需要在Application Load Balancer上启用会话粘性。
,既然您提到您将 Terraform 用于 AWS 部署,我将检查您的 AWS 安全组的配置,特别是您在 EC2 实例和 Elasticache Redis 之间设置安全组的位置。
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_cluster
编辑:乍一看,我刚刚注意到您是如何启动 uwsgi 和 daphne 的。你现在这样做的方式是在前台启动 uwsgi,然后这个过程只是等待而 daphne 永远不会启动(因此出现 502 错误)。
改变
uwsgi --socket :9000 --workers 4 --master --enable-threads --module app.wsgi
daphne -b 0.0.0.0 -p 9001 app.asgi:application
到
uwsgi --socket :9000 --workers 4 --master --enable-threads --module app.wsgi & daphne -b 0.0.0.0 -p 9001 app.asgi:application
这将在后台启动 uwsgi,然后继续启动 Daphne。
如果您需要一种方法来杀死两者,您可以在脚本中运行它,然后在最后添加一个 wait
,这样当您杀死脚本时,uwsgi 和 daphne 进程也会被杀死。否则,您可以考虑使用 systemd 或 supervisor 来守护 uwsgi 和 daphne 初创公司。
这里可能存在一些问题。在处理 websocket 请求时,我发现的第一件事是它们在您的服务器上的行为与在 localhost 上的行为不同。我不得不根据 Django、Django Channels、Daphne 等的版本在几个不同的区域修改我的 Django Channels 逻辑。
例如:当我们升级到 Channels 3.0 时,我们无法在没有 database_sync_to_async()
装饰器的情况下访问我们的数据库,并且不得不将调用卸载到它们自己的单独函数中。
检查您的 routing.py
是否有像 AllowHostsOriginValidator
这样的请求阻止器。
如果您使用自定义中间件,范围对象会根据您的环境和您访问数据的方式而有所不同。
此外,尝试通过 unix 套接字在守护进程之外运行 Daphne,如下所示:
daphne -u /etc/supervisor/socks/daphne.sock --fd 0 --access-log - --proxy-headers project.asgi:application -v 3
如果您想试一试,我们使用以下设置。
负载均衡nginx配置:
upstream mywebapp {
server front_end_ip:port;
}
#This upgrades the connection for websockets from https to websocket
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
location /ws/ {
add_header X-debug-message "The /ws/ location was served from the ascend load balancer" always;
proxy_pass http://mywebapp/ws/;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "connection_upgrade";
proxy_read_timeout 86400;
}
前端nginx配置:
upstream mybackend {
server django_server_ip:port;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
location /ws/ {
add_header X-debug-message "The /ws/ location was served from angular 1" always;
proxy_pass http://mybackend/ws/;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "connection_upgrade";
proxy_read_timeout 86400;
Django 服务器 nginx 配置:
upstream daphne {
server 0.0.0.0:9001;
}
location /ws/ {
add_header X-debug-message "The /ws/ location was served from daphne" always;
proxy_pass http://daphne;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_cache_bypass $http_upgrade;
}