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

Jenkins PipelineでのShared Librariesの活用

Jenkins PipelineでのShared Librariesの活用

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]

    井口 恒志 

    システム本部 品質統括部 品質管理部 SWET第二グループ

    株式会社ディー・エヌ・エー

    @ DeNA Co.,Ltd.

    View full-size slide

  2. 2
    自己紹介

    井口 恒志 (Hisashi Iguchi)

    自動テストの開発・導入サポート

    CI/CDの活用促進や関連技術の調査

    プロジェクトの状態可視化基盤などの整備

    CircleCI のJapanユーザコミュニティでも活動中


    バスケ好きで試合観戦や週1, 2でプレイもしてます

    DeNA システム本部 SWET第二グループ

    @hisa9chi


    View full-size slide

  3. 3
    目次

    SWETとは

    Shared Libraries

    活用事例

    まとめ

    1
    2
    3
    4

    View full-size slide

  4. 4
    本発表に関して

    ● 弊社チームのTesting Blogへ投
    稿した内容を発表形式にまとめ
    ています

    https://swet.dena.com/entry/2021/01/18/200000

    View full-size slide

  5. 5
    本発表での前提

    ● Jenkins controllerのversionは2.x系


    ● Jenkinsのジョブの種類は“Pipeline”

    ○ ジョブの定義はJenkinsfileにしてリポジトリへ登録

    ○ Pipeline Syntaxは“Declarative Pipeline”


    View full-size slide

  6. 6
    1. SWETとは

    SWETのビジョンや役割などを紹介


    View full-size slide

  7. 7
    SoftWare Engineer in Test

    c.f.> Google SET 「テストから見えてくるグーグルのソフトウェア開発」 

    https://www.nikkeibp.co.jp/atclpubmkt/book/13/P85120/ 


    View full-size slide

  8. 8
    MISSION

    1
    ソフトウェアを起点とした

    ○ DeNAサービス全般の品質向上

    ○ DeNAエンジニアの開発生産性向上

    により、価値あるものを素早く提供できるようにする

    View full-size slide

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

    2
    ● Fun

    ○ テスティングを楽しくクリエイティブなものにする

    ● Smart

    ○ 定型的なテストを人の手を煩わせず実現する

    ○ テストによるフィードバックを上手に活用して、素早い開発サイクルを実現す
    る

    ● Delighting End-Users

    ○ テストにより事業の成功を後押しするため、エンドユーザをデライトさせる
    様々な品質を計測し改善する


    View full-size slide

  10. 10
    SWETメンバーのプロジェクトへの関わり

    3
    ● ソフトウェアテスト技術分野ごとのチーム

    ○ アーキテクチャ

    ○ 自動テスト

    ○ プロセス(CI/CD)

    ● それぞれのチームが様々な技術領域の案件をサポート

    ○ QAによるテスト工程よりも前の工程で品質・生産性の向上

    ○ デバッグの効率化・工数削減などテスト技術の蓄積

    ○ テスト全般の実行環境・プロセスサポート・サービス化

    View full-size slide

  11. 11
    プロセスチームのJenkinsに関する取り組み

    4
    ● チームではJenkinsの運用コストを削減するための取り組みを実施






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


    View full-size slide

  12. 12
    2
    SWETの取り組みなどを

    ブログで定期的に投稿中


    https://swet.dena.com


    もしくは


    SWET Testing Blog で検索


    View full-size slide

  13. 13
    2. Shared Libraries

    利用方法や特徴などを紹介


    View full-size slide

  14. Pipeline

    1
    ● PipelineのJenkinsfileをリポジトリ
    に登録・管理

    14
    sample-01.jenkinsfile
    pipeline {
    agent { label 'ubuntu' }
    stages {
    stage ( 'pre-build' ) {
    ....
    }
    stage ( ‘build’ ) {
    ....
    }
    }
    }

    View full-size slide

  15. 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’ ) {
    ....
    }
    }
    }

    View full-size slide

  16. 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() {
    ….
    }

    View full-size slide

  17. 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() {
    ….
    }
    重複部分を外部に定義

    再利用性とメンテナンス性が向上

    View full-size slide

  18. 18
    Shared Libraries


    View full-size slide

  19. 19
    Shared Libraries : 概要

    ● 概要

    ○ Pipelineの外部に定義

    ○ ライブラリはSCM管理

    ○ ライブラリはPipeline毎にロードして利用

    ■ ロードは複数指定可能 

    ● 特徴

    ○ ジョブ毎で異なるバージョンの利用が可能

    ○ メンテナンス性と再利用性の向上

    ○ クラスのstaticメソッドのapproveが不要

    2

    View full-size slide

  20. 20
    Shared Libraries : 特徴 - ジョブ毎で異なるバージョンの利用が可能

    ● ライブラリのロード時に以下の指定が可能

    ○ タグ

    ○ ブランチ

    ○ コミットハッシュ

    2
    ライブラリのロード方法
    // デフォルトバージョンの読み込み
    // "Global Pipeline Libraries 設定" の "Default version"
    @Library( 'ライブラリ名' ) _
    // ブランチ,タグ,コミットハッシュを指定する場合
    @Library( 'ライブラリ名@{branch | tag | commit hash}' ) _
    // 複数ライブラリの読み込み
    @Library( ['ライブラリ名1', 'ライブラリ名2'] ) _
    pipeline {
    agent { label 'ubuntu' }
    ....
    }

    View full-size slide

  21. 21
    Shared Libraries : 特徴 - メンテナンス性と再利用性の向上

    ● 変更はライブラリ内に閉じる

    ● 他のJenkinsからも利用可能

    ○ ライブラリを管理しているリポジトリのアクセス権は必要

    ● Plugin利用をラップしたライブラリの作成

    ○ Pluginの更新による変更影響をライブラリ内に閉じれる場合あり

    2

    View full-size slide

  22. 22
    Shared Libraries : 特徴 - クラスのstaticメソッドのapproveが不要

    ● Pipelineではgroovyによるクラスのstaticメソッドが実行可能

    ○ セキュリティ的に問題

    ○ 管理者が承認したクラスのstaticメソッドの場合実行可能

    ● Pipeline内のgroovyコードにてクラスのstaticメソッドを利用した場合

    ○ 利用メソッド全てにapproveが必要

    ○ 結構面倒な作業(管理者に承認してもらう必要があるため)

    2

    View full-size slide

  23. 23
    Shared Libraries : 特徴 - クラスのstaticメソッドのapproveが不要

    ● Pipelineではgroovyによるクラスのstaticメソッドが実行可能

    ○ セキュリティ的に問題

    ○ 管理者が承認したクラスのstaticメソッドの場合実行可能

    ● Pipeline内のgroovyコードにてクラスのstaticメソッドを利用した場合

    ○ 利用メソッド全てにapproveが必要

    ○ 結構面倒な作業(管理者に承認してもらう必要があるため)

    ● ライブラリであればapproveは不要

    ○ 承認の面倒な作業が発生しない

    2

    View full-size slide

  24. 24
    Shared Libraries : Jenkins Plugin と比較

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

    ※2 Plugin を利用している場合は Plugin の更新に依存して変更影響が発生する場合あり 


    View full-size slide

  25. 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

    View full-size slide

  26. 26
    3. 活用事例

    社内で Shared Libraries をどのように活用しているか紹介


    View full-size slide

  27. 27
    活用事例

    ● ジョブの成果物のGCS(Google Cloud Storage)保存

    ● エラーとなったstage名の検出

    ● Slack通知時のアタッチメント作成

    ● Slack通知時のメンション先ユーザのID取得

    1

    View full-size slide

  28. 28
    活用事例:ジョブの成果物のGCS保存

    ● ジョブ実行時の成果物はJenkins controller自体に保存が可能

    ○ controllerの容量には制限

    ■ ディスクの拡張などのメンテが必要

    ○ 成果物(アプリのバイナリなど)をJenkins外から取得する時に面倒

    ● ジョブの成果物をGCSへ保存

    ○ ストレージの拡張などメンテフリー

    ○ オブジェクトのライフサイクル設定でデータ自動削除可能

    ○ パブリッククラウドのため他サービスへの取り回しが比較的楽

    2

    View full-size slide

  29. 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をラップしたライブラリを作成

    View full-size slide

  30. 活用事例:ジョブの成果物の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' )
    }

    View full-size slide

  31. 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名を通知など実行 }
    }
    }

    View full-size slide

  32. 活用事例:エラーとなった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名を通知など実行 }
    }
    }
    メンテナンス性低下の要因となる


    View full-size slide

  33. 活用事例:エラーとなった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()}" }
    }

    View full-size slide

  34. 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オブジェクトを作成する必要ある

    View full-size slide

  35. 活用事例: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 )
    }

    View full-size slide

  36. 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;
    これ


    View full-size slide

  37. 活用事例: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の取得が可能になっている模
    様


    View full-size slide

  38. 38
    ライブラリ利用の現在

    ● 導入当初は1プロジェクトで利用 -> 現在は
    複数プロジェクトが利用

    ○ Jenkins controller はプロジェクト毎に構築


    ● 他のプロジェクトメンバーもライブラリを作成して共有


    ● 既存ライブラリの機能拡張

    6

    View full-size slide

  39. 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 )

    View full-size slide

  40. 40
    4. まとめ 

    本日の総括です


    View full-size slide

  41. 41
    まとめ

    ● Shared Libraries を利用することで以下の利点

    ○ ジョブのメンテナンス性向上

    ○ 再利用性の向上

    ○ Jenkins Pluginとは異なり複数バージョン利用可能

    ■ 更新時の影響範囲が限定

    ● 他で作成されたライブラリを利用

    ○ アクセス権があれば他のJenkins controllerからも
    容易に利用可能

    ○ より効率的にジョブの作成

    ● 多くのプロジェクトで利用しやすいよう
    ライブラリを改善

    1

    View full-size slide

  42. 42
    質疑応答 

    何かあれば


    View full-size slide

  43. @ DeNA Co.,Ltd.

    View full-size slide