🏃

【GCP】Cloud Run 色々試した時の覚書

2022/06/18に公開

GCPのCloud Runを色々試した際に、メモした内容をつらつら書いています。

まずは Hello World的なことをやってみる

クイックスタート: 事前にビルドされたサンプル コンテナをデプロイする  |  Cloud Run のドキュメント  |  Google Cloud
↑のサンプルを試しにやってみたいと思います。

↑のサービスの作成を選択

↑サンプルコンテナでテストを選択

↑未認証の呼び出しを許可にチェック
作成をクリックし、Cloud Runが作成されるまで待ちます。

作成されたら表示されているURLにアクセスし、以下の様な画面が表示されればOKです ✨

ポチポチやって簡単にデプロイして試す事ができました。

デプロイ方法

次は実際に自作したコンテナイメージをCloud Run上で動かしてみたいと思います。
コンテナイメージをPushする先は現時点では以下の2種類あります。

  • Container Registry
  • Artifact Registry

Artifact Registry は従来の Container Registry の機能を拡張したもので、
Docker イメージに加えて、言語・OSパッケージ管理も提供している
Container Registry と違い、1つのプロジェクト内に複数のリポジトリを作成可能で、その他にも様々な拡張がされている

gcloud使ってデプロイ

今回は gcloud CLIを使って Dockerfileを用意してコマンド実行するとおまかせでよしなにやってくれる方法を試してみたいと思います。

ディレクトリ構成

- gcp
    - README.md
    - docker-compose.yml
    - cloudrun
        - ソースファイル色々
        - .gcloudignore
        - deploy.sh
    - etc...

docker-compose.yml

version: '3.0'
services:
  gcloud:
    image: google/cloud-sdk:376.0.0-alpine
    volumes:
      - .:/usr/src
      - gcloud_config_data:/root/.config/gcloud
    working_dir: /usr/src
    entrypoint: bash

volumes:
  gcloud_config_data:

CLIが使える環境を↑の docker-compose.yml にて準備し、早速コンテナ起動してアカウントログインを実施してみます。

$ docker-compose run --rm gcloud
$ gcloud auth login --no-launch-browser

↑で表示されたURLにアクセスしGCPを扱うアカウントで連携、認証コードを入力してログインしておきます。
gcloud auth login  |  Google Cloud CLI Documentation

$ gcloud projects list # プロジェクト一覧確認
$ gcloud config set project [プロジェクト名] # 対象のプロジェクトを設定
$ gcloud config list # 設定されているか確認

↑で実際に連携されたかの確認や、デフォルトのプロジェクトなどを必要に応じて設定しておきます。

サンプルのExpressを実装する

試すものとして、Expressを用いて /hello にアクセスするとタイムスタンプを返すような簡単なものをデプロイしてみたいと思います。

const express = require('express');
const app = express();

const port = process.env.PORT || 8080;
app.listen(port, () => {
  console.log(`Listening on port ${port}.`);
});

app.get("/hello", async (req, res) => {
  const word = new Date();
  res.send(`Hello, timestamp ${word.getTime()}!`);
});

↑を index.js として保存。

 FROM node:12.4.0-alpine

 WORKDIR /usr/src/app
 COPY package*.json ./
 RUN yarn install
 COPY . ./
 CMD [ "yarn", "start" ]

Dockerfile を↑で用意しておく。

{
  "name": "node",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
     "start": "node index.js"
  },
  "dependencies": {
     "express": "^4.17.3"
  }
}

package.json は↑の様な設定。

#! /bin/bash

# エラーで処理中断
set -ex

# サービス名
export SERVICE_NAME=$1

gcloud run deploy \
  ${SERVICE_NAME}
  --region asia-northeast1
  --source .

今回は、デプロイ用のスクリプトとして deploy.sh を↑の内容で作成しました。
いざ、デプロイしてみます。

