RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

AtlasとArgoCDでDBマイグレーションの仕組みを構築してみた

はじめに

こんにちは!ラクスでSREをしているモリモト(2025/3に中途入社)です。

業務の中で、AtlasとArgoCDを使ってGoアプリケーションのDBマイグレーションの仕組みを新規に構築したので、その方法を書き残してみたいと思います。

構築したフロー

今回構築した全体のマイグレーションフローは、以下のような流れになっています。

  1. ローカル環境でスキーマ定義を編集し、差分からマイグレーションファイルを生成
  2. 生成したマイグレーションファイルをアプリ用のGitHubリポジトリへプッシュ
  3. GitHub Actionsでマイグレーション用のDockerイメージをビルドし、Container Registryにプッシュ(タグにはコミットハッシュおよびlatestを付与)
  4. k8sマニフェスト用のGitHubリポジトリでPRをdevelopブランチにマージすると、ArgoCDのPreSync HookによりマイグレーションJobが起動(latestタグのイメージを使用)し、検証環境に対してマイグレーションを自動実行
  5. 同様に、PRをmainブランチにマージすると、ArgoCDのPreSync HookによりマイグレーションJobが起動(特定のタグのイメージを使用)し、本番環境に対してマイグレーションを自動実行

実現したかったこと

実現したかったことは、以下の3つです。

1. 宣言的なスキーマファイルを管理できる

マイグレーションファイルが多くなると、最終的なDB構造が見えにくくなることがあるかと思います。宣言的なスキーマファイルを管理することで、現在の理想的なDB構造を一目で確認できる状態を作りたいです。

2. 宣言的なスキーマファイルからマイグレーションファイルを生成でき、それをバージョン管理できる

差分を自動的に検出し、マイグレーションファイルを自動で生成したいです。 また、生成されたSQLファイルはGitで管理することで変更履歴を容易に管理したいです。

3. 検証環境や本番環境に対して、自動でDBマイグレーションを実行できる

手動で実行することなくデプロイ処理に組み込むことで、「マイグレーション漏れ」や「コマンド実行ミス」によるリリース失敗のリスクを減らしたいです。

アーキテクチャ検討

前提として、私がアプリケーションの技術スタックは以下となります。

技術スタック
Backend Go
DB Postgres(CloudNativePG)
Infra Kubernetes
CI/CD GitHub Actions, ArgoCD

まずは、Goで使用できるDBマイグレーションツールの選定です。

ただし、この件についてはチーム内の別システムで、DBマイグレーションに「Atlas」というツールを採用する方針がすでに決まっていました。 少し調べたところ、実現したい内容を満たせそうだったことに加え、チーム内での横展開も見込めるため、Atlasを使うことにしました。

次に、検証環境や本番環境に対してどうやってDBマイグレーションを自動実行するかの検討を行いました。

ArgoCDにはResource Hookという仕組みがあり、同期処理(Sync)の前後などに処理をはさむことが容易にできます。

そのため、ArgoCDのResource Hookの一つであるPreSyncを活用して、マニフェスト適用の直前でDBマイグレーションを自動実行できる仕組みを構築することにしました。

以下では、この仕組みをどのように実現したかを具体的に説明していきます。

Atlasの導入とスキーマ管理の仕組み

まずはAtlasを導入し、宣言的なスキーマ管理ができる環境を整備していきます。

Atlasは以下のようなディレクトリ構成で利用しています。

/db
  ├── schema.sql         // 宣言的に定義したDBスキーマ
  ├── atlas.hcl          // atlasの設定ファイル
  └── /migrations        // マイグレーション時に実行するSQLファイルが格納されるディレクトリ
      └── 20250513060616_create_blog_posts.sql
       ・
       ・
/app
   ├──main.go
    ・
    ・

スキーマの定義

schema.sql ファイルでは、現在の理想的なDBの状態をsqlファイルで定義します。(init.sqlみたいな感じです)

-- db/schema.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

