×

学习如何基于现成的预构建镜像创建您自己的容器镜像。此过程包括学习编写镜像的最佳实践、定义镜像元数据、测试镜像以及使用自定义构建器工作流创建可在 Red Hat OpenShift Service on AWS 上使用的镜像。

学习容器最佳实践

在创建要在 Red Hat OpenShift Service on AWS 上运行的容器镜像时,镜像作者需要考虑许多最佳实践,以确保为这些镜像的使用者提供良好的体验。由于镜像旨在保持不变并按原样使用,因此以下指南有助于确保您的镜像高度可用且易于在 Red Hat OpenShift Service on AWS 上使用。

通用容器镜像指南

以下指南适用于创建容器镜像的一般情况,无论这些镜像是否在 Red Hat OpenShift Service on AWS 上使用。

重用镜像

尽可能使用 `FROM` 语句基于适当的上游镜像构建您的镜像。这确保您的镜像可以在上游镜像更新时轻松获取安全修复,而不是您必须直接更新您的依赖项。

此外,在 `FROM` 指令中使用标签,例如 `rhel:rhel7`,以便让用户清楚地了解您的镜像基于哪个版本的镜像。使用除 `latest` 之外的标签可确保您的镜像不会受到可能进入上游镜像 `latest` 版本的重大更改的影响。

在标签内保持兼容性

在标记您自己的镜像时,尝试在标签内保持向后兼容性。例如,如果您提供名为 `image` 的镜像,并且它当前包含版本 `1.0`,则您可以提供 `image:v1` 的标签。当您更新镜像时,只要它继续与原始镜像兼容,您就可以继续将新镜像标记为 `image:v1`,并且此标签的下游使用者能够获得更新而不会中断。

如果您以后发布不兼容的更新,则切换到新的标签,例如 `image:v2`。这允许下游使用者随意升级到新版本,但不会被新的不兼容镜像意外破坏。任何使用 `image:latest` 的下游使用者都会承担引入任何不兼容更改的风险。

避免多个进程

不要在一个容器内启动多个服务,例如数据库和 `SSHD`。这没有必要,因为容器很轻量级,可以轻松链接在一起以编排多个进程。Red Hat OpenShift Service on AWS 允许您通过将相关的镜像分组到单个 Pod 中来轻松地共同定位和共同管理它们。

此共同定位确保容器共享网络命名空间和用于通信的存储。更新也较少中断,因为每个镜像都可以更不频繁地独立更新。信号处理流程在单个进程中也更清晰,因为您不必管理将信号路由到生成的进程。

在包装器脚本中使用 `exec`

许多镜像使用包装器脚本在启动要运行的软件的进程之前进行一些设置。如果您的镜像使用此类脚本,则该脚本使用 `exec`,以便用您的软件替换脚本的进程。如果您不使用 `exec`,则容器运行时发送的信号将转到您的包装器脚本而不是您的软件的进程。这不是您想要的。

如果您有一个包装器脚本,它为某个服务器启动一个进程。您启动容器,例如使用 `podman run -i`,它运行包装器脚本,然后启动您的进程。如果您想使用 `CTRL+C` 关闭您的容器。如果您的包装器脚本使用 `exec` 启动服务器进程,`podman` 将 SIGINT 发送到服务器进程,一切都会按预期工作。如果您在包装器脚本中没有使用 `exec`,`podman` 将 SIGINT 发送到包装器脚本的进程,而您的进程会继续运行,就像什么也没发生一样。

还要注意,您的进程在容器中运行时为 `PID 1`。这意味着如果您的主进程终止,则整个容器将停止,取消您从 `PID 1` 进程启动的任何子进程。

清理临时文件

删除在构建过程中创建的所有临时文件。这还包括使用 `ADD` 命令添加的所有文件。例如,在执行 `yum install` 操作后运行 `yum clean` 命令。

您可以通过如下创建 `RUN` 语句来防止 `yum` 缓存最终出现在镜像层中

RUN yum -y install mypackage && yum -y install myotherpackage && yum clean all -y

请注意,如果您改为编写

RUN yum -y install mypackage
RUN yum -y install myotherpackage && yum clean all -y

那么第一次 `yum` 调用会在该层中留下额外的文件,并且在稍后运行 `yum clean` 操作时无法删除这些文件。额外的文件在最终镜像中不可见,但它们存在于底层中。

