多容器應用程式

到目前為止,你一直在處理單容器應用程式。現在,你將要把 MySQL 加入到應用程式堆疊中。這時通常會產生一個疑問:「MySQL 要在哪裡執行?是把它安裝在同一個容器中,還是分開執行?」一般而言,每個容器應該只做一件事,並且把它做好。以下是將容器分開執行的幾個原因:

  • 很有可能你需要對 API 和前端進行不同於資料庫的擴展(Scale)。
  • 獨立的容器讓你能夠隔離地進行版本控制和版本更新。
  • 雖然你在本地端開發時可能會使用容器來執行資料庫,但在生產環境中,你可能更傾向於使用受管資料庫服務。那樣一來,你就不需要把資料庫引擎與應用程式打包在一起部署。
  • 執行多個處理序(Process)需要一個處理序管理器(因為容器預設只啟動一個處理序),這會增加容器啟動和關閉的複雜度。

除此之外還有更多理由。因此,如同下方的圖示,將應用程式拆分為多個容器執行是最好的做法。

Todo App connected to MySQL container

容器網路

請記住,容器預設是隔離的,它們對同一台機器上的其他處理序或容器一無所知。那麼,該如何讓一個容器與另一個容器通訊呢?答案就是網路(Networking)。只要將兩個容器放置在同一個網路中,它們就能夠互相通訊。

啟動 MySQL

將容器加入網路有兩種方式:

  • 在啟動容器時指定網路。
  • 將已經在執行的容器連接到網路。

