背景
Jenkins Pipelineは便利ですよね。そのスクリプトの開発&動作検証はDockerなどでローカル端末に構築したJenkinsサーバを利用し、それらを用いて以下の3パターンのどれかで進めている人が多いのではないでしょうか。
- テキストボックスに直接追記、編集しながら動作検証。終わったらスクリプトファイル化する
 - JenkinsのReplay 機能を利用
 - スクリプトにはロジックをほとんど記載せず、そこから呼び出すShell ScriptやNodejsに処理を寄せて、それらのShell ScriptやNodejsを個別にテストする
 
一方で、3のパターンで呼び出している Shell ScriptやNodejsが、Pipelineスクリプトと強く依存していたり、それらのファイルの呼び出し方が分からなかったりするケースも多く、とはいえ編集内容をいちいちgit pushして外部のSCM経由ではなくもっと気軽に動作検証したいという場合も多いのではないでしょうか?
開発生産性の観点や、使い捨てできる環境を作るという面でも、本記事を役立ててもらえればと思います。
前提
- JenkinsのPipelineスクリプトはローカルのパスを指定できない
- SCM1(GitやSVN)のリポジトリを指定する必要がある
 - このため、dockerのVolumeマウントでホスト側のファイルを見る手段は取れない
 
 
環境構成
ホスト側にSSHを利用したGitサーバをたてて、コンテナ内のJenkinsサーバから参照する方法を取ります。
環境
Windows10、 Git 2.23、 OpenSSH for Windows7.7です。
>ver
Microsoft Windows [Version 10.0.18362.295]
>git --version
git version 2.23.0.windows.1
>ssh -V
OpenSSH_for_Windows_7.7p1, LibreSSL 2.6.5
Macの場合はホスト側のSSHサーバの構築がおそらくグッと楽になるくらいで、ほぼ同じ手順で再現できると思います。
💻ホスト側
今回の手順ではコンテナ上からはHTTP(s)ではなくSSHでgitリポジトリを参照させるため、まずWindows側にSSHサーバを構築します。
OpenSSH
Windows本体に組み込まれているので機能を有効にすればOKです。
- Windowsの設定からアプリを開く
 - アプリと機能のページにあるアプリと機能の欄のオプション機能の管理を開く
 - 機能追加を開く
 - OpenSSH Clientをインストール
 - 
ssh localhostで接続できたらOK. 
デフォルトShellの変更
OpenSSHのデフォルトShellはWindowsの(多分)コマンドプロンプトになっているため、このままではsshを使ったgitクライアントが認識してくれません。そのためデフォルトでShellをgit bash に変更します。
- PowerSellを管理者モードで開く
 - デフォルトShellを変更
 
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Program Files\Git\bin\bash.exe" -PropertyType String -Force
- OpenSSHの再起動
 
Restart-Service sshd
- 
ssh localhostで接続先のコンソールが変更されていればOK. 
ホスト側の設定は以上です。
sshd_configの設定
コンテナからのssh接続をパスワード無しログインで連携したいため、設定を変更します。
sshd_configがどこにあるかで諸説ありますが、私の環境では C:\ProgramData\ssh\sshd_config が有効でした。
以下のようにPubkeyAuthentication yes とコメントを外してyesに必要に応じて書き換えてください。
ついでに、いざというときの切り分けのためにsshdのログも出力するようにしておきます。(ログは、C:\ProgramData\ssh\logs 配下に出力されます)。
また、私の環境では、 AuthorizedKeysFile が以下のように __PROGRAMDATA__ といった変なパスが指定されていたためコメントアウトします。
PubkeyAuthentication yes
SyslogFacility LOCAL0
LogLevel DEBUG
# コメントアウトする
# Match Group administrators
#       AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
🐋コンテナ側
JenkinsはバージョンLTSを利用します。まずはDockerfile(composeのYAML)を作成.
version: "3"
services:
  jenkins:
    container_name: localjenkins
    image: jenkins/jenkins:lts
    ports:
      - 8080:8080
    volumes:
      - ./jenkins_home:/var/jenkins_home
