跳至主要内容

3.3 Docker 的 DNS

這邊先科普一下 DNS 本身是什麼意思,全名:網域名稱系統( 英語:Domain Name System,縮寫:DNS ),白話來說就是網際網路世界的電話簿,那為什麼我們會需要電話簿呢?

這邊先假設大家都知道網際網路的互動是透過 IP 位置來找到對方並進行訊息的傳送 ( 想像是打電話給您的親朋好友,必須要知道手機號碼才有辦法聯繫 )。 舉個簡單的例子,在網址中輸入 https://142.251.42.238 會前往 google.com,但平常真的會輸入一大段 IP 位置嗎?也太難記了吧!

而 DNS 就是輸入 google.com 時會幫您找到 https://142.251.42.238 的應用程式,可以想像 IP 位置就是真實的地址,而 DNS 則是一個別名的概念,就像大家常常會提到北車 ( 台北車站 ),但你真的知道台北車站的地址嗎?或是你真的需要知道地址才可以到達台北車站嗎?

Docker 中的 DNS

在 Docker 的世界,我們可以忘掉 IP 位置這件事情,就如同前面章節有提過的,容器的 IP 位置會由 Docker 分配,每次容器啟動的時機不同,快一秒或慢一秒就會導致每個容器的 IP 位置不相同,所以若是要透過 IP 位置來讓容器們彼此溝通顯然是不切實際的事情。

這時候剛剛提到的 DNS 就是 Docker 提出的解決方案,要如何可以一直連線到某一個服務而不論其啟動順序呢?

答案就在一開始我們就學會的 --name 這個指令,當我們替容器命名後,在 Docker 的虛擬網路中,我們為他的命名就會是這個服務的 DNS,我們可以透過容器的名字輕易的訪問到容器本身。

如果使用 IP 位置來溝通

這裡示範用 IP 位置來連接到另外一個容器,是錯誤的示範,來看看如果容器啟動的順序不同會造成什麼樣的問題吧!

首先我們透過下方指令清空所有的容器。

$ docker container rm --force $(docker container ls --all --quiet)

把接下來啟動的容器都連接到剛剛建立的 app 虛擬網路內。

$ docker container run --publish 3000:3000 --detach --name whoami --network app robeeerto/whoami # 不換行

接著打開瀏覽器輸入 http://127.0.0.1:3000,應該會看到畫面顯示著:

容器名稱:eb356e256481 # 不會和我一樣
容器的 IP 位置:172.26.0.2 # 不會和我一樣
環境變數 AUTHOR 是:robertchang # 這邊會一樣

這邊的容器名稱就是我們剛剛啟動容器的 ID,而 IP 位置則是前面介紹過 Docker 分配的 IP 位置,至於第三個環境變數,則等到 Docker 映像檔篇 才會進行解說,這邊可以暫時不理會,接著我們在啟動第二個相同的容器吧!

$ docker container run --publish 3001:3000 --detach --name whoami-2 --network app robeeerto/whoami # 不換行

接著打開瀏覽器輸入 http://127.0.0.1:3001,應該會看到畫面顯示著:

容器名稱:c531f601ebf8 # 不會和我一樣
容器的 IP 位置:172.26.0.3 # 不會和我一樣
環境變數 AUTHOR 是:robertchang # 這邊會一樣

現在 app 的虛擬網路中有著 whoami 以及 whoami-2 兩個容器,接著我們試著用 IP 位置的方式來進行溝通吧!

$ docker container exec --interactive --tty whoami sh <- 進入第一個容器
/ # curl 172.26.0.3:3000
容器名稱:c531f601ebf8<br>容器的 IP 位置:172.26.0.3<br>環境變數 AUTHOR 是:robertchang

成功的透過 curl 這項工具和 whoami-2 這個容器進行溝通了。

乍看之下沒有問題,但若是容器的啟動順序不同呢?

如果把剛剛的流程走過一次但在中間穿插一個不一樣的服務會發生什麼事呢?

$ docker container rm --force $(docker container ls --all --quiet)
# 清空所有容器

$ docker container run --publish 3000:3000 --detach --name whoami --network app robeeerto/whoami # 不換行

$ docker container run --detach --name pg --env POSTGRES_PASSWORD=mysecretpassword --network app postgres # 不換行