bash-5.1# ./deploy.sh express-sample
+ export SERVICE_NAME=express-sample
+ SERVICE_NAME=express-sample
+ gcloud run deploy express-sample
Deploying from source. To deploy a container use [--image]. See https://cloud.google.com/run/docs/deploying-source-code for more details.
Source code location (/usr/src/cloudrun):  

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] /usr/src/cloudrun` and `gcloud run deploy express-sample --image [IMAGE]`

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

Building using Dockerfile and deploying container to Cloud Run service [express-sample] in project [xxxxx] region [asia-northeast1]
⠹ Building and deploying new service... Building Container.                              
  ✓ Creating Container Repository...                                                     
  ✓ Uploading sources...                                                                 
  ⠹ Building Container... Logs are available at [https://console.cloud.google.com/cloud-b
  uild/builds/........].                
  . Creating Revision...                                                                 
  . Routing traffic...                                                                   
  . Setting IAM Policy...     

デプロイに成功するとGCP内に関連したリソースとして

  • Cloud Storage
  • Code Build
  • Artifact Registry
  • Cloud Run
    に何かしらリソースが作成されます。

今回作成されたサービスの詳細は

  • 容量

  • 変数とシークレット

  • 接続

↑の様な設定でデプロイされていました。

削除

$ gcloud run services delete SERVICE-NAME

削除対象としては Cloud Run のみで Cloud StorageやCode Build, Artifact Registryは残ったままでした。

Railsを動かしてみる

Cloud Run 環境での Rails の実行  |  Ruby  |  Google Cloud
↑の記事を参考に、Cloud RunでRailsを動かして、DatabaseはCloudSQL for MySQLと連携する想定で試してみます。

環境構築

以下のVerでRailsプロジェクトを新規作成し試してみます。

- Ruby: 3.1.0
- Rails: 7.0.1

DB設定

最終的な database.yml は以下のようになりました。(CloudSQLはパブリックIPとした場合)
staging がCloudSQLの接続設定になります。

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  host: <%= ENV.fetch('RAILS_DB_HOST') { 'mysql' } %>

development:
  <<: *default
  database: xxxx_development

test:
  <<: *default
  database: xxxx_test

staging:
  <<: *default
  database: xxxx_staging
  username: <%= ENV.fetch('CLOUDSQL_USERNAME') { 'root' } %>
  password: <%= ENV.fetch('CLOUDSQL_PASSWORD') { '' } %>
  socket: <%= ENV.fetch('CLOUDSQL_SOCKETPATH') { '' } %>
  host: localhost

ログに関して

GKE上RailsのアプリケーションログをStackdriver Loggingで運用する方法 | by Riki Shimma | Medium

↑を見る限り方法は2通りありそうで、

  1. google-cloud-ruby gem を利用して、API経由でログを保存する
  2. 標準出力
    単純に 2. 標準出力 する方が簡単そうだったので設定を追加
  config.log_formatter = ::Logger::Formatter.new
  if ENV['RAILS_LOG_TO_STDOUT'].present?
    logger           = ActiveSupport::Logger.new(STDOUT)
    logger.formatter = config.log_formatter
    config.logger    = ActiveSupport::TaggedLogging.new(logger)
    config.colorize_logging = false
  end

プライベートIPなCloudSQL接続にSSLを使用する

Serverless VPC Accessのコネクタを通じてSSLでプライベートIPなCloud SQLに接続する場合

development:
  adapter: mysql2
  database: some_database
  host: localhost
  username: rails
  password: password
  sslca: /path/to/rails/root/db/cacert.pem
  sslkey: /path/to/rails/root/db/test01.key
  sslcert: /path/to/rails/root/db/test01.cer

こちらを参考に事前に証明書を作成し、参照できる場所に格納しておきます。

デプロイ用の deploy.sh の内容を以下で作成して実行します。

#! /bin/bash

# エラーで処理中断
set -ex

# dev / stg / prod
export ENV=$1

# CloudSQL接続先
export INSTANCE_CONNECTION_NAME=$2

# MASKTER_KEY
export RAILS_MASTER_KEY=$3

# CloudSQLのパスワード
export CLOUDSQL_PASSWORD=$4

# CloudSQLのホスト
export CLOUDSQL_HOST=$5

# サービス名
export SERVICE_NAME=xxxx-${ENV}

gcloud run deploy \
  ${SERVICE_NAME} \
  --min-instances 1 \
  --add-cloudsql-instances ${INSTANCE_CONNECTION_NAME} \
  --set-env-vars "RAILS_ENV=${ENV}" \
  --set-env-vars "RAILS_LOG_TO_STDOUT=true" \
  --set-env-vars "RAILS_MASTER_KEY=${RAILS_MASTER_KEY}" \
  --set-env-vars "CLOUDSQL_USERNAME=xxxx" \
  --set-env-vars "CLOUDSQL_PASSWORD=${CLOUDSQL_PASSWORD}" \
  --set-env-vars "CLOUDSQL_HOST=${CLOUDSQL_HOST}" \
  --vpc-connector=xxxxxx \
  --region asia-northeast1 \
  --source .

バッドノウハウ

Cloud Run上でRailsからCloud SQLに接続できない時は host: localhost が必要かも
host でエラーが出ていたので、↑の設定をしたらいけました :sparkles:

GCPのPubSubトリガーで反応するサンプルをデプロイする

Pub/Sub の push によるトリガー  |  Cloud Run のドキュメント  |  Google Cloud
↑こちらを参考に試してみたいと思います。

トピックの作成

今回使用するトピック名は test-topic として進めていきます。
CLIでトピック作成

$ gcloud pubsub topics create test-topic
#=> Created topic [projects/[プロジェクト名]/topics/test-topic].

ExpressのサンプルをPubSub用に修正する

こちらを参考に修正。

const express = require('express');
const app = express();

app.use(express.json());

const port = process.env.PORT || 8080;
app.listen(port, () => {
  console.log(`Listening on port ${port}.`);
});

app.post('/', (req, res) => {
  if (!req.body) {
    const msg = 'no Pub/Sub message received';
    console.error(`error: ${msg}`);
    res.status(400).send(`Bad Request: ${msg}`);
    return;
  }
  if (!req.body.message) {
    const msg = 'invalid Pub/Sub message format';
    console.error(`error: ${msg}`);
    res.status(400).send(`Bad Request: ${msg}`);
    return;
  }

  const pubSubMessage = req.body.message;
  const name = pubSubMessage.data
    ? Buffer.from(pubSubMessage.data, 'base64').toString().trim()
    : 'NONE';

  console.log(`Receive ${name}!`);
  res.status(204).send();
});

module.exports = app;

早速デプロイする

$ ./deploy.sh pubsub-sample

Pub/Sub と Cloud Run の連携設定

  1. プロジェクトで Pub / Sub 認証トークンを作成できるようにする

    $ gcloud projects add-iam-policy-binding $PROJECT_ID \
        --member=serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com \
        --role=roles/iam.serviceAccountTokenCreator
    
  2. Cloud Run に作成したサービスを起動するためのサービスアカウントを作成

    $ gcloud iam service-accounts create cloud-run-pubsub-invoker \
        --display-name "Cloud Run Pub/Sub Invoker"
    
  3. 作成したサービスアカウントにサービスを起動する権限を付与

    $ gcloud run services add-iam-policy-binding $SERVICE_NAME \
        --member=serviceAccount:cloud-run-pubsub-invoker@${PROJECT_ID}.iam.gserviceaccount.com \
        --role=roles/run.invoker
    

あとは、トピックを購読するサブスクリプションを、endpointをcloudrunのURLに、auth-service-account を↑で作成したサービスアカウントを設定して作成してやればOKです。

Firestoreにアクセスする

Cloud Runの中からFirestoreにアクセスする方法を調査。
実は「Cloud Datastore ユーザー」の権限を持つサービスアカウントを関連づけてしまえばアクセスできるようになります。

実装例

@google-cloud/firestoreパッケージを追加

$ yarn add @google-cloud/firestore

パッケージ追加したらそのまま特になにもしなくても使用できます。

import { Firestore } from '@google-cloud/firestore';

const db = new Firestore();

  app.get('/fs', async (req: express.Request, res: express.Response) => {
    const ret = await db.doc('xxxx').get();
    res.json(ret.data());
  });

参考URL

Discussion