Open24

時雨堂クラウドサービスを支える技術

自社パッケージ製品のクラウド版 を開発していて、色々やりたい放題やってるのでメモ。

方針

  • 遠回り駆動開発
  • やりたい放題やる
  • 王道は無視して「ぼくのかんがえたさいきょうの」をでいく
  • 可能な限り OSS 開発元が提供しているクラウドサービスを利用する
  • ベアメタルサーバーを使う

HTTP アプリ

  • Cloudflare Load Balancing を利用したラウンドロビンを行う
  • 宛先は Vultr と Linode
  • Cloudflare オリジンサーバ証明書を利用

Vultr

Linode

  • ブラウザ -> Cloudflare -> Cloudflare Workers -> Cloudflare Load Balancing -> Linode FW -> Linode App -> TimescaleDB
    • Linode FW にて Linode App は Cloudflare からのみリクエストを受ける

WebRTC SFU

  • クライアント -> nginx -> WebRTC SFU Sora
    • nginx で TLS 終端
  • Sora ウェブフック処理
    • Sora -> <mTLS> -> Cloudflare Workers -> ... (以下省略)
  • Sora API 処理
    • HTTP クライアント -> Cloudflare -> Clouflware Workers -> Sora
    • Vutlr App | Linode App -> <Tailscale> -> Sora (DataPacket)
  • Let's Encrypt を利用

Cloudflare

ウェブフック署名

Stripe の 時間+JSON への HMAC を利用したシンプルな署名の仕組みを採用。

HTTP テストクライアント

HTTP API のドキュメント例には HTTPie – API testing client that flows with you を採用。

バックエンド

テスト

go.mod

go 1.19

require (
	github.com/BurntSushi/toml v1.2.1
	github.com/getsentry/sentry-go v0.15.0
	github.com/go-playground/validator/v10 v10.11.1
	github.com/google/uuid v1.3.0
	github.com/jackc/pgconn v1.13.0
	github.com/jackc/pgtype v1.12.0
	github.com/jackc/pgx/v4 v4.17.2
	github.com/labstack/echo-contrib v0.13.0
	github.com/labstack/echo/v4 v4.9.1
	github.com/lestrrat-go/jwx/v2 v2.0.8
	github.com/meilisearch/meilisearch-go v0.21.1
	github.com/minio/minio-go/v7 v7.0.45
	github.com/rs/zerolog v1.28.0
	github.com/shiguredo/lumberjack/v3 v3.0.0
	github.com/shogo82148/go-clockwork-base32 v1.0.0
	github.com/slack-go/slack v0.11.4
	golang.org/x/sync v0.1.0
)

自社パッケージ製品以外は Vultr と Linode をマルチクラウドとして採用。

Vultr

Linode

DataPacket

自社製品は DataPacket。

  • 10Gbps Unmetered Dedicated Servers | DataPacket.com
    • IPMI (ntelligent Platform Management Interface) を利用可能
  • Discord が利用している転送量無制限のベアメタルサーバ
  • 帯域で課金される
    • かなり柔軟に対応してくれる
  • IPv6 は無料で取得可能
  • AMD EPYC 7443P 24 Cores, 48 Threads, 2.85 GHz / MEM 128 GB / SSD 500 GB x 2
  • 1 回線を複数サーバで契約する Bandwidth Pool を利用

OS

  • Ubuntu 20.04 LTS を採用
  • Ubuntu Advantage for Infrastructure (Physical server) を契約し Livepatch を利用することで WebRTC SFU のダウンタイムを減らしている

Bandwidth pool

Bandwith pool allows you to have single traffic plan for servers within same continent (or between EU & US servers). For example, your servers can share 20 Gbps regardless of their individual consumption.

Bandwithプールにより、同一大陸内のサーバー(またはEUとUSのサーバー)に対して、単一のトラフィックプランを設定することができます。例えば、各サーバーの消費量に関係なく20Gbpsを共有することができます。

