巨人の足元でたじlog

そうして言葉を軽んじるから――― 君は私の言葉を聞き逃す

GoをCloud Runで動かしてみる。コードとDockerfileそれぞれでデプロイ

Goをデプロイしてみます。

 

qiita.com

 

前提条件として、GCPはちょっと知っていないとだめそうですね。

 

GCPは大昔触ったことがありましたが、プロジェクトとかの分け方がよくわかってないため、そのあたりからおさらいでやっていきます。

 

qiita.com

 

これを参考にしてやってみます。

まず、クイックスタートをやってみたいと思います。

その前に、公式のドキュメントもチェックしておきます。

チュートリアル

cloud.google.com

 

チュートリアルは、なんかトピックが多すぎて、だめだ。

 

cloud.google.com

クイックスタートはなんか軽そうだし、上のQiitaの記事でも参考にしていたので、これを確認します。

cloud.google.com

ちょうどGoのやつがありました。これでいいじゃん。

「Cloud Shell」でできるみたいなので、これで進める。

1. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

プロジェクトを作成します。

作成できました。

 

2. Cloud プロジェクトに対して課金が有効になっていることを確認します。詳しくは、プロジェクトで課金が有効になっているかどうかを確認する方法をご覧ください。

大丈夫そうでした。というより、新規アカウントで作成したので、そのときに登録しました。

300$クレジットげっと!

 

3. Google Cloud CLI をインストールして初期化します。

 

3-1. gcloudをmacにinstallしてみます。

Quickstart: Install the Google Cloud CLI  |  Google Cloud CLI Documentation

 

と、これをやろうとしたのですが、brewでありそうなので、そっちでやってみます。

formulae.brew.sh

zenn.dev

brewのバージョンを確認

% brew -v
Homebrew 3.6.3
Homebrew/homebrew-core (git revision 3de6ff81a00; last commit 2022-10-01)
Homebrew/homebrew-cask (git revision 81521f14c8; last commit 2022-10-01)

 

installします。

$ brew install --cask google-cloud-sdk

完了後、以下を.zshrcに追記

source "$(brew --prefix)/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.zsh.inc"

以下実行

source ~/.zshrc

これでパスが通りましたわ。

% gcloud -v
Google Cloud SDK 405.0.0
bq 2.0.78
core 2022.09.30
gcloud-crc32c 1.0.0
gsutil 5.14

初期化も必要みたいなので、やっておきます。

$ gcloud init

ブラウザで認証画面になるので、使いたいアカウントを設定します。

使用するプロジェクトもここで選ぶように言われるので選びます。

 

なんかzennの記事だとできなかったので、普通にbrewの指示通りにやったらできました。やはり公式が正義か。。

 

Cloud Run サービスのデフォルト プロジェクトを設定するには

$ gcloud config set project PROJECT_ID
Updated property [core/project].

(gcloud initで選択済みなのでこれは必要なさそうだったが、まあいい)

 

4. サンプル アプリケーションを作成する

helloworldディレクトリを作成して、

go.modファイルを以下で作成

module github.com/GoogleCloudPlatform/golang-samples/run/helloworld

go 1.13

 

以下でも良いようだが、goのバージョンが上がっていそうなので、素直に上記ファイルを作成した。

 

$ go mod init

 

main.goを以下で作成

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
)

func main() {
    log.Print("starting server...")
    http.HandleFunc("/", handler)

    // Determin port for HTTP service.
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
        log.Printf("defaulting to port %s", port)
    }

    // Start HTTP server.
    log.Printf("listening on port %s", port)
    if err := http.ListenAndServe(":"+port, nil); err != nil {
        log.Fatal(err)
    }
}

func handler(w http.ResponseWriter, r *http.Request) {
    name := os.Getenv("NAME")
    if name == "" {
        name = "World"
    }
    fmt.Fprintf(w, "Hello %s!\n", name)
}

 

5. ソースから Cloud Run にデプロイする

main.goのあるディレクトリで以下コマンド

$ gcloud run deploy

諸々聞かれるオプションを適当に設定していく。

 

This command is equivalent to running `gcloud builds submit --pack image=[IMAGE] y` and `gcloud run deploy y --image [IMAGE]`

 

と、エラーになった。

ERROR: (gcloud.run.deploy) INVALID_ARGUMENT: could not resolve source: googleapi: Error 403: 385636020183@cloudbuild.gserviceaccount.com does not have storage.objects.get access to the Google Cloud Storage object., forbiddenさ

アカウント作ってから何もいじってないからadminユーザーとして実行しているような気がするんだがー

 

もう一回実行してみたら、行けました。

たぶん途中で待ち時間の間にオプションに変な文字が入ってしまっていて正しく入力できていなかったのかもしれません。

 

Service URL: https://helloworld-xxxxxxxxxxxx-x.a.run.app

との表示があったのでアクセスすると、Hello, Worldの文字が!

成功!?

デプロイ簡単すぎわろたぁ〜

クイックスタートはここで終わりです。

すぐに終わって物足りない感あったので、もう少し見てみます。

 

<サービスを開発する>

cloud.google.com

軽く読みました。

 

 

<全般的な開発のヒント>

cloud.google.com

 

以下自分が気になった部分の引用と、まとめです。

・バックグラウンド アクティビティ

・Cloud Run はリクエストを 10 秒以上維持しないため、コンテナの起動に 10 秒以上かかる場合は、最小インスタンス数を 1 以上に設定する必要があります。

