GAE スタンダード環境からCloud Runへの移行
こんにちは、テラーノベルでサーバーサイドを担当している@manikaです。
GAEスタンダード環境からCloud Runへ移行についての備忘録を記事にしました。
GAEからCloud Runへ移行が完了すると、試算ですが現状の4分の1程度にインスタンス料金を抑えれる見込みの為それならばやるしかないな、という事で進めています。
概要
基本的にはGAEに依存した機能やパッケージを削除又は変更していくという作業になりますが、Cloud Runへ移行するまでの間GAEでも動作させる必要があり、コードの2重管理は避けたかったのでGAEでもCloud Runでも同じコードで動くよう改修を行なっていく必要もありました。いくつかピックアップして注意点等を記載します。
GAE依存の環境変数の廃止
GAEの環境変数を使用している箇所をCloud Runの環境変数へ置き換えていきました。
また、一部の情報は環境変数としては定義されておらず、メタデータサーバーから取得する必要があった為そちらから取得をするようにしました。
例えば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
}
appengine/v2/log -> log/slog に変更
ログにはappengine/v2/logを使用していましたが、log/slogに変更しました。
ただそのまま slog へ置き換えてしますと通常のログとしては問題ないのですがCloud Loggingを使用していると構造が異なるのでCloud Logging上でグルーピングされない等の問題がありました。その為以下2つの対応を行いました。
- 自動で付与してくれていたトレースIDが付与されないので追加する
- フィールド名がCloud Loggingと異なるのでCloud Loggingに合わせる
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に変更
Cloud Tasksへのタスクを登録するタイプがこれまでAppEngine
としていました。GAEの場合はそれで問題ないのですが、AppEngine
のままではCloud Runに移行した際、タスクからCloud Runにアクセスが出来ない為タイプをHTTP
に変更する必要がありました。
また、以下のエミュレーターを使用する事でローカル環境での確認が楽になりました。
Memcache -> Memorystore for Redisに変更
GAEのMemcacheを使用していましたが、それをMemorystore for Redisに変更しました。
Redisのインスタンスを作成したのみではGAEからRedisへ接続が出来ず、サーバーレスVPCアクセスコネクタ経由で接続する必要があります。
GAE -> Memorystoreへの接続
- VPCの作成
- サブネットの作成
- サーバーレスVPCアクセスコネクタの作成
- Redisのインスタンス作成時に(1)で作成したVPCネットワークを選択する
- app.yamlに
vpc_access_connector
を追加する。
vpc_access_connector:
name: projects/<PROJECT_ID>/locations/<REGION>/connectors/<(3)で作成したコネクター名>
上記でデプロイを行いGAEから接続可能となりました。
Cloud Run -> Memorystoreへの接続
Cloud Runから接続するには、GAEと同じくサーバーレスVPCアクセスコネクタを使用しても良いのですが、ダイレクトVPCを使用する方法にしました。
サーバーレスVPCアクセスコネクタを作成するとVMインスタンスの費用がかかりますがダイレクトVPCにはその費用がないのでコスト削減にもなります。
- VPCの作成
- サブネットの作成
- service.yamlに
network-interfaces
とvpc-access-egress
を追加する。
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を使用しているものがあればそのバージョンを削除しましょう。
まとめ
GAEとCloud Runそれぞれで仕様が異なるので双方で動作させようとすると中々スムーズにいかない事もありましたが、それぞれの違いを把握する事で対応できる事が多かったです。ドキュメント大事。
Discussion