Jenkins2をコード化しよう - その1:Ansibleでプロビジョニングする

Jenkins2をコード化しよう - その1:Ansibleでプロビジョニングする
目次

今回は、CIサーバとして広く使われているJenkinsをAnsibleでコード化するための手順を紹介します。

背景:Jenkinsサーバがカオスだった

配属された現場で使用されていたJenkinsサーバが誰もメンテンナンスできていない状態でしたので、「完璧にコード化案件ですねこれは」と頑張ってみました。

困ったことに、複数存在するJenkinsサーバのそれぞれにインストールされているJenkins PluginやOSコマンドが異なっていましたし、 Jenkins自体もHomebrew経由でインストールしているものもあれば、直接Jarファイルをダウンロードしたものもあり、「これは再現性がないぞ」と思いました。 更に、Jenkinsたちはオンプレのサーバに立っていて、バックアップも取られていません。

これでは、サーバが死んだら開発が止まってしまいますよね。 このような状況はきっと他にもあるのだろうと思ったので、Tipsをまとめてみました。

Ansibleを使ってJenkinsコード化する

以降では、JenkinsをAnsible化するにあたってのポイントを aisible-playbookyml 形式で順に説明します。

環境情報

  • Ansible
    • 2.3.1
  • プロビジョニング対象のOS
    • AWS EC2の AmazonLinux2 (CentOS7がベースになっています)
  • インストールするJenkins
    • 2.164.2

Jenkinsのインストール

Jenkinsの起動に必要なJava8をインストールした後、Jenkinsのyumリポジトリを追加します。 インストールには公開鍵が必要なので、取得を行った後にJenkinsを yum でインストールします。

 1- name: yumリポジトリを有効にします
 2  shell: amazon-linux-extras enable corretto8
 3- name: OpenJDK 8(corretto) をインストールします
 4  yum:
 5    name: java-1.8.0-amazon-corretto-devel
 6    state: present
 7- name: jenkinsのyumリポジトリを追加します
 8  get_url:
 9    url: http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo
10    dest: /etc/yum.repos.d/jenkins.repo
11- name: 公開鍵をインストールします
12  rpm_key:
13    key: http://pkg.jenkins-ci.org/redhat-stable/jenkins-ci.org.key
14- name: jenkinsをインストールします
15  yum:
16    name: "jenkins-2.164.2-1.1"
17    state: present

Jenkinsの起動設定の変更

Jenkinsの起動引数を変更したい場合には、いくつか方法はありますが、今回は /etc/sysconfig/jenkins ファイルを差し替えます。 ここでは ${JENKINS_HOME} の場所や、待受ポート、SSL証明書へのパス、Jenkinsの起動引数の追加等が行えます。

1- name: jenkinsの起動引数を変更します
2  copy:
3    src: "../files/etc/sysconfig/jenkins"
4    dest: /etc/sysconfig/jenkins
5    owner: root
6    group: root
7    mode: 0600

セッションタイムアウトを伸ばしたければ、 JENKINS_ARGS="--sessionTimeout=120" といった要領です。

Jenkinsを起動

Jenkinsを起動します。AmazonLinux2(CentOS7)から systemd になっているので、デーモンに登録しておきます。

1- name: jenkinsを起動します
2  systemd:
3    name: jenkins
4    state: started
5    enabled: yes

Jenkinsが起動すると初期化処理に時間がかかるので、ログイン画面が表示されるまで待ちましょう。

 1- name: Jenkinsが起動するまでwaitします
 2  uri:
 3    url: http://localhost:8080/login
 4    status_code: 200
 5    timeout: 10
 6  register: jenkins_service_status
 7  retries: 15
 8  delay: 20
 9  until: >
10     'status' in jenkins_service_status and
11     jenkins_service_status['status'] == 200     

Jenkins ユーザに sudo権限を付与

Jenkinsをインストールすると、Jenkinsユーザが誕生します。 Jenkinsユーザに何らかの理由で sudo させたいのであれば、以下のように sudoers.d 配下のファイルに追記しておきます。

 1- name: jenkinsユーザにsudo権限を付与します
 2  lineinfile:
 3    dest: "/etc/sudoers.d/users"
 4    owner: root
 5    group: root
 6    mode: 0440
 7    state: present
 8    create: yes
 9    line: "jenkins ALL=(ALL) NOPASSWD:ALL"
10    validate: 'visudo -cf %s'

セットアップウィザードを無効にする

Jenkinsを初回起動した時にセットアップウィザードが表示されます。

setupwizard_jenkins

これを無効化するために、 ${JENKINS_HOME}/jenkins.install.InstallUtil.lastExecVersion ファイルを作成してしまいましょう。 ファイルの中身にはJenkinsのバージョン番号(今回は 2.164.2 )が記載されていればOKです。

