type holyshared = Engineer<mixed>

技術的なことなど色々

CloudSQLのPostgreSQLのバージョンアップ

PostgreSQLのバージョンをv15からv18にアップグレードした。 仕事でAWSのDBバージョンを上げる作業を見ていて、そういえば今個人で動かしているCloudSQLのバージョンなんだけっけかと思ったらv15でした。

まだサポートされているバージョンですが、使えなくなったりする前に一気にv18にしました。
作業がを開始する前にv15の最新のバックアップをオンデマンドでとっておき、terraformの構成変更を適用して、v18で起動するのを待ちました。

resource "google_sql_database_instance" "shoes_manager" {
  project = module.project.project_id

  name             = "app-db"
  database_version = "POSTGRES_18" // POSTGRES_15からPOSTGRES_18へ
  region           = var.region

  settings {
    tier = "db-f1-micro"

    backup_configuration {
      enabled = true
    }
  }

  deletion_protection = "true"
}

DBのCPU利用状況

一時的に25分くらい落ちてますが、v18へのアップグレートが完了し、正常に稼働してよかったです。

画像の配信をCloudinaryからCloudflare Workersに変えた

趣味で運用しているアプリケーションの画像配信をCloudinaryからCloudflare Workersに変更する対応を行なっていました。 Cloudinaryの無料のクレジット枠をオーバーしていたので、有料のプランを利用していましたが、画像の変換をCloudflare Workersで行えないか検証していました。

画像はS3にあるので、R2に移行してCloudflare WorkersでR2から取得した画像を変換して、キャッシュで配信しようとしましたが、CPUの実行時間がオーバーしてしまうので、そのままでは上手くいきませんでした。

有償にして、CPU時間に余裕を持たせても怪しそうだったので、既存のAPIサーバーに画像変換用の処理を追加して、Cloudflare Workersから画像変換用の処理を実行させてCPUの時間を稼ぐ方法に変更しました。

APIサーバー自体はCloud Runで動かしていて、画像の変換自体は時間がかかりますが、Cloudflare WorkersのCPU時間を1mぐらいで抑えることができました。

Cloudflare Workersのログ

一旦これで運用してみようと思います。

SippyでS3のバケットからCloudflareのR2にオンデマンド移行する

CloudflareのSippyドキュメントを見ながらが移行の設定を行います。

developers.cloudflare.com

AWS側でS3のポリシーを追加して、IAMユーザーを作成する

ポリシーは下記のものを用意して、IAMユーザーにアタッチします。
アタッチした後にアクセスキーを作成して、アクセスキーIDとシークレット アクセス キーをコピーしておきます。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket*", "s3:GetObject*"],
      "Resource": [
        "arn:aws:s3:::<BUCKET_NAME>",
        "arn:aws:s3:::<BUCKET_NAME>/*"
      ]
    }
  ]
}

S3バケットのリージョンとバケット名をコピーする

移行の設定で必要になるので、こちらもコピーしておきます。

Cloudflare R2のバケットを作成して、Sippyの設定をする

Sippyの設定

R2バケットの「オンデマンド移行」メニューを選びコピーしていた値を設定していきます。
設定が完了したら「有効にする」を選択して完了させます。

wranglerを使用して、オブジェクトがS3からコピーされるのを確認する

R2のGetコマンドをwranglerを使用して、実行します。
リソースは[R2バケット名]/[S3のキー名]形式で指定します。

wrangler r2 object get --remote [R2バケット名]/[S3のキー名]

実行が正常に終了したらR2のバッケットにコピーされているかを確認します。
無事R2にコピーされていれば、完了です。

TerraformでArtifactRegistryの世代管理を行う

google_artifact_registry_repositoryリソースを定義する時に cleanup_policies を指定することで、どのバージョンまでストレージに残すかを設定できます。

設定してないと古いバージョンが溜まっていくので、コストが上がったりします。  
アプリケーションの場合、古いバージョンのコンテナを残しておくことはメリットがないので、デプロイ時のロールバックに支障がない範囲で古いバージョンを消していくのがいいかなと思います。

下記の例では3バージョンまで残す設定例です。

resource "google_artifact_registry_repository" "app_artifact_registry" {
  project       = var.project_id
  location      = var.region
  repository_id = "app"
  description   = "application docker repository"
  format        = "DOCKER"

  cleanup_policies {
    id = "delete"
    action = "DELETE"
    condition {
      tag_state = "ANY"
    }
  }

  cleanup_policies {
    id                  = "keep-5gen"
    action              = "KEEP"
    most_recent_versions {
      keep_count        = 3
    }
  }
}

