コンテナ向けデプロイツールMRSKを試してみる

## 3行まとめ

* MRSKは「コンテナ時代のCapistrano（Capistrano for Containers）」的なデプロイツール。すごくDHHぽい。
* $5くらいの素のcompute instanceがHerokuみたいに使えるようになる(ただしDBやS3やRedisは必要に応じて別途用意する前提、合わせて別インスタンスで立ち上げる機能もあり)
* 37Signalsではproductionで使ってるようだけどまだまだ荒削りなので、しばらくは一緒に開発したいくらいの勢いで使いたい人向け

## MRSKとは

MRSKはRailsの創始者DHHが新しく作ったデプロイツールです。

https://mrsk.dev/

初コミットは2023年の1月7日ということで真新しいプロダクトなのですが、中身を見るとあまり新しそうに見えないというか、今どきのクラウドネイティブな世界観から見ると正直懐かしい感じもあります。なんで今これが作られたのでしょうか。その理由は37Signalsの（脱）クラウド戦略にあります。

これまで37Signalsではいろんなサービスを作ったり閉じたりしつつ、それなりの規模のサービスを運用しています。現行のサービスの運用にはクラウドを多用していたそうですが、最近脱クラウドを進めているそうです。ワークロードが安定している場合にはクラウドを使わず自前の機材を使った方が安くつきそう、ということのようですね。
とはいえ自前の機材では運用する基盤が欲しくなります。これまではKubenatesを利用していたりもしていたそうですが、自前でK8sを動かすのも大変なので、もっと手軽に運用できるツールを探した結果「シンプルな奴を自分で作った方が早いのでは？」という発想で作られたのがこのMRSKです。

クラウド離脱・移転のもろもろについてはDHHもブログでいろいろ書いているのですが、どちらかというと今回の移転作業にも協力しているDeft社のケーススタディ記事を読んだ方が分かりやすいかもしれません。
クラウドの価格はさておき自力でオンプレ運用するのは大変そうなので、脱クラウドするのは今ひとつ現実味がないのですが、こういうところと組めるなら実現可能なのかも？　と思いました。

https://deft.com/case-study-37signals/

DHHと脱クラウドとMRSKについては以下の記事も参考になります。

https://laiso.hatenablog.com/entry/ddhh

とはいえ普通は脱クラウドに走るよりも、特定のクラウドの機能べったりで動かすのではなく、実験用環境や学習用環境、個人開発等のため、安くて低機能なサーバで動かしたい、みたいな需要の方が大きそうです。
今どきはそんな用途でもコンテナを使いたくなったりするわけなのですが、コンテナとなるとAWS等でコストをかけて構築するか、Herokuやfly.io等のもろもろ隠蔽された便利サービスを使う場合が多いように思います。そういう環境は便利なところもある反面、あれがないとかこれが高いとかみたいな話になりがちでした。

このMRSKは、もっと手軽に格安コンテナ環境を動かしたい、というある程度サーバの知識がある人向けのデプロイツールとしても期待できそうに思います。

## 使い方

という話はさておき、MRSKを使ってみましょう。
とりあえずサーバのハードを調達して…というのも大変なので、ここではクラウドやVPSなどで使う前提で話を進めてみます。

ここではMRSKのバージョンは<del>0.8.4を前提としています</del>0.9.1に上げました。

### 必要なもの

* Linuxサーバ
* Docker Hubかなにかのアカウント
* アプリをDockerで動かすためのDockerfile

accessoriesとしてDBやredisをコンテナで立ち上げることもできますが、別途用意したサーバを使うことも可能です。
私はLinodeでDBを用意してみましたが、もちろんRDS等を使うのもよいでしょう。

### サーバを用意する

とりあえずsshでログインできるLinuxサーバを用意します。ディストリビューションはUbuntuかDebianが推奨です。私はDebianで試していました。
デフォルトではrootでいきなりsshログインするというすごい思想なのですが、別アカウントでも利用可能です（若干手間がかかります）。

