上篇:使用Docker 部署Django + Uwsgi(单容器)
部署一个简单的Django项目
在服务器部署一个简单的Django项目。不实用 uwsgi和nginx,数据库默认使用 sqllite3,只是把django放在一个简单的容器里面!
- 整个项目结构如下所示,目前该项目放在宿主机(服务器)上。
mysite1
├── db.sqlite3
├── Dockerfile # 用于生产docker镜像的Dockerfile
├── manage.py
├── mysite1
│ ├── asgi.py
│ ├── init.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── pip.conf # 非必需。pypi源设置成国内,加速pip安装
└── requirements.txt # 项目只依赖Django,所以里面只有django==3.0.5一条
注意:Django默认ALLOWED_HOSTS = []为空,在正式部署前你需要修改settings.py, 把它设置为服务器实际对外IP地址,否则后面部署会出现错误,这个与docker无关。即使你不用docker部署,ALLOWED_HOSTS也要设置好的。
- 第一步:编写Dockerfile,内容如下:
# 建立 python3.7 环境
FROM python:3.7
# 镜像作者
MAINTAINER WW
# 设置 python 环境变量
ENV PYTHONUNBUFFERED 1
# 设置pip源为国内源
COPY pip.conf /root/.pip/pip.conf
# 在容器内/var/www/html/下创建 mysite1文件夹
RUN mkdir -p /var/www/html/mysite1
# 设置容器内工作目录
WORKDIR /var/www/html/mysite1
# 将当前目录文件加入到容器工作目录中(. 表示当前宿主机目录)
ADD . /var/www/html/mysite1
# 利用 pip 安装依赖
RUN pip install -r requirements.txt
将pip设置成阿里云镜像,pip.conf文件内容如下:
[global]
index-url = https://mirrors.aliyun.com/pypi/simple/
[install]
trusted-host=mirrors.aliyun.com
- 第二步:使用当前目录的 Dockerfile 创建镜像,标签为 django_docker_img:v1。
进入Dockerfile所在目录,输入如下命令:
# 根据Dockerfile创建名为django_docker_img的镜像,版本v1,.代表当前目录
sudo docker build -t django_docker_img:v1 .
# 查看镜像是否创建成功, 后面-a可以查看所有本地的镜像
sudo docker images
这是你应该可以看到有一个名为django_docker_img的docker镜像创建成功了,版本v1。
- 第三步:根据镜像生成容器并运行,容器名为mysite1, 并将宿主机的80端口映射到容器的8000端口。
# 如成功,根据镜像创建mysite1容器并运行。宿主机80:容器8000。-d表示后台运行。
sudo docker run -it -d --name mysite1 -p 80:8000 django_docker_img:v1
# 查看容器状态,后面加-a可以查看所有容器列表,包括停止运行的容器
sudo docker ps
# 进入容器,如果复制命令的话,结尾千万不能有空格。
sudo docker exec -it mysite1 /bin/bash
这时你应该可以看到mysite1容器开始运行了。使用sudo docker exec -it mysite1 /bin/bash即可进入容器内部。
- 第四步:进入容器内部后,执行如下命令
python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py runserver 0.0.0.0:8000
这时你打开Chrome浏览器输入http://your_server_ip,你就可以看到你的Django网站已经上线了,恭喜你!
从客户端到Docker容器内部到底发生了什么
前面我们已经提到如果不指定容器网络,Docker创建运行每个容器时会为每个容器分配一个IP地址。我们可以通过如下命令查看。
sudo docker inspect mysite1 | grep “IPAddress”
用户访问的是宿主机服务器地址,并不是我们容器的IP地址,那么用户是如何获取容器内部内容的呢?答案就是端口映射。因为我们将宿主机的80端口(HTTP协议)隐射到了容器的8000端口,所以当用户访问服务器IP地址80端口时自动转发到了容器的8000端口。
注意:容器的IP地址很重要,以后要经常用到。同一宿主机上的不同容器之间可以通过容器的IP地址直接通信。一般容器的IP地址与容器名进行绑定,只要容器名不变,容器IP地址不变。
把UWSGI加入Django容器中的准备工作
在前面例子中我们使用了Django了自带的runserver命令启动了测试服务器,但实际生成环境中你应该需要使用支持高并发的uwsgi服务器来启动Django服务。尽管本节标题是把uwsgi加入到Django容器中,但本身这句话就是错的,因为我们Django的容器是根据django_docker_img:v1这个镜像生成的,我们的镜像里并没有包含uwsgi相关内容,只是把uwsgi.ini配置文件拷入到Django容器是不会工作的。
所以这里我们需要构建新的Dockerfile并构建新的镜像和容器。为了方便演示,我们创建了一个名为mysite2的项目,项目结构如下所示:
mysite2
├── db.sqlite3
├── Dockerfile # 构建docker镜像所用到的文件
├── manage.py
├── mysite2
│ ├── asgi.py
│ ├── init.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── pip.conf
├── requirements.txt # 两个依赖:django3.0.5 uwsgi2.0.18
├── start.sh # 进入容器后需要执行的命令,后面会用到
└── uwsgi.ini # uwsgi配置文件
新的Dockerfile内容如下所示:
# 建立 python3.7 环境
FROM python:3.7
# 镜像作者
MAINTAINER WW
# 设置 python 环境变量
ENV PYTHONUNBUFFERED 1
# 设置pypi源头为国内源
COPY pip.conf /root/.pip/pip.conf
# 在容器内/var/www/html/下创建 mysite2 文件夹
RUN mkdir -p /var/www/html/mysite2
# 设置容器内工作目录
WORKDIR /var/www/html/mysite2
# 将当前目录文件拷贝一份到工作目录中(. 表示当前目录)
ADD . /var/www/html/mysite2
# 利用 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. 用 uwsgi启动 django 服务, 不再使用python manage.py runserver
python manage.py makemigrations&&
python manage.py migrate&&
uwsgi --ini /var/www/html/mysite2/uwsgi.ini
# python manage.py runserver 0.0.0.0:8000
uwsgi.ini
配置文件内容如下所示。
[uwsgi]
project=mysite2
uid=www-data
gid=www-data
base=/var/www/html
chdir=%(base)/%(project)
module=%(project).wsgi:application
master=True
processes=2
http=0.0.0.0:8000 #这里直接使用uwsgi做web服务器,使用http。如果使用nginx,需要使用socket沟通。
buffer-size=65536
pidfile=/tmp/%(project)-master.pid
vacuum=True
max-requests=5000
daemonize=/tmp/%(project)-uwsgi.log
#设置一个请求的超时时间(秒),如果一个请求超过了这个时间,则请求被丢弃
harakiri=60
#当一个请求被harakiri杀掉会,会输出一条日志
harakiri-verbose=true
单容器部署 Django + UWSGI
- 第一步:生成名为django_uwsgi_img:v1的镜像
sudo docker build -t django_uwsgi_img:v1 .
- 第二步:启动并运行mysite2的容器
# 启动并运行mysite2的容器
sudo docker run -it -d --name mysite2 -p 80:8000 django_uwsgi_img:v1
- 第三步:进入mysite2的容器内部,并运行脚本命令start.sh
# 进入容器,如果复制命令的话,结尾千万不能有空格。
sudo docker exec -it mysite2 /bin/bash
# 执行脚本命令
sh start.sh
以上两句命令也可以合并成一条命令
sudo docker exec -it mysite2 /bin/bash start.sh
这时你打开浏览器输入http://your_server_ip,你就可以看到你的Django网站已经上线了,恭喜你!这次是uwsgi启动的服务哦,因为你根本没输入python manage.py runserver命令。
故障排查:此时如果你没有看到网站上线,主要有两个可能原因:
- uwsgi配置文件错误。尤其http服务IP地址为0.0.0.0:8000,不应是服务器的ip:8000,因为我们uwsgi在容器里,并不在服务器上。
- 浏览器设置了http(端口80)到https(端口443)自动跳转,。因为容器8000端口映射的是宿主机80端口,如果请求来自宿主机的443端口,容器将接收不到外部请求。解决方案清空浏览器设置缓存或换个浏览器。
注意:你会留意到网站虽然上线了,但有些图片和网页样式显示不对,这是因为uwsgi是处理动态请求的服务器,静态文件请求需要交给更专业的服务器比如Nginx处理。下篇文章中我们将介绍Docker双容器部署Django+Uwsgi+Nginx,欢迎关注
中篇:使用Docker 部署Django + Uwsgi + nginx(双容器)
我们将构建两个容器,一个容器放Django + Uwsgi,另一个容器放Nginx。
双容器部署Django+Uwsgi+Nginx项目示意图
整个项目流程示意图如下所示。用户通过客户端访问服务器的80端口(http协议默认端口)时,请求由于宿主机和容器1间存在80:80端口映射关系会被转发到Nginx所在的容器1。Nginx接收到请求后会判断请求是静态的还是动态的,静态文件请求自己处理,动态请求则转发到Django+Uwsgi所在的容器2处理,容器2的开放端口为8000。
本例中所使用到的容器1的名字为mysite3-nginx, 容器2的名称为mysite3。由于两个容器在一台宿主机上,你可以看到docker分配的容器IP地址非常接近,有点像局域网IP。使用如下命名即可查看容器的IP地址。
sudo docker inspect container_name | grep "IPAddress"
双容器部署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 URL
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = "/static/"
# MEDIA ROOT 和 MEDIA URL
MEDIA_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 XXX
# 设置 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 runserver
python manage.py collectstatic --noinput&&
python manage.py makemigrations&&
python manage.py migrate&&
uwsgi --ini /var/www/html/mysite3/uwsgi.ini
本例中使用到 Dockerfile
和pip.conf
和上篇单容器部署Django+Uwsgi的基本一样,唯一不同的是start.sh和uwsgi.ini。start.sh多了收集静态文件的命令
。前例uwsgi.ini中我们使用了http协议与客户端通信。由于本例中uwsgi并不直接与客户端沟通,而是与nginx进行沟通,这时我们使用了socket通信。
[uwsgi]
project=mysite3
uid=www-data
gid=www-data
base=/var/www/html
chdir=%(base)/%(project)
module=%(project).wsgi:application
master=True
processes=2
socket=0.0.0.0:8000
chown-socket=%(uid):www-data
chmod-socket=660
buffer-size=65536
pidfile=/tmp/%(project)-master.pid
daemonize=/tmp/%(project)-uwsgi.log # 以守护进程运行,并将log生成与temp文件夹。
vacuum=True
max-requests=5000
#设置一个请求的超时时间(秒),如果一个请求超过了这个时间,则请求被丢弃
harakiri=60
post 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)都已运行,如下所示:
进入Django+UWSGI容器执行Django命令并启动uwsgi服务器
虽然我们两个容器都已启动运行,但我们还没有执行Django相关