EKSにデプロイしたGitLabのオブジェクトストレージをS3に変更する

EKSにデプロイしたGitLabのオブジェクトストレージをS3に変更する

Clock Icon2025.02.12

Helmを使って、EKSにGitLabを構築する機会がありました。

デフォルト設定では、オブジェクトストレージとしてminioが使われます。

minioは同一クラスター上でPodsとして動作しており、データはノードのEBS上に保存されます。

ノードの置き換え時にデータが消えてしまうことや、S3と比べて割高なため、minioをS3に置き換えます。

前提

  • EKSにHelmを使ってGitLabを構築済みであること

https://dev.classmethod.jp/articles/gitlab-eks-helm-tutorial/

S3バケットの作成

S3バケットを作成します。公式ドキュメントに以下の記載があり、それぞれバケットを分けて作成します。

A different bucket is needed for each, otherwise performing a restore from backup doesn’t function properly.

翻訳: それぞれに異なるバケットが必要です。そうしないと、バックアップからの復元が正しく機能しません。

Configure the GitLab chart with an external object storage | GitLab

作成するS3バケットの概要

今回作成するS3バケットは以下です。

S3バケット 概要
registry コンテナイメージ
lfs Git Large File Storage objects
artifact CI/CDジョブのartifacts
uploads ユーザーのアップロード(アイコン画像等)
packages プロジェクト パッケージ (PyPI、Maven、NuGet等)
backups バックアップ
tmp バックアップを復元するときに一時的に使用

サンプルのvalues.yamlを参考に最低限のS3バケットを作成しています。

以下の場合は、ドキュメントを参考に追加でS3バケットを用意してください。

  • Terraform Stateの管理をGitLabで行いたい(terraform_state)
  • マージリクエストの差分保存をDBからオブジェクトストレージを移したい(external_diffs)

Object storage | GitLab

TerraformによるS3バケットとIAMロールの作成

それぞれのバケットを作成します。

また、PodsからS3バケットにアクセスするためのIAM Roleも作成します。

Terraformコードは以下です。

main.tf
terraform {
  required_version = "~> 1.10.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.82.0"
    }
  }
}

provider "aws" {
  region = var.region
}

variable "name_prefix" {}
variable "region" {
  default = "ap-northeast-1"
}
variable "oidc_provider_arn" {}

locals {
  buckets_names = [
    "registry",
    "lfs",
    "artifacts",
    "uploads",
    "packages",
    "backups",
    "tmp"
  ]
  oidc_provider = element(split("/id/", var.oidc_provider_arn), 1)
}

data "aws_kms_key" "aws_s3" {
  key_id = "alias/aws/s3"
}

resource "aws_s3_bucket" "gitlab" {
  for_each      = toset(local.buckets_names)
  bucket        = "${var.name_prefix}-${each.key}"
  force_destroy = true
}

resource "aws_s3_bucket_versioning" "gitlab" {
  for_each = aws_s3_bucket.gitlab

  bucket = each.value.id

  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "gitlab" {
  for_each = aws_s3_bucket.gitlab

  bucket = each.value.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = data.aws_kms_key.aws_s3.arn
    }
  }
}

resource "aws_s3_bucket_public_access_block" "gitlab" {
  for_each = aws_s3_bucket.gitlab

  bucket = each.value.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

## S3 access policy for all buckets except registry and backups (covered by separate policies)
resource "aws_iam_policy" "gitlab_s3_policy" {
  name = "${var.name_prefix}-s3-policy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "s3:PutObject",
          "s3:GetObject",
          "s3:DeleteObject",
        ]
        Effect   = "Allow"
        Resource = [for k, v in aws_s3_bucket.gitlab : "${v.arn}/*" if !contains(["registry", "backups"], k)]
      },
      # Backup Utility and Geo Replication
      {
        Action = [
          "s3:ListBucket",
        ]
        Effect   = "Allow"
        Resource = [for k, v in aws_s3_bucket.gitlab : v.arn if !contains(["registry", "backups"], k)]
      },
    ]
  })
}

resource "aws_iam_policy" "gitlab_s3_backups_policy" {
  name = "${var.name_prefix}-s3-backups-policy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "s3:PutObject",
          "s3:GetObject",
          "s3:DeleteObject",
        ]
        Effect   = "Allow"
        Resource = ["${aws_s3_bucket.gitlab["backups"].arn}/*"]
      }
    ]
  })
}