古いバージョンの削除はいつ行うなどの設定はできないみたいなのでそれだけは気をつける必要があります。

2026/01/11 追記

KEEPだけではなくて、DELETEも追加しないと期待通りになりませんでした。

Terraformのバージョンを上げた

terraformを1.12系にあげて、providerも一緒に最新まであげる作業をやっていた。    まずterraformのファイルのバージョン指定を変えて、Terraform Cloudの設定でバージョンを~> 1.12.0に変える。

Terraform Cloudの設定でバージョンを変えないと、古いTerraformで実行しようとするので必須。

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 6.47"
    }
    google-beta = {
      source  = "hashicorp/google-beta"
      version = "~> 6.47"
    }
  }
  required_version = "~> 1.12.0"
}

また、google-betaで使用していたリソースが、googleで利用できるようになっていたので、providerをコマンドで変える。

terraform state replace-provider \
  registry.terraform.io/hashicorp/google-beta \
  registry.terraform.io/hashicorp/google

これでローカルで terraform plan を実行して、差分にクリティカルなものが出なかったので、このままgit commitしてpushし、Terraform Cloudの結果を確認した。

内容に問題なかったので、Applyして実際にアプリケーションの動作確認して問題なかったので作業は完了した。

Apollo Serverをv4からv5にする

ApolloServer v5がリリースされたのでアップグレードして見ました。
expressのmiddlewareとしてして組み込んでいたのでそれの対応だけで済みました。

今回express用のmiddlewareは別のパッケージになったみたいなので別途インストールします。
expressもついでにv5にしたかったので、express v5用のパッケージをインストールします。

yarn add @apollo/server @as-integrations/express5 express

後はmiddlewareを指定しているところを新しいパッケージに置き換えます。

import { createServer } from "http";
import { expressMiddleware, type ExpressContextFunctionArgument } from "@as-integrations/express5";

const app = express();
const httpServer = createServer(app);

const graphqlServer = ApolloServer({});
await graphqlServer.start();

app.use(
  "/graphql",
  cors({
    origin: [
      "http://localhost:3000",
    ],
    credentials: true,
  }),
  express.json(),
  expressMiddleware(graphqlServer, {
    context: async (
      args: ExpressContextFunctionArgument,
    ): Promise<{}> => {
      return {}
    }
  });
);

app.listen(process.env.PORT || 3000);

これだけでバージョンアップ自体は完了でした。
パッケージの参照を変えるだけで、v5系に移行できたので対応自体はすぐに終わりました。

build-push-actionでsecretを扱う

npmのプライベートなパッケージをインストールするのに環境変数を利用している際、DockerのコンテナをNPMのアクセストークンが漏れないようにビルドする必要が出たりします。

例えば.yarnrc.ymlで環境変数を参照している場合などです。

npmAuthToken: "${NPM_AUTH_TOKEN}"

この場合、Dockerコンテナをビルドする場合、環境変数をSecretとしマウントします。

docker buildx build -f packages/api/Dockerfile \
  --secret id=npm_auth_token,env=NPM_AUTH_TOKEN \
  -t api:latest .

Dockerファイルでは--mount=type=secretを利用して参照します。
これでプライベートなパッケージもインストールでき、NPMのアクセストークンも漏れないはずです。

FROM --platform=x86_64 node:22.17.0-bullseye-slim AS builder

COPY package.json yarn.lock tsconfig.json .yarnrc.yml /app/

WORKDIR /app

# id,envはdockerコマンドで指定する。
RUN --mount=type=secret,id=npm_auth_token,env=NPM_AUTH_TOKEN yarn workspaces focus -A --production

ここまでできたらGithub Actionsでbuild-push-actionを利用して、コンテナをpushできるようになります。
秘匿情報はbuild-push-actionのsecret-envsをid=環境変数名形式で指定すれば利用できます。

- uses: docker/build-push-action@v5
  env:
    NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
  with:
    push: true
    file: "packages/api/Dockerfile"
    provenance: false
    tags: |
      ${{ secrets.REPOSITORY_URL }}:latest
    secret-envs: |
      "npm_auth_token=NPM_AUTH_TOKEN"

無事にGithub Actionsでコンテナがビルドできれば問題ありません。