GitLab Runner Kubernetes Executor

# はじめに
GitLabはGitLab Runnerを組み合わせることでCI/CDを実現できます。
このGitLab CIでは`.gitlab_ci.yml`でジョブを定義しますが、GitLab Runner自体がジョブを実行するのではなくGitLab Runnerで設定された**Executor**が実行します。

Executorは例えば次のものがあります。

* Shell Executor
* Docker Executor
* Kubernetes Executor

今回は、Kubernetes上にデプロイしたPodがジョブを実行する**Kubernetes Executor**に焦点を絞って説明します。

# Kubernetes Executorの構成
Kubernetes ExecutorはGitLab CIでジョブ実行時にKubernetes上にPodとして立ち上がります。

GitLab CI Job Podには次の3種類のコンテナが存在します。

* Helperコンテナ(1個)
* Buildコンテナ(1個)
* Serviceコンテナ(0個以上)

それぞれ説明していきます。

## Helperコンテナ
主にGit操作を行うコンテナです。
プロジェクトのCI/CD設定で`git clone`と`git fetch`を選択できますが、実際にこれを行うのがHelperコンテナです。

コンテナイメージはGitLab Runnerに設定します。
Gitの設定を変更したい場合は公式のイメージをベースイメージとして独自の物を作成します。

## Buildコンテナ
Helperコンテナが取得したソースコードをもとに`.gitlab_ci.yml`の内容のジョブを実行するのがBuildコンテナです。
`.gitlab_ci.yml`で指定したイメージがBuildコンテナとなりますが、指定していない場合はGitLab Runnerに設定したイメージが使用されます。

## Serviceコンテナ
ジョブ実行時にBuildコンテナがアクセスできるコンテナがServiceコンテナです。
これは0個以上デプロイされます。すなわち`.gitlab_ci.yml`に定義しなければデプロイされることはありません。

Serviceコンテナには例えばMysql、Redis、dindなどが挙げられます。

# ジョブ実行までの流れ
順序は次のようになります。

1. GitLab RunnerがGitLabでジョブが発動したかを検知する。
2. GitLab RunnerがジョブPodを作成する。
3. ジョブPod内のHelperコンテナがGitLabから`git clone`もしくは`git fetch`する。
4. ジョブPod内のBuildコンテナが`.gitlab_ci.yml`に記述されたジョブを実行する。

図ではGitLab Runnerが同一クラスタにデプロイされているものとしています。
ポーリングの時間間隔はGitLab Runnerで設定できます。

<img width="959" alt="スクリーンショット 2019-12-20 22.51.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/d58a21b6-c440-6ab6-b7bd-feef8f8915e1.png">


# Kubernetes Executorの検証
実際にJob Podをデプロイして検証します。

## GitLab Runnerのデプロイ
Helmfileを使用します。
`releases[].values[].runners.image`に設定するのはデフォルトのBuildコンテナのイメージです。
`releases[].values[].runners.privileged`と`releases[].values[].runners.env`に設定している値はdindを使用するためのものです。

GitLabをKubernetes上にデプロイしている場合、`releases[].values[].gitlabUrl`にService名を指定しても疎通はできますがArtifactsアップロードに失敗するので注意しないといけません。

```yaml:helmfile.yaml
repositories:
- name: gitlab
  url: https://charts.gitlab.io/
releases:
- name: gitlab-runner
  namespace: dev
  chart: gitlab/gitlab-runner
  version: 0.11.0
  values:
  - image: gitlab/gitlab-runner:alpine-v12.5.0
    gitlabUrl: http://gitlab.example.com
    runnerRegistrationToken: "XXXXXXXXXX"
    rbac:
      create: true
      resources: ["pods", "pods/exec", "secrets"]
      verbs: ["get", "list", "watch", "create", "patch", "delete"]
      clusterWideAccess: false
    metrics:
      enabled: false
    runners:
      image: busybox:1.31.1
      privileged: true
      env:
        DOCKER_HOST: tcp://localhost:2375
```

下記コマンドでデプロイします。

```console
$ helmfile apply
```

## ジョブの実行
GitLab RunnerをGitLabに紐付けた後にプロジェクトに`.gitlab_ci.yml`を作成します。
Podの解析のために`sleep`させてます。

```yaml:.gitlab_ci.yml
image: docker:18.09.7

services:
  - docker:18.09.7-dind

build:
  stage: test
  script:
    - sleep 3600
```

ジョブを実行させます。

