type holyshared = Engineer<mixed>

技術的なことなど色々

2025年の靴の着用回数を集計した

毎年恒例の履いた靴の集計をしてみました。
今年は夏場暑くて、ジョギングができなかったので、BROOCKSのGhost14やASICSのGEL KAYANO 31の着用頻度が下がってますね。

またSOLOVAIRの靴が2足増えてるのと、Grensonも2足増えてます。
Urban Hikerは旅行の時に便利だったのでこれからも履くと思います。

以前から履いている靴

No. モデル ブランド 2025年までの着用回数 2025年の着用回数
1 GEL KAYANO 31 ASICS 25 22
2 MORPHIC BASE PUMA 30 24
3 WK400 KEEN 47 22
4 Skipton Zip Boot George Cox 61 29
5 Clearcut m trippen 105 28
6 Strike Norman Walsh 73 22
7 SX 78C02 MoonStar 97 24
8 Tanker Pro Nicks 80 22
9 SKIPTON George Cox 97 24
10 ROOTS LOOP II 8.5 Rolling dub trio 128 24
11 Classical Horsehide Mesh Sandals Dapper’s 157 31
12 BRIGHTON Loake 102 24
13 Ghost14 BROOCKS 142 19
14 Overstone Hi Derby Crown Northampton 131 24
15 Neon Blue 8 Eye Derby Boot SOLOVAIR 106 22
16 6Eye Astronaut Boot SOLOVAIR 112 23
17 飛鳥ホールカット KOTOKA 135 25
18 LAW NPS shoes 126 24
19 Harry BROTHER BRIDGE 149 25
20 6inch Boots Reverse Black Odessa Chippewa 130 22
21 BOYS KATSUYA TOKUNAGA 125 22
22 GARRISON TRAIL GORE-TEX HIKING SHOES Timberland 152 23
23 M-43 SERVICE SHOES JOHN LOFGREN 133 21
24 BERLIN BROTHER BRIDGE 152 25
25 CONISTON Crockett&Jones 145 22
26 Morgan BROTHER BRIDGE 143 23
27 Custom Jobmaster 38LTT Wesco 140 21
28 442 MILITARY CAP TOE OXFORD RAMSEY 179 24
29 DOBULE MONK SANDAL Tokyo Sandals 598 73
30 ROOTS Rolling dub trio 197 24
31 Irish Setter Redwing 182 24
32 6inch Service Boots Chippewa 165 22
33 McCLOUD BROTHER BRIDGE 171 23
34 JAMES BROTHER BRIDGE 181 24

新しく購入した靴

No. モデル ブランド 2025年の着用回数
1 Navy Gaucho Urban Hiker SOLOVAIR 11
2 Light Green 8 Eye Derby Boot SOLOVAIR 14
3 Button up shoes plain toe forme 17
4 JED Grenson 24
5 ABEL Grenson 24

手放した靴

No. モデル ブランド 2025年までの着用回数 2025年の着用回数
1 TimberLoop EK Utility Boot Timberland 67 8
2 Solar Wave Leather/Fabric Mid Hiker Boots Timberland 114 9

アプリケーションをS3からGCSへ移行する

自分で運用しているアプリケーションのストレージサービスをS3に完全に移行しました。
S3を利用していたのはアプリケーションがもともとHerokuで運用していたからです。

アプリケーション自体は今はGoogle Cloud Platform上に構築していますが、もともとHerokuで運用してしていました。
なのでストレージサービスはGCSではなく、S3になっていました。

これをさすがGCSに移行することにしました。
移行の理由としては、完全にアプリケーションをGoogle Cloud Platform上に移行したかったからと、請求を一本化したかったからです。

移行方法の検討と実施

