在本系列的前三篇文章中,我们探讨了 Source-to-Image (S2I) 系统的通用要求,并准备和测试了专门用于 Go (Golang) 应用程序的环境。此 S2I 构建非常适合本地开发或维护带有代码管道的构建器镜像,但如果您可以访问 OKD 或 OpenShift 集群(或 Minishift),则可以使用 OKD BuildConfigs 设置整个工作流程,不仅可以构建和维护构建器镜像,还可以使用构建器镜像自动创建应用程序镜像和后续运行时镜像。这样,当下游镜像更改时,镜像可以自动重建,并且可以触发 OKD deploymentConfigs 以重新部署从这些镜像运行的应用程序。
步骤 1:在 OKD 中构建构建器镜像
与本地 S2I 用法一样,第一步是创建构建器镜像,以构建 GoHelloWorld 测试应用程序,我们可以重用该应用程序来编译其他基于 Go 的应用程序。第一个构建步骤将是 Docker 构建,就像以前一样,它从 Git 存储库中拉取 Dockerfile 和 S2I 脚本以构建镜像。因此,这些文件必须被提交并可在公共 Git 仓库中获得(或者您可以使用本文的配套 GitHub 仓库)。
注意: OKD BuildConfigs 不要求源 Git 仓库是公共的。要使用私有仓库,您必须设置部署密钥并将密钥链接到构建器服务帐户。这并不困难,但为了简单起见,本练习将使用公共仓库。
为构建器镜像创建镜像流
BuildConfig 将为我们创建一个构建器镜像来编译 GoHelloWorld 应用程序,但首先,我们需要一个地方来存储该镜像。在 OKD 中,这个地方是镜像流。
镜像流及其标签就像相关镜像和镜像标签的清单或列表。它充当一个抽象层,允许您引用镜像,即使镜像发生更改。可以将其视为引用特定镜像的别名集合,并且随着镜像的更新,自动指向新的镜像版本。镜像流除了这些别名之外什么都不是——只是存储在注册表中的真实镜像的元数据。
可以使用 oc create imagestream <name> 命令创建镜像流,也可以使用带有 oc create -f <filename> 的 YAML 文件创建镜像流。无论哪种方式,全新的镜像流都是一个小的占位符对象,在用镜像引用填充之前都是空的,无论是手动填充(谁想手动操作?)还是使用 BuildConfig 填充。
我们的 golang-builder 镜像流看起来像这样
# imageStream-golang-builder.yaml
---
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
generation: 1
name: golang-builder
spec:
lookupPolicy:
local: false
除了名称和一个(大部分)为空的规范之外,实际上没有任何东西。
注意: lookupPolicy 与允许 Kubernetes 原生组件解析镜像流引用有关,因为镜像流是 OKD 原生的,而不是 Kubernetes 核心的一部分。此主题超出了本文的范围,但您可以在 OKD 的文档 将镜像流与 Kubernetes 资源一起使用 中阅读更多关于其工作原理的信息。
为构建器镜像及其后代创建镜像流。
$ oc create -f imageStream-golangBuilder.yaml
# Check the ImageStream
$ oc get imagestream golang-builder
NAME DOCKER REPO TAGS UPDATED
imagestream.image.openshift.io/golang-builder docker-registry.default.svc:5000/golang-builder/golang-builder
请注意,新创建的镜像流没有标签,并且从未更新过。
为构建器镜像创建 BuildConfig
在 OKD 中,BuildConfig 描述了如何从特定来源构建容器镜像以及何时构建的触发器。不要被语言所迷惑——正如您可能会说您从 Dockerfile 构建和重新构建同一个镜像,但实际上,您构建了多个镜像,BuildConfig 构建和重新构建同一个镜像,但实际上,它创建了多个镜像。(突然间,镜像流的原因变得更加清晰!)
我们的构建器镜像 BuildConfig 描述了如何构建和重新构建我们的构建器镜像。BuildConfig 的核心由四个重要部分组成
- 构建源
- 构建策略
- 构建输出
- 构建触发器
构建源(顾名思义)描述了运行构建的东西来自哪里。golang-builder BuildConfig 描述的构建将使用我们之前创建的 Dockerfile 和 S2I 脚本,并使用 Git 类型构建源,克隆 Git 仓库以获取执行构建的文件。
source:
type: Git
git:
ref: master
uri: https://github.com/clcollins/golang-s2i.git
构建策略描述了构建将如何处理来自构建源的源文件。golang-builder BuildConfig 通过使用 Docker 类型构建策略来模拟我们之前用于构建本地构建器镜像的 docker build。
strategy:
type: Docker
dockerStrategy: {}
dockerStrategy 构建类型告诉 OKD 从构建源指定的源中包含的 Dockerfile 构建 Docker 镜像。
构建输出告诉 BuildConfig 如何处理生成的镜像。在本例中,我们指定了上面创建的镜像流和一个要赋予镜像的标签。与我们的本地构建一样,我们将其标记为 golang-builder:1.12,作为对从父镜像继承的 Go 版本的引用。
output:
to:
kind: ImageStreamTag
name: golang-builder:1.12
最后,BuildConfig 定义了一组构建触发器——将导致镜像自动重建的事件。对于此 BuildConfig,BuildConfig 配置的更改或上游镜像 (golang:1.12) 的更新将触发新的构建。
triggers:
- type: ConfigChange
- imageChange:
type: ImageChange
使用 GitHub 仓库中的 构建器镜像 BuildConfig 作为参考(或仅使用该文件),创建一个 BuildConfig YAML 文件并使用它来创建 BuildConfig。
$ oc create -f buildConfig-golang-builder.yaml
# Check the BuildConfig
$ oc get bc golang-builder
NAME TYPE FROM LATEST
golang-builder Docker Git@master 1
由于 BuildConfig 包含“ImageChange”触发器,它会立即启动新的构建。您可以使用 oc get builds 命令检查是否已创建构建。
# Check the Builds
$ oc get builds
NAME TYPE FROM STATUS STARTED DURATION
golang-builder-1 Docker Git@8eff001 Complete About a minute ago 13s
在构建运行时和构建完成后,您可以使用 oc logs -f <build name> 查看其日志,并查看 Docker 构建输出,就像在本地一样。
$ oc logs -f golang-builder-1-build
Step 1/11 : FROM docker.io/golang:1.12
---> 7ced090ee82e
Step 2/11 : LABEL maintainer "Chris Collins <collins.christopher@gmail.com>"
---> 7ad989b765e4
Step 3/11 : ENV CGO_ENABLED 0 GOOS linux GOCACHE /tmp STI_SCRIPTS_PATH /usr/libexec/s2i SOURCE_DIR /go/src/app
---> 2cee2ce6757d
<...>
如果您没有包含任何构建触发器(或没有将它们放在正确的位置),您的构建可能不会自动启动。您可以使用 oc start-build 命令手动启动新的构建。
$ oc start-build golang-builder
# Or, if you want to automatically tail the build log
$ oc start-build golang-builder --follow
构建完成后,生成的镜像将被标记并推送到集成的镜像注册表,并且镜像流将使用新镜像的信息进行更新。使用 oc get imagestream 命令检查镜像流,以查看新标签是否存在。
$ oc get imagestream golang-builder
NAME DOCKER REPO TAGS UPDATED
golang-builder docker-registry.default.svc:5000/golang-builder/golang-builder 1.12 33 seconds ago
步骤 2:在 OKD 中构建应用程序镜像
现在我们已经创建了用于我们的 Golang 应用程序的构建器镜像并将其存储在 OKD 中,我们可以使用此构建器镜像来编译我们所有的 Go 应用程序。首先是来自我们的 本地构建示例 的示例 GoHelloWorld 应用程序。GoHelloWorld 是一个简单的 Go 应用程序,它在运行时仅输出 Hello World!。
正如我们在本地示例中使用 s2i build 命令所做的那样,我们可以告诉 OKD 使用我们的构建器镜像和 S2I 来构建 GoHelloWorld 的应用程序镜像,从 GoHelloWorld GitHub 仓库 中的源代码编译 Go 二进制文件。这可以使用带有 sourceStrategy 构建的 BuildConfig 完成。
为应用程序镜像创建镜像流
首先,我们需要创建一个镜像流来管理由 BuildConfig 创建的镜像。镜像流就像 golang-builder 镜像流一样,只是名称不同。使用 oc create is 或使用来自 GitHub 仓库 的 YAML 文件创建它。
$ oc create -f imageStream-goHelloWorld-appimage.yaml
imagestream.image.openshift.io/go-hello-world-appimage created
为应用程序镜像创建 BuildConfig
正如我们对构建器镜像 BuildConfig 所做的那样,此 BuildConfig 将使用 Git 源选项从 GoHelloWorld 仓库克隆我们的源代码。
source:
type: Git
git:
uri: https://github.com/clcollins/goHelloWorld.git
此 BuildConfig 将使用 sourceStrategy 定义来使用 S2I 构建镜像,而不是使用 DockerStrategy 构建从 Dockerfile 创建镜像。
strategy:
type: Source
sourceStrategy:
from:
kind: ImageStreamTag
name: golang-builder:1.12
请注意 sourceStrategy 中的 from: 哈希。这告诉 OKD 使用我们之前为 S2I 构建创建的 golang-builder:1.12 镜像。
BuildConfig 将输出到我们创建的新 appimage 镜像流,并且我们将包含 config- 和 image-change 触发器,以便在任何内容更新时自动启动新构建。
output:
to:
kind: ImageStreamTag
name: go-hello-world-appimage:1.0
triggers:
- type: ConfigChange
- imageChange:
type: ImageChange
再次,创建一个 BuildConfig 或使用来自 GitHub 仓库的 BuildConfig。
$ oc create -f buildConfig-goHelloWorld-appimage.yaml
新构建与 golang-builder 构建一起显示,并且由于指定了 image-change 触发器,构建会立即开始。
$ oc get builds
NAME TYPE FROM STATUS STARTED DURATION
golang-builder-1 Docker Git@8eff001 Complete 8 minutes ago 13s
go-hello-world-appimage-1 Source Git@99699a6 Running 44 seconds ago
如果您想观看构建日志,请使用 oc logs -f 命令。一旦应用程序镜像构建完成,它将被推送到我们指定的镜像流,然后创建新的镜像流标签。
$ oc get is go-hello-world-appimage
NAME DOCKER REPO TAGS UPDATED
go-hello-world-appimage docker-registry.default.svc:5000/golang-builder/go-hello-world-appimage 1.0 10 minutes ago
成功!GoHelloWorld 应用程序已从源代码克隆到新镜像中,并使用我们的 S2I 脚本进行了编译和测试。我们可以按原样使用该镜像,但与我们的本地 S2I 构建一样,我们可以做得更好,并创建一个只包含新 Go 二进制文件的镜像。
步骤 3:在 OKD 中构建运行时镜像
现在已经使用 GoHelloWorld 应用程序的已编译 Go 二进制文件创建了应用程序镜像,我们可以使用一种称为链式构建的方法来模拟我们从本地应用程序镜像中提取二进制文件并创建一个新的运行时镜像,其中只包含二进制文件的情况。
为运行时镜像创建镜像流
再次,第一步是为新的运行时镜像创建镜像流镜像。
# Create the ImageStream
$ oc create -f imageStream-goHelloWorld.yaml
imagestream.image.openshift.io/go-hello-world created
# Get the ImageStream
$ oc get imagestream go-hello-world
NAME DOCKER REPO TAGS UPDATED
go-hello-world docker-registry.default.svc:5000/golang-builder/go-hello-world
链式构建
链式构建是指使用一个或多个 BuildConfig 来编译软件或组装应用程序的工件,并且这些工件被后续 BuildConfig 保存和使用,以生成运行时镜像,而无需重新编译代码。