続いて、コンテナイメージを取得し実行します。
docker-compose up -d
ここからコンテナに接続して(!)の作業です。
ホストと鍵交換さえできればよいので本来は docker build で完結できる気もしますが、build中にホストのネットワーク解決出来ないだろうと思ったので、調査する気に慣れず素直にコンテナ内作業です。
docker exec -it localjenkins /bin/bash
SSH接続のための設定を行います。
# 同じホスト内でしか接続させないので、全部空指定でOK
ssh-keygen
# ホスト側に公開鍵を登録
# <User>は各環境のものに書き換えてください。host.docker.internalはホストOSを指します
ssh-copy-id <User>@host.docker.internal
# SSH接続確認を実施(接続できたらexitとかでログアウトしてください)
# "Permission denied (publickey,keyboard-interactive)." が出る場合は鍵交換が間違ったか、SSHサーバの設定不備が考えられます
ssh <User>@localhost
# SSHが繋がっていれば、git接続を確認
git ls-remote <User>@host.docker.internal:/c/projects/juv/.git HEAD
# git cloneを確認. ホスト側は C:\projects 配下にGitHubなどからgit cloneしたリポジトリがあるとします
# もしPermission errorが出ていたらLinuxユーザの権限を確認
cd /home
git clone <User>@host.docker.internal:/c/projects/<Your Repository>/.git
ここまででDockerコンテナ内から、ホストOSのgitリポジトリを参照できたことになります🎉
以下のようなエラーが出た場合は、リポジトリパスが間違っている可能性があります。最後は/.git なのでご注意ください。 <Your Repository>.git ではないです。
fatal: 'C:/projects/<Your Repository>' does not appear to be a git repository
fatal: Could not read from remote repository.
ちなみに、最後のcloneは git clone ssh://<User>@host.docker.internal:/c/projects/<Your Repository>/.git でも成功します。
お好みでどちらでもどうぞ。
🎩Jenkins設定
Jenkins起動時に出力されるAdmin passwordを取得します。 docker logs localjenkins で見つけましょう。
docker logs localjenkins
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
*************************************************************
続いて、以下の手順でセットアップします。ここからは通常のJenkinsJobを作成する手順なので適時読み替えをお願いします。
- 
localhost:8080にブラウザでアクセス - さきほど取得したAdmin passwordを入力
 - Setupで、リコメンド設定をクリック
 - 「Create First Admin User」で任意のユーザを作成
 - 新規ジョブ作成で、例として 
パイプラインを選択 - パイプラインの定義> SCM> Git > リポジトリに、 
<User>@host.docker.internal:/c/projects/<Your Repository>/.gitを入力 
- エラーが出なければOK. なにか問題があればパスやRSAファイル、SSHサーバの設定を確認ください
 - credentials は指定不要です
 
- Script Pathにリポジトリに含まれるGroovyファイルを指定
 
これでホスト側のgitリポジトリを参照するジョブが作成できました。
🚀実行
例として参照するホスト側のGitリポジトリ直下に以下のようなechoだけするファイルを置いています。
pipeline {
    agent any
    stages {
        stage('hello') {
            steps {
                echo 'hello world'
            }
        }
    }
}
このGroovyファイルを実行するJenkins Jobを作成してから、実行すると想定通りhello world が出力。
(略)
First time build. Skipping changelog.
[Pipeline] }
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (hello)
[Pipeline] echo
hello world
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
エディタでhello.groovyのecho部分をhello world hello worldと書き換えて、git commit -m "update" し、ジョブを再実行。
(略)
Commit message: "update"
 > git rev-list --no-walk c8c2af6dde9272a1049b9cdcb38f04952fee9682 # timeout=10
[Pipeline] }
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (hello)
[Pipeline] echo
hello world hello world
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
...認識していますね🎉
ということで、ホスト側のファイルを git commit さえしてしまえば、コンテナ上のJenkinsがその変更を取得してくれました。
今回は簡略化のためgroovyファイルだけの差分ですが、groovyからShell ScriptやNodejsのファイルを読み込んでいたとしても同様に認識してくれるため、いちいちリモートへgit pushする手間を省いて作業できるため多少は効率的に開発ができると思います。
さらに上を目指したい方向けですが、ジョブのトリガーをHTTPにして、gitのcommit-hookスクリプトで、コミットした瞬間にJenkinsジョブをキックするという手法も考案されています2。パラメータ付きのジョブには利用しにくいかもしれませんが、条件によっては非常に有用な手法だと思いますのでぜひご検討ください。
まとめ
- JenkinsはDockerで簡単に建てられるし、Pipeline Scriptでちょっとした修正の動作確認する場合は"Replay" 機能が便利
 - ローカルのPipeline Scriptから呼ばれるNode.jsなどのスクリプトをPipeline Script経由で動かしたい場合は、ローカルファイルパスを参照できず、GitHubやGitLabなどのSCM経由になりやや手間
 - ホスト上でSSHサーバを動かしGitサーバとしてJenkinsから参照させれば、git pushの手間は回避でき、開発生産性が少しは上がるし、気軽に使い捨てることができる環境を作れる
 
参考
- https://stackoverflow.com/questions/36309063/how-can-i-test-a-change-made-to-jenkinsfile-locally
 - https://qiita.com/a4_nghm/items/b5b339dce43824e07531
 
- 
Software Configuration Management ↩
 - 
https://stackoverflow.com/questions/36309063/how-can-i-test-a-change-made-to-jenkinsfile-locally ↩
 
