時雨堂クラウドサービスを支える技術 v1
v2 へ移行中です
円安の影響や、自社製品がスケールアウトに対応したこと、Cloudflare LB の WebSocket の挙動が残念だったことなどを踏まえ、サービス構成を変更を検討中です。
-
脱 Vultr
- Egress 料金が Linode だと Vultr の半額
- Linode へ移行
-
脱 Cloudflare LB
- Linode の NodeBalancers へ移行
- マルチリージョンでの利用は Linode の Akamai Global Load Balancer 公開待ち
-
脱 Cloudflare
- サポートに不安があるため Akamai へ移行
-
脱 TimescaledB
- 移行は Linode PostgreSQL の提供開始待ち
- OLAP は DuckDB + S3 互換オブジェクトストレージへ移行
- ログ保存は VictoriaLogs へ移行予定
-
脱 DataPacket
- Linode Dedicated CPU へ移行
- 自社製品が Raft ベースの分散システムになったため小さなサーバをたくさん上げた方が効率がいい
設計しつつ、少しずつ移行中です。そのうち v2 を書こうと思います。
自社パッケージ製品のクラウド版 を開発していて、色々やりたい放題やってるのでメモ。
方針
- 遠回り駆動開発
- やりたい放題やる
- 王道は無視して「じぶんのかんがえたさいきょうの」をでいく
- 可能な限り OSS 開発元が提供しているクラウドサービスを利用する
- ベアメタルサーバーを使う
- 三大クラウドサービス (AWS, GCP, Azure) を使わない
なぜ利用している技術を公開するのか
自社で使って良かった OSS やサービスはより多くの人に知って欲しいと考えています。
また、特に隠す理由がないというのもあります、むしろ大きな声で Tailscale や TimescaleDB 、 VictoriaMetrics 、Cloudflare 、DataPacket など、ほんとうに素晴らしい使わせて頂いていると言っていきたい。
HTTP アプリ
- Cloudflare Load Balancing を利用したラウンドロビンを行う
- 宛先は Vultr と Akamai Connected Cloud
- Cloudflare オリジンサーバ証明書を利用
- DNS は指定なし 、LB からは IP での指定
- App は Cloudflare からのみリクエストを受け付ける
Vultr と Akamai Connected Cloud
- ブラウザ -> Cloudflare -> Cloudflare Workers -> Cloudflare Load Balancing -> App -> TimescaleDB
App が Vultr と Akamai Connected Cloud。
自社パッケージ製品 WebRTC SFU Sora
- WebRTC SFU Sora は Erlang/OTP を利用
- WebSocket (シグナリング)
- クライアント -> nginx -> WebRTC SFU Sora
- Cloudflare LB は WebSocket 突然切断される問題があり採用を取りやめた
- nginx で TLS 終端
- LB では Host 名を指定
- クライアント -> nginx -> WebRTC SFU Sora
- TURN-UDP
- クライアント -> WebRTC SFU Sora
- TURN-TCP
- クライアント -> nginx -> WebRTC SFU Sora
- TURN-TLS
- クライアント -> nginx -> WebRTC SFU Sora
- nginx で TLS 終端
- Sora ウェブフック処理
- Sora -> <mTLS> -> Cloudflare Workers -> ... (以下省略)
- Sora API 処理
- HTTP クライアント -> Cloudflare -> Clouflware Workers -> Sora
- Vutlr App | Akamai Connected Cloud App -> <Tailscale> -> Sora (DataPacket)
-
Let's Encrypt を利用
- スポンサーになってる https://letsencrypt.org/sponsors/
- DNS-01
Cloudflare
- すべての HTTP リクエストは Cloudflare 経由で処理する
- Cloudflare Workers®
- SSL/TLS encryption mode is Full (strict)
- Cloudflare Load Balancing | DNS Based Load Balancing Solution | Cloudflare
- 管理サイトのセキュリティは Cloudflare Zero Trust を利用
- Cloudflare Access の メール認証を採用
- Cloudflare Workers -> Vutlr App と Akamai Connected Cloud App は Basic 認証を採用
- デプロイは GitHub Actions 経由
- Custom Domains · Cloudflare Workers docs を採用
- Workers のログ書き込みなどは Cloudflare Queues を採用予定
-
Smart Placement (beta) · Cloudflare Workers docs
- 導入済み
ウェブフック署名
Stripe の 時間+JSON への HMAC を利用したシンプルな署名の仕組みを採用。
HTTP テストクライアント
HTTP API のドキュメント例には HTTPie – API testing client that flows with you を採用。
バックエンド
- Go を利用。
- OS は Ubuntu 22.04 を利用
-
labstack/echo: High performance, minimalist Go web framework
- Cloudflare オリジンサーバ証明書を利用して HTTPS で受信
-
sqlc-dev/sqlc: Generate type-safe code from SQL
- SQL からコードジェネレートする sqlc を利用
-
jackc/pgx: PostgreSQL driver and toolkit for Go
- 慣れてるので
-
rs/zerolog: Zero Allocation JSON Logger
- 慣れてるので
- slog は検討したが zerolog で困っていないため zerolog を継続することにした
-
lestrrat-go/jwx: Implementation of various JWx (Javascript Object Signing and Encryption/JOSE) technologies
- テストコードが多く、やりたいことが見つけやすい
- メンテナンスが積極的にされている
-
go-ini/ini: Package ini provides INI file read and write functionality in Go
- INI で困ることがないことに気付いた
-
cosmtrek/air: ☁️ Live reload for Go apps
- 開発用の自動リロード
- .air.toml で設定できて大変良い
-
go-playground/validator: :100:Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving
- 慣れているので
- ローカルでの開発や検証は docker compose を利用
- docker compose up / down は本当に楽
- https://zenn.dev/shiguredo/articles/docker-compose-timescaledb-meilisearch
テスト
- mock は利用しない
-
ory/dockertest: Write better integration tests! Dockertest helps you boot up ephermal docker images for your Go tests with minimal work.
- dockertest で TimescaleDB を docker で実際に起動して利用している
- dockertest を Testcontainers へ切り替え中
-
stretchr/testify: A toolkit with common assertions and mocks that plays nicely with the standard library
- assert モデルが好きなので
go.mod
go 1.22.0
require (
github.com/conduitio/bwlimit v0.1.0
github.com/coreos/go-oidc/v3 v3.9.0
github.com/getsentry/sentry-go v0.27.0
github.com/go-playground/validator/v10 v10.18.0
github.com/gofrs/uuid/v5 v5.0.0
github.com/jackc/pgx/v5 v5.5.3
github.com/labstack/echo-contrib v0.15.0
github.com/labstack/echo/v4 v4.11.4
github.com/lestrrat-go/jwx/v2 v2.0.20
github.com/meilisearch/meilisearch-go v0.26.2
github.com/minio/minio-go/v7 v7.0.67
github.com/rs/zerolog v1.32.0
github.com/shogo82148/go-clockwork-base32 v1.1.0
github.com/slack-go/slack v0.12.4
github.com/urfave/cli/v2 v2.27.1
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
golang.org/x/sync v0.6.0
gopkg.in/ini.v1 v1.67.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)
// テストのみ
require (
github.com/ory/dockertest/v3 v3.10.0
github.com/stretchr/testify v1.8.4
)
自社パッケージ製品以外は Vultr と Akamai Connected Cloud をマルチクラウドとして採用。
Vultr
-
SSD VPS Servers, Cloud Servers and Cloud Hosting by Vultr - Vultr.com
- 証明書は Cloudflare のオリジン証明書を利用するので更新の必要性なし
- デフォルト 15 年
-
Vultr Block Storage
- HDD と NVMe SSD が選べる
- NVMe SSD は VictoriaMetrics のストレージとして利用
- HDD は MinIO のストレージとして利用
- 証明書は Cloudflare のオリジン証明書を利用するので更新の必要性なし
- FW や LB などは利用していない
- Tokyo リージョンがあり、シンプルで使いやすい、そして安い
Akamai Connected Cloud
- Cloud Computing Services | VMs, Kubernetes, Storage | Linode
- FW や LB などは利用していない
- Tokyo リージョンがあり、シンプルで使いやすい、そして転送量が安い
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 1.92 TB x 2
- 1 回線を複数サーバで契約する Bandwidth Pool を利用
詳細は別途記事にしてある。
DataPacket 雑感
OS
- Ubuntu 22.04 LTS を採用
- Ubuntu Pro (Infra-only) を契約し 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のスケーラブルな帯域プールが付属しています。各ビデオパケットは、混雑していない光ファイバー経路を使用して効率的に配信されます。
監視と可視化
-
VictoriaMetrics/VictoriaMetrics: VictoriaMetrics: fast, cost-effective monitoring solution and time series database
- 監視は VictoriaMetrics を採用
- クラスター構築、かつ長期的な保存をしたいので
- prometheus/node_exporter: Exporter for machine metrics
- nginxinc/nginx-prometheus-exporter: NGINX Prometheus Exporter for NGINX and NGINX Plus
-
prometheus/blackbox_exporter: Blackbox prober exporter
- Cloudflare Worker や Vultr や Akamai Connected Cloud などを監視する
-
Process Exporter Metrics
- プロセス統計の監視は fluent-bit を利用
-
shiguredo/sora_exporter: Prometheus exporter for WebRTC SFU Sora metrics.
- 自社製品用の exporter
-
Monitor MST with Prometheus | Timescale Docs
- Managed TimescaleDB 側で Prometheus Endpoint を用意してくれている
-
Grafana: The open observability platform | Grafana Labs
- これ1択
サーバ間通信
-
Tailscale · Best VPN Service for Secure Networks を採用
- Team プランを利用
- DataPacket 間は常にプライベートネットワークを利用する方法がなかったため利用を停止
-
Vultr <-> DataPacket
-
Akamai Cloud <-> DataPacket
-
Vultr <-> Akamai Cloud
-
- サーバー間通信を Tailscale を使って解決している話
-
ベアメタルサーバーを利用したクラウドサービスで発生する課題を Tailscale で解決する · Tailscale
- Tailscale の顧客事例に掲載してもらってる
メール送信
-
Transactional Email API Service For Developers | Mailgun
- http client だけで使えるので簡単
オンラインドキュメント
-
sphinx-doc/sphinx: Main repository for the Sphinx documentation builder
- 開発者 https://github.com/AA-Turner のスポンサーになってる
-
shiguredo/sphinx_shiguredo_theme: 時雨堂 Sphinx テーマ
- 独自にテーマ作成している
-
Cloudflare Pages
- デプロイ先として利用
- Cloudflare Access を利用して main ブランチ以外は閲覧制限をかけている
マルチクラウド
Cloudflare の後ろにいるアプリサーバをマルチクラウド化する。Vultr と Akamai Cloud で構成することにした。これでどちらかのアプリが落ちたりメンテナンスになっても稼働し続けられる。
構成はシンプルで Cloudflare LB でそれぞれのアプリにたいしてラウンドロビンをするだけ。
- Cloudflare Workers -> Cloudflare LB -> Vultr App -> Managed Service for TimescaleDB (GCP)
- Cloudflare Workers -> Cloudflare LB -> Akamai Cloud App -> Managed Service for TimescaleDB (GCP)
これで Vultr と Akamai Cloud のどちらかが落ちてもなんとかなる。セッションは Cloudflare Workers KV で管理しているので、気にしなくて良い。
監視はそれぞれに Tailscale をインストールしてうまいことやる。
DNS ラウンドロビン
Cloudflare Cloudflare Load Balancing を利用した DNS ラウンドロビンを利用して バックエンドを Vutlr と Akamai Connected Cloud のマルチクラウド。
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.
オリジンサーバーへのリクエストを減らすだけでなく、この設定によりアプリケーションはCloudflareのゼロダウンタイムフェイルオーバーを活用できます。一つのIPアドレスへのリクエストが失敗すると、Cloudflareは自動的に同じホスト名に関連付けられた他のIPアドレスへリクエストを再試行します。この挙動により、エンドユーザーがダウンタイムを経験することが防がれます。
E2E テスト
E2E テストは Python の pytest + Playwright + HTTPX を採用。
-
pytest: helps you write better programs
- テストランナー
-
pytest-dev/pytest-env: pytest plugin to set environment variables in pytest.ini or pyproject.toml file
- pytest で ini ファイルに環境変数を定義できるライブラリ
- -c で ini ファイルを変えるだけで prod や dev といった E2E 環境を変更できるようになった
- fixtures なんかよりグローバルな設定変更が多いので環境変数が一番使いやすかった
-
Fast and reliable end-to-end testing for modern web apps | Playwright Python
- ブラウザ E2E テストは Playwright を利用する
-
microsoft/playwright-pytest: Pytest plugin to write end-to-end browser tests with Playwright.
- Playwright を pytest から気軽に使えるようにする plugin が用意されている
-
HTTPX
- API テストは HTTPX を利用する
-
jpadilla/pyjwt: JSON Web Token implementation in Python
- API 認証用
-
mitsuhiko/rye: An Experimental Package Management Solution for Python
- 環境構築には Rye を採用
GitHub Actions の手動実行を利用してる。
データベース
PostgreSQL + TSDB の TimescaleDB を採用
-
Time-series data simplified | Timescale
- Managed Service for TimescaleDB | Timescale Docs
- マネージドの TimescaleDB を利用
- GCP / Tokyo リージョン
- ボタン一つでアップデートしてくれて本当に楽
- 大量の統計情報を突っ込むため TSDB を採用
- RDB と併用できる TimescaleDB が用途にマッチした
- Prometheus エンドポイント
-
PgBouncer - lightweight connection pooler for PostgreSQL
- fluent-bit 経由での追加は PgBouncer を利用
- PostgreSQL 16.x ベースの TimescaleDB を利用
Go + pgx + sqlc
ジョブキュー
負荷はほとんど無いため PostgreSQL の SKIP LOCKED 機能を利用して実現。
詳細は別途記事にしてある。
PostgreSQL で SKIP LOCKED を利用する
デプロイ
-
Ansible is Simple IT Automation
- 温かみのある手動デプロイ
- デプロイの自動化は当面行わず手動デプロイ
- 早すぎる最適化はやらない
Cloudflare Workers
- GitHub Actions 経由でデプロイ
- cloudflare/wrangler-action を利用
ツール
- インフラツールは Ansible にそろえて Python で作成
- Rye を利用
S3 互換オブジェクトストレージ
Cloudflare R2
R2の一般提供を開始しました
ストレージ 月額 $0.015 / GB
- MinIO Go SDK で動作する
- Presigned URL が利用可能
- Cloudflare R2 documentation · Cloudflare R2 docs
-
Pricing · Cloudflare R2 docs
- $0.015 / GB-month
MinIO
- 社内向けに Docker Compose で R2 の代わりに MinIO を利用
- プロダクション検証環境向けに R2 と併用で Vultr で 1 Server 1 Drive で立てて MinIO を利用
- Tailscale 経由でのみ利用可能にしている
- DataPacket <-> Vultr
特定のフォルダを監視し、ファイルが追加されたら S3 互換ストレージにアップロードする仕組み
- Go を採用
- バッチ処理には Systemd timer を利用
-
minio/minio-go: MinIO Client SDK for Go
- S3 互換オブジェクトストレージである MinIO の SDK を採用
- 公式 AWS Go SDK があまりにも使いづらい
エラー監視
安心と信頼の Sentry を採用。Team プランを採用。
検索
- オンラインドキュメントの全文検索
- Sphinx がターゲット
- ログの検索
- TimescaleDB や R2 に保存しているログの全文検索
- 検索サーバーとして
Meilisearch
Rust で書かれた全文検索エンジン Meilisearch を採用。2022 年に入って日本語にも対応。
- Meilisearch
- meilisearch/meilisearch: A lightning-fast search engine that fits effortlessly into your apps, websites, and workflow.
ドキュメント全文検索
Sphinx をターゲットとして docs-scraper を利用してインデックスを作成し、Sphinx 側で docs-searchbar.js を利用して、インスタント検索を実現する。
- meilisearch/docs-scraper: Scrape documentation into Meilisearch
- meilisearch/docs-searchbar.js: Front-end search bar for documentation with Meilisearch
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
- 定期実行は GitHub Actions を利用する
- 実行場所は Melisearch がインストールされている場所が良いので Self-hosted runners を利用
- docker run で 127.0.0.1:7700 向けに放り込む
synonyms
類義語は Meilisearch API を叩くことで実現可能。
Synonyms | Meilisearch Documentation
synonyms.json を更新すると GitHub Actions 経由で PUT する仕組みを用意した。
ページネーション
ページネーションが必要になるデータは TimescaleDB は引かずに、
Meilisearch をブラウザが引くようにする。
Melisearch + JWT
Cloudflare Workers で JWT を生成してブラウザへ払い出し、ブラウザ側で直接に見に行く。
検索
RDB や TSDB は検索には向いていないので Meilisearch を利用する。
構成
- Cloudflare DNS Proxy + Nginx + Meilisearch
- Nginx に Cloudflare Origin 証明書利用
- Nginx は HTTP/2 対応
- Vultr Optimized Cloud Compute
- CPU Optimized
- 2C/ 4G
- 75G NVMe
- Ubuntu 22.04 LTS
Meilisearch を利用してマルチテナントを実現する方法
- How to use Meilisearch in your multi-tenant application
- Multitenancy and tenant tokens | Meilisearch Documentation
Meilisearch の方針としては一時的なトークンを発行するという考えの模様。トークンは JWT 方式でその中に index と filter が設定できる。そのためそのトークンは ~ しか検索できないという縛りが可能になる。
Melisearch + JWT
Cloudflare Workers で JWT を生成してブラウザへ払い出し、ブラウザ側で直接に見に行く。
定期削除
顧客向けのログ検索は一定期間が過ぎたインデックスは削除するようにしたいが、それには Delete documents by batch
を利用する。
Documents | Meilisearch Documentation
Meilisearch Cloud
日本リージョンはないので手が出せない。
ログ
ログの収集は Fluent Bit を採用。
JSON Lines 形式
Erlang VM 特殊形式
Erlang が出力する crash.log と erl_crash.dump はかなり特殊なフォーマットなため、JSONL のようには扱えない。さらに、これらのログでた場合は基本的にはバグなので効率よく共有したい。
-
Multiline Parsing - Fluent Bit: Official Manual
- 正規表現を頑張る
- Fluent Bit から Cloudflare Workers へ送る
- 認証は mTLS を採用する
- TCP & TLS - Fluent Bit: Official Manual
-
Enable mTLS · Cloudflare SSL/TLS docs
- Cloudflare を使うと本当に簡単に mTLS が導入できるのでオススメ
- Cloudflare Workers 経由で R2 へ送る
- Cloudflare Workers で R2 の PresignedURL を生成する
- Wokrers の R2 には PresignedURL を生成する機能はないので AWS SDK を利用する
- https://developers.cloudflare.com/r2/examples/aws-sdk-js-v3/
- Cloudflare Workers で GitHub Issues を立てて PresignedURL などを追加
- GitHub App 連携して Slack 経由で通知
マルチテナント向けログ
fluent-bit 側から JSONL ログをフィルターして TimescaleDB (Postgres) のジョブキューを経由して Meilisearch に追加しています。
ToDo
- ログ検索システムを Cloudflare Pages + Cloudflare Zero Trust で用意
フロントエンド
TypeScript + Remix + Cloudflare Workers を採用
-
Biome
- スポンサーになっている https://github.com/biomejs/biome
- Fast, disk space efficient package manager | pnpm
- TypeScript: JavaScript With Syntax For Types.
-
Remix
- Cloudflare Workers ありき
- エッジでのセッション管理は Workers KV
- バックエンドでのセッション管理をしなくてすむ
- Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.
-
sergiodxa/remix-auth: Simple Authentication for Remix
- パスワードレスのメールアドレス認証を採用
- airjp73/remix-validated-form: Form component and utils for easy form validation in remix
-
meilisearch/meilisearch-js: JavaScript client for the Meilisearch API
- ページネーターが必要な情報は Meilisearch を採用
- バックエンドまたは Workers でトークンを払い出し直接ブラウザが Meilisearch を見に行く
- vitejs/vite: Next generation frontend tooling. It's fast!
- vitest-dev/vitest: A Vite-native test framework. It's fast!
- sindresorhus/pretty-bytes: Convert bytes to a human readable string: 1337 → 1.34 kB
- Chart.js | Open source HTML5 Charts for your website
プロトタイプデザイン
管理サイト
- フロントエンドと同じ構成
-
Zero Trust | Secure Your Hybrid Workforce | Cloudflare
- ログインには Cloudflare Zero Trust のメール認証を採用
-
Self-hosted applications · Cloudflare Zero Trust docs
- Validate JWTs · Cloudflare Zero Trust docs
-
Cf-Access-Jwt-Assertion
ヘッダーの JWT を利用して管理サイト用のセッションを作成 - セッション管理は Cloudflare Zero Trust 依存
バッチ処理
課金処理やその他のバッチ処理は Go と Systemd タイマーユニット を採用。