## S3 access policy for registry bucket
resource "aws_iam_policy" "gitlab_s3_registry_policy" {
  name = "${var.name_prefix}-s3-registry-policy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      # Docker Registry S3 bucket requires specific permissions
      # https://docs.docker.com/registry/storage-drivers/s3/#s3-permission-scopes
      {
        Action = [
          "s3:ListBucket",
          "s3:GetBucketLocation",
          "s3:ListBucketMultipartUploads",
        ]
        Effect   = "Allow"
        Resource = aws_s3_bucket.gitlab["registry"].arn
      },
      {
        Action = [
          "s3:PutObject",
          "s3:GetObject",
          "s3:DeleteObject",
          "s3:ListMultipartUploadParts",
          "s3:AbortMultipartUpload"
        ]
        Effect   = "Allow"
        Resource = "${aws_s3_bucket.gitlab["registry"].arn}/*"
      },
    ]
  })
}

resource "aws_iam_policy" "gitlab_s3_kms_policy" {
  name = "${var.name_prefix}-s3-kms-policy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "kms:Decrypt",
          "kms:GenerateDataKey"
        ]
        Effect = "Allow"
        Condition = {
          "StringLike" = {
            "kms:EncryptionContext:aws:s3:arn" = [
              for key, values in aws_s3_bucket.gitlab : values.arn
            ]
          }
        },
        Resource = data.aws_kms_key.aws_s3.arn
      }
    ]
  })
}

resource "aws_iam_role" "gitlab_eks_global" {
  name = "${var.name_prefix}-eks-global"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action    = "sts:AssumeRoleWithWebIdentity"
        Effect    = "Allow"
        Principal = { Federated = var.oidc_provider_arn }
        Condition = {
          "StringEquals" = {
            "${local.oidc_provider}:aud" = "sts.amazonaws.com",
            "${local.oidc_provider}:sub" = "system:serviceaccount:gitlab:gitlab-global"
          }
        }
      },
    ]
  })
}

resource "aws_iam_role_policy_attachment" "gitlab_s3_policy" {
  policy_arn = aws_iam_policy.gitlab_s3_policy.arn
  role       = aws_iam_role.gitlab_eks_global.name
}
resource "aws_iam_role_policy_attachment" "gitlab_s3_backups_policy" {
  policy_arn = aws_iam_policy.gitlab_s3_backups_policy.arn
  role       = aws_iam_role.gitlab_eks_global.name
}
resource "aws_iam_role_policy_attachment" "gitlab_s3_registry_policy" {
  policy_arn = aws_iam_policy.gitlab_s3_registry_policy.arn
  role       = aws_iam_role.gitlab_eks_global.name
}
resource "aws_iam_role_policy_attachment" "gitlab_s3_kms_policy" {
  policy_arn = aws_iam_policy.gitlab_s3_kms_policy.arn
  role       = aws_iam_role.gitlab_eks_global.name
}

output "gitlab_eks_global_role" {
  value = aws_iam_role.gitlab_eks_global.arn
}

terraform.tfvarsで任意の値を渡して、リソースを作成します。

terraform.tfvars
name_prefix = "" # Example: example-gitlab-eks
region = "" # Example: us-east-1
oidc_provider_arn = "" # Example: arn:aws:iam::0123456890:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/HOGEHOGEHOGE

oidc_provider_arnは、IAMのIDプロバイダから確認できます。

AWS CLIを使う場合は、以下コマンドで取得できます。

# 1. クラスターのOIDC IDを取得
CLUSTER_ID=$(aws eks describe-cluster --name <クラスター名> \
  --query "cluster.identity.oidc.issuer" \
  --output text --region us-east-2 | cut -d'/' -f5)

# 2. OIDC IDを使ってARNをフィルタリング
aws iam list-open-id-connect-providers \
  --query "OpenIDConnectProviderList[?contains(Arn, '${CLUSTER_ID}')].Arn" \
  --output text

Terraformを実行して、リソースを作成します。

terraform init
terraform plan
terraform apply

IAM Role ARNが出力されますので、これを控えておきます。

今回は、手順を簡略化するためIAMロールを1つだけ作りました。

IAMロールやServiceAccountを複数作成することで、細かく権限管理をすることも可能です。(例: registry用のPodsはregistryのS3バケットにしかアクセスできない)