当前的容器构建流程不允许在后面的层中运行命令来缩减前面层中已删除内容所占用的空间。但是,这在未来可能会改变。这意味着,如果您在后面的层中执行rm命令,虽然文件被隐藏了,但这不会减小要下载的镜像的整体大小。因此,与yum clean示例一样,最好在创建文件的同一命令中删除文件(如果可能),这样它们就不会写入层中。

此外,在单个RUN语句中执行多个命令可以减少镜像中的层数,从而提高下载和解压速度。

按正确的顺序放置指令

容器构建器读取Dockerfile并从上到下运行指令。每个成功执行的指令都会创建一个层,该层可以在下次构建此镜像或其他镜像时重用。将很少更改的指令放在Dockerfile的顶部非常重要。这样做可以确保相同镜像的后续构建非常快,因为缓存不会因上层更改而失效。

例如,如果您正在处理一个包含ADD命令(用于安装您正在迭代的文件)和RUN命令(用于yum install一个包)的Dockerfile,最好将ADD命令放在最后。

FROM foo
RUN yum -y install mypackage && yum clean all -y
ADD myfile /test/myfile

这样,每次您编辑myfile并重新运行podman builddocker build时,系统都会重用yum命令的缓存层,并且只为ADD操作生成新的层。

如果您改为这样编写Dockerfile

FROM foo
ADD myfile /test/myfile
RUN yum -y install mypackage && yum clean all -y

那么每次您更改myfile并重新运行podman builddocker build时,ADD操作都会使RUN层的缓存失效,因此yum操作也必须重新运行。

标记重要端口

EXPOSE指令使容器中的端口可用于主机系统和其他容器。虽然可以使用podman run调用指定应公开的端口,但在Dockerfile中使用EXPOSE指令可以通过显式声明软件运行所需的端口,使人和软件更容易使用您的镜像。

  • 公开的端口显示在与使用您的镜像创建的容器关联的podman ps下。

  • 公开的端口存在于podman inspect返回的镜像元数据中。

  • 当您将一个容器链接到另一个容器时,公开的端口也会被链接。

设置环境变量

使用ENV指令设置环境变量是一个好习惯。一个例子是设置项目的版本。这使得人们无需查看Dockerfile即可轻松找到版本。另一个例子是宣传系统上可能被另一个进程使用的路径,例如JAVA_HOME

避免使用默认密码

避免设置默认密码。许多人扩展镜像并忘记删除或更改默认密码。如果生产环境中的用户被分配了一个众所周知的密码,这可能会导致安全问题。密码可以使用环境变量进行配置。

如果您确实选择设置默认密码,请确保在容器启动时显示相应的警告消息。该消息应告知用户默认密码的值,并解释如何更改它,例如设置哪个环境变量。

避免使用sshd

最好避免在镜像中运行sshd。您可以使用podman execdocker exec命令访问在本地主机上运行的容器。或者,您可以使用oc exec命令或oc rsh命令访问在AWS上的Red Hat OpenShift Service集群上运行的容器。在镜像中安装和运行sshd会打开额外的攻击媒介和安全补丁的需求。

使用卷存储持久化数据

镜像使用存储持久化数据。这样,AWS上的Red Hat OpenShift Service会将网络存储挂载到运行容器的节点上,如果容器移动到新的节点,存储就会重新挂载到该节点上。通过将卷用于所有持久性存储需求,即使容器重新启动或移动,内容也会保留。如果您的镜像将数据写入容器内的任意位置,则可能无法保留该内容。

即使在容器销毁后也需要保留的所有数据都必须写入卷。容器引擎支持容器的readonly标志,可用于严格执行关于不将数据写入容器中短暂存储的良好实践。现在围绕该功能设计您的镜像,可以更容易地在以后利用它。

在您的Dockerfile中显式定义卷,使镜像使用者很容易理解在运行您的镜像时必须定义哪些卷。

有关如何在AWS上的Red Hat OpenShift Service中使用卷的更多信息,请参阅Kubernetes文档

即使使用持久卷,您的每个镜像实例都有其自己的卷,并且文件系统不会在实例之间共享。这意味着卷不能用于在集群中共享状态。

AWS上的Red Hat OpenShift Service专用指南

以下是专门针对在AWS上的Red Hat OpenShift Service上使用而创建容器镜像时适用的指南。

