Go 1.11 から最新の Go ランタイムに移行する

サポートされている第 2 世代の Go ランタイムに移行すると、最新の言語機能を使用し、慣用的なコードでより移植性の高いアプリを構築できます。

第 2 世代のランタイムの変更点

サポートされている第 2 世代の Go ランタイムに移行する場合は、第 1 世代のランタイムと第 2 世代のランタイムの違いに注意してください。

  • ランタイム移行の労力と複雑さを軽減するため、App Engine スタンダード環境では、Memcache など、以前のバンドル サービスや API の多くは第 2 世代ランタイムでも利用できるようになっています。第 2 世代の Go アプリは、App Engine SDK for Go を介してバンドル サービスの API を呼び出し、Go 1.11 ランタイムとほぼ同じ機能にアクセスできます。

    以前のバンドル サービスと同様の機能を備えた Google Cloud プロダクトを使用することもできます。これらの Google Cloud プロダクトでは、慣用的な Go 用 Cloud クライアント ライブラリを使用できます。Google Cloud で独立したプロダクトとして使用できないバンドル サービス(画像処理、検索、メッセージングなど)の場合は、サードパーティ プロバイダまたはその他の回避策を使用できます。

    バンドルされていないサービスへの移行については、バンドル サービスからの移行をご覧ください。

  • app.yaml 構成ファイルの一部の要素の動作が変更されました。詳細については、app.yaml ファイルの変更点をご覧ください。

  • 第 2 世代のランタイムのロギングは、Cloud Logging のロギング基準に従います。第 2 世代のランタイムでは、アプリログがリクエストログにバンドルされなくなり、別のレコードに分離されます。第 2 世代のランタイムでのログの読み取りと書き込みの詳細については、ロギングガイドをご覧ください。

メモリ使用量の違い

第 2 世代のランタイムでは、第 1 世代のランタイムよりもメモリ使用量のベースラインが高くなります。これは、ベースイメージのバージョンの違いや、2 つの世代でのメモリ使用量の計算方法の違いなど、複数の要因によるものです。

第 2 世代のランタイムでは、アプリプロセスで使用されるものとメモリに動的にキャッシュされるアプリケーション ファイルの数の合計としてインスタンスのメモリ使用量が計算されます。メモリ使用量の多いアプリケーションで、メモリの上限を超えてインスタンスがシャットダウンしないようにするには、より多くのメモリを備えているインスタンス クラスにアップグレードします。

CPU 使用率の違い

第 2 世代のランタイムでは、インスタンスのコールド スタート時の CPU 使用率のベースラインが高くなります。アプリケーションのスケーリング構成によっては、CPU 使用率に基づいてスケーリングするようにアプリケーションを構成すると、インスタンス数が想定よりも多くなるなど、意図しない結果が生じることがあります。この問題を回避するには、アプリケーションのスケーリング構成をテストして、インスタンス数が許容範囲内であることを確認します。

リクエスト ヘッダーの違い

第 1 世代のランタイムでは、アンダースコア付きのリクエスト ヘッダー(X-Test-Foo_bar など)をアプリケーションに転送できます。第 2 世代のランタイムでは、ホスト アーキテクチャに Nginx が導入されています。この変更の結果、第 2 世代のランタイムはアンダースコア(_)付きのヘッダーを自動的に削除するように構成されています。アプリケーションでの問題を回避するため、アプリケーションのリクエスト ヘッダーにアンダースコアを使用しないでください。

app.yaml ファイルの変更点

app.yaml 構成ファイルの一部の要素の動作が変更されました。

要素 変更タイプ 説明
app_engine_apis 以前のバンドル サービスを使用しているアプリに必須 第 2 世代ランタイム用の以前のバンドル サービスにアクセスする場合は、true に設定する必要があります。
login app_engine_apistrue の場合にサポート 第 2 世代ランタイム用の以前のバンドル サービスを使用していない場合は、別の方法でユーザーを認証します。
runtime 変更 runtime 要素を変更して、第 2 世代のランタイムを指定します。

詳細については、app.yaml リファレンスをご覧ください。

main パッケージを作成する

サービスのソースファイルのうち少なくとも 1 つpackage main ステートメントを含める必要があります。また、サービスが google.golang.org/appengine パッケージを使用している場合は、appengine.Main() の呼び出しを含めます。

main パッケージを記述する

サービスに main パッケージがない場合は、package main ステートメントを追加し、main() 関数を記述します。少なくとも、次の main() 関数が必要です。

  • PORT 環境変数を読み込み、http.ListenAndServe() 関数を呼び出します。

    port := os.Getenv("PORT")
    if port == "" {
    	port = "8080"
    	log.Printf("Defaulting to port %s", port)
    }
    
    log.Printf("Listening on port %s", port)
    if err := http.ListenAndServe(":"+port, nil); err != nil {
    	log.Fatal(err)
    }

HTTP ハンドラを登録する

HTTP ハンドラを登録するには、次のいずれかの方法を選択します。

  • 推奨される方法は、すべての http.HandleFunc() 呼び出しをパッケージから main パッケージの main() 関数に手動で移動する方法です。
  • アプリケーションのパッケージを main パッケージにインポートし、各 init() 関数(http.HandleFunc() への呼び出しを含む)が起動時に実行されるようにする方法もあります。

    次の bash スクリプトを使用すると、http.HandleFunc() 呼び出しを使用するすべてのパッケージを検索して、出力を main パッケージの import ブロックにコピーできます。

    gp=$(go env GOPATH) && p=$(pwd) && pkg=${p#"$gp/src/"} && find . -name "*.go" | xargs grep "http.HandleFunc" --files-with-matches | grep -v vendor/ | grep -v '/main.go' | sed "s#\./\(.*\)/[^/]\+\.go#\t_ \"$pkg/\1\"#" | sort | uniq
    

ファイルの構造化

Go では、パッケージごとにディレクトリを作成する必要があります。プロジェクトの app.yaml ファイルで main: を使用して、main パッケージの場所について App Engine に通知できます。たとえば、アプリのファイル構造が次のようになっているとします。

myapp/
├── app.yaml
├── foo.go
├── bar.go
└── web/
    └── main.go

app.yaml ファイルは次のようになります。

main: ./web # Relative filepath to the directory containing your main package.

main フラグの詳細については、app.yaml リファレンスをご覧ください。

GOPATH へのファイルの移動

次のコマンドを使用して、GOPATH を見つけます。

go env GOPATH

関連するすべてのファイルを移動し、GOPATH にインポートします。import ./guestbook のような間接的なインポートを行う場合は、完全なパス import github.com/example/myapp/guestbook を使用するようにインポートを更新します。