atlasの設定ファイル(atlas.hcl

atlas.hcl は、Atlasの実行設定を定義するためのファイルです。

マイグレーション対象のデータベースやスキーマファイルの場所、マイグレーションディレクトリのパスなどを記述します。

このファイルを作成しておくことで、コマンドラインでの指定を省略したり、atlas migrate apply などのコマンドで共通設定を使い回すことができます。

atlas.hclのサンプル
# db/atlas.hcl

variable "db_user" {
    type    = string
    default = getenv("DB_USER")
}
variable "db_password" {
    type    = string
    default = getenv("DB_PASSWORD")
}
variable "db_host" {
    type    = string
    default = getenv("DB_HOST")
}
variable "db_port" {
    type    = string
    default = getenv("DB_PORT")
}
variable "db_name" {
    type    = string
    default = getenv("DB_NAME")
}
variable "db_sslmode" {
    type    = string
    default = getenv("DB_SSLMODE")
}

locals {
    pg_url = "postgres://${var.db_user}:${var.db_password}@${var.db_host}:${var.db_port}/${var.db_name}?sslmode=${var.db_sslmode}"
}

# local起動用
env "local" {
    url = local.pg_url
    src = "file://db/schema.sql"
    dev = "docker://postgres/16/dev"

    migration {
        dir     = "file://db/migrations"
        exclude = [
            # river
            "public.river_*",
        ]
    }
}

# localのDockerで起動用
env "local-docker" {
    url = local.pg_url

    migration {
        dir     = "file://migrations"
        exclude = [
            # river
            "public.river_*",
        ]
    }
}

# stg,prd用
env "prod" {
    url = local.pg_url

    migration {
        dir     = "file://migrations"
        exclude = [
            # river
            "public.river_*",
        ]
    }
}
env内の主な設定項目
項目 説明
url 実際にマイグレーションを適用するDBの接続URLを指定します。
dev 比較・検証用の一時的な開発DBを指定します(例: Docker上のPostgreSQL)。差分検出時に使用されます。
migration.dir マイグレーションファイルの保存ディレクトリを指定します。
migration.exclude マイグレーションの対象外としたいスキーマ、テーブルを指定できます。

※ Terraformのようにvariableで変数を作成することもでき、今回はDBの設定情報を環境変数から取得してきてvariableに格納するようにしています。

マイグレーションファイルの生成

Atlasでは、宣言的なスキーマファイルと、現状のDB状態を比較して自動的にマイグレーションファイル(SQL)を生成することができます。

atlas migrate diff add_user_schema --env "local" --config "file://db/atlas.hcl" --to "file://db/schema.sql"

# 以下のようにenvを利用せずに直接オプションを指定しても実行可能
atlas migrate diff --to "file://db/schema.sql" --dev-url "docker://postgres/16/dev" --dir "file://db/migrations"

このコマンドにより、atlas/migrations ディレクトリに新しいマイグレーションSQLが自動生成されます。 これをGitで管理することで、スキーマ変更の履歴も明確に追跡できます。

コマンド実行時に --env local のように指定することで、atlas.hclで定義した設定を利用できます。

atlas migrate diffコマンドの各オプション説明
オプション 説明
--to "file://db/schema.sql" 宣言的に定義された理想的なスキーマを指定します。
--dev-url "docker://postgres/16/dev" DBマイグレーション用の一時データベースのURLです。
Atlasがスキーマ比較を行う際にこのDB上に現在のスキーマを一時的に構築します。
--dir "file://db/migrations" 差分から生成されたマイグレーションファイルを出力するディレクトリを指定します。
ここに .sql ファイルが生成され、順序付きのマイグレーション履歴として管理されます。

※ --dev-url には、ローカルのPostgreSQLや他の接続先を指定することも可能です。

マイグレーションのローカルDBへの適用

生成されたマイグレーションファイルは、atlas migrate applyコマンドでデータベースに適用することができます。

atlas migrate apply --env "local" --config "file://db/atlas.hcl"

このコマンドにより、db/migrations にあるマイグレーションファイルが順番に実行され、指定したデータベースに反映されます。

マイグレーション用Dockerイメージを作成する

ArgoCDのジョブからマイグレーションを実行するために、Dockerイメージを作成します。

Dockerfileを作成

# ./Dockerfile

# local用
FROM arigaio/atlas:0.33.0-distroless AS migration-local
WORKDIR /app
COPY ./db/migrations/ /app/migrations
COPY ./db/atlas.hcl /app/atlas.hcl
CMD ["migrate", "apply", "--env", "local-docker", "--config", "file://atlas.hcl"]

# prod用
FROM arigaio/atlas:0.33.0-distroless AS migration-prod
WORKDIR /app
COPY ./db/migrations/ /app/migrations
COPY ./db/atlas.hcl /app/atlas.hcl
CMD ["migrate", "apply", "--env", "prod", "--config", "file://atlas.hcl", "--baseline", "20250513060616"]

prod用のみ --baseline オプションをつけているのは、prod環境にすでに初期データベースが存在していたためです。 通常、Atlasはマイグレーション履歴テーブル(atlas_schema_revisions)が存在しないと、全てのマイグレーションファイルを最初から適用しようとします。 しかし、prodでは過去のマイグレーションはすでに手動などで適用済みであるため、適用済みと見なすマイグレーションファイルのバージョン(この例では 20250513060616)を --baseline に指定することで、それ以前のファイルはスキップされます。

このようにすることで、既存DBの破壊を避けつつ、マイグレーションをAtlasで管理できる状態に移行できます。

Github Actionsワークフローを作成

GitHub Actions でマイグレーション用Dockerイメージをビルド&プッシュするためのワークフローを作成します。

私は社内に展開されている独自のワークフローを流用したため、ここにコードを貼り付けることはできないです。。すみません。

ただ実装は単純で、マイグレーションファイルが更新されたら、push or PRマージ のタイミングで、Dockerfileをビルドして自身のコンテナレジストリgithub.shaとlatestの2つのタグをpushしています。

以下は例です!(chatgptに適当に書かせたやつなので、動かなかったらすみません。。)

# .github/workflows/build-push-migration.yaml
name: Build and push Migration

on:
  push:
    branches:
      - main
    paths:
      - db/migrations/**
      - .github/workflows/build-push-migration.yaml

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Login to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push migration image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: db/Dockerfile
          push: true
          tags: |
            yourorg/db-migration:latest
            yourorg/db-migration:${{ github.sha }}

ArgoCDのPreSyncでマイグレーションを実行する

ArgoCDを使ってマイグレーションを自動実行する仕組みを構築します。

ここでは Job リソースに argocd.argoproj.io/hook: PreSync アノテーションを付与することで、 アプリケーションのマニフェストが適用される前にDBマイグレーションを行うようにしています。

マイグレーション用Jobの例

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration-job
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: "-1"
spec:
  backoffLimit: 0
  completions: 1
  parallelism: 1
  template:
    spec:
      restartPolicy: Never
      imagePullSecrets:
        - name: my-registry-secret
      containers:
        - name: db-migration-job
          # 検証環境はlatest、本番環境はコミットハッシュで特定のイメージタグを指定
          image: "yourorg/db-migration:latest"
          imagePullPolicy: Always
          envFrom:
            - secretRef:
                name: db-secret
ArgoCD Hookアノテーションの解説

以下のアノテーションを使うことで、ArgoCDの同期処理に対してマイグレーションJobの実行タイミングや順序、削除方針を制御できます。

アノテーション 役割 説明
argocd.argoproj.io/hook: PreSync 実行タイミング指定 同期処理の にこのリソースを実行します。DBマイグレーションやバリデーションなどに最適です。
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation 削除ポリシー 次の同期時にリソースを再作成できるよう、実行前に削除します。
argocd.argoproj.io/sync-wave: "-1" 実行順序制御 同じタイミング(ここではPreSync)に複数リソースがある場合、小さい値から順に実行されます。

今回、job実行のためのDB接続情報はSecretリソースに格納されているため、 SecretもPreSyncにする必要があります。

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: "-2"
type: Opaque
stringData:
  DB_USER: your_user
  DB_PASSWORD: your_password
  DB_HOST: your_host
  DB_PORT: "5432"
  DB_NAME: your_db
  DB_SSLMODE: disable

※ sync-wave: "-2" とすることで、マイグレーションJob(sync-wave: "-1")より先にSecretが適用されます。

これらのリソースをマニフェストに追加することで、マニフェスト同期の前にDBマイグレーションが自動実行されるようになります。

さいごに

今回、AtlasとArgoCDを使った自動マイグレーションの仕組みを構築してみて、大部分の作業を自動化できました!

同じようなことをしたい方の参考になれば幸いです。

最後まで読んでいただきありがとうございました!

参考リンク

qiita.com tech.gunosy.io zenn.dev techblog.tver.co.jp speakerdeck.com

Copyright © RAKUS Co., Ltd. All rights reserved.