使用 Port forwarding 開放服務

今天將會使用 Docker 啟動 HTTP server,並讓瀏覽器能看得到 HTTP server 所提供的 hello world。

常見的 HTTP server 主要有兩種:NginxApache,今天會以 Apache HTTP server(以下簡稱 Apache)為例。

Container 管理小技巧

首先,先使用 docker run 跑 Apache container。

# Apache 的 image 名稱叫 httpd
docker run --name web httpd

# 停止 Apache 後,移除已停止的 container
docker ps -a
docker rm web

Apache 執行時,會停在前景等待收 request。若要中止它,可以按 Ctrl + C 快捷鍵強制中離。從訊息可以觀察到,Ctrl + Cdocker stop 一樣是發出 SIGTERM 信號給 process。狀態已經停止了,但 container 還存在,如果不需要的話得手動移除。

通常練習或測試的 container 都不需要保留,每次手動移除也很麻煩,這時可以使用參數 --rm,範例如下:

# 加帶 --rm 參數
docker run --rm --name web httpd

# 檢查是否移除
docker ps -a

--rm 選項的功能是,當 container 的狀態變成 Exited 時,會自動把這個 container 移除,在練習或測試的時候非常方便。

背景執行

前景執行 Apache 的時候,按下昨天提到的 Ctrl + PCtrl + Q,會發現一點用都沒有。但像今天是啟動 HTTP server,通常會希望它是背景執行,這時可以使用另一個參數 -d,範例如下:

# 執行完會馬上回到 host 上
docker run -d httpd

# 觀察 container 是否在運作中
docker ps

執行完 docker run 會回 CONTAINER ID 並回到原本的命令提示字元。這時再觀察 docker ps 會看到對應的 container 是運作中的狀態。

Hello Docker 說明 docker rm 的時候有提到,container 的狀態不是 Up 才能移除。除了先使用 docker stop 再使用 docker rm 外,也可以使用 docker rm-f 參數強制移除 container:

# 執行完會馬上回到 host 上
docker run -d --name web httpd

# 確認正在執行中
docker ps

# 因 container 還在運作中,所以直接 rm 會出現錯誤訊息
docker rm web

# 注意 rm -f 是發出與 docker kill 相同的 SIGKILL 信號,而不是 docker stop 的 SIGTERM
docker rm -f web

--rm 選項可以讓它結束後自動移除,但得先用 docker stop 停止 container 才會觸發移除,那倒不如使用 docker rm -f 指令還比較直接一點。

雖然 Docker 並沒有特別禁止,但建議不要同時啟用 --rm-d 這兩個選項;另外還有一個原因是,在 Docker v1.13.0 版以前,是不能同時啟用 --rm-d 的,詳細可參考 Docker issue

存取服務

我們先把 container 都移除,然後背景執行一個 Apache container。

# 背景啟動 Apache
docker run -d httpd

# 確認 PORTS
docker ps

# curl 試試 HTTP 服務,也可以用瀏覽器測試
curl http://localhost/

docker ps 裡面有個欄位是 PORTS,上面寫著 80/tcp,看起來很像是可以透過本地的 80 port 來存取 HTTP server。但實際上用 curl 會回應連線被拒絕,這代表從 host 連到 container 的方法有問題。

說明解法前,大家可以先試看看下面的指令:run 3 個 Apache container:

docker run -d httpd
docker run -d httpd
docker run -d httpd

# 查看 container
docker ps

docker ps 可以觀察到,所有 port 都是 80/tcp,但奇妙的是,全部 container 都沒有遇到 port 衝突!?從這個實驗結果可以了解 container 具有隔離特性,也就是每個 container 都是獨立的個體,它們各自有屬於自己的 80 port 可以用,因此才不會相衝。

今天的目標是要從外界存取 container,因此要做點手腳才行--正是標題提到的 port forwarding。Docker 使用 -p 選項設定 port forwarding,範例如下:

# 加上 -p 參數
docker run -d -p 8080:80 httpd

# 確認 port 有被開出來了
curl http://localhost:8080/

-p 後面參數 8080:80 的意思代表:當連線到 host 的 8080 port 會轉接到 container 的 80 port。以上面的指令為例,只要輸入 http://localhost:8080/ 即可接到 container 所啟動的 HTTP 服務。

下面是簡單示意圖:

Container 啟動完後,8080 是被綁定在 host 上的。只要有另一個服務綁 8080 在 host 上的話,就會出現錯誤訊息 port is already allocated.

docker run -d -p 8080:80 httpd
docker run -d -p 8080:80 httpd

指令補充說明

docker run

補充說明其他參數:

  • --rm 當 container 主程序一結束時,立刻移除 container
  • -d|--detach 背景執行 container
  • -p|--publish 把 container 的 port 公開到 host 上,格式為 [[[IP:]HOST_PORT:]CONTAINER_PORT]
    給完整格式的話,會把 IP:HOST_PORT 綁定到 CONTAINER_PORT;如果沒給 IP,則 IP 會代 0.0.0.0;如果連 HOST_PORT 也沒給,則會使用 0.0.0.0 加上隨機選一個 port,如 0.0.0.0:32768

docker rm

補充說明其他參數:

  • -f|--force 如果是執行中的 container,會強制移除(使用 SIGKILL)

今日自我回顧

Container 具備了隔離機制,因此有辦法把它當作「輕量的 VM 」使用。

今天說明的 port forwarding,可以應用在任何有開 port 提供服務的 server,如:MySQLRedisMemcached 等,有興趣可以參考官網範例自行試試。

  • 了解 container 隔離的特性
  • 練習使用 docker run--rm 參數與 -p 參數
  • 練習透過 port forwarding 存取 container 提供的 HTTP server 或其他 server