Django容器化之开发环境部署

前面我们讲解了如何通过uWsgi来部署Django应用、部署的过程其实还是蛮复杂的;为了提高工作效率、有没有一种简单、快速的部署方式可以将Django应用快速发布到开发环境中呢?有的、这就是我们今天要讲的Django容器化;所谓的Django容器化是指我们可以利用Docker把Django应用以及相关依赖打包到一个可移动的镜像中、然后我们通过一条命令就可以把Django应用快速发布到类Unix的主机上。Django的开发环境需要安装很多的PIP包、C++库以及我们访问MySQL数据库的时候还要部署MySQL数据库的library等等。Django应用容器化以后我们可以复用开发环境、我们使用一个命令搭建可以运行的开发环境且每个应用隔离的环境、无Python/PIP包版本冲突。Docker的优点这里就不再详细介绍了、下面我们直奔主题、咱们先来看看如何通过Dockerfile构建Django镜像。

代码适配容器

Django容器化需要做的第一件事情就是代码适配。容器环境是一个独立的网络环境、容器内部的IP地址和宿主机是不通的;所以如果我们想从外面来访问容器的IP地址是无法访问的。因为Django应用默认只允许localhost进行访问、所以我们就需要去修改settings.py文件中的ALLOWD_HOSTS参数。还有一些配置是在我们运行Docker容器的时候能够通过变量来指定、对于这一类的变量我们也要在代码中把他们从硬编码改成从环境变量来读取。这样就可以保证我们在启动Docker容器的时候、配置是通过环境变量的方式来获取的。

# 允许所有人访问
ALLOWED_HOSTS=['*']

# 配置项放到环境变量中
python manage.py runserver 0.0.0.0:8000 --settings=settings.dev_settings
# 修改为
python manage.py runserver 0.0.0.0:8000 $server_params

我们在项目目录下创建一个名为 start.bat 的启动文件、内容如下:

# --settings=settings.dev_settings
python3 manage.py runserver 0.0.0.0:8000 $server_params

我们在启动脚本中把启动配置文件的参数放到环境变量的server_params中,目的是可以通过Dockerfile把环境变量传递进来覆盖默认的变量配置。这样我们就可以通过变量来指定现在的环境是开发环境还是生产环境了、根据不同的环境来指定不通的启动文件。

Dockerfile构建

注:没有安装Docker和Docker Compose的同学请自行百度安装。

代码适配完成之后我们就可以来通过Dockrfile构建镜像了、我们先来创建一个Dockerfile文件、内容如下:

# 引用python:3.9-alpine基础镜像
FROM python:3.9-alpine
# 容器中的工作目录
WORKDIR /data/EnvsProject/datasite
# 默认传递一个空值、我们通过docker run命令把变量传递进来
ENV server_params=

