RDBに保存された画像データを安全にGCSへ移行する
こんにちは。今回はRDBに保存されていたbase64形式の画像データをGCSへ移行した流れをご紹介します。私自身はWebフロントを主軸にエンジニアをしており、最近はチームの状況もあってバックエンドから幅広く対応をする事が増えました。
はじめに
私の開発するサービスはGCPを基本に設計をしておりRDBにCloud SQL、Object StorageにGCSを使用しています。また、GKEを使ったKubernetesによるコンテナ管理をしています。詳細なテーブル定義は省略しますが、従来の実装ではMessage Tableの中にbase64形式の画像データが保存されていました。
ですがサイズの大きいbase64の画像データをそのままDBで管理すると、DBへの負荷が増えパフォーマンス低下の懸念があります。
そこでDBに保存されていたbase64形式の画像データをKubernetesのJobを使用してGCSへ移行した流れをご紹介します。また、今回はRDBからGCSへの画像データ移行を主な目的とするためアプリケーションの詳細な実装は割愛します。
全体像
全体像としてはこのようなイメージです。GitHub ActionsをトリガーにしてKubernetesのJobをApplyし、Privateなネットワークの中でRDBから画像データを取得してGCSへアップロードします。
実行するスクリプトを用意する
私たちのチームはBackendにもTypeScriptを採用しておりORMにはDrizzleを使用しています。まずはGCSへ移行するためのスクリプトを用意します。
用意したスクリプトは非常に単純なもので、DBからbase64の画像データを取得してGCSへ逐次アップロードするといったものです。コードの詳細は省略して掲載します。
const migrateDBToStorage = async () => {
// ...省略
const messages = await db.select().from(MessageTable).execute()
if (messages.length > 0) {
for (const message of messages) {
const base64EncodedImageString = message.base64ImageData.replace(
/^data:image\/\w+;base64,/,
''
)
const buffer = Buffer.from(base64EncodedImageString, 'base64')
const fileType = await fileTypeFromBuffer(buffer)
if (!fileType) {
logger.error({
message: `Failed to get file type: ${message.id}`,
})
continue
}
const result = await storageClient.uploadBlobsInBatch([
{
path: `messages/${message.id}.${fileType.ext}`,
content: buffer,
},
])
if (!result.isSuccess) {
logger.error({
message: 'Failed to upload image',
stack: result.getError().stack,
})
}
}
}
// ...省略
}
Kubernetesの設定
Kubernetesの構成はkustomizeを使用して各環境ごとに設定値を調整できるようにしています。これによりdev, stg, prodの各環境に対して共通のベース設定を使用しつつ、環境ごとの差分だけを管理することができます。
.
├── base
│ ├── external-secret.yaml
│ ├── job.yaml
│ └── kustomization.yaml
└── overlays
├── dev
│ ├── config.properties
│ ├── external-secret.yaml
│ ├── job.yaml
│ ├── kustomization.yaml
│ └── serviceaccount.yaml
├── prod
└── stage
環境変数などはconfigMapで管理をしていますが、SecretはExternal Secret Operatorを使用してSecret ManagerのSecretを読み取り、KubernetesのSecretオブジェクトに保存するようにしています。
そして先ほど用意したスクリプトを実行させる設定をJobに定義し、Artifact RegistryへPushしたコンテナのimage tagを指定します。
apiVersion: batch/v1
kind: Job
metadata:
name: db-to-storage-migration
spec:
template:
spec:
containers:
- name: db-to-storage-migration
image: { Artifact Registryに登録されたimage }
command:
- "sh"
- "-c"
- "bun run /app/index.js migrate-db-to-storage"
Service Accountの管理
KubernetesのPodからCloud SQLやGCSへアクセスをするためには適切なService Accountの設定が必要になります。
今回はそれぞれに対して最低限以下の権限を与える必要がありました。
- Cloud SQL
- roles/cloudsql.client
- https://cloud.google.com/sql/docs/mysql/iam-roles?hl=ja
- GCS
- roles/storage.objectCreator
- https://cloud.google.com/storage/docs/access-control/iam-roles?hl=ja
これらのアクセス権を定義したGoogle Service Accountを用意し、Workload IdentityによってKubernetes側のService Accountと紐づけて権限を借用するようにします。
apiVersion: v1
kind: ServiceAccount
metadata:
name: db-to-storage-migration-sa
annotations:
iam.gke.io/gcp-service-account: 用意したService Account
GitHub ActionsでTriggerする
最後に設定したKubernetesのJobをGKE上にApplyします。その際にGitHub ActionsからApplyをするようにしました。その理由は以下の通りです。
- 手元からApplyすることによるオペレーションミスを防ぐ
- 既にGitHub ActionsからWorkload Identityを使用したGKEへのアクセス制御がされていたため追加の権限管理が不要
また、Workload IdentityとOIDCの認証を組み合わせることによりサービスアカウントキーを使用せずに権限を借用できます。
name: Database to Storage Migration
on:
workflow_dispatch:
env:
WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}
GHA_SERVICE_ACCOUNT: ${{ secrets.GHA_SERVICE_ACCOUNT }}
REGION: { region名 }
GCP_PROJECT: { project名 }
GKE_CLUSTER: { cluster名 }
jobs:
db-to-storage-migration:
name: Database to Storage Migration
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'
environment: Development
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.ref_name || 'development' }}
- uses: 'google-github-actions/auth@v2'
with:
WORKLOAD_IDENTITY_PROVIDER: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
SERVICE_ACCOUNT: ${{ env.GHA_SERVICE_ACCOUNT }}
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v2'
with:
version: '>= 363.0.0'
- name: Database to Storage Migration
run: |
gcloud components install gke-gcloud-auth-plugin
gcloud container clusters get-credentials $GKE_CLUSTER --region $REGION --project $GCP_PROJECT
kubectl apply -k deployment/k8s/manifests/db-to-storage-migration/overlays/dev
まとめ
最後に用意したGitHub Actionsを各環境ごとに走らせれば完了です。
この対応によってRDBで管理されていた画像データをGCSへ移行することができました。あとはアプリケーションコードを修正してGCSに対して画像を追加・参照するように変更します。
そして今回は一時的な対応だったため、用意したKubernetesのリソースやMigrationスクリプトなどは必要がなくなったタイミングで削除しました。
慣れない対応だったため手探りでの実装となりましたが、非常に学びがあるタスクとなりました。
最後に
AI Shiftではエンジニアの採用に力を入れています!
少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか?
(オンライン・19時以降の面談も可能です!)
【面談フォームはこちら】
Discussion