Kamal 2で さくらのVPS にRailsアプリをデプロイ

2024/09/28に公開

開催されたばかりのRailsWorld 2024で発表されたKamal 2を使って、さくらのVPSにRailsアプリをデプロイしましたので報告します。

Linuxのサーバデプロイの知識がある程度あることを前提に、私が引っかかったポイントを中心に説明します。入門的な内容は、いつか別途書きたいと思います。

TL;DR

  • Kamalを使うと さくらのVPSなどに、Railsアプリを簡単にデプロイできます
  • VPS、ドメイン、Dockerイメージレポジトリは事前に用意が必要です
  • SSLもLet's Encyryptから自動的に取得され、追加の設定が不要です
  • 多数のアプリをひとつのVPSに同時に載せることができますので、ポートフォリオや個人開発には最適です
  • Railsに限らず、Docker化できるアプリならなんでもいける
  • 自分の感触だと、AWSよりマジでずっと簡単 (例えばこの例など)

前提とする知識

以下のことを前提に話をします。難しい内容ではなく、かなり枯れた技術なので、LinuxのVPSサーバを使ったことがなくてもググれば学習できるはずです。

  • さくらのVPSをセットアップできること
    • OSはUbuntu
    • ポート 22 (SSH)と 80 (http)、443 (https)を開ける
    • SSHにパスワードなし、public keyを使ったアクセスを設定できること
    • rootからpublic keyでアクセスできるように設定できること (通常passwordによるアクセスはさせない)
  • ドメイン名を取得できること
  • DNS設定をして、取得したドメイン名をセットアップしたVPSに向けられること
  • Docker, Gitの基本的な使い方ができること
  • DockerHubにアカウントを作成し、Docker imageのレポジトリが用意できること
  • ローカルマシンにRubyがインストールされていること(必須ではないのですが、本記事ではこれを前提とします)。Rbenvでインストールされているという前提で進めます

Kamalとは何か

Kamalの公式ドキュメントの解説を下に引用します

Kamal offers zero-downtime deploys, rolling restarts, asset bridging, remote builds, accessory service management, and everything else you need to deploy and manage your web app in production with Docker. Originally built for Rails apps, Kamal will work with any type of web app that can be containerized.

  • Docker化ができるweb appであればすべてデプロイできます
  • ゼロダウンタイムのデプロイができます
  • データベースなどの"accessory service"にも対応します(同じVPSでホストできます)
  • 1台のVPSに複数のサービスを搭載できます

一言でいうと、自分で管理しているサーバ(例えばさくらのVPS)で、HerokuとかRenderとかFly.ioのような超簡単なデプロイが安価に、かつ定額でできるようになります

Kamalのデプロイ手順(超概略版:精度は微妙だけど感覚的にはこれ)

  1. ローカルマシンでkamal setup (あるいはkamal deploy)コマンドを打ち込みます
  2. Kamalはconfig/deploy.yml(GitHub)に登録されたサーバにSSHで接続し、必要ならDockerをインストールします
  3. 次にKamalはgitにcommitされている内容を元に、Dockerfileに従ってDockerイメージを作成します
  4. DockerHubなどのレポジトリに接続して、作成されたDockerイメージをアップロードします
  5. 次にKamalはconfig/deploy.yml(GitHub)に登録されたサーバにSSHで接続します、上記のレポジトリからDockerイメージをダウンロードさせます
  6. 各サーバではDockerイメージを使ってコンテナを起動します
  7. Health checkをして、コンテナが正常に起動していることを確認したら、ネットワークからのリクエストを新しいコンテナに振り向けます(古いコンテナから新しいコンテナに切り替えます)
  8. 古いコンテナを終了させます
  9. デプロイ完了!

やること

さくらのVPSを用意する

  1. 新規にUbuntuをインストールします (ゼロからやるのが一番早いです)
  2. ポート22, 80, 443を開けます
  3. ローカルマシンからrootユーザでSSHログインできるようにします(パスワードではなくpublic key認証にするのが良い)

ドメイン名の取得

DNS設定が伝播するのに時間がかかることがありますので、まず最初にこれをやっておきます。好きなドメイン名を取得して、これが上記のVPSに向かうように設定してください

DockerHubでレジストリを作成

Kamalでデプロイをするためには、DockerHub等でレポジトリを登録できる必要があります

  1. DockerHubでレポジトリを作成します。公開レポジトリなら無数に作れるので、今回は公開のものを作ると良いでしょう
  2. KamalからDockerHubにアクセスするためのアクセストークン(Personal access token)を作成し、記録しておきます。"Read & Write"の権限が必要です。あとで使います

Railsアプリを新規作成

  1. Railsがインストーラされていない場合はgem install railsでインストールします
  2. rails new kamal-railsで新規プロジェクトを作ります。ファイルはkamal-railsのフォルダに作成されます
  3. 最新のRailsはDockerfileが自動的に作成されますので、この場所と内容を軽く確認します。変更は不要です
  4. Kamalは静的ファイルを配信するNGINXのようなウェブサーバがありませんので、config/environments/production.rb(GitHub)でconfig.public_file_server.enabled = trueとします。いまどきは静的ファイルはCDNを使いますので、静的ファイル配信用のウェブサーバを使わないことも多くなっています。なおGoで書かれたThrusterは静的ファイルの配信が得意なので、実際にはThrusterもインストールした方が良いしょう
  5. RailsのデフォルトのデータベースはSqlite3です。config/database.yml(GitHub)の設定をみると、本番(production)環境だけdatabaseが設定されていません。本番データベースを下記のように設定してください
