[关闭]
@liuhui0803 2016-04-29T10:17:06.000000Z 字数 12894 阅读 4136

如何使用Docker部署Go Web应用程序

Docker Go


摘要:

了解Docker如何改善您构建、测试,和部署Go Web应用程序的方法,以及如何使用Semaphore实现持续部署。

正文:

简介

虽然大部分Go应用程序可以编译为一个单一的二进制文件,但Web应用程序可能还有自己的模板和配置文件。如果一个项目中包含大量文件,可能会因为文件不同步而导致出错并造成更多严重问题。

您将通过本文了解如何使用Docker部署Go Web应用程序,以及Docker如何帮您改善开发工作流和部署过程。各种规模的团队都能从本文内容中获益。

目标

通过阅读本文,您将能:

前提要求

为了完成本文,您需要:

理解Docker

Docker可以帮您为自己的应用程序创建一个单一的可部署“单位”。这样的单位也叫做容器,其中包含了应用程序需要的一切。例如代码(或二进制文件)、运行时、系统工具,以及系统库文件。将所有这些需要的内容打包为一个单一的单位,可确保无论将应用程序部署在何处,都能提供完全相同的环境。这种技术还可以帮您维持完全一致的开发和生产环境,通常这些环境是很难被追踪的。

一旦搭建完成,容器的创建和部署将可自动进行。这本身就可以避免一系列问题。这些问题中大部分都是因为文件不同步,或开发和生产环境之间的差异导致的。Docker可以解决这些问题。

相对于虚拟机的优势

容器提供了与虚拟机类似的资源分配和隔离等好处。然而相似之处仅此而已。

虚拟机需要自己的来宾操作系统,容器则能与宿主操作系统共享内核。这意味着容器更轻,需要的资源更少。虚拟机从本质上来说,实际上就是一个操作系统内部运行的另一个操作系统。然而容器更像是操作系统内部运行的一个应用程序。通常容器需要的资源(内存、磁盘空间等)远低于虚拟机,同时启动速度也比虚拟机快很多。

在开发过程中使用Docker所获得的收益

在开发工作中使用Docker可以获得的部分收益包括:

为何通过Docker使用Go Web应用程序?

大部分Go应用程序都是简单的二进制文件。这就引出了另一个问题 - 为何通过Docker使用Go应用程序?通过Docker使用Go的部分原因包括:

创建一个简单的Go Web应用程序

我们将使用Go创建一个简单的Web应用程序作为本文的范例。这个我们称之为MathApp的应用程序可以:

访问/sum/3/6会打开一个显示了36相加后结果的页面。同理,访问/product/3/6会打开一个显示了36相乘后结果的页面。

本文中我们使用了Beego框架。请注意,您自己的应用程序可以使用任何框架(或者完全不使用)。

最终的目录结构

完成后的MathApp其目录结构应该是类似这样的:

  1. MathApp
  2. ├── conf
  3. └── app.conf
  4. ├── main.go
  5. ├── main_test.go
  6. └── views
  7. ├── invalid-route.html
  8. └── result.html

我们会假设MathApp目录位于/app目录下。

程序主文件是位于应用程序根目录的main.go,这个文件中包含了应用的所有功能。main.go的部分功能可使用main_test.go进行测试。

views文件夹包含视图文件invalid-route.htmlresult.html。配置文件app.conf位于conf文件夹中。Beego可使用该文件对应用程序进行定制。

应用程序文件的内容

