GitLab RunnerのKubernetes Executorを完全に理解してカスタマイズの基礎を身につける

GitLab Runnerをセルフホスティングする際には、その内部構造の理解が必須です。この記事では、Kubernetes Executorの内部構造を理解し、今後のカスタマイズの基礎を習得します。
2022.05.06

「このExecutorの動き、どないなってるんや…」

現在支援している顧客環境では、CI/CD基盤として、GitLab RunnerをセルフホスティングのEKS上で動作させています。設定自体は、マニュアルを読みながら実施することでそれほど苦労するところはなかったんですが、さらに踏み込んでちょっとしたカスタマイズやインフラの最適化を実施しようとすると、内部構造を事前に理解しておく必要に迫られました。

GitLab Runnerのセルフホスティングと一口で言ってもその方式は複数種類あるのですが、このブログでは、その中のKubernetes Executorの動作原理を、公式マニュアルを紐解きながら解説していきます。

(祭) ∧ ∧
 Y  ( ゚Д゚)
 Φ[_ソ__y_l〉     GitLab Runnerマツリダワッショイ
    |_|_|
    し'´J

GitLab Runnerとは?

公式マニュアルのトップはこちら。

GitLab Runner | GitLab

GitLab Runnerは、GitLab社が提供するCI/CD基盤。位置づけとしてはGitHubにおけるGitHub Actionsと同等です。SaaSとしてGitLab.comの中でも利用できますし、セルフホスティングして自分で管理するインフラ上で動作させることも可能です。

GitLab RunnerのExecutorとは?

Executors | GitLab

GitLab Runnerをセルフホスティングする際に、Runnerを動作させる利用方式です。2022年5月現在、以下のExecutorが用意されています。

  • SSH
  • Shell
  • Parallels
  • VirtualBox
  • Docker
  • Docker Machine (auto-scaling)
  • Kubernetes
  • Custom

各Executorによる機能差はこちらに記載のとおり。

Selecting the Executor

今回支援している顧客環境では、アプリケーションワークロードにEKSを利用していることも有り、機能が豊富でノウハウもあるKubernetes Executorを採用することになりました。

Kubernetes Executorの内部構造を理解する

The Kubernetes executor for GitLab Runner | GitLab

GitLabとGitLab RunnerのKubernetesの関連をシーケンス図で把握するとこうなります。画像は公式マニュアルからの引用です。

GitLab Runnerからは定期的にGitLab側のCIジョブを監視、CIジョブが登録されたら、ジョブが生成されたらその情報を取得、その情報を元にKUbernetes APIを叩き、ジョブ用のPodが起動します。起動したPodの中では、以下の処理が実行されます。

  1. Prepare:ジョブ用のPodが生成される
  2. Pre-build:全ステージのアーティファクトクローン、キャッシュ復元、ダウンロードを行う。これは、Podの一部である専用コンテナを利用
  3. Build:自身のジョブのビルドを実行
  4. Post-build:キャッシュ作成、アーティファクトのGitLabへのアップロード。これも、Podの一部である専用コンテナを利用

GitLab RunnerをKubernetesにインストールし、内部構造を調べてみる

ここまでで基本的なKubernetes Executorの内部構造のイメージができたかと思います。

GitLabからは、KubernetesへのGitLab Runnerインストール用途でHelm Chartが公開されています。ここからは、このHelmChartを利用して、もう少し細かくKubernetes Executorの中身を確認していきます。

GitLab Runner Helm Chart | GitLab

事前に最小構成の設定ファイルconfig-tmp.yamlを用意しておきます。

config-tmp.yaml

gitlabUrl: https://gitlab.com/
runnerRegistrationToken: "XXXXXXXXXX1234567890"
rbac:
  create: true
  • gitlabUrl:GitLabサーバーのURL。SaaS版のGitLabからGitLab Runnerを利用する場合は、https://gitlab.com/
  • runnerRegistrationToken:GitLabからこのGtiLab Runnerを利用するために必要なトークン。事前にGitLab側で取得しておく必要あり
  • rbac:Role Base Accecc Controleを有効にするために設定しておきます

helm installをdry-runし、適用されるマニフェストファイルを確認します。

helm install hamada-gitlab-runner gitlab/gitlab-runner --dry-run -f config-tmp.yaml --namespace gitlab-runner

上記を実行すると適用予定のマニフェストファイル一式が出力されます。作成されるリソースは、以下の通り

  • ServiceAccount
  • Secret
  • ConfigMap
  • Role
  • RoleBinding
  • Deployment

以下に、作成されるリソースについて解説していきます。

ServiceAccount

GitLab Runnerで利用するService Accountの定義です。

# Source: gitlab-runner/templates/service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
  name: hamada-gitlab-runner-gitlab-runner
  labels:
    app: hamada-gitlab-runner-gitlab-runner
    chart: gitlab-runner-0.37.2
    release: "hamada-gitlab-runner"
    heritage: "Helm"

Secret

GitLabからGitLab Runnerを起動する際に利用するトークンを格納するためのSecretを作成します。

# Source: gitlab-runner/templates/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: "hamada-gitlab-runner-gitlab-runner"
  labels:
    app: hamada-gitlab-runner-gitlab-runner
    chart: gitlab-runner-0.37.2
    release: "hamada-gitlab-runner"
    heritage: "Helm"
type: Opaque
data:
  runner-registration-token: "R1IxMzQ4OTQxSGlMZnVFcmVGR2pHdno2WVhQOFM="
  runner-token: ""

ConfigMap

GitLab RunnerのJobが実行されるとき専用のPodが起動しますが、そのPodで利用する設定情報が、このConfigMapで定義されます。data以降に、PodのEntryポイントが記載されているので、環境編集や設定ファイルがどのように展開されているか、ここで確認できます。

data以降は長いのでここには全文は掲載していません。

# Source: gitlab-runner/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: hamada-gitlab-runner-gitlab-runner
  labels:
    app: hamada-gitlab-runner-gitlab-runner
    chart: gitlab-runner-0.37.2
    release: "hamada-gitlab-runner"
    heritage: "Helm"
data:
  entrypoint: |
    #!/bin/bash
    set -e

    mkdir -p /home/gitlab-runner/.gitlab-runner/

    cp /configmaps/config.toml /home/gitlab-runner/.gitlab-runner/

    # Set up environment variables for cache
    if [[ -f /secrets/accesskey && -f /secrets/secretkey ]]; then
      export CACHE_S3_ACCESS_KEY=$(cat /secrets/accesskey)
      export CACHE_S3_SECRET_KEY=$(cat /secrets/secretkey)
    fi

〜〜後、省略〜〜

Role

ジョブ実行のPodで利用するServiceAccountで利用するRoleの設定です。権限は、rules:配下に記載があるとおり、クラスター内の全リソースに対する全ての処理が許可されています。

# Source: gitlab-runner/templates/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: "Role"
metadata:
  name: hamada-gitlab-runner-gitlab-runner
  labels:
    app: hamada-gitlab-runner-gitlab-runner
    chart: gitlab-runner-0.37.2
    release: "hamada-gitlab-runner"
    heritage: "Helm"
  namespace: "gitlab-runner"
rules:
- apiGroups: [""]
  resources: ["*"]
  verbs: ["*"]

RoleBinding

前述で作成したRoleを、このRoleBindingでServiceAccountに紐付けます。roleRef:で紐付けるRoleを、subjects:で紐付けるServiceAccountの名前を定義しています。

# Source: gitlab-runner/templates/role-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: "RoleBinding"
metadata:
  name: hamada-gitlab-runner-gitlab-runner
  labels:
    app: hamada-gitlab-runner-gitlab-runner
    chart: gitlab-runner-0.37.2
    release: "hamada-gitlab-runner"
    heritage: "Helm"
  namespace: "gitlab-runner"
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: "Role"
  name: hamada-gitlab-runner-gitlab-runner
subjects:
- kind: ServiceAccount
  name: hamada-gitlab-runner-gitlab-runner
  namespace: "gitlab-runner"

Deployment

ジョブ実行時に起動するPodの初期情報を設定するDeploymentです。ここで、Podに設定されるImage、Volume、紐づくConfigmap、利用するServiceAccount、環境変数などが全て指定されます。

実際にPodが起動される際に利用させる全ての設定情報がここに記載されているので、細かい情報はここで確認可能です。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hamada-gitlab-runner-gitlab-runner
  labels:
    app: hamada-gitlab-runner-gitlab-runner
    chart: gitlab-runner-0.37.2
    release: "hamada-gitlab-runner"
    heritage: "Helm"
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: hamada-gitlab-runner-gitlab-runner
  template:
    metadata:
      labels:
        app: hamada-gitlab-runner-gitlab-runner
        chart: gitlab-runner-0.37.2
        release: "hamada-gitlab-runner"
        heritage: "Helm"
      annotations:
        checksum/configmap: 9f5e01ff935a315851d42292060f67bc1387319f7ca2814f43546e9dd0daab7d
        checksum/secrets: 6cb1d605cac5eac1bca1526e9761f649a33f0ca2d647bc7ba6b121c62b20850c
        prometheus.io/scrape: 'true'
        prometheus.io/port: "9252"
    spec:
      securityContext:
        runAsUser: 100
        fsGroup: 65533
      terminationGracePeriodSeconds: 3600
      initContainers:
      - name: configure
        command: ['sh', '/configmaps/configure']
        image: gitlab/gitlab-runner:alpine-v14.7.0
        imagePullPolicy: "IfNotPresent"
        securityContext:
          allowPrivilegeEscalation: false
        env:
                
        - name: CI_SERVER_URL
          value: "https://gitlab.com/"
        - name: CLONE_URL
          value: ""
        - name: RUNNER_EXECUTOR
          value: "kubernetes"
        - name: REGISTER_LOCKED
          value: "true"
        - name: RUNNER_TAG_LIST
          value: ""
        volumeMounts:
        - name: runner-secrets
          mountPath: /secrets
          readOnly: false
        - name: configmaps
          mountPath: /configmaps
          readOnly: true
        - name: init-runner-secrets
          mountPath: /init-secrets
          readOnly: true
        resources:
          {}
      serviceAccountName: hamada-gitlab-runner-gitlab-runner
      containers:
      - name: hamada-gitlab-runner-gitlab-runner
        image: gitlab/gitlab-runner:alpine-v14.7.0
        imagePullPolicy: "IfNotPresent"
        securityContext:
          allowPrivilegeEscalation: false
        lifecycle:
          preStop:
            exec:
              command: ["/entrypoint", "unregister", "--all-runners"]
        command: ["/usr/bin/dumb-init", "--", "/bin/bash", "/configmaps/entrypoint"]
        env:
                
        - name: CI_SERVER_URL
          value: "https://gitlab.com/"
        - name: CLONE_URL
          value: ""
        - name: RUNNER_EXECUTOR
          value: "kubernetes"
        - name: REGISTER_LOCKED
          value: "true"
        - name: RUNNER_TAG_LIST
          value: ""
        livenessProbe:
          exec:
            command: ["/bin/bash", "/configmaps/check-live"]
          initialDelaySeconds: 60
          timeoutSeconds: 1
          periodSeconds: 10
          successThreshold: 1
          failureThreshold: 3
        readinessProbe:
          exec:
            command: ["/usr/bin/pgrep","gitlab.*runner"]
          initialDelaySeconds: 10
          timeoutSeconds: 1
          periodSeconds: 10
          successThreshold: 1
          failureThreshold: 3
        ports:
        - name: "metrics"
          containerPort: 9252
        volumeMounts:
        - name: runner-secrets
          mountPath: /secrets
        - name: etc-gitlab-runner
          mountPath: /home/gitlab-runner/.gitlab-runner
        - name: configmaps
          mountPath: /configmaps
        resources:
          {}
      volumes:
      - name: runner-secrets
        emptyDir:
          medium: "Memory"
      - name: etc-gitlab-runner
        emptyDir:
          medium: "Memory"
      - name: init-runner-secrets
        projected:
          sources:
            - secret:
                name: "hamada-gitlab-runner-gitlab-runner"
                items:
                  - key: runner-registration-token
                    path: runner-registration-token
                  - key: runner-token
                    path: runner-token
      - name: configmaps
        configMap:
          name: hamada-gitlab-runner-gitlab-runner

GitLab Runnerのインストール

ここまでで、実際に作成されるリソースの一覧の概要を把握できたので、実際にhelm installすることで、Kubernetes環境上にGitLab Runnerをインストールします。

helm install hamada-gitlab-runner gitlab/gitlab-runner -f config-tmp.yaml --namespace gitlab-runner

無事インストールされ、GitLab.comからこのようにGitLab Runnerが認識されていれば、インストール成功です。お疲れさまでした!

[Settings] -> [CI/CD Settings] -> [Runners]

実際にデプロイされたPodを確認

Namespaceにgitlab-runnerを指定しているため、該当Namespaceのに以下のPodが常駐しています。

$ kubectl get pods -n gitlab-runner
NAME                                           READY   STATUS    RESTARTS   AGE
gitlab-runner-gitlab-runner-XXXXXXXXXX-YYYYYYYY   1/1     Running   0          3d17h

Describeすると、GitLab Runnerインストール時に設定された各種関連リソースを確認できます。

$ kubectl describe pod gitlab-runner-gitlab-runner-XXXXXXXXXX-YYYYYYYY -n gitlab-runne
Name:         gitlab-runner-gitlab-runner-XXXXXXXXXX-YYYYYYYY
Namespace:    gitlab-runner
Priority:     0
Node:         ip-10-0-2-230.ec2.internal/10.0.2.230
Start Time:   Mon, 02 May 2022 14:53:22 +0900
Labels:       app=gitlab-runner-gitlab-runner
              chart=gitlab-runner-0.28.0
              heritage=Helm
              pod-template-hash=5bf78645f6
              release=gitlab-runner
Annotations:  checksum/configmap: f38a8b6828475f9a23923abbc5cfefc71b8e77dd1711eb4a643ee33adc2f2035
              checksum/secrets: b854aa3a7279d6415ddd6de04c79674630640ea1753a0dfee201d6c55d24003a
              kubernetes.io/psp: eks.privileged
              prometheus.io/port: 9252
              prometheus.io/scrape: true
Status:       Running
IP:           10.0.2.105
IPs:
  IP:           10.0.2.105
Controlled By:  ReplicaSet/gitlab-runner-gitlab-runner-5bf78645f6
Init Containers:
  configure:
    Container ID:  docker://9b929e7b9b418a782e18c62b8c571339ce71b7fdb41a7557d1d21e7d6cbfcf6e
    Image:         gitlab/gitlab-runner:alpine-v13.11.0
    Image ID:      docker-pullable://gitlab/gitlab-runner@sha256:d01c7fdfe5b85d55b66bb0ba01d1e52ac31747811b522f05dac2d514219fbd9f
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
      /configmaps/configure
    State:          Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Mon, 02 May 2022 14:53:23 +0900
      Finished:     Mon, 02 May 2022 14:53:23 +0900
    Ready:          True
    Restart Count:  0
    Environment:
      CI_SERVER_URL:                                    https://gitlab.com/
      CLONE_URL:                                        
      RUNNER_REQUEST_CONCURRENCY:                       1
      RUNNER_EXECUTOR:                                  kubernetes
      REGISTER_LOCKED:                                  false
      RUNNER_TAG_LIST:                                  hamada-common-eks-gitlab-runner
      KUBERNETES_IMAGE:                                 
      KUBERNETES_NAMESPACE:                             gitlab-runner
      KUBERNETES_CPU_LIMIT:                             
      KUBERNETES_CPU_LIMIT_OVERWRITE_MAX_ALLOWED:       
      KUBERNETES_MEMORY_LIMIT:                          
      KUBERNETES_MEMORY_LIMIT_OVERWRITE_MAX_ALLOWED:    
      KUBERNETES_CPU_REQUEST:                           
      KUBERNETES_CPU_REQUEST_OVERWRITE_MAX_ALLOWED:     
      KUBERNETES_MEMORY_REQUEST:                        
      KUBERNETES_MEMORY_REQUEST_OVERWRITE_MAX_ALLOWED:  
      KUBERNETES_SERVICE_ACCOUNT:                       
      KUBERNETES_SERVICE_CPU_LIMIT:                     
      KUBERNETES_SERVICE_MEMORY_LIMIT:                  
      KUBERNETES_SERVICE_CPU_REQUEST:                   
      KUBERNETES_SERVICE_MEMORY_REQUEST:                
      KUBERNETES_HELPER_CPU_LIMIT:                      
      KUBERNETES_HELPER_MEMORY_LIMIT:                   
      KUBERNETES_HELPER_CPU_REQUEST:                    
      KUBERNETES_HELPER_MEMORY_REQUEST:                 
      KUBERNETES_HELPER_IMAGE:                          
      KUBERNETES_PULL_POLICY:                           
    Mounts:
      /configmaps from configmaps (ro)
      /init-secrets from init-runner-secrets (ro)
      /secrets from runner-secrets (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from gitlab-runner-gitlab-runner-token-6cqb4 (ro)
Containers:
  gitlab-runner-gitlab-runner:
    Container ID:  docker://fcb071087e27e0a73fc33f095baa6c32cc60dc5644bd48b16b07f766ac09a01a
    Image:         gitlab/gitlab-runner:alpine-v13.11.0
    Image ID:      docker-pullable://gitlab/gitlab-runner@sha256:d01c7fdfe5b85d55b66bb0ba01d1e52ac31747811b522f05dac2d514219fbd9f
    Port:          9252/TCP
    Host Port:     0/TCP
    Command:
      /bin/bash
      /configmaps/entrypoint
    State:          Running
      Started:      Mon, 02 May 2022 14:53:24 +0900
    Ready:          True
    Restart Count:  0
    Liveness:       exec [/bin/bash /configmaps/check-live] delay=60s timeout=1s period=10s #success=1 #failure=3
    Readiness:      exec [/usr/bin/pgrep gitlab.*runner] delay=10s timeout=1s period=10s #success=1 #failure=3
    Environment:
      CI_SERVER_URL:                                    https://gitlab.com/
      CLONE_URL:                                        
      RUNNER_REQUEST_CONCURRENCY:                       1
      RUNNER_EXECUTOR:                                  kubernetes
      REGISTER_LOCKED:                                  false
      RUNNER_TAG_LIST:                                  hamada-common-eks-gitlab-runner
      KUBERNETES_IMAGE:                                 
      KUBERNETES_NAMESPACE:                             gitlab-runner
      KUBERNETES_CPU_LIMIT:                             
      KUBERNETES_CPU_LIMIT_OVERWRITE_MAX_ALLOWED:       
      KUBERNETES_MEMORY_LIMIT:                          
      KUBERNETES_MEMORY_LIMIT_OVERWRITE_MAX_ALLOWED:    
      KUBERNETES_CPU_REQUEST:                           
      KUBERNETES_CPU_REQUEST_OVERWRITE_MAX_ALLOWED:     
      KUBERNETES_MEMORY_REQUEST:                        
      KUBERNETES_MEMORY_REQUEST_OVERWRITE_MAX_ALLOWED:  
      KUBERNETES_SERVICE_ACCOUNT:                       
      KUBERNETES_SERVICE_CPU_LIMIT:                     
      KUBERNETES_SERVICE_MEMORY_LIMIT:                  
      KUBERNETES_SERVICE_CPU_REQUEST:                   
      KUBERNETES_SERVICE_MEMORY_REQUEST:                
      KUBERNETES_HELPER_CPU_LIMIT:                      
      KUBERNETES_HELPER_MEMORY_LIMIT:                   
      KUBERNETES_HELPER_CPU_REQUEST:                    
      KUBERNETES_HELPER_MEMORY_REQUEST:                 
      KUBERNETES_HELPER_IMAGE:                          
      KUBERNETES_PULL_POLICY:                           
    Mounts:
      /configmaps from configmaps (rw)
      /home/gitlab-runner/.gitlab-runner from etc-gitlab-runner (rw)
      /secrets from runner-secrets (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from gitlab-runner-gitlab-runner-token-6cqb4 (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  runner-secrets:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:     Memory
    SizeLimit:  <unset>
  etc-gitlab-runner:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:     Memory
    SizeLimit:  <unset>
  init-runner-secrets:
    Type:                Projected (a volume that contains injected data from multiple sources)
    SecretName:          gitlab-runner-gitlab-runner
    SecretOptionalName:  <nil>
  configmaps:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      gitlab-runner-gitlab-runner
    Optional:  false
  gitlab-runner-gitlab-runner-token-6cqb4:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  gitlab-runner-gitlab-runner-token-6cqb4
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:          <none>

参考:GitLab Runner構築時の公式マニュアルとそのあるき方

最後に、GitLab Runner関連の情報を得るときの公式マニュアルの参照方法を参考までにお届け。

こちらで、GitLab Runnerの構築手順が記載されています。GitLab Runnerは様々な環境でセルフホストする手段があるので、それぞれの環境で最適なものを選択し、利用してみてください。

Install GitLab Runner | GitLab

最初わかりにくかったのですが、公式マニュアルには、別の階層にGitLab Runnerの解説マニュアルがあります。

マニュアルの階層:[Use GitLab] -> [Build your application] -> [Runners]

GitLab Runner | GitLab

1つ目に記した[Install GitLab Runner]配下にも、GitLab Runnerのカスタマイズに関する情報が含まれていますが、こちらでは、より詳細な設定方法や運用上のベストプラクティスも含まれているため、本格的にGitLab Runnerをセルフホスティングで運用するのであれば、こちらも一通り目を通しておくことをオススメします。

セルフホスティングのGitLab Runnerを細かくカスタマイズするためには、内部構造の理解は必須

インストールして動かすだけであれば、マニュアルに基づきそのままHelm installでEKS上にGitLab Runnerをインストールするだけで事足ります。が、その後急遽プロジェクトの要件で、GitLab Runnerの各ジョブに個別にIAMロールを紐付ける必要性がでてきました。

最初とりあえずインストールしただけでは、GitLab Runnerの設定ファイルであるvalues.yamlの内容がイマイチ理解できていなかったのですが、今回、実際にGitLab Runnerインストール時に適用される各種Kubernetesリソースのマニフェストファイルを把握することで、起動されるジョブに紐づくServiceAccountや、関連するRole、各種設定ファイルの関連がわかり、より深くGitLab RunnerのKubernetes Executorの構造が理解できたのが収穫でした。

セルフホストのGitLab Runnerには膨大な設定項目があり、それらを利用することでよりプロジェクトの状況に合わせたCI/CD基盤が構築できます。カスタマイズを進めていくに当たり、基本的な構造を抑えておくことは必須なので、このブログを参考にしていただきながら、紹介した公式マニュアルと合わせて理解を深めてもらえれば幸いです。

それでは、今日はこのへんで。濱田(@hamako9999)でした。