🐥

Cloud Functions で Go で書いた関数のデプロイに失敗する

2022/10/19に公開

前提

ディレクトリ構成は以下。

.
├── 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

原因

ローカルのコードがデプロイするための以下の条件を満たしていない。

  1. ソースディレクトリ直下に go.mod, go.sum が存在すること
  2. ソースディレクトリのパッケージの 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