Kamal 2 を使い、インフラに詳しくない人でもNext.jsを296円のVPSにデプロイできるよう、説明してみる
Kamalシリーズ
Kamalについては他にも記事を書いていますので、ご覧ください
- Kamal 2で さくらのVPS にRailsアプリをデプロイ
- Kamal 2 を使い、インフラに詳しくない人でもNext.jsを296円のVPSにデプロイできるよう、説明してみる(本記事)
- Kamal 2でNext.js + DatabaseをVPSにデプロイする
- Hono + JSX + Hotwire + SQLite + Kamal
はじめに
9月26, 27日に開催され、めちゃくちゃ盛り上がったRails World 2024でKamal 2が発表されました。Kamal 2はRuby on Railsを作った37signals社が、自社の人気サービスをデプロイするのに使用しているツールです。
37signals社はAWS等のクラウドに年間で$3,201,564を使った(2022年: 日本円で4.5億円ほど)らしく、一方でAWSを使ってもインフラ系人員の削減もほとんどできなかったので、全然割に合わないからもうクラウドはやめて自分たちのサーバを使うと宣言しています。Kamal 2はそのために作られた、本格的なデプロイツールです。
(ちなみに自分たちのサーバを使うと言ってもオンプレミスでサーバを運用するというのではなく、さくらの専用サーバに近いイメージです)
- Dockerベース
- Docker化されたアプリなら何にでも対応 (Railsだけじゃない!)
- Dockerさえインストール可能なら、どんなLinuxサーバにもデプロイ可能 (Dockerは自動インストールしてくれる)
- ゼロダウンタイムデプロイなど、本格的なサービスに対応したデプロイ機能
- データベース等の付随サービスもKamalが管理し、サーバのDocker上にデプロイされる
Ruby on RailsをKamal 2でデプロイする記事は別途書きましたので、今回はNext.jsをデプロイします。しかも月額たった296円(税込)のVPSサーバ(ConoHa VPS)を使います!
本記事の目標
- 商用・個人開発の如何に関わらず、月額定額・全部込みの明朗会計のサービスだけを使い、安心して複数のプロジェクトを公開できること(高額請求の心配がない)
- インフラに詳しくないエンジニアやデザイナーが、自分でレンタルVPSをレンタル・セットアップし、Next.jsをデプロイできること
- インフラを触って勉強するきっかけになること
前提
以下のものがローカルマシンにすでにインストールされていて、基本的な使っ方ができることが前提になります。Next.jsの開発をしているのならば、3つともインストール済みではないかと思います。
- Node.jsおよびNPM
- Git
- Docker
もし使ったことがなくても非常に基本的な使い方しかしませんので、チュートリアルをやっていただければと思います。
作るもの
基本的なものですが、1時間あたり数千ユーザは捌ける構成です。
左から順番に解説します
- インターネットに接続したブラウザからリクエストが来ると、まず最初にVPSプロバイダ(今回はConoha VPS)が用意した
ファイヤーウォール
(Conohaではセキュリティグループと呼びます)に届きます- ここでport 22(SSH), 80(HTTP), 443(HTTPS)向けのトラフィックのみを通します
- つまり、外部からアクセスできるのはSSH (外部からターミナル接続できる機能), HTTP (暗号化なしの"http://...")接続機能, HTTPS (暗号化ありの"https://...")接続機能だけに制限します。これはセキュリティ上必須です
- ファイヤーウォールを通過したリクエストは
Kamal Proxy
に届きます-
Kamal-Proxy
に来たリクエストは、URLのホスト部分("app1.example.com"のところ)で振り分けられ、対応するNext.jsアプリのContainerに送られます - Next.jsアプリが複数ある場合は、URLのホスト部分に応じて振り分けられます
-
Kamal-Proxy
のもう1つ大事な機能は、TLS証明書の自動取得・更新です。これがないとHTTPS接続ができず、セキュリティ上の問題になります。一般には有料でかつ複雑なセットアップが必要ですが、Kamalは無料で全自動でやってくれます
-
-
Kamal-Proxy
によって振り分けられたリクエストは、適切なNext.jsアプリのContainerに届きます- Next.jsアプリのContainerは、皆さんがローカルのマシンで
npm run build
,npm run start
をやるのとほぼ同じ箇所です。つまりアプリの本体です - コードを書き換えてデプロイするときは、ここだけがスムーズに新旧交代します
- Next.jsアプリのContainerは、皆さんがローカルのマシンで
デプロイ時の流れ
デプロイは複雑なので完全に理解する必要はありません。以後の設定ファイル編集等に登場する3人の役者、つまりローカルマシン、DockerHub、そしてサーバ(VPS)が存在することだけでも把握していただければと思います。
- 「Docker Imageをbuildする」の箇所は、Docker開発をしている人なら馴染んでいる
docker compose build
もしくはdocker build .
コマンドに相当します。- Docker Imageは、Next.jsを起動するために必要なOS(Linux)、Node.js、NPM、Next.jsフレームワーク、自分自身が書いたコードをすべてまとめたものです。パソコンのドライブに相当するものです
- コードを変更するたびにDocker Imageを作り替えます。それが「Docker Imageをbuildする」ステップです
- 「DockerHubにアップロードする」の箇所は、Docker Imageをサーバに配信する準備段階です
- 本格運用をすると、サーバは何十、何百台になったりします。Docker Imageはそのすべてのサーバに届ける必要があります。さすがにローカルマシンから直接配信するのは心配です
- DockerHubはDocker Imageを保管するRepositoryです。何十、何百のサーバはそれぞれDockerHubにアクセスし、Docker Imageを要求すれば良いのです。そのために「DockerHubにアップロードする」ステップがあります
- Kamalは各サーバにDeployの指示を出します
- 指示を受けた各サーバはDockerHubに対して、適切なDocker Imageを要求します
- 指示されたDocker Imageはサーバにダウンロードされます
- 各サーバではDocker Imageを実行します。これはDocker開発をするのであれば、
docker compose run
もしくはdocker run
コマンドに相当します。Docker開発をしていないのであれば、npm run start
に相当します。Docker Imageが実行された状態、つまり稼働中のサーバはContainerと呼ばれます - Kamalのデプロイはゼロダウンタイムデプロイと呼ばれるものです。デプロイ時にサービスは一瞬も停止しません
- (6)で新しいContainerを立ち上げている間、古いContainerはまだ残しています。そしてインターネットから来たリクエストには、古いContainerが対応します
- 新しいContainerが立ち上がったことが確認できたら(healthcheck)、次のリクエストからは新しいContainerに振り分けられます。そして古いContainerは停止されます
VPSのセットアップ
Conoha VPSを使った場合のセットアップ手順については、ビデオで紹介していますので、ご覧ください
ここでやっていることをまとめると
- VPSを契約する
- VPSにUbuntuをインストールする
- VPSがSSHとWeb(HTTPとHTTPS)のサービスを提供するようにする
- ローカルマシンからSSHでログインできるようにする
以降のセットアップのビデオ
VPSのセットアップから実際のデプロイまでの様子(つまり以下全部)をビデオでも紹介しています。ご覧ください
ローカルマシンの準備
Node, Git, Dockerのインストール
上述したように、ローカルマシンには下記のものがインストールされている必要があります
- Node.js, NPM
- Git
- Docker
Next.js開発をしてもDockerは使っていない可能性がありますので、必要に応じてインストールしてください。Dockerのウェブサイトからインストールしてください
Kamalの準備
今回はKamalをインストールしないで使います。下記のコマンドだけ、ターミナルから実行してセットアップをしてください[1]。
alias kamal='docker run -it --rm -v "${PWD}:/workdir" -v "/run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock" -e SSH_AUTH_SOCK="/run/host-services/ssh-auth.sock" -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/basecamp/kamal:latest'
動作確認のために次のコマンドを実行してください。バージョン番号 (2024年9月30日時点では"2.0.0")が表示されたら成功です。
kamal version
Create Next AppでNext.jsの新しいアプリを作成
公式ドキュメントの通りに操作します。色々なオプションがありますが、どれでも問題ありません。
Next.jsアプリをDocker化
上記の項目で作成されたNext.jsアプリをDocker化します。公式ドキュメントのSelf-Hostingの項目の"A Node.js server"と"A Docker Container"のところを実施します。
私がやったもののコードがGitHubにありますので、こちらもご覧ください。
package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
Dockerfile
見本アプリのDockerfileをそのままコピーします
見本アプリのIn existing projectsに書かれている通りに、next.config.js
を変更します。ESモジュールを使っている場合は下記のようにしてください。
next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
};
export default nextConfig;
publicフォルダ
上記のDockerfileは/public
フォルダの存在を前提としています(無いとbuildが落ちます)。このように/public
フォルダを作り、その中に.keep
という空のファイルを作ってください。空のフォルダはGitに無視されるため、.keep
のファイルは重要です(何か一つでもファイルがあれば良いので、それがあれば.keep
じゃなくても良いです)。
動作確認を実施します。下記のコマンドを実行し、http://localhost:3000でNext.jsのページが表示されることを確認してください
docker build . -t kamal_next
docker run --rm -p 3000:3000 kamal_next
動作確認コマンドの解説)
-
docker build . -t kamal_next
は上記のDockerfileの中身に沿って、Docker Imageを作成するコマンドです。NodeとかNext.jsをインストールし、さらにnpm run build
をしてNext.jsをbuildします。Docker Imageの名前を"kamal_next"にしています(名前は任意)。 -
docker run -p 3000:3000 kamal_next
はDocker Imageを実行し、Docker Containerを実行します。node server.js
を実行して、Next.jsのサーバを立ち上げます。通常の開発時にnpm run start
とするのと同じです。
これが成功すれば、Dockerfileまではうまく作れた証明になります!
Next.jsにhealthcheckを導入
ヘルスチェックは、サーバが作業を正常に実行できるかどうかを確認する方法です。KamalではこれからデプロイしようとするDocker Containerが正常に立ち上がったかどうかを確認するのに使用します。
ヘルスチェックを実装するためには、Next.jsアプリに/up
のエンドポイントを作成し、これがStatus 200を返すようにします。下記のファイルを作成して実装します。
app/up/route.ts
export async function GET() {
return new Response("I'm Healthy", {status: 200})
}
Kamalのセットアップ
いよいよKamalのセットアップです。
下記を実行するとデフォルトの設定ファイルが生成されます。
kamal init
DockerHubアクセス用のPersonal Access Tokenの取得
Kamalは先ほどセットアップしたDockerHubのアカウントに接続して、Docker Imageをアップロードしたり、あるいはダウンロードしたりする必要があります。その時のパスワードがわりにpersonal access tokenを作ります(大元のパスワードは漏洩したくないので、必ずpersonal access tokenを使ってください)。
- "Account Settings" > "Personal Access Tokens" > "Generate New Token" で新しいpersonal access tokenを作成してください。
- "Access Permissions"は"Read & Write"としてください。Docker Imageをアップロードもダウンロードもするためです。
- Personal Access Tokenはあとで使いますので、記録しておいてください。
.kamal/secrets-common
の作成
-
/.kamal
フォルダにsecrets-common
というファイルを作成してください。 - これは機密情報を含みますので、gitには絶対にコミットしないでください。.gitignoreに
/.kamal/secrets-common
を登録してください - これは機密情報を含みますので、gitには絶対にコミットしないでください!。.gitignoreに
/.kamal/secrets-common
を登録してください - これは機密情報を含みますので、gitには絶対にコミットしないでください!!。.gitignoreに
/.kamal/secrets-common
を登録してください -
secrets-common
に、先ほどのPersonal Access Tokenを登録します。書き方は下記の通りになります -
.kamal/secrets
の方は、KAMAL_REGISTRY_PASSWORD
のところをコメントアウトしてください
.kamal/secrets-common
KAMAL_REGISTRY_PASSWORD=[先ほどのDockerHubのPersonal access token]
.kamal/secrets
# Option 1: Read secrets from the environment
# KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
config/deploy.yml
の編集
これがKamalのメインの設定ファイルです。下記のところを変更してください
# Name of your application. Used to uniquely configure containers.
service: [アプリの名前(任意の名前で良いです):例えば"hotwire-and-next"]
# Name of the container image.
image: [コンテナイメージの名前:<DockerHubのアカウント名>/<イメージ名(任意のもので良いです):例えば"naofumik/hotwire-and-next"]
# Deploy to these servers.
servers:
web:
- [VPSのIPアドレス:例えば "123.456.789.99"]xxx.xxx.xxx.xxx
# job:
# hosts:
# - 192.168.0.1
# cmd: bin/jobs
# Enable SSL auto certification via Let's Encrypt (and allow for multiple apps on one server).
# Set ssl: false if using something like Cloudflare to terminate SSL (but keep host!).
proxy:
ssl: true
host: [VPSのホスト名:例えば "hotwire-n-next.castle104.com"]
app_port: [Next.jsが動くポート番号:"3000"]
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
# server: registry.digitalocean.com / ghcr.io / ...
username: [DockerHubのアカウント名:例えば"naofumik"]
host:
のところは、VPSを登録したときのホスト名でも良いですが、自分でドメイン名を持っていれば、DNSを設定してこのVPSのIPアドレスに向けても良いです。DNSの設定方法については、ここでは紹介しませんが、一般的な内容ですので必要であれば各自でお調べください。
kamal setup
コマンドの実行
いよいよデプロイです!
- 現在のコードをGitにコミットします(GitHub等にpushする必要はありません。ローカルだけで十分です)
- コマンドラインから
kamal setup
を実行します
そうするとKamalは以下の処理を実行します
- 各サーバにDockerがインストールします
- デプロイ時の流れに沿ってデプロイを実行します
最後にブラウザからVPSに接続して、Next.jsのページが正しく表示されることを確認します
kamal deploy
コマンドの実行
2回目以降はkamal deploy
を実行すれば十分です(setup
を実行しても不具合は起こりませんが)
- 現在のコードをGitにコミットします(GitHub等にpushする必要はありません。ローカルだけで十分です)
- コマンドラインから
kamal deploy
を実行します
そうするとKamalはサーバへのDockerのインストールを省略して、再度、デプロイ時の流れに沿ってデプロイを実行します。
振り返り
流石にVercelにデプロイするときほどは簡単ではありませんが、上記でやったことをまとめました。ぜひお試しいただければと思います。もし引っかかるところやわからないところがありましたら、私もまだ勉強をしたいので、XのDMなどで気軽にご連絡ださい!
- VPSをセットアップしました
- DockerHubにアカウントを作りました
- Next.jsをDocker化しました
- Next.jsにhealthcheckのエンドポイントを作成しました
- Kamalの設定ファイルを編集しました (7行程度)
-
kamal setup
に実行
Kamalを使って、気軽にたくさんのプロジェクトを作り、たくさん公開しましょう!
-
RubyをインストールせずにKamalを実行するためにDockerを使っています。RubyとKamalをインストール済みのDocker Imageをタウンロードして、その中のKamalを実行する仕組みです。Dockerさえインストールされていれば、Rubyをインストールする手間が省けるわけです ↩︎
Discussion
Kamalの準備のセクションで、kamalのimageのダウンロードについて書かれています。
2024/10/10時点だと、latestタグで最新のimageがダウンロードされました。
非常に有用な記事をありがとうございます。
1つのVPS上に、productionとstagingの2つの環境を作成したいと考えています。
stagingのみまたは、productionのみのデプロイを行うことは可能でしょうか?
ありがとうございます。
私自身はやったことはないのですが、下記のようにやると良いのではないかと思います。
設定が異なるところは
config/deploy.staging.yml
に書き込む(差分)とのことです。そうすると ドメイン名(host)が変えられるはずです。
また環境変数も変えられると思いますので、Railsであれば
RAILS_ENV
も買えると良いと思います。あっ、忘れていましたけど、あと
deploy.staging.yml
ではservice
も変えないといけなさそうですねご返信いただき誠にありがとうございます!
ご返信いただいた内容を元に、staging, productionの環境を作成し、それぞれにNext.jsのアプリケーションをデプロイできる構成を作成してみました...。
よかったです!
GitHubへのリンク、共有ありがとうございます!