🚢

GAE スタンダード環境からCloud Runへの移行

2023/12/28に公開

こんにちは、テラーノベルでサーバーサイドを担当している@manikaです。
GAEスタンダード環境からCloud Runへ移行についての備忘録を記事にしました。
GAEからCloud Runへ移行が完了すると、試算ですが現状の4分の1程度にインスタンス料金を抑えれる見込みの為それならばやるしかないな、という事で進めています。

概要

基本的にはGAEに依存した機能やパッケージを削除又は変更していくという作業になりますが、Cloud Runへ移行するまでの間GAEでも動作させる必要があり、コードの2重管理は避けたかったのでGAEでもCloud Runでも同じコードで動くよう改修を行なっていく必要もありました。いくつかピックアップして注意点等を記載します。

GAE依存の環境変数の廃止

GAEの環境変数を使用している箇所をCloud Runの環境変数へ置き換えていきました。
https://cloud.google.com/appengine/migration-center/run/compare-gae-with-run?hl=ja#default_environment_variables

また、一部の情報は環境変数としては定義されておらず、メタデータサーバーから取得する必要があった為そちらから取得をするようにしました。

例えばGAEであればサービスアカウントも appengine.ServiceAccount(ctx) で取得できますが、メタデータサーバーから取得するには以下のようにする必要がありました。

package main

import (
	"cloud.google.com/go/compute/metadata"
	"github.com/pkg/errors"
)

func GetServiceAccountEmail() (string, error) {
	email, err := metadata.Email("")
	if err != nil {
		return "", err
	}
	return email, nil
}

https://cloud.google.com/compute/docs/metadata/predefined-metadata-keys?hl=ja

appengine/v2/log -> log/slog に変更

ログにはappengine/v2/logを使用していましたが、log/slogに変更しました。
https://pkg.go.dev/log/slog

ただそのまま slog へ置き換えてしますと通常のログとしては問題ないのですがCloud Loggingを使用していると構造が異なるのでCloud Logging上でグルーピングされない等の問題がありました。その為以下2つの対応を行いました。

  • 自動で付与してくれていたトレースIDが付与されないので追加する
  • フィールド名がCloud Loggingと異なるのでCloud Loggingに合わせる

https://cloud.google.com/logging/docs/structured-logging?hl=ja#structured_logging_special_fields

logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
	ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr {
		// Cloud LoggingのLogEntryの構造に合わせるためseverityとmessageに変換する
		switch attr.Key {
		case slog.LevelKey:
			attr = slog.Attr{
				Key:   "severity",
				Value: attr.Value,
			}
		case slog.MessageKey:
			attr = slog.Attr{
				Key:   "message",
				Value: attr.Value,
			}
		}
		return attr
	},
}))
slog.SetDefault(logger)

参考:https://zenn.dev/hytkgami/articles/cloud-logging-with-slog

appengine/v2/taskqueue -> google.cloud.tasksに変更

https://cloud.google.com/tasks/docs/reference/rpc/google.cloud.tasks.v2

Cloud Tasksへのタスクを登録するタイプがこれまでAppEngineとしていました。GAEの場合はそれで問題ないのですが、AppEngineのままではCloud Runに移行した際、タスクからCloud Runにアクセスが出来ない為タイプをHTTPに変更する必要がありました。

また、以下のエミュレーターを使用する事でローカル環境での確認が楽になりました。
https://github.com/aertje/cloud-tasks-emulator

Memcache -> Memorystore for Redisに変更

GAEのMemcacheを使用していましたが、それをMemorystore for Redisに変更しました。
Redisのインスタンスを作成したのみではGAEからRedisへ接続が出来ず、サーバーレスVPCアクセスコネクタ経由で接続する必要があります。

https://cloud.google.com/appengine/docs/standard/go/connecting-vpc?hl=ja#console

GAE -> Memorystoreへの接続

  1. VPCの作成
  2. サブネットの作成
  3. サーバーレスVPCアクセスコネクタの作成
  4. Redisのインスタンス作成時に(1)で作成したVPCネットワークを選択する
  5. app.yamlに vpc_access_connector を追加する。
app.yaml
vpc_access_connector:
  name: projects/<PROJECT_ID>/locations/<REGION>/connectors/<(3)で作成したコネクター名>

上記でデプロイを行いGAEから接続可能となりました。

Cloud Run -> Memorystoreへの接続

Cloud Runから接続するには、GAEと同じくサーバーレスVPCアクセスコネクタを使用しても良いのですが、ダイレクトVPCを使用する方法にしました。
サーバーレスVPCアクセスコネクタを作成するとVMインスタンスの費用がかかりますがダイレクトVPCにはその費用がないのでコスト削減にもなります。

https://cloud.google.com/vpc/docs/configure-serverless-vpc-access?hl=ja#configure-environment-direct-vpc-egress

  1. VPCの作成
  2. サブネットの作成
  3. service.yamlに network-interfacesvpc-access-egress を追加する。
service.yaml
spec:
  template:
    metadata:
      annotations:
        run.googleapis.com/network-interfaces: '[{"network":"(1)で作成したVPC名","subnetwork":"(2)で作成したサブネット名"}]'
        run.googleapis.com/vpc-access-egress: private-ranges-only

上記でデプロイを行いCloud Runインスタンスから接続可能となりました。

作成したVPC削除時の注意点

検証等で作成したVPCを削除しようとすると以下のようなエラーで削除出来ない状態に遭遇しました。

[delete] failed with message "The network resource 'projects/myproject/global/networks/my-vpc' is already being used by 'projects/myproject/global/networkInstances/v-1910956999-fb20ccc54-83x2-46a2-abb0-XXXXXXXXXXX'"

原因は消そうとしているVPCを使用しているリソースが存在しているから。
例えばGAEのバージョンや、Cloud Runのリビジョン内にも該当のVPCの設定をしたものが残っていると削除ができません。

GAEであれば「バージョン」の一番右の「構成」の「表示」で確認。
Cloud Runであれば各サービスの「リビジョン」から各リビジョンの「YAML」を確認。

VPCを使用しているものがあればそのバージョンを削除しましょう。

https://cloud.google.com/knowledge/kb/vpc-network-deletion-fails-because-the-network-resource-is-already-being-used-by-an-instance-000004733

まとめ

GAEとCloud Runそれぞれで仕様が異なるので双方で動作させようとすると中々スムーズにいかない事もありましたが、それぞれの違いを把握する事で対応できる事が多かったです。ドキュメント大事。

テラーノベル テックブログ

Discussion