$ docker container run --publish 3001:3000 --detach --name whoami-2 --network app robeeerto/whoami # 不換行

現在 app 虛擬網路中有了 whoamipgwhoami-2 共三個容器,且啟動的順序是在兩個 whoami 的容器中間穿插了 pg,讓我們繼續用 IP 位置來嘗試連線 whoami-2 這個容器吧!

$ docker container exec --interactive --tty whoami sh <- 進入第一個容器
/ # curl 172.26.0.3:3000
curl: (7) Failed to connect to 172.26.0.3 port 3000 after 1 ms: Connection refused

依照相同的 IP 位置去做連線就發生上述的狀況,因為啟動順序的調整,目前 172.26.0.3 這個 IP 位置被 pg 這個容器給拿走了。

這在之後的 Docker Compose 篇 更是大忌,畢竟容器啟動的順序在少量的情況下還可以手動控制,但若是幾百個容器時,完全沒辦法透過 IP 位置溝通。

透過容器名稱來溝通

延續著上一個小節的情境,若是改用容器名稱來取代 IP 位置,就能夠確保不因為服務的啟動順序而造成連線的問題。

/ # curl whoami-2:3000 <- 還是在第一個容器內
容器名稱:6c95c42a6ece<br>容器的 IP 位置:172.26.0.4<br>環境變數 AUTHOR 是:robertchang

如上面的示範,我們又成功的和 whoami-2 這個容器進行溝通了。

為什麼是 3000 port 呢?

不知道眼尖的你有沒有發現,在剛剛用 IP 位置溝通的範例中,為什麼我們在第一個容器內是去 curl 172.26.0.3:3000 呢?明明 whoami-2 這個容器是 --publish 3001:3000,也就是打開 3001 的 port 呀!

$ docker container exec --interactive --tty whoami sh
/ # curl 172.26.0.3:3000 <- 為什麼是 3000 port?
容器名稱:c531f601ebf8<br>容器的 IP 位置:172.26.0.3<br>環境變數 AUTHOR 是:robertchang

首先可以回想一下左邊的 port 代表的是什麼意思?

代表的是在這台機器上對應的 port 而不是容器本身打開的 port,以這個例子來說機器上打開 3001,但容器本身還是 3000。

而在相同的虛擬網路中,whoami 這個容器要找到 whoami-2 這個容器並不需要進入網際網路再回到虛擬網路中。

而是直接透過容器本身所在的虛擬網路內的容器名稱 ( DNS ) 加上打開的 port 進行連線即可,透過下方圖片可以更清楚的了解。

相同虛擬網路間容器的溝通

為什麼不用 bridge Network 呢?

如果不指定虛擬網路的話,Docker 不是預設會以 bridge 的虛擬網路為主嗎?那為什麼還要特地自己建立虛擬網路呢?

是因為預設的 bridge 虛擬網路其實沒有內建的 Docker DNS 功能,所以若是使用預設的虛擬網路會發現剛剛用 DNS 連線的範例是連線不到 whoami-2 的。

那這有解決辦法嗎?有的,但就是較舊版本的 --link 指令,讓我們先嘗試用預設的 bridge 虛擬網路訪問 whoami-2 容器吧!

$ docker container rm --force $(docker container ls --all --quiet) # 清空所有容器

$ docker container run --publish 3000:3000 --detach --name whoami robeeerto/whoami # 不換行

$ docker container run --publish 3001:3000 --detach --name whoami-2 robeeerto/whoami # 不換行

$ docker container exec --interactive --tty whoami sh
/ # curl whoami-2:3000
Could not resolve host: whoami-2

現在確定使用 bridge 虛擬網路並以 DNS 連線的方式是沒辦法找到 whoami-2 這個容器的,讓我們先移除掉這兩個容器,並且用 --link 的方式嘗試容器的溝通。

$ docker container rm --force $(docker container ls --all --quiet) # 清空所有容器

$ docker container run --publish 3000:3000 --detach --name whoami robeeerto/whoami # 不換行

$ docker container run --publish 3001:3000 --detach --name whoami-2 --link whoami robeeerto/whoami # 不換行

$ docker container exec --interactive --tty whoami-2 sh
/ # curl whoami:3000
容器名稱:da1c3d74fa2a<br>容器的 IP 位置:172.26.0.2<br>環境變數 AUTHOR 是:robertchang