```console
Running with gitlab-runner 12.5.0 (577f813d)
  on gitlab-runner-gitlab-runner-5cdf6fd4f6-vqrzq 3xycVVoJ
Using Kubernetes namespace: dev
Using Kubernetes executor with image docker:18.09.7 ...
Waiting for pod dev/runner-3xycvvoj-project-1-concurrent-0ngfr2 to be running, status is Pending
Running on runner-3xycvvoj-project-1-concurrent-0ngfr2 via gitlab-runner-gitlab-runner-5cdf6fd4f6-vqrzq...
Fetching changes with git depth set to 50...
Initialized empty Git repository in /builds/root/test/.git/
Created fresh repository.
From http://gitlab.moguta.ml/root/test
 * [new ref]         refs/pipelines/8 -> refs/pipelines/8
 * [new branch]      master           -> origin/master
Checking out f493d24c as master...

Skipping Git submodules setup
$ sleep 3600
```

## Job Podの確認
Podを確認すると3つのコンテナが存在することが分かります。

```console
$ kubectl get pod -n dev runner-3xycvvoj-project-1-concurrent-0ngfr2
runner-3xycvvoj-project-1-concurrent-0ngfr2         3/3     Running   0          5m25s
```

さらに中身を見ていきます。なお、GitLabのドメインは`gitlab.example.com`に変換していて載せています。

