熟悉Docker如何提升你在構建、測試并部署Go Web應用程序的方式,并且理解如何使用Semaphore來持續部署。
簡介
大多數情況下Go應用程序被編譯成單個二進制文件,web應用程序則會包括模版和配置文件。而當一個項目中有很多文件的時候,由于很多文件沒有同步就會導致錯誤的發生并且產生很多的問題。
在本教程中,你將學習如何使用Docker部署一個Go web應用程序,并且認識到Docker將如何改進你的開發工作流及部署流程。各種規模的團隊都會從這里所介紹的設置中受益。
目標
在本文結束后,你將:
。對Docker有基本的了解,
。發現在Docker將如何幫助你開發Go應用程序
。學習如何為一個生產環境中的Go應用程序創建Docker容器
。知道如何使用Semaphore持續地在你的服務器上部署Docker容器
先決條件
為了學習本教程,你講需要:
。在你的主機或者服務器上安裝Docker
。具有一臺能夠使用SSH密鑰對SSH請求進行認證的服務器
理解Docker
Docker幫助你為應用程序創建一個單獨的可部署單元。這個單元,也被稱為容器,包含該應用程序需要的所有東西。它包括代碼(或者二進制文件)、runtime(運行環境)、系統工具盒系統庫。將所有必需的資源打包成一個單元將確保無論應用程序部署到哪里都有完全相同的環境。這也有助于維護一個完全相同的開發和生產配置,這在以前是很難追蹤的。
一旦開始,容器的創建和部署將自動完成。它消除了一大類問題。這些問題主要是由于文件沒有同步或者開發和生產環境之間的差異導致的。Docker幫助解決了這些問題。
相比于虛擬機的優勢
容器提供了與虛擬機相似的資源分配和隔離優勢。然而,相同之處僅此而已。
一個虛擬機需要它自己的客戶操作系統而容器共享主機操作系統的內核。這意味著容器更加輕量而且需要更少的資源。從本質上講,一個虛擬機是操作系統中的一個操作系統。而另一方面的容器則更像是操作系統中的其它應用程序。基本上,容器需要的資源(內存、磁盤空間等等)比虛擬機少很多,并且具有比虛擬機快很多的啟動時間。
Docker在開發階段的優勢
在開發中使用Docker的優勢包括:
。一個用于所有團隊成員的標準開發環境
。更新的依賴性集中化以及在任何地方都能使用相同的容器
。在開發和生產中完全相同的環境
。修復了可能只會出現在生產環境中的潛在問題
為什么使用Docker運行一個Go Web應用程序?
多數Go應用程序時簡單的二進制文件。這就引發一個問題 - 為什么使用Docker運行一個Go應用程序?一些使用Docker運行Go的理由包括:
。Web應用程序通常都有模版和配置文件。Docker有助于保持這些文件與二進制文件的同步
。Docker確保了在開發或生產中完全相同的配置。很多時候當應用程序可以在開發環境中正常工作時,在生產環境去無法正常工作。使用DOcker則把你從對這些問題的擔心中解放了出來。
。在一個大型的團隊中主機、操作系統及所安裝的軟件可能存在很大的不同。Docker提供了一種機制來確保一致的開發環境配置。這將提升團隊的生產力并且在開發階段減少沖突和可避免問題的發生。
創建一個簡單的Go Web應用程序
在本文中味了演示,我們會用Go創建一個簡單的Web應用程序。這個我們稱之為MathApp的應用程序將:
。探索不同數學運算的路徑
。在視圖中使用HTML模版
。使用一個可配置的文件來定制化該應用程序
。包含所選功能的測試
訪問 /sum/3/6 將顯示一個包含3與6相加后結果的頁面。同樣的,訪問 /product/3/6 將顯示一個3和6乘積的頁面。
在本文中我們使用 Beego 框架。請注意你可以為你的應用亨旭使用任何框架(或者什么也不用)。
最終的目錄結構
完成之后,MathApp的目錄結構應該看起來如下:
?
?
MathApp ├── conf │ └── app.conf ├── main.go ├── main_test.go └── views ├── invalid-route.html └── result.html
?
?
我們假設 MathApp 目錄位于 /app 目錄之中。
應用程序的主文件時 main.go ,為主應用程序的根目錄中。這個文件包含該應用的所有功能。一些 main.go 中的功能是使用 main_test.go 來測試的。
views文件夾中包含視圖文件 invald-route.html 和 result.html 。配置文件 app.conf 位于 conf 文件夾中。 Beego 使用該文件來定制化應用程序。
應用程序文件的內容
應用程序主文件(main.go)包含所有的應用程序邏輯。該文件的內容如下:
?
?
*// main.go* **package** main **import** ( "strconv" "github.com/astaxie/beego" ) *// The main function defines a single route, its handler* *// and starts listening on port 8080 (default port for Beego)* **func** main() { */* This would match routes like the following:* */sum/3/5* */product/6/23* *...* **/* beego.Router("/:operation/int/int", &mainController{}) beego.Run() } *// This is the controller that this application uses* **type** mainController **struct** { beego.Controller } *// Get() handles all requests to the route defined above* **func** (c *mainController) Get() { *//Obtain the values of the route parameters defined in the route above* operation := c.Ctx.Input.Param(":operation") num1, _ := strconv.Atoi(c.Ctx.Input.Param(":num1")) num2, _ := strconv.Atoi(c.Ctx.Input.Param(":num2")) *//Set the values for use in the template* c.Data["operation"] = operation c.Data["num1"] = num1 c.Data["num2"] = num2 c.TplName = "result.html" *// Perform the calculation depending on the 'operation' route parameter* **switch** operation { **case** "sum": c.Data["result"] = add(num1, num2) **case** "product": c.Data["result"] = multiply(num1, num2) **default**: c.TplName = "invalid-route.html" } } **func** add(n1, n2 int) int { **return** n1 + n2 } **func** multiply(n1, n2 int) int { **return** n1 * n2 }
?
?
在你的應用程序中,它可能被分割到多個文件中。然而,針對本教程的目的,我們希望事情簡單化。
測試文件的內容
main.go文件有一些需要測試的功能。對于這些功能的測試可以在main_test.go中找到。該文件的內容如下:
?
?
// main_test.go package main import "testing" func TestSum(t *testing.T) { if add(2, 5) != 7 { t.Fail() } if add(2, 100) != 102 { t.Fail() } if add(222, 100) != 322 { t.Fail() } } func TestProduct(t *testing.T) { if multiply(2, 5) != 10 { t.Fail() } if multiply(2, 100) != 200 { t.Fail() } if multiply(222, 3) != 666 { t.Fail() }
?
?
}
如果你想進行持續的部署,那么對你的應用程序進行測試是特別有用的。如果你有了足夠的測試,那么你可以持續地部署而不必擔心在你的應用程序中出現錯誤。
視圖文件內容
視圖文件時HTML模版。應用程序使用它們來顯示對請求的應答。result.html的內容如下:
?
?
MathApp - {{.operation}} The {{.operation}} of {{.num1}} and {{.num2}} is {{.result}} invalid-route.html的內容如下:MathApp Invalid operation
?
?
配置文件的內容
app.conf是Beego用于配置應用程序的文件。它的內容如下:
?
?
; app.conf appname = MathApp httpport = 8080 runmode = dev
?
?
在這個文件中:
。appname是應用程序將要運行的進程的名字
。httpport是應用程序將要監聽的的端口
。runmode聲明了應用程序將要運行的模式。有效的指包括dev用于開發而prod用于生產。
在開發中使用Docker
本節將介紹在開發過程中使用Docker的好處,并且向你展示在開發中使用Docker的必須步驟。
配置Docker用于開發
我們將使用dockerfile來配置Docker以便用于開發。針對開發環境,對其的配置應該滿足以下的要求:
。我們將使用上一節所提及的應用程序
。這些文件無論從容器的內部還是外部都可以訪問
。我們將使用beego自帶的bee工具。它用于在開發過程中在線地重新加載應用程序(在Docker容器的內部)
。Docker將為應用程序開放8080端口
。在我們的主機上,應用程序保存在/app/MathApp中
。在Docker容器中,應用程序保存在/go/src/MathApp中
。我們將為開發所創建的Docker image的名字是ma-image
。我們將要運行的Docker容器的名字是ma-instance
步驟一 - 創建Dockerfile
如下的Dockerfile可以滿足以上的要求:
?
?
**FROM** golang:1.6 *# Install beego and the bee dev tool* **RUN** go get github.com/astaxie/beego && go get github.com/beego/bee *# Expose the application on port 8080* **EXPOSE** 8080 *# Set the entry point of the container to the bee command that runs the* *# application and watches for changes* **CMD** ["bee", "run"]
?
?
第一行,
?
?
FROM golang:1.6
?
?
將Go的官方映像文件作為基礎映像。該映像文件預安裝了 Go 1.6 . 該映像已經把 $GOPATH 的值設置到了 /go 。所有安裝在 /go/src 中的包將能夠被go命令訪問。
第二行,
?
?
RUN go get github.com/astaxie/beego && go get github.com/beego/bee
?
?
安裝 beego 包和 bee 工具。 beego 包將在應用程序中使用。 bee 工具用語在開發中再現地重新加載我們的代碼。
第三行,
?
?
EXPOSE 8080
?
?
在開發主機上利用容器為應用程序開放8080端口。
最后一行,
?
?
CMD ["bee", "run"]
?
?
使用bee命令啟動應用程序的在線重新加載。
步驟二 - 構建image
一旦創建了Docker file,運行如下的命令來創建image:
?
?
docker build -t ma-image .
?
?
執行以上的命令將創建名為ma-image的image。該image現在可以用于使用該應用程序的任何人。這將確保這個團隊能夠使用一個統一的開發環境。
為了查看你的系統上的image列表,運行如下的命令:
?
?
docker images
?
?
這行該命令將輸出與以下類似的內容:
?
?
REPOSITORY TAG IMAGE ID CREATED SIZE ma-image latest 8d53aa0dd0cb 31 seconds ago 784.7 MB golang 1.6 22a6ecf1f7cc 5 days ago 743.9 MB
?
?
注意image的確切名字和編號可能不同,但是,你應該至少看到列表中有 golang 和 ma-image image。
步驟三 - 運行容器
一旦 ma-image 已經完成,你可以使用以下的命令啟動一個容器:
?
?
docker run -it --rm --name ma-instance -p 8080:8080 -v /app/MathApp:/go/src/MathApp -w /go/src/MathApp ma-image
?
?
讓我們分析一下上面的命令來看看它做了什么。
。docker run命令用于從一個image上啟動一個容器
。-it 標簽以交互的方式啟動容器
。--rm 標簽在容器關閉后將會將其清除
。--name ma-instance 將容器命名為ma-instance
。-p 8080:8080 標簽允許通過8080端口訪問該容器
。-v /app/MathApp:/go/src/MathApp更復雜一些。它將主機的/app/MathApp映射到容器中的/go/src/MathApp。這將使得開發文件在容器的內部和外部都可以訪問。
。ma-image 部分聲明了用于容器的image。
執行以上的命令將啟動Docker容器。該容器為你的應用程序開發了8080端口。無論何時你做了變更,它都將自動地重構你的應用程序。你將在console(控制臺)上看到以下的輸出:
?
?
bee :1.4.1 beego :1.6.1 Go :go version go1.6 linux/amd64 2016/04/10 1315 [INFO] Uses 'MathApp' as 'appname' 2016/04/10 1315 [INFO] Initializing watcher... 2016/04/10 1315 [TRAC] Directory(/go/src/MathApp) 2016/04/10 1315 [INFO] Start building... 2016/04/10 1318 [SUCC] Build was successful 2016/04/10 1318 [INFO] Restarting MathApp ... 2016/04/10 1318 [INFO] ./MathApp is running... 2016/04/10 1318 [asm_amd64.s:1998][I] http server Running on :8080
?
?
為了檢查相關的設置,可以在瀏覽器中訪問 http://localhost:8080/sum/4/5 。你講看到與下面類似的東西:
注意:這里假定你是在使用本地主機
步驟四 - 開發應用程序
現在,讓我們看看這將如何在開發階段提供幫助。在完成以下的操作時請確保容器在運行。在## main.go ##文件中,將第34行:
?
?
c.Data["operation"] = operation
?
?
改成:
?
?
c.Data["operation"] = "real " + operation
?
?
在你保存修改的一刻,你講看到類似以下的輸出:
?
?
2016/04/10 1351 [EVEN] "/go/src/MathApp/main.go": MODIFY 2016/04/10 1351 [SKIP] "/go/src/MathApp/main.go": MODIFY 2016/04/10 1352 [INFO] Start building... 2016/04/10 1356 [SUCC] Build was successful 2016/04/10 1356 [INFO] Restarting MathApp ... 2016/04/10 1356 [INFO] ./MathApp is running... 2016/04/10 1356 [asm_amd64.s:1998][I] http server Running on :8080
?
?
為了檢查該變更,在你的瀏覽器中訪問 http://localhost:8080/sum/4/5 。你將看到類似下面的輸出:
如你所見,你的應用程序在保存了修改之后自動地編譯并提供了服務。
在生產中使用Docker
本節將講解如何在一個Docker容器中部署Go應用程序。我們將使用Semaphore來完成以下的工作:
。當一個變更被推送到git資料庫后自動地進行編譯
。自動地運行測試
。如果編譯成功并且通過測試就創建一個Docker映像
。將Docker映像文件推送入Docker Hub
。更新服務器以便使用最新的Docker映像
創建一個生產用的Dockerfile
在開發過程中,我們的目錄有如下的結構:
?
?
MathApp ├── conf │ └── app.conf ├── main.go ├── main_test.go └── views ├── invalid-route.html └── result.html
?
?
由于我們想要從項目中構建Docker映像,我們需要創建一個將用于生產環境的Dockerfile。在項目的根目錄中創建一個Dockerfile。新的目錄結構如下所示:
?
?
MathApp ├── conf │ └── app.conf ├── Dockerfile ├── main.go ├── main_test.go └── views ├── invalid-route.html └── result.html
?
?
在Dockerfile文件中輸入以下的內容:
?
?
FROM golang:1.6
?
?
Create the directory where the application will reside
RUN?mkdir?/app
Copy the application files (needed for production)
ADD?MathApp?/app/MathApp
ADD?views?/app/views
ADD?conf?/app/conf
Set the working directory to the app directory
WORKDIR?/app
Expose the application on port 8080.
This should be the same as in the app.conf file
EXPOSE?8080
Set the entry point of the container to the application executable
ENTRYPOINT?/app/MathApp
讓我們具體看一下這些命令都做了什么。第一個命令,
?
?
FROM golang:1.6
?
?
表明將基于我們在開發中使用的golang:1.6映像構建新的映像文件。第二個命令:
?
?
RUN mkdir /app
?
?
在容器的根里創建一個名為app的目錄,我們用其來保存項目文件。第三個命令集:
?
?
ADD MathApp /app/MathApp ADD views /app/views ADD conf /app/conf
?
?
從主機中拷貝二進制、視圖文件夾及配置文件夾到映像文件中的應用程序目錄。第四個命令:
?
?
WORKDIR /app
?
?
在映像文件中把/app設置為工作目錄。第五個命令:
?
?
EXPOSE 8080
?
?
在容器中開放8080端口。該端口應該與應用程序的 app.conf 文件中聲明的端口一致。最后的命令:
?
?
ENTRYPOINT /app/MathApp
?
?
將映像文件的入口設置為應用程序的二進制文件。這將啟動二進制文件的執行并監聽8080端口。
自動地編譯及測試
一旦你把代碼上傳到你的資料庫中Semaphore將自動地對代碼進行編譯和測試,一切都變得簡單了。點擊這里了解如何添加你的 Github 或 Bitbucket 項目并且在Semaphore上設置Golang項目。
一個Go項目的缺省配置文件關注以下幾點:
。獲取相關文件
。編譯項目
。運行測試
一旦你完成這個過程,就可以在Semaphore儀表盤上看到最近的編譯和測試狀態。如果編譯或測試失敗,該過程會終止而且也不會部署任何內容。
在Semaphore上創建Initial Setup來實現自動部署
一旦你配置好了編譯過程,下一步就是配置部署過程。為了部署應用程序,你需要:
1. 創建Docker image
2. 將Docker image推送入Docker Hub
3. 拉取新的image來更新服務器并基于該image啟動一個新的Docker容器
作為開始,我們需要在semaphore上配置項目實現持續部署。
前三個步驟相對簡單:
。選擇部署模式
。選擇部署策略
。選擇在部署過程中使用的資料庫分支
第四步(設置部署命令),我們將使用下一節中的命令。當前暫且空著并轉到下一步。
在第五步中,輸入你的服務器中用戶的SSH私鑰。這將使得一些部署命令可以在你的服務器上安全執行而不需要輸入口令。
在第六部中,你可以命名你的服務器。如果你做的話,Semaphore會給該服務器指定一個類似server-1234這樣的隨機名字。
在服務器上設置更新腳本
之后,我們將配置部署過程,Semaphore將創建新的image冰將其上傳到Docker Hub中。一旦完成,一個Semaphore的命令將執行你的服務器上的腳本來初始化更新過程。
為了完成這個工作,我們需要將名為 update.sh 的文件放置到你的服務器中。
?
?
#!/bin/bash docker pull $1/ma-prod:latest if docker stop ma-app; then docker rm ma-app; fi docker run -d -p 8080:8080 --name ma-app $1/ma-prod if docker rmi $(docker images --filter "dangling=true" -q --no-trunc); then :; fi
?
?
使用如下的命令給該文件賦予執行權限:
?
?
chmod +x update.sh
?
?
讓我們來看一下該文件是如何使用的。這個腳本接收一個參數并且在命令中使用該參數。這個參數應該是你在Docker Hub上的用戶名。下面是使用該命令的例子:
?
?
./update.sh docker_hub_username
?
?
現在讓我們看一下這個文件中的每一個命令來理解他們要做什么。
第一個命令,
?
?
docker pull $1/ma-prod:latest
?
?
從Docker Hub上拉取最新的image到服務器中。如果你在Docker Hub上的用戶名是 demo_user ,該命令將拉取Docker Hub上標記為 latest 、名為 demo_user/ma-prod 的image。
第二個命令:
?
?
if docker stop ma-app; then docker rm ma-app; fi
?
?
停止并刪除之前任何以 ma-app 為名字而啟動的容器。
第三個命令:
?
?
docker run -d -p 8080:8080 --name ma-app $1/ma-prod
?
?
使用在最近一次編譯中包涵變更的最新image來啟動一個新的容器。
最后的命令:
?
?
docker rmi $(docker images --filter "dangling=true" -q --no-trunc)
?
?
從服務器上刪除任何沒有用的image。這種清理將保持服務器整潔并降低磁盤空間的占用。
注意:這個文件必須存放在用戶主目錄中,而該用戶就是之前的步驟中所用到的SSH密鑰的所有者。如果文件的位置發生了變化,則需要在后面的章節中相應地更新部署命令。
配置項目使其能夠支持Docker
缺省情況下,Semaphore上的新項目使用 Ubuntu 14.04 LTS v1603 平臺。該平臺并不支持Docker。由于我們希望使用Docker,我們需要修改Semaphore的配置來使用 Ubuntu 14.04 LTS v1603(beta with Docker support) 平臺。
設置環境變量
為了在部署過程中安全使用Docker Hub,我們需要把我們的證書保存在Semaphore自動初始化的環境變量中。
我們將保存以下的變量:
。DH_USERNAME - Docker Hub用戶名
。DH_PASSWORD - Docker Hub口令
。DH_EMAIL - Docker Hub email地址
這里是如何以安全的方式設置環境變量。
設置部署命令
雖然我們完成了初始配置,但是實際上什么也不會部署。原因是我們在命令環節中都還是空白。
在第一步,我們將輸入用于完成部署過程的命令。為了完成這一步,請進入Semaphore中你的項目主頁。
在這一頁上,點擊 Server 欄中服務器的名字。這將帶你進入:
點擊位于頁頭下方頁面右側的 Edit server 按鈕。
在隨后的一頁中,我們需要關注標題為 Deploy commands 的最后一欄。點擊 Change deploy commands 鏈接來開啟命令編輯。
在編輯框中,輸入如下的命令并點擊 Save Deploy Commands 按鈕:
?
?
go get -v -d ./ go build -v -o MathApp docker login -u $DH_USERNAME -p $DH_PASSWORD -e $DH_EMAIL docker build -t ma-prod . docker tag ma-prod:latest $DH_USERNAME/ma-prod:latest docker push $DH_USERNAME/ma-prod:latest ssh -oStrictHostKeyChecking=no your_server_username@your_ip_address "~/update.sh $DH_USERNAME"
?
?
注意:請確定用正確的值替換 your_server_username@your_ip_address 。
讓我們具體看一下每個命令。
前兩個命令 go get和go build是Go的標準命令用于獲取相關文件并相應地編譯項目。注意go build命令說聲明的二進制文件名應該是MathApp。這個名字應該與Dockerfile中使用的名字相同。
第三個命令,
?
?
docker login -u $DH_USERNAME -p $DH_PASSWORD -e $DH_EMAIL
?
?
使用環境變量實現在Docker Hub上的認證,從而使得我們能夠推送最新的Image。第四個命令,
?
?
docker build -t ma-prod .
?
?
基于最新的代碼庫萊創建Docker image。第五個命令,
?
?
docker tag ma-prod:latest $DH_USERNAME/ma-prod:latest
?
?
將新生成的image標記為 your_docker_hub_username/ma-prod:latest 。完成這一步后,我們就可以把image推送到Docker Hub上相應的資料庫中。第六個命令,
?
?
docker push $DH_USERNAME/ma-prod:latest
?
?
將該image推送到Docker Hub中。最后一個命令,
?
?
ssh -oStrictHostKeyChecking=no your_server_username@your_ip_address "~/update.sh $DH_USERNAME"
?
?
使用ssh命令登陸到你的服務器上并執行我們之前創建的 update.sh 腳本。這個腳本從Docker Hub上獲取最新的image并在其上啟動一個新的容器。
部署應用程序
由于我們目前還沒有真正地在我們的服務器上部署應用程序,那么我們就手工操作一遍。注意你大可不必這么做。以后你提交任何變更到你的資料庫后,如果編譯和測試都成功的話Semaphore會自動部署你的應用程序。我們現在手工部署它知識測試是否一切都能工作正常。
你可以在semaphore 文檔的編譯頁中找到如何手工地部署一個應用程序。
一旦你已經部署了應用程序,就可以訪問:
?
?
http://your_ip_address:8080/sum/4/5
?
?
這將顯示與以下類似的結果:
這與我們在開發過程中看到的應該是一致的。唯一的不同在于localhost被替換掉,在URL中你應使用服務器的IP地址。
對配置進行測試
現在我們已經擁有了自動編譯和部署路程配置,我們將看到它是如何簡化工作流的。讓我們做一個小修改然后看看服務器上的應用程序如何自動地響應。
讓我們嘗試吧文本的顏色從黑色改為紅色。為了這個結果,在 views/result.html 文件中把第8行從
?
?
?
?
改成
?
?
?
?
現在,保存文件。在你的應用程序目錄中,使用如下的命令提交變更:
?
?
git add views/result.html git commit -m 'Change the color of text from black (default) to red'
?
?
使用如下的命令將變更推送到你的資料庫中:
?
?
git push origin master
?
?
一旦git push命令完成,Semaphore將監測到你的資料庫中的變化并且自動地啟動編譯過程。只要編譯過程(包括測試)成功完成,Semaphore將啟動部署過程。Semaphore的儀表盤上會實時地顯示編譯和部署的過程。
一旦Semaphore的儀表盤上顯示編譯和部署過程都以完成,刷新頁面:
?
?
http://your_ip_address:8080/sum/4/5
?
?
你現在應該看到類似以下的內容:
總結
在本教程中,我們學習了如何為一個Go應用程序創建Docker容器并且使用Semaphore將容器部署到服務器上。
你現在應該能夠使用Docker來簡化以后的Go應用程序的部署任務。
審核編輯:黃飛
?
評論
查看更多