・Cloud Run では、リクエスト間でサービスの状態が維持されるとは限りません。ただし、Cloud Run はコンテナ インスタンスを再利用してトラフィックの処理を継続するため、グローバル スコープで変数を宣言することで、その値を以降の呼び出しで再利用できます。個々のリクエストで値が再利用されるかどうかを事前に確認することはできません。

グローバル変数の扱いに注意する

・alpineかscratch等、小さいサイズのベースイメージを使用する。

 

<コンテナをビルドする>

cloud.google.com

 

Dockerfileからデプロイ

大枠はこれで良さそうですが、Dockerfileとか含め、実際に絶対に動くやつでまずは確認したいので、そういった情報を探してみます。

 

qiita.com

 

これを参考にしてやってみる。

新しいプロジェクトを作ってトライします。

`main.go`

package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello world\n")
}

func main() {
    port, _ := strconv.Atoi(os.Args[1])
    fmt.Printf("Starting server at Port %d", port)
    http.HandleFunc("/", handler)
    http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}

以下実行

go run main.go 3000

ブラウザからhttp://localhost:3000 にアクセス。

「Hello, world」が表示されました。良さそうです。

 

`Dockerfile`

FROM golang:latest as builder

ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
WORKDIR /myprojectsample
COPY . .
RUN go build main.go

# runtime image
FROM alpine
COPY --from=builder /myprojectsample /app

CMD /app/main $PORT

 

docker build -t myprojectsample .

→成功。

 

% docker run -e "PORT=3000" -p 3000:3000 -t farsidesample

http://localhost:3000 にアクセスして成功。hello, worldが表示される。

 

Container RegistryへのPush

Container RegistryよりもArtifact Registryの方が推奨されているみたいなので、使ってみるか?

一旦GCRの方でやってみて、すぐ変更できそうな気もするのであとでやる。

 

cloud.google.com

 

 

Container Registryにまずはpushしてみる。

gcloud auth configure-docker

 

push するイメージを取得する

docker pull gcr.io/google-samples/hello-app:1.0

 

イメージを Container Registry に追加する

docker tag gcr.io/google-samples/hello-app:1.0 gcr.io/PROJECT_ID/quickstart-image:tag1

 

イメージを Container Registry に push する

docker push gcr.io/PROJECT_ID/quickstart-image:tag1

 

GCPコンソールにイメージが確認できました。

Artifact Registryのクイックスタートも見てみます。

 

Artifact Registryのクイックスタート

cloud.google.com

Dockerリポジトリを作成する

gcloud artifacts repositories create quickstart-docker-repo --repository-format=docker \
--location=asia-northeast1 --description="Docker repository"

 

リポジトリが作成されたことを確認

gcloud artifacts repositories list

→OK

 

認証を構成

gcloud auth configure-docker asia-northeast1-docker.pkg.dev

 

pushするイメージを取得

docker pull us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0

 

イメージにレジストリ名をタグ付けする

docker tag us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0 \
asia-northeast1-docker.pkg.dev/PROJECT/quickstart-docker-repo/quickstart-image:tag1

 

イメージをArtifact Registryにpushする

docker push asia-northeast1-docker.pkg.dev/PROJECT/quickstart-docker-repo/quickstart-image:tag1

 

GCPコンソールでイメージがpushされていることが確認できました!

 

 

qiitaの記事に戻る

imageのpushをします。

qiitaの記事ではContainer Registryを使っていましたが、Artifact Registryを使っていきます。

先にArtifact RegistryのGCPコンソールからリポジトリを作っておきます。

 

イメージにタグをつける

docker tag myprojectsample asia-northeast1-docker.pkg.dev/PROJECT_ID/myprojectsample/sample:firstbuild

 

pushする

docker push asia-northeast1-docker.pkg.dev/PROJECT_ID/myprojectsample/sample:firstbuild

→ GCPコンソールから確認できました。

 

サービスの作成

うまくいかない。

イメージからCloud Runにデプロイをやってみると、以下のエラーメッセージが出てデプロイ失敗してしまいました。

The user-provided container failed to start and listen on the port defined provided by the PORT=8080 environment variable. 

 

localでは動いていたのに、、

cloud.google.com

これもやってみました。

gcloud beta code dev

これではちゃんと表示されていたので、なおさらおかしいですね。

 

 

以下のDockerfileとmain.goでやってみてもだめでした。

github.com

 

これを見て解決できるか?

cloud.google.com

 

ポートは問題ないと思うけどなぁ。

注: ARM ベースのマシンでコンテナ イメージを作成した場合、Cloud Run で意図したとおりに動作しないことがあります。この問題を解決するには、Cloud Build を使用してイメージをビルドします。

 

ARMベースじゃなくてApple M1 Proなんだけど、もしかしてこのあたりが問題なのか?

結論、以下で解決しました。

stackoverflow.com

 

以下オプションを付けてbuildしてみる。

--platform linux/amd64

 

これでbuildしたらいけました!

 

Artifact RegistryのイメージからCloud Runにデプロイを選択して、リージョンはasia-northeast1にして、認証は「未認証の呼び出しを許可」にして「作成」

すると、サービスが作成されて、表示されたURLにアクセスすると、Hello World!の文字が!

 

くぅ〜これにて完結です!

 

このあたりは一回どっかできれいにまとめたいところ。