💻

Express(Node.js)+TypeScriptな環境をCloud Runに「エイヤ!!!」でデプロイする

2023/01/03に公開約7,700字

はじめに

毎回忘れるので、備忘録代わりに書いておきます。
この記事では、Express(Node.js)+TypeScriptなプロジェクトをCloud Runにデプロイすることを目指します。

前提条件

  • gcloud コマンドラインツールが有効になっている環境
  • Macのzshで実行(WSL2やLinux環境でも動くと思いますが他の環境でうまく行かない場合は適宜コマンドを読み替えてください)
  • Node.jsおよびnpmのインストール(可能ならば最新のLTS版が望ましい)

Express(Node.js)+TypeScriptのプロジェクト作成

プロジェクトのディレクトリーを作成し、npmで初期化します(入力画面は、ほとんどEnterで問題ないです)

❯ mkdir express-ts 
❯ cd express-ts 
❯ npm init 

必要なモジュールをnpmでインストールします。

❯ npm install typescript @types/node ts-node-dev express @types/express npm-run-all 

tsconfig.jsonを作成します。

❯ npx tsc --init 

お好みで、tsconfig.jsonの中身を書き換えましょう
下記は一例です。

  • srcディレクトリー配下にTypeScriptのソースコードを配置する
  • distディレクトリー配下にトランスコンパイルされたJavaScriptファイルを配置する

という設定になっています。その他にもTypeScriptのオプションを変更しています。

