背景:なぜKubernetesではなく、なぜKamalなのか?
KubernetesはパワフルですがK8sクラスターをセットアップして月5ドルのVPS上で動くRailsやNode.jsアプリをデプロイするのは完全にやりすぎです。自分もその段階を経験しました — Deployment、Service、Ingress、cert-managerのYAMLを書いて…数百ユーザーのアプリをデプロイするためだけに。割に合いません。
もっと古い方法は?サーバーにSSH接続し、新しいイメージを取得して、コンテナを手動で再起動する。シンプルですが、ゼロダウンタイムもなく、自動ロールバックもなく、複数サーバーへのデプロイが必要になるとBashスクリプトはメンテナンスの悪夢になり始めます。
Kamal(以前はMRSKと呼ばれていた、Basecamp/37signalsチームが開発)はそのちょうど中間を埋めてくれます。提供される機能:
- SSH経由で直接デプロイ — サーバーにエージェントやデーモンのインストール不要
- コンテナスワップによるゼロダウンタイムデプロイ、リバースプロキシにTraefikを使用
- Let’s Encryptによる自動SSL管理(Traefik経由)
- 1コマンドで前バージョンにロールバック:
kamal rollback - マルチサーバーサポート — 5台のサーバーへの同時デプロイも可能
インフラを再構築したときにdocker-compose v1からv2にスタック全体を移行しましたが、そのプロセスはかなりスムーズでした。それでも痛点はありました:毎回のデプロイで手動SSHして、コマンドをコピペして、ダウンタイムが起きないよう祈る必要があったのです。Kamalはまさにその問題を解決します。
Kamalのインストール
KamalはRuby gemですが、使うためにRubyを知る必要はありません。Ruby 3.1+がローカルマシンに必要です — サーバーではありません。
# macOS — rbenvを使用
brew install rbenv
rbenv install 3.2.2
rbenv global 3.2.2
# Ubuntu/Debian(ローカルマシン)
sudo apt install ruby ruby-dev build-essential
# Kamalをインストール
gem install kamal
# バージョン確認
kamal version
VPS(サーバー)側の要件:
- Ubuntu 20.04+またはDebian 11+
- Dockerはあらかじめインストール不要 — セットアップ時にKamalが自動インストール
- SSH keyを設定済み(パスワード認証不使用)
- sudo権限のあるユーザー
何かする前にSSHアクセスを確認:
ssh user@your-vps-ip "echo OK"
deploy.ymlによる詳細設定
アプリディレクトリに移動し、Kamalプロジェクトを初期化:
kamal init
このコマンドはconfig/deploy.ymlファイルを作成します — 最も重要なファイルです。以下は一般的なWebアプリの実際の設定例です:
# config/deploy.yml
service: myapp
image: ghcr.io/your-username/myapp
servers:
web:
hosts:
- 103.x.x.x
labels:
traefik.http.routers.myapp-secure.rule: Host(`myapp.com`)
traefik.http.routers.myapp-secure.tls: true
traefik.http.routers.myapp-secure.tls.certresolver: letsencrypt
registry:
server: ghcr.io
username: your-github-username
password:
- KAMAL_REGISTRY_PASSWORD
env:
clear:
APP_ENV: production
PORT: 3000
secret:
- DATABASE_URL
- SECRET_KEY_BASE
healthcheck:
path: /up
port: 3000
interval: 3s
timeout: 10s
retries: 5
traefik:
options:
publish:
- "443:443"
volume:
- "/letsencrypt/acme.json:/letsencrypt/acme.json"
args:
entryPoints.web.address: ":80"
entryPoints.websecure.address: ":443"
certificatesResolvers.letsencrypt.acme.email: "[email protected]"
certificatesResolvers.letsencrypt.acme.storage: "/letsencrypt/acme.json"
certificatesResolvers.letsencrypt.acme.tlschallenge: true
accessories:
db:
image: postgres:16
host: 103.x.x.x
port: 5432
env:
clear:
POSTGRES_DB: myapp_production
secret:
- POSTGRES_PASSWORD
volumes:
- /data/postgres:/var/lib/postgresql/data
redis:
image: redis:7
host: 103.x.x.x
port: 6379
volumes:
- /data/redis:/data
プロジェクトルートに.envファイルを作成(gitにコミットしないこと):
# .env
KAMAL_REGISTRY_PASSWORD=ghp_your_github_token
DATABASE_URL=postgresql://user:pass@localhost/myapp_production
SECRET_KEY_BASE=your_long_random_secret
POSTGRES_PASSWORD=strong_db_password
# すぐに.gitignoreに追加
echo ".env" >> .gitignore
設定時の便利なヒント
ゼロダウンタイムを本当に実現したい場合、ヘルスチェックは必須です。 Kamalは新しいコンテナがヘルスチェックを通過するまで古いコンテナを実行し続けます。/upエンドポイントがない?HTTP 200を返すルートを自分で作るだけで十分です。
永続データにはvolumesを使用 — アップロードファイル、ストレージ、キャッシュ。Railsの例:
volumes:
- /data/myapp/storage:/rails/storage
マルチサーバーデプロイ — Kamalは並列処理し、デフォルトでローリングアップデート:
servers:
web:
hosts:
- 103.x.x.1
- 103.x.x.2
workers:
hosts:
- 103.x.x.3
cmd: bundle exec sidekiq
初回デプロイ
# 初回サーバーセットアップ — Docker、Traefik、アクセサリのインストール
kamal setup
# 以降のデプロイ
kamal deploy
初回のkamal setup実行時、KamalはサーバーにSSH接続して残りの処理を自動で行います:Dockerのインストール、Traefik起動、Postgres/Redis起動、イメージのプルとアプリ起動。所要時間は約2〜3分。2回目以降はkamal deployが通常30〜60秒で完了します。
デプロイ後の確認とモニタリング
アプリはproductionで動いています — しかし作業はそこで終わりではありません。以下のコマンドを頻繁に使うことになります:
# リアルタイムログを表示
kamal app logs -f
# 各サーバーのコンテナ状態
kamal app details
# デバッグのために実行中のコンテナにSSH接続
kamal app exec --interactive "bash"
# コンテナ内でコマンドを1回実行(例:DBマイグレーション)
kamal app exec "rails db:migrate"
# Traefikのログを表示
kamal traefik logs
# 前バージョンにロールバック(非常に高速、既存イメージを使用)
kamal rollback
SSLとルーティングの確認
SSLが正しく動作しているか確認するには、素早く実行:
curl -I https://myapp.com
HTTP/2 200とLet’s Encryptの証明書が表示されれば問題ありません。初回のLet’s Encrypt証明書の発行には数秒から2〜3分かかることがあります — その間TraefikはセルフサインのSSL証明書を一時的に使用しますので心配不要です。
よくあるエラーと対処法
ポート80/443が使用中の場合: NginxまたはApacheがサーバーで実行中の場合、Traefikはポートをバインドできません。先に旧サービスを停止:
ssh user@vps "sudo systemctl stop nginx && sudo systemctl disable nginx"
acme.jsonのパーミッションエラー: Let’s Encrypt証明書を保存するファイルには600のパーミッションが必要です。そうでないとTraefikが読み取りを拒否します:
ssh user@vps "sudo touch /letsencrypt/acme.json && sudo chmod 600 /letsencrypt/acme.json"
イメージのプルが遅い場合: Docker HubではなくGitHub Container Registry(ghcr.io)を使用してください。アジアのサーバーへghcr.ioから約500MBのイメージをプルするのに通常20〜40秒かかります;同じイメージをDocker Hubからだと2〜3分かかることがあります。
明らかにKamalは100のマイクロサービスや水平オートスケーリングが必要なケースには向いていません。しかし、1〜5台のサーバー、数千concurrent users以下のアプリ、そしてK8sのセットアップに1週間使いたくないなら — これが正しい選択です。自分は約800ユーザーのSaaSに使っていて、6ヶ月間安定稼働しており、一度もインフラに手を加える必要がありませんでした。