https://www.datapacket.com/use-cases

Video streaming services
To meet the infrastructure challenges associated with delivering video content to thousands of simultaneous viewers, our 32 or 64-core streaming servers are equipped with dual 40GE ports and come with scalable bandwidth pools of 10–900 Gbps. Each video packet is delivered efficiently using uncongested optical fibre paths.

何千人もの視聴者に同時にビデオコンテンツを配信するというインフラの課題に対応するため、32コアまたは64コアのストリーミングサーバーには、40GEのデュアルポートが装備され、10~900Gbpsのスケーラブルな帯域プールが付属しています。各ビデオパケットは、混雑していない光ファイバー経路を使用して効率的に配信されます。

Hidden comment

マルチクラウド

Cloudflare の後ろにいるアプリサーバをマルチクラウド化する。Vultr と Akamai に買収された Linode で構成することにした。これでどちらかのアプリが落ちたりメンテナンスになっても稼働し続けられる。

構成はシンプルで Cloudflare LB でそれぞれのアプリにたいしてラウンドロビンをするだけ。

  • Cloudflare Workers -> Cloudflare LB -> Vultr App -> Managed Service for TimescaleDB (GCP)
  • Cloudflare Workers -> Cloudflare LB -> Linode App -> Managed Service for TimescaleDB (GCP)

これで Vultr と Vultr App のどちらかが落ちてもなんとかなる。セッションは Cloudflare Workers KV / DO で管理しているので、気にしなくて良い。もし何か App で共有したいものがあれば TimescaleDB か Redis Labo の Redis を立てる。

監視はそれぞれに Tailscale をインストールしてうまいことやる。

DNS ラウンドロビン

Cloudflare Cloudflare Load Balancing を利用した DNS ラウンドロビンを利用して バックエンドを Vutlr と Linode のマルチクラウド。

Load balancing with DNS records · Cloudflare DNS docs

Beyond reducing requests to your origin server, this setup allows your application to take advantage of Cloudflare’s Zero downtime failover. When a request to one IP address fails, Cloudflare automatically retries the request to other IP addresses associated with the same hostname. This behavior prevents end users from experiencing downtime.

DeepL Pro を利用した翻訳

オリジンサーバへのリクエストを減らすだけでなく、このセットアップにより、アプリケーションはCloudflareのゼロダウンタイムフェイルオーバーを利用することができます。あるIPアドレスへのリクエストに失敗すると、Cloudflareは自動的に同じホスト名に関連する他のIPアドレスへのリクエストを再試行します。この動作により、エンドユーザーがダウンタイムを経験することがありません。

E2E テスト

E2E テストは Python の pytest + Playwright + Requests を採用。

GitHub Actions の手動実行を利用してる。

データベース

PostgreSQL + TSDB の TimescaleDB を採用

デプロイ

  • Ansible is Simple IT Automation
    • 温かみのある手動デプロイ
    • デプロイの自動化は当面行わず手動デプロイ
      • 早すぎる最適化はやらない

S3 互換オブジェクトストレージアップロード

特定のフォルダを監視し、ファイルが追加されたら S3 互換ストレージにアップロードする仕組み。

全文検索

  • オンラインドキュメントの全文検索
    • Sphinx がターゲット
  • ログの全文検索
    • TimescaleDB や R2 に保存しているログの全文検索

Meilisearch

Rust で書かれた全文検索エンジン Meilisearch を採用。2022 年に入って日本語にも対応。

0.5C/1G 程度のインスタンスで 20 万ドキュメント程度は処理できる模様。

ドキュメント全文検索

Sphinx をターゲットとして docs-scraper を利用してインデックスを作成し、Sphinx 側で docs-searchbar.js を利用して、インスタント検索を実現する。

Meilisearch と Sphinx による全文検索は別途記事にまとめてある。

Meilisearch を利用して Sphinx で日本語全文検索を実現する

