STGYのインストール

本記事では、STGYのシステム全体を、開発環境とデモ運用環境の双方にインストールする方法について説明する。Dockerを使った運用とネイティブサービスを使った運用の双方についても説明する。ドメイン名やIPアドレスは自分のものに読み替えられたい。

Mac OS上での開発環境の構築

STGYの開発作業はDockerが動く環境であればどこでもできるが、ここではMac OSでの環境構築方法について述べる。Linuxについてはデモ環境の構築方法と一緒に後述する。Mac上では、作業環境は自宅等のホームネットワーク環境を想定し、外部からの接続を受け付けることは想定しない。Dockerを使って各種の依存サービスを立てた上で、自ら開発するSTGYのバックエンドサービスとフロントエンドサービスをローカルホストで動かしながら開発を進める。検証時のために、バックエンドサービスとフロントエンドサービスも含めて全てDockerで動くようにもする。

まずは、Homebrewを入れる。カスタマイズ性の高いMacPortsとかよりも、通り一遍で再現性の高い手順となるHomebrewの方が管理しやすい。

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

Homebrewで、その他の必要パッケージを入れる。Docker VMであるColima、Dockerクライアント、composeプラグイン、Node.js、Git、GitHub CLIである。

brew install colima docker docker-compose node git gh

Colimaを起動する。初回の起動ではリソースの使用量をオプションで指定する。

colima start --cpu 4 --memory 8 --disk 100

DockerクライアントがColimaを使うように設定する。

docker context use colima

~/.docker/config.jsonに以下のような記述をして、ColimaとComposeプラグインを利用可能にする。Colima等の既存の設定があればそのまま残す。

{
  "auths": {},
  "currentContext": "colima",
  "cliPluginsExtraDirs": ["/opt/homebrew/lib/docker/cli-plugins"]
}

Dockerクライアントの動作確認をする。それぞれ、「Docker Compose version 2.39.3」的な文字列と「Hello from Docker!」が表示されれば成功。

docker compose version
docker run --rm hello-world

Node.jsの動作確認をする。それぞれ、「v24.4.0」と「11.4.2」的な文字列が表示されれば成功。

node -v
npm -v

GitHubにログインする。アクセスキーを聞かれるので、事前にGitHub上でアクセスキーを作っておくこと。

NO_COLOR=1 gh auth login

開発作業用のディレクトリを作り、stgyのリポジトリのクローンを作成する。以後、stgyディレクトリで作業を行う。

mkdir ~/dev
cd ~/dev
git clone git@github.com:estraier/stgy.git
cd stgy

リポジトリ構成

STGYの関連パッケージは全て単一のリポジトリに収められている。いわゆるmonorepo構成である。主要なディレクトリ構造とファイルを以下に示す。

  • package.json : NPMでパッケージ管理をするための設定ファイル
  • package-lock.json : 依存パッケージのバージョンを固定するための設定ファイル
  • docker-compose.yml : 依存サービスをDockerで管理するための設定ファイル
  • docker-compose.vps.yml : VPSのデモ環境用に上記設定を上書きするためのファイル
  • env.dev.txt : docker composeや他のスクリプトが読み込む環境設定ファイル
  • env.vps.txt : VPSのデモ環境用の環境変数設定ファイル
  • Caddyfile : リバースプロクシCaddyの設定ファイル
  • Caddyfile.vps : VPSのデモ環境用で使うCaddyの設定ファイル
  • packages/ : 独自の依存パッケージ群のディレクトリ
  • backend/ : バックエンドサービスのソースコードや設定ファイル一式のディレクトリ
  • frontend/ : フロントエンドサービスのソースコードや設定ファイル一式のディレクトリ
  • scripts/ : 管理用の便利スクリプト一式のディレクトリ
  • tests/ : テスト用のコードやファイル一式のディレクトリ
  • postgres/ : PostgreSQLの設定一式のディレクトリ
  • minio/ : MinIOの設定一式のディレクトリ
  • seeder/ : 初期登録データ一式のディレクトリ

パッケージ管理はNPMで行っており、その設定はpackage.jsonに書いてある。Dockerの管理はそこにあるスクリプトコマンドを叩いて行う。ワークスペース構成を取っていて、packages/markdownとbackendとfrontendの下にあるpackage.jsonはその管理下にある。それぞれのサブパッケージには、以下の共通したファイルやディレクトリが格納されている。

  • package.json : NPMでパッケージ管理をするための設定ファイル
  • tsconfig.json : TypeScriptのビルド設定ファイル
  • tsconfig.lint.json : eslintが使うビルド設定ファイル
  • eslint.config.mjs : eslintによるコード解析の設定ファイル
  • jest.config.js : jestによるユニットテストの設定ファイル
  • src/ : ソースコード一式

依存パッケージのインストールはプロジェクトルート直下に作られるnode_modulesディレクトリに格納され、それらのバージョンはpackage-lock.jsonで固定される。以下のコマンドで依存関係を処理する。

  • npm ci : 固定されたバージョンの依存パッケージをインストールする
  • npm install : package.jsonの記述に基づき、新しいバージョンの依存パッケージをインストールする
  • npm run clean : 依存パッケージを消す

backendとfrontendにはDockerfileがあり、それぞれのサービスのDockerインスタンスの設定が書いてある。それ以外の依存サービスの設定はdocker-compose.ymlに直接書いてある。docker composeは.envファイルから環境変数を読み込むが、.envはリポジトリには含まれない。env.dev.txtかenv.vps.txtを.envにコピーしてから、それを環境に合わせて編集して使う。.envにはパスワードなどの秘密情報が書かれるので、Gitの管理対象にすべきではない。

scriptsの下には、システムの管理に必要なスクリプトが入っている。多くのものはnpmを介して起動される。.envの環境変数から設定を読み込むので、.envを事前に作っておく必要がある。

  • reset-data.sh : PostgreSQLのデータベースを初期化し、seederの初期データを投入する
  • reset-minio-data.sh : MinIOのデータベースを初期化する
  • reset-cache.sh : Redisのキャッシュデータを初期化する
  • edit_users.py : ユーザを作成または更新する
  • edit_posts.py : 投稿を作成または更新する
  • user_actions.py : 個々のユーザでログインしてイイネやフォローやブロックを行う
  • make_volume_test.py : 検証用に大量のユーザと投稿を作成する
  • run-local-backend.sh : バックエンドサービスをローカルホスト上で起動する
  • run-local-frontend.sh : フロントエンドサービスをローカルホスト上で起動する
  • run-databaseUtil.sh : データベース管理用のユーティリティ
  • run-storageUtil.sh : メディアストレージ管理用のユーティリティ
  • deploy-backend.sh : デモ環境にてバックエンドをデプロイする
  • deploy-frontend.sh : デモ環境にてフロントエンドをデプロイする

postgresの下にはデータベースのスキーマ定義を行うSQLファイルが置いてある。minioの下にはオブジェクトストレージのバケットの初期設定を行うスクリプトが置いてある。