tsconfig.json
{
  "compilerOptions": {
    "allowJs": true,
    "target": "ES2020",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./dist",
    "strict": true,
    "strictNullChecks": true,
    "alwaysStrict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

srcディレクトリーをexpress-tsディレクトリー配下に作成しapp.tsファイルを作成します。

src/app.ts
import express, { Application, Request, Response } from "express";

const app: Application = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get("/", async (req: Request, res: Response) => {
  return res.status(200).send({
    message: "Hello World!",
  });
});

const port = process.env.PORT || 8000;
try {
  app.listen(port, () => {
    console.log(`Running at Port ${port}...`);
  });
} catch (e) {
  if (e instanceof Error) {
    console.error(e.message);
  }
}

package.jsonのmainとscriptsを下記のように書き換えましょう

package.json
  "main": "dist/app.js",
  "scripts": {
    "start": "node .",
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "ts-node-dev --respawn src/app.ts",
    "clean": "rimraf dist",
    "tsc": "tsc",
    "build": "npm-run-all clean tsc"
  },

下記コマンドでExpressを起動させましょう。

❯ npm run dev

> express-ts@1.0.0 dev
> ts-node-dev --respawn src/app.ts

[INFO] 12:28:44 ts-node-dev ver. 2.0.0 (using ts-node ver. 10.9.1, typescript ver. 4.9.4)
Running at Port 8000...

また上記のように「Running at Port 8000...」という文字列が出たら、下記URLにアクセスして、サーバーが起動することを確認してください
http://localhost:8000/

また内部でnpmのts-node-devパッケージを使っているため、リアルタイムでのソースコード書き換え(ホットリロード)にも対応しており、開発効率を上げることができます。
たとえば「Hello World!」という文字列を書き換えて、ブラウザーをリロードすればレスポンスの文字列が書き換わります。

Cloud Runの設定

GCPで新規プロジェクトを作成し、下記コマンドでプロジェクトIDを設定してください。
[PROJECT_ID] を手動でGCPのプロジェクトIDに置き換えてコマンドを実行します。

export PROJECT_ID=[PROJECT_ID]

Cloud Runを実行するため、GCPでプロジェクトの課金が有効になっていることを確認してください。

❯ gcloud beta billing projects describe ${PROJECT_ID} | grep billingEnabled
billingEnabled: true

gcloud コマンドで操作対象のプロジェクトを新規プロジェクトに切り替えてください。

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

Cloud Run の利用するリージョン、プラットフォームのデフォルト値を設定します。

gcloud config set run/region asia-northeast1
gcloud config set run/platform managed

Dockerfileの配置

プロジェクトの配下に以下の内容のDockerfileを配置してください

Dockerfile
FROM node:lts-slim

WORKDIR /usr/src/app

ENV PORT 8080

COPY package*.json ./

RUN npm install --only=production

COPY . ./

RUN npm run build
CMD [ "npm", "start" ]

デプロイ

Cloud Runへのデプロイ方法は極めてシンプルです。
gcloud run deployコマンド一発でいけます。

なにか聞かれたら、内容を読んでください。
※ 今回は検証用のため、なにか聞かれたら、ほとんどEnterとyでいけると思います。

❯ gcloud run deploy --source .
Service name (express-ts):
API [artifactregistry.googleapis.com] not enabled on project [1234567890]. Would you like to enable and retry (this will take a few minutes)?
(y/N)?  y

Enabling service [artifactregistry.googleapis.com] on project [1234567890]...

Deploying from source requires an Artifact Registry Docker repository to store built containers. A repository named [cloud-run-source-deploy] in
region [asia-northeast1] will be created.

Do you want to continue (Y/n)?  y

This command is equivalent to running `gcloud builds submit --tag [IMAGE] .` and `gcloud run deploy express-ts --image [IMAGE]`

API [run.googleapis.com] not enabled on project [1234567890]. Would you like to enable and retry (this will take a few minutes)? (y/N)?  y

Enabling service [run.googleapis.com] on project [1234567890]...
Allow unauthenticated invocations to [express-ts] (y/N)?  y

Building using Dockerfile and deploying container to Cloud Run service [express-ts] in project [express-ts-123456] region [asia-northeast1]
⠧ Building and deploying new service... Uploading sources.
⠼ Building and deploying new service... Uploading sources.
  ✓ Uploading sources...
  . Building Container...
⠏ Building and deploying new service... Uploading sources.
⠹ Building and deploying new service... Uploading sources.
X Building and deploying new service... Uploading sources.
API [cloudbuild.googleapis.com] not enabled on project [1234567890]. Would you like to enable and retry (this will take a few minutes)? (y/N)?
y

Enabling service [cloudbuild.googleapis.com] on project [1234567890]...

Deployment failed
ERROR: (gcloud.run.deploy) INVALID_ARGUMENT: could not resolve source: googleapi: Error 403: 1234567890@cloudbuild.gserviceaccount.com does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist)., forbidden

一度は上記のようにエラーメッセージが表示されます。これはPermission 'storage.objects.get' denied on resource (or it may not exist)., forbiddenとある通り、権限の問題です。

API [cloudbuild.googleapis.com] not enabled on project [1234567890]. Would you like to enable and retry (this will take a few minutes)?でyを選択してるなら、権限がONになっているはずです。

慌てず、もう一度下記コマンドを実行しましょう。

❯ gcloud run deploy --source .
Service name (express-ts):
This command is equivalent to running `gcloud builds submit --tag [IMAGE] .` and `gcloud run deploy express-ts --image [IMAGE]`

Allow unauthenticated invocations to [express-ts] (y/N)?  y

Building using Dockerfile and deploying container to Cloud Run service [express-ts] in project [express-ts-123456] region [asia-northeast1]
⠶ Building and deploying new service... Deploying Revision. Waiting on revision express-ts-00001-pag.
  ✓ Uploading sources...
✓ Building and deploying new service... Done.
  ⠶ Creating Revision... Revision deployment finished. Checking container health.
  . Routing traffic...
  ✓ Setting IAM Policy...
  ✓ Creating Revision... Revision deployment finished. Checking container health.
  ✓ Routing traffic...
Enabling service [run.googleapis.com] on project [1234567890]...
Done.
Service [express-ts] revision [express-ts-00001-pag] has been deployed and is serving 100 percent of traffic.
Service URL: https://express-ts-qwerty-an.a.run.app

発行されたService URLにアクセスし、先程のlocalhostと変わらない応答結果が返却されることを確認します。

Cloud Runの環境変数やメモリーなどを編集したい場合は下記URLのコンソールに飛び、サービス名(express-ts)を選択>「新しいリビジョンの編集とデプロイ」から設定してください。

https://console.cloud.google.com/run

お疲れさまでした。

お片付け

不要になったプロジェクトを削除し、コストがかからないようにします。

  1. Google Cloud のデフォルトプロジェクト設定の削除
    gcloud config unset project
  2. プロジェクトの削除
    gcloud projects delete ${PROJECT_ID}
  3. ハンズオン資材の削除
    express-tsディレクトリーの削除

参考サイト

gcp-getting-started-cloudrun/tutorial.md at main · google-cloud-japan/gcp-getting-started-cloudrun
ExpressとTypeScriptの環境構築

※ Google Cloud Japan公式のリファレンスはCloud Runの内部挙動まで記述されていて特に参考になります。更に知りたいことがある場合は時間のある際、目を通すようにしましょう。

Discussion

ログインするとコメントできます