一項神奇的武功:Java S2I

S2I不只是Openshift的一個功能,仍是個開源項目
php

S2I是紅帽Openshift開發的一個功能組件。目前能夠獨立於Openshift運行。在社區裏,他的名字是Java S2I。html

Java S2I在github的地址:https://github.com/openshift/source-to-image/blob/master/README.mdjava

圖片


基於Openshift的S2I內功心法以下:node

圖片

Java S2I映像使開發人員可以經過簡單地指定其應用程序源代碼或已編譯的Java二進制文件的位置,在OpenShift容器平臺中按需自動構建,部署和運行Java應用程序。在許多狀況下,這些Java應用程序是可啓動的「Fat JAR」,包括應用程序服務器和其餘框架的嵌入式版本。此類開源項目的示例包括Spring Boot,Eclipse Vert.x和WildFly Swarm。擁有專爲OpenShift設計的通用Java S2I映像,經過包含許多有用的功能,能夠更輕鬆地開發可啓動的Fat JAR,例如:git


  • 簡單而靈活:Java S2I映像能夠處理複雜的構建結構,但默認狀況下,它會假定在成功構建後,/target目錄中將運行要運行的JAR。若是不是這種狀況,您可使用環境變量ARTIFACT_DIR進行調整。此外,若是構建生成多個JAR文件,則可使用環境變量JAVA_APP_JAR指定要運行的JAR文件。可是,在大多數狀況下,您所要作的就是直接指向源存儲庫,Java S2I映像將起做用。github


  • 自動JVM內存配置:在OpenShift中,資源可能受限於配額等。若是存在此類限制,Java S2I映像將自動採用JVM內存設置,以便JVM不會使用比容許的內存更多的內存。use,反過來有助於避免OutOfMemory異常。默認狀況下啓用此功能,但能夠經過在環境變量JAVA_OPTIONS中使用-Xmx爲堆設置固定值來禁用此功能。web

  • 經過Jolokia公開JMX統計信息:默認狀況下,Java S2I映像將啓用Jolokia,它經過HTTP公開JMX統計信息和操做。這能夠實現更詳細的應用程序監視功能。默認狀況下啓用此功能,但能夠經過將環境變量AB_JOLOKIA_OFF設置爲true來禁用此功能。算法

  • 減少docker image大小:爲了使docker image的大小保持最小,能夠在構建最終圖像以前讓Java S2I圖像刪除任何maven repo數據。默認狀況下禁用此功能以支持增量構建。要從最終容器映像中刪除maven工件,請將環境變量MAVEN_CLEAR_REPO設置爲true。docker


S2I若是獨立於Openshift安裝,其安裝方式以下:
npm

使用 s2i 工具爲 S2I 構建器鏡像建立所需的模板文件和文件夾。經過運行 s2i create 命令爲構建器鏡像建立模板文件:

圖片

四級文件的功能說明:

圖片

腳本寫好之後,執行:docker build -t s2i-httpd .     生成Buildder Image。

在使用build imager實現S2I的時候,執行:s2i build命令(s2i-httpd是builder iage)。例如:

#s2i build test/test-app/  s2i-httpd s2i-sample-app

...Using "assemble" installed from "image:///usr/libexec/s2i/assemble"

...Using "run" installed from "image:///usr/libexec/s2i/run"

...Using "save-artifacts" installed from "image:///usr/libexec/s2i/saveartifacts"

---> Copying source files to web server directory...


Source-to-Image 語言檢測OpenShift 能夠直接基於 Git 存儲庫中存儲的源代碼來建立應用。oc new-app 命令的語法更簡潔,它只會將 Git 存儲庫 URL 用做參數,而後,OpenShift 會嘗試自動檢測應用所用的編程語言,並選擇兼容的構建器鏡像。編程語言檢測功能會在 Git 存儲庫的根目錄中查找特定的文件名稱。

圖片

OpenShift 會採用多步算法來肯定 URL 是否指向源代碼存儲庫,若是是,則還會採用該算法來肯定應由哪一個構建器鏡像來執行構建。如下是該算法的簡單介紹:


1.URL 會被看成容器註冊表 URL 來進行訪問。如能成功訪問,則需建立構建配置。OpenShift 會建立部署容器鏡像所需的部署配置和其餘資源。