在接下來的步驟中,你將會先建立一個網路,然後在啟動時將 MySQL 容器附加到該網路。

  1. 建立網路。

    $ docker network create todo-app
    
  2. 啟動一個 MySQL 容器並將其附加到網路。你還需要定義一些資料庫用來初始化的環境變數。若要深入了解 MySQL 環境變數,請參閱 MySQL Docker Hub 頁面中的「環境變數 (Environment Variables)」章節。

    $ docker run -d \
        --network todo-app --network-alias mysql \
        -v todo-mysql-data:/var/lib/mysql \
        -e MYSQL_ROOT_PASSWORD=secret \
        -e MYSQL_DATABASE=todos \
        mysql:8.0
    
    $ docker run -d `
        --network todo-app --network-alias mysql `
        -v todo-mysql-data:/var/lib/mysql `
        -e MYSQL_ROOT_PASSWORD=secret `
        -e MYSQL_DATABASE=todos `
        mysql:8.0
    $ docker run -d ^
        --network todo-app --network-alias mysql ^
        -v todo-mysql-data:/var/lib/mysql ^
        -e MYSQL_ROOT_PASSWORD=secret ^
        -e MYSQL_DATABASE=todos ^
        mysql:8.0
    

    在先前的指令中,你可以看到 --network-alias 旗標。在後面的章節中,你將會更深入了解此旗標的用途。

    提示

    你會注意到上述指令中出現了一個名為 todo-mysql-data 的磁碟區(Volume),它被掛載到 /var/lib/mysql,這是 MySQL 儲存資料的地方。然而,你並沒有事先執行 docker volume create 指令。Docker 會自動識別到你想使用具名磁碟區,並為你自動建立。

  3. 若要確認資料庫已成功啟動並執行,請嘗試連接資料庫以驗證是否能順利連線。

    $ docker exec -it <mysql-container-id> mysql -u root -p
    

    當出現密碼提示時,輸入 secret。在 MySQL Shell 中,列出所有資料庫並確認你能看到 todos 資料庫。

    mysql> SHOW DATABASES;
    

    你應該會看到類似以下的輸出:

    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    | todos              |
    +--------------------+
    5 rows in set (0.00 sec)
  4. 退出 MySQL Shell 以返回你機器上的 Shell。

    mysql> exit
    

    現在你已經有了 todos 資料庫,且已準備就緒供你使用。

連接至 MySQL

既然你知道 MySQL 已經順利執行,接下來就可以開始使用它了。但該怎麼連線呢?如果你在同一個網路中執行另一個容器,該如何找到該容器?請記住,每個容器都有屬於自己的 IP 位址。

為了回答上述問題並更好地理解容器網路,你將會使用 nicolaka/netshoot 容器,它內建了許多對網路問題進行故障排除或除錯時非常實用的工具。

  1. 使用 nicolaka/netshoot 映像檔啟動一個新容器。請確保將其連接到同一個網路。

    $ docker run -it --network todo-app nicolaka/netshoot
    
  2. 在容器內部,你將使用 dig 指令(這是一個很有用的 DNS 工具)。你將搜尋主機名稱 mysql 的 IP 位址。

    $ dig mysql
    

    你應該會得到如下所示的輸出。

    ; <<>> DiG 9.18.8 <<>> mysql
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;mysql.				IN	A
    
    ;; ANSWER SECTION:
    mysql.			600	IN	A	172.23.0.2
    
    ;; Query time: 0 msec
    ;; SERVER: 127.0.0.11#53(127.0.0.11)
    ;; WHEN: Tue Oct 01 23:47:24 UTC 2019
    ;; MSG SIZE  rcvd: 44

    在「ANSWER SECTION」中,你會看到一筆關於 mysqlA 記錄,它指向 172.23.0.2(你的 IP 位址可能會有不同)。雖然 mysql 通常不是一個有效的主機名稱,但 Docker 成功地將其解析為擁有該網路別名 (network alias) 的容器 IP 位址。記得嗎?你稍早使用了 --network-alias 指令。

    這意味著你的應用程式只需要簡單地連接到名為 mysql 的主機,它就能夠與資料庫通訊。

使用 MySQL 執行你的應用程式

Todo 應用程式支援設定幾個環境變數來指定 MySQL 的連線設定,它們分別是:

  • MYSQL_HOST - 正在執行的 MySQL 伺服器主機名稱
  • MYSQL_USER - 連線使用的使用者名稱
  • MYSQL_PASSWORD - 連線使用的密碼
  • MYSQL_DB - 連線後使用的資料庫名稱
注意

雖然在開發階段使用環境變數來設定連線參數是被普遍接受的,但在生產環境中執行應用程式時,強烈不建議這麼做。前 Docker 安全主管 Diogo Monica 曾撰寫了一篇精彩的部落格文章解釋原因。

更安全的做法是使用容器編排框架提供的祕密 (Secret) 支援功能。在大多數情況下,這些祕密會被掛載為執行中容器內的檔案。你會發現許多應用程式(包括 MySQL 映像檔和 Todo 應用程式)也支援以 _FILE 為結尾的環境變數,用來指向包含該變數內容的檔案。

例如,設定 MYSQL_PASSWORD_FILE 變數會使應用程式將所指向檔案的內容作為連線密碼。Docker 本身並不支援這些特定的環境變數,而是你的應用程式需要具備讀取該變數並取得檔案內容的能力。

現在你可以啟動準備好開發環境的容器了。

  1. 指定上述的每一個環境變數,並將容器連接到你的應用程式網路。請確保在執行此指令時,你位於 getting-started-app 目錄中。

    $ docker run -dp 127.0.0.1:3000:3000 \
      -w /app -v "$(pwd):/app" \
      --network todo-app \
      -e MYSQL_HOST=mysql \
      -e MYSQL_USER=root \
      -e MYSQL_PASSWORD=secret \
      -e MYSQL_DB=todos \
      node:18-alpine \
      sh -c "yarn install && yarn run dev"
    

    在 Windows 上,請在 PowerShell 中執行此指令。

    $ docker run -dp 127.0.0.1:3000:3000 `
      -w /app -v "$(pwd):/app" `
      --network todo-app `
      -e MYSQL_HOST=mysql `
      -e MYSQL_USER=root `
      -e MYSQL_PASSWORD=secret `
      -e MYSQL_DB=todos `
      node:18-alpine `
      sh -c "yarn install && yarn run dev"

    在 Windows 上,請在命令提示字元中執行此指令。

    $ docker run -dp 127.0.0.1:3000:3000 ^
      -w /app -v "%cd%:/app" ^
      --network todo-app ^
      -e MYSQL_HOST=mysql ^
      -e MYSQL_USER=root ^
      -e MYSQL_PASSWORD=secret ^
      -e MYSQL_DB=todos ^
      node:18-alpine ^
      sh -c "yarn install && yarn run dev"
    
    $ docker run -dp 127.0.0.1:3000:3000 \
      -w //app -v "/$(pwd):/app" \
      --network todo-app \
      -e MYSQL_HOST=mysql \
      -e MYSQL_USER=root \
      -e MYSQL_PASSWORD=secret \
      -e MYSQL_DB=todos \
      node:18-alpine \
      sh -c "yarn install && yarn run dev"
    
  2. 如果你查看容器的日誌 (docker logs -f <container-id>),你應該會看到類似以下的訊息,表示它正在使用 mysql 資料庫。

    $ nodemon src/index.js
    [nodemon] 2.0.20
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching dir(s): *.*
    [nodemon] starting `node src/index.js`
    Connected to mysql db at host mysql
    Listening on port 3000
    
  3. 在瀏覽器中開啟應用程式,並在待辦清單中新增幾項內容。

  4. 連接至 mysql 資料庫,驗證這些項目是否已寫入資料庫。請記住,密碼是 secret

    $ docker exec -it <mysql-container-id> mysql -p todos
    

    在 mysql shell 中,執行以下指令:

    mysql> select * from todo_items;
    +--------------------------------------+--------------------+-----------+
    | id                                   | name               | completed |
    +--------------------------------------+--------------------+-----------+
    | c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! |         0 |
    | 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome!        |         0 |
    +--------------------------------------+--------------------+-----------+
    

    你的資料表內容會因為輸入的項目而有所不同,但你應該能看到它們儲存在那裡。

總結

至此,你已經擁有一個將資料儲存在外部資料庫(執行於獨立容器中)的應用程式。你也學到了一些關於容器網路和使用 DNS 進行服務探索 (Service Discovery) 的概念。

相關資訊

下一步

你可能開始覺得啟動此應用程式所需的一切步驟有點令人不知所措。你需要建立網路、啟動容器、指定所有環境變數、曝露通訊埠等等。這有很多內容需要記憶,而且顯然增加了將專案交接給他人的難度。

在下一節中,你將學習 Docker Compose。有了 Docker Compose,你可以更輕鬆地分享你的應用程式堆疊,並讓其他人透過一個簡單的指令就能將其啟動。使用 Docker Compose

© . This site is unofficial and not affiliated with Kubernetes or Docker Inc.