OpenShift在企业中的实践:PaaS DevOps微服务(第2版)
上QQ阅读APP看书,第一时间看更新

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