#当記事について
2019/11/15にGAになったGCPのコンテナサーバレス実行環境であるGoogle Cloud Runのポートについて理解を深める記事です。
公式ドキュメントのクイックスタートのからポートに関する部分を読み解きつつ解説します。
#参考にしたサイト
#コンテナ内部で使用するポート
初めに、コンテナ内部で使用しているポートを理会するためにクイックスタートのアプリケーションを見ていきましょう。
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
log.Print("Hello world received a request.")
target := os.Getenv("TARGET")
if target == "" {
target = "World"
}
fmt.Fprintf(w, "Hello %s!\n", target)
}
func main() {
log.Print("Hello world sample started.")
http.HandleFunc("/", handler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}
##handler関数
handler関数では、環境変数"TARGET"から値を取得して、Helloの後ろにくっつけて引数のResponseWriterに出力しているのが解ります。環境変数"TARGET"がなければ"World"が入ります。
##main関数
main関数では以下の二つのことを行っているのが解ります。
- ルートでリクエストを待ち受けて、リクエストが来た場合に上のhandler関数を呼び出すようにしています。
- 環境変数"PORT"から値を取得して取得した値のポート番号でリクエストを受け付けます。環境変数"PORT"がなければ"8080"が入ります。
コンテナ内部で動くアプリケーションは環境変数"PORT"の値で動いているのが解ります。
#環境変数"PORT"の中身
Google Cloud Runの内部の環境変数"PORT"がどのようになっているのか、クックスタートにあるサンプルソースのmain関数を以下のようにしてビルド&デプロイしてみます。
func main() {
log.Print("Hello world sample started.")
http.HandleFunc("/", handler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
} else {
log.Printf("PORT is %s", port)
}
log.Printf("Hello world sample is listening on port %s.", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}
$ gcloud builds submit --tag gcr.io/[PROJECT_ID]/helloworld
(省略)
$ gcloud run deploy --image gcr.io/[PROJECT_ID]/helloworld
(省略)
Service [helloworld] revision [helloworld-XXXXXX-vof] has been deployed and is serving 100 percent of traffic at https://helloworld-XXXXXXXXX-an.a.run.app
デプロイ後、Loggingでログを確認すると次の画像のようになっていました。
Google Cloud Run上で動くコンテナの環境変数"PORT"は8080であることが解ります。
#外部から接続されるポート
外部から接続されるポートを調べるために、実際にcURLを使いアクセスしてみます。
$ curl https://helloworld-XXXXXXXXX-an.a.run.app
Hello World!
$ curl https://helloworld-XXXXXXXXX-an.a.run.app:443
Hello World!
$ curl https://helloworld-XXXXXXXXX-an.a.run.app:80
curl: (35) error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol
$ curl https://helloworld-XXXXXXXXX-an.a.run.app:8080
^C
$ curl https://helloworld-XXXXXXXXX-an.a.run.app:8081
^C
ポート番号443を使用しているのが解ります。8080や8081のように関係のないポートは応答がありませんでした。
#外部のポートと内部のポートの紐づけ
外部の443ポートとコンテナ内部の8080ポートが環境変数"PORT"経由で紐づけられているのが解ります。
図示するとこのようになります。
デプロイ時のコマンドにポートを紐づけるオプションは無いので、Dockerコマンドでいう"-p 443:8080"のようなパラメータはデプロイ時に自動で行ってくれていることになります。
#待ち受けポート実装の仕方
デプロイ時に自動で紐づけられるということは、コンテナ内部で待ち受けるポートはGoogle Cloud Runの制約として決められているということになりますね。Cloud Runの制約を読んでみると、コンテナ内部で待ち受けるポートは環境変数"PORT"に収められていると書いてあります。
Google Cloud Runで動かすアプリケーションは環境変数"PORT"を取得してその番号のポートで待ち受けるようにしましょう。
また、既に構築済みのコンテナをGoogle Cloud Runにデプロイする場合は待ち受けポートが何になっているかをきちんと確認するようにしましょう。