为 Go 应用程序设计 Source-to-Image 构建

在本系列关于 Golang 的 S2I 的第二篇文章中,了解如何专门为 Go 应用程序准备环境。
204 位读者喜欢这个。
gopher illustrations

Renee French。CC BY 3.0

在我的关于 Source-to-Image (S2I) 系列的第一篇文章中,我们研究了必需的文件,并讨论了 S2I 标准如何与任何编程语言(从 Python 到 Ruby 再到 Go)一起工作。现在让我们探讨如何专门为 Go 应用程序设计 S2I 构建。免责声明:我仍然喜欢称 Go 为“Golang”,即使它不是官方名称。

Golang S2I 文件

所有 S2I 环境的核心都始于定义为 Dockerfile 的配置文件。我们的 Golang 构建器镜像也不例外;这个项目将需要一个 Dockerfile 来创建构建器,一个 assemble 脚本来包含编译 Go 应用程序的逻辑,一个 run 脚本来启动应用程序(仅为方便起见),以及一个 save-artifacts 脚本来保存编译后的应用程序。

让我们看看这些必需的文件,因为我们将把它们用于构建器镜像。

注意: 所有引用的文件都可以在我的 golang-s2i GitHub 存储库中找到。

Dockerfile

这个 Dockerfile 不是很复杂。此构建器镜像将使用上游 golang:1.12 镜像作为其基础,因此我们不必安装 Go 和设置环境。必须设置两个环境变量,以允许 Go 应用程序在容器环境中运行

  • CGO_ENABLED=0
  • GOOS=linux

如果您想了解更多关于为什么使用它们的信息,请查看 Kelsey Hightower 的优秀文章“为静态 Go 二进制文件构建 Docker 镜像”。

GOCACHE=/tmp 环境变量也需要在 Dockerfile 中设置,以避免在以非 root 用户身份运行构建时出现写入错误。按照 OKD/OpenShift 惯例,支持使用任意 UID 运行容器,但避免构建也需要 root 权限也是一个好的做法。请查看 Dan Walsh 的文章“对(容器中的)root 说不”,了解更多关于为什么这是一件好事。

然后设置另外两个环境变量

  • STI_SCRIPTS_PATH=/usr/libexec/s2i
  • SOURCE_DIR=/go/src/app

第一个告诉 S2I 在哪里找到它需要运行的脚本,第二个只是为了方便起见,以便我们的项目是 DRY(不要重复自己)。

在下一节中,将 S2I 脚本(见下文)COPY 到构建器镜像,创建并 chmod $SOURCE_DIR,脚本将在其中编译应用程序,并将此设置为 WORKDIR,以便后续操作在该目录中进行

COPY ./s2i/bin/ ${STI_SCRIPTS_PATH}
RUN mkdir -p $SOURCE_DIR \
      && chmod 0777 $SOURCE_DIR
WORKDIR $SOURCE_DIR

注意: $SOURCE_DIR 设置为 /go/src/app,因为父 golang:1.12 镜像中的 $GOPATH 变量设置为 /go

最后,设置 USER 1001 以删除 root 权限并确保支持随机 UID,如上所述,并将 CMD 设置为 S2I 使用脚本

USER 1001
CMD ["/usr/libexec/s2i/usage"]

此时,我们的 Dockerfile 应该看起来像我的 GitHub 仓库中的 Dockerfile ,除了几个标签和注释。

Assemble

S2I 使用 assemble 脚本来编译 Go 应用程序。

当 S2I 将应用程序代码复制到构建器镜像时,它将其放置在 /tmp/src 中。由于上游镜像将 GOPATH 设置为 /go,因此 assemble 脚本只需要复制到其中的一个目录:我们在镜像中较早设置的 $SOURCE_DIR。然后只需运行 go getgo build 即可。因为 WORKDIR 设置为相同的目录,所以脚本将从那里运行

#!/bin/bash -e

# Copy the src to the current directory - the WORKDIR/$SOURCE_DIR.
cp -Rf /tmp/src/. ./
go get -v
go build -v -o app -a -installsuffix cgo

go build 命令使用 -o app 构建标志运行,以便生成的二进制文件将具有可预测的名称(具体来说是“app”)。

这就是 assemble 脚本所需的全部内容,但也可以在脚本末尾包含 go test -v,以确保应用程序通过其所有代码测试(因为它会使镜像构建在任何测试失败时失败)。

assemble 脚本就是这样了。GitHub 中的 assemble 脚本 包含一些用于从以前的构建中复制工件的命令,但除此之外,它是相同的。

run

如果 S2I 从生成的镜像构建运行容器,则使用 run 脚本来执行应用程序。恰如其分,它只有两行

#!/bin/bash
exec app

注意: 如果应用程序需要参数,则需要稍微调整此脚本以传递这些参数(这也是为什么为 Go 完全包含脚本的原因,而应用程序本身可以只是“run”脚本)。

save-artifacts

save-artifacts 镜像不是 S2I 所必需的。其目的是重用以前构建中的工件(想想:下载的 PIP 包或 Ruby gems)并进行增量构建,以便它可以使开发中的镜像构建更快。Golang 构建器镜像将以稍微不同的方式使用它:允许我们提取编译后的二进制文件,以便它可以包含在更精简的运行时镜像中(见下文)。

S2I 希望 save-artifacts 脚本获取应用程序的所有文件和依赖项,并通过 tar 命令将其流式传输到 stdout,以便可以在另一端接收并保存。这在理论上很容易,但如果不小心可能会很棘手。流式传输到 stdout 的内容必须包含 tar 文件的内容;我们必须小心防止发送文本或其他内容。我们需要将 tar 输出之外的所有输出通过管道传输到 /dev/null,以确保 tar 存档不会被其他数据损坏。

专家提示: 如果我们尝试通过将 save-artifacts 脚本作为容器的命令手动流式传输 tar 文件的内容,我们绝不能使用 -i-t 参数,因为 tar 拒绝将输出流式传输到伪终端。

在我们的例子中,因为构建器正在编译单个二进制文件,所以此脚本也只有两行,并且没有其他输出需要担心

#!/bin/sh -e
tar cf – app

我们现在有了一个功能齐全的 Golang 应用程序 S2I 环境。我们可以在命令行上试用它。如果您需要回顾所有这些文件,请返回并查看本系列中的第一篇文章。在下一篇文章中,我们将继续构建过程,并完成构建 Go 应用程序的工作流程。

Chris Collins
Chris Collins 是 Red Hat 的 SRE 和 OpenSource.com 通讯员,对自动化、容器编排及其周围的生态系统充满热情,并且喜欢在家中重现企业级技术以获得乐趣。

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.