とろろこんぶろぐ

かけだしR&Dフロントエンジニアの小言

【GCP】GCE/GKEを利用したWebサーバーを公開するまで

概要

GCP 上で Web サーバーを公開するまでにやったことを忘れないようにメモしておく。

Web サーバーは、

  • アプリケーション(Node.js)
  • DB (Postgresql)

で構築される。

やることまとめ

  1. アプリケーションをローカルで開発する
  2. アプリケーションをコンテナ化しておく
  3. ローカルで DB を立てる(on docker)
  4. ローカルで DB + アプリケーションを dockerize し動作を確認する(docker-compose)
  5. GCE で DB を構築する
  6. GKE でアプリケーションをデプロイする
  7. アプリケーションから DB を接続するためにファイアウォールを設定する
  8. GKE のサービスにドメインを設定する

詳細

1. アプリケーションをローカルで開発する

いつも通り node.js のアプリケーションを開発する。

npm install
npm run build
npm run start

とさえすれば動くようにしておく。

今回は graphql を利用したので、 tsc で dist ファイルに build する際に、

tsc && cp src/schema.graphql dist

として、schema.graphql もコピーした。

そうじゃないと以下のような実行時エラーが出る。

Error:
      Unable to find any GraphQL type definitions for the following pointers:

          - /Users/xxx/sample-server/dist/schema.graphql

    at prepareResult (/Users/xxx/sample-server/node_modules/@graphql-tools/load/index.cjs.js:591:15)
    at loadTypedefsSync (/Users/...

2. アプリケーションをコンテナ化しておく

のちに GKE でコンテナとして動かしたいので、 Dockerfile を用意しておく。

FROM node:14

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

RUN npx prisma generate
RUN npm run build

EXPOSE 4000

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

今回アプリケーションに prisma を利用したため、 prisma generate を行って node_modules 配下に types を配置しないと build が通らない。

こんな感じのエラーが出る。

 > [6/6] RUN npm run build:
#10 4.414
#10 4.414 > sample-server@1.0.0 build /usr/src/app
#10 4.414 > tsc && cp src/schema.graphql dist/src
#10 4.414
#10 21.51 src/resolvers/Query/index.ts(13,25): error TS7006: Parameter 'item' implicitly has an 'any' type.

2. ローカルで DB を立てる(on docker)

あとでアプリケーションをのせる前提で、postgres だけ立てておく。

version: "3"
services:
  postgres:
    image: postgres:latest
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: xxx
      POSTGRES_DB: xxx
    volumes:
      - ./pgsql-tmp:/var/lib/postgresql/data
    ports:
      - 15432:5432

localhost:15432 で接続を確認しておく。

3. ローカルで DB + アプリケーションを dockerize し動作を確認する(docker-compose)

docker-compose.yaml にアプリケーション (app) を追加する。

  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - 4000:4000
    environment:
      DATABASE_URL: "postgresql://dev:xxx@postgres:5432/sample?schema=public"
    depends_on:
      - postgres

docker-compose up -d --build とし、localhost:4000/graphql で Playground が見られることを確認しておく。

4. GCE で DB を構築する

GCP には新たな Project を作っておく。 この Project で GCE / GKE を使う。

Cloud SQL は有料しかないので、無料枠で楽しむために DB は GCE で立てる。

以下の記事を参考にすれば GCE 上に簡単に Postgresql を立てることができる。

Set up PostgreSQL on Compute Engine  |  Google Cloud Platform Community

5. GKE でアプリケーションをデプロイする

5.1 コンテナを GCR にあげる

まずアプリケーションの Docker コンテナを build し、 GCR (コンテナレジストリ)にアップロードする必要がある。

docker build -f Dockerfile -t gcr.io/${projectID}/sample-server:v1 .
docker push gcr.io/${projectID}/sample-server:v1

うまくいけば、 GCP の自分のプロジェクトの Container Registry であげた image を確認できる。

5.2 secret で環境変数を用意

ここから GKE になる。 あらかじめ Kubernetes クラスタ を新規で作成しておく。 ゾーンを GCE と揃えておかないと DB にアクセスできないので注意する。

kubectl を対象のクラスタ(sample-cluster)で使えるようにするため、以下コマンドを実行する。

gcloud container clusters get-credentials sample-cluster --zone us-central1

prisma環境変数で DATABASE_URL を指定するようになっている。 ローカルで開発する際は、 .env で管理するようになっている環境変数を、 k8s の service 実行時に指定する必要がある。

ただし DB のパスワードも含まれるため、これを k8s の secret で管理するようにする。

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  database_url:  XXX

base64 で指定するため、

 echo "postgresql://postgres:xxx@10.128.0.1:5432/sample?schema=public" | base64

のようにして得られた base64 の値を指定する。 IP は GCE の VM の内部 IP を指定する。

secret を k8s に apply する。

kubectl apply -f k8s/secret.yaml

5.3 deployment/service をデプロイ

deployment と service を示す yaml を書いて、 apply する。

kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml

作成したファイルは以下。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-server
  labels:
    app: sample-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample-server
  template:
    metadata:
      labels:
        app: sample-server
    spec:
      containers:
        - name: sample-server
          image: gcr.io/sample/sample-server:v1
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: mysecret
                  key: database_url
          ports:
            - containerPort: 4000
          resources:
            requests:
              cpu: 200m

環境変数は上で作った mysecret から取得する。

kind: Service
apiVersion: v1
metadata:
  name: sample-server-service
spec:
  type: LoadBalancer
  selector:
    app: sample-server
  ports:
    - protocol: TCP
      port: 80
      targetPort: 4000

kubectl get service で得られる EXTERNAL-IP の外部 IP を使い、

XX.XX.XX.XX/graphql などで、Playground が見られることを確認する。

$ kubectl get service
NAME                  TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
sample-server-service   LoadBalancer   10.128.0.1   XX.XX.XX.XX  80:30174/TCP   56m
kubernetes            ClusterIP      10.128.0.1     <none>           443/TCP        60m

おそらくこの時点では実際にクエリをリクエストしても DB にアクセスできない旨のエラーが表示される。

6. アプリケーションから DB を接続するためにファイアウォールを設定する

GKE のアプリケーションから GCE の DB にアクセスを許可するために少し設定が必要になる。

GCE に ssh で入り、 pg_hba.conf に Pod のアドレス範囲を許可する様に指定しておく。

Kubernetes クラスタ の詳細から、 ポッドのアドレス範囲 10.0.0.0/17 はわかる。

host    all             all           [POD_ADDRESS]         md5

GKE のクラスタを作成すると、 自動的にファイアウォールが作成されていた。

gke-sample-cluster-xxxxxxx-all

これをみると、ソースフィルタ の IP 範囲がクラスタの Pod のアドレス範囲になっているためこれを適用するようにする。

ターゲットタグの gke-sample-cluster-xxxxxxx-node のようなタグをコピーし、 GCE の VM インスタンスのネットワーク タグに貼り付ける。

これによりファイアウォール側のコンソールに、インスタンスとして Postgres の VM が表示され、実際に IP ベースで Graphql にアクセス可能になっている。

7. GKE のサービスにドメインを設定する

静的 IP を取得する。

gcloud compute addresses create sample-server-ip --region us-central1

以下のコマンドで取得した IP を確認できる。

gcloud compute addresses describe sample-server-ip --region us-central1

service.yaml に上記で確認した IP を以下のように追加する。

kind: Service
...
      targetPort: 4000
+  loadBalancerIP: "XX.XX.XX.XX"

apply する。

kubectl apply -f k8s/service.yaml

確認する。

kubectl get service # EXTERNAL-IP を確認できる
curl http://XX.XX.XX.XX/

これにより固定化された IP で Web サービスにアクセスできる。

今回は、ドメインはもともと取得していたもののサブドメインに追加する。 Vercel で DNS の設定をしていたため、 Vercel の管理画面からサブドメインを IP 設定する A レコードを追加する。

api A XX.XX.XX.XX 60

これで http://api.sample.com/ のような形でアクセスできるようになる。

まとめ

とりあえず GKE / GCE で Web サーバーを動かすところまでやってみた。 ファイアウォール周り、cloud build 、terraform 、 https 化などもやってみたいがまた今度。