Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Jenkins PipelineでのShared Librariesの活用

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Jenkins PipelineでのShared Librariesの活用

Avatar for Hisashi.Iguchi

Hisashi.Iguchi

March 23, 2022
Tweet

More Decks by Hisashi.Iguchi

Other Decks in Technology

Transcript

  1. Jenkins Pipelineでの
   Shared Librariesの活用
 @Jenkins Day Japan 2021 [2021/12/10]
 井口

    恒志 <Hisashi Iguchi>
 システム本部 品質統括部 品質管理部 SWET第二グループ
 株式会社ディー・エヌ・エー
 @ DeNA Co.,Ltd.
  2. 2 自己紹介
 井口 恒志 (Hisashi Iguchi)
 自動テストの開発・導入サポート 
 CI/CDの活用促進や関連技術の調査 


    プロジェクトの状態可視化基盤などの整備 
 CircleCI のJapanユーザコミュニティでも活動中 
 
 バスケ好きで試合観戦や週1, 2でプレイもしてます 
 DeNA システム本部 SWET第二グループ
 @hisa9chi

  3. 9 Vision: Make Testing Fun, Smart and Delighting End-Users
 2

    • Fun
 ◦ テスティングを楽しくクリエイティブなものにする 
 • Smart
 ◦ 定型的なテストを人の手を煩わせず実現する 
 ◦ テストによるフィードバックを上手に活用して、素早い開発サイクルを実現す る
 • Delighting End-Users
 ◦ テストにより事業の成功を後押しするため、エンドユーザをデライトさせる 様々な品質を計測し改善する

  4. 10 SWETメンバーのプロジェクトへの関わり
 3 • ソフトウェアテスト技術分野ごとのチーム 
 ◦ アーキテクチャ
 ◦ 自動テスト


    ◦ プロセス(CI/CD)
 • それぞれのチームが様々な技術領域の案件をサポート 
 ◦ QAによるテスト工程よりも前の工程で品質・生産性の向上 
 ◦ デバッグの効率化・工数削減などテスト技術の蓄積 
 ◦ テスト全般の実行環境・プロセスサポート・サービス化 

  5. 11 プロセスチームのJenkinsに関する取り組み
 4 • チームではJenkinsの運用コストを削減するための取り組みを実施 
 
 
 
 


    
 https://speakerdeck.com/dena_tech/mohairukemukai-fa-niokerujenkinskurautoshi-dai-falsejenkinsgou-zhu-toguan-li-tekunituk 

  6. Pipeline
 1 • PipelineのJenkinsfileをリポジトリ に登録・管理
 14 sample-01.jenkinsfile pipeline { agent

    { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } }
  7. Pipeline
 1 • PipelineのJenkinsfileをリポジトリ に登録・管理
 • ジョブの数だけJenkinsfileは増加 
 15 sample-01.jenkinsfile

    pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } sample-02.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } sample-03.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } }
  8. Pipeline
 1 • PipelineのJenkinsfileをリポジトリ に登録・管理
 • ジョブの数だけJenkinsfileは増加 
 • 時にはメソッドも定義


    16 sample-01.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } sample-02.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } sample-03.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } sample-04.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } def myFunc() { …. }
  9. Pipeline
 1 • PipelineのJenkinsfileをリポジトリ に登録・管理
 • ジョブの数だけJenkinsfileは増加 
 • 時にはメソッドも定義


    • コードの重複発生
 17 sample-01.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } sample-02.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } sample-03.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } sample-04.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } def myFunc() { …. } sample-05.jenkinsfile pipeline { agent { label 'ubuntu' } stages { stage ( 'pre-build' ) { .... } stage ( ‘build’ ) { .... } } } def myFunc() { …. } 重複部分を外部に定義
 再利用性とメンテナンス性が向上 

  10. 19 Shared Libraries : 概要
 • 概要
 ◦ Pipelineの外部に定義
 ◦

    ライブラリはSCM管理
 ◦ ライブラリはPipeline毎にロードして利用
 ▪ ロードは複数指定可能 
 • 特徴
 ◦ ジョブ毎で異なるバージョンの利用が可能
 ◦ メンテナンス性と再利用性の向上
 ◦ クラスのstaticメソッドのapproveが不要
 2
  11. 20 Shared Libraries : 特徴 - ジョブ毎で異なるバージョンの利用が可能 
 • ライブラリのロード時に以下の指定が可能

    
 ◦ タグ
 ◦ ブランチ
 ◦ コミットハッシュ
 2 ライブラリのロード方法 // デフォルトバージョンの読み込み // "Global Pipeline Libraries 設定" の "Default version" @Library( 'ライブラリ名' ) _ // ブランチ,タグ,コミットハッシュを指定する場合 @Library( 'ライブラリ名@{branch | tag | commit hash}' ) _ // 複数ライブラリの読み込み @Library( ['ライブラリ名1', 'ライブラリ名2'] ) _ pipeline { agent { label 'ubuntu' } .... }
  12. 21 Shared Libraries : 特徴 - メンテナンス性と再利用性の向上 
 • 変更はライブラリ内に閉じる


    • 他のJenkinsからも利用可能
 ◦ ライブラリを管理しているリポジトリのアクセス権は必要 
 • Plugin利用をラップしたライブラリの作成 
 ◦ Pluginの更新による変更影響をライブラリ内に閉じれる場合あり 
 2
  13. 22 Shared Libraries : 特徴 - クラスのstaticメソッドのapproveが不要 
 • Pipelineではgroovyによるクラスのstaticメソッドが実行可能

    
 ◦ セキュリティ的に問題
 ◦ 管理者が承認したクラスのstaticメソッドの場合実行可能 
 • Pipeline内のgroovyコードにてクラスのstaticメソッドを利用した場合 
 ◦ 利用メソッド全てにapproveが必要 
 ◦ 結構面倒な作業(管理者に承認してもらう必要があるため)
 2
  14. 23 Shared Libraries : 特徴 - クラスのstaticメソッドのapproveが不要 
 • Pipelineではgroovyによるクラスのstaticメソッドが実行可能

    
 ◦ セキュリティ的に問題
 ◦ 管理者が承認したクラスのstaticメソッドの場合実行可能 
 • Pipeline内のgroovyコードにてクラスのstaticメソッドを利用した場合 
 ◦ 利用メソッド全てにapproveが必要 
 ◦ 結構面倒な作業(管理者に承認してもらう必要があるため)
 • ライブラリであればapproveは不要 
 ◦ 承認の面倒な作業が発生しない 
 2
  15. 24 Shared Libraries : Jenkins Plugin と比較
 3 Shared Libraries

    Jenkins Plugin 他のcontrollerからの 利用 可能 Jenkins設定のみ 可能 Plugin のインストール 利用バージョン 複数 タグ/ブランチ/コミットハッシュ 1バージョン ※1 インストールされたバージョン メンテナンス ライブラリ管理者 / 作成者 Pluginメンテナー 変更影響 なし ※2 複数バージョン指定可能なため 全ジョブに影響 変更によりジョブの失敗の可能性あり ※1 Plugin の更新に controller のバージョン更新や依存 Plugin の更新が必要な場合あり 
 ※2 Plugin を利用している場合は Plugin の更新に依存して変更影響が発生する場合あり 

  16. 25 Shared Libraries : 作成と設定
 • 作成方法と実際の設定に関しては公式サイトを参考 
 ◦ https://www.jenkins.io/doc/book/pipeline/shared-libraries/


    
 • SWET Testing Blog に少し記載しているのでご参考に 
 ◦ https://swet.dena.com/entry/2021/01/18/200000
 4
  17. 27 活用事例
 • ジョブの成果物のGCS(Google Cloud Storage)保存 
 • エラーとなったstage名の検出
 •

    Slack通知時のアタッチメント作成 
 • Slack通知時のメンション先ユーザのID取得 
 1
  18. 28 活用事例:ジョブの成果物のGCS保存
 • ジョブ実行時の成果物はJenkins controller自体に保存が可能 
 ◦ controllerの容量には制限
 ▪ ディスクの拡張などのメンテが必要

    
 ◦ 成果物(アプリのバイナリなど)をJenkins外から取得する時に面倒 
 • ジョブの成果物をGCSへ保存
 ◦ ストレージの拡張などメンテフリー 
 ◦ オブジェクトのライフサイクル設定でデータ自動削除可能 
 ◦ パブリッククラウドのため他サービスへの取り回しが比較的楽 
 2
  19. 29 活用事例:ジョブの成果物のGCS保存
 • GCSアップロードには Plugin が存在 
 ◦ Google Cloud

    Storage Plugin: https://plugins.jenkins.io/google-storage-plugin/
 ◦ アップロード時には以下のパス構成でGCSへ保存しておきたい 
 ▪ backet_name/job_name/build_no/upload_files 
 ◦ Plugin利用時は上記のアップロードファイル以前のパスの指定 
 ▪ 基本的にはどのプロジェクトでも同様の方針 
 2 Plugin を利用したGCSアップロード // backet_name/job_name/build_no のパス設定 UPLOAD_DIR = "gs://${params.bucket_name}/${env.JOB_NAME}/${env.BUILD_NUMBER}" googleStorageUpload( bucket: UPLOAD_DIR, credentialsId: params.credentialsId, pattern: uplodFiles ) • 上記改善のためPluginをラップしたライブラリを作成 

  20. 活用事例:ジョブの成果物のGCS保存
 
 2 • ライブラリ側で以下を付加して アップロード
 ◦ ジョブ名/ビルド番号
 
 •

    Pipeline側では以下を指定
 ◦ バケット名
 ◦ 認証情報ID
 ◦ アップロードファイル 
 30 ライブラリ側 (libUploadArtifactsToGCS) // バケット名にジョブ名/ビルド番号 を付与 def uploadDir = "gs://${params.bucket}/${env.JOB_NAME}/${env.BUILD_NUMBER}" // ファイルのGCSへアップロード googleStorageUpload( bucket: uploadDir, credentialsId: params.credentialsId, pattern: params.pattern ) Pipeline側(呼び出し側) steps { // バケット名、認証情報ID、アップロード対象を指定してライブラリ呼び出し libUploadArtifactsToGCS( bucket: 'jenkins-artifacts', credentialsId: 'google-service-account', pattern: 'upload-sample.ipa' ) }
  21. 31 活用事例:エラーとなったstageの検出
 • ジョブ実行時にエラーとなったstage名を通知に利用したい 
 ◦ エラーとなったstage名を取得は以下の方法知られている 
 3 エラーとなったstage名を通知などに利用する場合

    pipeline { agent { label ‘ubuntu’ } stages { stage ( ‘pre-build’ ) { …. } post { // stage毎の post process にて失敗時タスクで環境変数に stage名を設定 failure { script { env.ERROR_STAGE_NAME = ‘pre-build’ } } } …. } post { failure { // env.ERROR_STAGE_NAME を利用して失敗 stage名を通知など実行 } } }
  22. 活用事例:エラーとなったstageの検出
 
 3 • この方法は以下の問題あり
 ◦ コード量の増加
 ◦ stageが新規追加される度 に追加が必要


    32 エラーとなったstage名を通知などに利用する場合 pipeline { agent { label ‘ubuntu’ } stages { stage ( ‘pre-build’ ) { …. } post { // stage毎の post process にて失敗時タスクで環境変数にstage名を設定 failure { script { env.ERROR_STAGE_NAME = ‘pre-build’ } } } …. } post { failure { // env.ERROR_STAGE_NAME を利用して失敗stage名を通知など実行 } } } メンテナンス性低下の要因となる

  23. 活用事例:エラーとなったstageの検出
 
 3 • ライブラリ側で以下の手順で失 敗したstage名を返却
 ◦ 全てのstage情報取得
 ◦ 先頭から順に調べて最初

    に見つかった失敗状態の stage名を取得
 
 • Pipeline 側は失敗したstage名が 欲しい時に呼び出すのみ
 33 ライブラリ側 (libGetFailedStageName) // stage 情報の取得 ChunkVisitor visitor = new ChunkVisitor( workFlowRun ); ForkScanner.visitSimpleChunks( workFlowRun.getExecution().getCurrentHeads(), visitor, new StageChunkFinder() ); // 全ての stage のステータスを検索 for ( StageNodeExt stageExt : visitor.getStages() ) { if ( stageExt.getStatus() == StatusExt.FAILED ) { errorStageName = stageExt.getName(); break; } } return errorStageName Pipeline側 (呼び出し側) .... post { failure { sh "echo Error Stage: ${libGetFailedStageName()}" } }
  24. 34 活用事例:Slack通知時のアタッチメント作成
 • Slack通知以下のような共通の処理が多く存在 
 ◦ 通知用のアタッチメント作成
 4 アタッチメント作成 JSONObject

    attachmentObj = new JSONObject(); attachmentObj.put( 'title', title.toString() ); attachmentObj.put( 'title_link', title_link.toString() ); attachmentObj.put( 'text', subject.toString() ); attachmentObj.put( 'color', colorCode ); if ( fieldsMessages.size() > 0 ) { JSONArray fileds = new JSONArray(); fieldsMessages.each { Map message -> JSONObject customField = new JSONObject(); customField.put( 'title', message.title.toString() ) customField.put( 'value', message.message.toString() ) customField.put( 'short', message.short ) fileds.add( customField ); } attachmentObj.put( 'fields', fileds ); } アタッチメント作成(続き) if ( messages.size() > 0 ) { messages.each { key, value -> // 既存の設定も上書きできるようにする attachmentObj.put( key, value ); } } JSONArray attachmentArray = new JSONArray(); attachmentArray.add( attachmentObj ); return attachmentArray.toString(); jsonオブジェクトを作成する必要ある 

  25. 活用事例:Slack通知時のアタッチメント作成
 
 4 • ライブラリ側でkey-valueの配列 で情報を受け取りjson文字列を 作成して返却
 ◦ 受け取ったkey-value配列 からjsonオブジェクトを作

    成
 
 • Pipeline側ではkey-value配列を 渡す
 35 ライブラリ側 (libCreateSlackAttachments) JSONObject attachmentObj = new JSONObject(); …. if ( fieldsMessages.size() > 0 ) { JSONArray fileds = new JSONArray(); fieldsMessages.each { Map message -> JSONObject customField = new JSONObject(); …. fileds.add( customField ); } attachmentObj.put( 'fields', fileds ); } JSONArray attachmentArray = new JSONArray(); attachmentArray.add( attachmentObj ); return attachmentArray.toString(); Pipeline側 (呼び出し側) script { def List fieldsMessages = [ [ "title":"Execute Node", "message":"test-node", "short":false ], [ "title":"Test Branch", "message":"origin/master", "short":true ] ] def messages = [ "text": "message" ] def attachments = libCreateSlackAttachments( currentBuild.currentResult, fieldsMessages, messages ) }
  26. 36 活用事例:Slack通知時のメンション先ユーザのID取得
 • Slack通知時にジョブを実行した方へメンションをしたい 
 ◦ ジョブ実行者のSlack IDを取得してメッセージにそのIDを設定 
 5

    ジョブ実行ユーザのemailからslack id の取得 // Slack API: https://slack.com/api/users.list?token=xxx へのリクエスト // Response取得 Response response = client.newCall( request ).execute(); // json へ変換 Object json = new JsonSlurper().parseText( response.body().string() ) // User の mention 用 ID 文字列 String mentionIdsStr = ""; // email から User ID を検出 for ( user in json.members ) { for ( target in params.userEmails ) { if ( user.profile.email == target ) { mentionIdsStr += "<@" + user.id + "> " } } } return mentionIdsStr; これ

  27. 活用事例:Slack通知時のメンション先ユーザのID取得
 
 5 • ライブラリ側でemailを受け取っ てslackのIDを通知形式で返却
 ◦ Slack APIを活用
 


    • Pipeline側はジョブ実行者の emailを渡すだけ
 37 ライブラリ側 (libGetFailedStageName) // Slack API: https://slack.com/api/users.list?token=xxx へのリクエスト // Response取得 Response response = client.newCall( request ).execute(); // json へ変換 Object json = new JsonSlurper().parseText( response.body().string() ) // User の mention 用 ID 文字列 String mentionIdsStr = ""; // email から User ID を検出 for ( user in json.members ) { for ( target in params.userEmails ) { if ( user.profile.email == target ) { mentionIdsStr += "<@" + user.id + "> " } } } return mentionIdsStr; Pipeline側 (呼び出し側) step { def id = libGetFailedStageName( userEmails: ['[email protected]', '[email protected]'], slackAPIToken: 'token’ ) } 現在はSlack Notification Pluginでもemailから SlackのユーザIDの取得が可能になっている模 様

  28. 38 ライブラリ利用の現在
 • 導入当初は1プロジェクトで利用 -> 現在は 複数プロジェクトが利用
 ◦ Jenkins controller

    はプロジェクト毎に構築 
 
 • 他のプロジェクトメンバーもライブラリを作成して共有 
 
 • 既存ライブラリの機能拡張
 6
  29. 39 ライブラリ利用の現在:既存ライブラリの機能拡張
 • GCSファイルのアップロード/ダウンロードに関して 
 ◦ アップロード時に設定可能にした key/tagを利用してindexファイルも保存 
 ▪

    indexファイル保存パス: bucket_name/index/key/tag 
 • index ファイル内にはアップロードした成果物のダウンロードURLを記載
 ◦ ダウンロードの際は key/tagを指定することで成果物のダウンロードが可能 
 ▪ 拡張前はジョブ名と、ビルド番号が必要であり目的のファイルの場所を探すのが面倒
 6 GCSへのアップロード時 // key / tag を設定してアップロード libUploadArtifactsToGCS( bucket: BUCKET_NAME, credentialsId: GCS_CREDENTIAL_ID, pattern: build.ipa, key: "ios_app", tags: ["latest"] ) GCSからダウンロード時 // key / tag を指定して目的の成果物をダウンロード libDownloadArtifactsFromGCS( bucket: BUCKET_NAME, credentialsId: GCS_CREDENTIAL_ID, key: "ios_app", tag: "latest", dirPath: ARTIFACTS_DIR )
  30. 41 まとめ
 • Shared Libraries を利用することで以下の利点 
 ◦ ジョブのメンテナンス性向上
 ◦

    再利用性の向上
 ◦ Jenkins Pluginとは異なり複数バージョン利用可能
 ▪ 更新時の影響範囲が限定
 • 他で作成されたライブラリを利用 
 ◦ アクセス権があれば他のJenkins controllerからも 容易に利用可能
 ◦ より効率的にジョブの作成
 • 多くのプロジェクトで利用しやすいよう ライブラリを改善
 1