開発環境でのサービスの起動と終了

Macの開発環境においては、以下のコマンドでDockerホストVNの起動と終了を行う。

  • colima start
    • Colimaを起動
  • colima stop
    • Colimaを停止する
  • colima restart
    • Colimaを再起動する

リポジトリルートにて、開発環境の設定が書いてあるenv-dev.txtを.envとしてコピーする。これは忘れがちなので注意。

cp env.dev.txt .env

以下のコマンドでDocker上のサービスの起動と終了を行う。

  • npm run docker:init
    • ホスト全体を初期化。他プロジェクトも含めてすべてのコンテナとイメージとボリュームを強制削除する。データも消える。
  • npm run docker:reset
    • Dockerイメージを削除して再作成する。
  • npm run docker:build
    • Dockerイメージを作成する。
  • npm run docker:destroy
    • Dockerイメージを削除する。
  • npm run docker:create
    • Dockerコンテナを作成する。必要ならネットワークやボリュームも作成。イメージのpullも行う。
  • npm run docker:remove
    • Dockerコンテナを削除する。
  • npm run docker:up
    • Dockerイメージの作成、Dockerコンテナの作成、Dockerコンテナの起動を一気に行う。
  • npm run docker:down
    • Dockerコンテナの停止と削除を一気に行う。
  • npm run docker:start
    • Dockerコンテナを起動する。
  • npm run docker:stop
    • Dockerコンテナを停止する。

Docker関係のコマンドを利用目的別に整理する。

  • 最も簡単に全サービスの起動と終了を行う。
    • npm run docker:up
    • npm run docker:stop
  • まっさらな状態から逐次的にサービス起動状態までたどり着く
    • npm run docker:build
    • npm run docker:create
    • npm run docker:start
  • サービス起動状態から逐次的にまっさらな状態に戻す。
    • npm run docker:stop
    • npm run docker:remove
    • npm run docker:destroy
  • ビルド設定やTypeScriptコードの変更を強制反映してサービスを再起動する。
    • npm run docker:reset
    • npm run docker:start
  • ビルド設定やTypeScriptコードの変更を差分反映してサービスを再起動する。
    • npm run docker:up
  • Docker環境全体とデータを完全初期化する。ストレージ逼迫の場合もこれを使う。
    • npm run docker:init

開発中には、backendとfrontendはローカルホストで起動し、その他の依存サービスのみをDockerで起動するのが楽である。以下の手順で行う。

  • npm ci
    • ローカル環境に外部依存パッケージをインストールする。
  • npm run packages:build
    • ローカル環境で内部依存パッケージをビルドして準備する。
  • npm run docker:start-dev
    • backendとfrontend以外の依存サービスのDockerコンテナを起動する。
    • 止める時はnpm run docker:stopを実行する。
  • ./scripts/run-local-backend.sh
    • backendサービスと各種ワーカをローカルホストで起動する。
    • --startオプションをつけると、ビルド結果を実行する。
    • 止める時はCtrl-Cで落とす。
  • ./scripts/run-local-backend.sh
    • frontendサービスをローカルホストで起動する。
    • --startオプションをつけると、ビルド結果を実行する。
    • 止める時はCtrl-Cで落とす。
  • npm run reset-data
    • DBとオブジェクトストレージを初期化して、初期データを投入する。
  • npm run reset-data-test
    • DBとオブジェクトストレージを初期化して、性能テスト用の大量のデータを投入する。
  • npm run reset-cache
    • Redisのキャッシュを初期化する。

バックエンドやフロントエンドのTypeScriptコードを変更すると、それぞれの起動中のサービスは勝手に再起動して変更を取り込む。ただし、tscやlintのチェックが甘いので、そのままだとDockerイメージにした時に動く保証がない。よって、以下のコマンド群で品質管理をする。frontendやbackendの中で実行しても良い。

  • npm run test
    • *.test.tsに書かれたユニットテストを実行する。
    • backendやfrontendの中でnpm run testとしてもよい。
  • npm run lint
    • eslintで静的なコードの検証を行う。
  • npm run build
    • tscの厳格なルールで本番用にビルドする。
  • npm run fmt
    • prettierでコードを整形する。

全てのサービスを立ち上げたら、ブラウザで以下のURLを開くと、STGYのサービスが使い始められる。

  • https://localhost:8080/ : ユーザが使うサイト
    • デフォルトでは、「admin」ユーザのパスワードが「stgystgy」になっている。
  • https://s3-console.stgy.jp/ : MinIOのコンソール
    • アップロードされた画像データはここで確認できる。
  • https://mail.localhost:8080/ : 確認メールの保存先
    • サインアップやメアド変更の際の確認メールはここに送られる。

Linux上での開発環境の構築

Linux上でSTGYの稼働と開発を行うための環境設定の手順を記す。まずは、Macの時と同様に、Dockerを使ってサービスを立ち上げる方法を示す。バックエンドとフロントエンドをローカルで動かすことも当然できる。データセンター等の環境を想定し、インターネットからの接続を受け付ける。ドメインを取得し、VPSのホストが利用できることを前提とする。ここでは、stgy.jpというドメインを取得し、VPS上のUbuntu Linux環境の上でシステムを構築する。IPアドレスは153.127.47.206が割り振られている。

事前にドメイン取得サービスのDNS設定で、該当ホストのレコードを設定する。主たるドメインと、画像配信やその他のサブドメインを同じIPアドレスで登録する。

  • サブドメイン=(空文字列)、種別=A、値=153.127.47.206
  • サブドメイン=s3、種別=A、値=153.127.47.206
  • サブドメイン=s3-console、種別=A、値=153.127.47.206
  • サブドメイン=www、種別=A、値=153.127.47.206
  • サブドメイン=mail、種別=A、値=153.127.47.206
  • サブドメイン=(空文字列)、種別=TXT、値=v=spf1 ip4:153.127.47.206 -all

上記設定が伝搬していることを確認する。「Name: stgy.jp Address: 153.127.47.206」的な文字列が表示されれば成功。

nslookup stgy.jp

VPSサービスのコントロールパネルで、逆引き設定をする。メールが弾かれないことを優先するので、mail.stgy.jpとして登録する。設定後、以下のコマンドで伝搬を確認する。

dig +trace -x 153.127.47.206

VPSサービスのコントロールパネルで、Ubuntuの最新のLTS版をインストールし、コンソールからログインして初期設定の作業を行う。初期ユーザ「ubuntu」でログインするが、それはスーパーユーザではないので、sudoして管理作業を行う。インストール後の初期設定が終わったら作業用のprodユーザを作り、以後はそのユーザからsudoして作業を行う。

  • ソフトウェアリストの更新: sudo apt update
  • ソフトウェアの更新: sudo apt upgrade
  • ロケールのインストールと有効化:
    • sudo apt install locales
    • /etc/locale.genを開いて必要なロケール(en_US.UTF-8 UTF-8とja_JP.UTF-8 UTF-8)を戻す
    • sudo locale-gen
  • ホスト名の設定: sudo hostnamectl set-hostname setagaya
  • 通常ユーザ作成: adduser prod
  • sudo権限付与: sudo gpasswd -a prod sudo
  • 再起動: sudo shutdown -r now

