本章将在本地搭建一个容器化的 Django 项目,感受 Docker 的运作方式。
前期准备
开发环境
虽然有基于 Windows 的 Docker 版本,但各方面兼容做得都不太好(安装也麻烦些),因此建议读者在学习前,自行安装好 Linux 或 Mac 系统。当然你愿意折腾的话,在 Windows 上搞也行。
别担心,以后开发 Django 项目仍然可以在 Windows 下进行,仅仅是开发时不使用 Docker 而已。
软件安装
- Docker:学习 Docker 当然要安装 Docker 软件了(免费的社区版),安装方法见官方文档。
- Docker-compose:这是 Docker 官方推出的用于编排、运行多个容器的工具,安装方法见官方文档。本教程大部分内容都与它有关。
- Python3:教程部署的是 Django 项目,那 Python3 是当然要有的了(包括 python 的包管理工具 pip)。
准备就绪后就继续下一步吧。
创建 Django 项目
打开 Linux/Mac 的终端,安装 Django 库:
$ pip install django==2.2
在一个你喜欢得位置(比如/home/)创建新的 Django 项目:
$ django-admin startproject django_app
进入项目根目录:
$ cd django_app
教程后面的内容全部都在此目录中操作了。为方便阅读,命令提示符 $
代表目前在项目根目录 django_app/
,mysql $
则代表目前在目录 django_app/mysql/
中,请读者操作时稍加留意当前的工作目录。
然后迁移数据:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
...
Applying sessions.0001_initial... OK
准备工作就搞定了。
用Docker构建项目
初识Docker
Docker 的整个生命周期由三部分组成:镜像(image)+ 容器(container)+ 仓库(repository)。
容器是由镜像实例化而来,这有点像面向对象的概念:镜像就是类,容器是类实例化之后的对象。
镜像是一个只读的模板,它包括了运行容器所需的数据。镜像可以包含一个完整的 Linux 操作环境,里面仅安装了 Python 或者其他用户需要的程序。
容器是由镜像创建出来的实例,类似虚拟机,里面可以运行特定的应用,并且容器与容器是相互隔离的。
仓库概念与 Git 和 Github 类似,如果你用过它们就非常容易理解。Docker 使用的默认仓库是由官方维护的 Docker hub 公共仓库,从中上传、拉取的操作类似 Git。
目前需要了解的就这么多,下面通过实践来理解。
Hello-world
为了确认 Docker 已经正确安装了,运行以下指令:
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
...
latest: Pulling from library/hello-world
1b930d010525: Pull complete
...
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
一切正常的话,终端将打印出如上图所示的欢迎语句。docker run hello-world
指令的含义是:用名称为 hello-world
的镜像构建一个容器并运行。如果本地上没有这个 hello-world
的镜像, Docker 会自动从仓库搜索并下载同名的镜像。
我们可以用 docker images
查看本地已有的镜像:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest fce289e99eb9 9 months ago 1.84kB
表列分别为镜像名、版本、ID 号、创建时间、大小。
还可以查看本地已有的容器:
$ docker ps -a
CONTAINER ID IMAGE .. CREATED ..
38cb03a96dca hello-world .. 2 minutes ago ..
除此之外还有一些非常有用的基础指令:
docker rmi [images ID] # 删除此 ID 的镜像
docker container stop [container ID] # 停止此 ID 的容器
docker container start [container ID] # 启动此 ID 的容器
docker container rm [container ID] # 删除此 ID 的容器
由于测试时会频繁生成镜像,因此你肯定会用上面的指令查看、删除无用的镜像和容器。
牛刀小试完毕,接下来正式构建 Django 容器。
Dockerfile
Docker 允许通过文本格式的配置文件来构建镜像,默认名称为 Dockerfile。因此在项目根目录新建文件 Dockerfile,写入:
# 从仓库拉取 带有 python 3.7 的 Linux 环境
FROM python:3.7
# 设置 python 环境变量
ENV PYTHONUNBUFFERED 1
# 创建 code 文件夹并将其设置为工作目录
RUN mkdir /code
WORKDIR /code
# 更新 pip
RUN pip install pip -U
# 将 requirements.txt 复制到容器的 code 目录
ADD requirements.txt /code/
# 安装库
RUN pip install -r requirements.txt
# 将当前目录复制到容器的 code 目录
ADD . /code/
理解这些 Docker 指令的关键在于,一定要牢记容器里的环境和外界(宿主机)是隔离的,它两是完全不一样的。换句话说,要搞清楚哪些操作是针对宿主机、哪些操作是针对容器。
FROM python:3.7
指令从仓库拉取一个包含 python 3.7 的 Linux 操作系统环境(Linux 版本为 Debian)。
RUN
和 WORKDIR
指令都是针对容器的,功能是在容器里创建目录、并将其设置为工作目录。注意宿主机是没有这个目录的。
ADD
指令出现了两次。ADD requirements.txt /code/
意思是将宿主机当前目录(即 Dockerfile 所在目录)的 requirements.txt
文件复制到容器的 /code
目录中。ADD . /code/
意思是把当前目录所有内容复制到容器 /code/
目录,注意中间那个点。
目前项目依赖的唯一库就是 Django,所以在项目根目录创建 requirements.txt
并写入:
django==2.2
前面不是已经安装过 Django 了吗,为什么这里还要安装?原因是前面是在宿主机安装的,容器里是没有 Django 的!
所以目前的文件结构如下:
django_app
- Dockerfile
- requirements.txt
- manage.py
- django_app
- db.sqlite3
配置文件就写好了,接下来看看 Docker-compose
怎么用。
Docker-compose
在线上环境中,通常不会将项目的所有组件放到同一个容器中;更好的做法是把每个独立的功能装进单独的容器,这样方便复用。比如将 Django 代码放到容器A,将 Mysql 数据库放到容器B,以此类推。
因此同一个服务器上有可能会运行着多个容器,如果每次都靠一条条指令去启动,未免也太繁琐了。 Docker-compose
就是解决这个问题的,它用来编排多个容器,将启动容器的命令统一写到 docker-compose.yml 文件中,以后每次启动这一组容器时,只需要 docker-compose up
就可以了。因此教程也会用 docker-compose 来管理容器。
首先确认 docker-compose 是否安装成功:
$ docker-compose -v
docker-compose version 1.24.1, build 4667896b
确认无误后,在项目根目录创建 docker-compose.yml 并写入:
version: "3"
services:
app:
restart: always
build: . # '点'代表当前目录
command: "python3 manage.py runserver 0.0.0.0:8000"
volumes:
- .:/code
ports:
- "8000:8000"
让我们来分解一下其中的各项含义。
version
代表 docker-compose.yml 的版本,目前最新版为 3,不需要改动它。
接着定义了一个名叫 app
的容器。后面的内容都是 app
容器的相关配置:
-
restart
:除正常工作外,容器会在任何时候重启,比如遭遇 bug、进程崩溃、docker 重启等情况。 -
build
:指定一个包含 Dockerfile 的路径,并通过此 Dockerfile 来构建容器镜像。注意那个 "." ,代表当前目录。 -
command
:容器运行时需要执行的命令。这里就是我们很熟悉的运行开发服务器了。 -
volumes
:卷,这是个很重要的概念。前面说过容器是和宿主机完全隔离的,但是有些时候又需要将其连通;比如我们开发的 Django 项目代码常常会更新,并且更新时还依赖如 Git 之类的程序,在容器里操作就显得不太方便。所以就有卷,它定义了宿主机和容器之间的映射:"." 表示宿主机的当前目录,":" 为分隔符,"/code" 表示容器中的目录。即宿主机当前目录和容器的 /code 目录是连通的,宿主机当前目录的 Django 代码更新时,容器中的 /code 目录中的代码也相应的更新了。这有点儿像是在容器上打了一个洞,某种程度上也是实用性和隔离性的一种妥协。
严格意义上讲,这里用到的 .:/code
并不是卷,而是叫挂载,它两是有区别的,只不过 docker-compose 允许将挂载写到卷的配置中。后面章节会讲到。
-
ports
:定义了宿主机和容器的端口映射。容器的隔离不止环境,甚至连端口都隔离起来了。但 web 应用不通过端口跟外界通信当然不行,因此这里定义将宿主机的 8000 端口映射到容器的 8000 端口,即访问宿主机的 8000 端口就是访问到了容器的 8000 端口,但要确保端口没有被其他程序占用。
配置就写好了。现在项目的目录结构如下:
django_app
- docker-compose.yml
- Dockerfile
- requirements.txt
- manage.py
- django_app
- db.sqlite3
测试
输入指令 docker-compose up
启动容器服务:
$ docker-compose up
Creating network "django_app_default" with the default driver
Building app
Step 1/8 : FROM python:3.7
3.7: Pulling from library/python
4a56a430b2ba: Pull complete
...
6933d3d46042: Pull complete
Digest: sha256:0f0e991a97426db345ca7ec59fa911c8ed27ced27c88ae9966b452bcc6438c2f
Status: Downloaded newer image for python:3.7
---> 02d2bb146b3b
Step 1/8 : FROM python:3.7
---> 02d2bb146b3b
...
Step 7/8 : RUN pip install -r requirements.txt
---> Running in 62a60a3003fe
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting django==2.2 (from -r requirements.txt (line 1))
Downloading https://files.pythonhosted.org/packages/54/85/0bef63668fb170888c1a2970ec897d4528d6072f32dee27653381a332642/Django-2.2-py3-none-any.whl (7.4MB)
...
Installing collected packages: sqlparse, pytz, django
Successfully installed django-2.2 pytz-2019.2 sqlparse-0.3.0
...
Step 8/8 : ADD . /code/
---> cb23f483ffb6
Successfully built cb23f483ffb6
Successfully tagged django_app_app:latest
WARNING: Image for service app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating django_app_app_1 ... done
Attaching to django_app_app_1
app_1 | Watching for file changes with StatReloader
app_1 | Performing system checks...
app_1 |
app_1 | System check identified no issues (0 silenced).
app_1 | October 05, 2019 - 15:03:15
app_1 | Django version 2.2, using settings 'django_app.settings'
app_1 | Starting development server at http://0.0.0.0:8000/
app_1 | Quit the server with CONTROL-C.
可以看到 Docker 按照配置文件的要求,成功构建了镜像及容器,并启动了容器。
打开浏览器,输入本地 IP 端口 127.0.0.1:8000
:
看到 Django 的小火箭,项目成功运行起来啦。按 Ctrl + C
即可停止开发服务器运行。
停止服务器后实际上容器还存在,只是停止运行了而已。输入:
$ docker-compose down
就可以删除容器。
如果想在后台运行容器,则输入:
$ docker-compose up -d
另外,如果你需要重新构建镜像,则输入命令:
$ docker-compose build
启动和停止已有的容器:
$ docker-compose start
$ docker-compose stop
很简单吧。
下载太慢?
由于众所周知的原因,国内的网络环境非常复杂。在构建镜像时经常需要从国外的远程仓库拉取资源,岿然不动的下载速度真的头疼。
解决方法就是将资源拉取链接修改为国内的镜像源,比如清华的镜像源。
修改 Dockerfile 如下:
FROM python:3.7
ENV PYTHONUNBUFFERED 1
# 添加 Debian 清华镜像源
RUN echo \
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster main contrib non-free\
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-updates main contrib non-free\
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-backports main contrib non-free\
deb https://mirrors.tuna.tsinghua.edu.cn/debian-security buster/updates main contrib non-free\
> /etc/apt/sources.list
RUN mkdir /code
WORKDIR /code
# 添加 pip 清华镜像源
RUN pip install pip -U -i https://pypi.tuna.tsinghua.edu.cn/simple
ADD requirements.txt /code/
# 添加 pip 清华镜像源
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
ADD . /code/
重新构建镜像,下载速度就飞一样快了。
教程后面的内容都使用了清华源,但为了方便阅读,把这一部分更换源的代码省去了,读者心里明白就好。
总结
本章初步感受了 Docker 的工作流程,并且很轻松的构建了一个容器化的 Django 项目。
下一章将 MySQL 数据库也加入到容器编排中。
- 有疑问请在杜赛的个人网站留言,我会尽快回复。
- 教程示例代码:django-docker-tutorial
- 或Email私信我:dusaiphoto@foxmail.com
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。