S3とGCSにファイルを出力しながら段階的に移行していこうかと考えたのですが、めんどくさかったので下記の手順で実施しました。

  1. アプリケーションのS3の処理をGCSに置き換える。
  2. Terraformで移行先のGCSバケット作成、CORSの設定を行う。
  3. StorageTransferServiceを利用して、S3のバケットのオブジェクトをGCSに転送する。
  4. アプリケーションをデプロイする。
  5. 正常に稼働しているかを確認する。

S3とGCSへのオブジェクト転送

StorageTransferServiceを利用したS3からGCSへ転送ですが、下記の方法で実行しました。 docs.cloud.google.com

  1. SecretManagerに転送用のシークレットを作成する

     {
       "accessKeyId": "AWS_ACCESS_KEY_ID",
       "secretAccessKey": "AWS_SECRET_ACCESS_KEY"
     }
    
  2. 転送元をAmazon S3のバケットにする、ここで作成したシークレットのアクセスキーを利用するようにする

  3. 転送先のGCSのバケットを選ぶ
  4. ネットワークルーティングオプションでマネージドプライベートネットワークを選択する

    ネットワークルーティングオプション

  5. ジョブを実行する

これで転送ジョブが開始されます。

転送結果

6,948ファイル、20GB近くのバケットから転送しましたが、一回では全て転送できませんでした。
2回手動で再実行しました。

今回は一回でやろうとしたので、手動で実行しましたが、本来は定期的に実行するようスケジュールリングして、転送するのだと思います。

ジョブの結果

結果は10分くらいで転送できました。

開始: 2025年11月29日 11:38:34
終了: 2025年11月29日 11:47:45

思いのほか短時間で終わってよかったです。

余談ですが、初回だけ権限エラーが発生してました。
project-[プロジェクト番号]で始まるサービスアカウントなので、GCP側で自動で作成されるサービスアカウントです。

project-[プロジェクト番号]@storage-transfer-service.iam.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist).

2回目以降はエラーが発生してないので、問題なと思います。

長期的に転送ジョブを利用する場合は、権限の管理を細かくできるサービスアカウントを自前で用意して、ジョブの設定で「ユーザー管理のサービス エージェント」を選択して割り当てる方がいいと思います。

docs.cloud.google.com

TypeORMでCloud SQLにIAMユーザーで接続する

Cloud SQLで組み込みの認証を利用せず、IAMベースの認証でDBに接続できるかやってみました。
IAMベースの認証にすると、パスワードが不要になり、SSL/TLSで接続できるようになります。

今回は下記の条件で試しました。

  • プリンシパルではなく、サービスアカウントを利用したIAM認証
  • Cloud SQL
    • PostgreSQL v18
    • SSL接続のみを許可する(信頼できるクライアント証明書を必須にするが本来はいい)
    • パブリック IP接続
    • IAM認証を有効にする
  • Cloud Run
    • Nodejsアプリケーション

手順としては、DBインスタンスを用意して、接続できるサービスアカウントを用意し、アプリケーションをIAM認証で接続する様に変更する感じです。

Cloud SQLのインスタンスを起動する

ssl_modeをENCRYPTED_ONLYにするのとcloudsql.iam_authenticationフラグを有効にして、インスタンスを起動する。
「SSL 接続のみを許可する」になっているのと、データベースのフラグとパラメータのcloudsql.iam_authenticationがonになっているのを確認する。

resource "google_sql_database_instance" "app_db" {
  project = var.project_id

  name             = "app-db"
  database_version = "POSTGRES_18"
  region           = var.region

  settings {
    tier = "db-f1-micro"

    backup_configuration {
      enabled = true
    }
    // サービスアカウントで接続できるようにフラグを有効にする
    database_flags {
      name  = "cloudsql.iam_authentication"
      value = "on"
    }
    ip_configuration {
      // SSL/TLSで接続するが、クライアント証明書は検証しない
      ssl_mode = "ENCRYPTED_ONLY"
    }

    deletion_protection_enabled = true
  }

  deletion_protection = true
}

