読者です 読者をやめる 読者になる 読者になる

あらしおブログ

技術ネタ中心の粗削りブログ

Jenkins パイプラインの stash/unstash を使ってノード間でファイルを転送する

Jenkins のパイプラインで stash/unstash を使って異なるノード間でファイルを転送する方法を紹介します。 英語の stash には、「しまう」という意味があるようです。一時的にファイルをどこかにしまって(stash)、 必要になったら取り出す(unstash) という操作です。

使いどころとしては、

  • マスターでソースコードをチェックアウトし、複数のスレーブに転送して並列テストを実行する*1
  • スレーブノードで得られたビルド後の成果物をマスターに転送する

などが考えられます。 便利なのですが、「しまう」という表現のためか、ちょっと抽象化されすぎていて stash/unstash の中で実際に何をしているのか不安だったので、そこらへんも調べてみました。

stash/unstash を使った例

以下のような簡単なシナリオで stash/unstash の動作を確認しましょう。

  1. マスターで git からソースコードをチェックアウト後、stash する。
  2. スレーブでソースコードunstash 後、テストを実行する

パイプラインスクリプトは以下のようになります。(スレーブノードの環境設定とノード追加は実施済みとします)

node('master') {
    deleteDir()
    git url: 'https://github.com/arasio/simple-gradle-project-with-tests.git'
    stash name: 'project'
    sh 'ls -l'
}

node('slave1') {
    deleteDir()
    unstash 'project'
    sh 'ls -l'
    sh 'gradle test'
}
まずはワークスペースの掃除

各ノードでまず deleteDir() をしているのは、前回のビルドでワークスペースに残っているゴミを掃除するためです。 上のジョブを1回実行すると、マスターでは git からチェックアウトしたソースコードがそのまま残りますし、 スレーブでも unstash したソースコードがそのまま残ってしまいます。ワークスペースはまっさらにしておいたほうが精神衛生上良いですね。

マスターでソースコードを stash する

git ステップでソースコードをチェックアウトしたあと、 stash name: 'project' で現在のディレクトリにあるファイルやディレクトリを stash します。 後で unstash できるように stash するときは名前をつけます。 ここで ls -l を実行してワークスペースの状態を確認してみると、下のようになっています。 これらのファイルが stash されたことになります。

+ ls -l
total 28
-rw-r--r-- 1 jenkins jenkins 1063 Oct 18 12:47 LICENSE 
-rw-r--r-- 1 jenkins jenkins  253 Oct 18 12:47 build.gradle
drwxr-xr-x 3 jenkins jenkins 4096 Oct 18 12:47 gradle
-rwxr-xr-x 1 jenkins jenkins 5242 Oct 18 12:47 gradlew
-rw-r--r-- 1 jenkins jenkins 2260 Oct 18 12:47 gradlew.bat
drwxr-xr-x 4 jenkins jenkins 4096 Oct 18 12:47 src
スレーブでソースコードを unstash する

スレーブ側でもワークスペースを掃除した後、unstash 'project' を実行すると、さきほど stash したソースコードをスレーブノード上で取り出すことができます。 unstash 後に ls -l を実行して確認すると、上のマスターのワークスペースと全く同じ状態になっていることがわかります。 ちなみに、ディレクトリは再帰的に処理されますので、src ディレクトリ以下もすべて転送されています。

そのあとはテストを実行するなり、実際にやりたい処理を書くことになるでしょう。

stash/unstash の中では何が起きている?

stash/unstash が実際にどのような処理になっているか知っておくと安心して使えます。 基本的には

  • stash: 対象ノードのカレントディレクトリを圧縮してマスターの所定の場所に退避
  • unstash: マスターに退避した圧縮ファイルを対象ノードのカレントディレクトリにコピーして展開

という処理になっています。

stash を実行すると対象ノードのカレントディレクトリを tar.gz で圧縮して、ジョブのビルドディレクトリに保存します。 上の例では、stash された圧縮ファイルは以下にできていました。stash するときに 'project' という名前をつけたので project.tar.gz になっています。

${JENKINS_HOME}/jobs/${JOB_NAME}/builds/${BUILD_NUMBER}/stashes/project.tar.gz

このファイルは一時ファイルなので、ビルドが終了すると消えます。掃除は考える必要がありません。 スレーブで stash を実行してもマスターのビルドディレクトリに圧縮ファイルが生成されます。

unstash は stash で退避した圧縮ファイルを対象ノードにコピーして展開します。 注意したいのが展開したファイルの所有者はすべて jenkins ユーザになることです。 圧縮が jenkins ユーザで実行されるので展開すると元の所有者によらず jenkins ユーザになります。

より詳細に知りたい方は Jenkins のソースコードを直接確認すると良いでしょう。私は以下を参考にしました。

オプション

stash は基本的にはカレントディレクトリを圧縮するのですが、 いくつかのオプションが用意されています。

stash name: 'test', excludes: '**/*.log'

ワイルドカードの表記は ant スタイルに従います。

stash name: 'test', includes: '**/*.java'
  • デフォルトで除外されるファイルも stash する (useDefaultExcludes)

デフォルトでは特定の隠しファイルなどは stash されません。上の例では、.git/ や .gitignore などは実は stash されていません。 これらのファイルも含めて stash/unstash したい場合は useDefaultExcludesfalse に設定します。

stash name: 'test', useDefaultExcludes: false

なお、デフォルトで除外されるファイル一覧は以下のようになっています。

**/*~
**/#*#
**/.#*
**/%*%
**/._*
**/CVS
**/CVS/**
**/.cvsignore
**/SCCS
**/SCCS/**
**/vssver.scc
**/.svn
**/.svn/**
**/.DS_Store
**/.git
**/.git/**
**/.gitattributes
**/.gitignore
**/.gitmodules
**/.hg
**/.hg/**
**/.hgignore
**/.hgsub
**/.hgsubstate
**/.hgtags
**/.bzr
**/.bzr/**
**/.bzrignore

出典: https://ant.apache.org/manual/dirtasks.html#defaultexcludes

*1:下の記事で実際のサンプルを紹介していますので興味ある方はご覧くださいarasio.hatenablog.com