django 转发_Docker部署Django由浅入深系列(中): 双容器部署Django + Uwsgi + Nginx

本文是使用Docker部署Django由浅入深系列的中篇,我们将构建两个容器,一个容器放Django + Uwsgi,另一个容器放Nginx。我们将了解不同容器间是如何通信的,并学会正确配置uwsgi.ini和nginx.conf使整个项目跑起来。在阅读本篇前,请先阅读本系列上篇使用Docker单容器部署Django + Uwsgi。下篇我们会更进一步介绍如何使用docker-compose部署Django + Uwsgi + Nginx + MySQL + Redis(多容器组合), 欢迎关注我们的微信公众号【Python Web与Django开发】。

575554e7e966a621f1649ab8c297838b.png

双容器部署Django+Uwsgi+Nginx项目示意图

整个项目流程示意图如下所示。用户通过客户端访问服务器的80端口(http协议默认端口)时,请求由于宿主机和容器1间存在80:80端口映射关系会被转发到Nginx所在的容器1。Nginx接收到请求后会判断请求是静态的还是动态的,静态文件请求自己处理,动态请求则转发到Django+Uwsgi所在的容器2处理,容器2的开放端口为8000。cf46bf4c087deda4c816cf5f251d37e7.png本例中所使用到的容器1的名字为mysite3-nginx, 容器2的名称为mysite3。由于两个容器在一台宿主机上,你可以看到docker分配的容器IP地址非常接近,有点像局域网IP。使用如下命名即可查看容器的IP地址。

 sudo docker inspect container_name | grep "IPAddress"

例子:

3bf45314df508a3530b945e0000a0b29.png

注意:

  • 容器创建和启动的先后顺序很重要,本例中容器1对容器2有依赖。这是因为如果容器2不运行,容器1收到动态请求后不知道给谁处理。

  • 创建容器并运行前,必须先使用docker pull命令或Dockerfile构建容器对应的镜像。

双容器部署Django+Uwsgi+Nginx代码布局图

整个项目的代码布局如下所示。我们新建了一个compose文件夹,专门存放用于创建其它镜像的Dockerfile及配置文件。在本例中,我们只创建了一个nginx文件夹。在下篇文章中,我们会将MySQL和Redis也加进去。

 mysite3├── compose│   └── nginx│       ├── Dockerfile # 创建nginx镜像需要用到的Dockerfile│       ├── log # 存放nginx的日志│       ├── nginx.conf # nginx配置文件│       ├── ssl # 如果需要配置https需要用到├── db.sqlite3├── Dockerfile # 创建django+uwsgi镜像需要用到的Dockerfile├── manage.py├── mysite3│   ├── asgi.py│   ├── __init__.py│   ├── settings.py│   ├── urls.py│   └── wsgi.py├── pip.conf # 设置pypi为国内源,加速静态文件安装├── static # 静态文件夹,存放css,js和图片├── media # 媒体文件夹,存放用户上传的媒体文件├── requirements.txt├── start.sh # 容器运行后需要启动的脚本文件└── uwsgi.ini # uwsgi配置文件

注意:

  • Django项目默认ALLOWED_HOSTS = []为空,在正式部署前你需要修改settings.py, 把它设置为服务器实际对外IP地址,否则后面部署会出现错误,这个与docker无关。

  • 本例中使用了nginx提供静态文件服务,你必须在settings.py设置MEDIA_ROOT和STATIC_ROOT,如下所示。否则即使nginx服务正常,静态文件也无法正常显示。

 # STATIC ROOT 和 STATIC URLSTATIC_ROOT = os.path.join(BASE_DIR, 'static')STATIC_URL = "/static/"# MEDIA ROOT 和 MEDIA URLMEDIA_ROOT = os.path.join(BASE_DIR, 'media')MEDIA_URL = "/media/"

创建容器2 (Django+Uwsgi)对应的镜像并启动运行容器

创建容器2的镜像所使用的Dockerfile内容如下所示:

 # 基础镜像:python3.7 环境,也可使用python3.7-alphine缩小镜像体积FROM python:3.7# 镜像作者大江狗MAINTAINER DJG# 设置 python 环境变量ENV PYTHONUNBUFFERED 1# 设置pypi源头为国内源COPY pip.conf /root/.pip/pip.conf# 在容器内/var/www/html/下创建 mysite3文件夹RUN mkdir -p /var/www/html/mysite3# 设置容器内工作目录WORKDIR /var/www/html/mysite3# 将当前目录文件拷贝一份到工作目录中(. 表示当前目录)ADD . /var/www/html/mysite3# 利用 pip 安装依赖RUN pip install -r requirements.txt# Windows环境下编写的start.sh每行命令结尾有多余的\r字符,需移除。RUN sed -i 's/\r//' ./start.sh# 设置start.sh文件可执行权限RUN chmod +x ./start.sh