应用程序主文件(main.go)包含应用程序的所有逻辑。该文件的内容如下:

  1. // main.go
  2. package main
  3. import (
  4. "strconv"
  5. "github.com/astaxie/beego"
  6. )
  7. // The main function defines a single route, its handler
  8. // and starts listening on port 8080 (default port for Beego)
  9. func main() {
  10. /* This would match routes like the following:
  11. /sum/3/5
  12. /product/6/23
  13. ...
  14. */
  15. beego.Router("/:operation/:num1:int/:num2:int", &mainController{})
  16. beego.Run()
  17. }
  18. // This is the controller that this application uses
  19. type mainController struct {
  20. beego.Controller
  21. }
  22. // Get() handles all requests to the route defined above
  23. func (c *mainController) Get() {
  24. //Obtain the values of the route parameters defined in the route above
  25. operation := c.Ctx.Input.Param(":operation")
  26. num1, _ := strconv.Atoi(c.Ctx.Input.Param(":num1"))
  27. num2, _ := strconv.Atoi(c.Ctx.Input.Param(":num2"))
  28. //Set the values for use in the template
  29. c.Data["operation"] = operation
  30. c.Data["num1"] = num1
  31. c.Data["num2"] = num2
  32. c.TplName = "result.html"
  33. // Perform the calculation depending on the 'operation' route parameter
  34. switch operation {
  35. case "sum":
  36. c.Data["result"] = add(num1, num2)
  37. case "product":
  38. c.Data["result"] = multiply(num1, num2)
  39. default:
  40. c.TplName = "invalid-route.html"
  41. }
  42. }
  43. func add(n1, n2 int) int {
  44. return n1 + n2
  45. }
  46. func multiply(n1, n2 int) int {
  47. return n1 * n2
  48. }

在您的应用程序中,这些内容可能分散保存在多个文件中。但是出于演示的用途,我们希望尽量确保内容足够简单。

测试文件的内容

main.go文件包含一些需要测试的功能。对这些功能的测试位于main_test.go,这个文件的内容如下:

  1. // main_test.go
  2. package main
  3. import "testing"
  4. func TestSum(t *testing.T) {
  5. if add(2, 5) != 7 {
  6. t.Fail()
  7. }
  8. if add(2, 100) != 102 {
  9. t.Fail()
  10. }
  11. if add(222, 100) != 322 {
  12. t.Fail()
  13. }
  14. }
  15. func TestProduct(t *testing.T) {
  16. if multiply(2, 5) != 10 {
  17. t.Fail()
  18. }
  19. if multiply(2, 100) != 200 {
  20. t.Fail()
  21. }
  22. if multiply(222, 3) != 666 {
  23. t.Fail()
  24. }
  25. }

如果希望进行持续部署,应用程序的测试就显得尤为有用。如果您已经准备好相应的测试,即可在无需担心为应用程序引入错误的情况下进行持续部署。

视图文件的内容

视图文件其实是HTML模板。应用程序可以使用这些文件显示对请求做出的回应。result.html的内容如下:

  1. <!-- result.html -->
  2. <!-- This file is used to display the result of calculations -->
  3. <!doctype html>
  4. <html>
  5. <head>
  6. <title>MathApp - {{.operation}}</title>
  7. </head>
  8. <body>
  9. The {{.operation}} of {{.num1}} and {{.num2}} is {{.result}}
  10. </body>
  11. </html>

invalid-route.html的内容如下:

  1. <!-- invalid-route.html -->
  2. <!-- This file is used when an invalid operation is specified in the route -->
  3. <!doctype html>
  4. <html>
  5. <head>
  6. <title>MathApp</title>
  7. <meta name="viewport" content="width=device-width, initial-scale=1">
  8. <meta charset="UTF-8">
  9. </head>
  10. <body>
  11. Invalid operation
  12. </body>
  13. </html>

配置文件的内容

Beego可以使用app.conf文件配置应用程序,该文件的内容如下:

  1. ; app.conf
  2. appname = MathApp
  3. httpport = 8080
  4. runmode = dev

在这个文件中,

在开发过程中使用Docker

本节将介绍在开发过程中使用Docker所能获得的收益,并会指导您完成在开发过程中使用Docker的步骤。

针对开发工作配置Docker

我们会使用一个Dockerfile配置开发工作所用的Docker。配置工作需要满足的开发环境要求如下:

第1步 - 创建Dockerfile

