昨天講了如何分清楚 IP 位址,也知道上次部署完開網站時,用的是 EC2 的公網 IP。
不過,前面在本機開 Next.js,網址明明是 http://localhost:3000,到了 Day13,卻只要輸入 http://EC2-公網-IP 就能看到頁面。
後面的 :3000 跑去哪了?今天就來拆開看ㄅ!
一台主機可以同時提供網站、郵件等多個網路服務。而資料到主機後,作業系統還是需要分辨說:這些資料要交給哪個程式處理?
這時就會用到 port(通訊埠,也常翻成連接埠)。可以先把它理解成程式收送網路資料的邏輯入口,由作業系統管理,並以數字識別。
以銀行為例:IP 位址可以想成這棟銀行大樓的地址,port 就像裡面不同服務櫃檯的編號。
以這次部署的 Next.js 網站來說,伺服器程式會在指定的 IP 位址與 port 上等待連線,這個動作叫做 監聽(listen)。所以光知道入口編號還不夠,對應的程式也要有啟動並等待連線,才能提供服務。
看網址時,可以先這樣拆:
昨天介紹的 IP,主要處理「封包要送到哪裡」。至於程式之間如何傳送與接收資料,就會用到 TCP、UDP 這類通訊協定。
假設要傳送的資料分成三段:第一段、第二段、第三段。傳送途中,第二段資料遺失,接收端只收到第一段與第三段。這時 TCP 和 UDP 會怎麼處理?
| 過程 | TCP | UDP |
|---|---|---|
| 傳送前 | 先建立連線 | 不用先建立像 TCP 那樣的連線 |
| 第二段資料遺失時 | 嘗試補傳第二段資料 | UDP 本身不會自動補傳 |
| 已收到的第三段資料 | 先暫存,等第二段補齊後再按順序交給程式 | 可以交給程式,不會等待第二段 |
| 程式最後取得的資料 | 補傳成功後,依序取得完整資料 | 本例只取得第一段與第三段,缺少第二段 |
這是簡化的傳送情境;UDP 範例假設應用程式沒有另外設計補傳機制。
兩者都會搭配 IP 傳送資料,也都使用 port 找到對應的程式。這次部署的 Next.js 網站使用的是 TCP,所以接下來的設定會看到「TCP 80」和「TCP 3000」。
TCP 和 UDP 各自有一套 port 編號。 所以同一個 IP 位址上的 TCP 80 和 UDP 80,代表不同的通訊端點;不能只看到數字相同,就當成同一個入口。
因為這次我們部署的網站是使用 TCP,Docker 的對應與 Security Group 的規則,也都要對上 TCP。看到設定裡的 TCP / 80,可以拆成「使用 TCP,目的 port 是 80」。
所以昨天介紹的封包:來源與目標的 IP 位址寫在 IP 標頭裡,來源與目標的 port 則寫在 TCP 或 UDP 標頭裡。 IP 負責依照位址轉送封包,TCP/UDP 則透過 port,將收到的資料交給對應的程式。
因為通訊協定有預設的 port。像是在使用 http://的情況下,又沒有另外指定 port 時,預設會使用 80 的 port。
因此,下面兩個網址都是指向同一個入口:
http://203.0.113.10
http://203.0.113.10:80
HTTPS 的預設 port 則是 443,後面設定 HTTPS 時會再遇到。這些預設值讓我們平常輸入網址時,不必每次把 port 寫出來。
如果網站對外提供 HTTP 的 port 不是 80,就要在網址中指定這個對外 port,例如 http://EC2-公網-IP:8080。
3000 是 Next.js 的預設監聽 port,next dev 和 next start 預設都會使用它,這個 port 也是前端開發網站預覽時常用的。
而我們這次在做的 image 使用 standalone 的 server.js,Dockerfile 裡也明確寫了:
ENV PORT=3000
這會讓 Next.js 的 standalone server 會讀取這個環境變數,決定啟動時使用哪個 port。
所以程式一樣可以在 3000 提供給 HTTP ,也可以使用在正式模式執行。使用哪個 port,和程式是開發模式還是正式模式,是兩件分開的事。
-p 80:3000:把主機入口接到容器我們在啟動 container 時,有這段:
這是 Docker 的 -p 用來發布 container 的 port,寫法的順序是 主機 port:Container port。如果沒有另外指定協定時,預設是 TCP。
因此 -p 80:3000 的意思是:把送到 EC2 主機 TCP 80 的連線,轉送到 容器的 TCP 3000。
因為主機和 container 有各自的網路環境,兩邊的 port 可以不同。而 Next.js 在 container 裡仍然監聽 3000,外面的瀏覽器是連 EC2 的 80。
這就是網址後面不用加 :3000 的原因!瀏覽器連的是主機的入口,再由 Docker 接到 container 裡的網站。
PS. 假設改成 -p 8080:3000,而且外部連線規則也配合放行 8080,網址就要寫成 http://EC2-公網-IP:8080。Container 裡的 Next.js 仍然使用 3000。
EXPOSE 和 Security Group 又在做什麼?我們在 Dockerfile 寫了 EXPOSE 3000,啟動時又用了 -p 80:3000,AWS 還設定了 HTTP 80,看起來好像一直在重複「開 port」。
但把它們放在一起,就比較好分清楚:
| 設定 | 負責的事情 |
|---|---|
ENV PORT=3000 |
讓這次的 Next.js server 啟動時使用 3000 |
EXPOSE 3000 |
宣告 image 預期使用的 container port |
-p 80:3000 |
建立主機 80 到 container 3000 的對應 |
| Security Group 的 TCP 80/My IP | 允許指定來源連入 EC2 的 TCP 80 |
所以 EXPOSE 本身不會發布 port,也不會啟動伺服器。 可以把它看成 image 留給使用者的說明;而真正建立主機與容器之間對應的是 -p。
Security Group 則依來源、協定、port 等條件允許流量。我們設定的 My IP 限制了可以連進來的來源;而外部連線要的目標的 port 是 主機的 80,所以這次放行 TCP 80 就好,不需要因為 container 使用 3000,再額外放行主機的 3000。
同樣地,只把 Security Group 改成允許 3000,也不會自動產生主機 3000 到 container 的對應。監聽、Docker 對應與網路規則,這樣都要接得起來了。
如果 EC2 和 my-app container 有正在執行的話,可以在 EC2 的 Session Manager 終端機輸入:
sudo docker port my-app
這個指令會列出 container 的 port 對應。用我們之前設定與 Docker 預設綁定位址時,IPv4 那行預期像這樣:
3000/tcp -> 0.0.0.0:80
這裡是把「container 的 3000」列在左邊,右邊顯示它發布到「主機的 80」。0.0.0.0 表示綁定主機所有 IPv4 介面的位址;從瀏覽器連線時,仍然使用 EC2 的公網 IP。
看到這個對應就代表 Docker 有這項設定;但網站是否正常回應,還是要搭配服務狀態與實際連線確認。
今天先把 80 和 3000 接起來。下一篇再回到開 EC2 時看到的 VPC 與 Subnet,看看這台主機究竟被放在哪個網路裡。