iT邦幫忙

2022 iThome 鐵人賽

DAY 30
1

Day30 改造完成,雲端部署 Django 多租戶架構電商網站

終於來到了最後一天,今天要來將我們的多租戶架構網站進行雲端部署,馬上就開始吧!

在 Freenom 註冊免費網域

網域是多租戶網站不可或缺的一部分,Django 多租戶架構會透過子網域名稱進行租戶的區分。

前往 freenom 。

https://www.freenom.com/zu/index.html?lang=zu

https://ithelp.ithome.com.tw/upload/images/20221012/20151656f7qFGBXyIv.png

輸入想要建立的網域名稱(若無法通過判斷,事先加上後面兩碼即可通過)

https://ithelp.ithome.com.tw/upload/images/20221012/20151656iJVEVyuJPQ.png

確認網域

https://ithelp.ithome.com.tw/upload/images/20221012/201516560MG5TL0x3y.png

註冊或登入後送出

https://ithelp.ithome.com.tw/upload/images/20221012/20151656HoebSg5zIf.png

選擇一年免費方案

https://ithelp.ithome.com.tw/upload/images/20221012/20151656ufizgYkf3f.png

建立完成

https://ithelp.ithome.com.tw/upload/images/20221012/20151656H9zT3a9Zdj.png

建立 Nginx 服務

Nginx 是非同步框架的網頁伺服器,也可以用作反向代理、負載平衡器和HTTP快取。我們將使用他來運行我們的多租戶架構網站。

建立 nginx 目錄

cd ~/exmaple_tenant
mkdir nginx

建立自簽憑證,讓 nginx 的 https 可以先正常啟動,這裡可以將 .localhost 改為 freenom 申請的 .domain 。

cd ~/exmaple_tenant
mkdir nginx/certificates
cd nginx/certificates
openssl genrsa -des3 -passout pass:example_tenant -out example_tenant.pass.key 4096
openssl rsa -passin pass:example_tenant -in example_tenant.pass.key -out example_tenant.key
rm example_tenant.pass.key
openssl req -new -key example_tenant.key -out example_tenant.csr \
    -subj "/C=TW/ST=TW/L=Taipei/O=IVANKAO/OU=IT Department/CN=*.localhost"
openssl x509 -req -days 365 -in example_tenant.csr -signkey example_tenant.key -out example_tenant.crt

建立設定檔
這裡的範例使用 conf.d 目錄,有些 nginx 的目錄結構可能放至 sites-available 與 sites-enabled,要根據情況做對應的調整。

cd ~/exmaple_tenant
mkdir nginx/conf.d
cd nginx/conf.d
touch django.conf

django.conf 說明

  • uwsgi 為 python 與 nginx 溝通使用的服務,將會生成一個 sock 檔進行溝通。
  • listen 為監聽的 port,這裡開啟 80 與 443
  • ssl_certificate 為憑證,對照至先前建立的自簽憑證
  • location 進行與 Django 路徑的對應,需要在 docker-compose.yml 設定資料卷
  • server_name 為服務名稱,可以將 localhost 改為 freenom 申請的 domain。
# the upstream component nginx needs to connect to
upstream uwsgi {
    server unix:/opt/app/web/app.sock; # for a file socket
}

limit_req_zone $binary_remote_addr zone=reqlimit:1m rate=500r/s;
# configuration of the server
server {
    uwsgi_read_timeout 1200;
    include  /etc/nginx/mime.types;
    # when a client closes the connection then keep the channel to uwsgi open. Otherwise uwsgi throws an IOError
    uwsgi_ignore_client_abort on;
    add_header Access-Control-Allow-Headers "Authorization";
    add_header 'Referrer-Policy' 'origin';
    # the port your site will be served on
    listen    80;
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;

    # # 憑證與金鑰的路徑
    ssl_certificate /etc/nginx/certificates/example_tenant.crt;
    ssl_certificate_key /etc/nginx/certificates/example_tenant.key;

    # index  index.html;
    # the domain name it will serve for
    # substitute your machine's IP address or FQDN
    server_name  localhost;
    charset     utf-8;

    client_max_body_size 200M;   # adjust to taste

    # Django media
    location /media  {
        alias /opt/app/web/media;  # your Django project's media files - amend as required
    }

    location /static {
        alias /opt/app/web/static; # your Django project's static files - amend as required
    }

    location / {
        uwsgi_pass  uwsgi;
        include     /etc/nginx/uwsgi_params; # the uwsgi_params file you installed
    }

}