```console
$ kubectl describe pod -n dev runner-3xycvvoj-project-1-concurrent-0ngfr2
...
Containers:
  build:
    Container ID:  docker://f46846ec84f6517bb63dc1f2fd466126130b3014c344690debee465f2016aadc
    Image:         docker:18.09.7
    Image ID:      docker-pullable://docker@sha256:310156c95007d6cca1417d0692786fe4da816b886a08bc7de97edf02cab4db31
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
      -c
      if [ -x /usr/local/bin/bash ]; then
        exec /usr/local/bin/bash 
      elif [ -x /usr/bin/bash ]; then
        exec /usr/bin/bash 
      elif [ -x /bin/bash ]; then
        exec /bin/bash 
      elif [ -x /usr/local/bin/sh ]; then
        exec /usr/local/bin/sh 
      elif [ -x /usr/bin/sh ]; then
        exec /usr/bin/sh 
      elif [ -x /bin/sh ]; then
        exec /bin/sh 
      elif [ -x /busybox/sh ]; then
        exec /busybox/sh 
      else
        echo shell not found
        exit 1
      fi
      
      
    State:          Running
      Started:      Sat, 21 Dec 2019 01:20:45 +0900
    Ready:          True
    Restart Count:  0
    Environment:
      FF_CMD_DISABLE_DELAYED_ERROR_LEVEL_EXPANSION:  false
      FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER:           false
      FF_USE_LEGACY_VOLUMES_MOUNTING_ORDER:          false
      CI_RUNNER_SHORT_TOKEN:                         3xycVVoJ
      CI_BUILDS_DIR:                                 /builds
      CI_PROJECT_DIR:                                /builds/root/test
      CI_CONCURRENT_ID:                              0
      CI_CONCURRENT_PROJECT_ID:                      0
      CI_SERVER:                                     yes
      CI_PIPELINE_ID:                                8
      CI_PIPELINE_URL:                               http://gitlab.example.com/root/test/pipelines/8
      CI_JOB_ID:                                     9
      CI_JOB_URL:                                    http://gitlab.example.com/root/test/-/jobs/9
      CI_BUILD_ID:                                   9
      CI_REGISTRY_USER:                              gitlab-ci-token
      CI:                                            true
      GITLAB_CI:                                     true
      GITLAB_FEATURES:                               
      CI_SERVER_HOST:                                gitlab.example.com
      CI_SERVER_NAME:                                GitLab
      CI_SERVER_VERSION:                             12.5.3
      CI_SERVER_VERSION_MAJOR:                       12
      CI_SERVER_VERSION_MINOR:                       5
      CI_SERVER_VERSION_PATCH:                       3
      CI_SERVER_REVISION:                            
      CI_JOB_NAME:                                   build
      CI_JOB_STAGE:                                  test
      CI_COMMIT_SHA:                                 f493d24c574d3deee3e0284f6f413cd20b9a7e83
      CI_COMMIT_SHORT_SHA:                           f493d24c
      CI_COMMIT_BEFORE_SHA:                          4bddec9c21af3e1fcedde2a81f62eb03d9afaa2b
      CI_COMMIT_REF_NAME:                            master
      CI_COMMIT_REF_SLUG:                            master
      CI_NODE_TOTAL:                                 1
      CI_DEFAULT_BRANCH:                             master
      CI_BUILD_REF:                                  f493d24c574d3deee3e0284f6f413cd20b9a7e83
      CI_BUILD_BEFORE_SHA:                           4bddec9c21af3e1fcedde2a81f62eb03d9afaa2b
      CI_BUILD_REF_NAME:                             master
      CI_BUILD_REF_SLUG:                             master
      CI_BUILD_NAME:                                 build
      CI_BUILD_STAGE:                                test
      CI_PROJECT_ID:                                 1
      CI_PROJECT_NAME:                               test
      CI_PROJECT_TITLE:                              test
      CI_PROJECT_PATH:                               root/test
      CI_PROJECT_PATH_SLUG:                          root-test
      CI_PROJECT_NAMESPACE:                          root
      CI_PROJECT_URL:                                http://gitlab.example.com/root/test
      CI_PROJECT_VISIBILITY:                         private
      CI_PROJECT_REPOSITORY_LANGUAGES:               
      CI_PAGES_DOMAIN:                               example.com
      CI_PAGES_URL:                                  http://root.example.com/test
      CI_REGISTRY:                                   registry.example.com
      CI_REGISTRY_IMAGE:                             registry.example.com/root/test
      CI_API_V4_URL:                                 http://gitlab.example.com/api/v4
      CI_PIPELINE_IID:                               8
      CI_CONFIG_PATH:                                .gitlab-ci.yml
      CI_PIPELINE_SOURCE:                            push
      CI_COMMIT_MESSAGE:                             Update .gitlab-ci.yml
      CI_COMMIT_TITLE:                               Update .gitlab-ci.yml
      CI_COMMIT_DESCRIPTION:                         
      CI_COMMIT_REF_PROTECTED:                       true
      CI_RUNNER_ID:                                  1
      CI_RUNNER_DESCRIPTION:                         gitlab-runner-gitlab-runner-5cdf6fd4f6-vqrzq
      CI_RUNNER_TAGS:                                
      GITLAB_USER_ID:                                1
      GITLAB_USER_EMAIL:                             admin@example.com
      GITLAB_USER_LOGIN:                             root
      GITLAB_USER_NAME:                              Administrator
      CI_DISPOSABLE_ENVIRONMENT:                     true
      CI_RUNNER_VERSION:                             12.5.0
      CI_RUNNER_REVISION:                            577f813d
      CI_RUNNER_EXECUTABLE_ARCH:                     linux/amd64
    Mounts:
      /builds from repo (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-8gjvw (ro)
  helper:
    Container ID:  docker://de720b5d7dc94b57b47f8581b36ccf9ad263449ffdd69707e3a9c08c8c845dbd
    Image:         gitlab/gitlab-runner-helper:x86_64-577f813d
    Image ID:      docker-pullable://gitlab/gitlab-runner-helper@sha256:bd932463587dbd419c6f37667f7c3d63d5595ca2c5e446d81267838698828036
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
      -c
      if [ -x /usr/local/bin/bash ]; then
        exec /usr/local/bin/bash 
      elif [ -x /usr/bin/bash ]; then
        exec /usr/bin/bash 
      elif [ -x /bin/bash ]; then
        exec /bin/bash 
      elif [ -x /usr/local/bin/sh ]; then
        exec /usr/local/bin/sh 
      elif [ -x /usr/bin/sh ]; then
        exec /usr/bin/sh 
      elif [ -x /bin/sh ]; then
        exec /bin/sh 
      elif [ -x /busybox/sh ]; then
        exec /busybox/sh 
      else
        echo shell not found
        exit 1
      fi
      
      
    State:          Running
      Started:      Sat, 21 Dec 2019 01:20:45 +0900
    Ready:          True
    Restart Count:  0
    Environment:
      FF_CMD_DISABLE_DELAYED_ERROR_LEVEL_EXPANSION:  false
      FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER:           false
      FF_USE_LEGACY_VOLUMES_MOUNTING_ORDER:          false
      CI_RUNNER_SHORT_TOKEN:                         3xycVVoJ
      CI_BUILDS_DIR:                                 /builds
      CI_PROJECT_DIR:                                /builds/root/test
      CI_CONCURRENT_ID:                              0
      CI_CONCURRENT_PROJECT_ID:                      0
      CI_SERVER:                                     yes
      CI_PIPELINE_ID:                                8
      CI_PIPELINE_URL:                               http://gitlab.example.com/root/test/pipelines/8
      CI_JOB_ID:                                     9
      CI_JOB_URL:                                    http://gitlab.example.com/root/test/-/jobs/9
      CI_BUILD_ID:                                   9
      CI_REGISTRY_USER:                              gitlab-ci-token
      CI:                                            true
      GITLAB_CI:                                     true
      GITLAB_FEATURES:                               
      CI_SERVER_HOST:                                gitlab.example.com
      CI_SERVER_NAME:                                GitLab
      CI_SERVER_VERSION:                             12.5.3
      CI_SERVER_VERSION_MAJOR:                       12
      CI_SERVER_VERSION_MINOR:                       5
      CI_SERVER_VERSION_PATCH:                       3
      CI_SERVER_REVISION:                            
      CI_JOB_NAME:                                   build
      CI_JOB_STAGE:                                  test
      CI_COMMIT_SHA:                                 f493d24c574d3deee3e0284f6f413cd20b9a7e83
      CI_COMMIT_SHORT_SHA:                           f493d24c
      CI_COMMIT_BEFORE_SHA:                          4bddec9c21af3e1fcedde2a81f62eb03d9afaa2b
      CI_COMMIT_REF_NAME:                            master
      CI_COMMIT_REF_SLUG:                            master
      CI_NODE_TOTAL:                                 1
      CI_DEFAULT_BRANCH:                             master
      CI_BUILD_REF:                                  f493d24c574d3deee3e0284f6f413cd20b9a7e83
      CI_BUILD_BEFORE_SHA:                           4bddec9c21af3e1fcedde2a81f62eb03d9afaa2b
      CI_BUILD_REF_NAME:                             master
      CI_BUILD_REF_SLUG:                             master
      CI_BUILD_NAME:                                 build
      CI_BUILD_STAGE:                                test
      CI_PROJECT_ID:                                 1
      CI_PROJECT_NAME:                               test
      CI_PROJECT_TITLE:                              test
      CI_PROJECT_PATH:                               root/test
      CI_PROJECT_PATH_SLUG:                          root-test
      CI_PROJECT_NAMESPACE:                          root
      CI_PROJECT_URL:                                http://gitlab.example.com/root/test
      CI_PROJECT_VISIBILITY:                         private
      CI_PROJECT_REPOSITORY_LANGUAGES:               
      CI_PAGES_DOMAIN:                               example.com
      CI_PAGES_URL:                                  http://root.example.com/test
      CI_REGISTRY:                                   registry.example.com
      CI_REGISTRY_IMAGE:                             registry.example.com/root/test
      CI_API_V4_URL:                                 http://gitlab.example.com/api/v4
      CI_PIPELINE_IID:                               8
      CI_CONFIG_PATH:                                .gitlab-ci.yml
      CI_PIPELINE_SOURCE:                            push
      CI_COMMIT_MESSAGE:                             Update .gitlab-ci.yml
      CI_COMMIT_TITLE:                               Update .gitlab-ci.yml
      CI_COMMIT_DESCRIPTION:                         
      CI_COMMIT_REF_PROTECTED:                       true
      CI_RUNNER_ID:                                  1
      CI_RUNNER_DESCRIPTION:                         gitlab-runner-gitlab-runner-5cdf6fd4f6-vqrzq
      CI_RUNNER_TAGS:                                
      GITLAB_USER_ID:                                1
      GITLAB_USER_EMAIL:                             admin@example.com
      GITLAB_USER_LOGIN:                             root
      GITLAB_USER_NAME:                              Administrator
      CI_DISPOSABLE_ENVIRONMENT:                     true
      CI_RUNNER_VERSION:                             12.5.0
      CI_RUNNER_REVISION:                            577f813d
      CI_RUNNER_EXECUTABLE_ARCH:                     linux/amd64
    Mounts:
      /builds from repo (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-8gjvw (ro)
  svc-0:
    Container ID:   docker://cad84f7dad65eb0447190ea53df5dcbe608cb6937c1bc1bf74f9edc21a42f6a6
    Image:          docker:18.09.7-dind
    Image ID:       docker-pullable://docker@sha256:a490c83561c1cef49b6fe12aba2c31f908391ec3efe4eb173225809c981e50c3
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sat, 21 Dec 2019 01:20:45 +0900
    Ready:          True
    Restart Count:  0
    Environment:
      FF_CMD_DISABLE_DELAYED_ERROR_LEVEL_EXPANSION:  false
      FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER:           false
      FF_USE_LEGACY_VOLUMES_MOUNTING_ORDER:          false
      CI_RUNNER_SHORT_TOKEN:                         3xycVVoJ
      CI_BUILDS_DIR:                                 /builds
      CI_PROJECT_DIR:                                /builds/root/test
      CI_CONCURRENT_ID:                              0
      CI_CONCURRENT_PROJECT_ID:                      0
      CI_SERVER:                                     yes
      CI_PIPELINE_ID:                                8
      CI_PIPELINE_URL:                               http://gitlab.example.com/root/test/pipelines/8
      CI_JOB_ID:                                     9
      CI_JOB_URL:                                    http://gitlab.example.com/root/test/-/jobs/9
      CI_BUILD_ID:                                   9
      CI_REGISTRY_USER:                              gitlab-ci-token
      CI:                                            true
      GITLAB_CI:                                     true
      GITLAB_FEATURES:                               
      CI_SERVER_HOST:                                gitlab.example.com
      CI_SERVER_NAME:                                GitLab
      CI_SERVER_VERSION:                             12.5.3
      CI_SERVER_VERSION_MAJOR:                       12
      CI_SERVER_VERSION_MINOR:                       5
      CI_SERVER_VERSION_PATCH:                       3
      CI_SERVER_REVISION:                            
      CI_JOB_NAME:                                   build
      CI_JOB_STAGE:                                  test
      CI_COMMIT_SHA:                                 f493d24c574d3deee3e0284f6f413cd20b9a7e83
      CI_COMMIT_SHORT_SHA:                           f493d24c
      CI_COMMIT_BEFORE_SHA:                          4bddec9c21af3e1fcedde2a81f62eb03d9afaa2b
      CI_COMMIT_REF_NAME:                            master
      CI_COMMIT_REF_SLUG:                            master
      CI_NODE_TOTAL:                                 1
      CI_DEFAULT_BRANCH:                             master
      CI_BUILD_REF:                                  f493d24c574d3deee3e0284f6f413cd20b9a7e83
      CI_BUILD_BEFORE_SHA:                           4bddec9c21af3e1fcedde2a81f62eb03d9afaa2b
      CI_BUILD_REF_NAME:                             master
      CI_BUILD_REF_SLUG:                             master
      CI_BUILD_NAME:                                 build
      CI_BUILD_STAGE:                                test
      CI_PROJECT_ID:                                 1
      CI_PROJECT_NAME:                               test
      CI_PROJECT_TITLE:                              test
      CI_PROJECT_PATH:                               root/test
      CI_PROJECT_PATH_SLUG:                          root-test
      CI_PROJECT_NAMESPACE:                          root
      CI_PROJECT_URL:                                http://gitlab.example.com/root/test
      CI_PROJECT_VISIBILITY:                         private
      CI_PROJECT_REPOSITORY_LANGUAGES:               
      CI_PAGES_DOMAIN:                               example.com
      CI_PAGES_URL:                                  http://root.example.com/test
      CI_REGISTRY:                                   registry.example.com
      CI_REGISTRY_IMAGE:                             registry.example.com/root/test
      CI_API_V4_URL:                                 http://gitlab.example.com/api/v4
      CI_PIPELINE_IID:                               8
      CI_CONFIG_PATH:                                .gitlab-ci.yml
      CI_PIPELINE_SOURCE:                            push
      CI_COMMIT_MESSAGE:                             Update .gitlab-ci.yml
      CI_COMMIT_TITLE:                               Update .gitlab-ci.yml
      CI_COMMIT_DESCRIPTION:                         
      CI_COMMIT_REF_PROTECTED:                       true
      CI_RUNNER_ID:                                  1
      CI_RUNNER_DESCRIPTION:                         gitlab-runner-gitlab-runner-5cdf6fd4f6-vqrzq
      CI_RUNNER_TAGS:                                
      GITLAB_USER_ID:                                1
      GITLAB_USER_EMAIL:                             admin@example.com
      GITLAB_USER_LOGIN:                             root
      GITLAB_USER_NAME:                              Administrator
      CI_DISPOSABLE_ENVIRONMENT:                     true
      CI_RUNNER_VERSION:                             12.5.0
      CI_RUNNER_REVISION:                            577f813d
      CI_RUNNER_EXECUTABLE_ARCH:                     linux/amd64
    Mounts:
      /builds from repo (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-8gjvw (ro)
...
Volumes:
  repo:
    Type:    EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:  
  default-token-8gjvw:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-8gjvw
    Optional:    false
...
```

コンテナは`build`、`helper`、`svc-0`の3つです。
PodはEmptyDirをVolumeとして利用していて、`/builds`以下にソースコードが配置されることが分かります。

Serviceコンテナが増えると`svc-1`、`svc-2`と増えていきます。
resourceのrequestsを大きめに取るとNodeのリソースが足らずにPending状態になってしまうので注意しましょう。

# おわりに
GitLab RunnerのExecutorの1つであるKubernetes Executorを説明しました。
helmを使えば簡単にデプロイできるので導入コストは非常に低いと思います。
公式ドキュメントが豊富なので今後使用する人は増えていきそうです。