Jenkins 導入手順 2018年版

久しぶりにJenkinsでCI環境作ったところ、Jenkins2になったことで以前と比べてだいぶ導入方法が変わってたので、忘れないためのメモです。

# Jenkinsとは何か？ なぜJenkins ?

Jenkins は自動化を導入するソフトウェアで、CIやCDを実現するためのツールとして使われます。同じカテゴリのサービスに、Travis CIやCircle CIがあります。TravisがGitHubのみ、Circle CIがGitHubやBitbucket Cloudのみを対象としているのに対して、Jenkinsは対象の制限が無いのが強みです(オンプレでもOK)。

個人的には、コードを GitHub で管理してるなら、Jenkinsの学習コストや運用コストを考えると、Circle CIやTravis CIなどの CIサービスを使ったほうがいいと思います。
オンプレでBitBucketサーバを立てたりして、Circle CIが使えないときは Jenkins を使わざるを得ません。でも昔よりは運用コストが低くなってるのかなと思います。


# この記事で達成すること

* dockerを使ってJenkinsをインストール
* nginx を使って Port 80 -> 8080 にリバースプロキシーする
* Node.js のプロジェクトのPipelineを作成する。2分ごとにポーリングして監視するように設定


# 前提

* OS は Linux or Mac
* Jenkins からも dockerコンテナを起動して、dockerコンテナの中で処理させる


# dockerをインストールする

Jenkins公式が配布しているdockerイメージを使ってJenkinsを起動するのが、2018年現在だと一番簡単なセットアップ方法だと思いますので、この方法でセットアップします。

Docker store https://store.docker.com/search?type=edition&offering=community から  Docker Community Edition をインストールしてください
  

# Jenkinsの導入/起動

まず Jenkins のデータを格納するディレクトリを作成します。

```
mkdir $HOME/jenkins_home
```

jenkinsci/blueocean という公式が配布しているdockerイメージを使います。
以下のコマンドを実行すると、まだdockerイメージをダウンロードしてないときはダウンロードしてから、dockerコンテナを起動します。

```
sudo docker run \
   -u root \
   --rm \
   -d \
   -p 8080:8080 \
   -v $HOME/jenkins_home:/var/jenkins_home \
   -v /var/run/docker.sock:/var/run/docker.sock \
   jenkinsci/blueocean
```

* `-u root` : Jenkinsからdockerコンテナを起動するため、rootユーザーで実行します。
* `—-rm` : docker コンテナ終了時に、自動的にコンテナを削除します。Jenkinsの状態はすべて jenkins_home ディレクトリに格納されるため、コンテナ終了時に削除しても問題ないです。
* `-d` : バックグラウンド (detachモード)で実行します
* `-p 8080:8080` : ホストマシンのポートとコンテナのポートをマッピングします。先の数字がホストマシンのポートで、後ろの数字がコンテナのポートです。
* `-v $HOME/jenkins_home:/var/jenkins_home` : ホストマシンの $HOME/jenkins_home ディレクトリをコンテナの /var/jenkins_home ディレクトリにマップします。ここにJenkinsのデータが格納されます。
* `-v /var/run/docker.sock:/var/run/docker.sock` : docker.sock をコンテナにマップすることで、コンテナの中から新たにdockerコンテナを起動できるようになります。



# nginxでリバースプロキシする

(手元の環境で動かすだけならこの手順は不要です。)
80番ポートで受けたリクエストを8080番ポートに転送します。

以下のような転送設定でリクエストを転送できます。



```/etc/nginx/conf.d/default.conf
upstream jenkins {
  server 127.0.0.1:8080 fail_timeout=0;
}

server {
  listen       80;
  server_name  my-ci.com; # 実際のドメインを指定してください。手元で試すだけなら localhost で良いです。

  location / {
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;
    proxy_pass              http://jenkins;
  }
}
```