建立 uwsgi.ini 設定檔
在 web 目錄底下建立 uwsgi.ini 並寫入以下內容:

[uwsgi]

# http=0.0.0.0:8000
socket=app.sock
chmod-socket=666
master=True
# maximum number of worker processes
processes=4
buffer-size=65535
# Django's wsgi file
module=main.wsgi:application

# clear environment on exit
vacuum=True
py-autoreload = 2

在 docker-compose.yml 建立 nginx service

# docker-compose.yml

# ...

  nginx:
      image: nginx
      container_name: ${IMAGE_NAME}_nginx
      restart: always
      volumes:
          - .:/opt/app
          - ./nginx/conf.d:/etc/nginx/conf.d
          - ./nginx/certificates:/etc/nginx/certificates
      ports:
          - "80:80"
          - "443:443"
      depends_on:
          - web

部署環境設定

在部署前要先做一些設定與調整。

調整 settings.py

將 DEBUG 改為 False,並設定 STATIC_ROOT 與關閉 STATICFILES_DIRS。

# main/settings.py

# ...

DEBUG = False

# ...

STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = '/static/'
# STATICFILES_DIRS = [
#     BASE_DIR / "static",
# ]

收集靜態文件

將所有 static 收集至 ~/example_tenant/static 目錄。

docker exec -it --workdir /opt/app/web example_tenant_web \
    python3.10 manage.py collectstatic

建立 GCP Compute Engine 雲端主機

前往 Google Cloud ,註冊後可以獲得免費試用的額度。

https://cloud.google.com/gcp

https://ithelp.ithome.com.tw/upload/images/20221012/20151656pgKtnJRCrN.png

註冊後須要先輸入付款資訊,此階段並不會扣款。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656fRT2tTKyfC.png

啟用 Compute Engine API,我們將透過它來建立雲端主機。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656aRpI8JfKSA.png

建立執行個體。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656vyRPGCjVsw.png

填寫執行個體名稱與選擇區域,這裡可以選擇台灣。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656uVsAlQtZQC.png

下方防火牆設定允許 HTTP 與 HTTPS 流量。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656XDjaFopAaO.png

建立完成。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656lNdvq3WCdS.png

點選編輯,需要對一些主機設定進行調整。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656SRpcvuHqyj.png

建立 IP 位址,預設並沒有外部 IP,要讓外面的網際網路能連線需要定義外部 IP。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656CUPMTs2D11.png

建立 IP 位址名稱。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656gT0GrcuoNL.png

新增完成後點選儲存按鈕。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656ZEBpjpPzbZ.png

生成一組外部 IP 完成。

設定網域 DNS

進入管理網域頁面

https://ithelp.ithome.com.tw/upload/images/20221012/20151656g89zzU0Mpc.png

設定網域如下,這裡設定了租戶 example01 與 租戶 example02。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656pPqA3jvU8B.png

設定好後先至開發環境透過個租戶的管理介面加上新的 domain。
https://ithelp.ithome.com.tw/upload/images/20221013/20151656MpDg4X75hY.jpg

從開發環境(Ubuntu VM)連線至雲端主機

在開發環境(Ubuntu VM)建立金鑰。

ssh-keygen -t rsa -f ~/.ssh/KEY_FILENAME -C USERNAME -b 2048
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ivankao/.ssh/id_rsa): example_tenant
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in example_tenant

複製金鑰 ssh-rsa …

cat example_tenant.pub
ssh-rsa AAAA...

貼上金鑰至 GCP Compute Engine 的金鑰欄位並儲存。

https://ithelp.ithome.com.tw/upload/images/20221012/20151656jmN6l7iDPj.png

從開發環境(Ubuntu VM)透過金鑰連線至 GCP

ssh -i ~/.ssh/example_tenant USERNAME@雲端主機外部IP

...
# 連線成功
USERNAME@example-tenant:~$

如果要在開 VM 的本機透過 VScode 連線,也可以將私鑰放至本機後連線。

