RUN yum -y install mypackage && yum -y install myotherpackage && yum clean all -y
学习如何基于现成的预构建镜像创建您自己的容器镜像。此过程包括学习编写镜像的最佳实践、定义镜像的元数据、测试镜像以及使用自定义构建器工作流程创建可与 OpenShift Container Platform 一起使用的镜像。创建镜像后,您可以将其推送到 OpenShift 镜像注册表。
在创建用于 OpenShift Container Platform 的容器镜像时,镜像作者需要考虑许多最佳实践,以确保这些镜像的用户获得良好的体验。由于镜像旨在保持不变并按原样使用,因此以下准则有助于确保您的镜像在 OpenShift Container Platform 上易于使用和高度可用。
以下准则适用于一般容器镜像创建,无论这些镜像是否在 OpenShift Container Platform 上使用。
尽可能使用 `FROM` 语句基于适当的上游镜像构建您的镜像。这确保您的镜像在更新时可以轻松地从上游镜像中获取安全修复程序,而无需直接更新您的依赖项。
此外,在 `FROM` 指令中使用标签,例如 `rhel:rhel7`,以便用户清楚地了解您的镜像基于哪个版本的镜像。使用除 `latest` 之外的标签可确保您的镜像不会受到可能进入上游镜像 `latest` 版本的重大更改的影响。
在标记您自己的镜像时,尝试在标签内保持向后兼容性。例如,如果您提供名为 `image` 的镜像,并且它当前包含版本 `1.0`,则您可以提供 `image:v1` 的标签。当您更新镜像时,只要它继续与原始镜像兼容,您就可以继续将新镜像标记为 `image:v1`,并且此标签的下游使用者能够获得更新而不会中断。
如果您稍后发布不兼容的更新,则切换到新的标签,例如 `image:v2`。这允许下游使用者根据需要升级到新版本,但不会因新的不兼容镜像而意外中断。任何使用 `image:latest` 的下游使用者都承担引入任何不兼容更改的风险。
不要在一个容器内启动多个服务,例如数据库和 `SSHD`。这没有必要,因为容器很轻量级,并且可以轻松地链接在一起以编排多个进程。OpenShift Container Platform 允许您通过将相关的镜像分组到单个 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
命令访问在OpenShift Container Platform集群上运行的容器。在镜像中安装和运行sshd
会增加额外的攻击途径和安全补丁的需求。
镜像使用卷来存储持久化数据。这样,OpenShift Container Platform就会将网络存储挂载到运行容器的节点上,如果容器迁移到新的节点,存储也会重新挂载到该节点。通过对所有持久化存储需求使用卷,即使容器重启或迁移,内容也能保留。如果您的镜像将数据写入容器内的任意位置,则这些内容可能无法保留。
所有需要在容器销毁后仍然保留的数据都必须写入卷。容器引擎支持容器的readonly
标志,这可以用来严格执行关于不将数据写入容器中临时存储的良好实践。现在围绕此功能设计您的镜像,可以更容易地在以后利用它。
在您的Dockerfile
中显式定义卷,可以让镜像使用者轻松了解在运行您的镜像时必须定义哪些卷。
请参阅Kubernetes文档,了解更多关于如何在OpenShift Container Platform中使用卷的信息。
即使使用持久卷,您的镜像的每个实例都有其自己的卷,并且文件系统不会在实例之间共享。这意味着卷不能用于在集群中共享状态。 |
以下是专门针对在OpenShift Container Platform上使用而创建容器镜像时适用的指南。
对于旨在运行由第三方提供的应用程序代码的镜像,例如设计用于运行由开发人员提供的Ruby代码的Ruby镜像,您可以启用您的镜像以与Source-to-Image (S2I)构建工具一起工作。S2I是一个框架,它使编写将应用程序源代码作为输入并生成运行已组装应用程序的新镜像作为输出的镜像变得容易。
默认情况下,OpenShift Container Platform使用任意分配的用户ID运行容器。这提供了额外的安全措施,可以防止由于容器引擎漏洞而导致进程逃逸容器,从而在主机节点上获得提升的权限。
为了使镜像支持以任意用户身份运行,镜像中进程写入的目录和文件必须由root组拥有,并且该组可读写。要执行的文件也必须具有组执行权限。
将以下内容添加到您的Dockerfile中,可以设置目录和文件的权限,以允许root组中的用户访问它们。
RUN chgrp -R 0 /some/directory && \
chmod -R g=u /some/directory
因为容器用户始终是root组的成员,所以容器用户可以读取和写入这些文件。
更改容器敏感区域的目录和文件权限时必须小心。如果应用于敏感区域(例如 此外, |
此外,由于容器中运行的进程并非以特权用户身份运行,因此它们不得监听特权端口(低于1024的端口)。
如果您的S2I镜像不包含带有数字用户的 |
对于您的镜像需要与另一个镜像提供的服务进行通信的情况,例如需要访问数据库镜像来存储和检索数据的Web前端镜像,您的镜像会使用OpenShift Container Platform服务。服务提供静态的访问端点,该端点不会随着容器的停止、启动或迁移而改变。此外,服务还为请求提供负载均衡。
对于旨在运行由第三方提供的应用程序代码的镜像,请确保您的镜像包含平台常用的库。特别是,为与您的平台一起使用的常用数据库提供数据库驱动程序。例如,如果您正在创建Java框架镜像,请提供MySQL和PostgreSQL的JDBC驱动程序。这样做可以防止在应用程序组装时下载常用依赖项,从而加快应用程序镜像构建速度。它还简化了应用程序开发人员确保满足所有依赖项所需的工作。
您的镜像的用户能够对其进行配置,而无需基于您的镜像创建下游镜像。这意味着运行时配置是使用环境变量处理的。对于简单的配置,运行中的进程可以直接使用环境变量。对于更复杂的配置或不支持此功能的运行时,可以通过定义在启动期间处理的模板配置文件来配置运行时。在此处理过程中,可以使用环境变量提供的的值替换配置文件中的值,或用于决定在配置文件中设置哪些选项。
也可以并且建议使用环境变量将证书和密钥等密钥传递到容器中。这确保了密钥值不会最终提交到镜像中并泄漏到容器镜像注册表中。
提供环境变量允许您的镜像的使用者自定义行为,例如数据库设置、密码和性能调整,而无需在您的镜像之上引入新的层。相反,他们只需在定义pod时定义环境变量值,并在无需重建镜像的情况下更改这些设置。
对于极其复杂的场景,也可以使用在运行时挂载到容器中的卷来提供配置。但是,如果您选择这样做,您必须确保您的镜像在缺少必要的卷或配置时在启动时提供清晰的错误消息。
本主题与“使用服务进行图像间通信”主题相关,因为数据源之类的配置是根据提供服务端点信息的環境变量定义的。这允许应用程序动态地使用在 OpenShift Container Platform 环境中定义的数据源服务,而无需修改应用程序镜像。
此外,调整是通过检查容器的cgroups
设置来完成的。这允许镜像根据可用的内存、CPU和其他资源来自我调整。例如,基于 Java 的镜像会根据cgroup
的最大内存参数调整其堆大小,以确保它们不会超过限制并出现内存不足错误。
定义镜像元数据有助于 OpenShift Container Platform 更好地使用您的容器镜像,从而为使用您的镜像的开发人员创造更好的体验。例如,您可以添加元数据来提供镜像的有用描述,或提供其他所需镜像的建议。
您必须充分理解运行多个镜像实例的含义。在最简单的情况下,服务的负载均衡功能负责将流量路由到镜像的所有实例。但是,许多框架必须共享信息才能执行领导者选举或故障转移状态;例如,在会话复制中。
考虑您的实例在 OpenShift Container Platform 中运行时如何实现这种通信。尽管 Pod 可以直接相互通信,但只要 Pod 启动、停止或移动,它们的 IP 地址就会发生变化。因此,您的集群方案必须是动态的非常重要。
最好将所有日志发送到标准输出。OpenShift Container Platform 收集来自容器的标准输出并将其发送到中央日志服务,在那里可以查看它。如果必须分离日志内容,请在输出前面加上适当的关键字,以便可以过滤消息。
如果您的镜像记录到文件中,用户必须使用手动操作进入正在运行的容器并检索或查看日志文件。
定义镜像元数据有助于 OpenShift Container Platform 更好地使用您的容器镜像,从而为使用您的镜像的开发人员创造更好的体验。例如,您可以添加元数据来提供镜像的有用描述,或提供其他可能也需要的镜像建议。
本主题仅定义当前用例集所需的元数据。将来可能会添加其他元数据或用例。
您可以使用Dockerfile
中的LABEL
指令定义镜像元数据。标签类似于环境变量,它们是附加到镜像或容器的键值对。标签与环境变量的不同之处在于它们对正在运行的应用程序不可见,并且它们还可以用于快速查找镜像和容器。
有关LABEL
指令的更多信息,请参阅Docker 文档。
标签名称通常是带命名空间的。命名空间将相应地设置以反映将获取标签并使用它们的项目。对于 OpenShift Container Platform,命名空间设置为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
作为源到镜像 (S2I) 构建器镜像的作者,您可以本地测试您的 S2I 镜像,并使用 OpenShift Container Platform 构建系统进行自动化测试和持续集成。
S2I 需要assemble
和run
脚本才能成功运行 S2I 构建。提供save-artifacts
脚本可以重用构建工件,提供usage
脚本可以确保在有人在 S2I 之外运行容器镜像时将使用信息打印到控制台。
测试 S2I 镜像的目标是确保所有这些命令都能正常工作,即使基础容器镜像已更改或命令使用的工具已更新。
test
脚本的标准位置是test/run
。此脚本由 OpenShift Container Platform 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>
验证容器正在运行且应用程序正在响应。
运行这些步骤通常足以判断构建器镜像是否按预期工作。