2.若是 URL 指向某個 Git 存儲庫,則 OpenShift 會檢索該存儲庫中的文件列表並搜索 Dockerfile。若是找到了,則構建配置會使用 docker 策略。不然,構建配置會使用 source 策略,該策略須要使用 S2I 構建器鏡像。

3.OpenShift 會搜索語言構建器名稱與 supports 註釋值相匹配的鏡像流。搜索到的第一個匹配項會成爲 S2I 構建器鏡像。

4.若是沒有匹配的註釋,則 OpenShift 會搜索名稱與語言構建器名稱相匹配的鏡像流。搜索到的第一個匹配項會成爲 S2I 構建器鏡像。

只需執行第 3 步和第 4 步,便可輕鬆向 OpenShift 集羣添加新的構建器鏡像。這還意味着可能存在多個匹配的構建器鏡像。本章前文中已介紹過使用 oc new-app 命令行選項能夠避免這種不肯定性,並確保選擇正確的鏡像流做爲 S2I 構建器鏡像。


Source-to-Image (S2I) 構建流程
S2I 構建流程涉及三個基礎組件;經過組合使用這些組件,能夠建立最終容器鏡像:

  • 應用的源代碼。

  • S2I 腳本

  • 一組由構建流程執行以自定義 S2I 構建器鏡像的腳本。S2I 腳本可使用任意編程語言來編寫,這些腳本可在 S2I 構建器鏡像內執行。


實驗1:Apache HTTP 服務器 S2I 構建器鏡像 (rhscl/httpd-24-rhel7)。




查看包含自定義 S2I 腳本的應用源代碼。

.s2i/bin/assemble 腳本會將應用源中的 index.html 複製到位於 /opt/app-root/src 的 Web 服務器文檔中。它還會建立一個包含頁面構建時間和環境信息的 info.html 文件:

.s2i/bin/run 腳本會將 Web 服務器中的啓動消息默認日誌級別更改成 debug:

基於 Git 中的來源建立一個名爲 hello 新應用。須要將 httpd 鏡像流做爲前綴添加到 Git URL 中,以確保應用使用 rhscl/httpd24-rhel7 構建器鏡像。使用~表示強制指定image stream,而不是去查找:

使用路由公開應用供外部訪問。須要使用 --port 選項,由於容器鏡像會公開多個端口:


實驗2:使用S2I部署fat jar

  • OpenJDK S2I 構建器鏡像 (redhat-openjdk-18/openjdk18-openshift)。

  • 應用 Git 存儲庫 (java-serverhost)。

基於 Git 中的來源建立新應用。將應用命名爲 jhost,並在 oc new-app 命令中使用 --build-env 選項來定義構建環境變量和 maven 存儲庫位置。

oc new-app --name jhost --build-env MAVEN_MIRROR_URL=http://services.lab.example.com:8081/nexus/content/groups/training-java -i redhat-openjdk18-openshift http://services.lab.example.com/java-serverhost

圖片

圖片

將應用更新至 2.0 版。

編輯 /home/student/java-serverhost/src/main/java/com/redhat/training/example/ 文件夾中的 ServerHostEndPoint.java 文件,並更新至 2.0 版:
圖片
圖片
圖片

=====================================================

近兩年,筆者的不少客戶都在進行容器雲的研究,有些客戶已經實施容器雲。筆者也有幸參與了幾項目,所以有一些體會。


總體上看,客戶使用容器的用途大體有兩種:實現PaaS(包括或不包括微服務)、實現CI/CD。


不管實現哪一個功能(或者兩個都要實現),第一步都是須要實現應用容器化。接下來,咱們先看看應用容器化的方法。


1、應用容器化的方法

應用容器化,常見的方法有三種:

以上三種方式:


第一種本地構建最多見,也比較簡單,但效率過低。


第二種方式是經過CI構建。這種方式則是比較傳統的方法。須要指出的:CI/CD的實現,與容器並沒有必然的聯繫。在容器火以前,不少客戶也基於Kenkins和虛擬機實現了CD/CD。只是容器輕量級的優點,更容易實現CI/CD。


在紅帽Openshift中,咱們能夠經過CI構建實現容器鏡像。這種構建方式,其實是在openshift中部署Jenkins Slave Pod,在Slave Pod實現構建。紅帽提供四種Jenkins Slave Pod的鏡像(根據應用開發語言的不一樣),有基於maven的,有基於nodejs的、基於.net的、也有基礎鏡像(使用者基於基礎鏡像進行構建)。


