メリークリスマス🎅 この記事はフィヨルドブートキャンプ Part1 Advent Calendar 2024の24日目の記事です。
昨日は駒形さんの「フィヨルドブートキャンプのポッドキャスト」でした。 フィヨルドブートキャンプ Part 2 Advent Calendar 2024もあり、昨日はかわかみさんの「フィヨルドブートキャンプを卒業するために、真剣に考えてみました。」でした。
この記事ではフィヨルドブートキャンプの卒業課題である自作サービスのデプロイ方法としてKamalを使う場合を想定して検証したことをまとめたいと思います。
検証にあたって、対応方法が分からない部分もあり、勝手ながら師匠と呼んでいるursmさんにフォローいただきつつ、無事にデプロイをすることができました🙏
動機
単純にKamalを試してみたかったというのが一つ。
もう一つは自分がフィヨルドブートキャンプで卒業課題に取り組んでいた頃(2023年4月)、自作サービスのデプロイ先をどうするかの判断が少し難しかった記憶があったためでした。
当時はHerokuが有料になりその代案としてFly.ioをデプロイ先として選択する過渡期?のような状況で、その後Fly.ioも課金がされるようになったり、デプロイ先をどうするかというのはなんだかんだ課題として付き纏ってくるため、Kamalもサーバー費用は発生するけど選択肢の一つに入ってくるのではと思い、自分で触ってみることにしました。
検証用の成果物
今回、Kamalでデプロイを試すにあたって、Rails 8でブログアプリを作ってみました。
Rails 8で導入された認証機能を使って、管理画面にログインして記事を投稿できるというシンプルなアプリです。
使用技術は
といった感じです。
準備
それでは、Kamalでデプロイするための準備を見ていきたいと思います!
サーバーの用意とセットアップ
Kamalでデプロイするにはサーバーを契約する必要があります。
フィヨルドブートキャンプではLinuxのプラクティスがあり、そこでVPSサービスを契約して課題に取り組みます。
私はこの時に使用経験があることからさくらのVPSを契約しました。
とりあえず1GBのプランです。
OSはUbuntuです。 ここでrootユーザーのパスワードや、rootでリモートログインできないようにする設定などを済ませておきます。
※Kamalはrootで実行する必要がありますが、後ほど触れます。
おそらくプラクティスではやっていない部分としてはパケットフィルター設定からポート80 (http)を443 (https)を開ける必要がある点です。
さくらVPSの場合はサーバー設定画面で
- パケットフィルター設定のタブを選択
- 「パケットフィルターを設定」をクリック
- 「パケットフィルター設定を追加」をクリック
- フィルターの種類「Web」を選択
で設定できます。
ssh接続を鍵認証で行えるようにする
こちらもフィヨルドブートキャンプのLinuxのプラクティスでやっているため、卒業課題に取り組んでいる現役生は久しぶりの場合でも復習しつつやれば大丈夫。
「サーバーの用意とセットアップ」のところで書いたとおり、rootでのリモートログインはできない状態で問題ないです。
ドメインの用意
さくらVPSを契約していれば、オプションサービスでDNS登録ができるので、そこでAレコード設定や必要に応じてサブドメインの設定を行います。
DNS設定はnginxのプラクティスでやっているため、こちらもみんな経験済みですね。
Docker hubでアクセストークンの作成
Docker hubでアクセストークンを作成します。
権限はRead ,Write,Deleteで設定しました。 作成したアクセストークンはdirenvなどのツールを使って管理すると良さそうです。
私は最近miseを使い始めたのですが、direnv同様に環境変数の管理もできて便利です。
# .envrc KAMAL_REGISTRY_PASSWORD = "Docker hubで作成したアクセストークン"
deploy.ymlの編集
Rails 8にはKamalが含まれているため、プロジェクトを作成した時点でdeploy.ymlがconfig/ディレクトリ配下に作られています。
今回、データベースはSQLiteを使用しているため、deploy.ymlの変更は最低限で済みました。
設定を変更した箇所についてコメントをつけました。
service: michi
# Dockerのコンテナ名を指定する
image: maimu/michi
servers:
web:
# DNS設定をしたドメインを指定
- michi.maimux2x.com
proxy:
ssl: true
# DNS設定をしたドメインを指定
host: michi.maimux2x.com
registry:
# Docker hubで設定しているユーザー名
username: maimu
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- RAILS_MASTER_KEY
clear:
SOLID_QUEUE_IN_PUMA: true
aliases:
console: app exec --interactive --reuse "bin/rails console"
shell: app exec --interactive --reuse "bash"
logs: app logs -f
dbc: app exec --interactive --reuse "bin/rails dbconsole"
volumes:
- "michi_storage:/rails/storage"
asset_path: /rails/public/assets
builder:
arch: amd64
ssh:
# sshで使用するユーザー
user: ubuntu
RAILS_MASTER_KEYはconfig/ディレクトリ配下に作成されています。
こちらは.gitignoreに追加しておくのと、万が一紛失するとデプロイできなくなってしまうため、バックアップをとっておくと良さそうです。
SQLiteをデータベースとして使用する場合はdatabase.ymlの編集も不要でした。
ここで一息☕️
いざ、デプロイ!と進みたいところですが、ちょっと一息入れてデータベースについて触れたいと思います。
Rails 8ではSQLiteを本番DBとして利用可能になっていて、私も今回作成したアプリのデータベースはSQLiteを使用しています。
ただ、SQLiteを本番用のデータベースに利用するというのはお仕事の世界ではあまり(というかほとんど?)見たことがないです。
自分がフィヨルドブートキャンプで自作サービスの開発に取り組む際は雰囲気で(おい!)PostgreSQLを選んでいましたが、DHHはSQLiteを推しているしどう判断したらいいんでしょう?
この点が気になっていたため、仕事先でも聞いてみたりしたのですがSQLiteがPostgreSQLなどのデータベースと何が違うのかを把握していれば自作サービスのデータベースとして使用するのはいいんじゃないかと思うに至りました。
SQLiteはファイルベースのデータベースで、データが単一のファイルに保存されます。
作成したRailsプロジェクトのstorage/ディレクトリを確認すると分かります。
# SQLiteに関する結果のみ表示しています $ tree storage storage ├── development.sqlite3 └── test.sqlite3
本番についてはdeploy.ymlで設定した以下の部分を確認するとコンテナ内の/rails/storageにマウントするようになっています。
volumes: - "michi_storage:/rails/storage"
これに対して、PostgreSQLなどのデータベースはクライアント-サーバーモデルを採用していて、データベースが外部のサーバーで管理する形となります。
この違いはアプリをスケールさせる際に大きく影響してきます。
SQLiteは同じデータベースファイルを複数のアプリケーションインスタンスで共有するのが難しく、トランザクションの競合や高い同時アクセスが求められる場合はボトルネックになってしまいます。
この点については、卒業課題である自作サービスがどんな要件かに応じて検討する必要があると思いますが、SQLiteでも問題がないケースはそれなりにあるのではないかと思います(あくまで個人の考え)。
今回の記事のテーマはKamalのデプロイであるため、詳細な言及はしていません。 もし現役生で気になる方は調べてみたり、メンターさんに相談してみてください!
いざデプロイ!!
一息挟んだので、デプロイの確認に進みます。
ターミナルで以下のコマンドを実行します。
$ kamal setup
私が実行した際は以下のエラーが発生しました。
ERROR (SSHKit::Command::Failed): Exception while executing on host michi.maimux2x.com: sh exit status: 1 sh stdout: # Executing docker install script, commit: 711a0d41213afabc30b963f82c56e1442a3efe1c sh stderr: + sudo -E sh -c apt-get -qq update >/dev/null sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper sudo: a password is required
これはdeploy.ymlで設定した以下の箇所で指定しているユーザーにrootでの実行権がないためのエラーでした。
※ssh接続の設定箇所で「rootでのリモートログインはできない状態で問題ない」と書いた点はこちらで解消します。
ssh: # sshで使用するユーザー user: ubuntu
このエラーを解消するにはサーバー側で設定を変更する必要があります。
- sshでサーバー接続
- rootになる
visudoコマンドを実行
visudo コマンドで/etc/sudoers ファイルを編集します。
以下のように変更します。
# Allow members of group sudo to execute any command %sudo ALL=(ALL:ALL) NOPASSWD: ALL
nanoの場合はCtrl + Oで変更を保存、Ctrl + Xでファイルを閉じることができます。
こちらを対応後に改めて
$ kamal deploy
を実行してみます。
※2回目以降の実行はkamal setupではなく、kamal deployで大丈夫です。
今度は別のエラーが・・・
ERROR (SSHKit::Command::Failed): Exception while executing on host michi.maimux2x.com: docker exit status: 1 docker stdout: Nothing written docker stderr: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.47/images/create?fromImage=maimu%2Fmichi&tag=b371efd1422c637a73b3487f2da10170b7d76865": dial unix /var/run/docker.sock: connect: permission denied
このエラーに対してもサーバー側で設定を変更して対応します。
以下のコマンドを実行してユーザーをDockerグループに追加します。
sudo usermod -aG docker ubuntu
設定変更後にもう一度kamal deployを実行したところ、今度はデプロイが成功して設定したドメインにアクセスしてページを見ることができました!
嬉しい!!
検証のまとめ
ここまで私が検証用アプリをKamalでデプロイするまでを振り返ってまとめてみました。
root権限の設定部分がよく分からず、うまく調べられなかったこともあって躓きましたが、フィヨルドブートキャンプのプラクティスで対応したことがある手順も多いです。
それを踏まえると卒業課題である自作サービスのデプロイ先としてVPSを契約してKamalを使用することは選択肢の一つとして良いのではと考えています。
完全に余談ですが、自分でサーバーを契約して一手間かけてデプロイをするとサービスへの愛着が謎に湧いてきます(笑)
検証用に作ったブログアプリはその後も手を加えながら使っています!
今回の検証手順は2024年12月時点のものであるため、今後のアップグレードによっては変更が加わる可能性があります。
また、自分自身分からないベースで調べつつ試したため、説明が足りなかったり適切ではない部分があるかもしれません。
その上でこちらの記事がKamalでデプロイを試してみることへのきっかけになれば幸いです。
読んでいただき、ありがとうございました〜!