启用镜像以用于源到镜像 (S2I)

对于旨在运行第三方提供的应用程序代码的镜像,例如设计用于运行开发人员提供的 Ruby 代码的 Ruby 镜像,您可以启用您的镜像以与源到镜像 (S2I)构建工具一起使用。S2I 是一个框架,它使编写将应用程序源代码作为输入并生成运行已组装应用程序的新镜像作为输出的镜像变得容易。

支持任意用户 ID

默认情况下,AWS上的Red Hat OpenShift Service使用任意分配的用户 ID 运行容器。这提供了额外的安全性,可以防止进程由于容器引擎漏洞而逃离容器,从而在主机节点上获得提升的权限。

为了使镜像支持作为任意用户运行,镜像中进程写入的目录和文件必须由 root 组拥有,并且该组可以读写。要执行的文件也必须具有组执行权限。

将以下内容添加到您的 Dockerfile 中,可以设置目录和文件的权限,以允许 root 组中的用户访问构建的镜像中的它们。

RUN chgrp -R 0 /some/directory && \
    chmod -R g=u /some/directory

由于容器用户始终是root组的成员,因此容器用户可以读取和写入这些文件。

更改容器敏感区域的目录和文件权限时必须小心。如果应用于敏感区域(例如/etc/passwd文件),则此类更改可能允许意外用户修改这些文件,从而可能暴露容器或主机。CRI-O支持将任意用户ID插入容器的/etc/passwd文件中。因此,无需更改权限。

此外,任何容器镜像中都不应存在/etc/passwd文件。如果存在,CRI-O容器运行时将无法将随机UID注入/etc/passwd文件。在这种情况下,容器在解析活动UID时可能会面临挑战。未能满足此要求可能会影响某些容器化应用程序的功能。

此外,在容器中运行的进程不得监听特权端口(低于1024的端口),因为它们不是以特权用户身份运行的。

使用服务进行镜像间通信

对于镜像需要与另一个镜像提供的服务进行通信的情况(例如,需要访问数据库镜像来存储和检索数据的Web前端镜像),您的镜像使用AWS上的Red Hat OpenShift服务。服务提供静态访问端点,该端点不会因容器停止、启动或移动而更改。此外,服务还为请求提供负载均衡。

提供常用库

对于旨在运行第三方提供的应用程序代码的镜像,请确保您的镜像包含平台常用的库。特别是,为平台上常用的数据库提供数据库驱动程序。例如,如果您正在创建Java框架镜像,请提供MySQL和PostgreSQL的JDBC驱动程序。这样做可以避免在应用程序组装时下载常用依赖项,从而加快应用程序镜像构建速度。它还可以简化应用程序开发人员确保满足所有依赖项所需的工作。

使用环境变量进行配置

您的镜像用户能够对其进行配置,而无需基于您的镜像创建下游镜像。这意味着运行时配置是使用环境变量处理的。对于简单的配置,运行进程可以直接使用环境变量。对于更复杂的配置或不支持此功能的运行时,可以通过定义在启动期间处理的模板配置文件来配置运行时。在此处理过程中,可以使用环境变量提供的的值替换配置文件中的值,或用于决定在配置文件中设置哪些选项。

还可以建议使用环境变量将证书和密钥等密钥传递到容器中。这确保密钥值不会最终提交到镜像中并泄漏到容器镜像注册表。

提供环境变量允许您的镜像使用者自定义行为,例如数据库设置、密码和性能调整,而无需在您的镜像之上引入新层。相反,他们只需在定义 Pod 时定义环境变量值,并在无需重新构建镜像的情况下更改这些设置。

对于极其复杂的场景,也可以使用在运行时挂载到容器中的卷来提供配置。但是,如果您选择这样做,则必须确保您的镜像在缺少必要的卷或配置时在启动时提供清晰的错误消息。

本主题与“使用服务进行镜像间通信”主题相关,因为数据源之类的配置是根据提供服务端点信息的环境变量定义的。这允许应用程序动态使用在AWS上的Red Hat OpenShift环境中定义的数据源服务,而无需修改应用程序镜像。

此外,调整是通过检查容器的cgroups设置来完成的。这允许镜像自行调整到可用的内存、CPU和其他资源。例如,基于Java的镜像会根据cgroup最大内存参数调整其堆大小,以确保它们不会超过限制并出现内存不足错误。