COPY requirements.txt ./
RUN apk add --update --no-cache curl jq py3-configobj py3-pip py3-setuptools python3 python3-dev mariadb-connector-c-dev \
  && apk add --no-cache gcc g++ jpeg-dev zlib-dev libc-dev libressl-dev musl-dev libffi-dev mariadb-dev \
  && python -m pip install --upgrade pip \
  && pip install -r requirements.txt \
  # 把安装过程中不再需要的安装包清理掉、达到缩减镜像大小的目的
  && apk del gcc g++ libressl-dev musl-dev libffi-dev python3-dev \
  && apk del curl jq py3-configobj py3-pip py3-setuptools \
  && rm -rf /var/cache/apk/*
# 拷贝项目目录中的所有文件到容器中
COPY . .
# 映射8000端口
EXPOSE 8000
# 启动Django应用
CMD ["/bin/sh", "/data/EnvsProject/datasite/start.bat"]

注:需要注意的是、因为Dockerfile是层级加载的、每一行都是一个Layer层级;这里为了缩减镜像大小、我们通过 && 符号把执行命令连接起来。还有一点是特别需要说明的、如果你也跟我一样使用了 mysqlclient 的 PIP包、那么在构建镜像的时候一定要把 mariadb-connector-c-dev 和 mariadb-dev 这2个基础包安装上;不然后面在指定 pip install mysqlclient 的时候会报错的。

前面我们通过COPY . . 命令把项目目录中的所有文件拷贝到容器中;但是项目目录中有很多问题是我们不需要上传到容器中的、对于这些不需要上传到容器中的文件、我们可以在项目目录中创建一个 .dockerignore 文件、把不需要上传到容器中的文件名称写到 .dockerignore 文件中:

.git
static
media
settings/bas_settings.py
settings/pro_settings.py

镜像构件

上面这些内容操作完成以后我们就可以通过 docker build 命令去指定 tag 来构建镜像了:

[root@v2ray datasite]# docker build -t z0ukun/datasite-base:v1.0 -f Dockerfile .
Sending build context to Docker daemon 60.79 MB
Step 1/8 : FROM python:3.9-alpine
 ---> 53261e7e236b
Step 2/8 : WORKDIR /data/EnvsProject/datasite
 ---> fca6bccd80bb
Removing intermediate container 6ed7617c1a63
Step 3/8 : ENV server_params 
 ---> Running in a66712ca5246
 ---> 05e7affc4af6
Removing intermediate container a66712ca5246
Step 4/8 : COPY requirements.txt ./
 ---> 463b9118a317
Removing intermediate container 4244bc4589f2
Step 5/8 : RUN apk add --update --no-cache curl jq py3-configobj py3-pip py3-setuptools python3 python3-dev mariadb-connector-c-dev   && apk add --no-cache gcc g++ jpeg-dev zlib-dev libc-dev libressl-dev musl-dev libffi-dev ..................

..................
 ---> dcc00c898b82
Removing intermediate container 40737dd30eb4
Step 8/8 : CMD /bin/sh /data/EnvsProject/datasite/start.bat
 ---> Running in 8122d1fd3227
 ---> 216fcc4ee714
Removing intermediate container 8122d1fd3227
Successfully built 216fcc4ee714
[root@v2ray datasite]# docker images
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
z0ukun/datasite-base   v1.0                216fcc4ee714        8 minutes ago       397 MB
docker.io/python       3.9-alpine          53261e7e236b        2 days ago          44.9 MB
docker.io/python       3.6.8-alpine        f3e18b628c1b        19 months ago       79.3 MB
[root@v2ray datasite]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
[root@v2ray datasite]#

注:第一次 build 它的速度会很慢、因为会下载很多的安装包、请小伙伴们耐心等待;当然你也可以把 docker 的官方仓库修改为国内的镜像仓库而不是只用中央仓库或者你也可以使用全局代理来加速。如果后面我们需要对 Dockerfile 进行更新修改、当你再次build的时候速度就会快很多、因为系统里面已经有了很多cache;还有一个原因就是我们前面提到的、Dockerfile 的每一行命令都是一个层或者叫一个layer;这个layer构建以后下次再次运行 Docker 命令发现同样层的时候、他就不会重复的去构建(当然、前提是你的基础镜像和基础命令没有做特别大的修改)。如果在构建过程中遇到卡顿或者报错的时候可以停止下来、排障完成以后重新执行构建命令、构建完成以后我们通过 docker images 命令可以看到构建完成的镜像。

运行容器

镜像构建完成之后我们通过交互式命令的方式把 Docker 容器启动起来:

# 交互式命令
docker run -it --rm -p 8000:8000 --entrypoint /bin/sh z0ukun/datasite-base:v1.0

注:这里 -it 的意思是以 interactive terminal 的方式也就是以带有终端的交互式命令的方式来运行;–rm 表示如果我们把容器停止掉他就会自动把容器删除;-p 8000(主机端口):8000(容器端口) 表示我们把容器里面的8000端口映射到本机的8000端口;–entrypoint /bin/sh 表示容器启动以后我们需要运行容器的 /bin/sh 命令(shell命令)。

[root@localhost datasite]# docker run -it --rm -p 8000:8000 --entrypoint /bin/sh z0ukun/datasite-base:v1.0
/data/EnvsProject/datasite # 
/data/EnvsProject/datasite # ls
Dockerfile        datasite          logs              pid               settings          static            uwsgi.ini
chpa_data         db.sqlite3        manage.py         requirements.txt  start.bat         templates
/data/EnvsProject/datasite # uname
Linux
/data/EnvsProject/datasite # cat /etc/os-release 
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.12.3
PRETTY_NAME="Alpine Linux v3.12"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
/data/EnvsProject/datasite # python --version
Python 3.9.1
/data/EnvsProject/datasite # cd settings/
/data/EnvsProject/datasite/settings # ls
__init__.py       __pycache__       base_settings.py  dev_settings.py   pro_settings.py
/data/EnvsProject/datasite/settings # exit
[root@localhost datasite]# docker ps -a
CONTAINER ID   IMAGE                COMMAND                CREATED      STATUS        PORTS     NAMES
4382e099c0b7   prom/node-exporter   "/bin/node_exporter"   2 days ago   Up 20 hours             quirky_ardinghelli
[root@localhost datasite]# 

那么我们在开发环境中如何使用容器呢? 我们需要通过Docker容器把Django应用启动起来并在后台运行、我们通过 -v “$(pwd)” 命令在启动容器的时候把当前项目的路径 /data/EnvsProject/datasite mount到容器 /data/EnvsProject/datasite 目录中、这样只要我们本地的代码发生变化容器里面也会自动加载。然后通过 –env server_params=”–settings=settings.dev_settings” 命令指定一个环境变量。

# 指定加载源码 && 环境变量
docker run -it --rm -p 8000:8000 -v "(pwd)":/data/EnvsProject/datasite --env server_params="--settings=settings.dev_settings" z0ukun/datasite-base:v1.0


[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE                       COMMAND                  CREATED              STATUS              PORTS                    NAMES
e864b4218ba9   z0ukun/datasite-base:v1.0   "/bin/sh /data/EnvsP…"   About a minute ago   Up About a minute   0.0.0.0:8000->8000/tcp   frosty_brattain
4382e099c0b7   prom/node-exporter          "/bin/node_exporter"     2 days ago           Up 20 hours                                  quirky_ardinghelli
[root@localhost ~]# 

(datasite) [root@localhost datasite]# docker run -it --rm -p 8000:8000 -v "(pwd)":/data/EnvsProject/datasite --env server_params="--settings=settings.dev_settings" z0ukun/datasite-base:v1.0
Watching for file changes with StatReloader
2021-02-05 11:43:42,270 django.utils.autoreload 597 INFO     Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
February 05, 2021 - 11:43:42
Django version 2.2.4, using settings 'settings.dev_settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.


2021-02-05 11:43:44,101 chpa_data.middleware 16 INFO     0.011873960494995117 /metrics {}
[05/Feb/2021 11:43:44] "GET /metrics HTTP/1.1" 200 5430
2021-02-05 11:44:44,090 chpa_data.middleware 16 INFO     0.002233266830444336 /metrics {}
[05/Feb/2021 11:44:44] "GET /metrics HTTP/1.1" 200 5428

我们可以看到 Docker 容器已经成功启动了 Django 应用并且开放了8000端口、我们直接去访问宿主机的8000端口发现可以正常访问页面;我们现在要来测试一下如果修改了本地代码、Docker容器是不是会自动加载。

image-20210205114611316

我们在浏览器里面随便输出一个地址、从下图可以看到当前是DEBUG模式;我们把本地的 dev_settings.py 文件修改为 DEBUG=False、然后再次刷新页面:

image-20210205114853442

我们修改完 dev_settings.py 文件之后可以看到 Docker 容器自动重启并完成自动加载、访问页面也可以看到页面显示Not Found表示现在不是DEBUG模式了。

2021-02-05 11:47:43,366 django.request 222 WARNING  Not Found: /s
[05/Feb/2021 11:47:43] "GET /s HTTP/1.1" 404 2522
2021-02-05 11:47:44,104 chpa_data.middleware 16 INFO     0.014976978302001953 /metrics {}
[05/Feb/2021 11:47:44] "GET /metrics HTTP/1.1" 200 5449
2021-02-05 11:48:44,091 chpa_data.middleware 16 INFO     0.002314329147338867 /metrics {}
[05/Feb/2021 11:48:44] "GET /metrics HTTP/1.1" 200 5449
/data/EnvsProject/datasite/settings/dev_settings.py changed, reloading.
2021-02-05 11:49:17,569 django.utils.autoreload 217 INFO     /data/EnvsProject/datasite/settings/dev_settings.py changed, reloading.
2021-02-05 11:49:18,651 django.utils.autoreload 597 INFO     Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
February 05, 2021 - 11:49:19
Django version 2.2.4, using settings 'settings.dev_settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.
2021-02-05 11:49:25,258 chpa_data.middleware 16 INFO     0.0077250003814697266 /s {}
2021-02-05 11:49:25,259 django.request 222 WARNING  Not Found: /s
[05/Feb/2021 11:49:25] "GET /s HTTP/1.1" 404 77
2021-02-05 11:49:44,093 chpa_data.middleware 16 INFO     0.00237274169921875 /metrics {}
[05/Feb/2021 11:49:44] "GET /metrics HTTP/1.1" 200 5431

image-20210205114932366

这样我们就完成了Django容器化改造并快速实现了开发环境的部署、也可以在不同的开发人员之间去共享容器。

Docker RUN命令常用选项

# RUN 命令
# RUN 指令是用来执行命令行命令的,是最常用的指令之一。
# 命令格式:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
# 意思为:通过run命令创建一个新的容器
# 常用选项说明:
-d, --detach=false, # 指定容器运行于前台还是后台,默认为false
-i, --interactive=false, # 打开STDIN,用于控制台交互
-t, --tty=false, # 分配tty设备,该可以支持终端登录,默认为false
-u, --user="", # 指定容器的用户
-a, --attach=[], # 登录容器(必须是以docker run -d启动的容器)
-w, --workdir="", # 指定容器的工作目录
-c, --cpu-shares=0, # 设置容器CPU权重,在CPU共享场景使用
-e, --env=[], # 指定环境变量,容器中可以使用该环境变量
-m, --memory="", # 指定容器的内存上限
-P, --publish-all=false, # 指定容器暴露的端口
-p, --publish=[], # 指定容器暴露的端口
-h, --hostname="", # 指定容器的主机名
-v, --volume=[], # 给容器挂载存储卷,挂载到容器的某个目录
--volumes-from=[], # 给容器挂载其他容器上的卷,挂载到容器的某个目录
--cidfile="", # 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
--cpuset="", # 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
--device=[], # 添加主机设备给容器,相当于设备直通
--dns=[], # 指定容器的dns服务器
--dns-search=[], # 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
--entrypoint="", # 覆盖image的入口点
--env-file=[], # 指定环境变量文件,文件格式为每行一个环境变量
--expose=[], # 指定容器暴露的端口,即修改镜像的暴露端口
--link=[], # 指定容器间的关联,使用其他容器的IP、env等信息
--lxc-conf=[], # 指定容器的配置文件,只有在指定--exec-drive
--name="", # 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
--net="bridge", # 容器网络设置:bridge 使用docker daemon指定的网桥;host //容器使用主机的网络;container:NAME_or_ID >//使用其他容器的网路,共享IP和PORT等网络资源;none 容器使用自己的网络(类似--net=bridge),但是不进行配置。
--privileged=false, # 指定容器是否为特权容器,特权容器拥有所有的capabilities
--restart="no", # 指定容器停止后的重启策略:no:容器退出时不重启;on-failure:容器故障退出(返回值非零)时重启;always:容器退出时总是重启。
--rm=false, # 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)
--sig-proxy=true, # 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理。

推荐文章

1条评论

  1. Pretty! This was an extremely wonderful article. Thanks for providing this information. Jeannette Link Trip

发表评论

邮箱地址不会被公开。 必填项已用*标注