TerraformコードやIAMポリシーは以下を参考にしました。

terraform/modules/gitlab_ref_arch_aws/storage.tf · main · GitLab.org / GitLab Environment Toolkit · GitLab

ServiceAccountの作成

PodsからS3バケットにアクセスするために、ServiceAccountを用意します。

kubectl create serviceaccount gitlab-global

作成したServiceAccoutにアノテーションを追加します。

IAMロールARNは先程Terraformで作成したものを記載します。

kubectl annotate serviceaccount -n gitlab gitlab-global eks.amazonaws.com/role-arn=<IAMロールARN>

Secretsの作成

GitLabがs3cmdやRailsを使用するため、Configファイルが必要です。

helmチャート側ではConfigファイルの内容をSecretsで渡しています。

以下のConfigファイルを用意します。リージョン名やバケット名は各自の環境に合わせて、置き換えてください。

rails.yaml
provider: AWS
use_iam_profile: true
region: <リージョン名> # Example: us-east-1
s3cmd.conf
[default]
bucket_location = <リージョン名> # Example: us-east-1

registry.yamlのバケット名は、先程Terraformで作成したregistryバケットのバケット名を入れます。

registryバケットは、本記事のTerraformで作った場合は「<name_prefix>-registry」の名前で作成されます。

registry.yaml
s3:
  bucket: <バケット名> # Example: example-gitlab-eks-registry
  region:  <リージョン名> # Example: us-east-1
  v4auth: true
kubectl create secret generic gitlab-rails-storage \
    --from-file=connection=rails.yaml
kubectl create secret generic s3cmd-config \
    --from-file=config=s3cmd.conf
kubectl create secret generic registry-storage \
    --from-file=config=registry.yaml

values.yamlの準備

GitLab Helm Chartにて、S3を使うサンプルが公開されていました。

examples/values-external-objectstorage.yaml · master · GitLab.org / charts / GitLab Chart · GitLab

上記を参考に、values.yamlに以下の記述を追加します。

バケットのexample-gitlab-*の部分は環境に合わせて置き換えてください。

values.yaml(S3関連設定)
global:
  serviceAccount:
    # グローバルServiceAccount設定
    # このSAは以下のコンポーネントで使用されます:
    # - webservice
    # - sidekiq
    # - その他のGitLabコアコンポーネント
    #
    # 以下のコンポーネントは独自のSAを使用します:
    # - certmanager (gitlab-certmanager)
    # - redis (gitlab-redis)
    # - prometheus (gitlab-prometheus-server)
    enabled: true
    create: false # name指定でtrueはできないため、事前にsaを作成する
    name: gitlab-global
  minio:
    enabled: false
  registry:
    bucket: example-gitlab-eks-registry
  appConfig:
    lfs:
      bucket: example-gitlab-eks-lfs
      connection:
        secret: gitlab-rails-storage
        key: connection
    artifacts:
      bucket: example-gitlab-eks-artifacts
      connection:
        secret: gitlab-rails-storage
        key: connection
    uploads:
      bucket: example-gitlab-eks-uploads
      connection:
        secret: gitlab-rails-storage
        key: connection
    packages:
      bucket: example-gitlab-eks-packages
      connection:
        secret: gitlab-rails-storage
        key: connection
    backups:
      bucket: example-gitlab-eks-backups
      tmpBucket: example-gitlab-eks-tmp
  toolbox:
    backups:
      objectStorage:
        config:
          secret: s3cmd-config
          key: config
registry:
  enabled: true
  service:
    type: NodePort
  storage:
    secret: registry-storage
    key: config
ALB設定含むバージョン
values.yaml(ALB設定含むバージョン)
nginx-ingress:
  enabled: false
