在上篇教程中,我們手動構建了兩個容器,一個容器放Django + Uwsgi,另一個容器放Nginx,成功部署了一個簡單的Django項目。然而在實際的生產環境中,我們往往需要定義數量龐大的 docker 容器,並且容器之間具有錯綜複雜的依賴聯繫,一個一個去手動創建容器並記錄和配置這些複雜的容器關係,不僅效率低下而且容易出錯,所以迫切需要一種定義容器集羣編排和部署的工具,這就是docker-compose。本文是Docker部署Django項目的終章,我們將詳細介紹如何使用docker-compose工具八步部署Django + Uwsgi + Nginx + MySQL + Redis,並分享一個可以複用的項目佈局和各項服務的配置文件,強烈建議先收藏再閱讀。閱讀本篇前強烈建議先閱讀本系列文章的上篇和中篇。
什麼是docker-compose及docker-compose工具的安裝
Docker-compose是一個用來定義和運行復雜應用的 Docker 工具。使用 docker-compose 後不再需要使用 shell 腳本來逐一創建和啓動容器,還可以通過 docker-compose.yml 文件構建和管理複雜多容器組合。
Docker-compose的下載和安裝很簡單,網上有很多教程,我就不再詳述了。這裏只記錄下ubuntu系統下docker-compose的安裝過程。
# Step 1: 以ubuntu爲例,下載docker-compose $ sudo curl -L https://github.com/docker/compose/releases/download/1.17.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose # Step 2: 給予docker-compose可執行權限 $ sudo chmod +x /usr/local/bin/docker-compose # Step 3: 查看docker-compose版本 $ docker-compose --version
注意:安裝docker-compose前必需先安裝好docker。
Django + Uwsgi + Nginx + MySQL + Redis組合容器示意圖
本例中我們將使用docker-compose編排並啓動4個容器,這更接近於實際生成環境下的部署。
-
Django + Uwsgi容器:核心應用程序,處理動態請求
-
MySQL 容器:數據庫服務
-
Redis 容器:緩存服務
-
Nginx容器:反向代理服務並處理靜態資源請求
這四個容器的依賴關係是:Django+Uwsgi 容器依賴 Redis 容器和 MySQL 容器,Nginx 容器依賴Django+Uwsgi容器。爲了方便容器間的相互訪問和通信,我們使用docker-compose時可以給每個容器取個別名,這樣訪問容器時就可以直接使用別名訪問,而不使用Docker臨時給容器分配的IP了。
這四個容器的別名及通信端口如下圖所示:
Docker-compose部署Django項目佈局樹形圖
我們新建了一個compose文件夾,專門存放用於構建其它容器鏡像的Dockerfile及配置文件。compose文件夾與django項目的根目錄myproject同級。這樣做的好處是不同的django項目可以共享compose文件夾。
myproject_docker # 項目根目錄 ├── compose # 存放各項容器服務的Dockerfile和配置文件 │ ├── mysql │ │ ├── conf │ │ │ └── my.cnf # MySQL配置文件 │ │ └── init │ │ └── init.sql # MySQL啓動腳本 │ ├── nginx │ │ ├── Dockerfile # 構建Nginx鏡像所的Dockerfile │ │ ├── log # 掛載保存nginx容器內nginx日誌 │ │ ├── nginx.conf # Nginx配置文件 │ │ └── ssl # 如果需要配置https需要用到 │ ├── redis │ │ └── redis.conf # redis配置文件 │ └── uwsgi # 掛載保存django+uwsgi容器內uwsgi日誌 ├── docker-compose.yml # 核心編排文件 └── myproject # 常規Django項目目錄 ├── Dockerfile # 構建Django+Uwsgi鏡像的Dockerfile ├── apps # 存放Django項目的各個apps ├── manage.py ├── myproject # Django項目配置文件 │ ├── asgi.py │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── pip.conf # 非必需。pypi源設置成國內,加速pip安裝 ├── requirements.txt # Django項目依賴文件 ├── start.sh # 啓動Django+Uwsgi容器後要執行的腳本 ├── media # 用戶上傳的媒體資源與靜態文件 ├── static # 項目所使用到的靜態文件 └── uwsgi.ini # uwsgi配置文件
下面我們開始正式部署。
第一步:編寫docker-compose.yml文件
docker-compose.yml的核心內容如下。我們定義了3個數據卷,用於掛載各個容器內動態生成的數據,比如MySQL的存儲數據,redis生成的快照和django容器中用戶上傳的媒體資源與文件。這樣即使刪除容器,容器內產生的數據也不會丟失。我們還編排了4項容器服務,別名分別爲redis, db, nginx和web,接下來我們將依次看看各個容器的Dockerfile和配置文件。
version: "3" volumes: # 自定義數據卷,位於宿主機/var/lib/docker/volumes內 myproject_db_vol: # 定義數據卷同步容器內mysql數據 myproject_redis_vol: # 定義數據卷同步redis容器內數據 myproject_media_vol: # 定義數據卷同步media文件夾數據 services: redis: image: redis:5 command: redis-server /etc/redis/redis.conf # 容器啓動後啓動redis服務器 volumes: - myproject_redis_vol:/data # 通過掛載給redis數據備份 - ./compose/redis/redis.conf:/etc/redis/redis.conf # 掛載redis配置文件 ports: - "6379:6379" restart: always # always表容器運行發生錯誤時一直重啓 db: image: mysql:5.7 environment: - MYSQL_ROOT_PASSWORD=123456 # 數據庫密碼 - MYSQL_DATABASE=myproject # 數據庫名稱 - MYSQL_USER=dbuser # 數據庫用戶名 - MYSQL_PASSWORD=password # 用戶密碼 volumes: - myproject_db_vol:/var/lib/mysql:rw # 掛載數據庫數據, 可讀可寫 - ./compose/mysql/conf/my.cnf:/etc/mysql/my.cnf # 掛載配置文件 - ./compose/mysql/init:/docker-entrypoint-initdb.d/ # 掛載數據初始化sql腳本 ports: - "3306:3306" # 與配置文件保持一致 restart: always web: build: ./myproject # 使用myproject目錄下的Dockerfile expose: - "8000" volumes: - ./myproject:/var/www/html/myproject # 掛載項目代碼 - myproject_media_vol:/var/www/html/myproject/media # 以數據卷掛載容器內用戶上傳媒體文件 - ./compose/uwsgi:/tmp # 掛載uwsgi日誌 links: - db - redis depends_on: # 依賴關係 - db - redis environment: - DEBUG=False restart: always tty: true stdin_open: true nginx: build: ./compose/nginx ports: - "80:80" - "443:443" expose: - "80" volumes: - ./myproject/static:/usr/share/nginx/html/static # 掛載靜態文件 - ./compose/nginx/ssl:/usr/share/nginx/ssl # 掛載ssl證書目錄 - ./compose/nginx/log:/var/log/nginx # 掛載日誌 - myproject_media_vol:/usr/share/nginx/html/media # 掛載用戶上傳媒體文件 links: - web depends_on: - web restart: always
第二步:編寫Web (Django+Uwsgi)鏡像和容器所需文件
構建Web鏡像(Django+Uwsgi)的所使用的Dockerfile如下所示:
# myproject/Dockerfile # 建立 python3.7 環境 FROM python:3.7 # 鏡像作者大江狗 MAINTAINER DJG # 設置 python 環境變量 ENV PYTHONUNBUFFERED 1 COPY pip.conf /root/.pip/pip.conf # 創建 myproject 文件夾 RUN mkdir -p /var/www/html/myproject # 將 myproject 文件夾爲工作目錄 WORKDIR /var/www/html/myproject # 將當前目錄加入到工作目錄中(. 表示當前目錄) ADD . /var/www/html/myproject # 更新pip版本 RUN /usr/local/bin/python -m pip install --upgrade pip # 利用 pip 安裝依賴 RUN pip install -r requirements.txt # 去除windows系統編輯文件中多餘的\r回車空格 RUN sed -i 's/\r//' ./start.sh # 給start.sh可執行權限 RUN chmod +x ./start.sh
本Django項目所依賴的requirements.txt
內容如下所示:
# django django==3.0.6 # uwsgi uwsgi==2.0.18 # mysql mysqlclient==1.4.6 # redis django-redis==4.11.0 redis==3.5.0 # for images Pillow==7.1.2
start.sh
腳本文件內容如下所示。最重要的是最後一句,使用uwsgi.ini配置文件啓動Django服務。
#!/bin/bash # 從第一行到最後一行分別表示: # 1. 收集靜態文件到根目錄, # 2. 生成數據庫可執行文件, # 3. 根據數據庫可執行文件來修改數據庫 # 4. 用 uwsgi啓動 django 服務 python manage.py collectstatic --noinput&& python manage.py makemigrations&& python manage.py migrate&& uwsgi --ini /var/www/html/myproject/uwsgi.ini
uwsgi.ini
配置文件如下所示:
[uwsgi] project=myproject uid=www-data gid=www-data base=/var/www/html chdir=%(base)/%(project) module=%(project).wsgi:application master=True processes=2 socket=0.0.0.0:8000 chown-socket=%(uid):www-data chmod-socket=664 vacuum=True max-requests=5000 pidfile=/tmp/%(project)-master.pid daemonize=/tmp/%(project)-uwsgi.log #設置一個請求的超時時間(秒),如果一個請求超過了這個時間,則請求被丟棄 harakiri = 60 post buffering = 8192 buffer-size= 65535 #當一個請求被harakiri殺掉會,會輸出一條日誌 harakiri-verbose = true #開啓內存使用情況報告 memory-report = true #設置平滑的重啓(直到處理完接收到的請求)的長等待時間(秒) reload-mercy = 10 #設置工作進程使用虛擬內存超過N MB就回收重啓 reload-on-as= 1024 python-autoreload=1
第三步:編寫Nginx鏡像和容器所需文件
構建Nginx鏡像所使用的Dockerfile如下所示:
# nginx鏡像compose/nginx/Dockerfile FROM nginx:latest # 刪除原有配置文件,創建靜態資源文件夾和ssl證書保存文件夾 RUN rm /etc/nginx/conf.d/default.conf \ && mkdir -p /usr/share/nginx/html/static \ && mkdir -p /usr/share/nginx/html/media \ && mkdir -p /usr/share/nginx/ssl # 設置Media文件夾用戶和用戶組爲Linux默認www-data, 並給予可讀和可執行權限, # 否則用戶上傳的圖片無法正確顯示。 RUN chown -R www-data:www-data /usr/share/nginx/html/media \ && chmod -R 775 /usr/share/nginx/html/media # 添加配置文件 ADD ./nginx.conf /etc/nginx/conf.d/ # 關閉守護模式 CMD ["nginx", "-g", "daemon off;"]
Nginx的配置文件如下所示
# nginx配置文件 # compose/nginx/nginx.conf upstream django { ip_hash; server web:8000; # Docker-compose web服務端口 } server { listen 80; # 監聽80端口 server_name localhost; # 可以是nginx容器所在ip地址或127.0.0.1,不能寫宿主機外網ip地址 charset utf-8; client_max_body_size 10M; # 限制用戶上傳文件大小 location /static { alias /usr/share/nginx/html/static; # 靜態資源路徑 } location /media { alias /usr/share/nginx/html/media; # 媒體資源,用戶上傳文件路徑 } location / { include /etc/nginx/uwsgi_params; uwsgi_pass django; uwsgi_read_timeout 600; uwsgi_connect_timeout 600; uwsgi_send_timeout 600; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; # proxy_pass http://django; # 使用uwsgi通信,而不是http,所以不使用proxy_pass。 } } access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn; server_tokens off;
第四步:編寫Db (MySQL)容器配置文件
啓動MySQL容器我們直接使用官方鏡像即可,不過我們需要給MySQL增加配置文件。
# compose/mysql/conf/my.cnf [mysqld] user=mysql default-storage-engine=INNODB character-set-server=utf8 port = 3306 # 端口與docker-compose裏映射端口保持一致 #bind-address= localhost #一定要註釋掉,mysql所在容器和django所在容器不同IP basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock skip-name-resolve # 這個參數是禁止域名解析的,遠程訪問推薦開啓skip_name_resolve。 [client] port = 3306 default-character-set=utf8 [mysql] no-auto-rehash default-character-set=utf8
我們還需設置MySQL服務啓動時需要執行的腳本命令, 注意這裏的用戶名和password必需和docker-compose.yml裏與MySQL相關的環境變量保持一致。
# compose/mysql/init/init.sql GRANT ALL PRIVILEGES ON myproject.* TO dbuser@"%" IDENTIFIED BY "password"; FLUSH PRIVILEGES;
第五步:編寫Redis 容器配置文件
啓動redis容器我們直接使用官方鏡像即可,不過我們需要給redis增加配置文件。大部分情況下采用默認配置就好了,這裏我們只做出了如下幾條核心改動:
# compose/redis/redis.conf # Redis 5配置文件下載地址 # https://raw.githubusercontent.com/antirez/redis/5.0/redis.conf # 請註釋掉下面一行,變成#bind 127.0.0.1,這樣其它機器或容器也可訪問 bind 127.0.0.1 # 取消下行註釋,給redis設置登錄密碼。這個密碼django settings.py會用到。 requirepass yourpassword
第六步:修改Django項目settings.py
在你準備好docker-compose.yml並編排好各容器的Dockerfile及配置文件後,請先不要急於使用Docker-compose命令構建鏡像和啓動容器。這時還有一件非常重要的事情要做,那就是修改Django的settings.py, 提供mysql和redis服務的配置信息。最重要的幾項配置如下所示:
# 生產環境設置 Debug = False Debug = False # 設置ALLOWED HOSTS ALLOWED_HOSTS = ['your_server_IP', 'your_domain_name'] # 設置STATIC ROOT 和 STATIC URL STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_URL = "/static/" # 設置MEDIA ROOT 和 MEDIA URL MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = "/media/" # 設置數據庫。這裏用戶名和密碼必需和docker-compose.yml裏mysql環境變量保持一致 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'myproject', # 數據庫名 'USER':'dbuser', # 你設置的用戶名 - 非root用戶 'PASSWORD':'password', # # 換成你自己密碼 'HOST': 'db', # 注意:這裏使用的是db別名,docker會自動解析成ip 'PORT':'3306', # 端口 } } # 設置redis緩存。這裏密碼爲redis.conf裏設置的密碼 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://redis:6379/1", #這裏直接使用redis別名作爲host ip地址 "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "PASSWORD": "yourpassword", # 換成你自己密碼 }, } }
第七步:使用docker-compose 構建鏡像並啓動容器組服務
現在我們可以使用docker-compose命名構建鏡像並啓動容器組了。
# 進入docker-compose.yml所在文件夾,輸入以下命令構建鏡像 sudo docker-compose build # 查看已生成的鏡像 sudo docker images # 啓動容器組服務 sudo docker-compose up # 查看運行中的容器 sudo docker ps
如果一切順利,此時你應該可以看到四個容器都已經成功運行了。
第八步:進入web容器內執行Django命令並啓動uwsgi服務器
雖然我們四個容器都已啓動運行,但我們還沒有執行Django相關命令並啓動uwsgi服務器。現在我們只需進入web容器執行我們先前編寫的腳本文件start.sh
即可。
sudo docker exec -it myprojectdocker_web_1 /bin/bash start.sh
此時打開你的瀏覽器,輸入你服務器的ip地址或域名指向地址,你就應該可以看到網站已經上線啦。
小結
本文詳細地介紹瞭如何使用docker-compose工具分八步在生成環境下部署Django + Uwsgi + Nginx + MySQL + Redis。過程看似很複雜,但很多Dockerfile,項目佈局及docker-compose.yml都是可以複用的。花時間學習並練習本章內容是非常值得的,一但你學會了,基本上可以10分鐘內完成一個正式Django項目的部署,而且可以保證在任何一臺Linux機器上順利地運行。
注意:整個部署過程如果遇到問題,可以給我們留言或發信息,可以節省你的時間。
喜歡我們的文章,獲取更多幹貨內容,歡迎關注【Python Web與Django開發】並加星標哦。感謝大家的支持!
大江狗
2020.5.25