第六讲 用docker 部署Django项目

     阅读:32

当用Django框架开发的网站项目完成后上线的话可以用物理主机、虚拟机来部署,也可以容器。今天来讲讲如何用docker部署网站。本讲的主要内容:

1、ubuntu中安装dokcer和配置
2、使用Dockerfile文件来创建项目的镜像文件
3、使用docker-compose文件来部署项目
4、数据库迁移及容器互相访问

一、安装docker

1、安装docker:

sudo apt install -y docker.io
#或
sudo snap install -y docker.io --classic
#也可以安装docker.ce

2、查看docker版本:

#安装完成,docker已经启动好
docker -v

3、查看docker状态:

systemctl status docker

4、重新启动docker服务:

systemctl restart docker

5、停止docker服务:

systemctl stop docker

6、启动docker服务:

systemctl start docker

二、docker基本配置

Docker的守护线程绑定的是unix socket,而不是TCP端口,这个套接字默认属于root,其他用户可以通过sudo去访问这个套接字文件。所以docker服务进程都是以root账户运行。

解决的方式是创建docker用户组,把应用用户加入到docker用户组里面。只要docker组里的用户都可以直接执行docker命令。
可以先通过指令查看是否有用户组:

第一步:创建docker用户组

sudo groupadd docker

第二步:用户加入到用户组

sudo usermod -aG docker 用户名

第三步:检查是否有效

cat /etc/group

第四步:重启docker-daemon

sudo systemctl restart docker

第五步:给docker.sock添加权限

sudo chmod a+rw /var/run/docker.sock

三、根据Dockerfile文件创建镜像文件

在与django项目目录同级创建文件Dockerfile,文件内容如下;

Dockerfile

FROM 命令: 创建镜像是以一个基础镜像为底层来分层建立的。这里以 python:bulleye 这个dockerhub中公共的镜像来构建,该镜像系统是debian 11,python是3.10.4 ,pip 是 22.04版本。

RUN pip install django pysycopg2 gunicorn命令: 安装django框架,postgresql数据库的驱动psycopg2, gunicorn是被广泛应用的高性能的Python WSGI HTTP Server。用来解析HTTP请求的网关服务。对于简单的网站的运行它完全能够胜任。具体应用在下文件中介绍。

ADD almond /lichee-code 命令: 把当前项目目录中的所有内容复制到镜像中,目录名为lichee-code。它们是对应的,以后在almond中修改代码,容器中lichee-code会相应地修改。对项目作修改后,要重新启动容器,修改才能生效。

WORKDIR /lichee-code: 设置工作目录,Dockerfile中的任何RUN,CMD,ENTRPOINT,COPY和ADD指令的工作目录。

ENTRYPOINT entrypoint.sh: 容器启动时,在工作目录中找到entrypoint.sh,并运行。其中内容就是启动网站:

exec gunicorn -w 4 -b 0.0.0.0:8888 almond.wsgi > ./log/almond-code.log

执行命令:docker build -t lichee-code:1.0.1 .,为当前目录的Dockerfile来构建名称为 “lichee-code:1.0.1” 的镜像。

wuxc@wubuntu:/home/lichee$ docker build -t lichee-code:1.0.1 .
Sending build context to Docker daemon  71.86MB
Step 1/8 : FROM python:bullseye
bullseye: Pulling from library/python
dbba69284b27: Downloading [=========>                                         ]  10.23MB/54.94MB
9baf437a1bad: Downloading [============>                                      ]  1.262MB/5.156MB
6ade5c59e324: Downloading [=====>                                             ]  1.262MB/10.87MB
b19a994f6d4c: Waiting 
8fc2294f89de: Waiting
...........

636646f6a8cd: Pull complete 
Digest: sha256:9087640dab6b02f8a831a18fef5c756f33269fe53faa997533523912d27d61fb
Status: Downloaded newer image for python:bullseye
 ---> 403fd3ce9d68
Step 2/8 : RUN pip install install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -I --no-cache-dir django psycopg2 gunicorn
 ---> Running in 87a5938559e5
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple/
Collecting install
  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/4d/c8/8cbca135f9e167810756ea2bc34b028501936675fcbd7dadccf752fa4622/install-1.3.5-py3-none-any.whl (3.2 kB)