global:
  serviceAccount:
    # グローバルServiceAccount設定
    # このSAは以下のコンポーネントで使用されます:
    # - webservice
    # - sidekiq
    # - その他のGitLabコアコンポーネント
    #
    # 以下のコンポーネントは独自のSAを使用します:
    # - certmanager (gitlab-certmanager)
    # - redis (gitlab-redis)
    # - prometheus (gitlab-prometheus-server)
    enabled: true
    create: false # name指定でtrueはできないため、事前にsaを作成する
    name: gitlab-global
  hosts:
    domain: <ドメイン名>
  ingress:
    # Common annotations used by kas, registry, and webservice
    annotations:
      alb.ingress.kubernetes.io/backend-protocol: HTTP
      alb.ingress.kubernetes.io/certificate-arn: <ACM ARN>
      alb.ingress.kubernetes.io/group.name: gitlab
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      kubernetes.io/ingress.class: alb
      nginx.ingress.kubernetes.io/connection-proxy-header: "keep-alive"
    class: none
    configureCertmanager: false
    enabled: true
    path: /*
    pathType: ImplementationSpecific
    provider: aws
    tls:
      enabled: false
  minio:
    enabled: false
  registry:
    bucket: example-gitlab-eks-registry
  appConfig:
    lfs:
      bucket: example-gitlab-eks-lfs
      connection:
        secret: gitlab-rails-storage
        key: connection
    artifacts:
      bucket: example-gitlab-eks-artifacts
      connection:
        secret: gitlab-rails-storage
        key: connection
    uploads:
      bucket: example-gitlab-eks-uploads
      connection:
        secret: gitlab-rails-storage
        key: connection
    packages:
      bucket: example-gitlab-eks-packages
      connection:
        secret: gitlab-rails-storage
        key: connection
    backups:
      bucket: example-gitlab-eks-backups
      tmpBucket: example-gitlab-eks-tmp
gitlab:
  kas:
    enabled: true
    ingress:
      # Specific annotations needed for kas service to support websockets
      annotations:
        alb.ingress.kubernetes.io/healthcheck-path: /liveness
        alb.ingress.kubernetes.io/healthcheck-port: "8151"
        alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
        alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=4000,routing.http2.enabled=false
        alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=86400
        alb.ingress.kubernetes.io/target-type: ip
        kubernetes.io/tls-acme: "true"
        nginx.ingress.kubernetes.io/connection-proxy-header: "keep-alive"
        nginx.ingress.kubernetes.io/x-forwarded-prefix: "/path"
    # k8s services exposed via an ingress rule to an ELB need to be of type NodePort
    service:
      type: NodePort
  webservice:
    enabled: true
    service:
      type: NodePort
  # gitlab-shell (ssh) needs an NLB
  gitlab-shell:
    enabled: false
  toolbox:
    backups:
      objectStorage:
        config:
          secret: s3cmd-config
          key: config
registry:
  enabled: true
  service:
    type: NodePort
  storage:
    secret: registry-storage
    key: config

global.serviceAccount.createtrueにすることで、ServiceAccountをHelm側で作成することも可能です。

この場合、各コンポーネントごとにServiceAccountが作成されます。

検証用にIAM Roleの設定をシンプルにするため、1つのServiceAccountを使用することにしました。そのため、今回はfalseとしています。

また、global.serviceAccount.nameglobal.serviceAccount.createは同時に設定できません。

global.serviceAccount.createtrueにする場合は、ServiceAccount名が自動で設定されることに気をつけてください。

Do not use global.serviceAccount.create=true with global.serviceAccount.name, as it instructs the charts to create multiple ServiceAccount objects with the same name. Instead, use global.serviceAccount.create=false if specifying a global name.

Service Account |Configure charts using globals | GitLabより引用

Helmリリースの更新

用意したvalues.yamlを使って、リソースを更新します。

helm upgrade gitlab gitlab/gitlab -n gitlab -f values.yaml

動作確認

GitLabのProfileのアイコンを変更して、アイコン画像がS3にアップロードされるか確認してみます。

ログイン後、左上のアイコンからEdit profileを選択します。

Cursor_と_Projects_·_GitLab.png

Choose file...を選択して、適当なファイルを指定します。

Edit_Profile_·_User_Settings_·_GitLab-3.png

Update profile settingsでProfileを更新します。

Cursor_と_Edit_Profile_·_User_Settings_·_GitLab-5.png

uploads用のS3バケット(S3バケット名は<name-prefix>-upload)を見てみると、avatar.pngという名前で保存されていることが確認できました。

Cursor_と_sato-gitlab-eks-uploads_-_S3_バケット___S3___us-east-2-5.png

おわりに

オブジェクトストレージをデフォルトから、S3に変更してみました。

以下の改善点はありますが、S3への変更の最低限の部分はできたかと思います。

  • Pod Identityの利用
  • kubectl・helm cli部分のTerraform化

DBやキャッシュの外出しの対応を引き続き検証してみたいと思います。

以上、AWS事業本部の佐藤(@chari7311)でした。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.