はじめに
こんにちは!ラクスでSREをしているモリモト(2025/3に中途入社)です。
業務の中で、AtlasとArgoCDを使ってGoアプリケーションのDBマイグレーションの仕組みを新規に構築したので、その方法を書き残してみたいと思います。
- はじめに
- 構築したフロー
- 実現したかったこと
- アーキテクチャ検討
- Atlasの導入とスキーマ管理の仕組み
- マイグレーション用Dockerイメージを作成する
- ArgoCDのPreSyncでマイグレーションを実行する
- さいごに
- 参考リンク
構築したフロー
今回構築した全体のマイグレーションフローは、以下のような流れになっています。
- ローカル環境でスキーマ定義を編集し、差分からマイグレーションファイルを生成
- 生成したマイグレーションファイルをアプリ用のGitHubリポジトリへプッシュ
- GitHub Actionsでマイグレーション用のDockerイメージをビルドし、Container Registryにプッシュ(タグにはコミットハッシュおよびlatestを付与)
- k8sマニフェスト用のGitHubリポジトリでPRをdevelopブランチにマージすると、ArgoCDのPreSync HookによりマイグレーションJobが起動(latestタグのイメージを使用)し、検証環境に対してマイグレーションを自動実行
- 同様に、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