如何使用Docker部署Go Web應(yīng)用的實現(xiàn)
本文介紹了如何使用Docker以及Docker Compose部署我們的 Go Web 程序。
為什么需要Docker?
使用docker的主要目標(biāo)是容器化。也就是為你的應(yīng)用程序提供一致的環(huán)境,而不依賴于它運行的主機。
想象一下你是否也會遇到下面這個場景,你在本地開發(fā)了你的應(yīng)用程序,它很可能有很多的依賴環(huán)境或包,甚至對依賴的具體版本都有嚴(yán)格的要求,當(dāng)開發(fā)過程完成后,你希望將應(yīng)用程序部署到web服務(wù)器。這個時候你必須確保所有依賴項都安裝正確并且版本也完全相同,否則應(yīng)用程序可能會崩潰并無法運行。如果你想在另一個web服務(wù)器上也部署該應(yīng)用程序,那么你必須從頭開始重復(fù)這個過程。這種場景就是Docker發(fā)揮作用的地方。
對于運行我們應(yīng)用程序的主機,不管是筆記本電腦還是web服務(wù)器,我們唯一需要做的就是運行一個docker容器平臺。從以后,你就不需要擔(dān)心你使用的是MacOS,Ubuntu,Arch還是其他。你只需定義一次應(yīng)用,即可隨時隨地運行。
Docker部署示例
準(zhǔn)備代碼
這里我先用一段使用net/http庫編寫的簡單代碼為例講解如何使用Docker進(jìn)行部署,后面再講解稍微復(fù)雜一點的項目部署案例。
package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", hello) server := &http.Server{ Addr: ":8888", } fmt.Println("server startup...") if err := server.ListenAndServe(); err != nil { fmt.Printf("server startup failed, err:%v\n", err) } } func hello(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("hello liwenzhou.com!")) }
上面的代碼通過8888端口對外提供服務(wù),返回一個字符串響應(yīng):hello liwenzhou.com!。
創(chuàng)建Docker鏡像
鏡像(image)包含運行應(yīng)用程序所需的所有東西——代碼或二進(jìn)制文件、運行時、依賴項以及所需的任何其他文件系統(tǒng)對象。
或者簡單地說,鏡像(image)是定義應(yīng)用程序及其運行所需的一切。
編寫Dockerfile
要創(chuàng)建Docker鏡像(image)必須在配置文件中指定步驟。這個文件默認(rèn)我們通常稱之為Dockerfile。(雖然這個文件名可以隨意命名它,但最好還是使用默認(rèn)的Dockerfile。)
現(xiàn)在我們開始編寫Dockerfile,具體內(nèi)容如下:
注意:某些步驟不是唯一的,可以根據(jù)自己的需要修改諸如文件路徑、最終可執(zhí)行文件的名稱等
FROM golang:alpine # 為我們的鏡像設(shè)置必要的環(huán)境變量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移動到工作目錄:/build WORKDIR /build # 將代碼復(fù)制到容器中 COPY . . # 將我們的代碼編譯成二進(jìn)制可執(zhí)行文件app RUN go build -o app . # 移動到用于存放生成的二進(jìn)制文件的 /dist 目錄 WORKDIR /dist # 將二進(jìn)制文件從 /build 目錄復(fù)制到這里 RUN cp /build/app . # 聲明服務(wù)端口 EXPOSE 8888 # 啟動容器時運行的命令 CMD ["/dist/app"]
Dockerfile解析
From
我們正在使用基礎(chǔ)鏡像golang:alpine來創(chuàng)建我們的鏡像。這和我們要創(chuàng)建的鏡像一樣是一個我們能夠訪問的存儲在Docker倉庫的基礎(chǔ)鏡像。這個鏡像運行的是alpine Linux發(fā)行版,該發(fā)行版的大小很小并且內(nèi)置了Go,非常適合我們的用例。有大量公開可用的Docker鏡像,請查看https://hub.docker.com/_/golang
Env
用來設(shè)置我們編譯階段需要用的環(huán)境變量。
WORKDIR,COPY,RUN
這幾個命令做的事都寫在注釋里了,很好理解。
EXPORT,CMD
最后,我們聲明服務(wù)端口,因為我們的應(yīng)用程序監(jiān)聽的是這個端口并通過這個端口對外提供服務(wù)。并且我們還定義了在我們運行鏡像的時候默認(rèn)執(zhí)行的命令CMD ["/dist/app"]。
構(gòu)建鏡像
在項目目錄下,執(zhí)行下面的命令創(chuàng)建鏡像,并指定鏡像名稱為goweb_app:
docker build . -t goweb_app
等待構(gòu)建過程結(jié)束,輸出如下提示:
...
Successfully built 90d9283286b7
Successfully tagged goweb_app:latest
現(xiàn)在我們已經(jīng)準(zhǔn)備好了鏡像,但是目前它什么也沒做。我們接下來要做的是運行我們的鏡像,以便它能夠處理我們的請求。運行中的鏡像稱為容器。
執(zhí)行下面的命令來運行鏡像:
docker run -p 8888:8888 goweb_app
標(biāo)志位-p用來定義端口綁定。由于容器中的應(yīng)用程序在端口8888上運行,我們將其綁定到主機端口也是8888。如果要綁定到另一個端口,則可以使用-p $HOST_PORT:8888。例如-p 5000:8888。
現(xiàn)在就可以測試下我們的web程序是否工作正常,打開瀏覽器輸入http://127.0.0.1:8888就能看到我們事先定義的響應(yīng)內(nèi)容如下:
hello liwenzhou.com!
分階段構(gòu)建示例
我們的Go程序編譯之后會得到一個可執(zhí)行的二進(jìn)制文件,其實在最終的鏡像中是不需要go編譯器的,也就是說我們只需要一個運行最終二進(jìn)制文件的容器即可。
Docker的最佳實踐之一是通過僅保留二進(jìn)制文件來減小鏡像大小,為此,我們將使用一種稱為多階段構(gòu)建的技術(shù),這意味著我們將通過多個步驟構(gòu)建鏡像。
FROM golang:alpine AS builder # 為我們的鏡像設(shè)置必要的環(huán)境變量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移動到工作目錄:/build WORKDIR /build # 將代碼復(fù)制到容器中 COPY . . # 將我們的代碼編譯成二進(jìn)制可執(zhí)行文件 app RUN go build -o app . ################### # 接下來創(chuàng)建一個小鏡像 ################### FROM scratch # 從builder鏡像中把/dist/app 拷貝到當(dāng)前目錄 COPY --from=builder /build/app / # 需要運行的命令 ENTRYPOINT ["/app"]
使用這種技術(shù),我們剝離了使用golang:alpine作為編譯鏡像來編譯得到二進(jìn)制可執(zhí)行文件的過程,并基于scratch生成一個簡單的、非常小的新鏡像。我們將二進(jìn)制文件從命名為builder的第一個鏡像中復(fù)制到新創(chuàng)建的scratch鏡像中。有關(guān)scratch鏡像的更多信息,請查看https://hub.docker.com/_/scratch
附帶其他文件的部署示例
這里以我之前《Go Web視頻教程》中的小清單項目為例,項目的Github倉庫地址為:https://github.com/Q1mi/bubble。
如果項目中帶有靜態(tài)文件或配置文件,需要將其拷貝到最終的鏡像文件中。
我們的bubble項目用到了靜態(tài)文件和配置文件,具體目錄結(jié)構(gòu)如下:
bubble ├── README.md ├── bubble ├── conf │ └── config.ini ├── controller │ └── controller.go ├── dao │ └── mysql.go ├── example.png ├── go.mod ├── go.sum ├── main.go ├── models │ └── todo.go ├── routers │ └── routers.go ├── setting │ └── setting.go ├── static │ ├── css │ │ ├── app.8eeeaf31.css │ │ └── chunk-vendors.57db8905.css │ ├── fonts │ │ ├── element-icons.535877f5.woff │ │ └── element-icons.732389de.ttf │ └── js │ ├── app.007f9690.js │ └── chunk-vendors.ddcb6f91.js └── templates ├── favicon.ico └── index.html
我們需要將templates、static、conf三個文件夾中的內(nèi)容拷貝到最終的鏡像文件中。更新后的Dockerfile如下
FROM golang:alpine AS builder # 為我們的鏡像設(shè)置必要的環(huán)境變量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移動到工作目錄:/build WORKDIR /build # 復(fù)制項目中的 go.mod 和 go.sum文件并下載依賴信息 COPY go.mod . COPY go.sum . RUN go mod download # 將代碼復(fù)制到容器中 COPY . . # 將我們的代碼編譯成二進(jìn)制可執(zhí)行文件 bubble RUN go build -o bubble . ################### # 接下來創(chuàng)建一個小鏡像 ################### FROM scratch COPY ./templates /templates COPY ./static /static COPY ./conf /conf # 從builder鏡像中把/dist/app 拷貝到當(dāng)前目錄 COPY --from=builder /build/bubble / # 需要運行的命令 ENTRYPOINT ["/bubble", "conf/config.ini"]
簡單來說就是多了幾步COPY的步驟,大家看一下Dockerfile中的注釋即可。
Tips: 這里把COPY靜態(tài)文件的步驟放在上層,把COPY二進(jìn)制可執(zhí)行文件放在下層,爭取多使用緩存。
關(guān)聯(lián)其他容器
又因為我們的項目中使用了MySQL,我們可以選擇使用如下命令啟動一個MySQL容器,它的別名為mysql8019;root用戶的密碼為root1234;掛載容器中的/var/lib/mysql到本地的/Users/q1mi/docker/mysql目錄;內(nèi)部服務(wù)端口為3306,映射到外部的13306端口。
docker run --name mysql8019 -p 13306:3306 -e MYSQL_ROOT_PASSWORD=root1234 -v /Users/q1mi/docker/mysql:/var/lib/mysql -d mysql:8.0.19
這里需要修改一下我們程序中配置的MySQL的host地址為容器別名,使它們在內(nèi)部通過別名(此處為mysql8019)聯(lián)通。
[mysql] user = root password = root1234 host = mysql8019 port = 3306 db = bubble
修改后記得重新構(gòu)建bubble_app鏡像:
docker build . -t bubble_app
我們這里運行bubble_app容器的時候需要使用--link的方式與上面的mysql8019容器關(guān)聯(lián)起來,具體命令如下:
docker run --link=mysql8019:mysql8019 -p 8888:8888 bubble_app
Docker Compose模式
除了像上面一樣使用--link的方式來關(guān)聯(lián)兩個容器之外,我們還可以使用Docker Compose來定義和運行多個容器。
Compose是用于定義和運行多容器 Docker 應(yīng)用程序的工具。通過 Compose,你可以使用 YML 文件來配置應(yīng)用程序需要的所有服務(wù)。然后,使用一個命令,就可以從 YML 文件配置中創(chuàng)建并啟動所有服務(wù)。
使用Compose基本上是一個三步過程:
- 使用Dockerfile定義你的應(yīng)用環(huán)境以便可以在任何地方復(fù)制。
- 定義組成應(yīng)用程序的服務(wù),docker-compose.yml 以便它們可以在隔離的環(huán)境中一起運行。
- 執(zhí)行 docker-compose up命令來啟動并運行整個應(yīng)用程序。
我們的項目需要兩個容器分別運行mysql和bubble_app,我們編寫的docker-compose.yml文件內(nèi)容如下:
# yaml 配置 version: "3.7" services: mysql8019: image: "mysql:8.0.19" ports: - "33061:3306" command: "--default-authentication-plugin=mysql_native_password --init-file /data/application/init.sql" environment: MYSQL_ROOT_PASSWORD: "root1234" MYSQL_DATABASE: "bubble" MYSQL_PASSWORD: "root1234" volumes: - ./init.sql:/data/application/init.sql bubble_app: build: . command: sh -c "./wait-for.sh mysql8019:3306 -- ./bubble ./conf/config.ini" depends_on: - mysql8019 ports: - "8888:8888"
這個 Compose 文件定義了兩個服務(wù):bubble_app 和 mysql8019。其中:
bubble_app
使用當(dāng)前目錄下的Dockerfile文件構(gòu)建鏡像,并通過depends_on指定依賴mysql8019服務(wù),聲明服務(wù)端口8888并綁定對外8888端口。
mysql8019
mysql8019 服務(wù)使用 Docker Hub 的公共 mysql:8.0.19 鏡像,內(nèi)部端口3306,外部端口33061。
這里需要注意一個問題就是,我們的bubble_app容器需要等待mysql8019容器正常啟動之后再嘗試啟動,因為我們的web程序在啟動的時候會初始化MySQL連接。這里共有兩個地方要更改,第一個就是我們Dockerfile中要把最后一句注釋掉:
# Dockerfile ... # 需要運行的命令(注釋掉這一句,因為需要等MySQL啟動之后再啟動我們的Web程序) # ENTRYPOINT ["/bubble", "conf/config.ini"]
第二個地方是在bubble_app下面添加如下命令,使用提前編寫的wait-for.sh腳本檢測mysql8019:3306正常后再執(zhí)行后續(xù)啟動Web應(yīng)用程序的命令:
command: sh -c "./wait-for.sh mysql8019:3306 -- ./bubble ./conf/config.ini"
當(dāng)然,因為我們現(xiàn)在要在bubble_app鏡像中執(zhí)行sh命令,所以不能在使用scratch鏡像構(gòu)建了,這里改為使用debian:stretch-slim,同時還要安裝wait-for.sh腳本用到的netcat,最后不要忘了把wait-for.sh腳本文件COPY到最終的鏡像中,并賦予可執(zhí)行權(quán)限哦。更新后的Dockerfile內(nèi)容如下:
FROM golang:alpine AS builder # 為我們的鏡像設(shè)置必要的環(huán)境變量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移動到工作目錄:/build WORKDIR /build # 復(fù)制項目中的 go.mod 和 go.sum文件并下載依賴信息 COPY go.mod . COPY go.sum . RUN go mod download # 將代碼復(fù)制到容器中 COPY . . # 將我們的代碼編譯成二進(jìn)制可執(zhí)行文件 bubble RUN go build -o bubble . ################### # 接下來創(chuàng)建一個小鏡像 ################### FROM debian:stretch-slim COPY ./wait-for.sh / COPY ./templates /templates COPY ./static /static COPY ./conf /conf # 從builder鏡像中把/dist/app 拷貝到當(dāng)前目錄 COPY --from=builder /build/bubble / RUN set -eux; \ apt-get update; \ apt-get install -y \ --no-install-recommends \ netcat; \ chmod 755 wait-for.sh # 需要運行的命令 # ENTRYPOINT ["/bubble", "conf/config.ini"]
所有的條件都準(zhǔn)備就緒后,就可以執(zhí)行下面的命令跑起來了:
docker-compose up
完整版代碼示例,請查看我的github倉庫:https://github.com/Q1mi/deploy_bubble_using_docker。
總結(jié)
使用Docker容器能夠極大簡化我們在配置依賴環(huán)境方面的操作,但同時也對我們的技術(shù)儲備提了更高的要求。對于Docker不管你是熟悉抑或是不熟悉,技術(shù)發(fā)展的車輪都滾滾向前。
參考鏈接:
https://levelup.gitconnected.com/complete-guide-to-create-docker-container-for-your-golang-application-80f3fb59a15e
到此這篇關(guān)于如何使用Docker部署Go Web應(yīng)用的實現(xiàn)的文章就介紹到這了,更多相關(guān)Docker部署Go Web內(nèi)容請搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習(xí)參考,不代表本站立場,如有內(nèi)容涉嫌侵權(quán),請聯(lián)系alex-e#qq.com處理。