dockerのjenkins/jenkins, jenkins/inbound-agentイメージを使ってjenkins環境を構築する

# 概要
以前はOS上に直接Jenkins環境を構築していましたが、
簡単にJenkinsのバージョンアップ、戻しができるような環境を構築します。
# 構成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3494179/a76cf6ad-d4b7-2006-c5b1-aa954703ff0a.png)


# 構成要素
|構成要素  |概要  |
|---|---|
|WSL2|Windows上でLinuxを動かせる、マイクロソフト提供のサブシステムです。|
|Jenkins(Dockerコンテナ)  |Jenkinsのコントローラーや設定ファイルなどが乗っている、いわゆるJenkins本体のコンテナです。以下イメージを使用しています。<br>https://hub.docker.com/r/jenkins/jenkins/ |
|/var/jenkins_home  |Jenkinsの設定ファイルやジョブ設定ファイルなどが保存されるディレクトリです。コンテナを起動し直したときにJenkinsのジョブや各種設定が消えないよう、本体はDockerのVolumeに置いています。  |
|Docker volume|Dockerがホスト側に作成してくれる保存領域。※使用するときはボリューム名を指定すればよいので場所を意識する必要はないが、一応書いておくと/var/lib/docker/volumesに作成される|
|inbound-agentコンテナ|Jenkinsのジョブを実行するAgent（昔はスレーブと呼ばれていました）を含むコンテナです。なぜこれを立てるかというと、ジェンキンスコントローラーがあるマシンでジョブを実行した場合、実行シェルでコントローラーやJenkinsの設定に対して処理を実行できてしまうためです。※詳細は以下を参照ください。<br>https://www.jenkins.io/doc/book/security/controller-isolation/|
|/usr/local/jenkins_scripts|ジョブで実行するシェルを格納するディレクトリです。|

# Jenkinsのバージョンアップするときは
Jenkinsコンテナを停止し、jenkins/jenkinsイメージのバージョンを上げて新しくコンテナを建て直します。
設定ファイルや実行シェルはdocker volume上にあるため、コンテナを建て直してもジョブが消えたりすることはありません。
もしバージョンアップしてうまく動かない場合はすぐに以前のバージョンのコンテナを起動できるので、Jenkins環境が動かない時間を短くすることができます。

# 環境
- Windows 11 バージョン22H2 (OS ビルド 22621.1848)
- WSL2 (Ubuntu 20.04 LTS (GNU/Linux 5.10.16.3-microsoft-standard-WSL2 x86_64))
- Docker 23.0.6

# 前提条件
- WSL2をインストール済み
https://learn.microsoft.com/ja-jp/windows/wsl/install
- WSL2上にDockerをインストール済み
https://docs.docker.com/engine/install/ubuntu/

※今回は上記の環境で試していますが、Dockerが入った環境なら多分なんでも大丈夫だと思います。（動作確認はしておりません。）

# 構築手順
Docker composeで同時にコンテナを立ち上げたいところですが、
Agentを立ち上げる際にJenkinsコンテナで確認した内容を指定する必要があるので
先にJenkinsコンテナだけ立ち上げます。

### 1. volumeを作成
``` 
docker volume create -d local jenkins-data
docker volume create -d local jenkins-scripts
```
### 2. ネットワークを作成
```
docker network create jenkins
```
### 3. Jenkinsコンテナを起動
``` 
docker run \
  --name jenkins-2.401 \
  --restart=on-failure \
  --detach \
  --network jenkins \
  --publish 8080:8080 \
  --publish 50000:50000 \
  --mount type=volume,src=jenkins-data,dst=/var/jenkins_home \
  jenkins/jenkins:2.401.2-lts
  
```
### 4. 以下にアクセスし、ダッシュボード表示のところまで設定を進める
http://localhost:8080
※インストールするプラグイン指定の際、instance-identity pluginを対象に含めてください
※ほかの初期設定についてはネット上にたくさん情報あると思うのでそちらを参考にお願いします
※initialAdminPasswordは以下で確認できます
``` 
docker exec jenkins-2.401 cat /var/jenkins_home/secrets/initialAdminPassword
```