设置镜像元数据

定义镜像元数据有助于AWS上的Red Hat OpenShift服务更好地使用您的容器镜像,从而使AWS上的Red Hat OpenShift服务能够为使用您的镜像的开发人员创造更好的体验。例如,您可以添加元数据来提供对镜像的有用描述,或提供对所需其他镜像的建议。

集群

您必须完全理解运行多个镜像实例的含义。在最简单的情况下,服务的负载均衡功能负责将流量路由到所有镜像实例。但是,许多框架必须共享信息才能执行领导者选举或故障转移状态;例如,在会话复制中。

考虑您的实例在AWS上的Red Hat OpenShift服务中运行时如何实现这种通信。尽管Pod可以彼此直接通信,但它们的IP地址会在Pod启动、停止或移动时发生更改。因此,您的集群方案必须是动态的非常重要。

日志记录

最好将所有日志发送到标准输出。AWS上的Red Hat OpenShift服务收集容器的标准输出并将其发送到集中式日志服务,在那里可以查看它。如果必须分离日志内容,请在输出前添加适当的关键字,以便过滤消息。

如果您的镜像记录到文件中,用户必须使用手动操作进入正在运行的容器并检索或查看日志文件。

存活探针和就绪探针

记录可与您的镜像一起使用的示例存活探针和就绪探针。这些探针允许用户自信地部署您的镜像,确保只有在容器准备好处理请求后才将流量路由到该容器,并且如果进程进入不健康状态,则会重新启动容器。

模板

考虑为您的镜像提供一个示例模板。模板为用户提供了一种快速轻松地部署具有有效配置的镜像的方法。为完整起见,您的模板必须包含您使用镜像记录的存活探针和就绪探针。

在镜像中包含元数据

定义镜像元数据有助于 Red Hat OpenShift Service on AWS 更好地使用您的容器镜像,从而为使用您镜像的开发者创造更好的体验。例如,您可以添加元数据来提供镜像的有用描述,或提供可能也需要的其他镜像的建议。

本主题仅定义当前用例所需的元数据。将来可能会添加其他元数据或用例。

定义镜像元数据

您可以使用Dockerfile中的LABEL指令来定义镜像元数据。标签类似于环境变量,它们是附加到镜像或容器的键值对。标签与环境变量的不同之处在于,它们对正在运行的应用程序不可见,并且它们也可以用于快速查找镜像和容器。

Docker 文档,了解更多关于LABEL指令的信息。

标签名称通常是带命名空间的。命名空间相应地设置以反映将获取标签并使用它们的项目。对于 Red Hat OpenShift Service on AWS,命名空间设置为io.openshift;对于 Kubernetes,命名空间为io.k8s

有关格式的详细信息,请参阅Docker 自定义元数据文档。

表 1. 支持的元数据
变量 描述

io.openshift.tags

此标签包含以逗号分隔的字符串值列表表示的一组标签。标签是将容器镜像分类到广泛的功能领域的方法。标签帮助 UI 和生成工具在应用程序创建过程中建议相关的容器镜像。

LABEL io.openshift.tags   mongodb,mongodb24,nosql

io.openshift.wants

指定生成工具和 UI 用于提供相关建议的标签列表,如果您还没有带有指定标签的容器镜像。例如,如果容器镜像需要mysqlredis,而您没有带有redis标签的容器镜像,则 UI 可以建议您将此镜像添加到您的部署中。

LABEL io.openshift.wants   mongodb,redis

io.k8s.description

此标签可用于向容器镜像使用者提供有关此镜像提供的服务或功能的更详细信息。然后,UI 可以将此描述与容器镜像名称一起使用,为最终用户提供更友好的信息。

LABEL io.k8s.description The MySQL 5.5 Server with master-slave replication support

io.openshift.non-scalable

镜像可以使用此变量来建议它不支持缩放。然后,UI 将此信息传达给该镜像的使用者。不可缩放意味着replicas的值最初不应设置为高于1

LABEL io.openshift.non-scalable     true

io.openshift.min-memoryio.openshift.min-cpu

此标签建议容器镜像正常工作所需的资源量。UI 可以警告用户部署此容器镜像可能会超过其用户配额。值必须与 Kubernetes 数量兼容。

