发布于 2026-01-06 1 阅读
0

部署你的第一个 Golang Web 应用程序

部署你的第一个 Golang Web 应用程序

Go编程语言(通常简称为“golang”)在 DevOps 社区获得了实至名归的广泛关注。许多最流行的工具,例如DockerKubernetesTerraform,都是用 Go 编写的,但它也是构建 Web 应用程序和 API 的绝佳选择。

Go 语言兼具编译型语言的速度和性能,却拥有解释型语言般的编程体验。这得益于其强大的工具集,包括默认提供的编译器、运行器、测试套​​件和代码格式化工具。此外,Go 语言还拥有强大且易于理解的并发处理方式,能够最大限度地利用当今的多核或多 CPU 执行环境,因此 Go 社区发展如此迅速也就不足为奇了。

Go 语言似乎是在设定特定目标后才创建的,因此其语言设计看似简单,易于学习,而且功能强大。

在这篇文章中,我将向您展示如何轻松地使用 Go 开发一个简单的 Web 应用程序,将其打包成轻量级的 Docker 镜像,并部署到Heroku。我还会介绍 Go 的一个相对较新的特性:内置包管理。

Go 模块

本文将使用 Go 的内置模块支持功能。

Go 1.0 于 2012 年 3 月发布。直到 1.11 版本(2018 年 8 月发布)之前,开发 Go 应用程序需要为每个“工作区”管理一个 GOPATH,类似于 Java 的GOPATH JAVA_HOME,所有 Go 源代码和任何第三方库都存储在 GOPATH 下GOPATH

我一直觉得这种方式有点让人难以接受,相比之下,像 Ruby 或 Javascript 这样的语言就方便多了,我可以拥有更简洁的目录结构,将每个项目隔离开来。在 Ruby 和 Javascript 中,所有外部库都列在一个单独的文件中(GemfileRuby 是 .htm 文件,package.jsonJavascript 是 .htm 文件),包管理器会自动负责管理和安装依赖项。

我并不是说你不能通过管理GOPATH环境变量来隔离项目。我个人觉得使用包管理器的方法更简单。

值得庆幸的是,Go 现在内置了优秀的包管理功能,所以这不再是个问题。不过,你可能会GOPATH在许多较早的博客文章和文章中看到相关内容,这可能会让人有些困惑。

你好世界!

让我们开始开发我们的Web应用程序。和往常一样,这将是一个非常简单的“Hello, World!”应用程序,因为我想专注于开发和部署过程,并尽量控制文章篇幅。

先决条件

你需要:

  • 我使用的是最新版本的golang(1.14.9)。
  • Docker
  • 一个Heroku账户(本示例使用免费账户即可。)
  • Heroku命令行客户端
  • git

执行 mod 初始化

要创建新项目,我们需要为其创建一个目录,并使用go mod init命令将其初始化为 Go 模块。



mkdir helloworld
cd helloworld
go mod init digitalronin/helloworld


Enter fullscreen mode Exit fullscreen mode

通常的做法是使用你的 GitHub 用户名来保持项目名称的全局唯一性,并避免与任何项目依赖项发生名称冲突,但你也可以使用任何你喜欢的名称。

现在您会go.mod在该目录中看到一个文件。Go 会在这里跟踪所有项目依赖项。如果您查看该文件的内容,它应该类似于这样:



module digitalronin/helloworld

go 1.14


Enter fullscreen mode Exit fullscreen mode

让我们开始提交更改:



git init git add * git commit -m "Initial commit"


Enter fullscreen mode Exit fullscreen mode

杜松子酒

我们将使用Gin来开发我们的 Web 应用程序。Gin 是一个轻量级的 Web 框架,类似于Ruby 的Sinatra、 Javascript 的express.jsPython 的Flask 。

创建一个名为 `<filename>` 的文件,hello.go其中包含以下代码:



package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()

    r.GET("/hello", func(c *gin.Context) {
        c.String(200, "Hello, World!")
    })

    r.Run(":3000")
}


Enter fullscreen mode Exit fullscreen mode

让我们来详细分析一下:



r := gin.Default()


Enter fullscreen mode Exit fullscreen mode

这将创建一个路由器对象,r使用 gin 内置的默认值。