满足上述要求的Dockerfile内容如下:

  1. FROM golang:1.6
  2. # Install beego and the bee dev tool
  3. RUN go get github.com/astaxie/beego && go get github.com/beego/bee
  4. # Expose the application on port 8080
  5. EXPOSE 8080
  6. # Set the entry point of the container to the bee command that runs the
  7. # application and watches for changes
  8. CMD ["bee", "run"]

第一行,

  1. FROM golang:1.6

使用Go的官方映像作为基础映像。这个映像是Go 1.6预安装的。该映像的$GOPATH值已被设置为/go。所有安装在/go/src的程序包都能通过go命令访问。

第二行,

  1. RUN go get github.com/astaxie/beego && go get github.com/beego/bee

安装beego程序包和bee工具。beego程序包将在应用程序内部使用,bee工具将用于在开发过程中实时重载代码。

第三行,

  1. EXPOSE 8080

通过开发计算机上容器的8080端口暴露该应用程序。最后一行,

  1. CMD ["bee", "run"]

使用bee命令开始对我们的应用程序进行实时重载。

第2步 - 构建映像

创建好Docker文件之后,可运行下列命令创建映像:

  1. docker build -t ma-image .

执行上述命令可创建一个名为ma-image的映像。随后所有负责开发这个应用程序的人都可以使用这个映像。这样即可确保整个团队获得完全一致的开发环境。

要查看您系统中的映像列表,请运行下列命令:

  1. docker images

执行该命令可以看到类似下面的内容:

  1. REPOSITORY TAG IMAGE ID CREATED SIZE
  2. ma-image latest 8d53aa0dd0cb 31 seconds ago 784.7 MB
  3. golang 1.6 22a6ecf1f7cc 5 days ago 743.9 MB

请注意,实际的映像名称和数量可能各不相同,不过您至少应该可以在列表中看到golangma-image映像。

第3步 - 运行容器

准备好ma-image之后,即可使用下列命令启动一个容器:

  1. docker run -it --rm --name ma-instance -p 8080:8080 \
  2. -v /app/MathApp:/go/src/MathApp -w /go/src/MathApp ma-image

分别来看看上述命令的作用。

执行上述命令可启动Docker容器。这个容器可以将您的应用程序暴露到8080端口,还可以在您修改代码后自动重新构建您的应用程序。您将在控制台中看到下列输出结果:

  1. bee :1.4.1
  2. beego :1.6.1
  3. Go :go version go1.6 linux/amd64
  4. 2016/04/10 13:04:15 [INFO] Uses 'MathApp' as 'appname'
  5. 2016/04/10 13:04:15 [INFO] Initializing watcher...
  6. 2016/04/10 13:04:15 [TRAC] Directory(/go/src/MathApp)
  7. 2016/04/10 13:04:15 [INFO] Start building...
  8. 2016/04/10 13:04:18 [SUCC] Build was successful
  9. 2016/04/10 13:04:18 [INFO] Restarting MathApp ...
  10. 2016/04/10 13:04:18 [INFO] ./MathApp is running...
  11. 2016/04/10 13:04:18 [asm_amd64.s:1998][I] http server Running on :8080

若要检查整个环境,请通过浏览器访问http://localhost:8080/sum/4/5。您应该能看到类似下图的结果:

应用程序 - 开发版本1

注意:上述步骤假设您在自己的本地计算机上执行操作。

第4步 - 开发应用程序

随后一起来看看这种做法能对开发工作起到什么帮助。执行下文操作的过程中请确保容器始终处于运行状态。在main.go文件中,将第#34行从

  1. c.Data["operation"] = operation

修改为

  1. c.Data["operation"] = "real " + operation