1- name: セットアップウィザードを無効化します
2  copy:
3    content: "2.164.2"
4    dest: /var/lib/jenkins/jenkins.install.InstallUtil.lastExecVersion
5    owner: jenkins
6    group: jenkins
7    mode: 0644

アップグレードウィザードを無効にする

同様にしてアップグレードウィザードも無効にします。アップグレードウィザードは ${JENKINS_HOME}/jenkins.install.UpgradeWizard.state です。

1- name: アップグレードウィザードを無効化します
2  copy:
3    content: "2.164.2"
4    dest: /var/lib/jenkins/jenkins.install.UpgradeWizard.state
5    owner: jenkins
6    group: jenkins
7    mode: 0644

Jenkinsのセキュリティ設定を一時的にオフにする

以降の処理でJenkins2のセキュリティ設定がプロビジョニングの邪魔をすることがあるので、一時点にオフにします。 ${JENKINS_HOME}/config.xml を変更します。

1- name: セキュリティ無効化します
2  copy:
3    src: "../files/var/lib/jenkins/config.xml.unsecure"
4    dest: /var/lib/jenkins/config.xml
5    owner: jenkins
6    group: jenkins
7    mode: 0644

ここは xml を置き換える という少し泥臭い方法を取ります。

 1<?xml version='1.1' encoding='UTF-8'?>
 2<hudson>
 3  <disabledAdministrativeMonitors/>
 4  <version>2.164.2</version>
 5  <installStateName>RUNNING</installStateName>
 6  <numExecutors>60</numExecutors>
 7  <mode>NORMAL</mode>
 8  <!-- CLI実行のためにセキュリティ設定をOFFにする ココから -->
 9  <useSecurity>false</useSecurity>
10  <authorizationStrategy class="hudson.security.AuthorizationStrategy$Unsecured"/>
11  <securityRealm class="hudson.security.SecurityRealm$None"/>
12  <!-- ココまで -->
13  <disableRememberMe>false</disableRememberMe>
14  <projectNamingStrategy class="jenkins.model.ProjectNamingStrategy$DefaultProjectNamingStrategy"/>
15  <workspaceDir>${JENKINS_HOME}/workspace/${ITEM_FULL_NAME}</workspaceDir>
16  <buildsDir>${ITEM_ROOTDIR}/builds</buildsDir>
17  <jdks/>
18  <viewsTabBar class="hudson.views.DefaultViewsTabBar"/>
19  <myViewsTabBar class="hudson.views.DefaultMyViewsTabBar"/>
20  <clouds/>
21  <scmCheckoutRetryCount>0</scmCheckoutRetryCount>
22  <views>
23    <hudson.model.AllView>
24      <owner class="hudson" reference="../../.."/>
25      <name>all</name>
26      <filterExecutors>false</filterExecutors>
27      <filterQueue>false</filterQueue>
28      <properties class="hudson.model.View$PropertyList"/>
29    </hudson.model.AllView>
30  </views>
31  <primaryView>all</primaryView>
32  <slaveAgentPort>21000</slaveAgentPort>
33  <label></label>
34  <crumbIssuer class="hudson.security.csrf.DefaultCrumbIssuer">
35    <excludeClientIPFromCrumb>false</excludeClientIPFromCrumb>
36  </crumbIssuer>
37  <nodeProperties/>
38  <globalNodeProperties/>

セキュリティ設定を変更したら、再起動で設定ファイルの再読込をします。

1- name: jenkinsを再起動します
2  systemd:
3    name: jenkins
4    state: restarted
5    enabled: yes

Jenkins Pluginをインストールする

Jenkinsのプラグインをインストールするには Jenkins CLIを使うのがオススメです。 JenkinsプラグインのIDはAnsibleの vars/ 配下のファイルに書き出すなどしてループでインストールしてしまうのが良いでしょう。

セットアップウィザードをスキップしてしまっているので、セットアップウィザードでデフォルトでインストールするプラグインは このタイミングでインストールすると良いでしょう。

プラグインのインストール終了後のJenkins再起動も忘れずに行います。

 1- name: jenkins-cliをインストールします
 2  shell: wget http://localhost:8080/jnlpJars/jenkins-cli.jar -P /usr/lib/jenkins/
 3- name: jenkins pluginをjenkins-cli経由でインストールします
 4  shell: java -jar /usr/lib/jenkins/jenkins-cli.jar -s http://localhost:8080 install-plugin {{ item.plugin_name }}
 5  with_items: "{{ jenkins_plugins }}"
 6- name: jenkinsを再起動します
 7  systemd:
 8    name: jenkins
 9    state: restarted
10    enabled: yes

Jenkinsの起動スクリプトを配置する

Jenkinsは起動したタイミングで、${JENKINS_HOME}/init.groovy.d/ 配下にあるGroovyスクリプトを実行させることができます。 Groovyスクリプトを使うことで、 ${JENKINS_HOME} 配下にxmlをひたすらコピーするソリューションを防ぐことができます。