構成

  • Cloudflare DNS Proxy + Nginx + Meilisearch
    • Nginx に Cloudflare Origin 証明書利用
    • Nginx は HTTP/2 対応
  • Vultr Optimized Cloud Compute
    • CPU Optimized
    • 2C/ 4G
    • 50G NVMe
    • Ubuntu 22.04 LTS

docs-scraper

synonyms

類義語は Meilisearch API を叩くことで実現可能。

Synonyms | Meilisearch Documentation

synonyms.json を更新すると GitHub Actions 経由で PUT する仕組みを用意した。

ログ全文検索

RDB や TSDB は検索には向いていないのでログ検索は Meilisearch を利用する。

構成

  • Cloudflare DNS Proxy + Nginx + Meilisearch
    • Nginx に Cloudflare Origin 証明書利用
    • Nginx は HTTP/2 対応
  • Linode ハイメモリプラン
    • 2C/ 24G
    • ブロックストレージ利用
    • Ubuntu 22.04 LTS

Meilisearch を利用してマルチテナントを実現する方法

Meilisearch の方針としては一時的なトークンを発行するという考えの模様。トークンは JWT 方式でその中に index と filter が設定できる。そのためそのトークンは ~ しか検索できないという縛りが可能になる。

Melisearch + Go

JWT を生成する方法は別途まとめてある。

Meilisearch でマルチテナント

定期削除

顧客向けのログ検索は一定期間が過ぎたインデックスは削除するようにしたいが、それには Delete documents by batch を利用する。

Documents | Meilisearch Documentation

Meilisearch Cloud

日本リージョンはない。かつ月 $1200 からなので、まだ手が出ない。

https://www.meilisearch.com/pricing

ログ

ログの収集は Fluent Bit を採用。

JSON Lines 形式

Erlang VM 特殊形式

Erlang が出力する crash.log と erl_crash.dump はかなり特殊なフォーマットなため、JSONL のようには扱えない。さらに、これらのログでた場合は基本的にはバグなので効率よく共有したい。

ToDo

フロントエンド

フロントエンドは TypeScript + Remix を利用。セッション管理は Cloudflare Workers で行う。

フロントエンドアプリ

package.json

  "dependencies": {
    "@remix-run/cloudflare": "1.7.6",
    "@remix-run/cloudflare-workers": "1.7.6",
    "@remix-run/react": "1.7.6",
    "@remix-validated-form/with-zod": "2.0.5",
    "chart.js": "4.0.0",
    "cross-env": "7.0.3",
    "pretty-bytes": "6.0.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "remix-auth": "3.3.0",
    "remix-validated-form": "4.6.4",
    "zod": "3.19.1"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "3.18.0",
    "@playwright-testing-library/test": "4.5.0",
    "@playwright/test": "1.28.1",
    "@remix-run/dev": "1.7.6",
    "@remix-run/eslint-config": "1.7.6",
    "@tailwindcss/aspect-ratio": "0.4.2",
    "@tailwindcss/line-clamp": "0.4.2",
    "@tailwindcss/typography": "0.5.8",
    "@testing-library/dom": "8.19.0",
    "@testing-library/jest-dom": "5.16.5",
    "@testing-library/react": "13.4.0",
    "@testing-library/user-event": "14.4.3",
    "@types/react": "18.0.25",
    "@types/react-dom": "18.0.9",
    "@vitejs/plugin-react": "2.2.0",
    "concurrently": "7.6.0",
    "daisyui": "2.42.1",
    "eslint": "8.28.0",
    "eslint-config-prettier": "8.5.0",
    "miniflare": "2.11.0",
    "prettier": "2.8.0",
    "tailwindcss": "3.2.4",
    "typescript": "4.9.3",
    "vite": "3.2.4",
    "vite-tsconfig-paths": "3.6.0",
    "vitest": "0.25.3",
    "wrangler": "2.4.4"
  },
  "engines": {
    "node": ">=18.12"
  }
作成者以外のコメントは許可されていません