Jenkinsの公式Dockerイメージ使ってみた

以前のエントリ( http://knjname.hateblo.jp/entry/2014/05/03/190842 )で自分でJenkinsのDockerイメージを作成したりしてみましたが、 Jenkins公式でDockerイメージを配布するようになったので、それを使用したほうがいいと思います。

普通に使うだけなら、下記のようにすればいいだけですが、

docker run -p 8080:8080 jenkins

これだと何も細かいことを設定できていないので、いくつか補足。

Dockerイメージ内のJenkinsのバージョンについて

基本的にJenkinsのサイトのトップ( http://jenkins-ci.org/ )に載っているようなマジもんの最新版は不安定です。

ただ、DockerイメージのlatestはLTS版(安定版)の最新がデフォルトで指定されているようです。 ちゃんと運用するのであれば明示的なバージョン指定をオススメしますが、特に気にしなくても問題なさそうです。

# 今(2015-02-10)のところ下記3つは同じ
docker run -p 8080:8080 jenkins
docker run -p 8080:8080 jenkins:latest
docker run -p 8080:8080 jenkins:1.580.2

# (実はLTS版は1.580.3がリリースされていたりするがDockerHubにはない模様…)

今のところ、使用されるJavaはOpenJDK7のようです。気に入らなければ自分でイメージを作って別のJDKを使わせるようにしたほうがいいでしょう。

永続化ディレクトリ(JENKINS_HOME)について

永続化が必要なデータはコンテナ内の /var/jenkins_home に作成されるので、ホストディレクトリをその位置にマウントしましょう。

docker run -p 8080:8080 -v /hostdir/jenkins_home:/var/jenkins_home jenkins

ただし、root権限でそのまんま作成したディレクトリ(普通にdocker runが勝手に作ったものがこれに該当)はコンテナ側のJenkinsユーザ(jenkins(UID=1000) )で書くことができません。 以下のいずれかで対処しましょう。

  • オススメ: chown 1000 /hostdir/jenkins_home する。(※UIDはDockerfile内で明示的に指定されているので変わる可能性は低いでしょう)
  • chmod 777 /hostdir/jenkins_home する。
  • 独自のJenkinsイメージを作成してJenkinsの実行ユーザをrootにする。
  • 一度マウントしないで起動させたコンテナ内から/var/jenkins_homeをコピーしたものをあらためてマウントする。

JenkinsのJVMのオプションについて

JavaのメモリetcのためのJVMオプションはJAVA_OPTSという名前で環境変数に設定すればいいです。

docker run -p 8080:8080 -e JAVA_OPTS="-Xmx1g" jenkins

Jenkinsのオプションについて

たとえば、 http://hostname/jenkins のようにURLプレフィックスを設定するには、docker run jenkinsの後ろに引数追加すればいいです。あるいはJENKINS_OPTSに定義すればいいです。

docker run -p 8080:8080 jenkins --prefix=/jenkins
docker run -p 8080:8080 -e JENKINS_OPTS="--prefix=/jenkins" jenkins

Jenkins用の引数一覧は https://wiki.jenkins-ci.org/display/JENKINS/Starting+and+Accessing+Jenkins で一部リスト化されています。

ログファイルの出力先などもオプションでの指定できるはずなのですが、記載がないですね。https://github.com/jenkinsci/jenkins/blob/master/rpm/SOURCES/jenkins.init.in とかを参考に追加してみるといいかもしれません。

docker run -p 8080:8080 jenkins --logfile=/var/jenkins_home/jenkins.log

Jenkins初期化Groovyスクリプト

ほか、設定ファイルに永続化されておりパラメータでは初期指定できない情報や、 初期プラグインを勝手にインストールさせるなどを行わせるGroovyスクリプトをロードさせることができます。

(Groovyでの自動化の詳細は https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+Script+Console を見て下さい。)

ためしに、下記のスクリプト(my-jenkins-init.groovy)が入ったディレクトリを /var/jenkins_home/init.groovy.d/ にマウントしてみましょう。

// my-jenkins-init.groovy
import hudson.model.*;
import jenkins.model.*;

def inst = Jenkins.instance

def uCenter = inst.updateCenter

// Set the proxy for retrieving plugins. (Comment out and modify it if you need.)
// inst.proxy = new hudson.ProxyConfiguration("192.168.10.10", 3128, "proxyUser", "proxyPassword", "localhost, 192.*")

// Ensure the Update Center is up-to-date.
uCenter.updateAllSites()

def checkIfPluginInstalled = { pluginid ->
  for(p in inst.pluginManager.plugins) {
    if(p.shortName == pluginid){
      return true
    }
  }
  return false
}

// Installs the following plugins if not installed.
["checkstyle", "copyartifact", "build-timeout", "emotional-jenkins-plugin"].findAll({! checkIfPluginInstalled(it)}).each {
  uCenter.getPlugin(it).deploy()
}

// Set the number of executors (in master-node) to 0.
inst.setNumExecutors(0)

inst.reload()

自動でいくつかプラグインがインストールされており、マスターノードのエグゼキュータ数が0の設定が行われているはずです。

初期のイメージでは、スレーブ用のJNLPポートの自動設定スクリプトが入っているようです。

// tcp-slave-angent-port.groovy
import jenkins.model.*;

Thread.start {
      sleep 10000
      println "--> setting agent port for jnlp"
      Jenkins.instance.setSlaveAgentPort(50000)
}
Feb 09, 2015 5:08:41 PM jenkins.util.groovy.GroovyHookScript execute
INFO: Executing /var/jenkins_home/init.groovy.d/tcp-slave-angent-port.groovy
Feb 09, 2015 5:08:43 PM org.jenkinsci.main.modules.sshd.SSHD start

(2015/09/09) 今のJenkinsのイメージだとコンテナ起動時に指定された環境変数 JENKINS_SLAVE_AGENT_PORT の番号にJNLPのポートを変更するようになっています。良い改善だと思います。

ということで、

だいたい上記でやりたいことはできます。

yumapt-getで入れたパッケージ版のJenkinsも結局は、 上記で説明しているようなパラメータの設定を設定ファイルにバラしているだけなので、 パッケージ版に機能で遅れを取るといったことは一切心配不要です。

Jenkinsのマスターはマスター!

以前のエントリ( http://knjname.hateblo.jp/entry/2014/05/03/190842 )でも書いたとおり、Jenkinsのマスターノードにビルドを直接させる必要は一切ありません。

大量にいろんな種類のジョブをこなすようになると、スレーブが増えていき、 そんな中でマスターがビルドしているのが不自然かつ、マスター依存のジョブがあった日にはスケールが面倒になってきます。

特に貧弱な環境じゃない限り私はマスターノードにビルドをさせていません。マスターのジョブキューの太さは0にしてます。

今回のエントリでふれているのは、Jenkinsマスターノードのセットアップであり、スレーブノードの話ではありません。 Jenkinsのマスターノードは、WebUIを提供して、設定を保存して、スレーブノードとつながって、 スレーブが収めてくるジョブの履歴などをディレクトリに貯めていればそれでいいんです。(というのが私の考え。)

Jenkinsが活躍している開発現場でマスターノードが死ぬと開発者への影響が大ですが、 スレーブに関していえばホストOSごとおっ死んでも大して影響はないでしょうし、代替のスレーブがいればスレーブの1台2台ダウンしようが 平気なところも多いはずです。

実際ビルドを行わさせるスレーブについては、Jenkinsのビルド用のDockerコンテナを作成してSSHでマスタから繋くとかの平易な構成でいいと思います。 (もちろんDockerコンテナ使い捨てとか頑張るのもアリ)

ちなみにスレーブ内にビルド用のJDKやAntを含めてもいいですが、実際には含めず、Jenkinsの設定画面で JDKやAntのtar.gzやzipをビルド実行時にネットからインストール or どっかのURLから解凍できるように構成したほうが楽です。 (とはいえ、スレーブプロセスはJVMで動くので、スレーブ自身が動くためのJREは必要です。)

現状の公式Dockerイメージへの個人的な不満点

  • OpenJDK7を使っている。OpenJDK8じゃ駄目なのかな。個人的にはヒープはともかく、いろいろやると広がるパーマネント領域を意識したくない。 → 最新のイメージはOpenJDK8使っているようです (2015/08/05)
  • /var/jenkins_homeにroot権限で作成した外部ディレクトリをマウントさせると起動に失敗する件。…まあこれはDocker自体の問題に近いですが。 → docker run 時に -u root オプション付ければうまくいくことも多いでしょう(2015/09/09)
  • Jenkinsのログファイルの出力場所やログレベルの指定など細かい起動オプションが分かりづらい。