保存改动的同时,您应该能看到类似下面的内容:

  1. 2016/04/10 13:17:51 [EVEN] "/go/src/MathApp/main.go": MODIFY
  2. 2016/04/10 13:17:51 [SKIP] "/go/src/MathApp/main.go": MODIFY
  3. 2016/04/10 13:17:52 [INFO] Start building...
  4. 2016/04/10 13:17:56 [SUCC] Build was successful
  5. 2016/04/10 13:17:56 [INFO] Restarting MathApp ...
  6. 2016/04/10 13:17:56 [INFO] ./MathApp is running...
  7. 2016/04/10 13:17:56 [asm_amd64.s:1998][I] http server Running on :8080

要查看这些改动,请用浏览器访问http://localhost:8080/sum/4/5。您应该能看到类似下图的结果:

应用程序 - 开发版本2

如您所见,保存改动后,您的应用程序可以自动构建并开始提供服务。

在生产环境中使用Docker

本节将介绍如何在Docker容器中部署Go应用程序。我们将使用Semaphore实现下列目标:

为生产环境创建Dockerfile

在开发过程中,我们的目录结构如下:

  1. MathApp
  2. ├── conf
  3. └── app.conf
  4. ├── main.go
  5. ├── main_test.go
  6. └── views
  7. ├── invalid-route.html
  8. └── result.html

由于我们希望为这个项目构建Docker映像,因此需要创建一个在生产环境中使用的Dockerfile。请在项目的根目录下创建Dockerfile,随后新的目录结构应该是类似下面这样的:

  1. MathApp
  2. ├── conf
  3. └── app.conf
  4. ├── Dockerfile
  5. ├── main.go
  6. ├── main_test.go
  7. └── views
  8. ├── invalid-route.html
  9. └── result.html

在Dockerfile中输入下列内容:

  1. FROM golang:1.6
  2. # Create the directory where the application will reside
  3. RUN mkdir /app
  4. # Copy the application files (needed for production)
  5. ADD MathApp /app/MathApp
  6. ADD views /app/views
  7. ADD conf /app/conf
  8. # Set the working directory to the app directory
  9. WORKDIR /app
  10. # Expose the application on port 8080.
  11. # This should be the same as in the app.conf file
  12. EXPOSE 8080
  13. # Set the entry point of the container to the application executable
  14. ENTRYPOINT /app/MathApp

详细看看上述每条命令的作用。第一条命令,

  1. FROM golang:1.6

指定了在开发过程中所用的同一个golang:1.6映像基础之上构建映像。第二条命令,

  1. RUN mkdir /app

可以在容器的根目录下创建名为app的目录。这个目录将用于保存项目文件。第三组命令,

  1. ADD MathApp /app/MathApp
  2. ADD views /app/views
  3. ADD conf /app/conf

可以将库、视图文件夹,以及配置文件夹从计算机复制到映像内的应用程序文件夹中。第四条命令,

  1. WORKDIR /app

可将映像的工作目录设置为/app。第五条命令,

  1. EXPOSE 8080

可以从容器中暴露8080端口。这个端口应该与应用程序的app.conf文件中指定的端口完全一致。最后一条命令,

  1. ENTRYPOINT /app/MathApp

可将映像的入口点(Entry point)设置为应用程序的二进制文件。这样即可启动该二进制文件,并开始在8080端口提供服务。

自动构建和测试

一旦将改动的代码推送至代码库,Semaphore就可以帮您轻松地自动构建并测试代码。这里介绍了如何添加您的GitHub或Bitbucket项目,以及在Semaphore上设置Golang项目的方法。

Go项目的默认配置可解决下列问题:

完成上述过程后,便可通过Semaphore仪表板查看最新构建的状态并对其进行测试。如果构建或测试失败,整个过程将暂停,且不部署任何内容。

通过Semaphore为自动化开发创建初始环境

设置好构建过程后,下一步需要配置部署过程。若要部署应用程序,您需要:

  1. 构建Docker映像,
  2. 将Docker映像推送至Docker Hub,并
  3. 更新服务器以拉取新的映像,随后据此启动一个新的Docker容器。

首先我们需要在Semaphore上设置项目以进行持续部署

前三个步骤相对较为简单:

第4步(设置部署命令)我们将使用下一节介绍的命令。目前可以留空并执行下一个步骤。

第5步,输入服务器上用户的SSH密钥。这样即可在无需密码的情况下,以安全的方式在服务器上执行某些部署命令。

第6步,可以为服务器命名。如果不指定名称,Semaphore会为服务器分配类似server-1234这样的随机名称。

在服务器上设置更新脚本

接下来需要设置部署过程,这样Semaphore即可构建新映像并将其上传至Docker Hub。设置完成后,Semaphore将通过一条命令在服务器上执行脚本,发起更新过程。

为此我们需要将下列文件以update.sh为名放置在服务器上。

  1. #!/bin/bash
  2. docker pull $1/ma-prod:latest
  3. if docker stop ma-app; then docker rm ma-app; fi
  4. docker run -d -p 8080:8080 --name ma-app $1/ma-prod
  5. docker rmi $(docker images --filter "dangling=true" -q --no-trunc)

使用下列命令使得该文件可以执行:

  1. chmod +x update.sh

随后来看看这个文件是如何生效的。这个脚本可以接受单一参数,并在自己的命令中使用这个参数。这个参数可以是您在Docker Hub上的用户名。例如可以通过下面这样的格式使用该命令:

  1. ./update.sh docker_hub_username

为了理解具体的用途,随后再来看看文件中的每条命令。

第一条命令,

  1. docker pull $1/ma-prod:latest

将最新映像从Docker Hub拉取到服务器上。如果您在Docker Hub上的用户名是demo_user,这条命令将从Docker Hub拉取名为demo_user/ma-prod,且被标记为latest的映像。

第二条命令,

  1. if docker stop ma-app; then docker rm ma-app; fi

可以停止并移除任何曾以ma-app为名启动的容器。

第三条命令,

  1. docker run -d -p 8080:8080 --name ma-app $1/ma-prod

使用能够反映最新构建中所有变动的最新映像启动一个新的容器(名为ma-app)。

最后一条命令,

  1. docker rmi $(docker images --filter "dangling=true" -q --no-trunc)

可从服务器上删除任何未使用的映像。这个清理工作可以确保服务器整洁,并能降低磁盘空间的使用。

注意:这个文件必须放置在上一步骤中所用SSH密钥对应用户的根目录下。如果文件位置有变化,下文使用的部署命令也需要酌情更新。

设置项目配合Docker工作

默认情况下,Semaphore上的新项目会使用Ubuntu 14.04 LTS v1603平台。这个平台并非Docker自带的。因为我们感兴趣的是Docker的使用,因此需要更改Semaphore的平台设置以使用Ubuntu 14.04 LTS v1603(支持Docker的Beta测试版)平台。

设置环境变量

为了在开发过程中以安全的方式使用Docker Hub,我们需要将自己的凭据存储在Semaphore自动初始化出来的环境变量内。

我们需要存储下列变量:

可以参考这里了解如何用安全的方式设置环境变量

设置部署命令

虽然已经完成初始设置,但目前还无法进行部署。原因在于还没有配置需要运行的命令。

首先我们需要输入完成部署过程所需的命令,为此请在Semaphore上打开您的项目首页。

项目首页

在这个页面上,点击Servers选项下的服务器名称,随后可以看到下列界面:

服务器详情页面

点击页面右侧,页头下方的Edit server按钮。

服务器编辑页面

在下列页面上,我们需要注意底部名为Deploy commands的选项。点击其中的Change deploy commands链接,开始编辑要运行的命令。

编辑部署命令

在编辑框中输入下列命令,并点击Save Deploy Commands按钮:

  1. go get -v -d ./
  2. go build -v -o MathApp
  3. docker login -u $DH_USERNAME -p $DH_PASSWORD -e $DH_EMAIL
  4. docker build -t ma-prod .
  5. docker tag ma-prod:latest $DH_USERNAME/ma-prod:latest
  6. docker push $DH_USERNAME/ma-prod:latest
  7. ssh -oStrictHostKeyChecking=no your_server_username@your_ip_address "~/update.sh $DH_USERNAME"

