# 文章說明

這是將服務部署到雲端的專案。
VM 建立好後,將專案 pull 到 /opt 下即可快速部署。

# 環境

每個服務程式碼由 GitHub 託管,並設置自動打包成 Docker image。
機器安裝 Docker,部署時 pull Docker image 啟動多個服務 container。
由 Nginx 控制請求進入服務。

# 專案資料夾結構

資料夾結構
.
├── init.sh                # 機器初始化
├── prod
│   ├── docker-compose.yml # 正式站 Docker Swarm Stack 設定
│   └── nginx.conf         # 正式站 Nginx 設定
├── start-prod.sh          # 啟動
└── stop-prod.sh           # 停止

首先執行 init.sh 更新系統、安裝常用程式,參考 GCP VM 建置筆記

# start-prod.sh

start-prod.sh
#!/bin/sh
# 啟動正式站 container
docker stack deploy --compose-file prod/docker-compose.yml --with-registry-auth company

這邊沒什麼好介紹的,就是用 docker stack deploy 啟動 Docker Swarm Stack。

--with-registry-auth 會把本機 docker login 的 registry 認證一併傳給 Swarm 節點,
讓節點在拉私有 image 時能順利通過授權;不加的話,私有倉庫常會出現拉不到 image 的錯誤。

# docker-compose.yml

prod/docker-compose.yml
# 正式站 container
version: '3.8'
services:
  nginx:
    image: nginx
    restart: unless-stopped
    volumes:
      # Nginx 設定檔
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    environment:
      TZ: Asia/Taipei
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
  # 後端 API
  backend:
    image: ghcr.io/8loser/backend:main
    restart: unless-stopped
    environment:
      TZ: Asia/Taipei
  # 前端
  frontend:
    image: ghcr.io/8loser/frontend:main
    restart: unless-stopped
    environment:
      TZ: Asia/Taipei

說明:

  • 有 3 個服務;Nginx、frontend、backend。
  • Nginx 不用 depends_on backend, frontend 避免某個服務異常導致 Nginx 被牽連停止。
  • Nginx mode: host 直接用主機 80 對外提供服務,Load Balancer 流量導入 Nginx 80 port,僅有跑該服務的節點會開 80。
  • Nginx 設定檔直接掛載 prod/nginx.conf 檔案。
  • Stack 會使用 Swarm overlay network, proxy_pass http://frontend/proxy_pass http://backend/ 會解析到同一個 Stack 的 service。
  • ghcr.io 是 GitHub Container Registry,用於存放 Docker image。

# nginx.conf

prod/nginx.conf
# 正式站 Nginx
# 預設 server; 使用 IP 或未匹配的域名都會進入
server {
  listen 80;
  server_name _;
  # 不合法 Host 訪問 /,不返回內容
  location / {
      return 204;
  }
}
server {
  listen 80;
  # 允許的域名
  server_name www.*******.com *******.com;
  # Load Balancer Public IP address
  set_real_ip_from <LB_IP>/32; # 填入你的 LB IP
  # Google CIDR 網段
  set_real_ip_from 130.211.0.0/22;
  set_real_ip_from 35.191.0.0/16;
  real_ip_header X-Forwarded-For;
  real_ip_recursive on;
  # 阻擋的 User Agent
  if ($http_user_agent ~* "ApacheBench|pingback|YisouSpider|MJ12bot|AhrefsBot|360JK|Jorgee|FeedDemon|BOT/0.1 (BOT for JCE)|CrawlDaddy|Java|Jullo|Feedly|UniversalFeedParser|Swiftbot|YandexBot|jikeSpider|ZmEu phpmyadmin|WinHttp|EasouSpider|HttpClient|Microsoft URL Control|YYSpider|jaunty|oBot|Python-urllib|Indy Library|FlightDeckReports Bot|Linguee Bot" )
  {return 101;}
  # 正式站設定 robots.txt 允許爬蟲
  location = /robots.txt {
    add_header Content-Type text/plain;
    return 200 "User-agent: *\nAllow: /\n";
  }
  # 前端
  location / {
    proxy_pass http://frontend/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Custom-Referrer smc_identity_layer;
  }
  
  # 後端
  location /api/ {
    proxy_pass http://backend/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Custom-Referrer smc_identity_layer;
  }
}

說明:

  • 預設 server 用於攔截未匹配的 Host,直接回 204。
  • / 走 frontend、 /api/ 走 backend,使用 proxy_pass 轉發請求。
  • set_real_ip_from 用來限定可信任的轉發來源(LB 與 Google CIDR),才能正確還原 client IP,避免被偽造的 X-Forwarded-For 影響。
  • proxy_pass http://backend/backend 對應 docker-compose.yml 的 service 名稱。