${JENKINS_HOME}/init.groovy.d ディレクトリはデフォルトで存在しないので、ディレクトリを作成しておきます。

1- name: jenkins起動時のHOOK Groovy Scriptディレクトリを作成します
2  file:
3    dest: "/var/lib/jenkins/init.groovy.d/"
4    state: directory
5    owner: jenkins
6    group: jenkins
7    mode: 0700

Groovyスクリプトの書き方については、「Jenkins2をコード化しよう - その2:Jenkinsの起動時にプログラム(Groovy Hook Script)を動かす」 を参考にしてください。

xmlファイルを配置する

JenkinsやJenkinsプラグインの設定は xml ファイルで管理されています。

必要に応じて ${JENKINS_HOME} 配下に置きましょう。

1- name: Slackの設定をコピーします
2  copy:
3    src: "../files/var/lib/jenkins/jenkins.plugins.slack.SlackNotifier.xml"
4    dest: /var/lib/jenkins/jenkins.plugins.slack.SlackNotifier.xml
5    force: yes
6    owner: jenkins
7    group: jenkins
8    mode: 0755

Jenkinsのセキュリティ設定をオンに戻す

プラグインをインストールするためにオフにしたセキュリティ設定を元にもどします。

1- name: セキュリティ有効化します
2  copy:
3    src: "../files/var/lib/jenkins/config.xml.secure"
4    dest: /var/lib/jenkins/config.xml
5    owner: jenkins
6    group: jenkins
7    mode: 0644

Jenkinsインストール時の ${JENKINS_HOME}/config.xml の内容に戻してあげましょう。 サンプルは以下のような感じです。

 1<?xml version='1.1' encoding='UTF-8'?>
 2<hudson>
 3  <disabledAdministrativeMonitors/>
 4  <version>2.164.2</version>
 5  <installStateName>RUNNING</installStateName>
 6  <numExecutors>60</numExecutors>
 7  <mode>NORMAL</mode>
 8  <useSecurity>false</useSecurity>
 9  <authorizationStrategy class="hudson.security.FullControlOnceLoggedInAuthorizationStrategy">
10    <denyAnonymousReadAccess>true</denyAnonymousReadAccess>
11  </authorizationStrategy>
12  <securityRealm class="hudson.security.HudsonPrivateSecurityRealm">
13    <disableSignup>true</disableSignup>
14    <enableCaptcha>false</enableCaptcha>
15  </securityRealm>
16  <disableRememberMe>false</disableRememberMe>
17  <projectNamingStrategy class="jenkins.model.ProjectNamingStrategy$DefaultProjectNamingStrategy"/>
18  <workspaceDir>${JENKINS_HOME}/workspace/${ITEM_FULL_NAME}</workspaceDir>
19  <buildsDir>${ITEM_ROOTDIR}/builds</buildsDir>
20  <jdks/>
21  <viewsTabBar class="hudson.views.DefaultViewsTabBar"/>
22  <myViewsTabBar class="hudson.views.DefaultMyViewsTabBar"/>
23  <clouds/>
24  <scmCheckoutRetryCount>0</scmCheckoutRetryCount>
25  <views>
26    <hudson.model.AllView>
27      <owner class="hudson" reference="../../.."/>
28      <name>all</name>
29      <filterExecutors>false</filterExecutors>
30      <filterQueue>false</filterQueue>
31      <properties class="hudson.model.View$PropertyList"/>
32    </hudson.model.AllView>
33  </views>
34  <primaryView>all</primaryView>
35  <slaveAgentPort>21000</slaveAgentPort>
36  <label></label>
37  <crumbIssuer class="hudson.security.csrf.DefaultCrumbIssuer">
38    <excludeClientIPFromCrumb>false</excludeClientIPFromCrumb>
39  </crumbIssuer>
40  <nodeProperties/>
41  <globalNodeProperties/>

Jenkinsの再起動

最後にJenkinsを再起動して終了です。

1- name: jenkinsを再起動します
2  systemd:
3    name: jenkins
4    state: restarted
5    enabled: yes

注意点

プロビジョニングに時間がかかる

Jenkinsのマシンイメージのコード化にあたってプロビジョニングの所要時間を計測したところ、 インスタンスタイプが c5.xlarge で15分ほどかかりました。特に、プラグインインストールの所で時間がかかっているようでした。

xml編集という泥臭い作業は残る

コード化するにあたって、xmlを編集する という苦行は避けたかったのですが、全回避はできなかったです。 Jenkinsの設定をymlでかけるようにするための Configuration as Code Plugin というものがありますが、各プラグインがConfiguration as Code Plugin に対応する必要があるため、対応していないプラグインを使いたい場合には厄介です。