部署之前 当我们在本地开发完一个django
项目后,如果需要供其他人来访问,我们则必须将其部署到服务器上(一般是linux
),而要提供外部服务,则需要服务器能提供静态文件服务 、动态资源处理 等服务,而在我们调试阶段使用的runserver
,则无法提供这些功能。
因此,我们需要静态文件服务器(nginx)以及应用服务器(gunicorn、uwsgi服务器)。
这里以nginx
,gunicorn
为例。
先看一张关系图:
如图所示,当浏览器发起一个请求,nginx 服务器会先判断,该请求是否需要静态文件(即html
、css
、js
等文件),如果需要就直接返回给前端静态文件,如果还需要一些动态生成的数据,那么这个时候nginx 表示它自己处理不了了,但是它知道gunicorn 服务器能处理,此时,便会将该请求反向代理 到gunicorn 应用服务器上,gunicorn
收到该请求后,会将其请求报文(请求头、请求行、请求体)封装好,发送给django 框架来处理,django
拿到这个请求后,将其封装为HttpRequest
类,然后去查找路由表,匹配上路由后,调用对应的视图,再根据请求方法,调用对应的实例方法,来完成对数据的处理。数据处理完成后,将响应结果返回给gunicorn ,gunicorn 拿到响应结果将其封装(响应头、响应行、响应体)发送给nginx ,最后在由nginx 返回到前端展示。
因此,我们需要在linux服务器上安装配置gunicorn 、nginx ,这里我们采用docker
部署的方式。
部署准备 首先,我们的目标是要准备3个容器:
nginx容器:提供前、后端(在线接口文档平台)静态文件服务以及反向代理到应用服务器。
gunicorn容器:应用服务器,后端代码需要放在容器中。
mysql容器:数据库。
nginx容器 1、修改前端代码 api.js
将后端地址改为服务器地址。
2、生成静态文件 执行命令npm run build
,将vue的组件转换为静态js文件,执行之后,会生成一个dist
目录:
将该目录拷贝出并放到nginx_docker
文件夹下(本地新建一个)。
3、修改后端代码 settings.py
关闭调试模式
修改数据库连接信息,将数据库名改为mysql容器的名字db
(因为容器在桥接模式下,可以互相通过名称进行通信)。
执行pip freeze > requirements.txt
将项目依赖包导出。
4、收集静态文件 由于需要提供在线接口文档平台,所以还需要收集后端的静态文件。
在项目根目录下创建一个static
目录,然后在setting.py
中指明static文件配置:
然后命令行执行python manage.py collectstatic
收集静态文件,收集成功后,将static
目录下的rest_framwork
拷贝到步骤2 中生成的dist
目录下的static
目录下,如图:
5、修改nginx配置文件 首先修改nginx
业务配置
default.conf
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 # 定义反向代理服务器 upstream app_server { server django_app:8000; } server { # 提供后端服务 listen 8000; # 指定后端接口api的域名 server_name 121.5.140.33; # 接口文件平台 # 指定/static/路由 location /static { alias /var/www/html/static; } # 指定路由条目 location / { try_files $uri @proxy_to_app; } location @proxy_to_app { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://app_server; } } server { # 提供前端服务 # 指定监听的端口 listen 80; # 指定使用的域名 server_name 121.5.140.33; # 指定静态文件的根路径 root /var/www/html; # 指定日志文件 access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log; # 指定路由条目 location / { try_files $uri $uri/ /index.html; } error_page 404 /404.html; location = /404.html { root /usr/share/nginx/html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location ~ /\.ht { deny all; } }
为了保证nginx
服务挂掉后能自动重启,所以使用了守护进程supervisord
,在supervisord.conf
文件中添加监听nginx
服务的配置。
supervisord.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [unix_http_server] file=/tmp/supervisor.sock ; (the path to the socket file) [supervisord] logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log) logfile_maxbytes=5MB ; (max main logfile bytes b4 rotation;default 50MB) pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid) user=root nodaemon=true stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 [rpcinterface:supervisor] supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket [program:nginx] command=/usr/sbin/nginx stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stdout_events_enabled=true stderr_events_enabled=true
nginx主配置文件nginx.conf
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 27 28 29 30 31 32 33 user nginx; worker_processes 1; error_log /var/log/nginx/error.log; pid /run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; index index.html index.htm; include /etc/nginx/conf.d/*.conf; }
将上述三个配置文件放到一个文件夹configs
中(在本地新建一个)。
6、编写Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 FROM alpine:latestLABEL maintainer='AcientOne' LABEL description='Install nginx' RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && \ apk update && \ apk add --allow-untrusted ca-certificates bash curl iputils supervisor nginx && \ apk upgrade && \ rm -rf /var/cache/apk/* && \ mkdir /tmp/nginx && \ mkdir -p /var/www/html && \ chown -R nginx:nginx /var/www/html COPY dist/ /var/www/html/ COPY configs/default.conf /etc/nginx/conf.d/ COPY configs/nginx.conf /etc/nginx/nginx.conf COPY configs/supervisord.conf /etc/supervisord.conf VOLUME /var/log /nginx/ EXPOSE 80 8000 443 CMD ["supervisord" ]
最后,nginx容器构建所需文件目录如下:
gunicorn容器 1、编写gunicorn_config.py配置文件 gunicorn_config.py
1 2 3 4 5 6 7 8 bind = '0.0.0.0:8000' reload = True pidfile = '/usr/src/app/logs/gunicorn.pid' accesslog = '/usr/src/app/logs/gunicorn_acess.log' errorlog = '/usr/src/app/logs/gunicorn_error.log'
2、编写启动脚本docker-entrypoint.sh 1 2 3 4 5 6 7 8 9 10 # !/bin/sh # 在set -e之后出现的代码,一旦出现了返回值非零,整个脚本就会立即退出 set -e # 执行迁移 python manage.py makemigrations python manage.py migrate # 创建超级用户 echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('test', 'test@qq.com', 'test123')" | python manage.py shell &> /dev/null # 启动gunicorn服务,指定gunicorn配置文件和后端项目中的wsgi文件 /usr/local/bin/gunicorn -c /usr/src/app/configs/gunicorn_config.py ancient_test.wsgi
3、编写Dockerfile 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 27 28 29 30 31 32 33 FROM python:3 -alpineLABEL maintainer='AncientOne' LABEL description='deploying django project' WORKDIR /usr/src/app COPY ./ancient_test ./ancient_test/ COPY ./gunicorn_config.py ./configs/ COPY ./docker-entrypoint.sh /docker-entrypoint.sh WORKDIR ancient_test/ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && \ apk update && \ apk add --allow-untrusted build-base mariadb-connector-c-dev curl iputils && \ pip install --no-cache-dir -i https://pypi.douban.com/simple -r requirements.txt && \ pip install -i https://pypi.douban.com/simple gunicorn && \ apk add ca-certificates bash && \ apk update && apk upgrade && \ rm -rf /var/cache/apk/* && \ chmod u+x /docker-entrypoint.sh VOLUME /usr/src/app/logs/ VOLUME /usr/src/app/ancient_test/ EXPOSE 8000 ENTRYPOINT ["/docker-entrypoint.sh" ]
最后,gunicorn
容器构建所需的文件如下:
然后将DjangoDeploy 目录上传到linux服务器上,准备部署。
docker单容器启动 在所有容器构建前,先要创建一个容器桥接网络,让三个容器之间能互相通信。
为什么需要互相通信?
因为三个容器之间的关系是:前端访问–>nginx容器提供该服务(静态文件)–>如果需要数据,则反向代理给gunicorn服务–>gunicorn服务从数据库服务器拿数据。
而由于是桥接网络,所以三个容器之间可以通过容器名称互相访问,这里重命名三个容器的名称为:
nginx容器名:web
gunicore容器名:django_app
mysql容器名:db
需要注意的是,容器之间有依赖关系,所以容器的启动是有顺序的,mysql容器->gunicore容器->nginx容器。
1、执行命令创建网络,网络名为:django_app_net。
1 docker network create django_app_net
2、启动mysql容器。
1 docker run --name db -v mysql_db:/var/lib/mysql --restart=always -e MYSQL_ROOT_PASSWORD=xxxx -e MYSQL_DATABASE=ancient -d --network django_app_net mariadb --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
注:没有mariadb镜像会在容器启动前自动拉取。
若要进入数据库容器内部,执行:
1 docker run -it --network django_app_net --rm mariadb mysql -hdb -uxx -pxx -A ancient
3、构建gunicorn镜像
先进入gunicorn的Dockerfile
所在目录下,然后执行构建命令:
1 docker build -t ancientone/django_app:v1 .
4、构建nginx镜像
先进入nginx的Dockerfile
所在目录下,然后执行构建命令:
1 docker build -t ancientone/front_end:v1 .
5、启动gunicorn容器
1 docker run --name django_app -v logs:/usr/src/app/logs/ -v django_code:/usr/src/app/ancient_test/ --restart=always -d --network django_app_net -p 8080:8000 ancientone/django_app:v1
6、启动nginx容器
1 docker run --name web -v logs:/var/log /nginx/ -p 8444:80 -p 8440:8000 --restart=always -d --network django_app_net ancientone/front_end:v1
容器启动完成后,分别访问前后端,验证是否正常。
如图,表示前后端已能正常提供访问。
docker-compose批量启动 上面的容器启动方法,需要一个个的去启动,略显繁琐,有没有办法能批量启动呢?当然可以,使用docker-compose
。
安装docker-compose 分别执行:
1 sudo curl -L "https://github.com/docker/compose/releases/download/1.28.1/docker-compose-(uname -s) -(uname -m)" -o /usr/local /bin/docker-compose
1 sudo chmod +x /usr/local /bin/docker-compose
编写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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 version: '3' services: db: image: mariadb command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci volumes: - mysql_db:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: ancient networks: - django_app_net django_app: depends_on: - db build: ./django_app_docker image: ancientone/django_app:v1 restart: always volumes: - logs:/usr/src/app/logs/ - django_code:/usr/src/app/ancient_test/ networks: - django_app_net web: depends_on: - django_app build: ./nginx_docker image: ancientone/front_end:v1 restart: always ports: - "8444:80" - "8440:8000" volumes: - logs:/var/log/nginx/ networks: - django_app_net networks: django_app_net: volumes: mysql_db: django_code: logs:
启动 将docker-compose.yml
文件放到DjangoDeploy
目录下,然后执行
注:-d表示容器后台运行
启动后,会自动去构建镜像-启动容器,等待它自动执行完即可。
如果要停止所有启动的容器,执行
注:-v表示删除数据卷
shell脚本一键启动 继续偷懒,写个shell脚本来一键启动项目。
start.sh
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 # !/bin/bash # Author: AncientOne # Description: start run script # to project directory. PROJECT_DIR="/root/DeployDjango" function start_run { cd "${PROJECT_DIR}" docker-compose up -d &> /dev/null echo -e "构建中,请等待1分钟..." sleep 1m &> /dev/null echo -e "开始导入测试数据" bash import_test_data.sh &> /dev/null echo -e "构建完成!" } function main { cd "${PROJECT_DIR}" docker ps -a | grep -E 'deploydjango_.*' &> /dev/null if [[ $? -eq 0 ]] then echo -e "项目已经启动!" read -p "要重启项目吗? (y/n) " confirm if [[ "${confirm,,}" == "n" ]]; then echo -e "无需重启项目!\nBye!" exit 0 fi echo -e "准备重启项目..." # uninstalled this project echo -e "正在卸载项目..." docker-compose down &> /dev/null echo -e "正在删除数据卷..." docker volume rm -f `docker volume ls | awk '/deploydjango_.*/{print $2}'` &> /dev/null echo -e "开始重启项目..." start_run else echo -e "准备启动项目..." start_run fi } main
import_test_data.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # !/bin/bash # Author: AncientOne # Description: import test data into db container container_name="db" new_container_name="deploydjango_${container_name}_1" db_username="root" db_password="root" db_name="ancient" docker exec -i ${new_container_name} sh -c "exec mysql -u${db_username} -p'${db_password}' -A ${db_name}" < $PWD/datas/01_tb_projects.sql docker exec -i ${new_container_name} sh -c "exec mysql -u${db_username} -p'${db_password}' -A ${db_name}" < $PWD/datas/02_tb_interfaces.sql docker exec -i ${new_container_name} sh -c "exec mysql -u${db_username} -p'${db_password}' -A ${db_name}" < $PWD/datas/03_tb_testcases.sql docker exec -i ${new_container_name} sh -c "exec mysql -u${db_username} -p'${db_password}' -A ${db_name}" < $PWD/datas/04_tb_configures.sql docker exec -i ${new_container_name} sh -c "exec mysql -u${db_username} -p'${db_password}' -A ${db_name}" < $PWD/datas/05_tb_testsuits.sql docker exec -i ${new_container_name} sh -c "exec mysql -u${db_username} -p'${db_password}' -A ${db_name}" < $PWD/datas/06_tb_reports.sql docker exec -i ${new_container_name} sh -c "exec mysql -u${db_username} -p'${db_password}' -A ${db_name}" < $PWD/datas/07_tb_debugtalks.sql docker exec -i ${new_container_name} sh -c "exec mysql -u${db_username} -p'${db_password}' -A ${db_name}" < $PWD/datas/08_tb_envs.sql
将上述两个脚本放到部署目录Deploydjango
下,直接sh start.sh
启动。
大功告成,welldone~