🍦

GCPにコンテナで作ったAPIを公開する。認証も付ける。(Cloud Run + API Gateway + Firebase Auth)

2022/07/31に公開

はじめに

GCPにコンテナで作ったAPIをDeployして、API GatewayとFirebaseで認証つけてみたのでメモ。
GCP初心者なので、多少のミスは許してもらえると助かります。

構成図

こんな感じの構成。

作り方

API作ってコンテナ化

以前AWS App Runner用に作ったAPIを流用。
https://github.com/k-ibaraki/sample-app-runner

Dockerfileを書いておきました(なくてもDeployできたかもしれないけど、一応)

Dockerfile
FROM node:14.15.3-buster-slim

WORKDIR /app
COPY . .

EXPOSE 3000

RUN npm install

ENTRYPOINT ["npm", "start"]

Cloud Runにデプロイ

  • GCPのコンソールからCloud Runのページにいって、新規サービスを作成。
  • 「ソースリポジトリから新しいリビジョンを継続的にデプロイする」を選ぶ
  • 「SET UP WITH CLOUD BUILD」を選ぶ
  • GitHubとの連携の設定をする
  • 連携対象のレポジトリを選ぶ
  • ブランチを選んでDockerfileを指定
  • Ingressは「全てのトラフィック」、認証は「認証が必要」にする
    • 認証については最初は未認証も許可しておいて動作確認したほうがよいかもです。でないとAPI Gatewayの設定をするまで403になってしまい動作確認出来ないので。
  • 他を適当に設定して、「作成」を押せばコンテナをデプロイ出来るはず

Firebaseの設定

  • Firebaseにログインしてプロジェクトを作る。このとき、Cloud Runを作ったGCPのプロジェクトと紐付けておく
  • Authenticationに入って、適当にパスワード認証のユーザーを作っておく(テスト用)

API Gatewayを作る

  • GCPのコンソールからAPI Gatewayのページにいって、新規ゲートウェイを作成

  • 入力項目は「API仕様のアップロード」以外は適当にいれればOK

    • サービスアカウントはCloud Runと同じにした。本当はAPI Gateway用に作ったほうが良いのかも。
  • API仕様のアップロード用のSwagger仕様を書くのが厄介

    • 仕様がSwagger2.0で書かないと行けない。OpenAPI3の仕様で書くと弾かれる
    • Cloud RunへのURLを決められた書式で埋め込む必要あり
    • Firebase認証の設定をいれる
  • とりあえず下記のように書きました

swagger.yaml
swagger: '2.0'
info:
  title: sample-api
  description: Sample API on API Gateway with a Google Cloud Functions backend
  version: 1.0.0

# ここにCloud RunのURLを入れる
x-google-backend:
  address: https://xxxxxxx.a.run.app

# firebase認証の設定
securityDefinitions:
  firebase:
    authorizationUrl: "" # ここは空のままでOK
    flow: "implicit"
    type: "oauth2"
    x-google-issuer: "https://securetoken.google.com/(firebaseのプロジェクトid)" # ここ書き換える
    x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com"
    x-google-audiences: "(firebaseのプロジェクトid)" # ここ書き換える
security:
  - firebase: []

paths:
  /:
    get:
      summary: hello
      operationId: hello
      responses:
        '200':
          description: A successful response

あとは、「ゲートウェイを作成」を押して、しばらくすればAPI Gatewayが立ち上がります。

動作確認

  • Cloud Runに直接アクセスしてみる

    • 認証してないので403エラー(ネットワーク的には繋がるがIAMで弾いているので401や404でなく403になる)
  • API Gatewayに直接アクセスしてみる

    • トークンが無いので401エラー
  • API GatewayにFirebaseで認証して取得したidトークンをつけてアクセスしてみる

    • 認証されているので、200 OK

      ※ やたら時間がかかっているのは、お金をケチってコールドスタンバイだからです。

ということで、GCPにコンテナで作ったAPIを公開して認証を付けることが出来ました。

余談

Cloud RunのIngressと認証の設定について

Cloud RunのIngressの設定で「全てのトラフィック」を許可しました。
「API Gatewayからの通信だけを許可すればいいので、制限するべきでは?」と思う方がいるかも知れません。というか、私はそう思いました。
しかし、実際には通信を制限すると、API Gatewayからの通信も制限されて繋がらなくなってしまいます。
これについてはこちらのstackoverflowの回答が参考になりました。

つまり、少なくとも現時点では、Cloud Runへの通信をAPI Gatewayから制限することはネットワーク設定では無理なので、IAMで制限しましょうってことみたいです。
この手のマネージドサービスは、公開が簡単な代償にネットワークの制限をかけるのが大変な印象があります。AWSのAppRunnerもAzureのWebAppsもネットワーク周りの設定辛いんですよね。GCPお前もかって感じです。

「Firebaseで認証してidトークン取ってくる」ってどうすればいいの?という話

idトークンをつければAPIに接続できるよ書きましたが、つまりidトークン取ってこないといけないです。取ってくるコードを雑に書いたので貼っておきます。

  • npm install firebaseして使ってください
  • firebaseConfigはFIrebaseのコンソールで適当にアプリ作ると表示されるサンプルコードからコピペしてください
  • ユーザーとパスワードは、Firebase Authentication上に作ったユーザーのやつを設定してください
index.js
import { initializeApp } from "firebase/app";
import { getAuth, signInWithEmailAndPassword, getIdToken } from "firebase/auth";

// ここをFirebaseのコンソールから取れるソースコードの内容で上書きする
const firebaseConfig = {
  apiKey: "",
  authDomain: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: "",
  measurementId: ""
};
// firebaseに登録しているユーザーのメールアドレスとパスワード
const email = '';
const password = '';


// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

async function run() {
    // メールアドレスとパスワードを使って認証
  const credential = await signInWithEmailAndPassword(auth, email, password);
  // IDトークン(JWT)取得
  const token = await getIdToken(credential.user, true);

  console.log(token);
}

run();

何故か時々500エラーになる

  • Cloud Runのインスタンス数の最小を0にすると、Api Gateway経由で呼び出す時に立ち上げを待てずに500エラーになることがあるみたいです
  • Cloud Run側は起動に時間がかかるものの正常に動いていて10秒弱で200 OKを返しているのですが、Api Gateway側が8秒ぐらいでエラーと判断してしまうみたいです。タイムアウト時間を変更しても変わりませんでした
  • ググってみたけど詳細はよく分かりませんでした
  • Cloud Runの最小インスタンス数を1にすると500エラーがでなくなったので、とりあえず1にしています
    • 料金が1日20円くらい増えました

使った記憶がないのにStorageがやたら使われている

別記事に書きました
https://zenn.dev/ibaraki/articles/d524d404fffdb5

NCDCエンジニアブログ

Discussion