閱讀480 返回首頁    go 阿裏雲 go 技術社區[雲棲]


如何使用Docker部署一個Go Web應用程序

本文講的是如何使用Docker部署一個Go Web應用程序【編者的話】這是國外輕量級CJ廠商Semaphore發布的教程,旨在讓開發人員如何借助於Docker讓Go應用程序的編譯及發動實現自動化,提升開發效率的同時也能保證軟件部署的可靠性。

熟悉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.htmlresult.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/:num1:int/:num2: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的內容如下:
<!-- result.html -->
<!-- This file is used to display the result of calculations -->
<!doctype html>
<html>

<head>
    <title>MathApp - {{.operation}}</title>
</head>

<body>
    The {{.operation}} of {{.num1}} and {{.num2}} is {{.result}}
</body>

</html>

invalid-route.html的內容如下:

<!-- invalid-route.html -->
<!-- This file is used when an invalid operation is specified in the route -->
<!doctype html>
<html>

<head>
    <title>MathApp</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="UTF-8">
</head>

<body>
    Invalid operation
</body>

</html>

配置文件的內容

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的確切名字和編號可能不同,但是,你應該至少看到列表中有golangma-imageimage。

步驟三——運行容器

一旦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 13:04:15 [INFO] Uses 'MathApp' as 'appname'
2016/04/10 13:04:15 [INFO] Initializing watcher...
2016/04/10 13:04:15 [TRAC] Directory(/go/src/MathApp)
2016/04/10 13:04:15 [INFO] Start building...
2016/04/10 13:04:18 [SUCC] Build was successful
2016/04/10 13:04:18 [INFO] Restarting MathApp ...
2016/04/10 13:04:18 [INFO] ./MathApp is running...
2016/04/10 13:04:18 [asm_amd64.s:1998][I] http server Running on :8080

為了檢查相關的設置,可以在瀏覽器中訪問https://localhost:8080/sum/4/5。你講看到與下麵類似的東西:
Application_-_Production_Version_1.png

注意:這裏假定你是在使用本地主機

步驟四——開發應用程序

現在,讓我們看看這將如何在開發階段提供幫助。在完成以下的操作時請確保容器在運行。在## main.go ##文件中,將第34行:
c.Data["operation"] = operation

改成:
c.Data["operation"] =  "real " + operation

在你保存修改的一刻,你講看到類似以下的輸出:
2016/04/10 13:17:51 [EVEN] "/go/src/MathApp/main.go": MODIFY
2016/04/10 13:17:51 [SKIP] "/go/src/MathApp/main.go": MODIFY
2016/04/10 13:17:52 [INFO] Start building...
2016/04/10 13:17:56 [SUCC] Build was successful
2016/04/10 13:17:56 [INFO] Restarting MathApp ...
2016/04/10 13:17:56 [INFO] ./MathApp is running...
2016/04/10 13:17:56 [asm_amd64.s:1998][I] http server Running on :8080

為了檢查該變更,在你的瀏覽器中訪問https://localhost:8080/sum/4/5。你將看到類似下麵的輸出:
Application_-_Production_Version_2.png

如你所見,你的應用程序在保存了修改之後自動地編譯並提供了服務。

在生產中使用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將自動地對代碼進行編譯和測試,一切都變得簡單了。點擊這裏了解如何添加你的GithubBitbucket項目並且在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中你的項目主頁。
Project_Homepage.png

在這一頁上,點擊Server欄中服務器的名字。這將帶你進入:
Server_details_page.png

點擊位於頁頭下方頁麵右側的Edit server按鈕。
Server_edit_page.png

在隨後的一頁中,我們需要關注標題為Deploy commands的最後一欄。點擊Change deploy commands鏈接來開啟命令編輯。
Edit_the_deploy_commands.png

在編輯框中,輸入如下的命令並點擊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 文檔的編譯頁中找到如何手工地部署一個應用程序。

一旦你已經部署了應用程序,就可以訪問:
https://your_ip_address:8080/sum/4/5

這將顯示與以下類似的結果:
Application_-_Production_Version_1.png

這與我們在開發過程中看到的應該是一致的。唯一的不同在於localhost被替換掉,在URL中你應使用服務器的IP地址。

對配置進行測試

現在我們已經擁有了自動編譯和部署路程配置,我們將看到它是如何簡化工作流的。讓我們做一個小修改然後看看服務器上的應用程序如何自動地響應。

讓我們嚐試吧文本的顏色從黑色改為紅色。為了這個結果,在views/result.html文件中把第8行從
<body>

改成
<body >

現在,保存文件。在你的應用程序目錄中,使用如下的命令提交變更:
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的儀表盤上顯示編譯和部署過程都以完成,刷新頁麵:
https://your_ip_address:8080/sum/4/5

你現在應該看到類似以下的內容:
Application_-_Production_Version_2.png

總結

在本教程中,我們學習了如何為一個Go應用程序創建Docker容器並且使用Semaphore將容器部署到服務器上。

你現在應該能夠使用Docker來簡化以後的Go應用程序的部署任務。如果你有任何問題,請隨時在下麵的備注中發帖。

原文鏈接:How To Deploy a Go Web Application with Docker(翻譯:李毅)

=====================================================================
譯者介紹
李毅,中國惠普大學資深培訓專家。

原文發布時間為:2016-05-06
本文作者:Leo_li 
本文來自雲棲社區合作夥伴DockerOne,了解相關信息可以關注DockerOne。
原文標題:如何使用Docker部署一個Go Web應用程序

最後更新:2017-10-19 14:03:26

  上一篇:go  DockOne微信分享(六十七):互聯網場景下閃存優化測試和應用
  下一篇:go  最新阿裏雲服務器優惠活動匯總