如何用S2I创建一个builder image
【编者的话】众所周知,在容器编排领域,Kubernetes的市场占有率遥遥领先。但编者认为Kubernetes是面向运维人员的分布式系统,所以Redhat公司在Kubernetes的基础上推出了OpenShift,除了支持Kubernetes原有的所有功能外,针对Paas平台的特点,对Kubernetes进行了深度开发。文中介绍的S2I就是OpenShift之所以区别于Kubernetets,能成为PaaS平台的实现中一个主要环节。通过选择对应的image,编写assmble和run的脚本,最终生成包含依靠源代码生成的可以执行文件的docker image,并利用OpenShift现有的体系架构(BC和DC)直接发布,完成所有的应用的生命周期管理,让开发者真正仅仅关注与代码本身,这才是一个真正的Paas平台需要实现的。
Source-To-Image(S2I)是一个在创建builder image时可以独立运行的常用的工具。在OpenShiftV3里,它还是一个创建应用程序的主要手段。这是因为它具备如下优势:
- 速度快 —— 用S2I在应用程序编译的过程中可以在不创建新的layer的情况下执行大量的复杂操作,导致编译速度加快。
- 可以打补丁 —— 比如当你发现你的image存在一个安全问题时,S2I可以帮助你持续不断的rebuild你的应用程序。
- 用户效率高 —— S2I防止开发人员随意的用yum对image进行安装操作,因为这种操作会拖慢开发迭代的效率。
- 生态性 —— S2I鼓励共享一个生态系统的image,你可以利用它去运行你的应用程序。
这篇文档就是关于如何去创建一个简单的S2I的builder image。
总的来说,S2I利用源代码和builder Docker image去生成一个新的Docker image。S2I这个项目本身其实已经包含了一些常用的Docker builder image,比如用来创建Python和Ruby的builder image,当然你也可以根据需要去扩展它。
在讨论如何去创建一个builder image之前,我们先来解释一下S2I是如何工作的以及build image过程中builder image的角色。S2I(Source-To-Image),从名字上可以知道,它其实是负责把你应用程序源代码转换成可以在OpenShiftV3或者用
docker run
运行的Docker Image。所以这个Builder image包含了一些能够生成可执行镜像的所需的能力(也成为:build artifact)。整个的build过程包含对生成的image的调用,分几个步骤完成。在这边文章里我们用一个最简单的流程说明:- 下载S2I脚本(或者用builder image 里面的)
- 下载你的应用程序代码
- S2I stream脚本们和应用程序代码到builder image的容器里
- 在Build image里执行定于好的编译脚本(这个脚本就是上一步stream进去的)
- 保存这个容器到一个新的image
当然,为了让builder image完成所有这些工作,我们还需给它一些传入一些content。首先,因为builder image 负责实际意义上的build 你的源代码,所以它必须包含build和运行这个应用程序所依赖的libray和tools。比如,一个tomcat的builder image需要包含Tomcat,JDK和Maven。一个Python builder image包含Gunicorn或者cherry.py,SciPy的二进制包和pip。其次,它还需要包含一些执行build和run操作的逻辑脚本。这部分被下面两个S2I的脚本调用:
assemble
——负责build一个应用程序run
——负责运行一个应用程序
下面的我将介绍如何利用lighttpd server的docker image构造一个builder image去运行一个静态的html页面。
当然,这其中还包括了用GitHub存储创建过程中产生的文件。
现在,我们开始我的旅程,首先从https://github.com/openshift/s ... ases/ 获取最近版本的S2I(作者在写此篇文章时的最新版是1.0.9,译者翻译时是1.1.8)
步骤 1
S2I 是一个独立的命令,能够创建一个builder image所需要的目录结构,从这里可以得到更多关于它的信息,现在我们用下面命令创建出所必须的结构:s2i create lighttpd-centos7 s2i-lighttpd
我调用了
s2i create
命令传入了我想要的builder image的名字(lighttpd-centos7
)它的目录结构也已经被创建完成(s2i-lighttpd
)。如果这个目录不存在,这个命令就会为你创建它。所以,运行上面那个命令你将得到下面这个目录结构:s2i-lighttpd/
├── Dockerfile
├── Makefile
├── README.md
├── s2i
│ └── bin
│ ├── assemble
│ ├── run
│ ├── save-artifacts
│ └── usage
└── test
├── run
└── test-app
└── index.html
步骤 2
为了定制builder image,我们需要修改那个刚刚用S2I命令生成的Dockerfile
。# We are basing our builder image on openshift base-centos7 image
FROM openshift/base-centos7
Inform users who's the maintainer of this builder image
MAINTAINER Maciej Szulik <maszulik@redhat.com>
Inform about software versions being used inside the builder
ENV LIGHTTPD_VERSION=1.4.35
Set labels used in OpenShift to describe the builder images
LABEL io.k8s.description="Platform for serving static HTML files" \
io.k8s.display-name="Lighttpd 1.4.35" \
io.openshift.expose-services="8080:http" \
io.openshift.tags="builder,html,lighttpd"
Install the required software, namely Lighttpd and
RUN yum install -y lighttpd && \
# clean yum cache files, as they are not needed and will only make the image bigger in the end
yum clean all -y
Defines the location of the S2I
Although this is defined in openshift/base-centos7 image it's repeated here
to make it clear why the following COPY operation is happening
LABEL io.openshift.s2i.scripts-url=image:///usr/local/s2i
Copy the S2I scripts from ./.s2i/bin/ to /usr/local/s2i when making the builder image
COPY ./.s2i/bin/ /usr/local/s2i
Copy the lighttpd configuration file
COPY ./etc/ /opt/app-root/etc
Drop the root user and make the content of /opt/app-root owned by user 1001
RUN chown -R 1001:1001 /opt/app-root
Set the default user for the image, the user itself was created in the base image
USER 1001
Specify the ports the final image will expose
EXPOSE 8080
Set the default CMD to print the usage of the image, if somebody does docker run
CMD ["usage"]
步骤 3
一旦Dockerfile
修改完成,我们就可以填充builder image的剩余部分,也就是S2I的那些脚本。首先我们从assemble
脚本开始, 它是负责build我们的应用。在这个例子里,它仅仅负责拷贝源代码到Lighttpd server的目录下:#!/bin/bash -e
#
S2I assemble script for the 'lighttpd-centos7' image.
The 'assemble' script builds your application source ready to run.
#
For more information refer to the documentation:
https://github.com/openshift/s ... ge.md
#
echo "---> Installing application source"
cp -Rf /tmp/src/. ./
默认情况下,
s2i build
命令将源代码放在/tmp/src
目录下,在build过程中这个目录下的源代码文件和其他相关文件会被使用。当然你也可以通过修改io.openshift.s2i.destination
这个label或者传入--destination
这个参数去修改它,如果是那样的话源代码会被放在你指定的目录下的src
子目录下。在相面cp命令中的第二个参数./
是用来设置在openshift/base-centos7
中的工作目录,也就是/opt/app-root/src
。好,现在让我去处理第二个脚本文件
run
,故名思义它负责运行我们的应用程序。在我们的例子中,它会启动lighttd server:#!/bin/bash -e
#
S2I run script for the 'lighttpd-centos7' image.
The run script executes the server that runs your application.
#
For more information see the documentation:
https://github.com/openshift/s ... ge.md
#
exec lighttpd -D -f /opt/app-root/etc/lighttpd.conf
在上面的命令中我们用
exec
去run
lighttpd的进程。这是为了所有由Docker发送给lighttpd的信号都会输出到stdout
和stderr
。因为在我们的例子中,我们不关注增量build,所以我们可以去掉
save-artifacts
这个脚本。最后我们写一些关于如何使用这个build image的方法在usage
脚本中:#!/bin/bash -e
cat <<EOF
This is the lighttpd-centos7 S2I image:
To use it, install S2I: https://github.com/openshift/source-to-image
Sample invocation:
s2i build https://github.com/soltysh/sti-lighttpd.git --context-dir=test/test-app/ lighttpd-centos7 sample-app
You can then run the resulting image via:
docker run -p 8080:8080 sample-app
EOF
注意:修改这个脚本的执行权限,推荐为
755
。步骤 4
我们builder image还剩最后的一部分,一个关于lighttpd的配置文件。这个配置文件会被Dockerfile
和run
脚本去使用。如果没有这个配置文件,lighttpd恐怕不能够正常启动。我用下面的配置为lighttpd生成配置文件,将其保存在s2i-lighttpd/etc/lighttpd.conf
。# directory where the documents will be served from
server.document-root = "/opt/app-root/src"
port the server listens on
server.port = 8080
default file if none is provided in the URL
index-file.names = ( "index.html" )
configure specific mimetypes, otherwise application/octet-stream will be used for every file
mimetype.assign = (
".html" => "text/html",
".txt" => "text/plain",
".jpg" => "image/jpeg",
".png" => "image/png"
)
这是一个关于lighttpd的最简单配置文件,它仅仅设置了:
- lighttpd处理的源代码路径(
/opt/app-root/src
) - lighttpd监听的端口号(
8080
) - 默认文件的路径(
index.html
) - mimetype影射属性
到这步,我们的builder image已经做完了。我们能够确定的是
s2i-lighttpd
目录下的make
命令会在适当的时候去build它,当然你已经发现了这是因为里面有一个Makefile
,里面调用了docker build
命令。步骤 5
现在我们用一个简单的应用程序去测试这个builder image。我创建了一个index.html
在s2i-lighttpd/test/test-app
目录下:(译者注:在现在的1.18版本中,这个测试文件会被自动创建)<!doctype html>
<html>
<head>
<title>test-app</title>
</head>
<body>
<h1>Hello from lighttpd served index.html!</h1>
</body>
有了这个文件,我们现在可以运行s2i build了。在
s2i-lighttpd
目录下执行下面的命令:s2i build test/test-app/ lighttpd-centos7 sample-app
我们从
test/test-app
目录下创建了一个应用程序,用了我们刚刚的build image(lighttpd-centos7
)。生成了一个叫sample-app
的image。默认的S2I build 会打印出所有的由assemble
产生的输出,所以你会看到下面的输出在你的终端上(你可以忽略关于用本地builder image和非git目录产生的warning):---> Installing application source
现在我们实际的测试一下生成的image。运行这个image,暴露容器的8080端口到主机上:
docker run -p 8080:8080 sample-app
现在你可以通过http://localhost:8080/访问到
index.html
的内容了。步骤 6
这里面还有一些需要补充的部分:tests,大多数情况下可以用s2i-lighttpd/test/run
这个脚本去处理,假设你在Dockerfile
里选择默认的端口。在这个情况下,你可以在s2i-lighttpd
目录下运行下面这个命令:make test
注意:设置test/run的执行权限,建议设置成700。
这个脚本会运行
s2i build
(请却保s2i
在你的环境变量path里)用test-app
作为应用程序源代码,并且执行一些测试以确保image的可用性。这些测试包括:- 检查
s2i build
运行没有出错 - 检查
usage
脚本运行正确 - 测试最后生成的image
- 检查运行应用程序的容器有正确的响应
在这个例子里我没有增加对
test/run
的说明,因为那个文件太长了。感兴趣的读者可以去看一下https://github.com/soltysh/sti-lighttpd/。提交的历史可以准确地显示本文中给出的每一个步骤。现在祝贺你,已经有了一个可以使用的S2I builder image,使用S2I(Source Build strategy in OpenShift),处理一个在git代码仓库里的html文件,并且生成一个新的image去对外提供访问。总的来说,通过修改
assemble
和run
这两个脚本可以很容易的定义其他类型的builder image,然后用相应的image去运行对应类型的应用程序。原文连接:How to Create an S2I Builder Image (翻译:王晓轩)