start.sh脚本文件内容如下所示。最重要的是最后一句,使用uwsgi.ini配置文件启动Django服务。

 #!/bin/bash# 从第一行到最后一行分别表示:# 1. 收集静态文件到根目录# 2. 生产数据库迁移文件# 3. 根据数据库迁移文件来修改数据库# 4. 用 uwsgi启动 django 服务, 不再使用python manage.py runserverpython manage.py collectstatic --noinput&&python manage.py makemigrations&&python manage.py migrate&&uwsgi --ini /var/www/html/mysite3/uwsgi.ini

本例中使用到 Dockerfilepip.conf和上篇单容器部署Django+Uwsgi的基本一样,唯一不同的是start.shuwsgi.inistart.sh多了收集静态文件的命令。前例uwsgi.ini中我们使用了http协议与客户端通信。由于本例中uwsgi并不直接与客户端沟通,而是与nginx进行沟通,这时我们使用了socket通信。

 [uwsgi]project=mysite3uid=www-datagid=www-database=/var/www/htmlchdir=%(base)/%(project)module=%(project).wsgi:applicationmaster=Trueprocesses=2socket=0.0.0.0:8000chown-socket=%(uid):www-datachmod-socket=660buffer-size=65536pidfile=/tmp/%(project)-master.piddaemonize=/tmp/%(project)-uwsgi.log # 以守护进程运行,并将log生成与temp文件夹。vacuum=Truemax-requests=5000#设置一个请求的超时时间(秒),如果一个请求超过了这个时间,则请求被丢弃harakiri=60post buffering=8678#当一个请求被harakiri杀掉会,会输出一条日志harakiri-verbose=true#开启内存使用情况报告memory-report=true#设置平滑的重启(直到处理完接收到的请求)的长等待时间(秒)reload-mercy=10#设置工作进程使用虚拟内存超过N MB就回收重启reload-on-as= 1024

现在我们可以构建容器2对应的镜像,并运行启动容器2了,容器取名mysite3。

 # 进入mysite3目录下的Dockerfile创建名为django_mysite3的镜像,版本v1,.代表当前目录sudo docker build -t django_mysite3:v1 .# 启动并运行容器2(名称mysite3), -d为后台运行,-v进行目录挂载。sudo docker run -it --name mysite3 -p 8000:8000 \-v /home/enka/mysite3:/var/www/html/mysite3 \-d django_mysite3:v1# 查看容器是否运行sudo docker ps# 查看容器2(名称mysite3)的IP地址sudo docker inspect mysite3 | grep "IPAddress"

注意:

  • 启动运行容器时一定要考虑目录挂载,防止数据丢失。我们的项目在容器中的路径是/var/www/html/mysite3,用户产生的数据也存储在这个容器内。我们一但删除容器,那么容器内的数据也随之丢失了,即使重新创建容器数据也不会回来。

  • 通过-v参数可进行目录挂载。冒号前为宿主机目录,冒号后为镜像容器内挂载的路径,两者必须为绝对路径。如果没有指定宿主机的目录,则容器会在/var/lib/docker/volumes/随机配置一个目录。

  • 本例中我们使用了-v参数把容器中的目录/var/www/html/mysite3挂载到了宿主机的的目录/home/enka/mysite3上,实现了两者数据的同步。此时删除容器不用担心,数据会在宿主机上有备份。

创建容器1 (Nginx)的镜像并启动运行容器

创建Nginx镜像的Dockerfile如下所示:

 # nginx镜像FROM nginx:latest# 删除原有配置文件,创建静态资源文件夹和ssl证书保存文件夹RUN rm /etc/nginx/conf.d/default.conf \&& mkdir -p /usr/share/nginx/html/static \&& mkdir -p /usr/share/nginx/html/media \&& mkdir -p /usr/share/nginx/ssl# 添加配置文件ADD ./nginx.conf /etc/nginx/conf.d/# 关闭守护模式CMD ["nginx", "-g", "daemon off;"]

Nginx的配置文件nginx.conf内容如下所示。你注意到Nginx是如何将动态请求转到容器2(对应IP的172.17.0.2)的8000端口了吗?本例中我们使用了socket通信与uwsgi服务器通信,所以要使用uwsgi_pass转发请求,而不是使用proxy_pass转发请求。

 # nginx配置文件。upstream django {    ip_hash;    server 172.17.0.3:8000; # Django+uwsgi容器所在IP地址及开放端口,非宿主机外网IP}server {    listen 80; # 监听80端口    server_name localhost; # 可以是nginx容器所在ip地址或127.0.0.1,不能写宿主机外网ip地址    location /static {        alias /usr/share/nginx/html/static; # 静态资源路径    }    location /media {        alias /usr/share/nginx/html/media; # 媒体资源,用户上传文件路径    }    location / {        include /etc/nginx/uwsgi_params;        uwsgi_pass django;        uwsgi_read_timeout 600;        uwsgi_connect_timeout 600;        uwsgi_send_timeout 600;       # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;       # proxy_set_header Host $http_host;       # proxy_redirect off;       # proxy_set_header X-Real-IP  $remote_addr;       # proxy_pass http://django;    }}    access_log /var/log/nginx/access.log main;    error_log /var/log/nginx/error.log warn;