production:
  <<: *default
  database: storage/production.sqlite3

Kamalをインストール

  1. 新しく作ったkamal-railsフォルダに入ります(cd kamal-rails)。
  2. bundle add kamalでKamalをインストールします
  3. kamal initでKamalの設定ファイルを作成します

Kamalを設定

config/deploy.yml

詳しくはGitHubに公開していますので、ご確認ください。

  1. service:のところをサービス名にする。今回はkamal-rails
  2. image:のところは、DockerHubで作ったレジストリの名前です。私はnaofumik/kamal-railsにしたので、これを使います。ここは自分の情報を入力してください。
  3. servers: web:のところは、さくらのVPSのIPアドレスを入力します。私は133.167.104.211です。ここは自分の情報を入力してください。
  4. proxy: host:のところは、自分が取得したドメイン名を入力してください。私はkamal-rails.castle104.comです。ここは自分の情報を入力してください。
  5. proxy: app_port: 3000を追加してください。これはRailsコンテナに接続するポートになります。Thrusterを使うとportが80になるという情報もあり、その場合はこれが不要になると予想されます。
  6. registry: username:はDockerHubのユーザ名を入れてください。私はnaofumikです。ここは自分の情報を入力してください。
  7. env: secret: - RAILS_MASTER_KEYを記載してください。ここではRailsが稼働しているコンテナにRAILS_MASTER_KEY環境変数を渡します

また以下のところも必要に応じて変更してください。

/storageの永続化
# Use a persistent storage volume.
#
volumes:
  - "kamal_rails_storage:/rails/storage"

上記はRailsの/storageフォルダの中を永続化します。Dockerを使ったデプロイの時は、デプロイのたびにすべてのファイルが入れ替わってしまいます。/storageフォルダはsqlite3のデータベースやActiveStorageの画像ファイルを入れる場所になりますので、これでは都合が悪いです。そこでvolumesを使って、別のところにこのフォルダの中身を保存しています。

なお/rails/storageのところの"/rails"は、DockerfileのWORKDIRに相当しますので注意してください。

デプロイ時のAssetがスムーズに切り替わるように
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
# hitting 404 on in-flight requests. Combines all files from new and old
# version inside the asset_path.
#
asset_path: /rails/public/assets

RailsはJS, CSSや画像ファイル等のassetにfingerprint(指紋)のハッシュをつけます。assetはCDNやブラウザなどにキャッシュさせたい一方、更新された時に確実に新しいものに切り替える必要があります。そのためにRailsはSprocketsやPropshaftが指紋ハッシュをファイル名につけます。しかし、ゼロダウンタイムのデプロイをする時に問題になることがあります。何かの理由でHTMLのバージョンが古い場合、そこから呼び出される古いassetが見つからなくケースがあるためです。

Kamalはこのようなケースでも問題が起きないための工夫をしています。これがその設定です。

なお/rails/public/assetsのところの"/rails"は、DockerfileのWORKDIRに相当しますので注意してください。

.kamal/secrets

GitHubはこちら

デプロイする際に必要なトークンやパスワードを読み込むところです。ここに登録された情報は上記のconfig/deploy.ymlで読み込まれます。また.kamal/secretsに直接秘密情報を記載せず、別のところに記載します。

私は下記のようにしています。

  1. KAMAL_REGISTRY_PASSWORD.kamal/registry_password.keyを読み込むようにします。
  2. .kamal/registry_password.keyファイルを作ります。.kamal/registry_password.key.gitignoreでGitに登録されないようにしてください!!! 。中身はDockerHubのアクセストークン(Personal access token)の内容をペーストします
  3. # RAILS_MASTER_KEY=$(cat config/master.key)となっているところはコメントアウトして、RAILS_MASTER_KEY=$(cat config/master.key)としています。config/master.keyから読み込むようにしています

Healthcheckの設定

config/environments/production.rb(GitHub)を変更します。下記のところをコメントアウトしてください。ヘルスチェック時のリクエストに対してはSSLを使わないようにするものです。

  # Skip http-to-https redirect for the default health check endpoint.
  config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }

Kamal実行

  1. kamal setupでセットアップが行われ、Railsアプリがデプロイされます
  2. 次回以降はkamal deployでデプロイできます

Rails以外

Docker化できるものは基本的にKamalでデプロイできますので、試しに色々作ってみました。

やらなくて済むこと

やらなきゃいけない事も大切ですが、やらなくて済む、いや考える必要すらないことも大切です。最後にそこを少しだけお話しします

  • AWSと異なり、複雑な権限管理を考える必要はありません。AWSで出てくるVPCも考える必要がありません。内部では存在していますが
  • SSL証明書は無料だし、それどころか一切考える必要がありません。Let's EncryptとKamal 2がその辺りを全部やってくれます。SSLって何のことか知らなくてもほぼいけます
  • ゼロダウンタイムデプロイは勝手にやってくれます。それ以外のデプロイはやらせてくれません

今後

今回はsqlite3を使いましたのでRailsと同じコンテナの中で動かしましたが、別コンテナにデータベースを載せたり、あるいはそのバックアップを取ったりということをやりたいと思います

Discussion