在Docker环境运行Django

注解

本文实践采用 Docker Compose 完成设置,运行一个简单的Django/MySQK应用。

请参考 Django开发环境 完成设置,并确保能够 运行Django

在Docker环境中运行Django,需要创建Dockerfile,Python依赖文件,以及 docker-compose.yml 文件。

定义项目组件

在项目目录下,需要创建一个 Dockerfie ,定义了应用程序的镜像名以及更多的build命令来定制镜像。一旦构建之后,就可以在容器中运行镜像。

  • Dockerfile 内容如下:
1
2
3
4
5
6
7
FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/

这里的 Dockerfile 开始部分使用了 Python 3 parent image ,这个父镜像通过添加 code 目录,然后安装通过 requirements.txt 文件定义的Python运行依赖。

  • 在项目目录下创建 requirements.txt ,例如,我的app项目内容如下:
1
2
3
4
5
6
7
8
sphinx
sphinx_rtd_theme
Django
selenium
djangorestframework
markdown
django-filter
mysqlclient

这个Python运行依赖定义文件是通过 RUN pip install -r requirements.txt 执行的。

  • 创建 docker-compose.yml 配置

docker-compose.yml 文件描述应用服务,这里是web服务和数据库服务。compose文件描述了服务使用的Docker镜像,连接方式,以及在容器中挂载的卷。最后 docker-compose.yml 文件还描述了输出服务的端口。

docker-compose.yml :

 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
version: '3'

services:
  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    env_file:
      - db.env
    #environment:
    #    MYSQL_ROOT_PASSWORD: mypw
    #    MYSQL_DATABASE: mydb
    #    MYSQL_USER: myapp_user
    #    MYSQL_PASSWORD: myapp_passwd
    #    MYSQL_ALLOW_EMPTY_PASSWORD: no
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    env_file:
      - db.env
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

注解

后文我详细解释配置项用途。

创建Django项目

通过上述 docker-compose.yml 定义的image以及服务依赖关系,我们现在就可以创建Django项目( 通过 docker-compose run 命令可以在容器中执行命令)

docker-compose run web django-admin startproject myapp .

上述指令在容器中运行了 django-admin startproject myapp ,并且使用了web服务镜像和配置。初次运行时,web镜像还不存在。则Compose就会在当前目录下构建,因为这里在 docker-compose.yml 中指定了 build: .

注解

由于我已经 Django开发环境 完成设置,并确保能够 运行Django ,所以我在这里执行的命令是:

docker-compose run web

这样就跳过了 django-admin startporject myapp . 初始化Django项目,直接使用之前已经创建好的Django环境。

运行提示:

WARNING: You are using pip version 19.3.1; however, version 20.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

Successfully built adb7f7c47877
Successfully tagged onesre_web:latest
WARNING: Image for service web was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
January 22, 2020 - 08:34:52
Django version 3.0.2, using settings 'onesre.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

注意,此时还不能打开 http://0.0.0.0:8000/ 。但是,通过 docker exec -it <CONTAINER_ID> /bin/sh 登陆到容器内部,检查 ps aux | grep pyton 可以看到服务进程:

root         1  0.0  1.7  43568 35648 pts/0    Ss+  13:18   0:00 python manage.py runserver 0.0.0.0:8000
root         9  4.4  1.8 118980 38520 pts/0    Sl+  13:18   0:50 /usr/local/bin/python manage.py runserver 0.0.0.0:8000

这说明容器内部服务已经正常启动,但是docker没有实现port map。仔细看了 docker ps ,可以看到仅仅是启动了 myapp_web 容器。所以,此时没有启动db情况下,还没有做docker的端口映射。

MySQL容器化运行

注解

参考 dockerhub MySQL 说明 可以看到,官方mysql镜像支持通过环境变量传递初始化密码和数据库名。

  • docker-compose.yml 的以下高亮黄色的两行是读取环境 db.env 配置:
 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
version: '3'

services:
  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    env_file:
      - db.env
    #environment:
    #    MYSQL_ROOT_PASSWORD: mypw
    #    MYSQL_DATABASE: mydb
    #    MYSQL_USER: myapp_user
    #    MYSQL_PASSWORD: myapp_passwd
    #    MYSQL_ALLOW_EMPTY_PASSWORD: no
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    env_file:
      - db.env
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

注解

虽然 docker compose 支持直接在 docker-compose.yml 中直接配置环境变量,但是通常会把 docker-compose.yml 文件提交到软件仓库,这样存在安全隐患。

所以,我们采用 env_file 方式引用一个不会添加到git仓库的文件,例如 db.env ,内容包含如下:

1
2
3
4
5
MYSQL_ROOT_PASSWORD=root_passwd
MYSQL_DATABASE=mydb
MYSQL_USER=myapp_user
MYSQL_PASSWORD=myapp_passwd
MYSQL_ALLOW_EMPTY_PASSWORD=no

然后,在 .gitignore 中添加一行内容 db.env 避免该文件被提交到git仓库,以保证安全。

Compose 环境变量请参考 Environment variables in Compose

  • 启动数据库:

    docker-compose run db
    

此时会看到mysql数据库按照环境配置启动并初始化。完成后请执行:

docker exec -it <CONTAINER_ID> /bin/bash

进入数据库服务器,然后执行:

mysql mydb -umyapp_user -pmyapp_passwd

验证数据库连接和运行,并且可以执行简单的查询。

