はじめに
私のある勤め先では Google CloudRun (GCR) を使っております。
その上、スキルアップ・福利厚生・特定の業務中の検証環境を兼ねて Google Cloud (GC) の課金済の無料枠も全エンジニアに設定されています ✨️
CI/CD がしっかりしているおかげか、職場としては GCR を使っているものの私個人としてはあまり使い方をわかっていないサービスなので今回の三連休を活用してキャッチアップしてみることにしました 🙌
作ったもの
Gather のオンライン数を Slack へ投稿する Bot を作ってみました!
今回はこちらの実装とデプロイまでのステップを紹介します。
※Gather はロールプレイングゲームのような UI を持つバーチャルオフィスサービスの1つです🎮️
(余談1: 本当はゲームみたいに「〇〇さんがオンライン/オフラインになりました!」とリアルタイムな通知を出してみたかったのですが社内の個人に割り当てられた GC の枠で常時起動しているものを動かすのは厳しそうだったので諦めました🙏)
(余談2: オンライン数を定期的に投稿するだけならサーバレス利用がOKになった(らしい) GitHub Actions の Cron スケジュールイベントの機能で作っても良かったかも)
【1】 ローカルでの開発の準備
以下の package.json
を用意し npm install
今回は Node.js を使います。
※tsconfig.json
やbiome.json
の内容はお好みで(tsc
の出力先はdist
ディレクトリになるようにしてください)
{
"name": "gather-online-counter-on-slack",
"version": "1.0.0",
"description": "Gather Town のオンライン数を Slack へ投稿します",
"keywords": [
"gather-town",
"slack"
],
"main": "./dist/index.js",
"scripts": {
"build": "tsc",
"lint:fix": "npx @biomejs/biome check --write --unsafe ./src",
"lint:pkg": "npx sort-package-json",
"quick": "ts-node ./src/index.ts",
"start": "node ./dist/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@gathertown/gather-game-client": "^43.0.1",
"dotenv": "^16.4.7",
"isomorphic-ws": "^5.0.0",
"typescript": "^5.7.3"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"sort-package-json": "^2.14.0",
"ts-node": "^10.9.2"
}
}
Dockerfile
と docker-compose.yaml
を準備
GCR では 8080 ポートを使います。
FROM node:23-alpine3.20
WORKDIR /app
COPY package.json /app/package.json
COPY package-lock.json /app/package-lock.json
COPY tsconfig.json /app/tsconfig.json
COPY src/ /app/src
RUN npm i && npm run build
CMD ["npm", "start"]
version: '3'
services:
app:
build:
dockerfile: Dockerfile
context: ./
volumes:
- ./:/app
ports:
- "8080:8080"
env_file:
- .env
tty: true
stdin_open: true
【2】 各種サービスの API トークンなどを準備・確認する
確認した内容は env
ファイルにまとめてください
GCR
GC の今回使う PROJECT_ID
を確認し手元にひかえてください
Slack
-
Automations
を開く -
New Workflow
を押す -
count
プロパティの内容を Slack チャンネルに投稿できる Workflow を作成し、Web request URL
をコピーし手元にひかえてください
Gather
API キー
https://app.gather.town/apikeys にて取得し手元にひかえてください
スペースID と スペース名
対象の Gather スペースの URL の内、以下の部分がそれぞれ スペースID と スペース名 であるため手元にひかえてください
https://app.gather.town/app/GATHER_SPACE_ID/GATHER_SPACE_NAME
最終的にできあがる env
ファイル (例)
# app (後述のコードで使います)
PORT="8080"
UUID="" # 適当に生成し設定してください
# Slack の Workflow から webhook トリガーのものを作成し count ペイロードを受け取って表示できるようにしてください 例: https://hooks.slack.com/triggers/...
SLACK_WEBHOOK_URL=""
# https://app.gather.town/apikeys
GATHER_API_KEY=""
# https://app.gather.town/app/GATHER_SPACE_ID/GATHER_SPACE_NAME
GATHER_SPACE_ID=""
GATHER_SPACE_NAME=""
【3】 実装
src
ディレクトリを用意し、その中に index.ts
ファイルを作成して以下のコードを書いて保存してください。
また npm run build
できるか確認してください。
import http from 'node:http';
import { Game } from '@gathertown/gather-game-client';
import dotenv from 'dotenv';
import isomorphicWS from 'isomorphic-ws';
global.WebSocket = isomorphicWS;
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const STAGE = String(process.env.STAGE).toLocaleLowerCase();
if (STAGE === 'local') {
dotenv.config({ path: '.env' });
}
// app
const PORT = process.env.PORT || 8080;
const UUID = process.env.UUID || '';
// gather
const GATHER_API_KEY = process.env.GATHER_API_KEY || '';
const GATHER_SPACE_ID = process.env.GATHER_SPACE_ID || '';
const GATHER_SPACE_NAME = process.env.GATHER_SPACE_NAME || '';
const GATHER_SPACE_INFO = `${GATHER_SPACE_ID}\\${GATHER_SPACE_NAME}`;
// slack webhook url
const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL || '';
if (!UUID) {
console.error('UUID is not set');
process.exit(1);
}
if (!GATHER_API_KEY || !GATHER_SPACE_ID || !GATHER_SPACE_NAME) {
console.error('GAME_API_KEY, GAME_SPACE_ID, and GAME_SPACE_NAME must be provided from environment variables');
process.exit(1);
}
if (!SLACK_WEBHOOK_URL) {
console.error('SLACK_WEBHOOK_URL must be provided from environment variables');
process.exit(1);
}
http
.createServer((req, res) => {
// 念の為簡易に受け付けるリクエストを制限する
if (req.url === `/count/${UUID}`) {
res.writeHead(202, { 'Content-Type': 'application/json' });
res.end(`{ message: "request received to count gather online users" }\n`);
const gather = new Game(GATHER_SPACE_INFO, () => Promise.resolve({ apiKey: GATHER_API_KEY }));
gather.subscribeToConnection((connection) => {
if (!connection) {
return;
}
countAndReport(gather);
});
gather.connect();
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>gather-online-counter-on-slack</h1>\n');
}
})
.listen(PORT, () => {
console.log(`[LOG] Server running at http://localhost:${PORT}/`);
});
const countAndReport = async (gather: Game) => {
while (!Object.keys(gather.players).length) {
await wait(100);
}
const countOfPlayers = Object.keys(gather.players).length;
gather.disconnect();
// Slack の Webhook に対して count: countOfPlayers を fetch で送信する
const payload = {
count: `${countOfPlayers}`,
};
await fetch(SLACK_WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
console.log(`[LOG] gather online users: ${countOfPlayers}`);
};
【4】 docker compose up --build
最終的にこんな感じで起動すれば OK です
[+] Running 1/1
✔ Container bot Recreated 0.1s
Attaching to app-1
app-1 |
app-1 | > gather-online-counter-on-slack@1.0.0 start
app-1 | > node ./dist/index.js
app-1 |
[LOG] Server running at http://localhost:8080/
【5】 デプロイ
cloudbuild.yaml
を作成
※今回、アプリケーション名とタグ名はgather-online-counter-on-slack/development
※このファイルで今回主に作成するべきファイルは以上です!より詳細にファイル構造がわかるよう後日GitHubなどで公開できればと思います
steps:
- name: "gcr.io/cloud-builders/docker"
args:
[
"build",
"-f",
"Dockerfile",
"-t",
"gcr.io/$PROJECT_ID/gather-online-counter-on-slack/development",
".",
]
images:
- "gcr.io/$PROJECT_ID/gather-online-counter-on-slack/development"
gcloud builds submit
を実行(イメージを GCR 上に準備)
実行後、ビルドの経過がターミナル上に表示されるのでしばらくお待ち下さい
gcloud builds submit --project={【2】で準備したGCのPROJECT_ID} --config cloudbuild.yaml
GCR へデプロイ
gcloud run deploy gather-online-counter-on-slack --project={【2】で準備したGCのPROJECT_ID} --image="gcr.io/{【2】で準備したGCのPROJECT_ID}/gather-online-counter-on-slack/development" --platform=managed --region=asia-northeast1 --allow-unauthenticated --set-env-vars "UUID={適当に生成}" --set-env-vars "GATHER_API_KEY={Gatherから}" --set-env-vars "GATHER_SPACE_ID={Gatherから}" --set-env-vars "GATHER_SPACE_NAME={Gatherから}" --set-env-vars "SLACK_WEBHOOK_URL={Slackから}"
-
--set-env-vars "KEY1=VAL1"
で環境変数を設定できます -
--allow-unauthenticated
でとくに認証なく8080
ポートで動いているアプリケーションへアクセスできます
※上手くデプロイできればこのような表示になるはず |
---|
https://console.cloud.google.com/run?hl=ja&project={【2】で準備したGCのPROJECT_ID} |
![]() |
動作確認
GC の画面から URL https://???.asia-northeast1.run.app
をコピーし末尾に /count/【2】で用意したUUID
の URL` を書き加えてブラウザなどで開いてください。
このような画面が表示されしばらくすると、 Slack へ冒頭で紹介したような通知がながれるかと思います。
あとは uptimerobot とかでお好みの間隔で定期的に URL に対してリクエストされるようにすることで Slack へ定期的に Gather のオンライン数が投稿されます
さいごに
今回は GCR でデプロイしたいものの実装〜デプロイまでできて良かったです!
思ったよりも使いやすかったので、これからも GCR や Gather/Slack などの API その他でいろいろ作っていければと思います
ここまで、ご高覧ありがとうございました!