🔖

MRSKとCloud Native BuildpacksでサクッとRailsをコンテナ化してデプロイした話

2023/07/13に公開

こんにちは、みてねコールドクターでエンジニアマネージャーをしている遠藤です。
今回は、MRSKとCloud Native Buildpacksを使用して、サクッとRailsをコンテナ化してデプロイした話を紹介します。

背景

弊社のサービスは、元々Herokuでホスティングを行っていましたが、様々な事情によりAWSへと移行することになりました。
移行の準備期間が短かったため、ほぼHerokuのビルドシステムと同等の機能を持つCloud Native Buildpacksを使用することにしました。
さらにデプロイメントや運用を簡単に行うために、MRSKも併用することを決定しました。

Cloud Native Buildpacksとは

Cloud Native Buildpacksは、ソースコードからクラウド準拠のイメージを作成するためのオープンソースのツールです。具体的には、Cloud Native Buildpacksは、アプリケーションのソースコードをDockerやOCI互換のイメージに変換するための方法を提供します。これにより、開発者はアプリケーションをパッケージ化し、任意のクラウド環境で実行することができます。

Buildpacksは、アプリケーションのランタイム環境を自動的に検出し、依存関係を取得し、アプリケーションをコンパイルし、DockerやOCI互換のイメージを作成します。これは、開発者がランタイムや依存関係の管理から解放され、アプリケーションの開発に集中することを可能にします。

Cloud Native Buildpacksは、HerokuとPivotalが共同で開発したもので、Buildpacks v3 APIを使用しています。これは、既存のBuildpacks v2 APIと比較して改善され、より安全で効率的なアプリケーションイメージを作成することができます。

Cloud Native BuildpacksはCloud Native Computing Foundation(CNCF)の一部であり、KubernetesやDockerなどと同じく、クラウドネイティブテクノロジーの一部となっています。
(ChatGPTから引用)

MRSKとは

  • 37signalsが開発しているコンテナのデプロイツールです。雑に表現するとコンテナ版Capistranoです。

実装

イメージのビルド

通常MRSKでは、デプロイ時にDockerfileを使ってビルドしますが、今回はCloud Native Buildpacksを使ってビルドします。
Github Actionsでビルドし、GitHub Container Registryにpushするようにしました。

build.yml
name: App Build
on:
  push:
    branches:
    - main

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  register:
    name: Package, Publish, and Register
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
    - id: checkout
      uses: actions/checkout@v3
    - id: setup-tools
      uses: buildpacks/github-actions/setup-tools@v5.1.0
    - id: setup-pack
      uses: buildpacks/github-actions/setup-pack@v5.1.0
    - id: package
      run: |
        pack build -e RAILS_MASTER_KEY=${{ secrets.RAILS_MASTER_KEY }} --builder heroku/buildpacks:20 --buildpack heroku/nodejs,heroku/ruby@2.0.0,heroku/procfile app

    - name: Log in to the Container registry
      uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: push docker image to ghcr
      run: |
        docker tag app ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

buildpackにheroku/procfile を指定すると、Procfileに記載されたコマンドからentrypointを作成してくれます。
今回は、Procfileに以下のように記載しています。

web: bundle exec puma -C config/puma.rb
release: bundle exec rake db:migrate
worker: bundle exec sidekiq -C config/sidekiq.yml
schedule: bundle exec clockwork config/clock.rb

定期実行はclockworkを導入しました。
MRSKのサンプルでは、cronの実行をしていましたが、実装の簡潔さを優先しています。

デプロイ

デプロイは、MRSKを使ってデプロイします。
MRSKの設定ファイルは以下のようになります。

deploy.yml
# Name of your application. Used to uniquely configure containers.
service: backend-service

# Name of the container image.
image: image/app_name

# Deploy to these servers.
servers:
  web:
    hosts:
      - backend.example.com
    options:
      publish:
        - ":3000"
  worker:
    hosts:
      - worker.example.com
    options:
      entrypoint: worker

  schedule:
    hosts:
      - schedule.example.com
    options:
      entrypoint: schedule

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

  # Always use an access token rather than real password when possible.
  password:
    - MRSK_REGISTRY_PASSWORD

# Inject ENV variables into containers (secrets come from .env).
env:
  clear:
    RAILS_SERVE_STATIC_FILES: true
    RAILS_LOG_TO_STDOUT: true
    TZ: Asia/Tokyo
    PORT: 3000
    RAILS_ENV: production

  secret:
    - RAILS_MASTER_KEY
    - DATABASE_URL
    - REDIS_URL

# Use a different ssh user than root
ssh:
  user: app

traefik:
  args:
    accesslog: true
    accesslog.format: json

# Configure a custom healthcheck (default is /up on port 3000)
healthcheck:
  path: /up
  max_attempts: 7
  interval: 20s

optionsでDockerの起動時のオプションを指定できます。
デフォルトだとwebのポートが空いていないので、publishでポートを開放しています。
また、cmdでコマンドを指定した場合、rails serverの後ろにコマンドを追加して実行しようとするので、workerや定期実行させるコンテナには、Procfileで作成したentrypointを指定しています。

デプロイは、以下のようにします。

$ VERSION=latest mrsk deploy -P

VERSIONを指定しない場合、ローカルの状態からバージョンを取得しようとするので、明示的にlatestを指定しています。
また、-P オプションをつけることで、ビルドをスキップしてくれます。

各種設定が終わっていれば、これでデプロイは完了です。

課題

実行はどこで行うか?

Github上のコメント等から推測すると、ローカルマシンからの実行が想定されている気がします。
弊社でもいったんローカルからの実行を想定しています。

マイグレーションの実行はどうするか?

用意されているhooksではマイグレーションの実行は難しいと感じています。
マイグレーション専用(あるいは他の用途と兼用)のサーバを用意し、先にリリースしてマイグレーション、その後本番をリリースするという方法が考えられます。
Github上のコメントでも、ローカルからコマンドを想定していそうなコメントがありました。

まとめ

色々と課題がありつつも、MRSKとCloud Native Buildpacksを使うことで、デプロイの簡潔さを実現できました。
MRSKの想定されてない使われ方をしているというのもあり、全然情報がないので、調べるのに苦労しました。
またまだできたばかりというのもあり、欲しい機能がまだ実装されていなかったり、謎の挙動があったり(バグっぽい)し、困りつつも、短期間でサーバーの移転が実現できたのはよかったです。
今後は、MRSKの機能が充実していくことを期待しています。

みてねコールドクターテックブログ

Discussion