GitLabで使用するオブジェクトストレージをS3からMinio+EFSに移行する

# はじめに

GitLabではマージリクエストのdiffやGitLab CIジョブのログなどのデータをS3といったオブジェクトストレージに保存することができます。

ファイルサイズが大きい際レスポンスが遅い問題に直面したのでS3からMinioへの移行を検証しました。MinioにはEFSを使用して可用性を確保しています。

# 環境

EKS 1.16
Helm 3.4.0
Helmfile 0.122.0
GitLab Helm Chart 4.8.4
kube2iam Helm Chart 2.5.0
ALB Ingress Controller Helm Chart 0.1.11
NGINX Ingress Controller Helm Chart 1.39.0

# 手順

EKSクラスタやS3バケットは構築済みであることを前提とします。

以下の流れで実施します。

1. S3使用のGitLabでバックアップ
2. Minio用意
3. Minio使用のGitLabでリストア

## 0. 前準備

Kubernetesリソースをデプロイしていきます。


まずはGitLabがS3を使用できるようにSecretをデプロイします。

```yaml:rails-storage.yaml
provider: AWS
use_iam_profile: true
region: ap-northeast-1
```

```console
$ kubectl create secret generic gitlab-rails-storage \
  --from-file=connection=rails-storage.yaml
```

```yaml:storage.config
[default]
bucket_location = ap-northeast-1
```

```console
$ kubectl create secret generic storage-config \
  --from-file=config=storage.config
```

GitLab Helm Chartに同梱されているNGINX Ingress Controllerは使用しないため、templateのIngressを編集する必要があります。

```diff:gitlab/charts/gitlab/charts/webservice/templates/ingress.yaml
metadata:
  annotations:
-   kubernetes.io/ingress.class: "{{ template "gitlab.ingressclass" $ }}"
```

Helmfileでkube2iam、ALB Ingress Controller、NGINX Ingress Controller、GitLabをデプロイします。
`<ALB_INGRESS_CONTROLLER_IAM_ROLE_ARN>`、`<S3_IAM_ROLE_ARN>`にはkube2iamで使用可能なIAM RoleのARNに置き換えてください。

```yaml:helmfile.yaml
repositories:
  - name: stable
    url: https://charts.helm.sh/stable
  - name: incubator
    url: https://charts.helm.sh/incubator

releases:
  - name: kube2iam
    namespace: kube-system
    chart: stable/kube2iam
    version: 2.5.0
    values:
      - host:
          iptables: true
          interface: "eni+"
        extraArgs:
          auto-discover-base-arn: ""
        rbac:
          create: true
  - name: aws-alb-ingress-controller
    namespace: kube-system
    chart: incubator/aws-alb-ingress-controller
    version: 0.1.11
    values:
      - clusterName: <EKS_CLUSTER_NAME>
        autoDiscoverAwsRegion: true
        autoDiscoverAwsVpcID: true
        podAnnotations:
          iam.amazonaws.com/role: <ALB_INGRESS_CONTROLLER_IAM_ROLE_ARN>
  - name: nginx-ingress
    namespace: kube-system
    chart: stable/nginx-ingress
    version: 1.39.0
    values:
      - controller:
          service:
            type: NodePort
  - name: test-gitlab
    namespace: default
    chart: ./gitlab
    values:
      - global:
          edition: ce
          hosts:
            https: false
            gitlab:
              name: gitlab.sample.com
              https: false
          minio:
            enabled: false
          ingress:
            configureCertmanager: false
            enabled: false
            tls:
              enabled: false
          appConfig:
            lfs:
              bucket: test-gitlab-lfs
              connection:
                secret: gitlab-rails-storage
                key: connection
            artifacts:
              bucket: test-gitlab-artifacts
              connection:
                secret: gitlab-rails-storage
                key: connection
            uploads:
              bucket: test-gitlab-uploads
              connection:
                secret: gitlab-rails-storage
                key: connection
            packages:
              bucket: test-gitlab-packages
              connection:
                secret: gitlab-rails-storage
                key: connection
            externalDiffs:
              enabled: true
              bucket: test-gitlab-mr-diffs
              connection:
                secret: gitlab-rails-storage
                key: connection
            pseudonymizer:
              configMap:
              bucket: test-gitlab-pseudo
              connection:
                secret: gitlab-rails-storage
                key: connection
            backups:
              bucket: test-gitlab-backups
              tmpBucket: test-gitlab-tmp
        upgradeCheck:
          enabled: false
        certmanager:
          install: false
        nginx-ingress:
          enabled: false
        prometheus:
          install: false
        registry:
          enabled: false
        gitlab-runner:
          install: false
        gitlab:
          gitlab-exporter:
            enabled: false
          gitlab-shell:
            enabled: false
          sidekiq:
            annotations:
              iam.amazonaws.com/role: <S3_IAM_ROLE_ARN>
            registry:
              enabled: false
          task-runner:
            enabled: true
            annotations:
              iam.amazonaws.com/role: <S3_IAM_ROLE_ARN>
            registry:
              enabled: false
            backups:
              objectStorage:
                config:
                  secret: storage-config
                  key: config
          webservice:
            annotations:
              iam.amazonaws.com/role: <S3_IAM_ROLE_ARN>
            registry:
              enabled: false
            ingress:
              enabled: true
            minReplicas: 1
```

