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