第三種方式是經過S2I的方式構建應用鏡像。這種方式有第二種方式構建的優勢(構建好的應用客戶實現CI),而且比第二種方式效率更高、適用多種開發語言,聽起來很是牛逼,簡直是廠商PaaS工程師(尤爲是實施工程師)的救星!



在正式介紹S2I以前,咱們先介紹一下image stream,由於S2I會用到image stream。


2、鏡像流是個啥?

2.1 Image Stream的定義

簡單而言,一個image stream是一類應用鏡像的一組鏡像的集合。而image的tag則定義這些名字及標籤指向的具體鏡像(這類鏡像可能包含不少版本,經過tag來區分)。


例如一個httpd的image stream:


對於一個已經有的鏡像,如docker.io上的docker image,若是openshift想使用,則能夠經過image導入image stream:


# oc import-image my-ruby --from=docker.io/openshift/ruby-20-centos7 --confirm

這樣,導入is的時候,image stream的tag也就被建立了,它指向image:docker.io/openshift/ruby-20-centos7。


須要注意的是,咱們在從image 導入image stream的時候,最好加上 --scheduled=true。它的做用在於,當image stream建立好之後,IS會按期檢查鏡像庫的更新,而後更新到最新的image。若是不是這樣,形成的結果多是:docker.io有個新的/ruby-20-centos7的鏡像,而image stream的latest仍然指向舊的鏡像。


咱們再舉個例子,期初ose-haproxy-router默認是沒有image stream的:

咱們將其導入image stream:


再進行查看,就能夠看到is和istag了。

其中is是包含Openshift docker-registry的地址,istag則指向ose-haproxy-router鏡像。


須要指出的是:Image Stream不是K8S的組件,是Openshift的組件:


2.2 鏡像流image stream的做用是什麼?

剛接觸openshift的朋友可能會問:image stream的做用是什麼?


image stream的做用:

1.實現BC和鏡像倉庫的鬆耦合

2.配合Openshift的S2I全流程


在Openshift,部署應用的方法,一般有以下幾種:

2.2.1 .經過docker image部署:這種一般直接部署已經包含應用的打包好的鏡像,所以一般沒有bc。


2.2.2 .經過S2I 部署經過選擇builder image和指定code。指定完之後,code 先進行build,build成功,會將它push到內部的鏡像庫,而後部署一個新的pod。所以S2I一般會觸發build和deploy。

下圖中,選擇node.js這個image stream:

部署的時候,能夠選擇tag的版本,並會提示輸入git代碼倉庫的地址:


2.2.3.經過模板部署

模板是能夠把和一套應用相關的配置,都寫在一塊兒,而後經過這個模板部署應用。使用模板部署最大的好處在於,它能夠加快應用的部署速度。模板是由實現寫好的yaml或json文件建立的。

在以上的三種方式中,S2I的部署方式必然會包含BC,而經過模板可能會觸發BC。

S2I的bc的階段,是根據指定的builder imager和source code一塊兒,build造成可被部署的App image,並push到Openshift的docker registry中(內部集成鏡像庫)。

這樣就帶來了一個問題,是否是每一個S2I的每一個BC操做,都須要指定bc成功之後要push的docker registry的地址?這顯然很是麻煩。而Image Stream能夠作這件事。


2.3 是否只有bc階段能夠調用image stream?

既然上文內容提到了,image stream主要是爲S2I的bc階段服務的(image stream包含了docker-registry的地址,實現了bc與docker registry的鬆耦合),那麼dc階段,是否能夠調用image stream呢?


實際上,在整個完整的S2I流程中,當build成功之後,openshift將鏡像push到鏡像庫的時候,不會也不可能更新builder image的tag,讓新的tag指向生成的app image的(下圖的nationalpark:latest就是app image,而app image的builder image的image stream是simple-java-s2i):

S2I bc結束之後繼續的dc階段,則是將172.30.174.125:5000/explore-00/nationalpark:latest這個鏡像pull到要運行應用pod的node上,而後進行部署。


所以,在dc階段,openshift已經不會經過image stream部署應用了(由於dc要部署的是app image,而非builder image)。