在雲端主機建立專案目錄
之後要同步開發環境(Ubuntu VM)與雲端主機的程式與資料,需要在雲端主機安裝 rsync 套件。

安裝 rsync。

sudo apt-get install rsync

回到開發環境(Ubuntu VM),記得先將服務先停止。

cd ~/example_tenant
docker-compose down -v

接著使用 tar 打包檔案後再透過 rsync 傳送檔案至雲端主機

cd ~
sudo tar -zcvf example_tenant.tar.gz example_tenant

rsync -avzh -e "ssh -i ~/.ssh/example_tenant" ~/.example_tenant.tar.gz USERNAME@雲端主機外部IP:~/

GCP Compute Engine 環境設定

建立專案目錄
在雲端主機將 tar 檔案解開。

USERNAME@example-tenant:~$
cd ~
tar -zxvf example_tenant.tar.gz

安裝 Docker

設定 apt 套件,並允許透過 HTTPS

sudo apt-get update
sudo apt-get install \
  ca-certificates \
  curl \
  gnupg \
  lsb-release

加入 Docker GPG key

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

設置儲存庫

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

安裝 Docker

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

測試是否安裝完成

sudo docker run hello-world

...
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:7d246653d0511db2a6b2e0436cfd0e52ac8c066000264b3ce63331ac66dca625
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

安裝 Docker Compose

docker compose version

...
Docker Compose version v2.6.0

不習慣使用 CLI,使用自訂別名

echo 'alias docker-compose="docker compose"' >> ~/.bashrc
source ~/.bashrc
docker-compose version

...
Docker Compose version v2.6.0

以非 root 身分管理 Docker,在 GCP Compute Engine 環境預設的 UID 不是 1000,稍後需要對設定檔進行調整。

sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
sudo chmod 666 /var/run/docker.sock

重啟 docker 服務

sudo systemctl restart docker

雲端主機的 Docker 的環境架設完成。

運行網站

建立 example_tenant Image

docker build -t example_tenant .

調整 .env ,這裡因為 GCP 預設使用者對照的 UID 是 1001,參數也要進行調整才能正常運行。

# Docker Compose .env
IMAGE_NAME=example_tenant
POSTGRES_DB=db_name
POSTGRES_USER=db_user
POSTGRES_PASSWORD=db_password
WEB_PORT=8000
UID=1001

運行 docker-compose

cd ~/example_tenant
docker-compose up -d

雲端部署大功告成!!

頁面展示

租戶 example01

https://ithelp.ithome.com.tw/upload/images/20221013/20151656ysUFtv90Hv.png

租戶 example02
https://ithelp.ithome.com.tw/upload/images/20221013/20151656SX1DfHDmMs.png

總結

在這三十天我們從認識 Django 與 多租戶架構,開始架設多租戶網站,從環境建置、套版、功能開發、串接金流、雲端部署逐步實作完成,其中在多租戶的情境會遇到的問題也有進行了許多變化與問題排除,最後完成了我們的電商網站。
本系列已經將多租戶網站整體架構與電商網站的基本功能都已經完成,比較細節的文字翻譯或是一些頁面連結礙於篇幅與時間就沒有進行說明與調整,就留給實作的你來自由發揮囉,如果有遇到任何問題都歡迎留言,Github 連結已經放在系列的第一篇,歡迎參考。

完賽感言

一直以來都喜歡隨手寫一些技術文,可惜一直沒有時間好好地寫成系列文章,透過這次的鐵人賽與隊友們一起成功鍊成了,非常辛苦但也非常開心。這三十天就可以想像成每天加班到晚上12點,假日無休!!重複的寫程式整理文章、寫程式整理文章,尤其是最後幾天都在趕 Deadline,一個晚上要開發完功能再快速寫成文章,感覺像是功能即將上線卻發現重大 BUG 的緊張刺激感,只有每天送出文章後才能稍微鬆一口氣。
這些天雖然寫得很苦但不後悔,謝謝這次的鐵人賽與一起參加隊友,當然還有願意看到這裡的你,期待大家下次再見!


上一篇
Day 29 大花錢,Django 訂單串接金流
系列文
全能住宅改造王,Django 多租戶架構的應用 —— 實作一個電商網站30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言