Closed12

モノレポ環境の Next.js を Cloud Run にデプロイして社内のメンバーだけが閲覧できるようにするまで

JJJJ

構成

  • next.js: 13.4.2
  • react: 18.2.0
  • react-dom: 18.2.0
  • pnpm: 8.5.1

pnpm workspace + turbo でモノレポ管理されている想定
社内メンバーだけということで、管理画面などを想定する

JJJJ

query builder として kysely を使った

https://kysely.dev/

import {
  ColumnType,
  Generated,
  Insertable,
  Selectable,
  Updateable,
} from 'kysely';

export interface UserTable {
  id: Generated<number>;
  email: string;
  created_at: ColumnType<Date, string | undefined, never>;
  updated_at: ColumnType<Date, string | undefined, never>;
}

export type User = Selectable<UserTable>;
export type NewUser = Insertable<UserTable>;
export type UpdateUser = Updateable<UserTable>;

export interface Database {
  users: UserTable;
}

こんな感じで table のカラムなどを定義しておく

DB への接続はこんな感じでできる

import type {Database} from './types';
import {Pool} from 'pg';
import {Kysely, PostgresDialect} from 'kysely';

const dialect = new PostgresDialect({
  pool: new Pool({
    database: process.env.DB_NAME,
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    port: process.env.DB_PORT,
    max: 10,
  }),
});

export const db = new Kysely<Database>({dialect});

ローカルで開発環境のDBを繋いで開発してる時は、cloud sql proxy を使う前提で行くと

DB_HOST: 127.0.0.1

になる。

Cloud Run 経由で Cloud SQL に繋ぐ時は以下の記事のままやっていけばできる

https://cloud.google.com/sql/docs/mysql/connect-instance-cloud-run?hl=ja

DB を postgres にしてる場合、 kysely では INSTANCE_UNIX_SOCKET に当たるものをそのまま host に設定すればよいため、環境変数で /cloudsql/xxxxxx をつっこんでしまえば動く

JJJJ

例えばユーザー数を取得する場合は以下のように書ける

export async function getAllUserCount() {
  const res = await db
    .selectFrom('users')
    .select(u => u.fn.count<number>('id').as('count'))
    .executeTakeFirstOrThrow();

  return res.count;
}

kysely では fn.count("id").as("count") のような形で SQL の関数を実行する。ただしこの返り値は string | number | bigint になるので、 number に型を合わせている。
ただこれは問題で実際、ランタイム上では string になる

https://github.com/konojunya/kysely-type-not-match

これはドキュメントにある通りで、ドライバの設定により変わるため TS の型上でそれを表現できないので広めにとってるっぽい。

https://kysely-org.github.io/kysely-apidoc/interfaces/FunctionModule.html#count

なので実際は string で合わせにいって、 Number.parseInt() とかを噛ませるのが無難

JJJJ

そのため Dockerfile は以下のような形が最小構成になりそう

packages/admin/Dockerfile
FROM node:18-alpine

WORKDIR /app

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --chown=nextjs:nodejs ./packages/admin/next.config.js ./packages/admin/next.config.js
COPY --chown=nextjs:nodejs ./packages/admin/public ./packages/admin/public/
COPY --chown=nextjs:nodejs ./packages/admin/.next/static ./packages/admin/.next/static/

COPY --chown=nextjs:nodejs ./packages/admin/.next/standalone ./

USER nextjs

CMD ["node", "apps/admin/server.js"]

モノレポなので COPY をしてくる元のファイルや、そのコピー先もモノレポの時と同じように持っているが多分 flat に持っても大丈夫だと思う(未検証)

JJJJ

アプリケーションのビルドは

  1. pnpm build
  2. docker build
    の 2 回行うことになるが、ローカル開発の際にも DB_HOST などの環境変数を参照するので .env を作るのが多いと思う。
    この .env をおいたまま pnpm build してしまうと .next の方にも .env がコピーされてしまうため、手元で docker build を行って Cloud Run に一旦 deploy しようみたいに考えると、参照してる env が .next にある .env をロードしてしまい参照できない

つまり

packages/admin/.env
DB_HOST=127.0.0.1
env DB_HOST=postgres://xxxx pnpm build
docker build -t <Tag> -f packages/admin/Dockerfile .

みたいにビルドをしても、 DB_HOST127.0.0.1 のままになる

そのため手元でビルドする時は .env にローカル用と dev 環境用みたいに作っておく方がよいかも

JJJJ

ビルドして、cloud run へのデプロイは env を除いて以下のように実行

pnpm build
docker build -t <artifact registry の URL>/<gcp project id>/<folder>/<id>:<tag> --platform linux/amd64 -f apps/admin/Dockerfile .
docker push <image>
gcloud run deploy admin --project=<project id> --image=<image> --region=<region> --platform managed

この時、例えば僕の PC は Apple M2 だが、普通に docker build して成功しても cloud run に deploy すると 8080 port が使えないよみたいなエラーで怒られるが、これは PORT を開けてるかとかの問題ではなくだいたいは platform の問題なので

--platform linux/amd64

をつけるとうまく行く

https://cloud.google.com/code/docs/intellij/arm?hl=ja

JJJJ

上記の通り進めてるが、うまくOAuthは動いてる。そしてGoogle認証も使える。

ただ認証突破した後、403が返ってくる...😬
この問題を解決したらクローズする

JJJJ

IAM によるアクセス制御の手順に沿って、IAP がトラフィックを Cloud Run バックエンド サービスに送信することを承認します。
プリンシパル: service-[PROJECT-NUMBER]@gcp-sa-iap.iam.gserviceaccount.com
ロール: Cloud Run 起動元

この IAM どこにあるねん... って思ってたけどプロジェクト情報にあるプロジェクト番号をいれて IAMと管理 から Google 提供のロール付与を含める のチェックをつけると出てくるはず。

アクセス権をを付与 から Cloud Run 起動元を付与すればいい感じになる

このスクラップは2023/09/10にクローズされました