上記は80番ポートでアクセスを受け付ける時の設定例です。SSLを使用したときの設定例はこちらにあります。
https://wiki.jenkins.io/display/JENKINS/Jenkins+behind+an+NGinX+reverse+proxy



## Post-installation setup wizard

JenkinsをセットアップしたURLにブラウザでアクセスします。
(localhostにセットアップしているとして、 リバースプロキシを設定したならhttp://localhost/ 、 設定してないなら http://localhost:8080/ )

<img width="630" alt="Starting_Jenkins.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/3adbaae0-2e40-29d4-b125-c2fbea022326.png">


準備ができるとAdministrator passwordを求められます。

<img width="924" alt="Sign_in__Jenkins_-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/860f4b2b-cddd-9ee4-a9af-6751ab7e8f22.png">


画面に書かれてるように、initialAdminPassword というファイルにパスワードが記載されてます。

`cat $HOME/jenkins_home/secrets/initialAdminPassword`

コマンドを実行することでパスワードを取得できます。
これをブラウザのAdministrator password入力欄に貼り付けて、Continueをクリックします。

次にプラグインをインストールする画面が表示されます。

<img width="927" alt="SetupWizard__Jenkins_.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/7c2de4f7-b097-4125-3f26-0743a0177888.png">


こだわりがなければ 「Install suggested plugins」 を選んで、おすすめプラグインをインストールします。
プラグインがダウンロード/インストールされます。これには少し時間がかかります。

次に

* 初期ユーザの情報を登録します。ユーザ名、パスワードなどのフィールドを入力して保存します。
* Jenkins URLの設定をします。実際に利用するURLを設定してください。手元で試すだけなら `http://localhost:8080/` などで良いです。


これでJenkinsのインストールは完了です。


# Node.js のプロジェクトのPipelineを作成する

現在のJenkinsは、Pipelineという仕組みを使って処理内容を定義します。
Pipeline とは continuous delivery (CD) pipeline のJenkinsにおける表現です。CD pipelineは継続的デリバリーを実現するためにいくつかの処理を行いますが、Pipelineはそれらの処理を可視化したり、コードで定義する機能を提供します。

Pipelineの定義はJenkinsfileという設定ファイルで行います。JenkinsfileをSCMのリポジトリに含めておくことで、Jenkinsはリポジトリに含まれるJenkinsfileを見つけてPipelineを作成します。
ですので、Jenkinsにどういう処理をさせるかはJenkinsfileを書いておけば良く、画面から処理内容を設定する必要はなくなりました。JenkinsもTravis CIやCircle CIと同じようにファイルで処理を設定できるようになったということですね。

Jenkinsfileを含んだ、Node.jsのサンプルプロジェクトを用意しました。
https://github.com/kmdsbng/jenkinsfile_sample

このプロジェクトを監視するPipelineを作っていきます。

Open Blue Oceanリンクを選びます。

<img width="377" alt="ダッシュボード__Jenkins_.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/e7630f04-0d27-ae38-9951-2b7c55a7bce7.png">

Create a new Pipeline ボタンを選びます。


<img width="1032" alt="Jenkins_と_1__zsh_と_ondai_memo_20180927_txt_______-_VIM_と_すべてのノート.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/477a7d47-06f7-19f7-225c-b1d8bcd08097.png">

<img width="1016" alt="Jenkins.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/2c6b306d-49f6-fec4-70d5-8ec650db5422.png">


Gitを選びます。(たぶんGitHubを選んでもいける。その時はユーザ、パスワードを入力しないといけなかったはず。)


gitリポジトリのURLを入力すると、SSH公開鍵が表示されます。これをGitHubに登録します。

<img width="647" alt="Banners_and_Alerts_と_Jenkins.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/1f9f0f27-323d-ef76-1481-5cc7a3cc74bc.png">



<img width="672" alt="Jenkins-3.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/181091d1-f6f5-fbcf-e952-7bac1fc8c2ce.png">


これで、リポジトリに含まれたJenkinsfileに従って監視が開始されます。

<img width="1025" alt="Jenkins___jenkinsfile_sample.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/d172a374-5b00-f851-6117-2b08a86bb7b8.png">


Jenkinsfileの内容を見てみましょう。

https://github.com/kmdsbng/jenkinsfile_sample/blob/master/Jenkinsfile

```
pipeline {
  agent {
    docker {
      image 'node:8.12-alpine'
    }
  }

  triggers { pollSCM('H/2 * * * *') }

  stages {
    stage('Npm Build') {
      steps {
        sh 'npm install && npm run build'
      }
    }
  }

  post {
    success {
        echo 'I succeeeded!'
    }
    unstable {
        echo 'I am unstable :/'
    }
    failure {
        echo 'I failed :('
    }
  }
}
```

このJenkinsfileが設定していること。
* 2分毎にリポジトリの変更を監視
* node:8.12-alpine イメージを使って dockerを起動しそこで処理を行う。
* npmでビルドする

ファイルの記述を見れば、やってることがだいたいわかりますね。

次に、わざとビルドエラーを起こしてみましょう。コンパイルエラーが起こるようにプログラムを修正してからpushしてみます。




<img width="1028" alt="Jenkins___jenkinsfile_sample-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/53b7c6b0-fc84-62c2-9f2f-d4d30375292a.png">

<img width="1015" alt="jenkins___jenkinsfile_sample___master____3.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/18e6793d-d64c-4e1a-ffc4-ba78e648b28c.png">

<img width="1014" alt="jenkins___jenkinsfile_sample___master____3-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/ab7801df-48b9-3bea-ff75-c36f5260901a.png">


ビルド時にエラーが検知され、エラー内容のログを見ることができます。


<img width="1027" alt="Jenkins___jenkinsfile_sample-3.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/a7e0a2e1-240a-6b2e-39b5-b436c1328207.png">

プログラムを修正して push しなおすと、エラーがなくなったことを検知しました。


<img width="1304" alt="全画面_2018_10_13_15_29.png" src="https://qiita-image-store.s3.amazonaws.com/0/529/7a61aefa-6680-43f9-7602-3ea3f9e63147.png">

Jenkinsの設定の不備でビルドが失敗したときなどは、Branches画面から再度CIを実行することができます。












# Jenkinsの終了

`sudo docker ps`

コマンドで、Jenkinsの Container ID を確認して、

`sudo docker stop <Container ID>`

コマンドを実行します。


# 最新のJenkinsに更新

最新のJenkinsに更新するには、`sudo docker pull jenkinsci/blueocean` して、Jenkinsを再起動すればOK。


# まとめ

Jenkinsfile を使って設定することで、これまでの Jenkins のように画面から設定するよりは、設定の手間が下がったと感じます。ただ、Jenkinsfile もそんなに明確に書けるというわけではなく、情報が少ないし、概念もわりと複雑な感じで、別の学習コストはかかるなあという印象です。

Jenkinsの運用方針として、あまりJenkinsのプラグインを使用せず、できるだけ Jenkinsfile で設定できることで目的を達成したほうが良いかなと考えてます。
リポジトリさえ指定すればCIの設定が完了する、という構成にしておいたほうが、Jenkinsサーバを立てるときの運用コストが減りそうなので。


# 参考資料

Jenkinsfileの書き方については、Jenkins自体に有益なドキュメントが格納されています。

### Global Variable Reference

JENKINS_URL/pipeline-syntax/globals

ブランチ名などの Jenkinsfileで利用できるグローバル変数の説明です。

### Directive Generator

JENKINS_URL/directive-generator/

Jenkinsfileのディレクティブ文字列のジェネレータです。

### Steps Reference

JENKINS_URL/pipeline-syntax/html     

Jenkinsfileの書式の資料です。

### Installing Jenkins

https://jenkins.io/doc/book/installing/

Jenkinsのインストール方法を説明しています。この記事で紹介した docker を使ったインストール方法に加えて、パッケージをダウンロードしてインストールする手順なども説明しています。