Jenkinsにてdockerfileを用いてagentを起動する

# 目的
Docker上のJenkinsでCIパイプラインを構築する方法について記述します。
JenkinsのUI上での操作を減らし、「"極力"コード化したい」というところに重点を置きました。
お試しで構築した環境なので、実用的でない部分もあるかと思いますが、ご容赦頂ければ幸いです。

:::note warn
備忘録的な側面が強い記事になります！
:::

# イメージ
![container_demo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2787945/f536d526-5c79-01d0-9774-1e8887cdf979.png)

# 試したこと

### A. Jenkinsを立ち上げる

#### A-1. docker-composeを記述する
起動したDockerコンテナからDocker自体にアクセス（Docker outside of Docker）するために、いくつか変数を渡しています。
また、Jenkinsのmasterとagentの通信を可能にするために、"default"という名前でnetworkを設定します。
```yaml
version: "3"
services:
  jenkins:
    container_name: jenkins
    restart: always
    image: jenkins/jenkins:latest
    networks:
      - default
    user: "${UID}:${GID}"
    group_add:
      - ${DOCKER_GID}
    ports:
      - 8080:8080
      - 50000:50000
    tty: true
    volumes:
      - ${DOCKER_PATH}:/usr/bin/docker
      - /var/run/docker.sock:/var/run/docker.sock
      - ./jenkins/home:/var/jenkins_home
```

#### A-2. コマンドを実行し、コンテナを起動する
yamlファイル内で変数を参照するために、"docker-compose build" をする前にexportする必要があります。
```bash
export DOCKER_GID=`awk -F: '$1 == "docker" {print $3}' /etc/group`
export DOCKER_PATH=`which docker`
export UID=`id -u`
export GID=`id -g`
docker-compose build
docker-compose up -d
```

### B. Pipelineを構築する

#### B-1. DockerfileとJenkinsfileを新規作成する
この2点はGithubで管理します。
ディレクトリ構成は下記のような形になります。
```
test-pipeline/
    ├ .gitignore
    ├ Dockerfile
    ├ Jenkinsfile
    ├ requirements.txt
    ├ tests/
        ├ pytest.ini
        ├ test_sample.py
```

#### B-2. Dockerfileでagentの設計図を書く
今回は、Build後にPythonのテストが走ることを想定して、Python3の実行環境を構築します。
```dockerfile
FROM python:3.9
USER root

WORKDIR /root/src
COPY requirements.txt /root/src

RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
```

#### B-3. JenkinsfileでPipelineを記述する
"Build"→"Test"→"Deploy"という流れを表現しています。
```groovy
pipeline {
    // B-2で記述したDockerfileを用いて、コンテナとしてagentを起動する
    agent { 
        dockerfile {
            filename 'Dockerfile'
            args '--network=default'
        }
    }
    stages {
        stage('build'){
            steps{
                echo "build"
            }
        // ChromeとEdgeで並行してE2Eテストが実行される
        stage('test') {
            parallel{
                stage("Chrome"){
                    steps {
                        sh '''
                        cd tests/
                        export SELENIUM_BROWSER_ENV=Chrome
                        pytest test_sample.py
                        '''
                    }
                }
                stage("Edge"){
                    steps {
                        sh '''
                        cd tests/
                        export SELENIUM_BROWSER_ENV=Edge
                        pytest test_sample.py
                        '''
                    }
                }
            }
        }
        stage('Deploy'){
            steps{
                echo "deploy"
            }
        }
    }
}
```
:::note info
それぞれのStageにおいて、実行するagentを分けることもできます！
（そっちの方が実用的かも、、）
:::

#### B-4. JenkinsでJobを作成する
"Pipeline script from SCM"を選択し、リポジトリURLはB-1で作成したものを使用します。
![pipe_demo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2787945/4cb55414-5a37-cdc5-5300-e07cb87f144b.png)

:::note info
あとは作成したPipelineJobにトリガーを設定すれば、CIパイプラインが完成です！
:::

# 所感
コード化できるものはコード化することで、CIでのJob管理諸々が職人化してしまうことを回避できる可能性を感じました。
IaC（Infrastracture as Code）やPaC（Pipeline as Code）という言葉もあるように、コードで管理することで再現性が高く素早い環境構築ができるようにしていきたいです。
こういった設計図のひな形のようなものを資産として持っておくと、チームとしてちょっとした強みになっていくと思います。

# 参考
https://qiita.com/hiroyuki_onodera/items/c6c6e6163d61ddf4dd5c
https://qiita.com/dublog/items/38831adf42e3cfeed66a