### 5. nodeを作成
1. ダッシュボード > Jenkinsの管理 > Nodes and Cloudsをクリック
2. 右上のNew Nodeをクリック
3. 適当なノード名を入れ、Permanent Agentにチェックを入れCreateをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3494179/bc8656aa-b10f-611e-976e-666c664bdd4a.png)
4. 以下を設定し、保存を押下
ノード名：　適当なノード名
説明：　任意で入力
Number of executors：　Jenkins の同時ビルド数を設定（自分は5にしました）
リモートFSルート：　任意の場所を設定（自分は/var/jenkinsにしました）
ラベル：　任意のラベルを設定
起動方法：　Launch agent by connecting it to the controller
他はデフォルト設定

5. secretキーを確認
作成したnodeの概要を確認すると、Agentを立ち上げる際に必要なsecretキーが表示されるので控えておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3494179/75dc6960-2829-3292-e9b2-214cb4d74875.png)


### 6. Agentコンテナの起動
https://registry.hub.docker.com/r/jenkins/inbound-agent#!
※ここではお試しでdocker runでイメージをそのまま起動してますが、
ジョブの実行環境はいろいろインストールすると思うので
実際にはDockerfileを使ったほうが楽だと思います
```
docker run \
--restart=on-failure \
--detach \
--name inbound-agent \
-u root \
--network jenkins \
--mount type=volume,src=jenkins-scripts,dst=/usr/local/jenkins_scripts \
--init jenkins/inbound-agent \
-url http://jenkinsのipアドレス:8080 \
-workDir=5-4で指定したリモートFSルート \
bc32d6e3e7bf26ac7d5ccb80f06d20f6eae805ae14337102ba06c9023775aa78 4-4で作成したnode名
```
Agentがうまく起動できていれば、以下のように表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3494179/59f8b070-acc0-82c2-1179-c160a50d15d6.png)