```console
$ helmfile apply -f helmfile.yaml
```

ALB→NGINX Ingress Controllerへのルーティング設定のためのIngressをデプロイします。

```yaml:alb-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    kubernetes.io/ingress.class: alb
  name: alb-ingress
  namespace: kube-system
spec:
  rules:
    - http:
        paths:
          - backend:
              serviceName: test-gitlab-nginx-ingress-controller
              servicePort: 80
```

```console
$ kubectl apply -f alb-ingress.yaml
```

## 1. バックアップ

Task Runner Podからデータのバックアップを取ります。データはS3バケットに保存されます。


```console
$ kubectl exec <TASK_RUNNER_POD> -it backup-utility
```

## 2. Minioの用意

EFSアクセスポイントの設定値は下表になります。

| Key | Value |
|:-----------|:------------|
| 名前      | `minio`      |
| パス    | `/minio`    |
| POSIXユーザー      | `1000:1000`  |
| 作成情報      | `1000:1000(0777)` |

EKSでEFSを使用できるよう[EFS CSIドライバー](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/efs-csi.html)をデプロイします。

```console
$ kubectl apply -k "github.com/kubernetes-sigs/aws-efs-csi-driver/deploy/kubernetes/overlays/stable/ecr/?ref=release-1.0"
```

StorageClassも新規作成が必要になります。

```yaml:efs-sc.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
```

```console
$ kubectl apply -f efs-sc.yaml
```

MinioのPersistentVolumeでEFSが使用できるようtemplateにファイルを新規追加します。

```diff:gitlab/charts/minio/templates/minio_pv.yaml
+ {{- if and .Values.global.minio.enabled .Values.persistence.enabled }}
+ kind: PersistentVolume
+ apiVersion: v1
+ metadata:
+   name: {{ template "minio.fullname" . }}
+   namespace: {{ $.Release.Namespace }}
+   labels:
+ {{ include "gitlab.standardLabels" . | indent 4 }}
+ spec:
+   volumeMode: Filesystem
+   persistentVolumeReclaimPolicy: Retain
+   accessModes:
+     - {{ .Values.persistence.accessMode | quote }}
+   capacity:
+     storage: {{ .Values.persistence.size | quote }}
+ {{- if .Values.persistence.csi }}
+   csi:
+ {{ toYaml .Values.persistence.csi | indent 4 }}
+ {{- end -}}
+ {{- if .Values.persistence.storageClass }}
+ {{- if (eq "-" .Values.persistence.storageClass) }}
+   storageClassName: ""
+ {{- else }}
+   storageClassName: "{{ .Values.persistence.storageClass }}"
+ {{- end -}}
+ {{- end }}
+ {{- end }}
```

webservice同様、GitLab同梱のNGINX Ingress Controllerを使用しないため修正します。

```diff:gitlab/charts/minio/templates/ingress.yaml
metadata:
  annotations:
-   kubernetes.io/ingress.class: "{{ template "minio.ingressclass" . }}"
```

Minioを使用できるように、HelmfileのマニフェストファイルでGitLabのvaluesを修正します。
`<EFS_FILESYSTEM_ID>`、`<EFS_ACCESSPOINT_ID>`にはそれぞれEFSファイルシステムのID、EFSアクセスポイントのIDに置き換えてください。 