然后,我们分配一个处理函数,该函数将对发送到该路径的任何 HTTP GET 请求调用/hello,并返回字符串“Hello, World!”和 200(HTTP OK)状态码:



r.GET("/hello", func(c *gin.Context) {
        c.String(200, "Hello, World!")
    })


Enter fullscreen mode Exit fullscreen mode

最后,我们启动网络服务器,并让它监听3000端口:



r.Run(":3000")


Enter fullscreen mode Exit fullscreen mode

要运行此代码,请执行:



go run hello.go


Enter fullscreen mode Exit fullscreen mode

你应该看到类似这样的输出:



go: finding module for package github.com/gin-gonic/gin
go: found github.com/gin-gonic/gin in github.com/gin-gonic/gin v1.6.3
[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] GET    /hello                    --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :3000


Enter fullscreen mode Exit fullscreen mode

现在,如果您http://localhost:3000/hello在网络浏览器中访问,您应该会看到“Hello, World!”消息。

请注意,我们无需单独安装 gin,甚至无需编辑配置go.mod文件将其声明为依赖项。Go 会自动处理并进行必要的更改,这就是我们在输出中看到这些行的原因:



go: finding module for package github.com/gin-gonic/gin
go: found github.com/gin-gonic/gin in github.com/gin-gonic/gin v1.6.3


Enter fullscreen mode Exit fullscreen mode

查看该go.mod文件,你会发现它现在包含以下内容:



module digitalronin/helloworld

go 1.14

require github.com/gin-gonic/gin v1.6.3 // indirect


Enter fullscreen mode Exit fullscreen mode

您现在还会看到一个go.sum文件。这是一个文本文件,其中包含所有软件包依赖项及其依赖项的具体版本引用,以及相关模块该版本内容的加密哈希值。

该文件在 Javascript 项目或Ruby 项目中go.sum起着类似的作用,您应该始终将其与源代码一起检入版本控制系统。package-lock.jsonGemfile.lock

我们现在就开始吧:



git add *
git commit -m "Add 'Hello world' web server"


Enter fullscreen mode Exit fullscreen mode

提供 HTML 和 JSON 服务

我不会深入探讨 Gin 的功能,但我确实想演示一下它的其他一些功能,特别是发送 JSON 响应和提供静态文件服务。

我们先来看 JSON 响应。将以下代码添加到hello.go文件中,紧跟在r.GET代码块之后:



api := r.Group("/api")

api.GET("/ping", func(c *gin.Context) {
  c.JSON(200, gin.H{
    "message": "pong",
  })
})


Enter fullscreen mode Exit fullscreen mode

在这里,我们创建了一个路径后面的“路由组”,该/api路径/ping将返回一个 JSON 响应。

代码编写完成后,运行服务器go run,然后访问新的 API 端点:



curl http://localhost:3000/api/ping


Enter fullscreen mode Exit fullscreen mode

你应该会收到以下回复:



{"message":"pong"}


Enter fullscreen mode Exit fullscreen mode

最后,让我们让 Web 服务器提供静态文件服务。Gin 提供了一个额外的库来实现这一点。

将文件顶部的导入代码块更改hello.go为以下内容:



import (
    "github.com/gin-gonic/contrib/static"
    "github.com/gin-gonic/gin"
)


Enter fullscreen mode Exit fullscreen mode

最流行的代码编辑器都有 golang 支持包,您可以安装这些包,它们会自动为您处理import声明,并在您每次在代码中使用新模块时自动更新它们。

然后,在main函数内部添加这行代码:



r.Use(static.Serve("/", static.LocalFile("./views", true)))


Enter fullscreen mode Exit fullscreen mode

现在,我们Web应用程序的完整代码如下所示:

hello.go



package main

import (
    "github.com/gin-gonic/contrib/static"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.GET("/hello", func(c *gin.Context) {
        c.String(200, "Hello, World!")
    })

    api := r.Group("/api")

    api.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })

    r.Use(static.Serve("/", static.LocalFile("./views", true)))

    r.Run()
}


Enter fullscreen mode Exit fullscreen mode

