Docker Compose 部署先後端分離應用

部署先後端分離應用

容器化 Abp 應用

關於 Abp 應用的容器化,其實和普通的 ASP.NET Core 應用差很少,你們能夠參考我此前的文章javascript

惟一須要注意的是:由於 Abp 解決方案中有多個項目,在 publish 過程當中須要手動指定啓動項目,例如:css

# 其他內容請參考上述文章
# 修改 RUN dotnet publish -c Release -o /app 爲如下內容
RUN dotnet publish ./src/YourProjectName.Web.Host/YourProjectName.Web.Host.csproj -c Release -o /app

使用 sql 文件應用遷移

在使用 EF Core 進行 Code First 開發的時候,咱們每每都習慣在 VS 的控制檯中使用Update-Database完成遷移。但在實際部署過程當中,考慮開發環境可能沒法直接與部署所用主機相連,咱們能夠經過導出 sql 文件的形式來完成遷移。html

在解決方案的根目錄下打開命令行:前端

dotnet ef migrations script -p .\src\YourProjectName.EntityFrameworkCore\ -o .\init.sql

而後,將該 init.sql 文件掛載到 mysql 的鏡像的初始化目錄當中:java

version: "3"

services:
  mysql:
    # ...
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

這樣,就會在 mysql 容器啓動時,自動完成遷移。mysql

使用外部網絡橋接數據庫

在咱們的 API 應用中,會在啓動階段檢測數據庫中是否正確配置了基礎信息,這就帶來一個棘手的問題:當所用數據庫好比說 MySQL,也是經過同一個 Docker Compose 部署的時候,會有啓動延遲,從而使得數據庫還將來得及應用遷移(Apply Migrations),即使在配置文件中配置了depends_on也沒法避免。nginx

depends_on隻影響容器啓動的順序,而此處 MySQL 的啓動延遲是在容器啓動後發生的。git

在此前,我一直是經過腳本或者額外的代碼來爲 API 應用增添啓動延遲重試的功能,但顯然這對生產環境來講並不合適。web

其實,咱們能夠來分析一下幾個要求:sql

  1. MySQL 必須在 API 應用啓動前,完成初始化或遷移。
  2. 不該爲此給 API 應用引入額外的邏輯。
  3. API 應用的更新不該該影響數據庫。

因此,最終仍是決定將數據庫獨立出來,單獨用一個 Docker Compose 來進行部署,可這也帶來一個新的問題:API 應用沒法肯定數據庫所在的子網 IP,或者說二者甚至不在同一子網中。

那麼,天然得想辦法將數據庫從新加入 API 應用所在的子網,經過查找資料,Docker Compose 已經爲咱們提供了這一功能,即便用外部網絡。

這裏又能夠多說一句,在咱們使用 Docker Compose 部署的時候,其默認會爲咱們建立一個子網,並將咱們的定義的服務添加到該子網中。而這個網絡是直接由 Docker Compose 管理的,在up時建立,在down時銷燬,所以不符合咱們須要將多個 Docker Compose 定義的服務加入同一子網的要求。

手動建立一個虛擬子網:

docker network create xxx

在配置文件中定義該網絡,並將服務分別添加到該網絡:

# db.yml
version: "3"

services:
  mysql:
    # ...
    networks:
      - xxx

networks:
  # 定義該網絡爲外部網絡
  xxx:
    external: true

# 同理
# docker-compose.yml
version: "3"

services:
  api:
    # ...
  networks:
    - xxx

networks:
  xxx:
    external: true

而後,在部署的時候,咱們只須要在第一次部署時,先等等 MySQL 已經完成初始化,再啓動 API 應用便可。且對此後的 CI/CD 過程,咱們也只須要關注 API 應用鏡像的更新(須要修改數據庫表結構除外)。

使用 Nginx 部署 SPA 應用

咱們的前端都是以 SPA 應用來構建的,也就是說只需發佈靜態文件便可。這裏咱們就使用 Nginx 做爲 Web 服務器。

值得注意的也就如下三點:

  1. 掛載 Web 根目錄和配置文件
  2. 開啓反向代理
  3. 開啓僞靜態

掛載目錄和文件已是老生常談了,這裏就再也不贅述,在 docker-compose.yml 中配置如下兩行便可:

version: "3"
services:
  web:
    # ...
    volumes:
      - ./html:/usr/share/nginx/html
      - ./nginx.conf:/etc/nginx/conf.d/default.conf

接下來是開啓反向代理,這裏咱們所請求的 API 都是以/api爲前綴的相對路徑,所以只須要在 nginx.conf 中配置:

server {
  # ...
  location /api {
    proxy_pass http://api;
    proxy_set_header   X-Forwarded-Proto $scheme;
    proxy_set_header   Host              $http_host;
    proxy_set_header   X-Real-IP         $remote_addr;
  }
}

可能細心的人有發現這裏將請求代理給了 api 這個域名,其實也就是咱們所定義的 api 服務。那爲了讓這種寫法有效,天然別忘了將 web 服務和 api 服務添加到同一子網,能夠是以前的外部子網,或者再新建一個內部子網也行。

配置到這裏,其實已是能夠用了,但在你刷新頁面的時候不時會出現 404 錯誤。這是由於 SPA 應用的路由並不對應真正文件的路徑,咱們須要將對應的請求指向咱們真正的文件,也就是僞靜態。

例如,你的應用發佈在 Web 根目錄下,其主頁面名爲 index.html,能夠以下配置:

server {
  # ...
  location / {
    try_files $uri $uri/ /index.html;
  }
}

結語

最終,你全部的配置文件應該以下:

docker-compose.yml

version: "3"

services:
  api:
    container_name: xxx_api
    image: xxx:api
    ports:
      - "21021:80"
    volumes:
      # 這裏映射日誌目錄
      - ./App_Data:/app/App_Data
    environment:
      - ConnectionStrings:Default=server=mysql;userid=root;pwd=xxx;port=3306;database=xxx;Charset=utf-8;
      # 跨域控制
      - App:ServerRootAddress=http://xxx.xxx:21021
      - App:ClientRootAddress=http://xxx.xxx:8000
      - App:CorsOrigins=http://xxx.xxx:8000,http://xxx.xxx:21021
    networks:
      - xxx
  web:
    container_name: xxx_web
    image: nginx
    ports:
      - "8000:80"
    volumes:
      - ./html:/usr/share/nginx/html
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    networks:
      - xxx

networks:
  xxx:
    external: true

db.yml

version: "3"

services:
  mysql:
    container_name: xxx_mysql
    image: mysql:8.0
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=xxx
      - MYSQL_DATABASE=xxx
    volumes:
      - ./mysql:/var/lib/mysql
      - ./charset.cnf:/etc/mysql/conf.d/charset.cnf
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - xxx

networks:
  xxx:
    external: true

nginx.conf

server {
    listen 80;
    # gzip config
    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 9;
    gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
    gzip_vary on;
    gzip_disable "MSIE [1-6]\.";

    root /usr/share/nginx/html;

    location / {
        try_files $uri $uri/ /index.html;
    }
    location /api {
        proxy_pass http://api;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   Host              $http_host;
        proxy_set_header   X-Real-IP         $remote_addr;
    }
}
相關文章
相關標籤/搜索