🐥
Cloud Functions で Go で書いた関数のデプロイに失敗する
前提
ディレクトリ構成は以下。
.
├── go.mod
├── go.sum
├── app.go
└── function
└── function.go
現象
gcloud コマンドでローカルの関数をデプロイする。
gcloud functions deploy my-function \
--gen2 \
--runtime=go119 \
--region=asia-northeast1 \
--source=./fucntion \
--entry-point=HelloWorld \
--trigger-http \
--allow-unauthenticated
次のエラーが発生する。
ERROR: error fetching storage source: generic::unknown: retry budget exhausted (3 attempts): fetching gcs source: unpacking source from gcs: source fetch container exited with non-zero status: 1
原因
ローカルのコードがデプロイするための以下の条件を満たしていない。
- ソースディレクトリ直下に go.mod, go.sum が存在すること
- ソースディレクトリのパッケージの init 関数で functions.HTTP を呼び出すこと
ソースディレクトリは --source
フラグで指定したディレクトリのことを指しています。
解決策
go.mod, go.sub が存在するディレクトリをソースディレクトリとして指定
ディレクトリ構成が以下の場合、--source=.
を指定します。
.
├── function.go
├── go.mod
└── go.sum
ディレクトリ構成が以下の場合、--source=./gcf
を指定します。
.
└── gcf
├── function.go
├── go.mod
└── go.sum
functions.HTTP の呼び出しをソースディレクトリのパッケージで実行
成功パターンと失敗パターンがあるのでそれぞれ書いていきます。
- 成功パターン
- ソースディレクトリ内に functions.HTTP と ハンドラーを配置
- ソースディレクトリ内に functions.HTTP を配置、別パッケージにハンドラーを配置
- 失敗パターン
- ソースディレクトリ内に空ファイルを配置、別パッケージに functons.HTTP とハンドラーを配置
- ソースディレクト内に空ファイル(mainパッケージ)を配置、別パッケージに functons.HTTP とハンドラーを配置
成功パターン
ソースディレクトリ内に functions.HTTP と ハンドラーを配置
ソースディレクトリ内に functions.HTTP の呼び出しとハンドラー記述を行う例です。
成功します。
.
├── function.go
├── go.mod
└── go.sum
// function.go
package app
import (
"fmt"
"net/http"
"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)
const entryPoint = "HelloWorld"
func init() {
functions.HTTP(entryPoint, helloWorld)
}
func helloWorld(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
}
ソースディレクトリ内に functions.HTTP を配置、別パッケージにハンドラーを配置
ソースディレクトリ内で functions.HTTP 呼び出し、別パッケージにハンドラー記述を行う例です。
こちらも成功します。
.
├── go.mod
├── go.sum
├── app.go
└── function
└── function.go
// app.go
package app
import (
"github.com/GoogleCloudPlatform/functions-framework-go/functions"
"my-app/function"
)
const entryPoint = "HelloWorld"
func init() {
functions.HTTP(entryPoint, function.HelloWorld)
}
// function/function.go
package function
import (
"fmt"
"net/http"
)
func HelloWorld(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
}
失敗パターン
ソースディレクトリ内に空ファイルを配置、別パッケージに functons.HTTP とハンドラーを配置
このパターンは失敗します。
.
├── go.mod
├── go.sum
├── app.go
└── function
└── function.go
// app.go
package app
import _ "my-app/function"
// function/function.go
package function
import (
"fmt"
"net/http"
"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)
const entryPoint = "HelloWorld"
func init() {
functions.HTTP(entryPoint, HelloWorld)
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
}
次のエラーが発生します。
ERROR: (gcloud.functions.deploy) OperationError: code=3, message=Build failed with status: FAILURE and message: # functions.local/app
./main.go:43:21: undefined: gcf.HelloWorld.
ソースディレクトリ内に空ファイル(mainパッケージ)を配置、別パッケージに functons.HTTP とハンドラーを配置
上記のパターンで空ファイルのパッケージを main パッケージにした場合、次のエラーが発生します。
エラー文言が変化します。
ERROR: (gcloud.functions.deploy) OperationError: code=3, message=Build failed with status: FAILURE and message: main.go:18:2: import "hiroto.ohira/kamikou-twitter-notification" is a program, not an importable package.
終わりに
GCF を使用したかったのですが初っ端のデプロイの段階で色々とつまずきました。
この際なのでなぜ失敗するのかを調べてパターン分けしました。
どなたかのお役に立てれば幸いです。
良き GCP ライフを過ごしましょう〜。
Discussion