LABEL io.openshift.min-memory 16Gi
LABEL io.openshift.min-cpu     4

使用 source-to-image 创建镜像

Source-to-image (S2I) 是一个框架,它简化了编写将应用程序源代码作为输入并生成运行已组装应用程序的新镜像的镜像的过程。

使用 S2I 构建可重复容器镜像的主要优势在于易于开发者使用。作为构建器镜像作者,您必须了解两个基本概念才能使您的镜像提供最佳的 S2I 性能:构建过程和 S2I 脚本。

了解 source-to-image 构建过程

构建过程包括以下三个基本要素,它们组合成最终的容器镜像

  • 源代码

  • Source-to-image (S2I) 脚本

  • 构建器镜像

S2I 使用构建器镜像作为第一个FROM指令生成一个 Dockerfile。然后将 S2I 生成的 Dockerfile 传递给 Buildah。

如何编写 source-to-image 脚本

您可以使用任何编程语言编写 source-to-image (S2I) 脚本,只要脚本在构建器镜像内可执行即可。S2I 支持多个选项,提供assemble/run/save-artifacts脚本。在每次构建中,都将按以下顺序检查所有这些位置

  1. 在构建配置中指定的脚本。

  2. 在应用程序源代码.s2i/bin目录中找到的脚本。

  3. 在带有io.openshift.s2i.scripts-url标签的默认镜像 URL 中找到的脚本。

在镜像中指定的io.openshift.s2i.scripts-url标签和在构建配置中指定的脚本都可以采用以下形式之一

  • image:///path_to_scripts_dir:镜像内 S2I 脚本所在的目录的绝对路径。

  • file:///path_to_scripts_dir:主机上 S2I 脚本所在的目录的相对或绝对路径。

  • http(s)://path_to_scripts_dir:S2I 脚本所在的目录的 URL。

表 2. S2I 脚本
脚本 描述

assemble

assemble脚本从源代码构建应用程序构件,并将它们放置到镜像内的适当目录中。此脚本是必需的。此脚本的工作流程为:

  1. 可选:还原构建构件。如果您想支持增量构建,请确保也定义save-artifacts

  2. 将应用程序源代码放置在所需位置。

  3. 构建应用程序构件。

  4. 将构件安装到适合它们运行的位置。

run

run脚本执行您的应用程序。此脚本是必需的。

save-artifacts

save-artifacts脚本收集所有可以加快后续构建过程的依赖项。此脚本是可选的。例如:

  • 对于 Ruby,由 Bundler 安装的gems

  • 对于 Java,.m2内容。

这些依赖项被收集到一个tar文件中,并流式传输到标准输出。

usage

usage脚本允许您告知用户如何正确使用您的镜像。此脚本是可选的。

test/run

test/run脚本允许您创建一个流程来检查镜像是否正常工作。此脚本是可选的。该流程的建议流程为:

  1. 构建镜像。

  2. 运行镜像以验证usage脚本。

  3. 运行s2i build以验证assemble脚本。

  4. 可选:再次运行s2i build以验证save-artifactsassemble脚本的保存和还原构件功能。

  5. 运行镜像以验证测试应用程序是否正在工作。

建议将test/run脚本构建的测试应用程序放置在镜像存储库中的test/test-app目录中。

S2I 脚本示例

以下 S2I 脚本示例是用 Bash 编写的。每个示例都假设其tar内容已解压到/tmp/s2i目录中。

assemble脚本
#!/bin/bash

