RUN yum -y install mypackage && yum -y install myotherpackage && yum clean all -y
学习如何基于现成的预构建镜像创建您自己的容器镜像。此过程包括学习编写镜像的最佳实践、定义镜像元数据、测试镜像以及使用自定义构建器工作流创建可在 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`,则容器运行时发送的信号将转到您的包装器脚本而不是您的软件的进程。这不是您想要的。
如果您有一个包装器脚本,它为某个服务器启动一个进程。您启动容器,例如使用 `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 build
或docker build
时,系统都会重用yum
命令的缓存层,并且只为ADD
操作生成新的层。
如果您改为这样编写Dockerfile
:
FROM foo
ADD myfile /test/myfile
RUN yum -y install mypackage && yum clean all -y
那么每次您更改myfile
并重新运行podman build
或docker build
时,ADD
操作都会使RUN
层的缓存失效,因此yum
操作也必须重新运行。
EXPOSE
指令使容器中的端口可用于主机系统和其他容器。虽然可以使用podman run
调用指定应公开的端口,但在Dockerfile
中使用EXPOSE
指令可以通过显式声明软件运行所需的端口,使人和软件更容易使用您的镜像。
公开的端口显示在与使用您的镜像创建的容器关联的podman ps
下。
公开的端口存在于podman inspect
返回的镜像元数据中。
当您将一个容器链接到另一个容器时,公开的端口也会被链接。
使用ENV
指令设置环境变量是一个好习惯。一个例子是设置项目的版本。这使得人们无需查看Dockerfile
即可轻松找到版本。另一个例子是宣传系统上可能被另一个进程使用的路径,例如JAVA_HOME
。
避免设置默认密码。许多人扩展镜像并忘记删除或更改默认密码。如果生产环境中的用户被分配了一个众所周知的密码,这可能会导致安全问题。密码可以使用环境变量进行配置。
如果您确实选择设置默认密码,请确保在容器启动时显示相应的警告消息。该消息应告知用户默认密码的值,并解释如何更改它,例如设置哪个环境变量。
最好避免在镜像中运行sshd
。您可以使用podman exec
或docker 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上使用而创建容器镜像时适用的指南。
对于旨在运行第三方提供的应用程序代码的镜像,例如设计用于运行开发人员提供的 Ruby 代码的 Ruby 镜像,您可以启用您的镜像以与源到镜像 (S2I)构建工具一起使用。S2I 是一个框架,它使编写将应用程序源代码作为输入并生成运行已组装应用程序的新镜像作为输出的镜像变得容易。
默认情况下,AWS上的Red Hat OpenShift Service使用任意分配的用户 ID 运行容器。这提供了额外的安全性,可以防止进程由于容器引擎漏洞而逃离容器,从而在主机节点上获得提升的权限。
为了使镜像支持作为任意用户运行,镜像中进程写入的目录和文件必须由 root 组拥有,并且该组可以读写。要执行的文件也必须具有组执行权限。
将以下内容添加到您的 Dockerfile 中,可以设置目录和文件的权限,以允许 root 组中的用户访问构建的镜像中的它们。
RUN chgrp -R 0 /some/directory && \
chmod -R g=u /some/directory
由于容器用户始终是root组的成员,因此容器用户可以读取和写入这些文件。
更改容器敏感区域的目录和文件权限时必须小心。如果应用于敏感区域(例如 此外,任何容器镜像中都不应存在 |
此外,在容器中运行的进程不得监听特权端口(低于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 自定义元数据文档。
变量 | 描述 |
---|---|
|
此标签包含以逗号分隔的字符串值列表表示的一组标签。标签是将容器镜像分类到广泛的功能领域的方法。标签帮助 UI 和生成工具在应用程序创建过程中建议相关的容器镜像。 LABEL io.openshift.tags mongodb,mongodb24,nosql |
|
指定生成工具和 UI 用于提供相关建议的标签列表,如果您还没有带有指定标签的容器镜像。例如,如果容器镜像需要 LABEL io.openshift.wants mongodb,redis |
|
此标签可用于向容器镜像使用者提供有关此镜像提供的服务或功能的更详细信息。然后,UI 可以将此描述与容器镜像名称一起使用,为最终用户提供更友好的信息。 LABEL io.k8s.description The MySQL 5.5 Server with master-slave replication support |
|
镜像可以使用此变量来建议它不支持缩放。然后,UI 将此信息传达给该镜像的使用者。不可缩放意味着 LABEL io.openshift.non-scalable true |
|
此标签建议容器镜像正常工作所需的资源量。UI 可以警告用户部署此容器镜像可能会超过其用户配额。值必须与 Kubernetes 数量兼容。 LABEL io.openshift.min-memory 16Gi LABEL io.openshift.min-cpu 4 |
Source-to-image (S2I) 是一个框架,它简化了编写将应用程序源代码作为输入并生成运行已组装应用程序的新镜像的镜像的过程。
使用 S2I 构建可重复容器镜像的主要优势在于易于开发者使用。作为构建器镜像作者,您必须了解两个基本概念才能使您的镜像提供最佳的 S2I 性能:构建过程和 S2I 脚本。
构建过程包括以下三个基本要素,它们组合成最终的容器镜像
源代码
Source-to-image (S2I) 脚本
构建器镜像
S2I 使用构建器镜像作为第一个FROM
指令生成一个 Dockerfile。然后将 S2I 生成的 Dockerfile 传递给 Buildah。
您可以使用任何编程语言编写 source-to-image (S2I) 脚本,只要脚本在构建器镜像内可执行即可。S2I 支持多个选项,提供assemble
/run
/save-artifacts
脚本。在每次构建中,都将按以下顺序检查所有这些位置
在构建配置中指定的脚本。
在应用程序源代码.s2i/bin
目录中找到的脚本。
在带有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。
脚本 | 描述 | ||
---|---|---|---|
|
|
||
|
|
||
|
这些依赖项被收集到一个 |
||
|
|
||
|
|
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 (S2I) 构建器镜像作者,您可以在本地测试您的 S2I 镜像,并使用 Red Hat OpenShift Service on AWS 构建系统进行自动化测试和持续集成。
S2I 需要assemble
和run
脚本才能成功运行 S2I 构建。提供save-artifacts
脚本可以重用构建构件,提供usage
脚本可以确保在有人在 S2I 外运行容器镜像时将使用信息打印到控制台。
测试 S2I 镜像的目标是确保所有描述的命令都能正常工作,即使基础容器镜像已更改或命令使用的工具已更新。
test
脚本的标准位置是 test/run
。此脚本由 AWS 上的 Red Hat OpenShift 服务 S2I 镜像构建器调用,它可以是一个简单的 Bash 脚本或一个静态 Go 二进制文件。
test/run
脚本执行 S2I 构建,因此您必须在 $PATH
中提供 S2I 二进制文件。如有需要,请按照S2I自述文件中的安装说明进行操作。
S2I 将应用程序源代码和构建器镜像组合在一起,因此要对其进行测试,您需要一个示例应用程序源代码来验证源代码是否成功转换为可运行的容器镜像。示例应用程序应该很简单,但它应该演练 assemble
和 run
脚本的关键步骤。
S2I 工具附带强大的生成工具,可以加快创建新的 S2I 镜像的过程。s2i create
命令会生成所有必要的 S2I 脚本和测试工具以及 Makefile
。
$ s2i create <image_name> <destination_directory>
生成的 test/run
脚本必须进行调整才能有用,但它提供了一个良好的起点。
|
在本地运行 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 镜像构建器的默认流程:
验证 usage
脚本是否正常工作
如果您使用 Podman,请运行以下命令:
$ podman run <builder_image_name> .
如果您使用 Docker,请运行以下命令:
$ docker run <builder_image_name> .
构建镜像
$ s2i build file:///path-to-sample-app _<BUILDER_IMAGE_NAME>_ _<OUTPUT_APPLICATION_IMAGE_NAME>_
可选:如果您支持 save-artifacts
,请再次运行步骤 2 以验证保存和恢复构件是否正常工作。
运行容器
如果您使用 Podman,请运行以下命令:
$ podman run <output_application_image_name>
如果您使用 Docker,请运行以下命令:
$ docker run <output_application_image_name>
验证容器是否正在运行且应用程序正在响应。
运行这些步骤通常足以判断构建器镜像是否按预期工作。