r.Use(static.Serve...行代码使我们的 Web 服务器能够提供views目录中的任何静态文件,所以让我们添加一些文件:



mkdir -p views/css


Enter fullscreen mode Exit fullscreen mode

views/css/stylesheet.css



body {
  font-family: Arial;
}

h1 {
  color: red;
}


Enter fullscreen mode Exit fullscreen mode

views/index.html



<html>
  <head>
    <link rel="stylesheet" href="/css/stylesheet.css" />
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>


Enter fullscreen mode Exit fullscreen mode

现在重启 Web 服务器go run hello.go并访问http://localhost:3000,您应该会看到如下样式的消息:

Docker 化

我们已经编写好了 Go Web 应用程序,现在让我们把它打包成 Docker 镜像。我们可以把它创建成 Heroku 构建包,但 Go 的一个优点是可以将软件作为单个二进制文件分发。这正是 Go 的优势所在,而使用基于 Docker 的 Heroku 部署可以让我们充分利用这一优势。此外,这种技术并不局限于 Go 应用程序:你可以使用基于 Docker 的部署方式将任何语言的项目部署到 Heroku。因此,这是一种值得掌握的技巧。

到目前为止,我们一直使用go run命令运行代码。要将其编译成单个可执行二进制文件,我们只需运行:



go build


Enter fullscreen mode Exit fullscreen mode

这将编译我们所有的 Go 源代码并创建一个单独的文件。默认情况下,输出文件将根据模块名称命名,因此在我们的例子中,它将被命名为helloworld.

我们可以运行这个:



./helloworld


Enter fullscreen mode Exit fullscreen mode

我们可以像以前一样,通过curl网络浏览器访问相同的 HTTP 端点。

静态文件不会被编译到二进制文件中,所以如果您将helloworld文件放在其他目录中,它将找不到views用于提供 HTML 和 CSS 内容的目录。

这就是我们为任何开发平台(就我而言,是我的Mac笔记本电脑)创建二进制文件所需的全部步骤。但是,要在Docker容器内运行(最终部署到Heroku),我们需要编译一个适用于Docker容器运行架构的二进制文件。

我将使用Alpine Linux,所以我们先在这个操作系统上构建二进制文件。创建一个Dockerfile包含以下内容的文件:



FROM golang:1.14.9-alpine
RUN mkdir /build
ADD go.mod go.sum hello.go /build/
WORKDIR /build
RUN go build


Enter fullscreen mode Exit fullscreen mode

在这个镜像中,我们从golang基础镜像开始,添加我们的源代码,然后运行go build以创建我们的helloworld二进制文件。

我们可以像这样构建 Docker 镜像:



docker build -t helloworld .


Enter fullscreen mode Exit fullscreen mode

别忘了.在命令末尾加上 `--build` 参数。它告诉 Docker 我们要使用当前目录作为构建上下文。

这会创建一个包含我们二进制文件的 Docker 镜像,但它也包含了编译helloworld代码所需的所有 Go 工具,而我们不希望最终部署镜像中包含这些工具,因为这会使镜像体积过大。此外,在 Docker 镜像中安装不必要的可执行文件也可能存在安全风险。

我们可以通过以下方式查看 Docker 镜像的大小:



$ docker images helloworld
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
helloworld          latest              9657ec1ca905        4 minutes ago       370MB


Enter fullscreen mode Exit fullscreen mode

相比之下,alpine镜像(一个轻量级的 Linux 发行版,通常用作 Docker 镜像的基础)要小得多:



$ docker images alpine
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
alpine              latest              caf27325b298        20 months ago       5.53MB


Enter fullscreen mode Exit fullscreen mode

在我的 Mac 上,helloworld二进制文件大约 14MB,所以 golang 镜像比实际需要的要大得多。

我们想做的是使用这个 Dockerfile构建我们的 helloworld 二进制文件,以便在 alpine linux 上运行,然后将编译后的二进制文件复制到 alpine 基础镜像中,而不包含所有额外的 golang 工具。

我们可以使用“多阶段”Docker构建来实现这一点。修改Dockerfile,使其如下所示:



FROM golang:1.14.9-alpine AS builder
RUN mkdir /build
ADD go.mod go.sum hello.go /build/
WORKDIR /build
RUN go build

FROM alpine
RUN adduser -S -D -H -h /app appuser
USER appuser
COPY --from=builder /build/helloworld /app/
COPY views/ /app/views
WORKDIR /app
CMD ["./helloworld"]


Enter fullscreen mode Exit fullscreen mode

第一行,我们标记初始 Docker 镜像AS builder

之后,我们切换到另一个基础镜像FROM alpine,然后helloworld像这样从构建镜像中复制二进制文件:



COPY --from=builder /build/helloworld /app/


Enter fullscreen mode Exit fullscreen mode

构建新的 Docker 镜像:



docker build -t helloworld .


Enter fullscreen mode Exit fullscreen mode

现在,它的大小与一张基础高山图像加上我们的 helloworld 二进制文件的大小一致:



$ docker images helloworld
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
helloworld          latest              1d6d9cb64c7e        8 seconds ago       20.7MB


Enter fullscreen mode Exit fullscreen mode

我们可以像这样从 Docker 镜像运行我们的 Web 服务器。(如果您正在使用go run hello.go其他方式运行另一个版本./helloworld,则需要先停止该版本,以释放 3000 端口。)



docker run --rm -p 3000:3000 helloworld


Enter fullscreen mode Exit fullscreen mode

go run hello.goDocker 化的 Web 服务器的行为应该与原./helloworld生版本完全相同,只是它拥有自己的静态文件副本。因此,如果您更改了其中的任何文件,views/则必须重新构建 Docker 镜像并重启容器后才能看到更改。

部署到 Heroku

现在我们已经有了 Docker 化的 Web 应用程序,接下来将其部署到 Heroku。Heroku 是一个 PaaS 提供商,可以轻松部署和托管应用程序。您可以通过 Heroku 用户界面或 Heroku 命令行界面 (CLI) 设置和部署应用程序。在本示例中,我们将使用 Heroku 命令行应用程序。

从环境变量中获取端口号

我们已将 Web 服务器硬编码为运行在 3000 端口,但这在 Heroku 上行不通。我们需要将其更改为运行在PORT环境变量中指定的端口号上,Heroku 会自动提供该端口号。

为此,请修改r.Run文件底部附近的行hello.go,并删除":3000"字符串值,使该行变为:



r.Run()


Enter fullscreen mode Exit fullscreen mode

gin 的默认行为是使用PORT环境变量中指定的端口(如果没有指定端口,则使用 8080 端口)。这正是 Heroku 所需要的行为。

设置我们的 Heroku 应用

首先,登录 Heroku:



heroku login


Enter fullscreen mode Exit fullscreen mode

现在,创建一个应用程序:



heroku create


Enter fullscreen mode Exit fullscreen mode

告诉 Heroku 我们希望使用 Dockerfile 而不是构建包来构建此项目:



heroku stack:set container


Enter fullscreen mode Exit fullscreen mode

为此,我们还需要创建一个heroku.yml类似这样的文件:



build:
  docker:
    web: Dockerfile

run:
  web: ./helloworld


Enter fullscreen mode Exit fullscreen mode

heroku.yml文件是一个清单文件,它定义了我们的应用程序,并允许我们指定在应用程序配置期间要使用的插件和配置变量。

接下来,添加 git 并提交这些文件,然后推送到 Heroku 进行部署:



git push heroku main


Enter fullscreen mode Exit fullscreen mode

我的 Git 配置使用 ` main<branch_name>` 作为默认分支。如果你的默认分支名为 `<branch_name>` master,则运行 `git git add <branch_name>`git push heroku master代替。

你应该能看到 Heroku 根据你的 Dockerfile 构建镜像,并将其推送到 Heroku Docker 镜像仓库。命令完成后,你可以在浏览器中运行以下命令查看已部署的应用程序:



heroku open

Enter fullscreen mode Exit fullscreen mode




结论

综上所述,以下是我们今天所讲内容的总结:

  • 创建一个使用 Go 模块和 Gin Web 框架的 Go 语言 Web 应用程序,用于提供字符串、JSON 和静态文件服务。
  • 使用多阶段 Dockerfile 创建轻量级 Docker 镜像
  • heroku stack:set container使用文件将基于 Docker 的应用程序部署到 Heroku heroku.yml

本文只是浅谈了如何使用 Go 构建 Web 应用程序和 API。希望这些信息足以帮助你开始开发自己的 Go 语言 Web 应用程序。

文章来源:https://dev.to/heroku/deploying-your-first-golang-webapp-11b3