链式构建工作流程
为运行时镜像创建 BuildConfig
运行时 BuildConfig 使用 DockerStrategy 构建来从 Dockerfile 构建镜像——这与我们对构建器镜像 BuildConfig 所做的相同。但是,这次,源不是 Git 源,而是 Dockerfile 源。
什么是 Dockerfile 源?它是一个内联 Dockerfile!我们不是克隆包含 Dockerfile 的仓库并构建它,而是在 BuildConfig 中指定 Dockerfile。这对于我们的运行时 Dockerfile 尤其适用,因为它只有三行。
source:
type: Dockerfile
dockerfile: |-
FROM scratch
COPY app /app
ENTRYPOINT ["/app"]
images:
- from:
kind: ImageStreamTag
name: go-hello-world-appimage:1.0
paths:
- sourcePath: /go/src/app/app
destinationDir: "."
请注意,上面 Dockerfile 源定义中的 Dockerfile 与我们在本系列的第三篇文章中使用的 Dockerfile 相同,当时我们使用 S2I save-artifacts 脚本提取的二进制文件在本地构建了精简的 GoHelloWorld 镜像。
还需要注意:scratch 是 Dockerfile 中的保留字。与其他 FROM 语句不同,它不定义实际的镜像,而是定义此镜像的第一层将是空的。它使用 kind: DockerImage 定义,但不具有注册表或组/命名空间/项目字符串。在此优秀的 容器最佳实践 参考中了解有关此行为的更多信息。
Dockerfile 源的 images 部分描述了要用于构建的工件的来源;在本例中,来自之前生成的 appimage。paths 子部分描述了从哪里获取二进制文件(即,在 app 镜像的 /go/src/app 目录中,获取 app 二进制文件)以及将其保存在哪里(即,构建本身的当前工作目录中:".")。这允许 COPY app /app 从当前工作目录中获取二进制文件,并将其添加到运行时镜像中的 /app。
注意: paths 是源和目标路径对的数组。列表中的每个条目都包含一个源和一个目标。在上面的示例中,只有一个条目,因为只有一个二进制文件要复制。
然后使用 Docker 策略来构建内联 Dockerfile。
strategy:
type: Docker
dockerStrategy: {}
再次,它输出到之前创建的镜像流,并包括构建触发器以自动启动新的构建。
output:
to:
kind: ImageStreamTag
name: go-hello-world:1.0
triggers:
- type: ConfigChange
- imageChange:
type: ImageChange
创建一个 BuildConfig YAML 或使用来自 GitHub 仓库的运行时 BuildConfig。
$ oc create -f buildConfig-goHelloWorld.yaml
buildconfig.build.openshift.io/go-hello-world created
如果您观看日志,您会注意到第一步是 FROM scratch,这证实我们正在将已编译的二进制文件添加到空白镜像。
$ oc logs -f pod/go-hello-world-1-build
Step 1/5 : FROM scratch
--->
Step 2/5 : COPY app /app
---> 9e70e6c710f8
Removing intermediate container 4d0bd9cef0a7
Step 3/5 : ENTRYPOINT /app
---> Running in 7a2dfeba28ca
---> d697577910fc
<...>
构建完成后,检查镜像流标签以验证新镜像是否已推送到注册表并且镜像流已更新。
$ oc get imagestream go-hello-world
NAME DOCKER REPO TAGS UPDATED
go-hello-world docker-registry.default.svc:5000/golang-builder/go-hello-world 1.0 4 minutes ago
记下镜像的 DOCKER REPO 字符串。它将在下一节中用于运行镜像。
我们是否创建了一个微小的、仅包含二进制文件的镜像?
最后,让我们验证我们确实构建了一个微小的镜像,其中只包含二进制文件。
查看镜像详细信息。首先,从镜像流中获取镜像的名称。
$ oc describe imagestream go-hello-world
Name: go-hello-world
Namespace: golang-builder
Created: 42 minutes ago
Labels: <none>
Annotations: <none>
Docker Pull Spec: docker-registry.default.svc:5000/golang-builder/go-hello-world
Image Lookup: local=false
Unique Images: 1
Tags: 1
1.0
no spec tag
* docker-registry.default.svc:5000/golang-builder/go-hello-world@sha256:eb11e0147a2917312f5e0e9da71109f0cb80760e945fdc1e2db6424b91bc9053
13 minutes ago
镜像在底部列出,用 SHA 哈希描述(例如,sha256:eb11e0147a2917312f5e0e9da71109f0cb80760e945fdc1e2db6424b91bc9053;您的将有所不同)。
使用哈希获取镜像的详细信息。
$ oc describe image sha256:eb11e0147a2917312f5e0e9da71109f0cb80760e945fdc1e2db6424b91bc9053
Docker Image: docker-registry.default.svc:5000/golang-builder/go-hello-world@sha256:eb11e0147a2917312f5e0e9da71109f0cb80760e945fdc1e2db6424b91bc9053
Name: sha256:eb11e0147a2917312f5e0e9da71109f0cb80760e945fdc1e2db6424b91bc9053
Created: 15 minutes ago
Annotations: image.openshift.io/dockerLayersOrder=ascending
image.openshift.io/manifestBlobStored=true
openshift.io/image.managed=true
Image Size: 1.026MB
Image Created: 15 minutes ago
Author: <none>
Arch: amd64
Entrypoint: /app
Working Dir: <none>
User: <none>
Exposes Ports: <none>
Docker Labels: io.openshift.build.name=go-hello-world-1
io.openshift.build.namespace=golang-builder
Environment: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
OPENSHIFT_BUILD_NAME=go-hello-world-1
OPENSHIFT_BUILD_NAMESPACE=golang-builder
请注意镜像大小,1.026MB,完全符合我们的要求。该镜像是一个 scratch 镜像,其中只包含二进制文件!
使用运行时镜像运行 Pod
使用我们刚刚创建的运行时镜像,让我们按需创建一个 pod 并运行它,并验证它仍然有效。
这在 Kubernetes/OKD 中几乎从未发生过,但我们将运行一个 pod,只是一个 pod,它本身。
$ oc run -it go-hello-world --image=docker-registry.default.svc:5000/golang-builder/go-hello-world:1.0 --restart=Never
Hello World!
一切都按预期工作——镜像运行并输出“Hello World!”,就像在之前的本地 S2I 构建中一样。
通过在 OKD 中创建此工作流程,我们可以将 golang-builder S2I 镜像用于任何 Go 应用程序。此构建器镜像已就位并为任何其他应用程序构建,并且每当上游 golang:1.12 镜像更改时,它都会自动更新并重建自身。
通过在 OKD 中使用链式构建策略创建 appimage BuildConfig 来编译源代码和 runtime BuildConfig 来创建最终镜像,可以使用 S2I 构建自动构建新应用程序。使用构建触发器,Git 仓库中源代码的任何更改都将触发通过整个管道的重建,自动重建 appimage 和 runtime 镜像。
这是维护任何应用程序的更新镜像的好方法。与带有镜像构建触发器的 OKD deploymentConfig 配对使用,当提交新代码时,长期运行的应用程序(例如,webapp)将自动重新部署。
Source-to-Image 是开发构建器镜像以可重复的方式构建和编译 Go 应用程序的理想方法,并且当与 OKD BuildConfigs 结合使用时,它会变得更好。
评论已关闭。