现在我们可以构建容器1对应的镜像,并运行启动容器1了,并取名mysite3-nginx.

 # 进入nginx目录, 使用该目录下的Dockerfile创建名为mynginx的镜像,版本v1,.代表当前目录sudo docker build -t mynginx:v1 .# 启动并运行容器2(名称mysite3), -d为后台运行,宿主机与Nginx端口映射为80:80。Nginx下的static和media目录挂载到宿主机的Django项目下的media和static文件夹。sudo docker run -it -p 80:80 --name mysite3-nginx \-v /home/enka/mysite3/static:/usr/share/nginx/html/static \-v /home/enka/mysite3/media:/usr/share/nginx/html/media \-v /home/enka/mysite3/compose/nginx/log:/var/log/nginx \-d mynginx:v1# 查看容器是否运行sudo docker ps

这时你应该可以看到两个容器(mysite3和mysite3-nginx)都已运行,如下所示:

d8e23b902d3777758517435e0dc1ae1f.png

进入Django+UWSGI容器执行Django命令并启动uwsgi服务器

虽然我们两个容器都已启动运行,但我们还没有执行Django相关命令并启动uwsgi服务器。现在我们只需进入容器2执行我们先前编写的脚本文件start.sh即可。

 sudo docker exec -it mysite3 /bin/bash start.sh

start.sh实际上干了如下几件事:

 python manage.py collectstatic --noinput&&python manage.py makemigrations&&python manage.py migrate&&uwsgi --ini /var/www/html/mysite3/uwsgi.ini

这时你打开浏览器输入http://your_server_ip/admin,你应该可以看到你的Django网站已经上线了,静态文件也可以显示正常,恭喜你!这次是Nginx+Uwsgi提供的服务,这才真正实现了Django在生产环境的部署。

Docker部署Django+Uwsgi+Nginx常见问题及解决方案

使用Docker双容器部署Django+Uwsgi和Nginx过程中容易出现的错误及解决方案如下所示,供参考:

  • Nginx提示502 Gateway错误,静态文件可以正常显示:这是在提示服务器配置错误。应检查nginx.conf文件,看看其指向的uwsgi所在的容器IP及开放的端口是否正确,同时检查uwsgi.ini配置文件中socket的开放端口。

  • uwsgi日志提示probably another instance of uWSGI is running on the same address (0.0.0.0:8000).bind(): Address already in use [core/socket.c line 769]:这是在提示其它uWSGI示例占用了端口。应使用sudo pkill -f uwsgi -9关闭所有uwsgi进程,再重新启动uwsgi服务。

  • uwsgi日志提示invalid request block size: 4547 (max 4096)...skip:uWsgi默认的buffer size 为4096,如果http请求数据超过这个量,就会报错,为了解决这个问题,就需要修改uwsgi的配置文件,增加buffer-size=65536一项。

  • 浏览器总是从http(端口80)自动跳转到https(端口443)导致报错:这是因为Nginx容器80端口映射的是宿主机80端口,如果请求来自宿主机的443端口,容器将接收不到外部请求。解决方案清空浏览器设置缓存,换个浏览器或配置nginx支持https。

小结

本文成功使用Docker双容器在服务器上部署了Django + Uwsgi + Nginx。本例中我们只使用到了两个容器,所以可以手动去创建容器并运行。然而在实际的生产环境中,我们往往需要定义数量庞大的 docker 容器,并且容器之间具有错综复杂的依赖联系,手动的记录和配置这些复杂的容器关系,不仅效率低下而且容易出错,所以迫切需要一种定义容器集群编排和部署的工具,这就是Docker Compose。在下篇文章中(也是本系列的终章),我们将介绍如何使用Docker Compose部署Django + Uwsgi + Nginx + MySQL + Redis。请关注我们的微信公众号【Python Web与Django开发】,这样可以在第一时间收藏阅读。

fa0accaa9008ad30933c8baaef0b48a9.png

大江狗

2020.5

相关阅读

原创Docker部署Django由浅入深系列(上):单容器部署Django + Uwsgi

如何使用Docker搭建MySQL主从复制架构

如何在阿里云Ubuntu服务器通过uWSGI和Nginx部署Django项目教程-大江狗原创出品

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值