当用Django框架开发的网站项目完成后上线的话可以用物理主机、虚拟机来部署,也可以容器。今天来讲讲如何用docker部署网站。本讲的主要内容:
1、ubuntu中安装dokcer和配置
2、使用Dockerfile文件来创建项目的镜像文件
3、使用docker-compose文件来部署项目
4、数据库迁移及容器互相访问
sudo apt install -y docker.io
#或
sudo snap install -y docker.io --classic
#也可以安装docker.ce
#安装完成,docker已经启动好
docker -v
systemctl status docker
systemctl restart docker
systemctl stop docker
systemctl start docker
Docker的守护线程绑定的是unix socket,而不是TCP端口,这个套接字默认属于root,其他用户可以通过sudo去访问这个套接字文件。所以docker服务进程都是以root账户运行。
解决的方式是创建docker用户组,把应用用户加入到docker用户组里面。只要docker组里的用户都可以直接执行docker命令。
可以先通过指令查看是否有用户组:
sudo groupadd docker
sudo usermod -aG docker 用户名
cat /etc/group
sudo systemctl restart docker
sudo chmod a+rw /var/run/docker.sock
在与django项目目录同级创建文件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,于是可以与网站项目共用。依次来看。
到 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://: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支持的编程语言。
查询一下这些容器的网络信息:
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 请求,正在等待回应............
借助图形界面pgadmin4来完成。pgadmin4连接到宿主机及上述的容器中的postgresql。
#备份命令
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。
#还原命令
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"
这说明宿主机的数据库成功迁移到了容器中了,容器的项目almond如何连接到容器中的数据库呢?
在“四、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, 大功告成。
也可以在宿主机中这样访问,验证一下容器正常工作。实际中访问宿主机。
总结说明:
容器中内容修改不会反应到镜像中,镜像一旦建立是只读的,一个镜像可以产生多个容器。容器的内容修改后可以另存为新的镜像。
本讲中数据库的还原和代码的修改是在容器中进行的,当容器重新创建时它们就没有了,需要重来一遍的(把前面的坑填上)。可以把数据库迁移和代码修改放在最后做。
以下是网站项目及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 commit
和docker save
来生成新的镜像和备份镜像,再到别的机器上部署,镜像文件大小达10几个GB。怎么部署,看情况定吧。
本次系列文章到此告一段落,6篇文章是创建在线编程教学网站的一个开发实录,也是对近期工作的总结。不到之处请赐教!