数据库连接

  • 默认Django数据库连接是本地sqlite,通过配置项目目录下 myapp/settings.py 文件的 DATABASES = ... 部分来连接数据库

Django密码安全secrets.json

由于Django项目的 settings.py 也同样提交的git仓库,则为了避免风险,需要将数据库配置部分分离到独立配置文件,并且这个数据库账号配置文件不可提交到git仓库。

参考 Django, Security and Settings ,有两种方法实现Django敏感数据:

环境变量设置数据库密码

  • 如果你没有使用docker来运行django,则通常可以在 ~/.bash_profile 中设置环境变量,这样启动Django也是能获得数据库密码账号:

    export MYSQL_PASSWORD=myapp_passwd
    
  • 修订 settings.py 导入配置:

    ...
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': os.getenv('MYSQL_DATABASE'),
            'USER': os.getenv('MYSQL_USER'),
            'PASSWORD': os.getenv('MYSQL_PASSWORD'),
            'HOST': 'db',
            'PORT': '3306',
        }
    }
    

这样启动后Django就可以从环境变量中获取账号密码。

注解

How do I pass environment variables to Docker containers? 介绍了多种方法传递环境变量到Docker容器内部。在docker docs文档中提供案例: Set environment variables (-e, –env, –env-file)

docker run -e MYVAR1 –env MYVAR2=foo –env-file ./env.list ubuntu bash

这里的案例,你可以设置一个 DB_PASSWORD=myapp_passwd 环境变量,然后执行docker

同样,参考前面针对 db 服务的环境变量设置, docker-compose 也支持读取环境变量,我们可以创建一个 web.env 保存Django的数据库环境变量(实际上就是 db.env 配置的部分内容)。为了能够简化配置,我们可以复用 db 服务的环境配置(所以这里就用了相同的环境变量名)。

启动服务

集中上述配置,现在我们可以启动 docker-compose.yml 配置的 db 和 web 服务:

docker-compose up

此时通过浏览器访问 http://127.0.0.1:8000/ 可以看到Django欢迎页面:

../../_images/docker_compose_django.png

注意,这里控制台提示信息:

web_1  | You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
web_1  | Run 'python manage.py migrate' to apply them.

这说明我们遗漏了一步数据库迁移,所以我们先停止掉 db 和 web 容器,然后执行一次django数据库迁移:

docker-compose run web python manage.py migrate

这个步骤会自动启动数据库,然后执行django迁移,完成后再次启动 docker-compose up 就可以正常使用。

  • 停止应用的方法可以通过在控制台按下 Ctrl-C 来停止容器,另外也有一种比较优雅的方法,就是使用 docker-compose 命令,即启动另外一个shell窗口,还是在这个目录下执行:

    docker-compose down
    

快速实现Docker运行Djgnao

上述的部署过程比较曲折,以下为总结,可以快速完成 django + mysql 部署。

  • Dockerfile :
1
2
3
4
5
6
7
FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
  • requirements.txt (指示pip安装) :
1
2
3
4
5
6
7
8
sphinx
sphinx_rtd_theme
Django
selenium
djangorestframework
markdown
django-filter
mysqlclient
  • docker-compose.yml (构建容器关系,和数据库相关账号数据通过 db.env 引入):
 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
version: '3'

services:
  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    env_file:
      - db.env
    #environment:
    #    MYSQL_ROOT_PASSWORD: mypw
    #    MYSQL_DATABASE: mydb
    #    MYSQL_USER: myapp_user
    #    MYSQL_PASSWORD: myapp_passwd
    #    MYSQL_ALLOW_EMPTY_PASSWORD: no
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    env_file:
      - db.env
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db
  • db.env 配置db和web使用的账号和数据库配置:
1
2
3
4
5
MYSQL_ROOT_PASSWORD=root_passwd
MYSQL_DATABASE=mydb
MYSQL_USER=myapp_user
MYSQL_PASSWORD=myapp_passwd
MYSQL_ALLOW_EMPTY_PASSWORD=no
  • 启动并初始化数据库:

    docker-compose run db
    
  • 数据库初始化完成后,开启另外一个shell,在当前目录下停止容器:

    docker-compose down
    
  • 初始化Django项目(在当前目录执行创建myapp应用):

    docker-compose run web django-admin startproject myapp .
    

注解

这个步骤将启动django进行项目初始化,并提示:

Successfully built 6ab89519f3bd
Successfully tagged myapp_web:latest  # 这里标签是根据当前目录 myapp  加上 _web
  • 修改 Django 配置 myapp/settings.py

    ...
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': os.getenv('MYSQL_DATABASE'),
            'USER': os.getenv('MYSQL_USER'),
            'PASSWORD': os.getenv('MYSQL_PASSWORD'),
            'HOST': 'db',
            'PORT': '3306',
        }
    }
    
  • 执行django数据库迁移(该命令会依次启动db,然后再启动web服务进行django数据库初始化:

    docker-compose run web python manage.py migrate
    
  • 可以停止数据库再次启动验证:

    docker-compose down
    docker-compose up
    

一切正常情况下,使用浏览器就可以访问Django最初的欢迎页面,后续进行开发

  • 当代码迭代开发,则执行:

    docker-compose build
    
  • 然后再次启动:

    docker-compose up
    

也可以将上述两个命令结合成 docker-comose up --build

注解

目前发现这个部署还是有一点问题,mysql数据库初始化( docker-compose build )较慢,导致web启动后连接数据库失败。不过第二次启动,MySQL无需初始化则启动迅速,则web正常工作。