
4.2.6 S2I实现应用容器化
1.OpenShift S2I的介绍
Source-to-Image(S2I)是红帽OpenShift开发的一个功能组件。目前可以独立于OpenShift运行。在社区里,被称为Java S2I,GitHub的地址为https://github.com/openshift/source-to-image/blob/master/README.md。
Java S2I容器镜像使开发人员能够通过指定应用程序源代码或已编译的Java二进制文件的位置,在OpenShift容器平台中按需自动构建、部署和运行Java应用程序。此外,S2I还支持Spring Boot、Eclipse Vert.x和WildFly Swarm。使用S2I的优势在于:
·简单而灵活:Java S2I镜像可以处理复杂的构建结构,但默认情况下它会假定在成功构建后/target目录中将运行要运行的JAR。我们也可以使用环境变量ARTIFACT_DIR指定要运行的JAR。此外,如果构建生成多个JAR文件,则可以使用环境变量JAVA_APP_JAR指定要运行的JAR文件。但是,在大多数情况下,我们所要做的就是直接指向源存储库,Java S2I容器镜像将自动完成配置。
·自动JVM内存配置:在OpenShift中通过Quota做资源限制。如果存在此类限制,Java S2I镜像将自动采用JVM内存设置,避免JVM过量使用内存。
·控制镜像大小:为了使镜像保持最小化,可以在构建最终容器镜像之前在S2I脚本中删除Maven本地仓库的数据。将环境变量MAVEN_CLEAR_REPO设置为true,则会在构建过程中删除Maven本地仓库。
2.OpenShift S2I原理解析
OpenShift可以直接基于Git存储库中存储的源代码来创建应用。oc new-app指定Git的URL后OpenShift会自动检测应用所用的编程语言,并选择合适的Builder镜像。当然,我们也可以手工指定Builder镜像。
那么,S2I应该如何识别Git上的内容来自动检测编程语言呢?它会检测Git上的特征文件,然后按照表4-1的方式选择构建方式。
例如,如果Git上有pom.xml文件,S2I将会因为需要使用jee的构建语言,所以查找jee的Image Stream。
表4-1 S2I特征文件与构建方式

