はじめに
エキサイト株式会社 バックエンドエンジニアの山縣(@zsp2088dev)です。 エキサイトホールディングス - Qiita Advent Calendar 2024 - Qiita の18日目を担当します。
エキサイトブログの開発では、新機能の開発や既存機能の改修時に、データベースに新たなテーブルを作成し既存のデータを移行することがあります。 データ移行を何度か経験していく中で、より快適なデータ移行環境を目指し、AWS Copilot CLIのScheduled JobとSpring Shellを組み合わせたデータ移行環境を構築しました。 本記事では、このような環境を用意した背景や、実際に取り組んでいることについて紹介します。
背景
一般に、あるテーブルから別のテーブルへデータを移行する場合、複雑な加工処理をしないのであればINSERT... SELECTで済むことが多いです。 一方で、スキーマ間のデータ移行や複雑な加工処理を必要とするデータ移行では、アプリケーション内でデータの加工をしたほうが柔軟性が高くなります。 例えば、データの正規化や結合、不要なデータのフィルタリングなどを必要とする場合、SQLだけでは記述が複雑になりがちです。 また、アプリケーションコードを書くことで、テストコードを書くことができたり、ログ出力ができたりなどの利点があります。
エキサイトブログの開発でも、昨年のブログテーマ機能の改修のときに以下のような取り組みをしていました。
当時は、EC2上でgit clone
してアプリケーションコードを手動実行していました。
データ移行は繰り返し行うものではなく一回限りの実行であるため、煩わしさを感じつつも「頻度が高くないからこのままでいいな」と考えていました。
しかし、今後も同様の操作をすることを考えたときに、より快適なデータ移行環境を目指したほうがよいと考え、新たなデータ移行環境を検討するようになりました。
環境
本記事で扱う環境は以下の通りです。 記事内では実際にデータ移行しているところまでは触れず、「データ移行を実行する環境」について触れていきます。
Spring Shellの設定
Spring Shell 3系では@Command
アノテーションを使用してコマンド登録ができます。
下記のコードに対して、./gradlew bootRun --args="hello"
と実行したときに、test1
と出力されることが確認できます。
同様に、./gradlew bootRun --args="world"
と実行したときに、test2
と出力されます。
このように、引数に応じて実行したいメソッドを切り替えることができます。
package com.example.command; import lombok.RequiredArgsConstructor; import org.springframework.shell.command.annotation.Command; @Command @RequiredArgsConstructor public class SampleCommand { @Command(command = "hello") public String hello() { return "test1" } @Command(command = "world") public String hello() { return "test2" } }
データ移行時は、本メソッド内にデータの読み込み、書き込み、加工処理を書いていました。
Spring Bootをコンテナ化する
jib
を使用して、Spring Bootで作成したアプリケーションをコンテナ化しています。
args
プロパティに対して、@Command
アノテーションに定義したcommand
の値を設定することで、コンテナ実行時に任意のメソッドを実行できるようになります。
これを利用して、./gradlew jibDockerBuild -Pargs=hello
とコンテナ化することで、コンテナ実行時にtest1
と出力するコンテナイメージを作成できます。
jib { container { if (project.hasProperty("args")) { args = [ project.property('args') ] } } }
jib
の詳細やその他オプションについては下記のドキュメントをご参照ください。
AWS Copilot CLIの設定
Amazon ECS内でコンテナ化されたアプリケーションを実行するために、AWS Copilot CLIのScheduled Job
を使用しています。
Scheduled Job
のマニフェストのscheduled
フィールドにnone
を設定することで、タスクが定期実行されないように設定できます。
on: schedule: "none"
上記の設定により、タスクは定期実行されません。
Scheduled Job
のドキュメント内には、none
を設定するのは一時的無効化したい場合と記載されているため、本来の用途とは違った使い方をしています。
copilot job run
コマンドを実行すると、任意のタイミングでタスクを実行することができます。
これにより、タスクを実行したいときだけcopilot job run
コマンドを実行すればよいです。
GitHub Actionsから実行する
上記の内容までを実装することで、ローカル環境上でcopilot job run
と実行するとタスクを実行できます。
これを、ローカル環境上にcopilot
コマンドが無くても実行できるように、GitHub Actions上で実行できるようにしていきます。
ここでは、ワークフローを実行するのにGitHub Actionsのworkflow_dispatch
イベントと、workflow_dispatch.inputs
を使用します。
以下のようにworkflow_dispatch.inputs
を定義すると、ワークフロー内で ${{ inputs.ARGS }}
として扱えます。
on: workflow_dispatch: inputs: ARGS: description: ジョブ名を入力してください required: true type: string ENV: description: デプロイ先の環境を選択してください default: test required: true type: choice options: - test - prod
これにより、下記のようにhello
と入力し、ワークフロー内でコンテナをビルド、コンテナレジストリにプッシュ、copilot job run
と実行することで、ECS上でtest1
と出力できるようになります。
おわりに
本記事では、AWS Copilot CLIのScheduled JobとSpring Shellを組み合わせた一回限りのタスクを実行する方法について紹介しました。 サンプルでは標準出力をしただけですが、我々の開発ではデータベースからデータを読み取り、アプリケーション内で加工、データを書き込みといった操作をしています。 一度環境を整備してしまえば、いつでもすぐに利用できるのが非常に嬉しいです。
一方で、本記事では事業部内で知見のあるAWS Copilot CLIを使用していますが、以下のissueの通り、開発体制が不安定な状況にあるようです。 もし本記事と同様のことを実現したい場合は、以下の内容についてご留意いただけますと幸いです。
採用アナウンス
エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しています。 また、長期インターンも歓迎しています。
カジュアル面談からもOKです。少しでもご興味がございましたら、お気軽にご連絡頂ければ幸いです。
▼ 募集職種一覧 ▼ recruit.jobcan.jp