這邊進入的是 whoami-2 的容器內部,在運行時加入了 --link 的指令,所以可以知道這是一個單向的指令,若我們進入的是 whoami 這個容器,依舊還是沒辦法透過 DNS 的方式連線到 whoami-2

這邊只是說明一下這個特別的使用方式,但其實 --link 這個指令已經是舊時代的產物了,更好的做法就是透過建立虛擬網路,並且讓容器連接上同一個虛擬網路。

Round-robin DNS 練習

每一個章節都會提供一些小小的練習來熟悉本章的內容,通常會比較難一點;若是想不出來也不需要灰心,有些時候可能是指令不熟悉導致,可以往前翻閱來加強記憶喔,亦或是翻到後面的解答章節也可以幫助你解惑喔,記得 --help 以及 docs.docker.com 會是你最好的夥伴。

這個章節將會練習利用剛剛學到的 Docker DNS 知識來使用 Round-robin DNS 這項技術,也可能是你可能聽過的負載平衡或是 Load Balancing。

簡單地來說,Round-robin DNS 的工作模式就是不單純用一個 IP 位置來做回應,而是用一個 IP 位置的列表來做回應,相同的服務將會被註冊在同一個列表上,每次請求都會以不同的伺服器端做回應,避免單獨一個伺服器負載過大導致系統當機的問題。

簡而言之,就像是你伸手進去一籃雞蛋,你每次都會拿到不同顆的雞蛋,但是拿到的永遠都會是雞蛋。

而 Docker 也能做到眾多相同服務的容器共用一個 DNS 的名稱,並且予以回應,這也是這個練習的重點。

  1. 手動建立一個虛擬網路
  2. 上網查詢一下關於 --network-alias 這個指令的作用
  3. 建立兩個 robeeerto/whoami 的容器綁定到剛剛建立的虛擬網路,並且加入 --network-alias whoami 這個指令,讓這兩個容器共用 whoami 這個別名,這邊不用特地打開 port,是因為你並沒有要讓服務暴露到網際網路中。
  4. 使用 centos:centos7 中內建的 curl 套件,並執行 curl -s whoami:3000 指令去請求 whoami 這個 DNS,多嘗試幾次,你會發現內容有所不同

Round-robin DNS 練習解答

  1. 手動建立一個虛擬網路
$ docker network create practice
1ff4a785b4d09b2ec644bfe76fd4
  1. 上網查詢一下關於 --network-alias 這個指令的作用

該指令的作用就是給予不同的容器相同的別名,來做到負載平衡,這樣子使用 alias 的 DNS 在請求時,就會平均分配到有被註冊進這個名單的容器。

  1. 建立兩個 robeeerto/whoami 的容器綁定到剛剛建立的虛擬網路,並且加入 --network-alias whoami 這個指令,讓這兩個容器共用 whoami 這個別名,這邊不用特地打開 port,是因為你並沒有要讓服務暴露到網際網路中。
$ docker container run --detach --network practice --network-alias whoami robeeerto/whoami # 不換行
4c3ca560e1cd85b3167657586207ff9...

$ docker container run --detach --network practice --network-alias whoami robeeerto/whoami # 不換行
16c4bbbb5fa848c06264f0a6c5afaefb....

這邊執行兩次相同的指令,會發現我們並沒有給予容器個別的名字,反倒是用了 --network-alias 這個指令,至於不需要打開 port 的原因,除了不讓服務暴露在網際網路中之外,是因為映像檔本身的設計,就會讓容器打開自己的 port,而在虛擬網路內部,其他容器是可以直接透過目標容器自己打開的 port 訪問內部的。

  1. 使用 centos:centos7 中內建的 curl 套件,並執行 curl -s whoami:3000 指令去請求 whoami 這個 DNS,多嘗試幾次,你會發現內容有所不同
$ docker container run --rm --network practice centos:centos7 curl -s whoami:3000 # 不換行
容器名稱:16c4bbbb5fa8<br>容器的 IP 位置:172.27.0.3

$ docker container run --rm --network practice centos:centos7 curl -s whoami:3000 # 不換行
容器名稱:4c3ca560e1cd<br>容器的 IP 位置:172.27.0.2

可以看到訪問相同的 DNS,但回應的卻是不同的容器,這就是基礎的負載平衡。