Docker Run Port Binding

在之前這篇文章最後: Docker Certified Associate(DCA)認證考試學習-Docker Context

有跑起來一個nginx容器並且開放了一個端口8080,並且用瀏覽器打開網址就能在畫面上看到nginx的歡迎頁面

docker run --name nginx28 -p 8080:80 nginx

那麼背後的原理是什麼呢?
首先先看一下 -p 參數是什麼意思

--publish , -p		Publish a container’s port(s) to the host
--publish-all , -P		Publish all exposed ports to random ports

簡單來說就是把container內部的port與host的port做連結
首先host代表的是我們的實體機器也可以是一台VM
以Play with Docker為例,當我們按下ADD NEW INSTANCE按鈕時,下方會跑出一台新的機器,我們可以把這台機器當成HOST,這台HOST有自己完整的作業系統
同時在HOST內運行起多個container時,每個container內部也有一套完整的作業系統

所以當需求訪問其中一個container時,就需要有一個轉送的機制將HOST的port的流量傳送到container內部的port

現在在看一下以下範例 這邊使用 docker ps -a 列出所以運行中的image,其中有一個欄位叫做 PORTS 值為 0.0.0.0:8080->80/tcp

[node1] (local) root@192.168.0.8 ~
$ docker run -dit -p 8080:80 nginx

[node1] (local) root@192.168.0.8 ~
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                  NAMES
8a24ee600104   nginx     "/docker-entrypoint.…"   13 seconds ago   Up 12 seconds   0.0.0.0:8080->80/tcp   happy_archimedes

那麼在試試看 -p 參數帶入其他的值會發生什麼事

[node1] (local) root@192.168.0.8 ~
$ docker run -dit --name nginx28 -p 8080:81 nginx
b538e3d3eec1814aa7f0b71359a28fd1263f15968b77aaf0c108be1ff47af574

[node1] (local) root@192.168.0.8 ~
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                          NAMES
b538e3d3eec1   nginx     "/docker-entrypoint.…"   7 seconds ago   Up 6 seconds   80/tcp, 0.0.0.0:8080->81/tcp   clever_einstein

那麼在試試看如果不帶 -p 參數會發生什麼事

[node1] (local) root@192.168.0.8 ~
$ docker run -dit --name nginx28 nginx

[node1] (local) root@192.168.0.8 ~
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS     NAMES
b5311d14d1ae   nginx     "/docker-entrypoint.…"   13 seconds ago   Up 12 seconds   80/tcp    quirky_nightingale

該怎麼解讀這些結果呢? 首先第一個測試有帶 -p 8080:80 參數,結果可以看到PORTS欄位有一個值 0.0.0.0:8080->80/tcp PORTS欄位的值中間有一個箭頭->,代表這個操作是有方向行的,也就是從 AB 方向傳送
也就是說Docker會監聽Host的8080 port,並且把流量轉送到container的tcp 80 port

第二個測試帶入 -p 8080:80 參數,PORTS欄位有兩個值 80/tcp, 0.0.0.0:8080->81/tcp

第三個測試沒有帶入 -p 參數,PORTS欄位只有一個值 80/tcp

那麼為什麼沒有帶 -p 參數的結果也會輸出一個tcp 80 port呢? 而且為什麼是tcp 80 port可以選擇其它的值嘛?

接下來就要到Dockerfile中找答案了


Docker EXPOSE

首先先看一下Docker EXPOSE的文檔

只要開頭使用EXPOSE並且輸入指定的port即可,也可以只定TCP或者UDP,預設情況下為TCP

EXPOSE <port> [<port>/<protocol>...]

例如以下範例,會將container內部的tcp與udp 80 port對外開放

EXPOSE 80/tcp
EXPOSE 80/udp

學會了怎麼將container的port對外開放之後
可以到Nginx的Github看看Dockerfile是怎麼寫的
在這一行可以看到EXPOSE的語法,所以這就是為什麼將Nginx運行起來時Container總會有個80 port的原因

這邊需要注意關鍵是container內部的程式
以nginx為例,有使用過nginx的人一定知道nginx關鍵的設定檔nginx.conf
這個設定檔告知nginx將會監聽80 port並且顯示預設的首頁index.html
所以在將nginx包裝成image的過程也會有同樣的目錄結構
並且在image的最後使用Expose對外暴露一個80 port
來將host的流量轉發到container內部,最後讓nginx監聽到


根據上一段的說明,我們可以來製作一個專屬客製化的nginx image 首先建立以下目錄及檔案

mkdir customnginx && cd $_
touch Dockerfile
touch 8081.conf
touch custom.html

修改8081.conf內容,此規則會監聽8081 port並轉到custom.html頁面

#8081.conf
server {
    listen       8081;
    listen  [::]:8081;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  custom.html custom.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

修改custom.html內容,會顯示出客製化html

#custom.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx! From Custom Port</title>
</head>
<body>
<h1>Welcome to nginx! From Custom Port</h1>
</body>
</html>

我們這邊底層使用nginx,並將兩個檔案放到各自的資料夾內
要注意這邊的目錄可能會根據不同廠商發布的nginx image而有所不同 並且最後對將container的8081 port暴露出去

#Dockerfile
FROM nginx

COPY 8081.conf /etc/nginx/conf.d/
COPY custom.html /usr/share/nginx/html/

EXPOSE 8081

在這邊看到原本的80 port也還存在,並不會被我們自訂的port所取代,並且還額外綁定了一個8081 port
這兩個port也都綁定到HOST環境底下了,所以照理來說我們現在訪問機器的地址就能看到客制頁面了

docker build -t customnginx .
docker run -dit -p 80:80 -p 8081:8081 customnginx

[node1] (local) root@192.168.0.28 ~/customnginx
$ docker ps -a
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS         PORTS                                        NAMES
50f379fcb0e1   customnginx   "/docker-entrypoint.…"   9 seconds ago   Up 6 seconds   0.0.0.0:80->80/tcp, 0.0.0.0:8081->8081/tcp   vibrant_goodall
DCAEXPOSEPort-CustomPage

Summary

今天了解到Dockerfile的EXPOSE與Docker Run的 -p參數,兩個是要互相搭配使用的缺一不可 並且最後還將nginx官方的image作為底層客制出我們專用的Nginx版本,能顯示出我們自訂的頁面 當我將image打包完成後,你只需要下載我的image並且只要在有Docker的環境就可以馬上運行我的成果
而且也不需要知道我到底是怎麼實做的,我只要告訴你我的image要使用80和8081 port
你只要執行docker run命令即可,這就是Docker最方便的地方

有興趣的朋友可以直接運行以下命令,就以運行今天的image了

docker run -dit -p 80:80 -p 8081:8081 allengaodev/customnginx:latest