GitLabの運用をEC2からEKSに移行した話

# はじめに

GitLabを社内運用する際にインフラをEC2からEKSに移行しました。
これによって**冗長性確保**や**GitLab CIにおける料金効率化**を実現することができました。
本記事ではGitLabを運用している人向けに環境構成の変化やデータ移行手順を説明していきます。

# 環境構成

ここでは過去と現在の環境構成を説明していきます。

## 過去構成

<img width="500" alt="past-architecture.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649144/1cfb739f-3449-c529-f73a-e966ff851b12.jpeg">

EC2インスタンスを用意し、GitLabとGitLab Runnerをそれぞれインストールした構成となっています。
GitLabは内部にNginxやUnicornなどを全部入りした**Omnibus版**を使用しているので導入は容易です。
GitLab Runnerは**Docker Executor**を使用しています。すなわちGitLab CIのジョブ内容がDockerコンテナとして実行されることになります。

この構成では自動復旧や冗長性担保ができない問題がありました。これもKubernetesを選択した理由の一つです。

## 現在構成

<img width="500" alt="current-architecture.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649144/62142466-8a4c-db9d-f04a-617d9c745edc.jpeg">

現在構成を説明していきます。

GitLabとGitLab Runnerで別々のテナントに分ける**マルチテナント運用**にしています。テスト用テナントを別に作成する理由としては、GitLab CIの中でkubectlでアプリを作成し外部からアクセスするケースがあり、ユーザが自由に使えるテナントを用意したほうが勝手が良かったからです。それぞれのテナントについて説明していきます。

**EKS 1.16**を使用しており、Kubernetesリソースは基本Helmを使ってデプロイしています。


### アプリ用テナント