可是,若是咱們手工觸發一個應用的部署,但能夠經過選擇image stream tag(實際上,在建立S2I的時候,輸入image stram,實際上也須要指定image stream的tag,只是若是不指定,默認使用latest的istag罷了)來實現的。


緣由就在於:雖然image stream包含了docker-registry的地址,但image stream tag則指向docker image。咱們固然能夠經過指定image stream tag來部署應用了,這實際上也是間接方式的docker image部署(所以說,在這個部署階段image stream包含的docker-registry是不會用到的,對它也沒有什麼意義。由於部署過程當中,不會有bc過程,也就不會push鏡像大到docker registry中)。

固然,咱們也能夠對一個build成功的app image導入image stream(實際上,在Openshift的角度,builder image和app image並沒有本質的區別,這兩類的區別都是咱們人爲定的),而後根據mage stream的istag進行部署。例如上面實驗中的172.30.174.125:5000/explore-00/nationalpark:latest,它是個app的image,咱們能夠經過以下命令爲這個app image建立is:

 #oc import-image nationalpark --from=172.30.174.125:5000/explore-00/nationalpark:latest --confirm

圖片

總結手工觸發SI2的時候,必需要使用builder image的image stream。手工觸發一個已有鏡像的部署時,咱們可使用docker image的方式,也可使用image stream tag的方式,只是須要確保image stream tag指向的是咱們要部署的app image便可(固然,部署一個不含應用的builder image也能夠,但這在生產中,沒有什麼實際意義。)


3、深度解析S2I的過程

3.1 S2I的概念

S2I是Openshift的原創。其概念上文也大體提到了,根據指定的builder imager和source code一塊兒,build造成可被部署的docker image,並push到Openshift的docker registry中(內部集成鏡像庫)。而S2I,是Openshift實現CI/CD和Devops的框架。

圖片

嚴格意義上講,代碼的構建,不只能夠經過指定building image和source code,還能夠經過Binary和building image造成可被部署的鏡像,咱們稱之爲:B2I.


3.2 爲何Openshift既有S2I和B2I呢?


其實也很容易理解。代碼的構建,能夠經過JAR/WAR注入到Build而 image的方式進行構建(maven、nodejs、.net等方式),這是比較常見的。在這種方式下,咱們在執行命令時,能夠指定svn或git的地址,如如下格式的命令:


#oc new-appwebserver31-tomcat8.0-oraclejdk-openshift~http://gitlab.david.com/openshift/mspp.git 


而還有一種狀況,好比客戶的應用,是基於tomcat的php。若是可能要作應用的增量發佈。也就是說,將原有php的一部份內容,經過替換一部分新的jsp來實現,而應用的名稱和版本都不發生變化,這時候,jsp就是一種Binary文件,而非source code。


若是觸發B2I,命令行如如下格式的命令:

#oc start-builddzwls923c --from-dir=./ --follow=true --wait=true


上面命令的意思是:根據已經有的image stream爲dzwls923c的builder image,加上當前目錄下的binary文件,構建新的鏡像,新的鏡像將會被image stream tag(名字爲dzwls923c:latest)映射。



3.2 S2I/B2I的4個腳本
一個符合S2I/B2I的building image,在bin目錄下,是須要包含以下四個腳本的:


圖片


3.2.1 assemble腳本:

這個腳本負責將外部代碼庫的代碼下載到本地,而且進行編譯打包。

The assemble script is responsible for building the application artifacts from source and placing them into the appropriate directories inside the image. The workflow for the assemble script


3.2.2 run腳本:

這個腳本負責運行assemble編譯好的應用。

The run script is responsible for executing your application.

3.2.3 save-artifacts腳本

save-artifacts腳本負責將構建所須要的全部依賴包收集到一個tar文件中。save-artifacts的好處是能夠加速構建的過程。

The save-artifacts script is responsible for gathering all the dependencies into a tar file and streaming it to the standard output (eg. for Ruby - gems installed by Bundler, for Java - .m2 contents, etc.). The existence of this can speed up the following build processes. Note: it is critical that the save-artifacts script output only include the tar stream output and nothing else. This is handled by redirecting output to /dev/null in the sample script below.



3.2.4 usage腳本

usage腳本是告訴使用者若是使用鏡像。

The usage script is for you (as the builder image author) to inform the user how to use your image.


