iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 25
2
Modern Web

Go into Web!系列 第 25

Day 25 | 使用 Docker 封裝與運行 Go 程式(一)

寫完網站後,總不可能總是透過 go run 的指令將網站運行起來,勢必要透過 build 的方式將整個程式封裝誠執行檔,但光是封裝執行檔,對於不同環境必須要 build 出不同檔案,這時候將程式 build 成 docker image 就是一個不錯的選擇,今天就來聊聊怎麼將 golang 程式封裝成 docker image 吧!

先備知識

  • 需要有 docker 環境,安裝檔可以從 這裡 取得
  • 需要懂一點點 docker 指令

範例專案

還記得在前幾天我們有寫過一個 一對一隨機匿名聊天室 的專案嗎,我們透過此專案當作範例

專案位置:https://github.com/codingXiang/random_anonymous_chat

Dockerfile

要使用 Docker 打包必須要透過撰寫 DockerfileDocker Engine 知道要做什麼步驟,首先我們先一步一步建構簡單的 Dockerfile,首先,我們在專案的根目錄建立一個名為 Dockerfile 的檔案。

以下步驟都是要寫在 Dockerfile 裡面

來源

要 build go 的專案,勢必一定要有 go 的環境,我們可以在 docker hub 中找到 golang 的環境 image,所以可以在 FROM 的部分寫上 golang

FROM golang

定義專案位置

此步驟為定義專案要放在 container 內部的哪一個位置,通常我習慣會放在根目錄底下的 /app 資料夾,因此可以先建立 /app 後,將 WORKDIR 設定到此

RUN     mkdir -p /app
WORKDIR /app

加入專案

因為要在 Container 內部進行 build,因此我們將整個專案 COPY 到 Container 內

COPY . .

下載相依 package

在進行 build 之前,要先將相依的 package 進行下載,可以透過 go mod download 的方式,依照 go.modgo.sum 裡面定義的 package 版本進行下載

RUN     go mod download

打包

接下來就是重頭戲了,將專案透過 go build 的方式打包,這邊我們透過 -o 的方式指定 build 完的 binary 為 app

RUN    go build -o app

進入點

build 完畢之後,最後就是要定義在啟動 docker image 時,要執行哪個檔案,這邊是執行 app

ENTRYPOINT ["./app"]

總結

整個 Docker image 如下

FROM        golang
RUN         mkdir -p /app
WORKDIR     /app
COPY        . .
RUN         go mod download
RUN         go build -o app
ENTRYPOINT  ["./app"]

打包

透過 docker build -t 的指令進行打包

docker build -t 'random_anonymous_chat' .

打包過程如下

Sending build context to Docker daemon    276kB
Step 1/7 : FROM        golang
latest: Pulling from library/golang
57df1a1f1ad8: Pull complete 
71e126169501: Pull complete 
1af28a55c3f3: Pull complete 
03f1c9932170: Pull complete 
f4773b341423: Pull complete 
fb320882041b: Pull complete 
24b0ad6f9416: Pull complete 
Digest: sha256:da7ff43658854148b401f24075c0aa390e3b52187ab67cab0043f2b15e754a68
Status: Downloaded newer image for golang:latest
 ---> 05c8f6d2538a
Step 2/7 : RUN         mkdir -p /app
 ---> Running in b236f2a55671
Removing intermediate container b236f2a55671
 ---> a1d4433bc891
Step 3/7 : WORKDIR     /app
 ---> Running in 23926232deef
Removing intermediate container 23926232deef
 ---> 4a29514e9e2e
Step 4/7 : COPY        . .
 ---> 6982af8b156a
Step 5/7 : RUN         go mod download
 ---> Running in 7952a98e18b9
Removing intermediate container 7952a98e18b9
 ---> bfe1a6d44dec
Step 6/7 : RUN         go build -o app
 ---> Running in 1fbd1dc5d3a0
Removing intermediate container 1fbd1dc5d3a0
 ---> e18c00c53eff
Step 7/7 : ENTRYPOINT  ["./app"]
 ---> Running in 5aa4c1da9112
Removing intermediate container 5aa4c1da9112
 ---> 6c61e6e96b4b
Successfully built 6c61e6e96b4b
Successfully tagged random_anonymous_chat:latest

完成後可以透過 docker images 來查看,會看到已經有一個 image 出現拉

REPOSITORY                                           TAG                                              IMAGE ID            CREATED             SIZE
random_anonymous_chat                                latest                                           3e102196a966        4 seconds ago       1.38GB

執行

最後我們可以透過 docker run 的指令來運行封裝好的 image,可以用 -e 來指定環境變數、透過 -p 來指定 container 對外暴露的 port

docker run -e REDIS_URL=172.20.10.2 -p 5000:5000 random_anonymous_chat

可以看到執行的結果如下

2020/09/24 13:42:15 redis 回應成功, PONG
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] Loaded HTML Templates (2): 
        - 
        - index.html

[GIN-debug] GET    /assets/*filepath         --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (3 handlers)
[GIN-debug] HEAD   /assets/*filepath         --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (3 handlers)
[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] GET    /ws                       --> main.main.func2 (3 handlers)
[GIN-debug] Listening and serving HTTP on :5000

這時候開啟瀏覽器,輸入 http://127.0.0.1:5000 就可以看到網站順利運作拉!

範例專案

今天的範例程式我放在這邊~有興趣的朋友可以 fork 回去玩玩看
https://github.com/codingXiang/random_anonymous_chat/tree/docker

小結

透過 Docker 方式打包程式的好處在於,只要在任何有 Docker engine 的環境中都可以順利的被執行,而且可以確保每次的結果都是一樣的,才不會發生在不同環境執行會有環境的不相依的問題發生。

大家可以發現,build 完的 docker image 足足有 1.38G 這麼大,一定有辦法可以縮減它的大小的!
明天就讓我們繼續使用這個範例將整體的 image size 跟方法做最佳化吧!


上一篇
Day 24 | 自己測一下程式好嗎?淺入單元測試(二)
下一篇
Day26 | 使用 Docker 封裝與運行 Go 程式(二)
系列文
Go into Web!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

2
eric19740521
iT邦新手 1 級 ‧ 2021-01-22 15:01:04

請教一下
docker 用going寫網站服務
那麼let's憑證 要如何安裝在docker
讓用戶能在https://xxx.xxx.com 運作正常!!
是否能提點一下

阿翔 iT邦新手 4 級 ‧ 2021-01-22 15:17:50 檢舉

在不動到程式的情況下應該可以透過 docker 架設一台 nginx,把 let's encrypt 的憑證放在 nginx 內部並綁定網址 (xxx.xxx.com 之類的),接著再設定反向代理 將 traffic 轉向 golang 所啟動的 ip:port 即可。

步驟整理一下:

  1. 透過 docker 啟動 nginx
  2. 將憑證放置於 nginx 中並且設定 server 為自定義網址
  3. 設定反向代理將流量轉導至 golang 所啟動的 ip:port

tips : golang 所啟動的 web server 不要把 port 開放對外

這是我的想法~希望可以幫到你

好的!!我試看看

0
whitefloor
iT邦研究生 2 級 ‧ 2021-08-17 17:45:02

大神你這篇解決了我所有問題
太神辣

阿翔 iT邦新手 4 級 ‧ 2021-08-18 11:10:05 檢舉

很高興有幫到你 XD

我要留言

立即登入留言