☁️

Google Cloud Functions(第2世代)をTypescriptで作ってデプロイする

2023/07/07に公開

前提

  • firebaseではなくGCF
  • gcloudコマンドはインストール&認証済み
  • 必要なAPIは有効化済み

まずはjsでやる

いきなりTypescriptでやろうとしたけど関係ないところで色々ハマって原因の切り分けが面倒なのでまずはシンプルにjsからやりましょう

ファイルの準備

# プロジェクト作成
npm init

# パッケージのインストール
npm i @google-cloud/functions-framework

touch index.js

デプロイするファンクションを用意
第1世代と第2世代で異なるようです。
Pub/Subトリガーなどもありますが、今回はHttpトリガーで行います。

// index.js
const functions = require("@google-cloud/functions-framework");

functions.http("sample", (req, res) => {
  res.status(200).send("sushi");
});

この時点でファイル構成はこのようになっているはずです

┬ node_modules
├ index.js
├ package-lock.json
└ package.json

デプロイ

package.jsonmainで実行するファイルを指定します。
ついでにデプロイ用のコマンドも設定しておきましょう

{
  "name": "function-sample",
  "version": "1.0.0",
  "description": "",
+ "main": "index.js",
  "scripts": {
+   "deploy": "gcloud functions deploy sample --gen2 --runtime=nodejs18 --trigger-http --allow-unauthenticated",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@google-cloud/functions-framework": "^3.3.0"
  }
}

デプロイコマンドのそれぞれの意味は以下です

gcloud functions deploy \
  # ファンクション名
  sample \
  # 第2世代を指定
  --gen2 \
  # ランタイム
  --runtime=nodejs18 \
  # Httpトリガーを指定
  --trigger-http \
  # 未認証状態でも呼び出しを許可
  --allow-unauthenticated

ランタイムはランタイムサポートから選べます
リージョンは指定しなくてもコマンドラインで選べるようになってます
指定する場合は以下のように指定します

# 東京
--region asia-northeast1

それではデプロイを実行しましょう

npm run deploy

実行すると.gloudignoreが作られますが、事前に作っておいても大丈夫です。
ここで指定したファイルはアップロードする対象から除外されます。
中身は下記のようになってます

# .gloudignore
.gcloudignore
.git
.gitignore

アップロードされるファイルは以下のコマンドで確認できます

gcloud meta list-files-for-upload
# 表示される下記がアップロードされるファイル
# index.js
# package-lock.json
# package.json

しばらく時間がかかるかと思いますがエラーなどでなければデプロイ完了です
デプロイ後に表示されるURLやGCPのコンソールからURLが確認できるので、そこにアクセスすればデプロイしたファンクションの結果が表示されます

~~~~~~~
url: https://asia-northeast1-project-xxxxxxxxxxx.cloudfunctions.net/sample

--allow-unauthenticatedで誰でも呼び出せる状態になっているので、確認できたらこのファンクションは削除しておきましょう

エラー

ちなみに私は以下のエラーでしばらくハマりました
ビルドまでは問題ありませんでしたがCloud Runのあたりでエラーになりデプロイに失敗します。

Ready condition status changed to False for Service sample with message:
Revision 'sample-00001-kuc' is not ready and cannot serve traffic.
The user-provided container failed to start and listen on the port defined provided by the PORT=8080 environment variable.
Logs for this revision might contain more information.

ポートのリッスンに云々などと書かれているのであらぬところを疑ったりしていましたが、
原因はindex.jsに記載しているファンクション名と、デプロイコマンドで指定するファンクション名が異なっていたためでした

// index.js
functions.http(
  "sample", // <- これがサンプルコードからコピペしたままになってた
  (req, res) => {
    res.status(200).send("sushi");
  });
gcloud functions deploy \
  sample # <- ここと同じ名前にしましょう

Typescriptでやる

それでは本題のTypescriptを使っていきましょう
まずはtsファイルを用意

# パッケージのインストール
npm i -D typescript

# index.tsを用意
rm index.js
mkdir src
touch src/index.ts

# tsconfig.json
touch tsconfig.json

中身はほとんど変わりません

// src/index.ts
import * as functions from "@google-cloud/functions-framework";

functions.http("sample", (req, res) => {
  res.status(200).send("tempura");
});

tsconfig.jsonはこんな感じです

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "dist"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Cloud Buildを使ってビルドすることもできるようですが
今回はトランスパイル済みのものをアップロードするので、事前にトランスパイルします

npx tsc

この時点でファイル構成は以下のようになっているはずです

┬ dist
│   └ index.js
├ node_modules
├ src
│   └ index.ts
├ index.js
├ package-lock.json
└ package.json

tsファイルはいらないので.gcloudignoreを編集します

# .gloudignore
.gcloudignore
.git
.gitignore
+ src/**
+ tsconfig.json
gcloud meta list-files-for-upload
# アップロードされるファイル
# package-lock.json
# package.json
# dist/index.js

実行するファイルの場所が変わったのでpackage.jsonも変更します

{
  ...
- "main": "index.js",
+ "main": "dist/index.js",
  ...
}

ローカルテスト

ちなみローカルで動作確認はこちら
トランスパイルしたファイルも問題なさそうです

npx functions-framework --target=sample
curl localhost:8080
# tempura

デプロイ

ここまできたら後は同じようにデプロイするだけです

ちなみにpackage.jsonにbuildコマンドを設定しているとCloud Buildでこれが実行されてしまい失敗するので別の名前にしましょう。
ついでに今回は認証が必要になるように変更します。

{
  ...
  script: {
-   "build": "tsc",
+   "pre-build": "tsc",
-    "deploy": "gcloud functions deploy sample --gen2 --runtime=nodejs16 --trigger-http --allow-unauthenticated --region asia-northeast1",
+    "deploy": "gcloud functions deploy sample --gen2 --runtime=nodejs16 --trigger-http --region asia-northeast1",
  ...
}

あとは同じようにデプロイします
一度--allow-unauthenticatedで作ったファンクションを上書きしても認証が必要に変更されなかったので、一度削除してからもう一度デプロイしました。
詳しく調べてませんが、GCPのコンソールからも変更できなさそうだったのでそういう仕様なのかもしれません。

npm run deploy

動作確認しておしまい。お疲れ様でした。

# 未認証だとエラー
curl https://asia-northeast1-xxxxxxxxxxxxxxxxx.cloudfunctions.net/sample
# <html><head>
# <meta http-equiv="content-type" content="text/html;charset=utf-8">
# <title>403 Forbidden</title>
# </head>
# <body text=#000000 bgcolor=#ffffff>
# <h1>Error: Forbidden</h1>
# <h2>Your client does not have permission to get URL <code>/sample</code> from this # server.</h2>
# <h2></h2>
# </body></html>

# 認証情報付きで送る
curl -H "Authorization: bearer $(gcloud auth print-identity-token)" https://asia-northeast1-xxxxxxxxxxxxxxxxx.cloudfunctions.net/sample
# tempura

Discussion