🐡

Cloud Run環境でのデータ管理 - Next.jsとCloud Storageの活用事例

2024/03/18に公開

先日リリースした、英語のYoutube動画を翻訳しながら自動的に吹き替えてくれるサービス「Youtube日本語再生」では、毎日一回、トップページに表示する動画データの更新を行っています。

https://youtube-translate.shingoirie.com/

このサービスはNext.js+FastAPIで構築され、Cloud Run上で動作しています。基本的にはNext.jsのみで構築されていますが、必要なライブラリがPythonにしかなかったため、一部でFastAPIを使用することにしました。

Youtube日本語再生」ではトップページに動画の一覧を配置しています。

YoutubeのAPIで検索して動画データを取得していますが、アクセスがあるたびにAPIを呼び出していては読み込みも遅くなり、API制限にもひっかかってしまいます。また、動画は頻繁に更新されるものではないので、1日1回更新すれば十分です。

そこで、毎日一回APIでデータを取得し、Cloud Storage上のJSONファイルに格納し、そこから読み込むようにしています。Cloud Runを使用しているため、データが永続化されないので、Cloud Storageを利用しています。(データが複雑で大量になる場合は、データベースを使用するのがより適切だと思います。)

ここでは、Next.jsをCloud Runにデプロイし、Cloud Storageを使ってデータを保存・読み込みする方法を紹介します。

注意点

Cloud Storageへのアクセス認証にはサービスアカウントキーファイルが必要です。通常は、サービスアカウントキーを作成してディレクトリに保存し、.envファイルにパスを記載して読み込めるようにすることで、Cloud Storageアクセス時に自動で認証が行われます。

ただし、今回はCloud Runを使用しており、GitHubへのプッシュ時にデプロイが行われるようになっています。この中にサービスアカウントキーを設置してプッシュすることはセキュリティ上避けるべきです。

Googleクラウドには、そのようなときに便利に使えるSecret Managerがあります。ここにサービスアカウントキーを登録しておき、Cloud Runデプロイ時にサービスアカウントがSecret Managerから情報を取得して安全に認証を行ってくれます。

以下では、この方法での手順を進めていきます。

サービスアカウントのキー追加

サービスアカウントキーを作成します。

JSONファイル形式でキーが作成され、ダウンロードされます。

Cloud Security Managerへ追加

Secret Managerで新しいシークレットを作成します。名前は任意で構いません。

シークレットの値には、先ほど作成したJSONファイルの中身を入れます。

サービスアカウントの権限追加

サービスアカウントに、Secret Managerへのアクセス権限を追加します。IAMからCloud Runにデプロイしているサービスアカウントユーザーに対して、Secret Manager閲覧者の役割を追加してください。

ローカル環境での設定

ローカル開発環境のルートディレクトリに、サービスアカウントキーファイルを配置します。(GitHubにはプッシュしないように注意してください)

次に、.env.localファイルにGOOGLE_APPLICATION_CREDENTIALS=xxxxx.jsonと設定し、認証ファイルのパスを指定します。

これで、ローカル環境でもCloud Storageへの認証が通り、アクセスできるようになります。

データの保存と読み込み

次に、APIで取得した動画データをCloud Storageに保存し、読み込む処理を実装します。以下はそのサンプルコードです。

まずライブラリをインストールします。

npm install @google-cloud/storage

データ保存のコード例:

import type { NextApiRequest, NextApiResponse } from 'next'
import { Storage } from '@google-cloud/storage';
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';

// ...

async function uploadFileToGCS(filePath: string, bucketName: string, destination: string) {
  const storage = new Storage();
  await storage.bucket(bucketName).upload(filePath, {
    destination,
  });
  console.log(`${filePath} uploaded to ${bucketName}`);
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'POST') {
    const processKeywords = async (keywords: string[]) => {
      for (const query of keywords) {
        // ...
        
        const videos = await Promise.all(data.items.map(async (item: VideoItem) => {
          // ...
          return {
            videoId: item.id.videoId,
            title: translatedTitle, 
            description: translatedDescription,
            thumbnailUrl: item.snippet.thumbnails.medium.url,
          };
        }));

        const filePath = path.join(process.cwd(), 'data', `${query}Results.json`);
        fs.writeFileSync(filePath, JSON.stringify(videos));
        const bucketName = 'バケット名';
        const destination = `${query}Results.json`; //Cloud Storageに保存するファイル名
        await uploadFileToGCS(filePath, bucketName, destination);
      }
    };

    // ...
  }
}

データ読み込みのコード例:

import { NextApiRequest, NextApiResponse } from 'next';
import { Storage } from '@google-cloud/storage';

const storage = new Storage();

async function fetchCategoryData(req: NextApiRequest, res: NextApiResponse) {
  const { category } = req.query;

  const bucketName = 'バケット名';
  const fileName = `${category}Results.json`;

  try {
    const bucket = storage.bucket(bucketName);
    const file = bucket.file(fileName);
    const [fileExists] = await file.exists();
    const [fileContents] = await file.download();
    const jsonData = JSON.parse(fileContents.toString('utf-8'));

    return res.status(200).json(jsonData);
  } catch (error) {
    console.error('データの取得中にエラーが発生しました:', error);
    return res.status(500).json({ error: '内部サーバーエラーが発生しました。' });
  }
}

export default fetchCategoryData;

これで、APIで取得した動画データがCloud Storage上に保存され、保存したJSONファイルを読み込んでフロントエンドに表示できるようになりました。

まとめ

Next.jsとCloud Runを使ったWebアプリケーションで、Cloud Storageを利用してデータを保存・読み込みする方法を紹介しました。

Cloud Runを使う場合、ステートレスなコンテナ環境なので、データを永続化するためにはCloud Storageなどの外部ストレージが必要になります。今回のようにデータ量が少なく、更新頻度が低い場合は、Cloud Storageを使うのが手軽で良い選択肢だと思います。

認証周りの設定さえしっかりしておけば、ローカルでもCloud Run上でも同じようにCloud Storageにアクセスできるので便利ですね。

最後に、このサービスでプログラミングを中心に、テック系のYoutubeコンテンツをまとめました。海外のチュートリアル動画を日本語音声で聞きながらUdemy的に学べます。ぜひ使ってみていただけたらと思います。

https://youtube-translate.shingoirie.com/

Discussion