🐈

Typescript × Cloud FunctionsでServerless Frameworkに入門する

2021/06/13に公開

Typescript × Cloud Functions を利用して Serverless Framework に入門した際の作業ログをまとめています。
また、完成形のサンプルコードは下記の Repository にまとめています。

https://github.com/taxintt/serverless_framework_ts_sample

セットアップ

この記事では、下記のバージョンで検証を進めていきます。

node : v14.17.0
npm : v7.5.3
typescript : v4.3.2

最初に、Serverless の npm package を install します。
また、Plugin として利用する下記の npm package も install しておきます。
https://www.npmjs.com/package/serverless

$ npm install -g serverless
$ serverless --version
Framework Core: 2.46.0
Plugin: 5.3.0
SDK: 4.2.3
Components: 3.12.0

$ npm install --save serverless-google-cloudfunctions
$ npm install --save serverless-plugin-scripts
$ npm install --save serverless-plugin-typescript

Credentialの作成

Deploy 前に、Serverless Framework 側で GCP Resource の作成・管理ができるように必要な権限を渡す必要があります。
下記のドキュメントに従って、Service Account(Key file) の作成 → serverless.yml での Key file を指定します。
https://www.serverless.com/framework/docs/providers/google/guide/credentials/

gcloud iam service-accounts serverless-sample \
	--project <project_id> \
	--display-name "Service Account for the sample of serverless framework"

Service Account を作成したら、IAM Role をバインドします。
(今回は Document の内容を踏まえて、Deploy と最低限の動作確認ができる程度の IAM Role に修正しています。)
https://cloud.google.com/iam/docs/granting-changing-revoking-access

gcloud projects add-iam-policy-binding <project_id> \
	--member=serviceAccount:serverless-sample@<project_id>.iam.gserviceaccount.com \
	--role=roles/storage.objectAdmin

gcloud projects add-iam-policy-binding <project_id> \
	--member=serviceAccount:serverless-sample@<project_id>.iam.gserviceaccount.com \
	--role=roles/deploymentmanager.editor 

gcloud projects add-iam-policy-binding <project_id> \
	--member=serviceAccount:serverless-sample@<project_id>.iam.gserviceaccount.com \
	--role=roles/logging.logWriter

gcloud projects add-iam-policy-binding <project_id> \
	--member=serviceAccount:serverless-sample@<project_id>.iam.gserviceaccount.com \
	--role=roles/cloudfunctions.developer

次に、Service Account の key file を作成します。
https://cloud.google.com/iam/docs/creating-managing-service-account-keys#creating_service_account_keys

gcloud iam service-accounts keys create ~/.gcloud/serverless-sample-key.json \
	--iam-account=serverless-sample@<project_id>.iam.gserviceaccount.com

key file の作成後は、Serverless.yml 内の provider.credentials でパスを指定するので、忘れずに指定しておきましょう。

provider:
  name: google
  stage: dev
  runtime: nodejs10
  region: us-central1
  project: <project_id>
  # The GCF credentials can be a little tricky to set up. Luckily we've documented this for you here:
  # https://serverless.com/framework/docs/providers/google/guide/credentials/
  #
  # the path to the credentials file needs to be absolute
  credentials: ~/.gcloud/serverless-sample-key.json

サンプルコードの実装

次に Cloud Functions 側で動作させるサンプルコードを実装します。
今回は、動作確認目的なので、既存のテンプレートからコードを持ってきます。 
(この時点では Typescript ではなく単純な Node.js 用の template です)
https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/google-nodejs

$ serverless create --template google-nodejs --path serverless-framework-ts-sample --name serverless-framework-ts-sample
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/xxx/..."
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v2.46.0
 -------'

Serverless: Successfully generated boilerplate for template: "google-nodejs"

テンプレートでは、下記のような code が生成されるので、こちらを利用します

'use strict';

exports.http = (request, response) => {
  response.status(200).send('Hello World!');
};

exports.event = (event, callback) => {
  callback();
};

Cloud Functionsのデプロイ

Credential も含めた準備が完了したら、serverless deploy コマンドを実行します。
serverless invoke --function <function_name> で作成した Function を実行して、正常に response が返ってくることを確認します。

$ serverless deploy 
Serverless: Deprecation warning: Support for "package.include" and "package.exclude" will be removed with next major release. Please use "package.patterns" instead
            More Info: https://www.serverless.com/framework/docs/deprecations/#NEW_PACKAGE_PATTERNS
Serverless: Packaging service...

...

Deployed functions
first
  https://us-central1-<project_id>.cloudfunctions.net/sfw-sample-dev-first


**************************************************************************************************************************************
Serverless: Announcing Metrics, CI/CD, Secrets and more built into Serverless Framework. Run "serverless login" to activate for free..
**************************************************************************************************************************************

$ serverless invoke --function first
Serverless: Deprecation warning: Support for "package.include" and "package.exclude" will be removed with next major release. Please use "package.patterns" instead
            More Info: https://www.serverless.com/framework/docs/deprecations/#NEW_PACKAGE_PATTERNS
Serverless: 8l5l1vr48v2t Hello World!

serverless invoke で指定した Function name は serverless.yml の下記の部分から持ってきます。

functions:
  first: // function_name
    handler: helloWorld
    events:
      - http: path