# restore build artifacts
if [ "$(ls /tmp/s2i/artifacts/ 2>/dev/null)" ]; then
    mv /tmp/s2i/artifacts/* $HOME/.
fi

# move the application source
mv /tmp/s2i/src $HOME/src

# build application artifacts
pushd ${HOME}
make all

# install the artifacts
make install
popd
run脚本
#!/bin/bash

# run the application
/opt/application/run.sh
save-artifacts脚本
#!/bin/bash

pushd ${HOME}
if [ -d deps ]; then
    # all deps contents to tar stream
    tar cf - deps
fi
popd
usage脚本
#!/bin/bash

# inform the user how to use the image
cat <<EOF
This is a S2I sample builder image, to use it, install
https://github.com/openshift/source-to-image
EOF
其他资源

关于测试 source-to-image 镜像

作为 Source-to-Image (S2I) 构建器镜像作者,您可以在本地测试您的 S2I 镜像,并使用 Red Hat OpenShift Service on AWS 构建系统进行自动化测试和持续集成。

S2I 需要assemblerun脚本才能成功运行 S2I 构建。提供save-artifacts脚本可以重用构建构件,提供usage脚本可以确保在有人在 S2I 外运行容器镜像时将使用信息打印到控制台。

测试 S2I 镜像的目标是确保所有描述的命令都能正常工作,即使基础容器镜像已更改或命令使用的工具已更新。

理解测试需求

test 脚本的标准位置是 test/run。此脚本由 AWS 上的 Red Hat OpenShift 服务 S2I 镜像构建器调用,它可以是一个简单的 Bash 脚本或一个静态 Go 二进制文件。

test/run 脚本执行 S2I 构建,因此您必须在 $PATH 中提供 S2I 二进制文件。如有需要,请按照S2I自述文件中的安装说明进行操作。

S2I 将应用程序源代码和构建器镜像组合在一起,因此要对其进行测试,您需要一个示例应用程序源代码来验证源代码是否成功转换为可运行的容器镜像。示例应用程序应该很简单,但它应该演练 assemblerun 脚本的关键步骤。

生成脚本和工具

S2I 工具附带强大的生成工具,可以加快创建新的 S2I 镜像的过程。s2i create 命令会生成所有必要的 S2I 脚本和测试工具以及 Makefile

$ s2i create <image_name> <destination_directory>

生成的 test/run 脚本必须进行调整才能有用,但它提供了一个良好的起点。

s2i create 命令生成的 test/run 脚本要求示例应用程序源代码位于 test/test-app 目录中。

本地测试

在本地运行 S2I 镜像测试最简单的方法是使用生成的 Makefile

如果您没有使用 s2i create 命令,您可以复制以下 Makefile 模板并将 IMAGE_NAME 参数替换为您的镜像名称。

Makefile 示例
IMAGE_NAME = openshift/ruby-20-centos7
CONTAINER_ENGINE := $(shell command -v podman 2> /dev/null | echo docker)

build:
	${CONTAINER_ENGINE} build -t $(IMAGE_NAME) .

.PHONY: test
test:
	${CONTAINER_ENGINE} build -t $(IMAGE_NAME)-candidate .
	IMAGE_NAME=$(IMAGE_NAME)-candidate test/run

基本的测试流程

test 脚本假设您已经构建了要测试的镜像。如有需要,请先构建 S2I 镜像。运行以下命令之一:

  • 如果您使用 Podman,请运行以下命令:

    $ podman build -t <builder_image_name>
  • 如果您使用 Docker,请运行以下命令:

    $ docker build -t <builder_image_name>

以下步骤描述了测试 S2I 镜像构建器的默认流程:

  1. 验证 usage 脚本是否正常工作

    • 如果您使用 Podman,请运行以下命令:

      $ podman run <builder_image_name> .
    • 如果您使用 Docker,请运行以下命令:

      $ docker run <builder_image_name> .
  2. 构建镜像

    $ s2i build file:///path-to-sample-app _<BUILDER_IMAGE_NAME>_ _<OUTPUT_APPLICATION_IMAGE_NAME>_
  3. 可选:如果您支持 save-artifacts,请再次运行步骤 2 以验证保存和恢复构件是否正常工作。

  4. 运行容器

    • 如果您使用 Podman,请运行以下命令:

      $ podman run <output_application_image_name>
    • 如果您使用 Docker,请运行以下命令:

      $ docker run <output_application_image_name>
  5. 验证容器是否正在运行且应用程序正在响应。

运行这些步骤通常足以判断构建器镜像是否按预期工作。

使用 AWS 上的 Red Hat OpenShift 服务构建镜像

拥有构成新的 S2I 构建器镜像的 Dockerfile 和其他构件后,您可以将它们放入 Git 仓库并使用 AWS 上的 Red Hat OpenShift 服务来构建和推送镜像。定义一个指向您的仓库的 Docker 构建。

如果您的 AWS 上的 Red Hat OpenShift 服务实例托管在公共 IP 地址上,则每次向您的 S2I 构建器镜像 GitHub 仓库推送时都可以触发构建。

您还可以使用 ImageChangeTrigger 来触发基于您更新的 S2I 构建器镜像的应用程序的重建。