ファイアウォールを設定する。SSHの22、HTTPSの443、HTTPの80、メールリレーの587は外部公開する。PostgreSQLの5432とRedisの6379はDockerのブリッジからのみ接続可能にする。なお、br-stgyというブリッジはdocker-compose.ymlで定義されている。

sudo ufw default deny incoming
sudo ufw default allow outgoing

sudo ufw allow ssh
sudo ufw allow https
sudo ufw allow http
sudo ufw allow 587/tcp

sudo ufw allow in on br-stgy to any port 5432 proto tcp
sudo ufw allow in on br-stgy to any port 6379 proto tcp

sudo ufw enable
sudo ufw status

prodユーザでログインして、環境設定を行う。

  • SSHの鍵設定:
    • .ssh/id_rsaと.ssh/id_rsa.pubと.ssh/authorized_keysを既存環境からコピー。
    • パーミッション設定: chmod 600 .ssh ; chmod 755 .ssh/*
  • 最低限の環境設定
    • .bash_profileと.bashrcを既存環境からコピー
  • 任意のエディタの設定。Emacsの場合は以下
    • sudo apt install emacs
    • .emacsを既存環境からコピー
    • なぜかemacsと一緒に入るpostfixの削除: sudo apt purge postfix

Docker関連ツールを入れる。標準ではaptがDockerの公式リポジトリを見ないので、設定を加える必要がある。

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Dockerサービスを有効化する。また、dockerグループにprodユーザを入れて利用可能にする。

sudo systemctl enable --now docker
sudo usermod -aG docker prod
newgrp docker  # 現在のセッションにdockerを加える。ログインし直しても良い。

Dockerクライアントの動作確認をする。それぞれ、「Docker version 28.4.0」「Docker Compose version 2.39.3」的な文字列と「Hello from Docker!」が表示されれば成功。

docker --version
docker compose version
docker run --rm hello-world

Node.jsとnpmをインストールする。Ubuntuのデフォルト設定で入るNode.jsは古いので、Node 22系に手動更新する。

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install nodejs npm

Node.jsの動作確認をする。それぞれ、「v22.19.0」と「10.9.3」的な文字列が表示されれば成功。

node -v
npm -v

GitとGitHub CLIをインストールする。

sudo apt install git gh

GitHubにログインする。アクセスキーを聞かれるので、事前にGitHub上でアクセスキーを作っておくこと。SSHのキーとアクセスキーの対応が取れている必要がある。

NO_COLOR=1 gh auth login

開発作業用のディレクトリを作り、stgyのリポジトリのクローンを作成する。以後、stgyディレクトリで作業を行う。

git clone git@github.com:estraier/stgy.git
cd stgy

運用環境の選択

STGYはAWS上でRDSとS3を使って構築できるシステムアーキテクチャになっているのだが、多数のマネージドサービスを使うと、デモで使うにはランニングコストが高くなりすぎる。デプロイも面倒くさい。よって、デモ用途では1台構成のVPSで動かす方が手軽で良いと判断した。さくらVPSの4GBプランなら月4000円以下で運用できるし、ストレージが400GBまで使えるので、ユーザ数がかなり増えても大丈夫だろう。何より、固定額なのが安心だ。1円の収益にもなっていない趣味のサービスが物量攻撃されて何十万円も払う羽目になったら心がくじけてしまうだろう。デモ運用しているうちは、従量課金で際限なく金がかかるよりは、サービスが落ちる方がマシだ。

VPS上で全ての依存サービスを自前で運用するとして、それらをネイティブサービスとして動かすか、Dockerコンテナとして動かすかは、選択できる。ネイティブサービスとして動かす方法だと、性能上は最善で、ホストが単一なので手動での設定作業も楽だが、自動化しづらいので何度もやるのは厳しい。Dockerコンテナとして動かす方法だと、最初のコンテナの設定はややこしいが、一度やっておけば自動デプロイが簡単にできるようになる。Docker上で動かすとCPU効率とメモリ効率とI/O効率がほんの少し落ちる可能性はあるが、LinuxのDocker層のオーバーヘッドはほぼ無視できる程度に小さいので、性能に関しては気にする必要はない。よって、とりあえずはDockerで動くようにしよう。開発環境とほぼ同じ設定で、VPS上でDockerを動かして、そこにデータベースもオブジェクトストレージもバックエンドサーバもフロントエンドサーバも乗せてしまう。

Docker完結方式

Docker完結の前提で、アーキテクチャと設定を整理してみる。コンテナ間の接続は主に環境変数で管理することになるが、各コンテナが互いを認識するためにどの環境変数を使っているかを洗い出し、その適切な値を決めるのだ。内側(インターネット側)と外側(Dockerサブネット)で名前が違うのと、リバースプロクシが読み替えを行うのがややこしい。理解が甘いと後で確実に混乱するので、予め整理しておくべきだ。

  • リバースプロクシ(Caddy)
    • Node.jsのサービスはHTTPしか喋れないので、SSL化するのに必要。
      • 全ての設定はCaddyfileに書く。
    • https://stgy.jp/backend をDockerサブネットの http://backend:3001 であるバックエンドサーバに中継する。
    • https://stgy.jp のそれ以外をDockerサブネットの http://frontend:3000 であるフロントエンドサーバに中継する。
    • https://s3.stgy.jp をDockerサブネットの http://minio:9000 であるS3サーバに中継する。
    • https://mail.stgy.jp をDockerサブネットの http://mailpit:8025 であるメールサーバに中継する。
    • 証明書はCaddy自身が自動的にLet's Encryptから取ってきてくれる。
      • 7日間で5回以上取得するとレート制限がかかるので、down -vでボリュームを消さないように注意。
    • 外向きのポートは22と80と443と587しか空いていないので、Dockerで立てるサーバで外からは見えるのはこいつだけ。
  • フロントエンド(Node.js + Next.js)
    • Dockerサブネットでfrontend:3000をlistenし、主にJavaScriptを配信。表にはhttps://stgy.jpとして露出。
      • PORT=3000
    • フロントエンドをhttps://stgy.jpとして認識し、UI上の相対パスを絶対パスに解決する。
    • バックエンドをhttps://stgy.jp/backendとして認識し、その配下にAPIリクエストを送る。
      • NEXT_PUBLIC_BACKEND_API_BASE_URL=https://stgy.jp/backend
      • CSRのfetchは相対URLで動くが、SSRのfetchは絶対URLでないと動かないので注意。
    • S3サーバをhttps://s3.stgy.jpとして認識し、画像の配信とpresigned-POSTのベースURLにそれを用いる。
      • NEXT_PUBLIC_STORAGE_S3_BUCKET_PREFIX=stgy
      • NEXT_PUBLIC_STORAGE_S3_PUBLIC_URL_PREFIX=https://s3.stgy.jp/{bucket}/
  • バックエンド(Node.js + Express)
    • Dockerサブネットでbackend:3001をlistenし、サービスAPIのエンドポイントを担う。表にはhttps://stgy.jp/backendとして露出。
      • STGY_BACKEND_PORT=3001
    • フロントエンドをhttps://stgy.jpとして認識し、1ホップまでのプロクシ応答のX-Forwarded-Forを信じる。
    • S3サーバのエンドポイントをhttp://minio:9000として認識し、Dockerサブネットで通信。
    • ただし、オブジェクトの所在はhttps://s3.stgy.jp/{bucket_name} の配下としてクライアントに通達。
    • DBサーバをpostgres:5432として認識し、Dockerサブネットで通信。
      • STGY_DATABASE_HOST=postgres
      • STGY_DATABASE_PORT=5432
    • キャッシュサーバをredis:6379として認識し、Dockerサブネットで通信。
      • STGY_REDIS_HOST=redis
      • STGY_REDIS_PORT=6379
  • メールワーカ(Node.jsのCLI)
    • キャッシュサーバをredis:6379として認識し、Dockerサブネットで通信。
      • STGY_REDIS_HOST=redis
      • STGY_REDIS_PORT=6379
    • SMTPサーバをmailpit:5587として認識し、Dockerサブネットで通信。
      • STGY_SMTP_HOST=mailpit
      • STGY_SMTP_PORT=5587
  • メディアワーカ(Node.jsのCLI)
    • キャッシュサーバをredis:6379として認識し、Dockerサブネットで通信。
      • STGY_REDIS_HOST=redis
      • STGY_REDIS_PORT=6379
    • S3サーバのエンドポイントをhttp://minio:9000として認識し、Dockerサブネットで通信。
  • 通知ワーカ(Node.jsのCLI)
    • キャッシュサーバをredis:6379として認識し、Dockerサブネットで通信。
      • STGY_REDIS_HOST=redis
      • STGY_REDIS_PORT=6379
    • DBサーバをpostgres:5432として認識し、Dockerサブネットで通信。
      • STGY_DATABASE_HOST=db
      • STGY_DATABASE_PORT=5432
  • DBサーバ(PostgreSQL)
    • Dockerサブネットでpostgres:5432をlistenし、DBを管理。
      • STGY_DATABASE_PORT=5432
  • S3サーバ(MinIO)
    • Dockerサブネットでminio:9000をlistenし、オブジェクトストレージを管理。表にはhttps://s3-stgy.jpとして露出。
      • STGY_MINIO_PORT=9000
    • Dockerサブネットでminio:9001をlistenし、コンソール画面を表示。表にはhttps://s3-console.stgy.jpとして露出。
      • STGY_MINIO_CONSOLE_PORT=9001
  • キャッシュサーバ(Redis)
    • Dockerサブネットでredis:6379をlistenし、キャッシュを管理。
      • STGY_REDIS_PORT=6379
  • メールサーバ(Mailpit)
    • Dockerサブネットでmailpit:5587をlistenし、メールを管理。
      • STGY_SMTP_PORT=5587
    • Dockerサブネットでmailpit:8025をlistenし、コンソール画面を表示。表にはhttps://mail.stgy.jpとして露出。
      • STGY_MAILPIT_CONSOLE_PORT=8025
    • リレー設定があれば、本番のSMTPサーバにメールを転送。

どのサービスがどのサービスの接続を知っているかという参照関係を図にしておく。これは、実際に接続しているという意味ではなく、その設定を読み込んでいるという意味だ。例えば、バックエンドサーバがCaddyに接続することはないが、バックエンドにはCaddy越しに来るフロントエンドのURLを信用する設定が必要だ。また、バックエンドサーバはフロントエンドにS3サービスのURLを伝える必要があり、そこにはCaddyのアドレスが書いてある。同様にして、フロントエンドはCaddy越しにバックエンドやS3サーバに接続するアドレスを知っておく必要がある。

サーバ間参照

上述の設定は既にenv.vps.txtとdocker-compose.vps.ymlとCaddyfile.vpsに書いてあるので、Dockerによる本番運用は、DockerとGitが動くホストさえあれば、5分で始められるようになっている。他のドメイン名を使う場合、それらのファイルのstgy.jpの部分をそのドメイン名に変えれば良い。HTTPSでなくHTTPで運用する場合、docker-compose.vps.ymlとCaddyfile.vpsのhttpsの記述をhttpに置換する。ただし、HTTPだとパスワードやセッションIDが平文で転送されるため、検証用途以外で運用するのは止めておいた方がよい。

デモ環境Docker完結方式でのサービスの起動と終了

リポジトリルートにて、デモ環境Docker完結方式の設定が書いてあるenv-vps.txtを.envとしてコピーする。これは忘れがちなので注意。.envの内容を編集し、必要ならドメイン名やパスワードなどの値を変更する。MinIOのパスワード(STGY_MINIO_ROOT_PASSWORDとSTGY_STORAGE_S3_SECRET_ACCESS_KEYの両方)だけは、必ず初期値から変更すること。そうしないと外部から操作されてしまう。

cp env.vps.txt .env

手動でDockerコンテナを立ち上げる。npmのスクリプト名に"vps:"が接頭していることに注意。

npm run vps:docker:up

初期データを投入する。STGY上のadminユーザを変えている場合、環境変数STGY_ADMIN_EMAILとSTGY_ADMIN_PASSWORDに変更後の値を設定してから実行する。

npm run vps:reset-data

そして、以下のURLをブラウザで開けば、もう使えるようになっている。adminユーザのパスワードを変える作業を最初にやること。

  • https://stgy.jp/ : ユーザが使うサイト
    • デフォルトでは、「admin」ユーザのパスワードが「stgystgy」になっているので、それでログインして変える。
  • https://s3-console.stgy.jp/ : MinIOのコンソール
    • この管理者アカウント情報は、環境設定ファイルのものから変えては駄目。
  • https://mail.stgy.jp/ : 確認メールの保存先
    • デフォルトでは、Basic認証で、「admin」ユーザのパスワードが「stgystgy」になっている。

Basic認証のパスワードはCaddyfile.vpsにハッシュ化して書いてある。Caddyが起動している状態で npm run caddy:hash NEWPASSWORD を実行するとハッシュ値が表示されるので、それをファイルに貼り付けて再起動すると変えられる。

Mailpitはデフォルトではクライアントから送られてきたメールを溜め込むだけで、実際のメール配信は行わない。一般ユーザへのメール通知を行うには、別のSMTPサーバに自動リレーする必要がある。デモ運用では、docker-compose.ymlのmailpitサービスの記載でコメントアウトされている環境変数を有効化して、外部のSMTPサーバにリレーさせる。本番環境ではMailpitをPostfixに変えて同等のリレーを行うか、SES等のメールサーバに直接メールを送る設定にするのが望ましい。

Dockerコンテナの自動起動

OSの起動時に自動的に全サービスが立ち上がるようにすべきだ。起動スクリプトを動かして、上記のnpm run vps:docker:upを実行するようにしたい。そのためには、systemdの起動スクリプトを書く。/etc/systemd/system/stgy-docker.serviceに以下の内容を記述する。

[Unit]
Description=STGY stack via Docker Compose
Wants=network-online.target
After=network-online.target docker.service
Requires=docker.service

[Service]
Type=oneshot
User=prod
WorkingDirectory=/home/prod/stgy
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/npm run vps:docker:up
ExecStop=/usr/bin/npm run vps:docker:down
RemainAfterExit=yes
TimeoutStartSec=0
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

自動起動スクリプトを有効化し、状態を確認する。起動シーケンス(ExecStart)が実行されて、Dockerのサービスが起動するはずだ。

sudo systemctl daemon-reload
sudo systemctl enable --now stgy-docker
systemctl status stgy-docker

自動起動スクリプトを無効化するには、以下のコマンドを実行する。停止シーケンス(ExecStop)が実行されて、Dockerのサービスが停止するはずだ。

sudo systemctl disable --now stgy-docker

ネイティブサービス稼働方式

大規模サイトとしての本番運用を考えると、リバースプロクシとDBサーバとストレージサーバとメールサーバはクラウドのマネージドサービスを使うのが典型的だ。オンプレミスだとしても個々のサービスは個別のホストで稼働させて性能と可用性を向上させる構成にするのが定石だ。DockerによってSNSのサービスをカプセル化できるのは素晴らしいが、デモサイトをそれで運用していると、本番サイトの構成とかけ離れすぎていて、運用の訓練としては理想的ではない。依存サービスを同一ホストで動かすにしても、個別のネイティブサービスとして稼働させた方が本番の構成に近づけられる。

ネイティブサービスを動かすと、個々のサービスを他の用途にも使えるという利点もある。例えば、stgy.jpにSNS以外のサービスも乗っけるなら、単一のリバースプロクシにそれらの中継もさせた方が運用が楽だ。同様に、データベースやオブジェクトストレージを他のサービスと共有して何らかの連携を行う場合も、ネイティブサービスになっている方が管理しやすい。理想論としては、そのような同居や密結合は避けるべきで、SNSと他のサービスはホストも分けて運用すべきなのだが、リソースの制約でそうも言っていられないことがある。

実際のところ、デモ環境の運用に手間と金をかけたくない私としては、自前の様々なオープンソース製品のデモは1台のホストに集めて、1つのリバースプロクシで一括運用したい。デモ用途ならセキュリティやアベイラビリティの優先度は要件は無いようなものなので、運用効率重視の構成を追求できる。ホスト1台に集約しつつコンテナ運用をするのも可能だが、同じ役割のサービスが複数コンテナで起動するとポートや証明書の管理が煩雑になるので、同じ役割のサービスは統合したい。

コンテナ運用のサービスでも複数の用途で使い回せるとの指摘があるだろう。しかし、VPSの単一ホスト上で動かすことを前提とすると、単に面倒になるだけだ。サービスの設定ファイルを書いて、それをコンテナにマウントさせる旨を別の設定ファイルに書くという無駄な作業が発生する。また、用途Aと用途Bの双方から使われるコンテナの設定をどちらのリポジトリで管理するのかという話になる。単機能のコンテナを使い回すことにも利点はあり、スケールアウト戦略で多数のインスタンスを立てる場合には特に有用だ。しかし、その際にはECSなどのコンテナサービスを使うだろう。VPS1台構成の話とは分けて考えるべきだ。

結局のところ、どのサービスをネイティブにして、どのサービスをコンテナ運用にするかは、運用の都合で決まるということになる。いずれにせよ、全てのサービスをネイティブで稼働させる方法をここで洗い出しておくことは有益だ。必要に応じて、以下の手順をなぞればよいだろう。

systemctl

Ubuntu Linuxにおいて、ネイティブサービスすなわちデーモンプロセスの管理はsystemdによって行われる。その設定は/etc/systemd/systemの下に置かれる。サービスの管理はsystemctlコマンドを使って行われる。

  • sudo systemctl start SERVICENAME : 手動で起動
  • sudo systemctl stop SERVICENAME : 手動で停止
  • sudo systemctl restart SERVICENAME : 手動で再起動
  • sudo systemctl enable --now SERVICENAME : 自動起動を有効化
  • sudo systemctl disable --now SERVICENAME : 自動起動を無効化
  • systemctl status SERVICENAME : 起動設定の状態確認

ログを確認する際には、journalctlコマンドを使う。

  • journalctl -u SERVICENAME : ログ全体をページャで見る
  • journalctl -u SERVICENAME -f : 最新ログを表示して端末に表示し続ける

systemdの設定ファイル(*.service、*.socketなど)の変更を反映したい場合、以下のコマンドを実行する。

sudo systemctl daemon-reload

DockerでSTGYを立てている場合にネイティブサービスに運用に変えるには、stgy-dockerサービスをdisableしてから作業を行う。ネイティブサービスを立てている場合にDocker運用に変えるには、後述のcaddy等のサービス全てをdisableしてから作業を行う。

なお、運用方式を変える場合、単に起動方法を切り替えるだけではPostgreSQLやMinIOのデータは移行されないことには注意すべきだ。PostgreSQLのデータを移行したい場合、pg_dumpallやpg_backupを使うことになる。MinIOのデータを移行したい場合、mc mirrorやrsyncを使うことになる。

Caddy

CaddyはGoで書かれた軽量かつ高速のWebサーバである。設定も簡単で、何より証明書の取得を自動でやってくれるのが便利だ。Caddyは以下のようにインストールし、自動起動を有効化できる。

sudo apt install caddy
sudo systemctl enable --now caddy
systemctl status caddy

aptでインストールされるcaddyはプラグインが使えないので、バイナリだけをビルドし直して置き換える必要がある。go言語の最新版もインストールする必要がある。

sudo apt remove golang-go
wget https://go.dev/dl/go1.25.1.linux-amd64.tar.gz
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.25.1.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build
sudo mv caddy /usr/bin/caddy
sudo chown root:root /usr/bin/caddy
sudo chmod 755 /usr/bin/caddy

Caddyの自動起動スクリプトの本体は/usr/lib/systemd/system/caddy.serviceである。このファイルを編集して、Caddyの実行ユーザをprodにしておく。UMaskの行は追加の設定であり、同一グループのプロセスが証明書などのデータを読めるようにする。

User=prod
Group=prod
UMask=0027

起動スクリプトの変更を反映する。

sudo systemctl daemon-reload

/var/caddyというディレクトリを作り、そこにCaddy関連のデータを置くことにする。その所有者をCaddyの実行ユーザに合わせておく。

sudo mkdir /var/caddy
sudo chown -R prod:prod /var/caddy

Caddy自体の設定は/etc/caddy/Caddyfileで行う。典型的には以下のように書く。S3にはホットリンク防止のRefererの制限をかけている。STGYサービスの配信に加えて、/home/www以下のファイルを通常のWebサーバとして配信し、その中のprivate/配下にはBasic認証をかけるものとする。

{
  storage file_system {
    root /var/caddy
  }
}

https://stgy.jp {
  encode zstd gzip

  redir /backend /backend/
  handle_path /backend/* {
    reverse_proxy http://127.0.0.1:3001 {
      lb_policy first
      health_uri /health
      header_up Host {http.request.host}
    }
  }

  handle {
    reverse_proxy http://127.0.0.1:3000 {
      lb_policy first
      health_uri /
      header_up Host {http.request.host}
    }
  }
}

https://s3.stgy.jp {
  encode zstd gzip

  @okref header_regexp Referer ^https?://([^.]+\.)?stgy\.jp(/|$)
  @noref not header Referer *
  handle @okref {
    reverse_proxy 127.0.0.1:9000 {
      flush_interval -1
    }
  }
  handle @noref {
    reverse_proxy 127.0.0.1:9000 {
      flush_interval -1
    }
  }
  respond 403
}

https://s3-console.stgy.jp {
  encode zstd gzip

  reverse_proxy http://127.0.0.1:9001
}

https://www.stgy.jp {
  encode zstd gzip
  root * /home/prod/www

  @secret path /private*
  basic_auth @secret {
    admin $2a$14$uuicMQhEHEbzlvNfISPcue77In..Te9ZMzAKzppaRilla5wBlXdoa
  }

  file_server {
    index index.xhtml index.html
  }
}

以下のコマンドで、設定ファイルの検証や設定変更の反映をする。

sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl restart caddy

Basic認証で使うパスワードのハッシュ値は、以下のコマンドで生成できる。

caddy hash-password --plaintext NEWPASSWORD

STGYには必要ない余談だが、CGIスクリプトを使いたい場合、FastCGIのラッパーであるfcgiwrapを使うと良い。

sudo apt install fcgiwrap
sudo systemctl enable --now fcgiwrap.socket
sudo systemctl status fcgiwrap.socket

fcgiwrapの自動起動スクリプト本体は/usr/lib/systemd/system/fcgiwrap.serviceと/usr/lib/systemd/system/fcgiwrap.socketなので、それぞれを編集して実行ユーザと所有ユーザをprodにしておく。

User=prod
Group=prod
SocketUser=prod
SocketGroup=prod

起動スクリプトの変更を反映する。

sudo systemctl daemon-reload

そのうえで、Caddyfileのwww.stgy.jpのディレクティブの中に以下の設定を書くと、.cgi拡張子のファイルがCGIスクリプトとして実行されるようになる。ディレクティブは上から評価されるので、file_serverディレクティブより上に置くことが重要である。

  @cgi path_regexp cgi ^(.+\.cgi)(/.*)?$
  handle @cgi {
    reverse_proxy unix//run/fcgiwrap.socket {
      transport fastcgi {
        split .cgi
        env SCRIPT_FILENAME /home/prod/www{http.regexp.cgi.1}
        env DOCUMENT_ROOT /home/prod/www
        env SCRIPT_NAME {http.regexp.cgi.1}
        env PATH_INFO   {http.regexp.cgi.2}
      }
    }
  }

Caddyfileを書き換えたならCaddyを再起動する。

sudo systemctl restart caddy

HTTPSで使うサーバ証明書はLet's Encryptから自動取得され、/var/caddy/certificates配下に保存される。更新も勝手にやってくれて、その際のACME認証も自身のWebサーバの機能で通せるので、certbotを運用するより楽だ。後述のSendmailの設定でもCaddyが取得した証明書を流用できる。

$ cd /var/caddy/certificates/acme-v02.api.letsencrypt.org-directory
$ find . -name "*.crt" -o -name "*.key"
./s3-console.stgy.jp/s3-console.stgy.jp.crt
./s3-console.stgy.jp/s3-console.stgy.jp.key
./mail.stgy.jp/mail.stgy.jp.crt
./mail.stgy.jp/mail.stgy.jp.key
./stgy.jp/stgy.jp.key
./stgy.jp/stgy.jp.crt
./www.stgy.jp/www.stgy.jp.key
./www.stgy.jp/www.stgy.jp.crt
./s3.stgy.jp/s3.stgy.jp.key
./s3.stgy.jp/s3.stgy.jp.crt

Postfix

Postfixは定番のメールサーバである。Mailpitの代わりに稼働させることで、ちゃんとしたメールの配送が実現できる。Postfixは以下のようにインストールし、自動起動を有効化できる。インストール時に出るダイアログでは「No configurations」を選ぶ。SASL認証用のモジュールとコマンドも入れる。

sudo apt install postfix
sudo systemctl enable --now postfix
systemctl status postfix
sudo apt install libsasl2-modules sasl2-bin

postfixというユーザが自動的に作られるので、それがcaddyの証明書を読めるようにprodグループに追加する。また、SASL認証のデータベースにアクセスできるように、saslグループにも追加する。

sudo usermod -aG prod postfix
sudo usermod -aG sasl postfix
sudo chgrp sasl /etc/sasldb2

/etc/postfix/main.cfを編集(新規作成)する。ローカルホストで稼働するSTGYメールワーカから出されたメールは無認証で受け取る。Macで開発している場合にはMailpitのリレー設定でTLS認証で受け取る。

myhostname = mail.stgy.jp
mydomain = stgy.jp
myorigin = $mydomain

mydestination = $myhostname, localhost.$mydomain, localhost
inet_interfaces = all
inet_protocols = ipv4

mynetworks = 127.0.0.0/8, [::1]/128

mailbox_size_limit = 0
recipient_delimiter = +
append_dot_mydomain = no
biff = no

smtpd_recipient_restrictions =
  permit_mynetworks,
  permit_sasl_authenticated,
  reject_unauth_destination

smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = $mydomain
smtpd_sasl_path = smtpd
broken_sasl_auth_clients = yes

smtpd_tls_cert_file = /var/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.stgy.jp/mail.stgy.jp.crt
smtpd_tls_key_file = /var/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.stgy.jp/mail.stgy.jp.key
smtpd_use_tls = yes
smtpd_tls_security_level = may
smtp_tls_security_level = may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

maillog_file = /var/log/mail.log
message_size_limit = 10485760
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases

/etc/postfix/master.cfを編集する。既存の行を全てコメントアウトした上で以下を加える。すなわち、smtpの行のchrootをnに変え、submissionの行でローカルホストのクライアントとSASL認証を経たクライアントを許可する。

smtp       inet  n       -       n       -       -       smtpd
submission inet  n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination

SASL認証のためのアカウントを作る必要がある。ここでは postfix@stgy.jp というアカウントを作る。パスワードを聞かれるので、予め決めておいた文字列を入力する。その後の確認コマンドで「postfix@stgy.jp: userPassword」と表示されれば成功。

sudo saslpasswd2 postfix -u stgy.jp
sudo sasldblistusers2

Postfixを再起動する。

sudo systemctl restart postfix

Macでswaksコマンドを使うと、疎通が確認できる。パスワードは先ほど決めたものを指定すること。

swaks --to hirarin@gmail.com --server stgy.jp:587 --from noreply@stgy.jp --auth-user postfix@stgy.jp --auth-password SASLPASSWORD --auth PLAIN --header "Subject: test" --body "test1" --tls

SMTPサーバが配信先のSMTPサーバに信用されるためには、自身が名乗るホスト名(設定ファイル内のmyhostnameの値)とIPアドレスの逆引きの値が同じである必要がある。また、DNSのSPF設定で、自身のIPアドレスが登録されている必要がある。SASL認証はメールのリレーに必須の設定ではないが、インターネット越しで認証して安全にメールを受信するには、SSLとSASL認証の組み合わせを使うのが望ましい。

PostgreSQL

PostgreSQLは堅牢性と多機能性に定評のあるデータベースサーバである。STGYのいくつかの機能は標準SQLの範囲を超えてPostgreSQLの独自機能をいくつか使っているので、基本的にはPostgreSQLありきの実装になっている。PostgreSQLは以下のようにインストールし、自動起動を有効化できる。

sudo apt install postgresql postgresql-contrib postgresql-client
sudo systemctl enable --now postgresql
systemctl status postgresql

以下の手順はPostgreSQLのメジャーバージョンが16であることを前提とする。もし違うバージョンであれば16を読み替えること。まずは、/etc/postgresql/16/main/conf.d/90-stgy.confファイルを新規作成する。内容は以下のようにする。メモリ使用量はメモリ4GBのシステムに最適化してある。

listen_addresses = '*'
port = 5432

password_encryption = scram-sha-256

logging_collector = on
log_min_duration_statement = 1000
log_line_prefix = '%m [%p] %u@%d '

shared_buffers = '1GB'
effective_cache_size = '3GB'
work_mem = '16MB'
maintenance_work_mem = '256MB'
checkpoint_completion_target = 0.9
wal_compression = on
max_connections = 100

/etc/postgresql/16/main/pg_hba.confを編集して、ホストベース認証の設定をする。ローカルとDockerブリッジ(172.23.0.*)からの接続のみを受け付け、全てにパスワード認証を要求する。元の内容は全て消してよい。

local  all  postgres                    peer
local  all  all                         scram-sha-256
host   all  all       127.0.0.1/32      scram-sha-256
host   all  all       ::1/128           scram-sha-256
host   all  all       172.23.0.0/24     scram-sha-256

confファイルを書き換えたならPostgreSQLを再起動する。

sudo systemctl restart postgresql

psqlコマンドで接続し、STGY用のデータベースを作成する。

sudo -u postgres psql <<__EOF__
CREATE ROLE admin LOGIN PASSWORD 'stgystgy';
CREATE DATABASE stgy OWNER admin TEMPLATE template0 ENCODING 'UTF8' LC_COLLATE 'C' LC_CTYPE 'C';
GRANT ALL PRIVILEGES ON DATABASE stgy TO admin;
__EOF__

データベースのスキーマ定義や初期データの登録を行う。

ls postgres/init/*.sql | sort | while read file ; do
  PGPASSWORD=stgystgy psql -h 127.0.0.1 -p 5432 -U admin -d stgy < $file
done

データベースの内容を確認する。

PGPASSWORD=stgystgy psql -h 127.0.0.1 -p 5432 -U admin -d stgy

-- ロールの一覧
stgy=> \du
-- データベースの一覧
stgy=> \l
-- テーブルの一覧
stgy=> \dt
-- adminユーザの存在確認
stgy=> SELECT id, nickname, is_admin FROM users;

MinIO

MinIOはS3互換のオブジェクトストレージサービス、STGYではメディアデータの管理に使う。MinIOはaptパッケージが無いので、以下のようにバイナリを直接インストールする。

wget -O /tmp/minio https://dl.min.io/server/minio/release/linux-amd64/minio
sudo install -m 0755 /tmp/minio /usr/local/bin/minio
wget -O /tmp/mc https://dl.min.io/client/mc/release/linux-amd64/mc
sudo install -m 0755 /tmp/mc /usr/local/bin/mc

MinIOの実行ユーザを作り、設定とデータの置き場所を作る。

sudo useradd --system --home /var/minio --shell /usr/sbin/nologin minio
sudo mkdir -p /var/minio/data
sudo mkdir -p /etc/minio
sudo chown -R minio:minio /var/minio
sudo chown -R minio:minio /etc/minio

/etc/minio/minio.envを新規作成して設定を書く。MinIOは外部からアクセスされるので、ユーザとパスワードは必ず変更すること。

MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=stgystgy
MINIO_API_CORS_ALLOW_ORIGIN=https://stgy.jp,https://s3.stgy.jp,https://s3-console.stgy.jp
MINIO_BROWSER_REDIRECT_URL=https://s3-console.stgy.jp
MINIO_SERVER_URL=https://s3.stgy.jp

設定ファイルが他のユーザに読まれないように所有権とパーミッションを設定する。

sudo chown minio:minio /etc/minio/minio.env
sudo chmod 600 /etc/minio/minio.env

自動起動のためのsystemdスクリプトを/etc/systemd/system/minio.serviceとして書く。

[Unit]
Description=MinIO Object Storage
Wants=network-online.target
After=network-online.target

[Service]
User=minio
Group=minio
EnvironmentFile=-/etc/minio/minio.env
ExecStart=/usr/local/bin/minio server /var/minio/data \
  --address 127.0.0.1:9000 \
  --console-address 127.0.0.1:9001
Restart=always
RestartSec=5
LimitNOFILE=65536
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

自動起動を有効化する。

sudo systemctl daemon-reload
sudo systemctl enable --now minio
sudo systemctl status minio

STGY用のバケットを作成する。

mc alias set stgylocal http://127.0.0.1:9000 admin stgystgy
mc mb stgylocal/stgy-images
mc anonymous set download stgylocal/stgy-images
mc mb stgylocal/stgy-profiles
mc anonymous set download stgylocal/stgy-profiles

Redis

Redisはメモリ常駐のキー・バリュー型ストアで、STGYでは主にキャッシュ用途に使う。Redisは以下のようにインストールし、自動起動を有効化できる。

sudo apt install redis-server
sudo systemctl enable --now redis-server
systemctl status redis-server

/etc/redis/redis.confを編集する。内容は以下のようにする。全てのアドレスにバインドするが、パスワード認証を要求する。メモリ使用量は512MBに制限する。元の内容は全て消してよい。

bind 0.0.0.0
port 6379
protected-mode yes

supervised systemd
pidfile /run/redis/redis-server.pid
logfile ""
dir /var/lib/redis

requirepass stgystgy

save ""
appendonly no

maxmemory 512mb
maxmemory-policy allkeys-lru
timeout 0
tcp-keepalive 60

confファイルを書き換えたならRedisを再起動する。

sudo systemctl restart redis-server

CLIで接続確認する。

redis-cli -a stgystgy PING

STGYのバックエンドとフロントエンド

STGYのバックエンドとフロントエンドも実サービスとして稼働させられる。Gitのリポジトリ内でビルドして、/home/prod/stgy-backendおよび/home/prod/stgy-frontendの配下にデプロイして、そこにあるJavaScriptコードをNode.jsで実行する起動スクリプトをsystemd経由で実行する。

バックエンドサーバをデプロイするには、prodユーザが、stgyリポジトリの中で、以下のコマンドを実行する。stgy.jpで全て実サービスを使った場合の環境設定がスクリプト内に書いてある。

./scripts/deploy-backend.sh

デプロイ用スクリプトを実行すると、/home/prod/stgy-backend配下にJavaScriptがデプロイされる。その中にstart.shというスクリプトがあり、それを実行すればバックエンドサービスが起動する。mailWorkerとmediaWorkerとnotificationWorkerも同時に起動される。それを自動起動するsystemdスクリプトを/etc/systemd/system/stgy-backend.serviceとして書く。

[Unit]
Description=STGY Backend (API + workers)
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=prod
Group=prod
WorkingDirectory=/home/prod/stgy-backend
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/home/prod/stgy-backend/start.sh start
ExecStop=/home/prod/stgy-backend/start.sh stop
ExecReload=/home/prod/stgy-backend/start.sh restart
Restart=on-failure
RestartSec=2s
TimeoutStopSec=30s
KillMode=process

[Install]
WantedBy=multi-user.target

自動起動スクリプトを有効化し、状態を確認する。

sudo systemctl daemon-reload
sudo systemctl enable --now stgy-backend
systemctl status stgy-backend

フロントエンドサーバをデプロイするには、prodユーザが、stgyリポジトリの中で、以下のコマンドを実行する。stgy.jpで全て実サービスを使った場合の環境設定がスクリプト内に書いてある。

./scripts/deploy-frontend.sh

デプロイ用スクリプトを実行すると、/home/prod/stgy-frontend配下にJavaScriptがデプロイされる。その中にstart.shというスクリプトがあり、それを実行すればバックエンドサービスが起動する。それを自動起動するsystemdスクリプトを/etc/systemd/system/stgy-frontend.serviceとして書く。

[Unit]
Description=STGY Frontend
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=prod
Group=prod
WorkingDirectory=/home/prod/stgy-frontend
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/home/prod/stgy-frontend/start.sh start
ExecStop=/home/prod/stgy-frontend/start.sh stop
ExecReload=/home/prod/stgy-frontend/start.sh restart
Restart=on-failure
RestartSec=2s
TimeoutStopSec=30s
KillMode=process

[Install]
WantedBy=multi-user.target

自動起動スクリプトを有効化し、状態を確認する。

sudo systemctl daemon-reload
sudo systemctl enable --now stgy-frontend
systemctl status stgy-frontend

以下のコマンドで初期データの投入を行う。

./scripts/edit_users.py seeder/user-0*.txt
./scripts/edit_posts.py seeder/post-0*.txt
./scripts/user_actions.py seeder/action-*.txt

deploy-backend.shとdeploy-frontend.shが環境設定を内包するself-containedな実装になっているのは重要である。デプロイした時の環境設定で常にサービスが安定稼働するべきだからだ。デプロイ後に外部から環境変数を変化させて挙動を変えてはならない。二つのスクリプトはCI/CDへの布石である。STGYはOSS製品として頒布するので特定のデプロイ方式を前提としないが、少なくともGitHub Actionsでデプロイしやすいようには設計している。実際、GitHub Actionsのワークフローでリポジトリ内のスクリプトを実行するだけで、CDは達成できる。パスワード等はリポジトリのシークレットからパラメータ注入する。CIに関しては、単にテスト環境でnpm testを呼ぶワークフローを仕掛ければ良い。そこで呼ばれるテストが統合テストと呼ぶに足るものであるかどうかが品質を左右することになる。

実サービス運用での初期化

リポジトリ直下で以下のコマンドを実行すると、データを初期化できる。データベースとオブジェクトストレージとキャッシュの全てを問答無用で初期化するので注意すること。

PGPASSWORD=stgystgy psql -h 127.0.0.1 -p 5432 -U admin -d stgy <<__EOF__
DROP SCHEMA public CASCADE; CREATE SCHEMA public;
__EOF__
mc alias set stgylocal http://127.0.0.1:9000 admin stgystgy
mc rb --force stgylocal/stgy-images
redis-cli -a stgystgy FLUSHALL

その後、PostgreSQLの手順にあるスキーマ初期と、MinIOの手順にあるバケット初期設定と、STGYサービスの手順にある初期データの投入を行うこと。

実サービスの全てを無効化するには、以下のコマンドを実行する。Docker運用に戻したい場合は、一旦全ての実サービスを停止する必要がある。

sudo systemctl disable --now stgy-frontend
sudo systemctl disable --now stgy-backend
sudo systemctl disable --now postfix
sudo systemctl disable --now redis-server
sudo systemctl disable --now minio
sudo systemctl disable --now postgresql
sudo systemctl disable --now caddy

実サービスの全てを有効化するには、以下のコマンドを実行する。

sudo systemctl enable --now caddy
sudo systemctl enable --now postgresql
sudo systemctl enable --now minio
sudo systemctl enable --now redis-server
sudo systemctl enable --now postfix
sudo systemctl enable --now stgy-backend
sudo systemctl enable --now stgy-frontend

まとめ

STGYは、リバースプロクシにCaddy、データベースにPostgreSQL、オブジェクトストレージにMinIO、キャッシュにRedis、メール送信にMailpitとPostfixを用い、Node.jsのバックエンドとフロントエンドがそれらを操るアーキテクチャである。開発時にはMac上でDockerを用いて依存サービスを稼働させた上で、バックエンドとフロントエンドをローカルホストで動かす。

デモ運用は、VPSのUbuntu Linuxサーバ1台で行う。Docker運用と実サービス運用の双方が可能である。短期間のデモであればDocker運用が楽だ。長期的に運用し、かつ他のサービスを同一ホストに同居させるなら、実サービス運用をした方が良い。実サービス運用の場合、systemdを使って多くのサービスを管理する。また、バックエンドとフロントエンドのデプロイ用のスクリプトも用意してあるので、Dockerほどではないが、それほど手間を書けずに導入できるようになっている。

Next: STGYのデータベース