OpenShift会采用多步算法来确定URL是否指向源代码存储库,如果是,则还会采用该算法来确定应由哪个Builder镜像来执行构建。以下是该算法的大致执行过程:
1)如果S2I能够成功访问指定源码地址的URL,则需S2I开始进行下一步。
2)OpenShift检索Git存储库,搜索名为Dockerfile的文件。如果找到了Dockerfile,则会触发容器镜像构建。如果没找到Dockerfile,则进行第3步。
3)OpenShift按照表4-1的方式判断源码的类型匹配构建语言,自动查找Image Stream。搜索到的第一个匹配的Image Stream会成为S2I Builder镜像。
4)如果没有匹配的构建语言,OpenShift会搜索名称与构建语言名称相匹配的Image Stream。搜索到的第一个匹配项会成为S2I Builder镜像。
S2I构建过程涉及三个基本组件:应用程序的源代码、S2I脚本、S2I Builder镜像。它们组合在一起构建成最终的应用镜像,实现应用容器化。
S2I的本质是按照一定的规则执行构建过程,依赖于一些固定名称的S2I脚本,这些脚本在各个阶段执行构建工作流程。它们的名称和作用分别如下:
·assemble:负责将已经下载到本地的外部代码进行编译打包,然后将打包好的应用包拷贝到镜像对应的运行目录中。
·run:负责启动运行assemble编译、拷贝好的应用。
·usage:告诉使用者如何使用该镜像。
·save-artifacts:脚本负责将构建所需要的所有依赖包收集到一个tar文件中。save-artifacts的好处是可以加速构建的过程。
·test/run:通过test/run脚本,可以创建简单的流程来验证镜像是否正常工作。
在上面的五个脚本中,assemble和run是必须有的,其他三个脚本是可选的。这些脚本可以由多种方式提供,默认使用存放在镜像/usr/local/s2i目录下的脚本文件,也可以由源代码仓库或HTTP Server提供这些脚本。
有了这五个S2I脚本之后,就可以按照如下构建流程构建应用镜像了。
·启动构建之后,启动builder容器,首先实例化基于S2I Builder的容器。从Git上获取应用源代码,创建一个包含S2I脚本(如果Git上没有,则使用Builder镜像中的脚本)和应用源码的tar文件,将tar文件传入S2I Builder实例化的容器中。
·tar文件被解压缩到S2I builder容器中io.openshift.s2i.destination标签指定的目录位置,默认是/tmp。
·如果是增量构建,assemble脚本会先恢复被save-artifacts脚本保存的build artifacts。
·assemble脚本从源代码构建应用程序,并将生成的二进制文件放入应用运行的目录。
·如果是增量构建,执行save-artifacts脚本并保存所有构建以来的artifacts到tar文件。
·完成assemble脚本执行以后生成最终应用镜像。
·builder容器调用podman命令,将构建好的应用镜像推送到内部镜像仓库。
·应用镜像推送到内部镜像仓库之后,触发DeploymentConfig中的Image Stream触发器自动部署应用镜像。
·应用镜像运行,并执行RUN脚本配置应用参数以及启动应用。
在介绍了S2I的原理后,接下来通过分析一个红帽的Builder镜像,进一步加深对S2I的理解。
3.红帽Builder镜像分析
目前红帽的官网提供24个Builder镜像(https://access.redhat.com/containers/#/explore),如图4-9所示。

图4-9 官方镜像类型
官方提供的Builder镜像包含了大部分开发语言,如图4-10所示。

图4-10 官方Builder镜像
红帽Builder镜像包含了红帽研发的大量心血,其脚本的书写判断条件十分全面。我们以webserver31-tomcat8-openshift这个Builder镜像为例来体会一下。
首先在OpenShift项目下,通过对应的Image Stream导入webserver31-tomcat8-openshift镜像。
# oc import-image jboss-webserver31-tomcat8-openshift --from=registry.redhat.io/ jboss-webserver-3/webserver31-tomcat8-openshift --confirm imagestream.image.openshift.io/webserver31-tomcat8-openshift imported
接下来,使用导入成功的Image Stream部署Pod,如图4-11所示。

图4-11 选择Image Stream和版本
在OpenShift中选择Image Stream和Tag,使用Tag 1.4。部署完成后,等待Pod正常运行,如图4-12所示。

图4-12 Pod正常运行
我们查看Pod的名称。
# oc get pods NAME READY STATUS RESTARTS AGE jboss-webserver31-tomcat8-openshift-597c7f995-wtqrf 1/1 Running 0 25s
登录到Pod,切换到/usr/local/s2i目录中,查看镜像中的S2I脚本文件。
sh-4.2$ cd /usr/local/s2i; ls assemble common.sh run save-artifacts scl-enable-maven
查看assemble脚本。
sh-4.2$ cat assemble #!/bin/sh set -e source "${JBOSS_CONTAINER_UTIL_LOGGING_MODULE}/logging.sh" source "${JBOSS_CONTAINER_MAVEN_S2I_MODULE}/maven-s2i" source "${JBOSS_CONTAINER_JWS_S2I_MODULE}/s2i-core-hooks" maven_s2i_build
在上面脚本中的核心功能是执行maven_s2i_build这个函数。接下来,我们通过分析这个函数来体会Builder镜像设计的精妙所在。
maven_s2i_build函数在/opt/jboss/container/maven/s2i/maven-s2i文件中进行了定义。
查看/opt/jboss/container/maven/s2i/maven-s2i中maven_s2i_build的函数的描述。
sh-4.2$ cat /opt/jboss/container/maven/s2i/maven-s2i …… # main entry point, perform the build function maven_s2i_build() { maven_s2i_init if [ -f "${S2I_SOURCE_DIR}/pom.xml" ]; then # maven build maven_s2i_maven_build else # binary build maven_s2i_binary_build fi s2i_core_copy_artifacts "${S2I_SOURCE_DIR}" s2i_core_process_image_mounts s2i_core_cleanup rm -rf /tmp/hsperfdata_jboss } ……
上面的函数是一个判断,当{S2I_SOURCE_DIR}中有pom.xml文件时调用maven_s2i_maven_build函数,否则调用maven_s2i_binary_build函数。在判断体之外,调用s2i_core_copy_artifacts等函数。
由于篇幅有限,我们接下来只分析maven_s2i_maven_build和s2i_core_copy_artifacts两个函数。
接下来查看本文件内maven_s2i_maven_build函数的描述。
# perform a maven build, i.e. mvn ... # internal method function maven_s2i_maven_build() { maven_build "${S2I_SOURCE_DIR}" "${MAVEN_S2I_GOALS}" maven_s2i_deploy_artifacts maven_cleanup
也就是说,maven_s2i_maven_build函数调用了maven_build。而maven_build的定义在/opt/jboss/container/maven/default/maven.sh中,内容如下。
function maven_build() { local build_dir=${1:-$(cwd)} local goals=${2:-package} log_info "Performing Maven build in $build_dir" pushd $build_dir &> /dev/null log_info "Using MAVEN_OPTS ${MAVEN_OPTS}" log_info "Using $(mvn $MAVEN_ARGS --version)" log_info "Running 'mvn $MAVEN_ARGS $goals'" # Execute the actual build mvn $MAVEN_ARGS $goals popd &> /dev/null }
也就是说maven_build最终调用的是mvn命令,对源码进行构建。
整个调用链条是:S2I=>assemble脚本=>maven_s2i_build=>maven_s2i_maven_build=>maven_build=>mvn。
我们回到maven_s2i_build,当maven_s2i_maven_build构建成功以后,调用s2i_core_copy_artifacts函数。
我们查看s2i_core_copy_artifacts的定义(/opt/jboss/container/s2i/core/s2i-core文件中)。
# main entry point for copying artifacts from the build to the target # $1 - the base directory function s2i_core_copy_artifacts() { s2i_core_copy_configuration $* s2i_core_copy_data $* s2i_core_copy_deployments $* s2i_core_copy_artifacts_hook $* }
也就是说,s2i_core_copy_artifacts又会包含4个函数。由于篇幅有限,我们仅以s2i_core_copy_data为例进行分析。
在同文件(/opt/jboss/container/s2i/core/s2i-core文件)中的代码对s2i_core_copy_data进行了定义,内容如下。
# copy data files # $1 - the base directory to which $S2I_SOURCE_DATA_DIR is appended function s2i_core_copy_data() { if [ -d "${1}/${S2I_SOURCE_DATA_DIR}" ]; then if [ -z "${S2I_TARGET_DATA_DIR}" ]; then log_warning "Unable to copy data files. No target directory specified for S2I_TARGET_DATA_DIR" else if [ ! -d "${S2I_TARGET_DATA_DIR}" ]; then log_info "S2I_TARGET_DATA_DIR does not exist, creating ${S2I_TARGET_DATA_DIR}" mkdir -pm 775 "${S2I_TARGET_DATA_DIR}" fi log_info "Copying app data from $(realpath --relative-to ${S2I_SOURCE_DIR} ${1}/${S2I_SOURCE_DATA_DIR}) to ${S2I_TARGET_DATA_DIR}..." rsync -rl --out-format='%n' "${1}/${S2I_SOURCE_DATA_DIR}"/ "${S2I_TARGET_ DATA_DIR}"
代码会进行一系列判断,如果源目录和目标目录都同时存在,函数会调用rsync命令将源目录的内容拷贝到目标目录。
整个调用的链条是:S2I=>assemble脚本=>maven_s2i_build=>s2i_core_copy_artifacts=>s2i_core_copy_data=>rsync。
最后,我们查看S2I的run脚本。
sh-4.2$ cat /usr/local/s2i/run #!/bin/sh exec $JWS_HOME/bin/launch.sh
run脚本调用了launch.sh脚本。查看launch.sh脚本的部分内容。
sh-4.2$ cat /opt/webserver/bin/launch.sh #!/bin/sh CATALINA_OPTS="${CATALINA_OPTS} ${JAVA_PROXY_OPTIONS}" escape_catalina_opts log_info "Running $JBOSS_IMAGE_NAME image, version $JBOSS_IMAGE_VERSION" exec $JWS_HOME/bin/catalina.sh run
launch.sh最终调用了catalina.sh脚本来启动webserver。而$JWS_HOME的参数是读取的Pod环境变量。
sh-4.2$ env |grep -i JWS JBOSS_CONTAINER_JWS_S2I_MODULE=/opt/jboss/container/jws/s2i JWS_HOME=/opt/webserver
整个调用的链条是:S2I=>run脚本=>launch.sh脚本=>catalina.sh脚本。
也就是说,应用的源码被mvn构建以后,拷贝到Tomcat的部署目录中,然后catalina.sh脚本启动webserver,从而启动应用。
在上文我们只是分析了Builder镜像中S2I脚本的冰山一角,而红帽提供Builer镜像的完备性、功能强大性可想而知,官方提供的Builder镜像也能够满足绝大多数S2I的需求。
4.手工定制Builder镜像
那么有没有我们的需求超过红帽官方提供的Builder镜像,需要我们定制化的时候呢?
答案是肯定的。我们仅需要满足S2I的规范就可以自行构建一个支持S2I的Builder镜像,其流程如图4-13所示。

图4-13 定制S2I的流程
构建Builder镜像的步骤如下:
·首先使用S2I命令行创建目录结构,目录中将会包含S2I的脚本、Dockerfile等。
·基础镜像通常使用红帽官网提供的镜像,我们编写新的Dockerfile引用基础镜像。在构建子镜像时也可以用新的S2I脚本覆盖父镜像的脚本。
·Builder镜像构建成功以后,可以接收外部Git的代码注入,对源码进行编译打包,最终形成应用镜像。
·应用镜像会被部署到OpenShift集群中,并创建Service和Router对象用于应用访问。
定制化Builder镜像的第一步就是选择基础镜像,基础镜像的选择决定了工作量和难易程度。通常有两种选择:
·选择使用红帽已经提供的Builder镜像进行修改。直接以官方提供的Builder镜像作为基础镜像,书写Dockerfile进行任何想要的定制化,我们称生成新的Builder镜像为子Builder镜像,官方提供的Builder镜像为父Builder镜像。
·使用最底层基础镜像(如openjdk或rhel)进行制作。根据社区或红帽提供的最底层镜像(如openjdk或rhel)自行书写Dockerfile、S2I的相关脚本,生成子Builder镜像。然后基于子Builder镜像进行S2I,生成应用镜像,实现应用容器化。
第一种方法的优点是书写Dockerfile较为简便(建立在红帽提供的Builder镜像的Dockerfile基础上),缺点是生成的镜像较大,生成的镜像需要经过压缩处理。
第二种方法的优点是生成的镜像较小,缺点是需要技术人员很熟悉红帽制作镜像规范以及OpenShift对镜像的要求,否则做出来的镜像有些功能会不工作。
根据经验,我们建议选择第一种方法,即选择使用红帽已经提供的Builder镜像进行修改,具体操作通常有三种方法:
·使用已有的红帽Builder镜像,在构建应用的时候采用覆盖默认父镜像S2I脚本的方法,不构建子Builder镜像,直接生成应用镜像,实现应用容器化。
·使用已有的红帽基础镜像或Builder镜像书写新的Dockerfile,不覆盖父镜像S2I脚本,构建子Builder镜像。然后基于子Builder镜像进行S2I,生成应用镜像,实现应用容器化。
·使用已有的红帽基础镜像或Builder镜像书写新的Dockerfile,覆盖父镜像S2I脚本,构建子Builder镜像。然后基于子Builder镜像进行S2I,生成应用镜像,实现应用容器化。
在上述三种方法中,定制的复杂度逐级提升。第一种和第三种使用较多,并且比较有代表性,我们将介绍这两种方法。
(1)采用覆盖默认父镜像S2I脚本的方法生成应用镜像
我们以红帽提供的rhscl/httpd-24-rhel7 Builder镜像为例。展示如何通过覆盖父镜像S2I脚本的方法生成应用镜像。
首先导入rhscl/httpd-24-rhel7的Image Stream。
# oc import-image rhscl/httpd-24-rhel7 --from=registry.access.redhat.com/rhscl/ httpd-24-rhel7 --confirm imagestream.image.openshift.io/httpd-24-rhel7 imported
通过docker/podman run运行镜像。
# podman run --name test -it rhscl/httpd-24-rhel7 bash
查看Builder镜像的assemble脚本。
bash-4.2$ cd /usr/libexec/s2i/ bash-4.2$ cat assemble #!/bin/bash set -e source ${HTTPD_CONTAINER_SCRIPTS_PATH}/common.sh echo "---> Enabling s2i support in httpd24 image" config_s2i echo "---> Installing application source" cp -Rf /tmp/src/. ./ process_extending_files ${HTTPD_APP_ROOT}/src/httpd-post-assemble/ ${HTTPD_ CONTAINER_SCRIPTS_PATH}/post-assemble/ # Fix source directory permissions fix-permissions ./
查看run脚本内容。
bash-4.2$ cat run #!/bin/bash source ${HTTPD_CONTAINER_SCRIPTS_PATH}/common.sh export HTTPD_RUN_BY_S2I=1 exec run-httpd $@
接下来,我们创建S2I的脚本(assemble和run)和源码文件(index.html)。其目录结构如下,需要注意的是s2i必须是隐藏目录。
# tree -a └── s2i-scripts ├── index.html └── .s2i └── bin ├── assemble └── run
我们查看源码index.html内容。
# cat /root/david/s2i-scripts/index.html This is David Wei test for S2I!!!
编写新的assemble脚本,我们可以看到,这和rhscl/httpd-24-rhel7中的assemble脚本内容的区别。新assemble脚本主要完成如下事情:
·执行脚本的时候输出DavidWei S2I test!!!。
·将/tmp/src/.目录下的内容拷贝到./,由于后面GIT仓库地址包含的源码是index.html,因此拷贝的是该文件。
·将如下内容重定向到./info.html文件中。
Page built on $DATE DavidWei test: Proudly served by Apache HTTP Server version $HTTPD_VERSION
查看脚本内容。
# cat /root/david/s2i-scripts/.s2i/bin/assemble #!/bin/bash set -e source ${HTTPD_CONTAINER_SCRIPTS_PATH}/common.sh echo "---> DavidWei S2I test!!!" config_s2i echo "---> Installing application source" cp -Rf /tmp/src/. ./ process_extending_files ${HTTPD_APP_ROOT}/src/httpd-post-assemble/ ${HTTPD_ CONTAINER_SCRIPTS_PATH}/post-assemble/ # Fix source directory permissions fix-permissions ./ DATE=`date "+%b %d, %Y @ %H:%M %p"` echo "---> Creating info page" echo "Page built on $DATE" >> ./info.html echo "DavidWei test:Proudly served by Apache HTTP Server version $HTTPD_VERSION" >> ./info.html
查看run脚本内容,是打开debug模式。
# cat /root/david/s2i-scripts/.s2i/bin/run # Make Apache show 'debug' level logs during startup run-httpd -e debug $@
接下来,将/root/david/s2i-scripts目录的内容(S2I脚本和index.html)提交到GitHub。
# git init # git add /root/david/s2i-scripts/* # git add /root/david/s2i-scripts/.s2i/* # git remote add origin https://github.com/ocp-msa-devops/s2itest.git # git commit -m "s2i scripts" # git push -u origin master -f
提交成功以后,使用rhscl/httpd-24-rhel7和刚上传的源码地址进行S2I。将应用的名称设置为weixinyu。
# oc new-app --name weixinyu httpd-24-rhel7~https://github.com/ocp-msa-devops/s2itest --> Found image 0f1cb8c (6 weeks old) in image stream "openwhisk/httpd-24-rhel7" under tag "latest" for "httpd-24-rhel7"
查看Builder Pod的日志,我们可以看到:
·构建是使用rhscl/httpd-24-rhel7指向的Docker镜像。
·输出DavidWei S2I test!!!,说明执行了新的assemble脚本。
具体内容如下。
# oc logs -f weixinyu-1-build Using registry.access.redhat.com/rhscl/httpd-24-rhel7@sha256:684590af705d72af64b 88ade55c31ce6884bff3c1da7cbf8c11aaa0a4908f63f as the s2i Builder Image ---> DavidWei S2I test!!! AllowOverride All ---> Installing application source => sourcing 20-copy-config.sh ... => sourcing 40-ssl-certs.sh ... ---> Creating info page Pushing image docker-registry.default.svc:5000/openwhisk/weixinyu:latest ... Push successful
接下来,我们为部署好的Pod创建路由。
# oc expose svc weixinyu --port 8080 route.route.openshift.io/weixinyu exposed # oc get route weixinyu weixinyu-openwhisk.apps.example.com weixinyu 8080 None
通过curl访问路由,得出的结果正是我们在index.html中定义的内容。
# curl http://weixinyu-openwhisk.apps.example.com This is David Wei test for S2I!!!
通过curl访问路由,增加info.html URI,得到的返回信息正是我们在新assemble脚本中定义的内容。
# curl http://weixinyu-openwhisk.apps.example.com/info.html Page built on Jun 06, 2019 @ 14:35 PM DavidWei test: Proudly served by Apache HTTP Server version 2.4
也就是说,在S2I过程中指定GitHub地址上的新S2I脚本替换了父镜像中的S2I脚本。
至此,我们实现了使用已有的Builder镜像,采用覆盖默认父镜像S2I脚本的方法,不重新构建子Builder镜像,直接生成应用镜像,也就是实现了应用的容器化。
(2)书写新的Dockerfile,覆盖父镜像S2I脚本,生成子Builder镜像
接下来,我们看另外一类需求。在前文中,我们分析了Tomcat的Builder镜像。我们查看镜像的标签,我们使用的是latest,如图4-14所示。

图4-14 Tomcat镜像标签
我们查看1.4-7 tag对应的Dockerfile,它使用的是Tomcat 8:3.1.6,Maven是3.5,如图4-15所示。
如果客户需要的S2I builder的Tomcat版本必须是8.5,并且必须包含Maven 3.6.1,此外还必须支持SVN(S2I默认仅支持Git),我们应该怎么做呢?这时需要利用红帽提供的Builder镜像自行定制子Builder镜像。

图4-15 Tomcat的Dockerfile内容
使用s2i create命令来创建所需的模板文件,以创建新的S2I Builder镜像。
首先安装s2i命令行。
# subscription-manager repos --enable="rhel-server-rhscl-7-rpms" Repository 'rhel-server-rhscl-7-rpms' is enabled for this system. # yum -y install source-to-image
创建镜像和对应的目录。
# s2i create s2i_tomcat8.5_maven3.6.1 s2i_tomcat8.5_maven3.6.1
查看目录结构。
# tree s2i_tomcat8.5_maven3.6.1 s2i_tomcat8.5_maven3.6.1 ├── Dockerfile ├── Makefile ├── README.md ├── s2i │ └── bin │ ├── assemble │ ├── run │ ├── save-artifacts │ └── usage └── test ├── run └── test-app └── index.html
使用新的Dockerfile,针对官网提供的webserver31-tomcat8-openshift。
·用本地的apache-tomcat-8.5.24覆盖到/opt/webserver下。
·用本地的Maven3.6.1覆盖父Builder镜像中的Maven3.5。
从互联网下载Tomcat 8.5.24和Maven3.6.1的安装包,如图4-16所示。
放到我们上一步创建的目录中解压缩,如图4-17所示。

图4-16 Tomcat和Maven安装包

图4-17 解压安装包
编写新的Dockerfile,内容如下。
# tomcat8.5 FROM registry.access.redhat.com/jboss-webserver-3/webserver31-tomcat8-openshift RUN rm -fr /opt/webserver/* COPY ./apache-tomcat-8.5.24/ /opt/webserver RUN ln -s /deployments /opt/webserver/webapps USER root RUN rm -fr /opt/rh/rh-maven35/root/usr/share/maven/* COPY ./maven3.6.1/ /opt/rh/rh-maven35/root/usr/share/maven COPY ./maven3.6.1/bin/ /opt/rh/rh-maven35/root/bin RUN chown -R jboss:root /opt/webserver && \ chmod -R a+w /opt/webserver && \ chmod -R 777 /opt/webserver/bin && \ chmod -R 777 /opt/webserver && \ chmod -R 777 /opt/rh/rh-maven35/root/usr/share/maven && \ chmod -R 777 /opt/rh/rh-maven35/root/bin USER 1002
由于S2I默认仅支持Git,如果我们要用SVN,就需要在assemble脚本中进行相关设置,也就是在执行assemble脚本时触发命令从SVN_URI参数设置的地址获取源代码,即通过svn的方式获取代码,然后使用mvn进行编译。最后将编译好的War包拷贝到Webserver的webapps的目录中,Tomcat会自动解压和部署应用。
if [[ "$1" == "-h" ]]; then exec /usr/libexec/s2i/usage fi # Restore artifacts from the previous build (if they exist). # if [ "$(ls /tmp/artifacts/ 2>/dev/null)" ]; then echo "---> Restoring build artifacts..." mv /tmp/artifacts/. ./ fi echo "---> Installing application source..." cp -Rf /tmp/src/. ./ ls -l ./ ls -l /tmp/src/ WORK_DIR=/tmp/src; cd $WORK_DIR; if [ ! -z ${SVN_URI} ] ; then echo "Fetching source from Subversion repository ${SVN_URI}" svn co ${SVN_URI} --username ${SVN_USER} --password ${SVN_PWD} --no-auth-cache export SRC_DIR=`basename $SVN_URI` echo "Finished fetching source from Subversion repository ${SVN_URI}" else echo "SVN_URI not set, skip Subverion source download"; fi echo "---> Building application from source..." cd $WORK_DIR/$SRC_DIR/ ${BUILD_CMD} echo "---> Build application successfully." find /tmp/src/ -name '*.war'|xargs -i cp -v {} /opt/webserver/webapps/
由于红帽提供的run脚本最终调用catalina.sh是使用的$JWS_HOME,因此更替版本不会使执行路径发生变化,run脚本使用父镜像的脚本即可,或直接使用如下内容启动Webserver。
exec /opt/webserver/bin/catalina.sh run
所有准备工作完成之后就可以手工构建镜像,输出内容如下。
Sending build context to Docker daemon 19.66 MB Step 1/9 : FROM registry.access.redhat.com/jboss-webserver-3/webserver31-tomcat8- openshift ---> c303ee1e1273 Step 2/9 : RUN rm -fr /opt/webserver/* ---> Running in aa5cab28ecc2 ---> 0eaa859906d7 Removing intermediate container aa5cab28ecc2 Step 3/9 : COPY ./apache-tomcat-8.5.24/ /opt/webserver ---> 132fd21440c3 Removing intermediate container ea09b46d8a1a Step 4/9 : RUN ln -s /deployments /opt/webserver/webapps ---> Running in e9c43075f480 ---> 56faa88135d3 Removing intermediate container e9c43075f480 Step 5/9 : USER root ---> Running in e96b4ef66a20 ---> 2fc5139ee082 Removing intermediate container e96b4ef66a20 Step 6/9 : RUN rm -fr /opt/rh/rh-maven35/root/usr/share/maven/* ---> Running in 89d19c564fdd ---> 38e364d3ab50 Removing intermediate container 89d19c564fdd Step 7/9 : COPY ./maven3.6.1/ /opt/rh/rh-maven35/root/usr/share/maven ---> 2ec060686ce4 Removing intermediate container 047930b49000 Step 8/9 : RUN chown -R jboss:root /opt/webserver && chmod -R a+w /opt/ webserver && chmod -R 777 /opt/webserver/bin && chmod -R 777 / opt/webserver && chmod -R 777 /opt/rh/rh-maven35/root/usr/share/maven && chmod -R 777 /opt/rh/rh-maven35/root/bin ---> Running in dbda875ca5f0 ---> 02bb0fa0eadb Removing intermediate container dbda875ca5f0 Step 9/9 : USER 1002 ---> Running in 4fd3ac609041 ---> 703e3a4d58c2 Removing intermediate container 4fd3ac609041 Successfully built 703e3a4d58c2
查看构建成功的子Builder镜像。
# podman images | grep -i s2i s2i_tomcat8.5_maven3.6.1 latest 703e3a4d58c2 6 minutes ago 585 MB
我们可以将构建成功的子Builder镜像推送到自己的镜像仓库。至此,我们完成了定制化Builder镜像。
为了方便后续使用,通常会创建OpenShift的模板来实现构建和部署应用,模板的具体配置方法,我们将在第7章中进行详细介绍,模板创建成功后,如图4-18所示。

图4-18 模板参数
关于构建Builder镜像采用的基础镜像,当然也可以采用更为基础的基础镜像。例如我们可以使用openjdk8作为基础镜像来生成s2i_tomcat8.5_maven3.6.1,只不过与使用已有的Builder镜像:webserver31-tomcat8-openshift相比,这种方式的步骤会多很多。
如果使用openjdk,Dockerfile的部分内容参考如图4-19所示。
在介绍完应用容器化的方法以后,我们接下来介绍开发人员如何在OpenShift上快速部署应用。

图4-19 从openjdk构建镜像的Dockerfile