Red Hat で OpenShift のコンサルタントをしている id:hashnao です。 赤帽エンジニア Advent Calendar 2018 - Qiita 12日目の記事です。 今日は既に13日目ですが、気にせずリリースしていきます。
- イメージのビルド方式
- OpenShift Jenkins Pipeline (DSL) Plugin とは
- OpenShift Jenkins Pipeline (DSL) Plugin のチュートリアル
- Reference
イメージのビルド方式
OpenShift では OpenShift 上でコンテナイメージをビルドする際、複数のビルド方式 をサポートしています。
BuildConfig
にイメージのビルド方式を定義したり、 oc new-build
コマンドの --strategy=(docker|pipeline|source)
オプションに応じてビルド方式を指定することができます。
Source-to-Image (S2I) Build
- Git リポジトリ上のアプリケーションのソースコードをコンパイルし、コンテナイメージをビルドする
- 特に Java アプリケーションを Maven でビルドするケースで利用される
Pipeline Build
- Jenkins Pipeline を用いて任意のオペレーションをパイプラインに定義する
- Jenkins Master (Pod) で Jenkins の Job が管理され、Jenkins Slave (Pod) でパイプラインが実行される
- 例えば、アプリケーションのソースコードのビルド、単体テスト、結合テスト、ソースコード解析からコンテナイメージのビルドなどをパイプラインとして実行させる場合に利用される
Docker Build
Dockerfile
からdocker build
でイメージをビルドする- 既に
Dockerfile
単体でイメージを管理しておりその運用を踏襲する、インフラ管理者が社内など共通で利用するベースイメージを作成する際などに利用される
Custom Build
- 既存のイメージに RPM パッケージをインストールするなどベースイメージを拡張する際に利用する
Dockerfile
を利用するDocker
と目的は近いが、Dockerfile
を使わないため、ビルド方法が異なる- BuildConfig の環境変数にカスタマイズする際のディレクティブを指定することで任意のイメージを作成する
BuildConfig に指定する環境変数の一覧は https://docs.okd.io/latest/creating_images/custom.html#creating-images-custom に公開されています。
今回は Jenkins Pipeline に関する入門編として、OpenShift Jenkins Pipeline (DSL) Plugin を用いたビルド方法を解説します。
OpenShift Jenkins Pipeline (DSL) Plugin とは
OpenShift では OpenShift Jenkins Pipeline (DSL) Plugin と呼ばれる Jenkins Plugin を提供しています。
OpenShift DSL (Domain Specific Language) 形式で Jenkins Pipeline を定義することができます。
内部的には Jenkins Pipeline の書式をベースに Jenkins Slave から OpenShift API を呼び出し、OpenShift 用の BuildConfig
や DeploymentConfig
などのオブジェクトを作成したり、 oc start-build
や oc rollout
の様なビルドを開始する、デプロイを開始するといったオペレーションをパイプラインに表現することができます。 *1
Jenkins Pipeline と OpenShift Jenkins Pipeline (DSL) Plugin を連携させた一般的な使い方として、テストからリリース作業をパイプラインに定義することがあります。 例えば、次の様なパイプラインを想定した場合、前半はアプリケーションのソースコードのビルドや単体テストなどを実施、後半はコンテナイメージをビルドする、といった流れになります。 これを 1 つの Jenkinsfile に定義します。
アプリケーション
- Git リポジトリから Java アプリのソースコードを取得する
- Maven でソースコードから成果物 (WAR, EAR, JAR) を生成する
- Maven で単体テストを実施する
- SonarQube でソースコードの静的解析を実施する
- Nexus リポジトリにバージョン管理のためにビルドした成果物をプッシュする
OpenShift
- 生成した成果物を指定し、コンテナイメージをバイナリビルドする
- ビルドしたイメージで
DeploymentConfig
から Pod (コンテナ) をデプロイする curl
コマンドなどで簡易的な結合テストをデプロイした Pod に対し実施する- 開発環境からステージングや本番環境にイメージをプロモーションする
- 本番環境で新しくビルドしたイメージで
DeploymentConfig
から Pod (コンテナ) をデプロイする
上記の様な一般的なアプリケーションリリースに関する流れを1つのパイプラインに定義することで、継続的インテグレーション (Continuous Integration) を回すことができます。 パイプラインのサンプルやチュートリアル、デモを含めて後述する Reference に関連する URL をまとめましたので、興味のある方は参考にして下さい。
OpenShift Jenkins Pipeline (DSL) Plugin のチュートリアル
次に実際にこの OpenShift Jenkins Pipeline (DSL) Plugin を使って Jenkins Pipeline を作成してみます。
Jenkins Pipeline を定義するための Jenkinsfile を指定する方法は Embedded Definition と Reference to Git Repository をサポートしています。
- Embedded Definition:
BuildConfig
に Jenkinsfile を埋め込むBuildConfig
内の Jenkinsfile を直接修正、または Jenkins Console から Jenkinsfile を修正することができる- デモや検証用途に利用するケースが多い
- Reference to Git Repository: Jenkinsfile を Git リポジトリで管理し、
BuildConfig
に Git リポジトリと Jenkinsfile のパスを指定する- Jenkinsfile を修正するには Git リポジトリに修正をプッシュする必要があり、直接上記の様な方法で修正することはできない
- 検証及び本番環境での用途に利用するケースが多い
今回は修正が容易な BuildConfig
に Jenkinsfile を埋め込む方式とし、次のフローで Jenkins Pipeline を作成します。
- Git リポジトリからアプリケーションのソースを
git clone
- Maven で成果物 (WAR) をビルド
- 生成した成果物を指定し、コンテナイメージをバイナリビルド する
- ビルドしたコンテナイメージにバージョン + ビルド番号でタグをリリースする
実際の環境では最初に解説した例の用にソースコードの静的解析、Nexus リポジトリに成果物を格納するなどのフローを含めますが、今回は Jenkins Pipeline の雰囲気や流れを掴むことが目的なので、省略しています。
まず Jenkins Pipeline がイメージをビルドするための BuildConfig
を作成します。
バイナリビルドでイメージをビルドするため、--binary=true のオプションを指定します。
通常は BuildConfig
を作成すると自動でイメージのビルドが開始されますが、 --binary=true
を指定する場合、 oc start-build
時に --from-file
や --from-dir
オプションで WAR などのアプリケーションバイナリを指定して、ビルドを開始するため、この時点ではビルドは開始されません。
oc new-project <PROJECT> oc new-build --name=mavenapp --docker-image=openshift/wildfly-101-centos7 --binary=true
次に Jenkinsfile を定義した BuildConfig
を作成し、ビルドを開始します。
あとは Jenkins Pipeline が自動で開始されるので、Jenkins Console から Jenkins Job の実行結果を待つだけです。
oc create -f https://raw.githubusercontent.com/hashnao/openshift-jee-sample/master/mavenapp-pipeline.yml oc start-build mavenapp-pipeline
事前に Jenkins をデプロイせず、ビルド方式に Pipeline を定義した BuildConfig
を実行すると、自動で Jenkins Pod がデプロイされ、Jenkins Pipelie が実行されます。
OpenShift ではデフォルトで Jenkins をデプロイするためのテンプレートが用意されており、揮発性 (jenkins-ephemeral) と永続性 (jenkins-persistent) の 2 種類があります。
oc new-app
コマンドにいずれかのテンプレートを指定することで Jenkins をデプロイすることができます。
Jenkins Job や Credentials などを含む Jenkins の構成情報を保持する場合、後者のテンプレート (jenkins-persistent) を利用します。
jenkins-persistent から Jenkins をデプロイすると /var/lib/jenkins が PV としてマウントされ、このディレクトリに各種設定ファイル (config.xml, credentials.xml など) が含まれます。
Jenkins のデプロイ手順は https://docs.okd.io/latest/dev_guide/dev_tutorials/openshift_pipeline.html を参照下さい。
次に Jenkins Pipeline が完了するまでの間に BuildConfig
と Jenkinsfile の詳細を解説します。
まず、 BuildConfig
を見てみましょう。
spec.strategy
フィールドにビルド方式 (jenkinsPipelineStrategy
) を宣言します。
次に jenkinsfile
フィールド配下に Jenkins Pipeline を定義します。
kind: `BuildConfig` ... spec: strategy: jenkinsPipelineStrategy: jenkinsfile: |- pipeline { ... }
今回は BuildConfig
ファイルを作成し、 oc create
でオブジェクトを作成していますが、 oc new-build --strategy=pipeline
オプションを利用すると Git リポジトリにある Jenkinsfile を指定することができます。
コマンドの実行例は次のようになります。
-e
フラグを利用することで環境変数を定義することもできます。
oc new-build --name=BUILDCONFIG_NAME GIT_REPO --strategy=pipeline -e ENV="MY_ENV"
Jenkinsfile を BuildConfig
に埋め込む方式の場合、 oc new-build
コマンドのオプションとして、Jenkinsfile を引き渡すことはできないようです。
今回は利用していませんが、 BuildConfig
に環境変数を定義し、Jenkinsfile 内で変数を参照させる場合、オブジェクトを Template として定義し、 parameters
フィールドに宣言させる方法が楽です。
kind: Template ... objects: - kind: `BuildConfig` ... spec: strategy: type: JenkinsPipeline jenkinsPipelineStrategy: env: - name: APP_NAME value: ${APP_NAME} jenkinsfile: |- ... parameters: - displayName: Application Name name: APP_NAME value: "openshift-tasks"
次に Jenkinsfile を見てみます。
見やすいように Jenkinsfile のスペースは 4 -> 2 に修正し、 BuildConfig
に埋め込んだ際にインデントさせたスペースは潰しています。
pipeline { agent { label "maven" }
pipeline
で Jenkins Pipeline の開始を宣言します。
OpenShift Jenkins Pipeline (DSL) Plugin は Declarative Syntax をサポートしており、 script{}
ディレクティブを利用することで Scripted Syntax を併用することもできます。
label
には Jenkins Slave に利用するコンテナイメージの名称を指定します。
これは Jenkins Console の "Manage Jenkins" - "Configure System" の Kubernetes Pod Template セクションに定義している Name に該当します。
OpenShift の Jenkins ではデフォルトで maven
と nodejs
が定義されており、 maven
を指定すると、Red Hat Container Catalog 上の jenkins-agent-maven-35-rhel7 を利用します。
Jenkins Slave に用いるコンテナイメージを追加することや既存の Slave のイメージを変更することもできます。
environment { version = "1.0" devTag = "${version}-${BUILD_NUMBER}" }
environment
には Jenkinsfile 内で利用する変数を定義します。
今回はイメージのタグをセットするために version
と devTag
の変数を用意しています。
devTag
には BUILD_NUMBER
というデフォルトでセットされる環境変数を指定し、 Jenkins Job のビルド番号を加えることで、Jenkins Pipeline が実行されるたびにそのビルド番号でイメージにタグをセットしています。
今回はバージョンを明示的に変数に指定していますが、 readFile
などを駆使しして、 pom.xml
に定義したバージョンから取得させる方がアプリケーションのバージョンとひも付きやすいので便利です。
設定例は https://github.com/redhat-cop/spring-rest/blob/master/Jenkinsfile#L49-L53 あたりを参考にしてみて下さい。
... environment { version = getVersionFromPom("./pom.xml") } ... def getVersionFromPom(pom) { def matcher = readFile(pom) =~ '<version>(.+)</version>' matcher ? matcher[0][1] : null }
stages { stage("Clone Source") { steps { git url: "https://github.com/openshift/openshift-jee-sample.git", branch: "master" } }
次に stages
からが Pipeline で実行したい処理になります。
stage
に定義した文字列が Jenkins Pipeline で Stage View に表示されるステージを表します。
まずはアプリケーションのソースコードを Git リポジトリから取得するため、 git url
にリポジトリの URL を指定しています。
stage("Build Artifacts") { steps { sh "mvn clean package -Popenshift -DskipTests=true" stash includes: "target/ROOT.war", name: "war" } } stage("Run Unit Test") { steps { sh "mvn test" } }
次に Maven ビルドを実行するために sh
で shell を宣言し、実行するコマンドを定義します。
今回のステージは全て同じ Jenkins Slave Pod 上で実行されるため stash
を使う必要はありません。
ただし、次のステージが別のノード (Pod) である場合などは stash
でビルドした WAR (ROOT.war) を一旦格納し、他のノードで unstash
で展開させると便利です。
stage("Build Image") { steps { unstash "war" script { openshift.withCluster() { openshift.withProject() { def nb = openshift.selector("bc", "mavenapp") nb.startBuild("--from-file=./target/ROOT.war").logs("-f") def buildSelector = nb.narrow("bc").related("builds") timeout(5) { buildSelector.untilEach(1) { return (it.object().status.phase == "Complete") } } echo "Builds have been completed: ${buildSelector.names()}" } } } } }
ここからが OpenShift API に関する処理になります。
openshift.withCluster
は Master API Server のエンドポイントや認証のための Token を指定します。
今回は Jenkins Master は OpenShift 上で実行しているため、指定する必要はありません。
openshift.withProject
はスイッチするプロジェクト (namespace) を指定します。
指定しない場合、Jenkins がデプロイされているプロジェクトを利用します。
開発からステージングなどにスイッチさせる場合にプロジェクトを指定します。
次に openshift.selector
でオブジェクト (BuildConfig
) と BuildConfig 名を指定し、nb
の名前で Selector を定義します。
nb.startBuild()
は Selecror を指定し、 startBuild
メソッドで oc start-build
を実行します。
oc start-build
のオプションをそのまま利用できるので、ここらへんが非常に楽です。
logs
メソッドと -f
で oc logs -f bc/mavenapp
と同じ処理を行い、ビルド時のログをフォローさせます。
最後の buildSelector.untilEach
は 5 分間 oc get build のステータスが Complete になるまで繰り返し確認します。
BuildConfig
や DeploymentConfig
を開始し、ビルドやデプロイの完了を確認する際によく使います。
stage("Promote Image") { steps { script { openshift.withCluster() { openshift.withProject() { // Tag the mavenapp:latest image as mavenapp:${devTag} openshift.tag("mavenapp:latest", "mavenapp:${devTag}") } } } } } } }
最後にイメージにビルド番号でタグをセットします。
BuildConfig
の spec.output.to
フィールドには ImageStreamTag
で latest
を指定しているため、ビルドが完了すると常に ImageStream
は latest
でタグ付けされます。
BuildConfig
の該当する箇所を oc patch
などで事前に変更させることもできますが、 BuildConfig
を修正するよりは latest
に対し、ビルド番号でタグを付け直す方が管理が容易なので、イメージをビルド後に改めて ImageStream
にタグをセットしています。
それではビルド結果を見てみましょう。
oc get build
の結果だけを見ると無事にビルドが完了している様です。
$ oc get build NAME TYPE FROM STATUS STARTED DURATION mavenapp-1 Source Binary Complete About an hour ago 1m3s mavenapp-pipeline-1 JenkinsPipeline Complete About an hour ago
次に Jenkins Console から Pipeline やビルドのログを見てみましょう。 Jenkins Console の URL は次のコマンドで確認することができます。
$ oc get route jenkins NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD jenkins jenkins-1ba3-jenkins.apps.example.com jenkins <all> edge/Redirect None
Jenkins Console にログインすると Stage View から Pipeline の各ステージの処理の結果や所要時間がわかります。 Jenkins Pipeline のログは左下のビルド番号(#1)から確認することができます。
ちなみに Console Output (Plain Text) のログを Jenkins Console でなく curl
コマンドで確認したい場合、Token を利用し、Console Output の URL を指定することでログを出力させることができます。
Plain Text の URL は View as plain text ボタンから確認することができます。
JENKINS_TOKEN=$(oc sa get-token jenkins) URL=https://<MASTER_API_URL>/job/<NAMESPACE>/job/<JENKINS_JOB>/<BUILD_NUMBER>/consoleText curl -k -H "Authorization: Bearer ${JENKINS_TOKEN}" ${URL}
実際の出力結果は次のようになります。
dhcp-193-87:advdev_homework_template nhashimo$ curl -k -H "Authorization: Bearer ${JENKINS_TOKEN}" ${URL} OpenShift Build 1ba3-jenkins/mavenapp-pipeline-1 Running in Durability level: MAX_SURVIVABILITY [Pipeline] node Still waiting to schedule task maven-kc5lc is offline Agent maven-kc5lc is provisioned from template Kubernetes Pod Template Agent specification [Kubernetes Pod Template] (maven): ... Cloning the remote Git repository Cloning repository https://github.com/openshift/openshift-jee-sample.git > git init /tmp/workspace/1ba3-jenkins/1ba3-jenkins-mavenapp-pipeline # timeout=10 ... [Pipeline] End of Pipeline Finished: SUCCESS
最後に Jenkins Pipeline でビルドしたイメージ (ImageStream
) にタグがセットされたことを確認します。
oc describe
で ImageStream
を確認すると、ビルド番号 (1.0-1) でタグがセットされていることがわかります。
$ oc describe is mavenapp Name: mavenapp Namespace: 1ba3-jenkins Created: About an hour ago Labels: build=mavenapp Annotations: openshift.io/generated-by=OpenShiftNewBuild Docker Pull Spec: docker-registry.default.svc:5000/1ba3-jenkins/mavenapp Image Lookup: local=false Unique Images: 1 Tags: 2 latest no spec tag docker-registry.default.svc:5000/1ba3-jenkins/mavenapp@sha256:e693e85dddd387f390fc6c1468c650961bcffcf9aecce2f5bb32ef018f25e416 About an hour ago 1.0-1 tagged from mavenapp@sha256:e693e85dddd387f390fc6c1468c650961bcffcf9aecce2f5bb32ef018f25e416 * docker-registry.default.svc:5000/1ba3-jenkins/mavenapp@sha256:e693e85dddd387f390fc6c1468c650961bcffcf9aecce2f5bb32ef018f25e416 About an hour ago
この ImageStream
を指定して、 oc new-app
を実行するとビルドしたイメージから Pod をデプロイすることができます。
$ oc new-app -i mavenapp:1.0-1
まだ 赤帽エンジニア Advent Calendar 2018 - Qiita にエントリが残っているようなので、次回は引き続き Jenkins Pipeline 関連をテーマに Jenkins Slave イメージのカスタマイズか認証情報のマスクなどを書く予定です。
Reference
OpenShift Jenkins Pipeline
- https://docs.okd.io/latest/dev_guide/dev_tutorials/binary_builds.html
- https://docs.okd.io/latest/using_images/other_images/jenkins.html
- https://docs.okd.io/latest/using_images/other_images/jenkins_slaves.html
- https://github.com/openshift/jenkins-client-plugin
Tutorial, Sample
- https://access.redhat.com/documentation/en-us/reference_architectures/2017/html/application_cicd_on_openshift_container_platform_with_jenkins/
- 開発/ステージング/本番環境を想定し、アプリのビルドからデプロイまでを Jenkins Pipeline に含める
- https://docs.okd.io/latest/dev_guide/dev_tutorials/openshift_pipeline.html
- Jenkins Pipeline から Nodejs と MongoDB をデプロイする
- https://access.redhat.com/documentation/en-us/reference_architectures/2017/html-single/build_and_deployment_of_java_applications_on_openshift_container_platform_3/index
- Microservice を想定した 4 種類の Java EE アプリ (JBoss EAP) と MySQL をデプロイ
- Maven で生成したバイナリを指定し、Binary Build を用いてイメージをビルド
- 一連のビルドとデプロイのプロセスを最終的に Jenkins Pipeline でパイプラインを実行
- https://github.com/siamaksade/openshift-cd-demo
- Jenkins Pipeline に SonarQube などのソースコードの静的解析を含めたデモ
- https://blog.openshift.com/building-declarative-pipelines-openshift-dsl-plugin/
- Jenkins Pipeline で Decralative Syntax をベースとしたチュートリアル
- https://blog.openshift.com/promoting-container-images-between-registries-with-skopeo/
- Jenkins Pipeline から作成したイメージを Skopeo で異なるレジストリ間にコピーする
- https://blog.openshift.com/multiple-deployment-methods-openshift/
- S2I や Binary Build などビルドやデプロイのデザインパターン
- https://blog.openshift.com/openshift-cloudbees-jenkins-enterprise-devops/
- Jenkins Enterprise (CloudBees) をベースとした Jenkins Pipeline
- https://github.com/openshift/jenkins-client-plugin/tree/master/examples
- OpenShift Jenkins Pipeline (DSL) Plugin の Jenkins Pipeline サンプル
- https://github.com/openshift/origin/tree/master/examples/jenkins/pipeline
- https://github.com/redhat-cop/container-pipelines
- Red Hat Communities of Practice が作成した Java アプリと Pipeline のサンプル
*1:なお、OpenShift Jenkins Pipeline (DSL) Plugin より以前に開発された OpenShift V3 Plugin for Jenkins があり、このプラグインのサポートは 3.11 までとなります。 そのため、今後は OpenShift Jenkins Pipeline (DSL) Plugin を利用することが推奨されます。