#### エラーメモ
はじめはurlオプションでlocalhost:8080を指定しましたが、
これだとコンテナ起動時にconnection refusedではじかれてしまったのでIPアドレスを指定して起動しました
（localhost:50000にhttpのgetリクエストを送れば、Serverという項目で確認できます）
```
user01@DESKTOP-BDOERRQ:/etc$ curl 172.19.0.2:50000
Jenkins-Agent-Protocols: JNLP4-connect, Ping
Jenkins-Version: 2.401.2
Jenkins-Session: 671f7191
Client: 172.19.0.1
Server: 172.19.0.2
Remoting-Minimum-Version: 4.7
```
※エラー内容
```
user01@DESKTOP-BDOERRQ:/etc$ sudo docker run --network jenkins --init jenkins/inbound-agent -url http://localhost:8080 bc32d6e3e7bf26ac7d5ccb80f06d20f6eae805ae14337102ba06c9023775aa78 inbound-agent
Jul 03, 2023 9:33:46 AM hudson.remoting.jnlp.Main createEngine
INFO: Setting up agent: inbound-agent
Jul 03, 2023 9:33:46 AM hudson.remoting.Engine startEngine
INFO: Using Remoting version: 3131.vf2b_b_798b_ce99
Jul 03, 2023 9:33:46 AM hudson.remoting.Engine startEngine
WARNING: No Working Directory. Using the legacy JAR Cache location: /home/jenkins/.jenkins/cache/jars
Jul 03, 2023 9:33:46 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Locating server among [http://localhost:8080/]
Jul 03, 2023 9:33:46 AM hudson.remoting.jnlp.Main$CuiListener error
SEVERE: Failed to connect to http://localhost:8080/tcpSlaveAgentListener/: Connection refused (Connection refused)
java.io.IOException: Failed to connect to http://localhost:8080/tcpSlaveAgentListener/: Connection refused (Connection refused)
        at org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver.resolve(JnlpAgentEndpointResolver.java:216)
        at hudson.remoting.Engine.innerRun(Engine.java:760)
        at hudson.remoting.Engine.run(Engine.java:543)
Caused by: java.net.ConnectException: Connection refused (Connection refused)
        at java.base/java.net.PlainSocketImpl.socketConnect(Native Method)
        at java.base/java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
        at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
        at java.base/java.net.AbstractPlainSocketImpl.connect(Unknown Source)
        at java.base/java.net.Socket.connect(Unknown Source)
        at java.base/sun.net.NetworkClient.doConnect(Unknown Source)
        at java.base/sun.net.www.http.HttpClient.openServer(Unknown Source)
        at java.base/sun.net.www.http.HttpClient.openServer(Unknown Source)
        at java.base/sun.net.www.http.HttpClient.<init>(Unknown Source)
        at java.base/sun.net.www.http.HttpClient.New(Unknown Source)
        at java.base/sun.net.www.http.HttpClient.New(Unknown Source)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(Unknown Source)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(Unknown Source)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(Unknown Source)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(Unknown Source)
        at org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver.resolve(JnlpAgentEndpointResolver.java:213)
        ... 2 more
```
#### エラーメモ2
コマンドの中でworkDirを指定していますが、
コンテナ起動時にこれを作成できずにコンテナが異常終了してしまうため、
ルートユーザーを指定してコンテナを立ち上げました。
（-u rootをオプションに追加しました。）
※エラー内容
``` 
user01@DESKTOP-BDOERRQ:/etc$ sudo docker logs inbound-agent
Jul 03, 2023 10:27:24 AM hudson.remoting.jnlp.Main createEngine
INFO: Setting up agent: inbound-agent
Jul 03, 2023 10:27:24 AM hudson.remoting.Engine startEngine
INFO: Using Remoting version: 3131.vf2b_b_798b_ce99
Exception in thread "main" java.nio.file.AccessDeniedException: /var/jenkins
        at java.base/sun.nio.fs.UnixException.translateToIOException(Unknown Source)
        at java.base/sun.nio.fs.UnixException.rethrowAsIOException(Unknown Source)
        at java.base/sun.nio.fs.UnixException.rethrowAsIOException(Unknown Source)
        at java.base/sun.nio.fs.UnixFileSystemProvider.createDirectory(Unknown Source)
        at java.base/java.nio.file.Files.createDirectory(Unknown Source)
        at java.base/java.nio.file.Files.createAndCheckIsDirectory(Unknown Source)
        at java.base/java.nio.file.Files.createDirectories(Unknown Source)
        at org.jenkinsci.remoting.engine.WorkDirManager.initializeWorkDir(WorkDirManager.java:211)
        at hudson.remoting.Engine.startEngine(Engine.java:309)
        at hudson.remoting.Engine.startEngine(Engine.java:285)
        at hudson.remoting.jnlp.Main.main(Main.java:276)
        at hudson.remoting.jnlp.Main._main(Main.java:271)
        at hudson.remoting.jnlp.Main.main(Main.java:237)
```

### 7. マスターノードのジョブ実行を無効にする

Jenkinsの管理 > Nodes and Clouds > master > 設定 で、同時実行数を0にし保存を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3494179/dd294c3b-0139-5e74-8747-2f1804b39cc0.png)

### 8. ジョブの実行確認
適当なジョブを作って実行してみる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3494179/9e6b2831-a6c5-3a1e-714a-796fa35bd81e.png)
コンソール出力を見るとinbound-agentで実行できている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3494179/6ca726f1-a802-fde9-c68a-0422a782f85c.png)

### 9.適当な実行シェルでジョブを実行してみる
1. シェルを作成する
    ```
    docker exec -it inbound-agent bash
    echo 'echo "hello world"' > /usr/local/jenkins_scripts/test.sh
    chmod 755 /usr/local/jenkins_scripts/test.sh
    ```
2. 作成したシェルスクリプトを実行するようにジョブ設定を変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3494179/0600219c-9852-5b4a-ce43-ea1a542e7599.png)
3. 実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3494179/65571789-3921-5de6-df62-f02cfbaf9c38.png)

# 終わりに
ジョブで実行するシェルスクリプトもDocker volumeに突っ込みましたが、
編集したりgit管理したりすることを考えると
ホスト上のどっかのフォルダとbindしたほうが良いかもしれないです。