resource "google_sql_database" "database" {
  project = var.project_id

  name     = "app-db"
  instance = google_sql_database_instance.app_db.name
}

Cloud SQLの接続の為にサービスアカウントを作成する

DBに接続するサービスアカウントを用意する。
今回はCloud RunからCloud SQLに接続するので、Cloud Runで指定するサービスアカウントに対して、ロールとDBユーザーを用意する。

// サービスアカウント
resource "google_service_account" "app" {
  project  = var.project_id
  account_id = "app-server"
}

// DB接続に必要なロールを指定する
// 必要に応じてそのほかのロールを追加する
resource "google_project_iam_member" "client" {
  project = var.project_id
  role    = "roles/cloudsql.client"
  member  = "serviceAccount:${google_service_account.app.email}"
}

resource "google_project_iam_member" "instance_user" {
  project = var.project_id
  role    = "roles/cloudsql.instanceUser"
  member  = "serviceAccount:${google_service_account.app.email}"
}

// DBユーザーをサービスアカウントで追加する
resource "google_sql_user" "app_sql_user" {
  project  = var.project_id
  name     = trimsuffix(google_service_account.app.email, ".gserviceaccount.com")
  instance = google_sql_database.database.name
  type     = "CLOUD_IAM_SERVICE_ACCOUNT"
}

サービスアカウントで接続できるかを確認する

サービスアカウントの権限借用を使用するプリンシパルにロールのroles/iam.serviceAccountTokenCreatorを割り当てる。
そいうしないと、権限借用する際のアクセストークンを取得できない。

gcloud auth application-default login \
  --project [PROJECT_NAME] \
  --impersonate-service-account [SERVICE_ACCOUNT_EMAIL]

次はプロキシーを起動する。

cloud-sql-proxy --auto-iam-authn \
  --address 127.0.0.1 \
  --port 5432 \
  [CONNECTION STRING]

プロキシーを起動したらサービスアカウントで接続してみる。
接続できれば問題ないが、権限が何も割り当てられていないので、テーブルのデータをSELECTしたりすることができない。
なので、必要な権限を別途GRANTで割り当てる。

psql -h 127.0.0.1 -p 5432 -U [サービスアカウント(.iamまで)] app-db

アプリケーションのDBの接続方法を変える

言語コネクタ@google-cloud/cloud-sql-connectorが提供されているので、それを利用する。
TypeORMでの接続方法は@google-cloud/cloud-sql-connectorのexamplesにある。
prismaやsequelizeのもあるので、それぞれ参考にするといいかもしれない。

import "reflect-metadata";
import { PostgresConnectionOptions } from "typeorm/driver/postgres/PostgresConnectionOptions";
import { DataSource } from "typeorm";
import {
  Connector,
  AuthTypes,
  IpAddressTypes,
} from "@google-cloud/cloud-sql-connector";

export const AppDataSource = new DataSource({ type: "postgres" });

export const connect = async () => {
  const connector = new Connector();
  const clientOpts = await connector.getOptions({
    instanceConnectionName: process.env.INSTANCE_CONNECTION_NAME,
    ipType: IpAddressTypes.PUBLIC,
    authType: AuthTypes.IAM,
  });
  const datasourceOptions: PostgresConnectionOptions = {
    type: "postgres",
    database: process.env.DB_NAME,
    entities: [],
     synchronize: false,
    logging: true,
    migrations: [],
    username: process.env.IAM_DB_USERNAME,
    extra: clientOpts,
  };
  const ds = await AppDataSource.setOptions(datasourceOptions).initialize();
  return {
    async close() {
      await ds.destroy();
      connector.close();
    },
  };
};

コードの変更が完了したら、Cloud Runで環境変数を指定する。
ユーザー名、接続名、データベース名などはSecretManagerで管理して、環境変数はSecretManagerから取得するようにすると良い。

Cloud Runをデプロイして、接続できていれば完了です。

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も追加しないと期待通りになりませんでした。