```diff:helmfile.yaml
repositories:
  - name: stable
    url: https://charts.helm.sh/stable
  - name: incubator
    url: https://charts.helm.sh/incubator

releases:
  - name: kube2iam
    namespace: kube-system
    chart: stable/kube2iam
    version: 2.5.0
    values:
      - host:
          iptables: true
          interface: "eni+"
        extraArgs:
          auto-discover-base-arn: ""
        rbac:
          create: true
  - name: aws-alb-ingress-controller
    namespace: kube-system
    chart: incubator/aws-alb-ingress-controller
    version: 0.1.11
    values:
      - clusterName: <EKS_CLUSTER_NAME>
        autoDiscoverAwsRegion: true
        autoDiscoverAwsVpcID: true
        podAnnotations:
          iam.amazonaws.com/role: <ALB_INGRESS_CONTROLLER_IAM_ROLE_ARN>
  - name: nginx-ingress
    namespace: kube-system
    chart: stable/nginx-ingress
    version: 1.39.0
    values:
      - controller:
          service:
            type: NodePort
  - name: test-gitlab
    namespace: default
    chart: ./gitlab
    values:
      - global:
          edition: ce
          hosts:
            https: false
            gitlab:
              name: gitlab.sample.com
              https: false
+           minio:
+             name: minio.sample.com
+             https: false
          minio:
-           enabled: false
+           enabled: true
          ingress:
            configureCertmanager: false
            enabled: false
            tls:
              enabled: false
          appConfig:
-           lfs:
-             bucket: test-gitlab-lfs
-             connection:
-               secret: gitlab-rails-storage
-               key: connection
-           artifacts:
-             bucket: test-gitlab-artifacts
-             connection:
-               secret: gitlab-rails-storage
-               key: connection
-           uploads:
-             bucket: test-gitlab-uploads
-             connection:
-               secret: gitlab-rails-storage
-               key: connection
-           packages:
-             bucket: test-gitlab-packages
-             connection:
-               secret: gitlab-rails-storage
-               key: connection
            externalDiffs:
              enabled: true
-             bucket: test-gitlab-mr-diffs
-             connection:
-               secret: gitlab-rails-storage
-               key: connection
-           pseudonymizer:
-             configMap:
-             bucket: test-gitlab-pseudo
-             connection:
-               secret: gitlab-rails-storage
-               key: connection
-           backups:
-             bucket: test-gitlab-backups
-             tmpBucket: test-gitlab-tmp
        upgradeCheck:
          enabled: false
        certmanager:
          install: false
        nginx-ingress:
          enabled: false
        prometheus:
          install: false
        registry:
          enabled: false
        gitlab-runner:
          install: false
        gitlab:
          gitlab-exporter:
            enabled: false
          gitlab-shell:
            enabled: false
          sidekiq:
-           annotations:
-             iam.amazonaws.com/role: <S3_IAM_ROLE_ARN>
            registry:
              enabled: false
          task-runner:
            enabled: true
-           annotations:
-             iam.amazonaws.com/role: <S3_IAM_ROLE_ARN>
            registry:
              enabled: false
-           backups:
-             objectStorage:
-               config:
-                 secret: storage-config
-                 key: config
          webservice:
-           annotations:
-             iam.amazonaws.com/role: <S3_IAM_ROLE_ARN>
            registry:
              enabled: false
            ingress:
              enabled: true
            minReplicas: 1
+         minio:
+           ingress:
+             enabled: true
+           persistence:
+             storageClass: efs-sc
+             accessMode: ReadWriteMany
+             csi:
+               driver: efs.csi.aws.com
+               volumeHandle: <EFS_FILESYSTEM_ID>::<EFS_ACCESSPOINT_ID>
```

```console
$ helmfile apply -f helmfile.yaml
```


## 3. リストア

Minio Podが立ち上がったら、Task Runner Podでリストア実行します。
バックアップファイルはS3バケット上に存在しているので以下のコマンドで事前に取得する必要があります。

```console
$ aws s3 cp s3://test-gitlab-backups/<BACKUP_FILE_TAR>
$ kubectl cp <BACKUP_FILE_TAR> <TASK_RUNNER_POD>:/tmp
$ kubectl exec <TASK_RUNNER_POD> -it -- backup-utility --restore -f file:///tmp/<BACKUP_FILE_TAR>
```

# まとめ

GitLabで使用するオブジェクトストレージをS3からMinioに変更する手順を説明しました。
MinioでEFSを使用するための設定もしていますが、それほど複雑ではないことが分かると思います。

S3と比べてレスポンス速度は変わりますが、可用性の観点では劣るので要件に応じて方式を考える必要があります。