在本系列的前两篇文章中,我们探讨了 Source To Image (S2I) 系统的通用要求 并准备了一个环境,专门用于 Go (Golang) 应用程序。现在让我们试用一下。
构建构建器镜像
一旦 Dockerfile 和 Source-to-Image (S2I) 脚本准备就绪,就可以使用 docker build 命令创建 Golang 构建器镜像
docker build -t golang-builder .
这将生成一个名为 golang-builder 的构建器镜像,其上下文为当前目录。
构建应用程序镜像
没有要构建的应用程序,golang-builder 镜像用途不大。在本练习中,我们将构建一个简单的 hello-world 应用程序。
GoHelloWorld
让我们认识一下我们的测试应用程序 GoHelloWorld。如果您想继续学习,请下载最新版本的 Go。此存储库中有两个重要的(对于本练习而言)文件
// goHelloWorld.go
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
这是一个非常基础的应用程序,但它对于测试构建器镜像来说已经足够好了。我们还为 GoHelloWorld 准备了一个基本的测试
// goHelloWorld_test.go
package main
import "testing"
func TestMain(t *testing.T) {
t.Log("Hello World!")
}
构建应用程序镜像
构建应用程序镜像需要运行 s2i build 命令,并带有用于指定包含要构建代码的存储库的参数(或者使用 . 从当前目录构建代码),要使用的构建器镜像的名称,以及要创建的结果应用程序镜像的名称。
$ s2i build https://github.com/clcollins/goHelloWorld.git golang-builder go-hello-world
要从文件系统上的本地目录构建,请将 Git URL 替换为句点 以表示当前目录。例如
$ s2i build . golang-builder go-hello-world
注意: 如果在当前目录中初始化了 Git 存储库,则 S2I 将从存储库 URL 获取代码,而不是使用本地代码。这会导致在构建镜像时未使用本地、未提交的更改(如果您不熟悉我所说的“未提交的更改”,请复习一下Git 术语)。未进行 Git 初始化存储库的目录的行为符合预期。
运行应用程序镜像
一旦构建了应用程序镜像,就可以使用 Docker 命令运行它进行测试。Source-to-Image 已将镜像中的 CMD 替换为先前创建的运行脚本,因此它将执行构建过程中创建的 /go/src/app/app 二进制文件
$ docker run go-hello-world
Hello World!
成功!我们现在有一个编译后的 Go 应用程序,它位于 Docker 镜像内部,该镜像通过将 Git 存储库的内容传递给 S2I 而创建,并且无需为我们的应用程序使用特殊的 Dockerfile。
我们刚刚构建的应用程序镜像不仅包含应用程序,还包含其源代码、测试代码、S2I 脚本、Golang 库以及大部分 Debian Linux 发行版(因为 Golang 镜像基于 Debian 基础镜像)。生成的镜像并不小
$ docker images | grep go-hello-world
go-hello-world latest 75a70c79a12f 4 minutes ago 789 MB
对于用运行时解释并在链接库上运行的语言(如 Ruby 或 Python)编写的应用程序,拥有所有 源代码和操作系统是运行所必需的。构建镜像的结果将非常大,但至少我们知道它可以运行。对于这些语言, 我们可以就此停止我们的 S2I 构建。
但是,还有一种选择可以更明确地定义应用程序的生产要求。
由于生成的应用程序镜像将是运行生产应用程序的同一镜像,我想确保 所需的端口、卷和环境变量已添加到构建器镜像的 Dockerfile 中。通过以声明方式编写这些内容,我们的应用程序更接近 十二要素应用 推荐的做法。例如,如果我们使用构建器镜像为运行 Puma 的 Ruby on Rails 应用程序创建应用程序镜像,我们将需要打开一个端口来访问 Web 服务器。我们应该在构建器 Dockerfile 中添加 PORT 3000 行,以便它可以被从中生成的所有镜像继承。
但对于 Go 应用程序,我们可以做得更好。
构建运行时镜像
由于我们的构建器镜像创建了一个带有我们应用程序的静态编译 Go 二进制文件,我们可以创建一个最终的“运行时”镜像,其中仅包含二进制文件,而不包含其他冗余内容。
一旦创建了应用程序镜像,就可以提取已编译的 GoHelloWorld 应用程序,并使用 save-artifacts 脚本将其放入新的空镜像中。
运行时文件
仅需要应用程序二进制文件和一个 Dockerfile 即可创建运行时镜像。
应用程序二进制文件
在应用程序镜像内部,save-artifacts 脚本被编写为将应用程序二进制文件的 tar 存档流式传输到 stdout。我们可以使用 tar 的 -vt 标志检查 save-artifacts 创建的 tar 存档中包含的文件
$ docker run go-hello-world /usr/libexec/s2i/save-artifacts | tar -tvf -
-rwxr-xr-x 1001/root 1997502 2019-05-03 18:20 app
如果这导致类似“这似乎不是 tar 存档”的错误,则 save-artifacts 脚本可能除了 tar 流之外还输出了其他数据,如上所述。我们必须确保抑制除 tar 流之外的所有输出。
如果一切看起来正常,我们可以使用 save-artifacts 将二进制文件复制出应用程序镜像
$ docker run go-hello-world /usr/libexec/s2i/save-artifacts | tar -xf -
这会将 app 文件复制到当前目录中,准备添加到其自己的镜像中。
Dockerfile
Dockerfile 非常简单,只有三行。FROM scratch 源表示它使用一个空的、空白的父镜像。Dockerfile 的其余部分指定将 app 二进制文件复制到镜像中的 /app ,并将该二进制文件用作镜像 ENTRYPOINT
FROM scratch
COPY app /app
ENTRYPOINT ["/app"]
将此 Dockerfile 另存为 Dockerfile-runtime。
为什么是 ENTRYPOINT 而不是 CMD?我们可以两者都做,但由于镜像中没有其他内容(没有文件系统,没有 shell),无论如何我们都无法运行任何其他内容。
构建运行时镜像
有了 Dockerfile 和二进制文件,我们就可以构建新的运行时镜像了
$ docker build -f Dockerfile-runtime -t go-hello-world:slim .
新的运行时镜像要小得多——只有 2MB!
$ docker images | grep -e 'go-hello-world *slim'
go-hello-world slim 4bd091c43816 3 minutes ago 2 MB
我们可以使用 docker run 测试它是否仍然按预期工作
$ docker run go-hello-world:slim
Hello World!
使用 s2i create 引导 s2i
虽然我们在此示例中手动创建了所有 S2I 文件,但 s2i 命令有一个子命令可以帮助搭建我们可能需要的 Source-to-Image 构建的所有文件:s2i create。
使用 s2i create 命令,我们可以在 ./ghw2 目录中生成一个名为 go-hello-world-2 的新项目
$ s2i create go-hello-world-2 ./ghw2
$ ls ./ghw2/
Dockerfile Makefile README.md s2i test
create 子命令创建一个占位符 Dockerfile、一个包含有关如何使用 Source-to-Image 的信息的 README.md、一些示例 S2I 脚本、一个基本测试框架和一个 Makefile。Makefile 是自动化构建和测试 Source-to-Image 构建器镜像的好方法。开箱即用,运行 make 将构建我们的镜像,并且可以扩展它以执行更多操作。例如,我们可以添加步骤来构建基本应用程序镜像、运行测试或生成运行时 Dockerfile。
结论
在本教程中,我们学习了如何使用 Source-to-Image 构建自定义 Golang 构建器镜像,使用 s2i build 创建应用程序镜像,以及提取应用程序二进制文件以创建超精简运行时镜像。
在本系列的未来扩展中,我想看看如何使用我们使用 OKD 创建的构建器镜像,通过 buildConfigs、imageStreams 和 deploymentConfigs 自动部署我们的 Golang 应用程序。如果您有兴趣让我继续这个系列,请在评论中告诉我,感谢您的阅读。
2 条评论