Collecting django
  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/66/90/bce00eb942fbc47b0774ac78910ee4e6f719572aad56dc238823e5d0ee54/Django-4.0.4-py3-none-any.whl (8.0 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.0/8.0 MB 3.1 MB/s eta 0:00:00
Collecting psycopg2
  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/d1/1e/b450599a27b1809bccbd4e369f397cb18dc56b875778d961f9ae180b54b7/psycopg2-2.9.3.tar.gz (380 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 380.6/380.6 KB 1.7 MB/s eta 0:00:00
............
Building wheels for collected packages: psycopg2
Installing collected packages: sqlparse, setuptools, psycopg2, install, asgiref, gunicorn, django
Successfully installed asgiref-3.5.0 django-4.0.4 gunicorn-20.1.0 install-1.3.5 psycopg2-2.9.3 setuptools-62.1.0 sqlparse-0.4.2

Removing intermediate container 87a5938559e5
 ---> c88f11bb627a
Step 3/8 : RUN mkdir -p /lichee-code
 ---> Running in 19457df2d860
Removing intermediate container 19457df2d860
 ---> f5f41b896d8d
Step 4/8 : ADD almond /lichee-code
 ---> bcdc6470d813
Step 5/8 : WORKDIR /lichee-code
 ---> Running in deb1128df24a
Removing intermediate container deb1128df24a
 ---> 160ecc697482
Step 6/8 : EXPOSE 8888 8080 8000
 ---> Running in 15bd9c32b8d7
Removing intermediate container 15bd9c32b8d7
 ---> 8aeb8788671e
Step 7/8 : ENV SPIDER=/lichee-code
 ---> Running in 781e17b6edf3
Removing intermediate container 781e17b6edf3
 ---> 91ee96a528d0
Step 8/8 : ENTRYPOINT /lichee-code/entrypoint.sh
 ---> Running in 408884b4fcf2
Removing intermediate container 408884b4fcf2
 ---> 047646957cc6
Successfully built 047646957cc6
Successfully tagged lichee-code:1.0.1

构建过程中断,可以重新执行命令继续来构建。通过docker images 可以列表出所有的镜像。

wuxc@wubuntu:/home/lichee$ docker images | grep lichee
lichee-code                 1.0.1      047646957cc6   3 minutes ago   1GB

运行镜像,生成容器。容器可以看成是镜像的运行态,镜像是只读的。docker ps 查看容器运行情况。

wuxc@wubuntu:/home/lichee$ docker run -p 8000:8888 -d 0476 --name lichee-code:1.0.1
88210ac0467ae63e2c52141a2ba07b909bca8b33b7e377657c6601e701e9fa6a
wuxc@wubuntu:/home/lichee$ docker ps -l
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS              PORTS                     NAME
88210ac0467a   0476      "/bin/sh -c /lichee-…"   About a minute ago   Up About a minute   0.0.0.0:8000->8888/tcp    lichee-code:1.0.1

在浏览器中运行 http://127.0.0.1:8000 ,

访问容器中的网站

可以打开首页。其它页面打不开,还有两项工作要做。一是数据库部署到docker , 二是 judgeServer 部署到docker 。这里judge0作为程序的测评后台服务,它也用到了数据库postgresql,于是可以与网站项目共用。依次来看。

四、使用docker-compose.yml来部署judge0和Postgresql数据库等

1、部署容器

到 https://github.com/judge0/judge0/releases 下载 1.13.0版本, 官方安装教程如下:

wget https://github.com/judge0/judge0/releases/download/v1.13.0/judge0-v1.13.0.zip
unzip judge0-v1.13.0.zip

#Run all services and wait a few seconds until everything is initialized:
cd judge0-v1.13.0
docker-compose up -d db redis
sleep 10s
docker-compose up -d
sleep 5s
#Your instance of Judge0 CE v1.13.0 is now available at http://<IP ADDRESS OF YOUR SERVER>:2358.

解压之后有两个文件,一个是docker-compose.yml另一个是配置文件judge0.conf。命令docker-compose up -d,第一次运行会下载镜像,生成容器之后就后台运行,查看如下

wuxc@wubuntu:/home/lichee$ docker ps |grep lichee
88210ac0467a   0476                     "/bin/sh -c /lichee-…"    2 hours ago      Up 2 hours      8000->8888/tcp      lichee-code:1.0.1
58652583d9cc   judge0/judge0:1.13.0     "/api/docker-entrypo…"    3 months ago     Up 2 hours      2358/tcp            lichee_workers_1
fef6976690ed   judge0/judge0:1.13.0     "/api/docker-entrypo…"    3 months ago     Up 2 hours      2358->2358/tcp      lichee_server_1
e80f2158bcbf   postgres:13.0            "docker-entrypoint.s…"    3 months ago     Up 2 hours      5432/tcp            lichee_db_1
e6ba960bf997   redis:6.0                "docker-entrypoint.s…"    3 months ago     Up 2 hours      6379/tcp            lichee_redis_1

http://127.0.0.1:2358/languages ,看到judgeServer支持的编程语言。
juge0支持的编程语言
查询一下这些容器的网络信息:

wuxc@wubuntu:/home/lichee$ docker network ls | grep lichee
9e9a5d2e30aa   lichee_default        bridge    local

wuxc@wubuntu:/home/lichee$ docker inspect 9e9a | grep -E "Name|IPv4"
                "Name": "lichee_default",
                "Name": "lichee_workers_1",
                "IPv4Address": "172.19.0.4/16",
                "Name": "lichee_redis_1",
                "IPv4Address": "172.19.0.2/16",
                "Name": "lichee_db_1",
                "IPv4Address": "172.19.0.3/16",
                "Name": "lichee_server_1",
                "IPv4Address": "172.19.0.5/16",

postgresql数据库的地址是172.19.0.3,可以 wget 172.19.0.3:5432来验证一下:

wuxc@wubuntu:/home/lichee$ wget 172.19.0.3:5432
--2022-04-16 11:43:46--  http://172.19.0.3:5432/
正在连接 172.19.0.3:5432... 已连接。
已发出 HTTP 请求,正在等待回应............

2、迁移数据库

借助图形界面pgadmin4来完成。pgadmin4连接到宿主机及上述的容器中的postgresql。

  1. pgadmin4连接容器中的数据库,用户名和密码在配置文件judge0.conf中,还记得judge0-v1.13.0.zip解压后有两个文件。
    连接容器数据库

  2. 创建用户wuxc,密码自定,权限前几项都选上。
    创建用户

  3. 创建空数据库lichee-wuxc, 所有者选wuxc。
    创建数据库

  4. 备份宿主机的数据库:
    备份数据库

#备份命令
sudo pg_dump --file "/var/lib/pgadmin/storage/ntwuxc_qq.com/bak-almond-22-04-16" --host "127.0.0.1" --port "5432" --username "wuxc" --format=t --blobs "lichee-wuxc"

备份文件名是,bak-almond-22-04-16.tar。

  1. 还原到容器中:

还原数据库

#还原命令
pg_restore --host "172.19.0.3" --port "5432" --username "wuxc"  --dbname "lichee-wuxc" --verbose "/var/lib/pgadmin/storage/ntwuxc_qq.com/bak-almond-22-04-16"
  1. 查询容器中数据库

查询记录

这说明宿主机的数据库成功迁移到了容器中了,容器的项目almond如何连接到容器中的数据库呢?

五、almond网站项目与judge0放在一个容器网络内,以调用judgeServer和数据库

在“四、1、中”使用docker network ......docker inspect ...... 查询了容器网络信息,judge0的几个容器在一个虚拟网络中,它们都是按docker-compose.yml这个文件来创建并启动的。而almond网络项目的镜像和容器是单独创建和运行的,它与前面的4个容器不在一个网络中。把almond这个容器创建启动的配置加到docker-compose.yml中,让它们在一个网络中,方便调用judge0的服务。(当然,你也可以通过其他方法实现分离的容器通过网桥来互相访问。)

# docker-compose.yml 中增加 web: 这一段
version: '2'

x-logging:
  &default-logging
  logging:
    driver: json-file
    options:
      max-size: 100m

services:
  server:
    image: judge0/judge0:1.13.0
    volumes:
      - ./judge0.conf:/judge0.conf:ro
    ports:
      - "2358:2358"
    privileged: true
    <<: *default-logging
    restart: always

  workers:
    image: judge0/judge0:1.13.0
    command: ["./scripts/workers"]
    volumes:
      - ./judge0.conf:/judge0.conf:ro
    privileged: true
    <<: *default-logging
    restart: always

  db:
    image: postgres:13.0
    env_file: judge0.conf
    volumes:
      - postgres-data:/var/lib/postgresql/data/
    <<: *default-logging
    restart: always

  redis:
    image: redis:6.0
    command: [
      "bash", "-c",
      'docker-entrypoint.sh --appendonly yes --requirepass "$$REDIS_PASSWORD"'
    ]
    env_file: judge0.conf
    volumes:
      - redis-data:/data
    <<: *default-logging
    restart: always

  web:
    image: lichee-code:1.0.1
    container_name: lichee-code
    volumes:
      - ./almond:/lichee-code
    ports:
      - "0.0.0.0:9000:8888"
    restart: always
    depends_on:
      - db
      - server

volumes:
  postgres-data:
  redis-data:

web段是对应的almond网站项目的:image后的镜像是在“三、”创建的;container_name定义启动容器名称;volomes指定宿主与容器中的共享卷,可以理解成同步目录;ports是映射端口,在容器中开放的端口(Dockerfile文件中设置)对应左边的宿主机的端口,以后访问用的是宿主机的ip及端口;depends_on是让almond可以在容器内直接访数据库和judge0测评服务。先关闭所有容器,用修改后的docker-compose创建启动所有容器(这里有个坑,在本末有说明),进入almond容器中访问数据库服务和judge0测评服务来验证一下。

(base) wuxc@wubuntu:/home/lichee$ docker ps | grep judge0
58652583d9cc   judge0/judge0:1.13.0     "/api/docker-entrypo…"    3 months ago     Up 7 hours      2358/tc           judge0_workers_1
fef6976690ed   judge0/judge0:1.13.0     "/api/docker-entrypo…"    3 months ago     Up 7 hours      12358->2358/tcp   judge0_server_1
e80f2158bcbf   postgres:13.0            "docker-entrypoint.s…"    3 months ago     Up 7 hours      5432/tcp          judge0_db_1
e6ba960bf997   redis:6.0                "docker-entrypoint.s…"    3 months ago     Up 7 hours      6379/tcp          judge0_redis_1
(base) wuxc@wubuntu:/home/lichee$ docker container rm 8821 5865 fef6 e80f e6ba -f
8821
5865
f3f6
e80f
e6ba
(base) wuxc@wubuntu:/home/lichee$ docker-compose up -d
Creating lichee_workers_1 ... done
Creating lichee_redis_1   ... done
Creating lichee_db_1      ... done
Creating lichee_server_1  ... done
Creating lichee-code      ... done

(base) wuxc@wubuntu:/home/lichee$ docker ps | grep lichee
CONTAINER ID   IMAGE                    COMMAND                   CREATED         STATUS              PORTS             NAMES
2a2270d286b9   lichee-code:1.0.1        "/bin/sh -c /lichee-…"    8 minutes ago   Up 8 minutes        9000->8888/tcp    lichee-code
e3011fd98d71   judge0/judge0:1.13.0     "/api/docker-entrypo…"    8 minutes ago   Up 8 minutes        2358->2358/tcp    lichee_server_1
bbb5985e9b7d   postgres:13.0            "docker-entrypoint.s…"    8 minutes ago   Up 8 minutes        5432/tcp          lichee_db_1
66bb40224ea2   judge0/judge0:1.13.0     "/api/docker-entrypo…"    8 minutes ago   Up 8 minutes        2358/tcp          lichee_workers_1
a96c921897b0   redis:6.0                "docker-entrypoint.s…"    8 minutes ago   Up 8 minutes        6379/tcp          lichee_redis_1

进入almond网站项目的容器lichee-code中,访问两个服务:

(base) wuxc@wubuntu:/home/lichee$ docker exec -it 2a22 /bin/bash
#容器的提示符与宿主机是不一样的
root@2a2270d286b9:/lichee-code# wget db:5432
--2022-04-16 08:03:53--  http://db:5432/
Resolving db (db)... 172.20.0.2
Connecting to db (db)|172.20.0.2|:5432... connected.
HTTP request sent, awaiting response... No data received.
Retrying.
^C

root@2a2270d286b9:/lichee-code# wget server:2358/languages
--2022-04-16 08:04:29--  http://server:2358/languages
Resolving server (server)... 172.20.0.4
Connecting to server (server)|172.20.0.4|:2358... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/json]
Saving to: 'languages'

languages       [ <=>                                  ]   1.68K  --.-KB/s    in 0s

2022-04-16 08:04:29 (229 MB/s) - 'languages' saved [1723]

root@2a2270d286b9:/lichee-code# cat languages
[{"id":45,"name":"Assembly (NASM 2.14.02)"},{"id":46,"name":"Bash (5.0.0)"},{"id":47,"name":"Basic (FBC 1.07.1)"},{"id":75,"name":"C (Clang 7.0.1)"},{"id":76,"name":"C++ (Clang 7.0.1)"},{"id":48,"name":"C (GCC 7.4.0)"},{"id":52,"name":"C++ (GCC 7.4.0)"},{"id":49,"name":"C (GCC 8.3.0)"},{"id":53,"name":"C++ (GCC 8.3.0)"},{"id":50,"name":"C (GCC 9.2.0)"},{"id":54,"name":"C++ (GCC 9.2.0)"},{"id":86,"name":"Clojure (1.10.1)"},{"id":51,"name":"C# (Mono 6.6.0.161)"},{"id":77,"name":"COBOL (GnuCOBOL 2.2)"},{"id":55,"name":"Common Lisp (SBCL 2.0.0)"},{"id":56,"name":"D (DMD 2.089.1)"},{"id":57,"name":"Elixir (1.9.4)"},{"id":58,"name":"Erlang (OTP 22.2)"},{"id":44,"name":"Executable"},{"id":87,"name":"F# (.NET Core SDK 3.1.202)"},{"id":59,"name":"Fortran (GFortran 9.2.0)"},{"id":60,"name":"Go (1.13.5)"},{"id":88,"name":"Groovy (3.0.3)"},{"id":61,"name":"Haskell (GHC 8.8.1)"},{"id":62,"name":"Java (OpenJDK 13.0.1)"},{"id":63,"name":"JavaScript (Node.js 12.14.0)"},{"id":78,"name":"Kotlin (1.3.70)"},{"id":64,"name":"Lua (5.3.5)"},{"id":89,"name":"Multi-file program"},{"id":79,"name":"Objective-C (Clang 7.0.1)"},{"id":65,"name":"OCaml (4.09.0)"},{"id":66,"name":"Octave (5.1.0)"},{"id":67,"name":"Pascal (FPC 3.0.4)"},{"id":85,"name":"Perl (5.28.1)"},{"id":68,"name":"PHP (7.4.1)"},{"id":43,"name":"Plain Text"},{"id":69,"name":"Prolog (GNU Prolog 1.4.5)"},{"id":70,"name":"Python (2.7.17)"},{"id":71,"name":"Python (3.8.1)"},{"id":80,"name":"R (4.0.0)"},{"id":72,"name":"Ruby (2.7.0)"},{"id":73,"name":"Rust (1.40.0)"},{"id":81,"name":"Scala (2.13.2)"},{"id":82,"name":"SQL (SQLite 3.27.2)"},{"id":83,"name":"Swift (5.2.3)"},{"id":74,"name":"TypeScript (3.7.4)"},{"id":84,"name":"Visual Basic.Net (vbnc 0.0.0.5943)"}]

一切正常。

六、最后的工作

在宿主机中修改代码:
在settings.py中修改数据库的配置, 第1处27行:DEBUG = False;第2处92行:'HOST': 'db'
在coding.judge0.py中修改第1处56行
HEADERS = {"x-rapidapi-host": 'http://server:2358/', "useQueryString": True},第2处175行 API_URL = 'http://server:2358/'
这样容器中的项目就可以用名称’db’、'server’访问judge0的两个服务了。
这些修改会自动对应到容器lichee-code中去的代码的,重新容器lichee-code使之生效。

(base) wuxc@wubuntu:/home/lichee$ docker restart lichee-code
lichee-code

访问网站http://127.0.0.1:9000, 大功告成。

网站截图1也可以在宿主机中这样访问,验证一下容器正常工作。实际中访问宿主机。
网站截图2

总结说明:
容器中内容修改不会反应到镜像中,镜像一旦建立是只读的,一个镜像可以产生多个容器。容器的内容修改后可以另存为新的镜像。
本讲中数据库的还原和代码的修改是在容器中进行的,当容器重新创建时它们就没有了,需要重来一遍的(把前面的坑填上)。可以把数据库迁移和代码修改放在最后做。
以下是网站项目及docker部署的文件(数据库备份文件更名了),大小只有几MB。

wuxc@wubuntu:/home/lichee$ tree -L 1
.
├── almond
├── bak-almond-postgresql-22-04-16
├── docker-compose.yml
├── Dockerfile
└── judge0.conf

1 directory, 4 files

你可以使用docker commitdocker save来生成新的镜像和备份镜像,再到别的机器上部署,镜像文件大小达10几个GB。怎么部署,看情况定吧。

本次系列文章到此告一段落,6篇文章是创建在线编程教学网站的一个开发实录,也是对近期工作的总结。不到之处请赐教!