在S2I的四個腳本中,一般咱們只會用到assemble和run兩個腳本。



3.4.從tomcat S2I builder image看assemble和run腳本的做用


3.4.1 assemble是作啥的?

assemble是作什麼用的?它實際上是作組裝的用的,也就是構建source code,讓它變爲能夠被運行的狀態。


咱們看一下tomcat 8.2的S2I building image其中的assemble腳本(篇幅有限,只列出核心部分,註釋部分不列出):


echo "---> Installing application source..."

cp -Rf /tmp/src/. ./

ls -l ./ 

ls -l /tmp/src/


上面代碼作的事情,是將負責bc的pod的/tmp/src目錄下的source code(source code能夠自己就在/tmp/src目錄下,也能夠被外部注入,下面代碼將描述外部注入),拷貝到tomcat 的運行目錄。


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


上面代碼作的事情,若是在執行S2I的命令中指定SVN的地址,那麼構建過程會將svn代碼庫上的代碼,拷貝到bc pod的/tmp/src目錄下。


echo "---> Building application from source..."

# TODO: Add build steps for your application, eg npm install, bundle install, pip install, etc.

cd $WORK_DIR/$SRC_DIR/

#mvn package -Dmaven.test.skip=true;

${BUILD_CMD}

echo "---> Build application successfully."


上面代碼作的事情,是經過marven,對下載下來的source code進行編譯,打包。


find /tmp/src/ -name '*.war'|xargs -i cp -v {} /opt/app-root/jboss-eap-6.3/standalone/deployments

上面代碼作的事情,將編譯成功的war包,拷貝到tomcat的啓動目錄。這樣,當tomcat運行的時候,編譯好的war也就運行了。



3.4.2 run腳本是作啥的?


咱們看tomcat 8.5的S2I building image其中的run腳本(篇幅有限,只列出核心部分,註釋部分不列出):



#export JAVA_HOME=/opt/app-root/jdk1.6.0_38

cd /opt/app-root/jboss-eap-6.3

exec /opt/app-root/jboss-eap-6.3/bin/standalone.sh


從上面代碼能夠看出,run腳本就是啓動tomcat的。


也就是說,在S2I的build階段:assemble負責構建source code,讓它變爲能夠被運行的狀態。而run腳本負責運行構建好的應用。



而構建一個符合S2I標準的builder image(https://github.com/debianmaster/openshift-java-s2i-example),其流程以下:

圖片

在這張圖中,S2I的腳本,就是上文提到的assemble、run等腳本(他們將會被拷貝到構建成功的app image中);Base image就是標準的docker image;builder image是符合S2I標準的docker image,這個image能夠接收外部git、SVN的代碼注入,最終造成app的image,而後被openshift push到docker-registery中;push成功之後,Openhift觸發dc,根據這個應用的image部署pod。


爲了方便理解B2I,這裏我放兩個腳本:B2I的assemble和run(這是我同事周榮寫的兩個腳本)。這兩個腳本包含在一個weblogic的builder image中,用來實現增量發佈:

assemble:

#!/bin/sh

echo "Start copy folders"

cp -a /tmp/deploy/src/* /opt/bea/user_projects/domains/base_domain/deploy

echo "After successful assembling"


Run:

!/bin/bash

exec /opt/bea/user_projects/domains/base_domain/startWebLogic.sh


觸發B2I的命令:

#oc start-builddzwls923c --from-dir=./ --follow=true --wait=true


執行上面命令之後,當前目錄下的binary文件,會被先拷貝的bc pod的 /tmp/deploy/src目錄下,而後assemble腳本將這些binary文件從tmp/deploy/src目錄拷貝到應用的運行目錄/opt/bea/user_projects/domains/base_domain/deploy拷貝兩次的緣由是爲了實現鬆耦合,不一樣應用的運行目錄是不同的拷貝完成之後,實際上app的image已經造成。而run腳本,將會在app image被部署是運行,啓動weblogic應用。


總結:咱們在構建符合CI/CD的應用鏡像,有Jenkins的slave pod構建和SI2/B2I兩種方式。

而從經驗上看,S2I更靈活一些。而且紅帽的官網,registry.access.redhat.com,也爲紅帽的客戶提供了已經作好的,符合S2I標準的builder image。

相關文章
相關標籤/搜索