GitLab RunnerをKubernetes（EKS）で運用する

![eks_logo.png](https://qiita-image-store.s3.amazonaws.com/0/109606/6b6a5a4c-144e-5883-846b-657c29e2b0f2.png)

## TL;DR

- GitLab Runnerのexecutor（実行形式）には、Kubernetesを指定できる
- パイプラインのジョブは1つのPodとして実行される
- 無制限にジョブを並列実行できる
- スポットインスタンスを使うと低コストで運用できる
- 月額コストは最低$150～

今回のインフラは全てTerraformで構築しており、コードはGitHubで公開している。
https://github.com/hareku/terraform-eks-gitlab-runner

## GitLab Runnerのおさらい
GitLab環境でのCI/CDは、[GitLab CI/CD](https://about.gitlab.com/product/continuous-integration/)というGitLabに統合されたものを使用し、このGitLab CI/CDはあくまでCI/CDを管理するためのものとなる。実際のビルドやリリースなどのジョブは、[GitLab Runner](https://docs.gitlab.com/runner/)上で実行され、本記事のように別途サーバーを立てる必要がある。

GitLab RunnerはGitLab上でのコミットを検知し、ジョブを実行する。ジョブの実行形式としてexecutorがあり、executorに指定できるのはShellやDocker、そしてKubernetesがある。これらのジョブの結果はGitLab上で見ることができる。

![68747470733a2f2f646f63732e6769746c61622e636f6d2f65652f63692f696d672f706970656c696e65735f67726f757065642e706e67.png](https://qiita-image-store.s3.amazonaws.com/0/109606/7521bc13-3a71-0cb1-d86f-c83c669a8a12.png)

より詳しくはClassmethodの[GitLab RunnerでCI/CDしてみる｜DevelopersIO](https://dev.classmethod.jp/ci/gitlab-runner-ci-cd-1/)が参考になる。

## Kubernetes executorの全体像
今回、EKSで構築したアーキテクチャ図は以下のようになる。

![gitlab-runner.png](https://qiita-image-store.s3.amazonaws.com/0/109606/38d65fa8-e668-f58c-82cf-93576e435585.png)


図でも表しているが、Kubernetesがexecutorの場合は以下のようなフローでジョブが実行される。

1. 開発者がGitLabにプッシュ
2. GitLabがGitLab Runnerへジョブを通知する
3. GitLab Runnerが、ジョブをPodとして起動する
4. Podはジョブとともに終了される

### 月額コストは最低$150から
Amazon EKSは、Kubernetesのコントロールプレーンをマネージドで提供してくれる。料金は**東京リージョンで1時間あたり0.20 USD、つまり月に144.0 USD**となっている。
それに加えて、実際にGitLab Runnerが走るスポットインスタンスの料金、そして後述するcluster-autoscalerなどのエコシステムをデプロイするためのオンデマンドインスタンスに料金が掛かる。

AutoScalingGroupのインスタンス数を0にスケールダウンすることもできるので、夜間は停止すると考える場合、スポットインスタンスの月額料金は1/2と考えていい。月額料金は以下のように試算する。

- EKS = 144 USD
- スポットインスタンス (m4.2xlarge　0.1331 USD /1 時間) * (24 * 30 * 1/2) * (2台) = $96
- オンデマンドインスタンス (t3.small　0.0272 USD /1 時間) * (24 * 30) = $20
- 合計 260 USD

m4.2xlargeを2台で計算している。m4.2xlargeは8vCPUと32GBのメモリを持つ。1つのジョブに割り当てられるリソース数は指定できるが、ジョブ1つに1vCPUと1GBのメモリを割り当てる場合、**2台で常におよそ16のジョブを並列実行できる**計算となる。

並列実行したいジョブ数がたった10～30程度の場合、EKSの料金が少し目立つ。そのためEKSクラスタをGitLab Runner専用として使うのではなく、他のKubernetesシステムと共存するようなクラスタと共に運用する方が望ましい。

## 用いるKubernetesエコシステム
ここでは、利用したKubernetesエコシステムについての説明をする。

### kube2iam：IAMの管理
[GitHub - jtblin/kube2iam](https://github.com/jtblin/kube2iam)

kube2iamでは、Kubernetes上で動作する**各Podに異なるIAMロールを付与**することができる。今回もPodとして起動されるジョブにIAMロールを付与することで、AWSへのアクセス権限を（ECRへのプッシュなど）与えている。

### Cluster Autoscaler：NodeのAutoScaling
[GitHub - autoscaler/cluster-autoscaler](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler)

Cluster Autoscalerは、需要に応じてASG（Auto Scaling Group）のノード（EC2インスタンス）の数を調整する。需要というのは「CPUやメモリのリソース不足などによる、Podが配置できない状態」を指す。EC2インスタンスのCPU使用率ではないことに注意する。

### EC2 Spot Termination Notice Handler：スポットインスタンスの管理
[GitHub - kube-aws/kube-spot-termination-notice-handler](https://github.com/kube-aws/kube-spot-termination-notice-handler)

Pod（Job）を起動するノードはスポットインスタンスを利用する。スポットインスタンスは、オンデマンド価格の30％程度の料金で利用できるため、コスト削減に有効である。

ただスポットインスタンスは**入札価格や空き状態によって、強制的に終了**される。
具体的には、終了2分前から`http://169.254.169.254/latest/meta-data/spot/instance-action`でHTTP200レスポンスが返ってくる。kube-spot-termination-notice-handlerも、内部ではこのURLへポーリングを行っている。

2分以上かかるジョブで、途中で強制終了されては困るようなものがある場合、**スポットインスタンスではなくオンデマンド**で運用する方が良い。

#### インスタンスタイプの注意点
注意点としては、**m5やt3などの次世代のインスタンスタイプだとAWS側で余っている量が少なく**、スポットリクエストが`capacity not available`エラーになってスケールアウトできないケースによく陥る。

これはm4などの旧世代のインスタンスタイプだと余っている量が多く、容量不足のエラーが返ってくることがほぼ無くなる。そのため自分は**m4を選択して回避**している。今のところ容量不足によるCI環境のダウンは見ていないが、現状はプロダクション環境で使うのは控えたほうが良い。

またcluster-autoscalerは、スポットフリートや、それに準ずる価格モデルに基づいたASGの選択に現在対応していない。そのあたりを含め、GitHubのFAQにしっかりと目を通すことをおすすめする。
[autoscaler/FAQ.md at master · kubernetes/autoscaler · GitHub](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md)

## まとめ
以上がKubernetes（EKS）でGitLab Runnerを運用する紹介となる。

まだまだAWSのスポットインスタンス周りのエコシステムは成熟しきっていない。そのためAWSに固執する必要がなければ、GKE（Google Kubernetes Engine）を選択肢に入れてもよい。GCPについてはあまり詳しくないので、またいつか調べたい。