このテナントではEKS上にGitLabをデプロイしています。
使用しているHelm Chartは下表になります。
(実際にはリポジトリツールやテスト解析ツールもデプロイしていますが説明の都合上今回は割愛します...）

| #    | Chart名                  | Chartバージョン | 用途                                        |
| ---- | ------------------------ | --------------- | ------------------------------------------- |
| 1    | GitLab                   | 2.3.8           | Gitリポジトリ                               |
| 2    | ALB Ingress Controller   | 0.1.10          | ALBへのIngressルール反映                    |
| 3    | NGINX Ingress Controller | 1.21.0          | NGINXへのIngressルール反映                  |
| 4    | Cluster Autoscaler       | 3.2.0           | Nodeのオートスケール                        |
| 5    | Prometheus Adapter       | 1.3.0           | Podのオートスケール                         |
| 6    | Prometheus               | 9.1.0           | 各リソースの監視                            |
| 7    | Grafana                  | 3.8.6           | 監視内容の可視化                            |
| 8    | kube2iam                 | 2.0.1           | PodへのIAM Role付与                         |
| 9    | Fluentd-CloudWatch       | 0.10.2          | コンテナログの収集とCloudWatch Logsへの送信 |


### テスト用テナント

ユーザが自由に使えるテスト用テナントで使用しているHelm Chartは下表になります。
GitLab Runner以外はアプリ用テナントと同様です。

| #    | Chart名                  | Chartバージョン | 用途            |
| ---- | ------------------------ | --------------- | --------------- |
| 1    | GitLab Runner            | 0.8.0           | GitLab CIの実行 |
| 2    | ALB Ingress Controller   | 0.1.10          | ALBへのIngressルール反映        |
| 3    | NGINX Ingress Controller | 1.21.0          | NGINXへのIngressルール反映         |
| 4    | Cluster Autoscaler       | 3.2.0           | Nodeのオートスケール          |



# 過去構成との主な相違点

## Load Balancer

ALB Ingress Controllerでは複数NamespaceのIngressルールを1つのALBで対応することができません。料金を抑えるためにALB Ingress ControllerとNGINX Ingress Controllerを組み合わせてルーティングを行っています。



## Nodeのオートスケール

EKSのNodeにはAutoscalingGroup(ASG)を使用します。これとCluster Autoscalerを組み合わせることでNodeのオートスケールを実現します。
本システムにおいては複数のGitLabがデプロイされるのを想定して、ユーザに関わるPodと運用者のみに関わるPodでNamespaceともにASGを分離しています。
各コンテナにリソースリミットをかければ別コンテナへの影響を気にする必要がないが、性能試験で結果が出なかったためこのような構成にしています。



## Podのオートスケール

KubernetesのHorizontal Pod Autscaling(HPA)でPodのオートスケールを行います。メトリクス取得のためにmetrics serverを使用することが標準ですが、本システムではPrometheus Adapterを使用しています。これはオートスケール発動までの時間が後者の方が短かったからです。



## 監視

Prometheus, Grafanaで監視とその可視化を行っています。アプリと同一クラスタに存在するので、クラスタ自体がダウンしたときにはアラートを飛ばすことができません。これを解決させるためにCloudWatchによる監視も加えています。



## ロギング

ログ収集にはfluentdを使用しています。fluentd-cloudwatchというhelm chartを使用すれば簡単にログをCloudWatch Logsに転送するようにできます。デフォルトでは5秒おきの転送になっていますがそれも自分で変えることができます。



## バックアップ

Kubernetes cronjobでアプリケーションバックアップを取りS3にアップロードするようにしています。これはGitLab Helm Chartに同梱されている`task-runner`で行います。

## GitLab Runner

GitLab RunnerはKubernetes Executorを使用しています。
GitLab RunnerのHelm Chartで`valus.yaml`をデフォルトのままにしておくとBuild PodがデプロイされるNamespaceはRunnerと同一、ServiceAccountは`default`になります。今回はBuild PodからKubernetesリソースを好き勝手作れるよう`cluster-admin`を渡しています。

# データ移行手順

ここではomnibus版からのデータ移行手順を説明します。

## １. ominibus版の一部データのS3への移行

GitLabではアイコン画像やGitLab CIのartifactsをオブジェクトストレージに保存します。Helm版の場合デフォルトではMinIOとなっていますが、S3で代替できます。

1-1. `/etc/gitlab/gitlab.rb`でuploads、artifacts、lfsの保存先を移行先で使用するS3バケットに設定する。

1-2. reconfigureして設定を反映させる。

```bash
$ sudo gitlab-ctl reconfigure
```

1-3. 設定したS3バケットにデータを移行させる。

```bash
$ sudo gitlab-rake gitlab:uploads:migrate:all
$ sudo gitlab-rake gitlab:artifacts:migrate
$ sudo gitlab-rake gitlab:lfs:migrate
```

1-4. 手順2におけるバックアップファイルの中身に含めないために以下のディレクトリを移行させる。

```bash
$ sudo mv /var/opt/gitlab/gitlab-rails/uploads{,.bak}
$ sudo mv /var/opt/gitlab/gitlab-rails/shared/artifacts{,.bak}
$ sudo mv /var/opt/gitlab/gitlab-rails/shared/lfs-objects{,.bak}
```

1-5. 手順1-4のディレクトリがないとバックアップに失敗するので空ディレクトリを次のコマンドで作成する。

```bash
$ sudo gitlab-ctl reconfigure
```



## ２. omnibus版のバックアップファイルの作成

次のコマンドでバックアップを実行する。

```bash
$ sudo gitlab-rake gitlab:backup:create
```

`/var/opt/gitlab/backups`にtarファイルが作成されるので、これをS3バケットにアップロードする。



## ３. Helm版でリストア

3-1. Rails Secretのリストア

Omnibus版の`/etc/gitlab/gitlab-secrets.json`から必要な値をコピーし以下の形式でyamlファイルを作成する。

```yaml
production:
  db_key_base: <your key base value>
  secret_key_base: <your secret key base value>
  otp_key_base: <your otp key base value>
  openid_connect_signing_key: <your openid signing key>
  ci_jwt_signing_key: <your ci jwt signing key>
```

既存のRails Secretを削除する。

```bash
$ kubectl delete secret <rails-secret-name>
```

新規作成したyamlファイルからKubernetes Secretを作成する。

```bash
$ kubectl create secret generic <rails-secret-name> --from-file=secrets.yml=<local-yaml-filepath>
```



3-2. Podの再起動

手順3-1での設定を反映させるためにPodを再起動させます。

```bash
$ kubectl delete pods -lapp=sidekiq,release=<helm release name>
$ kubectl delete pods -lapp=webservice,release=<helm release name>
$ kubectl delete pods -lapp=task-runner,release=<helm release name>
```



3-3. Gitlabリストア

手順2で作成したバックアップファイルをtask-runnerを用いてリストアする。

```bash
$ kubectl exec <task-runner pod name> -it -- backup-utility --restore -t <timestamp>_<version>
```

# Kubernetes移行をして良かった点・悪かった点

ここではKubernetes移行した上で感じた良かった点と悪かった点を説明していきます。

## 良かった点

### １. GitLab RunnerとKubernetesの親和性が高い

GitLab CIではジョブを走らせるたびにPodを立てます。これはKubernetes Executorの場合ですが、Docker Executorでも同様のことを行います。KubernetesでNodeのオートスケールを使えば必要なときにスケールアウトし、不要なときにスケールインするので運用の手間と課金額を減らせます。

ちなみに[Fargate Executor](https://docs.gitlab.com/runner/configuration/runner_autoscale_aws_fargate/)も存在しますが、少し用途が違うようです。



### ２. 冗長化が容易である

Deploymentであればreplica数を変更するだけで操作できます。また、HPAと組み合わせてオートスケールも行えます。
問題はEBSを使用している場合のStatefulSetの冗長化です。GitLabではredisとgitalyがそれに該当します。EFSの使用はGitLab公式が推奨していないので自分たちはEBSを採用しました。EBSがReadWriteOnceである以上、rook等を使用してStatefulSetを冗長化する必要がありました。ただ、redisに関しては結構前にredis clusterを使用できますし、gitalyもGitLab 13.0からgitaly clusterを使用できるようになっています。



## 悪かった点

### １. リソース設計が難しい

先述の通り、最初は全コンテナにリソースリミットをかける予定でしたが、性能テストで良い結果が出ず断念しました。これはコンテナが増えれば増えるほど陥る問題な気がします。



### ２. クラスタのバージョンアップデートが必要になる

EKSではバージョンライフサイクルがあります。90日ごとに新バージョンが登場し、サポートされるのは3バージョン前までです。サポートが切れているバージョンのクラスタは自動でアップデートされます。毎回ノンデグテストを行う必要があるので作業量面でデメリットだと思います。

特に1.16ではマニフェストファイルの書き方が変わっている部分が多いので注意が必要です。



### ３. 運用開始までの学習コストが高い

今では公式ドキュメントにプロキシ環境でのEKSの説明がありますが、運用開始当時はドキュメントが少なく主にプロキシ関連でハマることが多かったです。プロキシ以外でもPodへのIAM Role割り当てのためのkube2iam(今ではIAM Role for Service Accountもある)だったり、Cluster AutoscalerがASG単位でオートスケールトリガーを決めることに依る制限がありました。



# おわりに

GitLabの運用をEC2からEKSに移行したときの環境構成の違いやデータ移行手順の説明をしました。
先述の通り、良かった点も悪かった点もあるので一概にどちらが良かったとは言えないですが、Kubernetes運用をして得られる恩恵が多々あり、運用ノウハウも学べたことは自分にとってすごくプラスだったと思います。