使用docker部署以uwsgi方式运行的django

Django是个被广泛使用的web框架,如何给这个框架进行docker化是个比较复杂的问题
我们先来确定一下我们的项目框架,进行过django-admin startproject ${PROJECT}之后的django项目文件结构应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.
├── ${APP_1}
│ ├── admin.py
│ ├── apps.py
│ ├── decorators.py
│ ├── __init__.py
│ ├── migrations
│ ├── models.py
│ ├── permissions.py
│ ├── serializer.py
│ ├── serializers.py
│ ├── static
│ ├── templates
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── ${PROJECT}
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── deploy
│ ├── entrypoint.sh
│ ├── nginx-app.conf
│ ├── supervisor-app.conf
│ ├── uwsgi.ini
│ └── uwsgi_params
├── Dockerfile
├── manage.py
├── README.md
└── requirements.txt

其中包含一个与项目名称同名的文件夹(包),以及若干个app。
我们再新建deploy文件夹,其中包含各种需要的配置文件。

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FROM python:3
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY . /usr/src/app

# Use sed because of potential file owner issue
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list && \
sed -i 's|security.debian.org/debian-security|mirrors.tuna.tsinghua.edu.cn/debian-security|g' /etc/apt/sources.list && \
sed -i 's|security.debian.org|mirrors.tuna.tsinghua.edu.cn/debian-security|g' /etc/apt/sources.list && \
apt-get update && \
apt-get install -y nginx supervisor && \
rm -rf /var/lib/apt/lists/* && \
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple && \
pip install -r requirements.txt --no-cache-dir && \
rm -rf /var/lib/apt/lists/* && \
echo "daemon off;" >> /etc/nginx/nginx.conf && \
python manage.py collectstatic --noinput
ENV DJANGO_PRODUCTION=1
COPY deploy/nginx-app.conf /etc/nginx/sites-available/default
COPY deploy/supervisor-app.conf /etc/supervisor/conf.d/
EXPOSE 80
ENTRYPOINT [ "/bin/bash", "deploy/entrypoint.sh" ]
CMD ["supervisord", "-n"]

先换源,安装nginx和supervisor,对pip换源,安装requirements.txt中的所有以来,复制配置文件。

Django设置

Django的SECRET_KEY和数据库设置是个比较麻烦的点,但是django的配置文件是动态的,所以我们可以做如下设置

1
2
3
4
5
SECRET_KEY = os.environ.get('SECRET_KEY') or 'xxxxxxxxxxxxx'
if os.environ.get('DJANGO_PRODUCTION'):
DEBUG = False
else:
DEBUG = True

通过环境变量来确定当前是生产环境还是开发环境,利用同样的原理,我们可以这样配置数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if DEBUG:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': os.environ.get('MYSQL_DATABASE_NAME') or os.environ.get('DJANGO_DATABASE_NAME') or 'clinic',
'USER': 'root',
'PASSWORD': os.environ.get('MYSQL_ENV_MYSQL_ROOT_PASSWORD') or os.environ.get('DJANGO_DATABASE_PASSWORD'),
'HOST': os.environ.get('MYSQL_PORT_3306_TCP_ADDR') or os.environ.get('DJANGO_DATABASE_HOST'),
}
}

值得注意的是,如果容器与数据库容器进行过link操作,那么在容器中就会产生${连接时指定的hostname}_DATABASE_NAME这样的环境变量,我们只要连接数据库时指定--link some-db:mysql就可以使用这些变量自动设置数据库。
并且要配置STATIC_ROOT:

1
STATIC_ROOT = '/usr/share/nginx/html/static/'

为了Dockerfile中的collectstatic,将所有的静态文件都集中到一个文件夹中,在这里我们集中到了nginx的默认文件目录。

配置nginx

首先要在nginx主配置文件中写入deamon off,并且把相关配置写入/etc/nginx/sites-available/default中,相关配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
upstream django {
server unix:/usr/src/app/deploy/app.sock; # for a file socket
# server 127.0.0.1:8001; # for a web port socket (we'll use this first)
}

# configuration of the server
server {
# the port your site will be served on, default_server indicates that this server block
# is the block to use if no blocks match the server_name
listen 80 default_server;

# the domain name it will serve for
server_name .example.com; # substitute your machine's IP address or FQDN
charset utf-8;

# max upload size
client_max_body_size 75M; # adjust to taste

location /static {
alias /usr/share/nginx/html/static; # your Django project's static files - amend as required
}

# Finally, send all non-media requests to the Django server.
location / {
uwsgi_pass django;
include /usr/src/app/deploy/uwsgi_params; # the uwsgi_params file you installed
}
}

server_name可以自己改一改。
并且还要引入一个配置文件uwsgi_params,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;

uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param HTTPS $https if_not_empty;

uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

uWSIG配置

还需要配置一个uwsgi.ini内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[uwsgi]
# this config will be loaded if nothing specific is specified
# load base config from below
ini = :base

# %d is the dir this configuration file is in
socket = /usr/src/app/deploy/app.sock
master = true
processes = 4

[dev]
ini = :base
# socket (uwsgi) is not the same as http, nor http-socket
socket = :8001

[local]
ini = :base
http = :8000
# set the virtual env to use

[base]
# chdir to the folder of this config file, plus app/website
chdir = /usr/src/app
# load the module from wsgi.py, it is a python path from
# the directory above.
module=${PROJECT}:application
# allow anyone to connect to the socket. This is very permissive
chmod-socket=666

注意将${PROJECT}改成项目名称(就是包含wsgi.py的包)

supervisord配置

supervisor保证nginx和uwsgi一起运行,supervisor-app.conf文件配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[supervisord]
nodaemon=true

[program:app-uwsgi]
command = /usr/local/bin/uwsgi --ini /usr/src/app/deploy/uwsgi.ini
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true

[program:nginx-app]
command = /usr/sbin/nginx
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true

将两个程序的日志全部重定向到stdout

完成migrate

migrate操作是在容器开始运行,连接数据库后需要进行的操作,该操作无法在docker build的时候完成,所以我们需要一个简单的entrypoint.sh

1
2
3
4
#!/bin/bash
set -e
python3 manage.py migrate
exec "$@"