DockerでJenkinsサーバ(master/slave)を構築してみる
概要
タイトルどおり、DockerでJenkinsをマスター・スレーブ構成で作成します。
読者はDockerとJenkinsをちょっとずつやったことがある人を想定しています。
このエントリで使っているソースは全てGithubリポジトリにて入手可能です。
どうやってやるか
DockerでJenkinsのマスターのイメージとスレーブのイメージをそれぞれ作って、それぞれ単純に立ち上げる。
マスターのイメージについては、おそらく1インスタンスしか立ち上げないが、スレーブについてはNインスタンス立ち上げるイメージ。
Dockerでやるメリットとデメリット
メリット
- すぐに使い捨てスレーブ作れるよ。Dockerがインストールされていればそこにスレーブ作れるよ。
- Jenkinsマスターも量産しようと思えばできるよ。
- ビルドにどんなツールが必要なのかDockerfile見りゃわかるよ。
デメリット
素でやるのに比べたらまじで色々とめんどい。
Jenkins構成概要
マスターとスレーブ(1つ以上)にわけて構成します。どんなに小規模であってもそうします。
マスターノード
マスターノードは基本的に何もビルドさせません。また、ビルドに必要なツールを持つ必要すらありません。
初期はJavaとJenkinsだけインストールしてある状態ですね。
下記の役割を受け持ちます。
- Webインタフェースの提供
- プラグイン・設定・ビルド履歴の保持(永続情報の保持)
- スレーブの管理
スレーブノード
スレーブノードが基本的にビルドを行います。
よって、ビルドに必要なツール(Antとかgccとか)やパッケージはスレーブで持ちます。
こうすることにより下記のメリットがあります。
- マスターノードに依存してしまう、ジョブの作成を防ぐ
- スレーブノードを好きなマシンに作成することができる
今回はDockerで構成するのでスレーブの複製・使い捨ても簡単です。
SSHでスレーブを構成します。
Dockerfile構成概要
上記の構成にあわせ、Dockerfileはマスターノード用とスレーブノード用にわけます。
マスター用Dockerfile
マスター用Dockerfileは下記要件を満たしている必要があります。
- JDKインストール済みであること
- Jenkinsインストール済み+立ち上がっていること
- 細かい設定が環境変数で指定可能であること (→ここらへんは後述のrun.shにて実現しています)
- 例)メモリetc JVMオプションが指定可能であること。
- 言うまでもなく必須条件ですね。
- 例)JNLPポート
- 例)Jenkinsのログの出力場所
- 例)JenkinsのURLのprefix
- → http://your_host/prefix にJenkinsを起動させたい場合も多いかと思います。(フロントにnginxをかませる場合など)これも環境変数で指定できるとクールです。
- 例)メモリetc JVMオプションが指定可能であること。
ログフォルダやJENKINS_HOME(ビルド履歴、プラグインなどが入るディレクトリ)は全て外部ボリュームとしてdocker run
時に指定し、永続化させます。
(外部ボリュームを指定したほうがIOパフォーマンスは出るようです。 参考:LXCベースのDockerゲストマシンとホストマシンのディスクI/O性能を比較検証(Bonnie++編) - Y-Ken Studio))
スレーブ用Dockerfile
スレーブ用Dockerfileは下記要件を満たしている必要があります。
今のところLinux以外のスレーブは想定してませんが、Linuxで多彩なスレーブが必要であればスレーブ用Dockerfileのバリエーションを作っておくことにより、色々なビルド要件に合わせることが可能です。
スレーブについて、起動時、ワークスペースを外部ボリュームにするかは任意ですが、ワークスペースを再構成するためのコスト(チェックアウトしなおしetc)のパフォーマンスが気になる人は外部ボリュームにしておきましょう。
Dockerfile実装例
マスター用Dockerfile
ここではknjname/jenkins-masterとしてDockerイメージをビルドします。
基本的には、JDKとSSHクライアント(多分要らない)入れ、SSH鍵をローカルに持っておいて(Jenkinsの設定次第では不要)、Jenkinsをインストールして、run.shという起動用のスクリプトファイルをコピーしているだけです。
起動用のrun.shというスクリプトについて
Dockerコンテナ起動時の環境変数で色々とJenkinsの挙動を制御できるようにしたいので、Dockerから叩くためのbashシェルスクリプトファイルとしてrun.shを作成し、追加しています。
(今回はもともとJenkinsのrpmに入っているサービススクリプト用ファイルをベースに自分の都合のいいように書き換えました。)
こういう便利な起動スクリプトが出来てしまうのは、Docker界隈ではよくあることです。
下記オプションが環境変数で指定可能です。-e
で実行時に指定して下さい。(今回はJNLPポートしか指定しません)
: ${JENKINS_WAR:="/jenkins/bin/jenkins.war"} : ${JENKINS_HOME:="/jenkins/home"} : ${JENKINS_LOG_DIR:="/jenkins/logs"} : ${JENKINS_URL_PREFIX:="/"} : ${JENKINS_JNLP_PORT:=""} : ${JENKINS_JAVA_CMD:="java"} : ${JENKINS_USER:="root"} : ${JENKINS_JAVA_OPTIONS:="-Djava.awt.headless=true"} : ${JENKINS_PORT:="8080"} : ${JENKINS_LISTEN_ADDRESS:=""} : ${JENKINS_HTTPS_PORT:=""} : ${JENKINS_HTTPS_LISTEN_ADDRESS:=""} : ${JENKINS_AJP_PORT:="-1"} : ${JENKINS_AJP_LISTEN_ADDRESS:=""} : ${JENKINS_DEBUG_LEVEL:="5"} : ${JENKINS_ENABLE_ACCESS_LOG:="no"} : ${JENKINS_HANDLER_MAX:="100"} : ${JENKINS_HANDLER_IDLE:="20"} : ${JENKINS_ARGS:=""}
スレーブ用Dockerfile
ここではknjname/jenkins-slaveとしてDockerイメージをビルドします。
基本的には、JDKとOpenSSHサーバ、他ビルドに必要なツールをインストールして、マスターノードをそのままSSHログインさせるためのauthorized_keysとその他(多分いらない)を持ち、最後にSSHDサーバを起動しているだけですね。
動かしてみよう
マスターノード
下記のコマンドで動かします。
-p ~~:~~
でDockerコンテナ内外のポートを結びつけています。8080でJenkinsのウェブインタフェース、10080でJNLPポートにバインドする構成です。
-e JENKINIS_JNLP_PORT=
やらの指定でJenkinsの細かいオプションを指定しています。今回はJenkinsのJNLPポートを指定してみました。
-v ~~:~~
のところで、永続化のためにログディレクトリやJENKINS_HOMEのための外部ディレクトリ(ボリューム)を指定しています。
立ち上がるとブラウザでアクセス可能なはずです。
これにスレーブの設定を追加したいところですね。
スレーブノード
下記のコマンドで動かします。
今回は特に外部ボリュームでスレーブのワークスペースは外部指定しませんでしたが、パフォーマンスを考えると-v どっか適当なディレクトリ:/jenkins/ws
したほうがいいです。
マスターとスレーブを連動させる
今までの手順を踏むと docker ps
は下記に近い状態になっているはずです。
root@ubuntuserver:~/jenkins-docker-example/master# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 03cf6daec38f knjname/jenkins-master:latest /bin/sh -c 'bash /je 18 minutes ago Up 18 minutes 0.0.0.0:8080->8080/tcp, 0.0.0.0:10080->10080/tcp jenkins-master c77efec916c7 knjname/jenkins-slave:latest /bin/sh -c '/usr/sbi About an hour ago Up About an hour 0.0.0.0:10022->22/tcp jenkins-slave
またここでは、 192.168.11.6サーバに上記コンテナが立ち上がっており、http://192.168.11.6:8080 で上記Jenkinsサービスにアクセスできるものとします。
さてさて!ここからjenkins-masterからjenkins-slaveをSSH経由でノード追加してみましょう。
Jenkinsの管理 → ノードの管理 → 新規ノード作成 で下記のようなノードを追加します。(ポートの指定忘れないように!!)
今回は認証情報にてシステムローカルの~/.ssh(Dockerfileにて追加済み)からSSH認証を読むようにしています。本当はSSH秘密鍵はどっかに別だしのほうがいいですね。それかユーザ名、パスワード認証でもいいでしょう。
そしてマスターのビルド機能は不要になったので、特定ジョブ専用にします。
上記例だと同時実行数も0にしていますが、config.xmlをジョブ経由でバックアップしたい場合など、マスターにしかできないジョブが発生すると考える場合は、わざわざ同時実行数を0にしなくていいと思います。
とりあえずできあがりました。
なんか実行してみましょう。
ちゃんとスレーブ上で実行できましたね!
スレーブ量産してみた
せっかくのDockerなんで、スレーブをいっぱい作ってみました。
root@ubuntuserver:~/jenkins-docker-example/slave# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c985b3b13e37 knjname/jenkins-slave:latest /bin/sh -c '/usr/sbi 2 seconds ago Up 2 seconds 0.0.0.0:20004->22/tcp jenkins-slave-20004 3b051e7e66ae knjname/jenkins-slave:latest /bin/sh -c '/usr/sbi 5 seconds ago Up 5 seconds 0.0.0.0:20003->22/tcp jenkins-slave-20003 7dd880653474 knjname/jenkins-slave:latest /bin/sh -c '/usr/sbi 7 seconds ago Up 7 seconds 0.0.0.0:20002->22/tcp jenkins-slave-20002 7bf196ba719f knjname/jenkins-slave:latest /bin/sh -c '/usr/sbi 11 seconds ago Up 10 seconds 0.0.0.0:20001->22/tcp jenkins-slave-20001 05ebb98e8ecc knjname/jenkins-slave:latest /bin/sh -c '/usr/sbi 19 minutes ago Up 19 minutes 0.0.0.0:10022->22/tcp jenkins-slave 03cf6daec38f knjname/jenkins-master:latest /bin/sh -c 'bash /je 49 minutes ago Up 49 minutes 0.0.0.0:8080->8080/tcp, 0.0.0.0:10080->10080/tcp jenkins-master
ただ、同じホストでたくさん同じスレーブを動しても全く旨味がないので、実際Dockerでスレーブ量産する時は、自分でスレーブのイメージをプライベートリポジトリとかに登録しておき、他マシンのDockerからそれをロードしてスレーブを起動させましょう。
Windowsと繋いでみた
JNLPポートの確認と設定をして…
Windowsスレーブもたててみましょう!
普通にいけますね。
やったほうがいいかもしれないこと
- 今回はDockerでJenkinsを立ち上げるまでの話なので、この後、当然素のJenkinsでも必要になってくる、JENKINS_HOMEのバックアップとか、設定XMLのバックアップとか、追加プラグインのインストールとかが必要になってきます。
- スレーブはDockerのオプションでCPU優先度やメモリの制限かけちゃったほうが運用が安定すると思います。
- JNLPポートについて、トンネル接続オプション使えば別に内外のポートを合わせる必要もないので、内側のポートを特定番号に固定してもいいかもしれません。(今回やってて途中まで気付かなかった。。。)
- SSHの鍵はもうちょっと上手に管理しましょう。
- ってか、もっと楽なスレーブの作り方あるんじゃね。
まとめ
- Jenkinsはマスターとスレーブ分けよう。マスターは実務させない。スレーブに実務させる。
- Dockerは開発用サーバセットアップでも役に立つ。