注意:请将上述命令中your_server_username@your_ip_address内容替换为您的实际值。

随后一起来仔细看看上述这些命令的用途。

前两条命令go getgo build是标准的Go命令,分别用于拉取依存项和构建项目。请注意go build命令指定的可执行文件的名称应该是MathApp。这个名称应该与Dockerfile中所用名称一致。

第三条命令,

  1. docker login -u $DH_USERNAME -p $DH_PASSWORD -e $DH_EMAIL

使用(上文操作中设置的)环境变量与Docker Hub进行身份验证,这样我们才可以推送最新映像。第四条命令,

  1. docker build -t ma-prod .

根据最新代码基构建一个名为ma-prod的Docker映像。第五条命令,

  1. docker tag ma-prod:latest $DH_USERNAME/ma-prod:latest

为最新创建的映像添加your_docker_hub_username/ma-prod:latest标签。这样我们就可以将该映像推送至Docker Hub上相应的代码库中。第六条命令,

  1. docker push $DH_USERNAME/ma-prod:latest

可将该映像推送至Docker Hub。最后一条命令,

  1. ssh -oStrictHostKeyChecking=no your_server_username@your_ip_address "~/update.sh $DH_USERNAME"

使用ssh命令登录到您的服务器,执行我们在上文步骤中创建的update.sh脚本。这个脚本可以从Docker Hub获取最新映像,并据此启动一个新的容器。

部署应用程序

截至目前我们并未真正将应用程序部署到服务器,因此先来进行手工部署。请注意这一操作并非是必须的。当您下一次将改动的代码推送至代码库后,只要构建和测试都能成功完成,Semaphore将自动部署您的应用程序。我们这里进行手工部署只是为了测试一切都能正常工作。

您可以阅读 Semaphore文档了解如何在构建页面手工部署应用程序。

应用程序部署完毕后,可通过下列地址访问:

http://your_ip_address:8080/sum/4/5

访问结果应该类似下图所示:

应用程序 - 生产版本1

结果与开发过程中看到的完全相同。唯一的差异在于此处访问时使用的URL并不是localhost,而是服务器的IP地址。

对配置进行测试

至此已配置了自动构建和部署过程,我们的工作流也得以大幅简化。让我们对代码进行一些细小的改动,然后看看服务器上的应用程序是如何自动更新以反映这些改动的。

试试看将文字的颜色由黑色改为红色。为此请在views/result.html文件中,将第#8行由

  1. <body>

改为

  1. <body style="color: red">

随后保存文件,并在您的应用程序目录中使用下列命令提交改动:

  1. git add views/result.html
  2. git commit -m 'Change the color of text from black (default) to red'

使用下列命令将改动推送至代码库:

  1. git push origin master

git push命令成功执行后,Semaphore会检测到代码库中的改动,并自动开始构建过程。构建过程(包括测试过程)成功完成后,Semaphore将启动部署过程。Semaphore仪表板会实时显示构建和部署过程的状态。

一旦Semaphore仪表板显示构建和部署过程均已完成,请刷新下列页面:

http://your_ip_address:8080/sum/4/5

随后您将可以看到类似下图的结果:

应用程序 - 生产版本2

结论

在这篇文章中,我们了解到如何为Go应用程序创建Docker容器,并使用Semaphore将Docker容器部署至服务器。

现在您已经可以使用Docker简化下一个Go应用程序的开发工作。如果您有任何问题,欢迎发布到下方的评论区。

此处输入图片的描述

Kulshekhar Kabra

Kulshekhar是一位独立开发者,他的工作可以方便地接触到各种新技术,并将其运用到新项目中。只要工作之余有闲时间,他还很喜欢撰写技术文章并制作视频教程。

作者:Kulshekhar Kabra
阅读英文原文How To Deploy a Go Web Application with Docker

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注