サーバの置き場所は何でも良さそうです。AWSとかAzureとかのインスタンスでもいいですし、DigitalOceanとかLinodeとかでもなんでも良さそうです。日本だと[WebARENA Indigo](https://web.arena.ne.jp/indigo/)が一番安そう(月額349円)ですが、Indigoでも動くようでした。もちろんベアメタルの自前サーバでもいいでしょう。

### アプリを用意する

当然のようにRailsを前提としていますが、それ以外でも動きそうではあります。
アプリのroot直下にDockerfileを置いて、それをbuildすれば動くようにしておきます。
手頃なRailsアプリがなければ適当にscaffoldして用意しておいてください。

一点、アプリ側ではhealthcheckに対応する必要があります。[Rails 7.1では標準で対応する](https://techracho.bpsinc.jp/hachi8833/2023_02_07/126734#1-8)ようなのですが、そうでなければ`/up`を叩いたらレスポンスコード200を返す機能を実装しておきます。

```health_controller.rb
class HealthController < ActionController::Base
  rescue_from(Exception) { render_down }

  def show
    render_up
  end

  private
    def render_up
      render html: html_status(color: "green")
    end

    def render_down
      render html: html_status(color: "red"), status: 500
    end

    def html_status(color:)
      %(<html><body style="background-color: #{color}"></body></html>).html_safe
    end
end
```

```config/route.rb
Rails.application.routes.draw do
  # (略)
  get "up" => "health#show"
  # (略)
end
```

Dockerfileはこんな感じの素朴なもので試していました。もうちょっと頑張った方が容量も減らせてうれしそうではあります。

```Dockerfile
FROM ruby:3.2.1-slim

# Install dependencies
RUN apt-get update -qq && apt-get install -y build-essential libvips curl libpq-dev

# Mount $PWD to this workdir
RUN mkdir /myapp
WORKDIR /myapp

ENV LANG="C.UTF-8"
ENV RAILS_ENV="production"
ENV NODE_ENV="production"

COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle config jobs 4 \
 && bundle config deployment true \
 && bundle config without 'development test' \
 && bundle install

COPY . /myapp

RUN SECRET_KEY_BASE=placeholder bundle exec rails assets:precompile

ENV RAILS_SERVE_STATIC_FILES="true"
ENV RAILS_LOG_TO_STDOUT="true"

EXPOSE 3000

CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "3000"]
```

### MRSKをインストールする

`gem install mrsk`でglobalにインストールしてもいいですし、Gemfileのdevelopmentに`gem 'mrsk'`を追加してもいいです。Railsであれば後者が一般的かと思います。

### 設定ファイルを用意する

`mrsk init`または`bundle exec mrsk init`を実行すると、config/deploy.ymlに設定ファイルが作られます。MRSKの設定はこのファイルに集約されます。これを必要に応じて編集します。

```yaml
# Name of your application. Used to uniquely configure containers.
service: sample-mrsk1

# Name of the container image.
image: takahashim/mrsk1

# Deploy to these servers.
servers:
  - mrsk1.example.com

# Credentials for your image host.
registry:
  # Specify the registry server, if you're not using Docker Hub
  # server: registry.digitalocean.com / ghcr.io / ...
  username: takahashim
  password:
    - MRSK_REGISTRY_PASSWORD

# Inject ENV variables into containers (secrets come from .env).
env:
  clear:
    DATABASE_NAME: sample_mrsk1_production
  secret:
    - RAILS_MASTER_KEY
    - DATABASE_HOST
    - DATABASE_USER
    - DATABASE_PASS

# Call a broadcast command on deploys.
# audit_broadcast_cmd:
#   bin/broadcast_to_bc

# Use a different ssh user than root
ssh:
  user: debian
```

最後のssh.userの設定はWebARENA Indigo用のものです（IndigoはDebianだと初期アカウントが`debian`になる）。rootでログインできるサーバなら不要です。

値が大文字になっているところは環境変数の値が使われるところです。MRSKは`.env`をサポートしているので、そこに環境変数を指定しておくとその値が使われるようになります。

```
MRSK_REGISTRY_PASSWORD=(Docker Hubのアクセストークン)
RAILS_MASTER_KEY=(Railsのマスターキー)
DATABASE_USER=(DBのユーサ名)
DATABASE_PASS=(DBのパスワード)
DATABASE_HOST=(DBのホストアドレス)
```

秘匿情報は1password等のサービスに格納してそれを使った`.env`を生成することもできます（現状はERbで`.env`のテンプレートをごりごり書くような感じです）。

### Docker Hubのアカウントを用意する

Docker Hubじゃなくてもいいようなのですが、コンテナのイメージを置く場所が必要になります。
Docker Hubを使う場合、Read/Writeができるアクセストークンを作り、それを上記.envの`MRSK_REGISTRY_PASSWORD=`に書いておきます。

Docker Hub以外を使う場合はconfig/deploy.ymlの`registry:`の情報を適宜更新します。

### デプロイする

`mrsk deploy`または`bundle exec mrsk deploy`でデプロイできます。

ところでWebARENA Indigoのようにrootを使わない場合、config/deploy.ymlを変更する他に、サーバにログインして別途設定を行っておく必要がありそうです。以下のようなコマンドを実行してからデプロイすることになります。

```setup.sh
# dockerのインストール
sudo apt-get update -y
sudo apt-get install -y docker.io curl

# rootにならずにdockerコマンドを使えるようにする
sudo gpasswd -a $(whoami) docker
sudo chgrp docker /var/run/docker.sock
sudo service docker restart
```

うまく動いたでしょうか。なお、２回め以降のデプロイは、`bundle exec mrsk redeploy`の方が早いです。

裏でどんなコマンドが動いているかはログを見ればその通りです。docker buildxを使ってローカルでイメージを作り、Docker Hubにアップロードした後、サーバ側からイメージをダウンロードし動かす、という流れです。
ホストのOS（Apple SiliconなmacOS）、ホストのコンテナ(ArmなLinux)、サーバのコンテナ(X86_64なLinux)といろいろな環境が混ざっていても、MRSK側でよろしくやってくれます。builderの設定を行うことも可能です。

### 動作確認など

MRSKにはその他いろいろなコマンドがあります。HerokuとかCapistranoっぽい雰囲気です。確認用などで使うのは以下のあたりでしょう。

* `mrsk details`: 動いているコンテナの情報が得られます。
* `mrsk app exec 'bin/rails about'`: Railsのコマンドも実行できます。
* `mrsk app exec -i bash`: コンテナ内のシェルにログインできます。
* `mrsk app exec -i 'bin/rails c'`: Railsのconsoleに入れます。
* `mrsk app logs`: ログが見られます。
* `mrsk config`: 設定情報が見られます。秘密情報がそのまま表示されてびっくりします⇒さすがに[issue](https://github.com/mrsked/mrsk/issues/96)が立ってました
* `mrsk stop`:アプリを止めます
* `mrsk start`:アプリを起動（再開）します
* `mrsk remove`: 全コンテナを消して、初期状態に戻します。

RailsでまっさらなDBから始めるなら、`bundle exec mrsk app exec 'bin/rails db:create db:migrate'`辺りから始めることになりそうです。

### トラブルシューティング

私が遭遇したトラブルを紹介します。

* `ERROR (SSHKit::Command::Failed)`が出る

サーバにdockerが入っていない場合いきなり(sudoもつけずに)apt-getしだすので、rootでないと動きません。また標準の設定ではrootでないとdockerコマンドが動かないようでした。
ssh.userでroot以外を設定した場合は、先述のsetup.shを実行しておきます。

* docker buildx buildを実行中に`ERROR: failed to do request`で死ぬ

詳細は不明ですが、私の環境ではここでエラーになることが何度かありました。ログに表示されるdockerコマンドを普通に実行してもエラーが再現したので、MRSK側の問題でもなさそうでした。
このエラーが出た場合、ローカルのDocker自体を終了＆再起動すると動いたりしました。

## 感想

試してみて思ったのは、これが半年くらい前、Heroku有料化の騒ぎが起こる前にこれが完成していれば選択肢が増えてよかったかな……というものでした。
ふつうにクラウドでコスト的にも満足している人には不要そうなツールですが、お金をけちりつつ自前サーバでいろいろやりたいという人にとっては、そこそこ実用的で面白いツールにはなりそうです。

実験用の格安サーバ環境としては、DBの準備が厄介です。
これについてはDHHはSQLite推しのようだったので、もしかすると[Litestream](https://litestream.io/)あたりを使って解決する方が賢明なのかもしれません。

もっとも最初に書いた通り、MRSK自体の完成度はまだ低そうというか、良く言うと伸び代しかない状態なので、それを楽しめる人は手を出してみるのも良さそうです。
私もcapistranoで運用しているRailsアプリをこれに移行してみようかなと思いました。