一点注意点として、--allow-unauthenticated (=Public からのアクセスを許可する) の状態で Function を Deploy するためには、追加で IAM 周りの調整が必要になります。
(Deploy した Functions に Cloud function invoker の Role を allUsers の Role をアタッチする必要があります。)

デフォルトの状態では、curl でリクエストを投げると 403 が返ってきます。

curl https://us-central1-<project_id>.cloudfunctions.net/sfw-sample-dev-first

<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>/sfw-sample-dev-first</code> from this server.</h2>
<h2></h2>
</body></html>

毎回、手動で実行するのは手間なので、Scripts の Plugin を利用して、Deploy 後に hook して IAM Role を Deploy された Function に bind します。
https://github.com/serverless/serverless-google-cloudfunctions/issues/205

Plugin の README.md を参照しつつ、custom.scripts 以下で hooks と commands の定義を追加します。
https://www.npmjs.com/package/serverless-plugin-scripts

plugins:
  - serverless-google-cloudfunctions
  - serverless-plugin-scripts
 
...

custom:
 # this comes from https://www.npmjs.com/package/serverless-plugin-scripts
  scripts:
    commands:
      make-public: gcloud functions add-iam-policy-binding ${self:service}-${self:provider.stage}-${env:FUNC_NAME, "first"} --member="allUsers" --role="roles/cloudfunctions.invoker" --project=${self:provider.project} --region=${self:provider.region} | xargs echo 
    hooks:
      'after:deploy:deploy': FUNC_NAME=first npx serverless make-public

再度、serverless deploy コマンドを実行します。

$ serverless deploy
Serverless: Deprecation warning: Support for "package.include" and "package.exclude" will be removed with next major release. Please use "package.patterns" instead
            More Info: https://www.serverless.com/framework/docs/deprecations/#NEW_PACKAGE_PATTERNS
Serverless: Packaging service...

...

Deployed functions
first
  https://us-central1-<project_id>.cloudfunctions.net/sfw-sample-dev-first

Serverless: Removing old artifacts...
Serverless: Deprecation warning: Detected unrecognized CLI options: "--function".
            Starting with the next major, Serverless Framework will report them with a thrown error
            More Info: https://www.serverless.com/framework/docs/deprecations/#UNSUPPORTED_CLI_OPTIONS
Serverless: Deprecation warning: Support for "package.include" and "package.exclude" will be removed with next major release. Please use "package.patterns" instead
            More Info: https://www.serverless.com/framework/docs/deprecations/#NEW_PACKAGE_PATTERNS
bindings: - members: - allUsers role: roles/cloudfunctions.invoker etag: BwXEjVhKUP4= version: 1

curl でリクエストを投げて、Functions から Response が返ってきたことを確認しました。

curl https://us-central1-<project_id>.cloudfunctions.net/sfw-sample-dev-first
Hello World!%       

Deploy 時の設定に、いくつか duprecated な設定が含まれているので、下記を参考にこちらも修正します。
https://www.serverless.com/framework/docs/deprecations/#UNSUPPORTED_CLI_OPTIONS
https://www.serverless.com/framework/docs/providers/google/guide/packaging/#exclude--include/

Typescriptでの再実装

次に、Typescript での実装方法についてまとめていきます。
Deploy 前に事前にコンパイルして、serverless deploy を実行する方法もありますが、今回は Typescript 用の Plugin を利用します。

https://www.serverless.com/plugins/serverless-plugin-typescript

Document に従い、serverless.yml での Plugin の指定と tsconfig.json の設定を行います。

plugins:
  - serverless-plugin-typescript

また、ここでは、下記のようにディレクトリ構造を変更しています。
(Function 上で動かすコードは src/ 配下に格納して、 package.json で entrypoint を指定します。)

|- src
|  |- index.ts
|
|- package.json
|- tsconfig.json

package.json では、entrypoint となるコンパイル後の js ファイルを指定します。

{
  // ...
  "main": "src/index.js",
  // ...
}

コンパイル自体は Serverless Framework 側で実行されて、outDir で指定された .build 配下にコンパイル済みのコードを吐き出します。

And this plugin will automatically compile your typescript correctly. Note that the field must refer to the compiled file name, namely, ending with a .js extension.

この状態で再度 serverless deploy を実行します。
(entrypoint などの設定に不備がなければ、正常に Deploy されます)

$ serverless deploy
Serverless: Compiling with Typescript...
Serverless: Using local tsconfig.json
Serverless: Typescript compiled.


Deployed functions
first
  https://us-central1-<project_id>.cloudfunctions.net/sfw-sample-dev-first

Serverless: Removing old artifacts...
bindings: - members: - allUsers role: roles/cloudfunctions.invoker etag: BwXEkgzdWow= version: 1

終わりに

この記事では、Serverless Framework の基本的な利用方法についてまとめました。
GCP は AWS と比較すると、S3 などの連携して利用する resources を local で emulate するための Plugin がありません。

また、CLI では invoke local (=local での Function 実行)のコマンドなど一部コマンドが実装されていません。
https://www.serverless.com/framework/docs/providers/google/cli-reference/

それでも、TS の Plugin など利用できるものもあるので、個人開発などであれば利用できるような印象を持ちました。

Discussion