LoginSignup
0
0

More than 1 year has passed since last update.

こんな感じでRailsコンテナーを作ると良いのではないか?

Posted at

やってみたこと

RailsでWebサービスを行うプロジェクトに参画したがどんな風にプロダクション用のDocker化をするのが良いか勉強がてらやってみた。

これを書いてる人のスペック

ここ10年ぐらいの RubyとRailsの文化を知らない。出始めにちょっと触ったことはあるが、すごく流行っていた時期にはほとんど触れる機会がなく、今更だけどRailsを勉強中。

  • 実はRailsによるアジャイルWebアプリケーション開発の初版本は読んでいた。
  • プロダクションレベルでRailsアプリの開発運用経験なし。(ゼロ年代にPoCや簡易ツールの為には利用あり)
  • gemパッケージを一つだけ登録したことがあるがゼロ年代の話(対象Adobeのサービスは終了済み)。
  • Railsで書かれているRedmineからGitLabのプロジェクト移行ツールへのコントリビュートはしたが、このツール自体はRubyでなくPythonで書かれている。
  • 現在Railsで書かれたコードを読んでいるが、webpacker(使われなくなるらしいが)とかsidekiqとかその他便利な周辺ツールが盛り込まれていることを最近知った。
  • AWSやAzureでPythonによるバックエンドやETL処理を書きTerraform/Serverless Frameworkでインフラを作る仕事がここ最近のメイン。CI/CDパイプラインやDockerfileなども書く。

環境構成

  • 開発環境: M1 Mac
  • Ruby 3.0 + Rails 6 + Node.js v16(LTS)
    • Ruby 3.1 は 3.2で廃止予定のメソッドをRails側が対応していないようで警告が出たり、標準ライブラリーのgem化があったり…。なので2.6->2.7->3.0->3.1で試したが、無難そうな3.0に落ち着いた
    • Rails 7 は webpackerの非標準化などの対応が面倒なので Rails 6 にした
    • Ruby 2.6, 2.7 + Rails 6 でも大体同じ感じで動くとおもう(最初は2.6,2.7で試していた)
    • Node.jsを入れてる通り、APIモードではなく、WebUI付きでWebPackしたいから

Dockerfile例

概要

  • マルチステージビルドで構築する
  • 余計なものはなるべく入れない
  • Alpineは小さくなるがCライブラリーがmuslの件もあるので避けた(Rubyでの状況は知らないが他の言語で面倒なことがあったので)

ビルド

ビルド用イメージ準備

まずビルド用のイメージとして、nodeイメージからnode/yarnを持ってきて、rubyイメージのbuild-essential入りと合体させる。これで、gcc/g++などでnative extensionsをビルドでき、yarnでnpmパッケージを取得しトランスパイル等が可能になる。

FROM node:16.16.0-slim as node
FROM ruby:3.0.4 as builder

COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /opt/yarn-* /opt/yarn
RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn
ビルド実行
  • gemのインストール
    • bundle installにて/usr/local/bundle/へgemパッケージ群をインストール
    • C言語等で書かれたnative extentionsはビルドされる
  • JavaScriptとCSSなどの作成
    • webpacker:compileassets:precompileにてpublic/packs,public/assetsへトランスパイルと最小化されたJavaScriptやCSSなどがインストールされる
WORKDIR /src

# Copy source code
COPY . /src

# Ruby gem packages
RUN bundle install

# Node.js packages & webpack
RUN yarn install && \
    bin/rails webpacker:compile && \
    bin/rails assets:precompile

実行に必要なものをまとめる

Rubyコードと設定、上記でビルドされたJavaScript/CSSを一旦/distにまとめる。builderイメージからそれぞれをコピーしても良いが、後述の実際のイメージにコピーする際にまとめておいた方が楽なのでこうした。

# Create runtime distribution
RUN mkdir -p /dist && \
    cp -pr Gemfile Gemfile.lock Rakefile config.ru app bin config db lib public \
    /dist/

実行イメージ

OS・パッケージ基本環境

ビルド時のイメージではgcc/g++などの開発ツール入りのイメージを利用したが、実行時にコンパイラーなどの開発ツールは不要だし、JavaScript/CSSも変換済みなのでnode/yarnも不要、ruby:x.y.z-slimのRubyだけのslimイメージを利用する。

また、SQLite3, MySQL, PostgreSQLが主に使われる3大データベースだと思うが、それぞれのgemにはネイティブのライブラリーが必要になる。また、mysqlやpsqlなどのCLIでSQLのアクセスを行う必要があるならmysql-client, postgresql-clientなを入れる必要がある。(APKやRPM系は調べてません)。まあ、ここはケチってlib*-devでなく*-clientを入れれば良いですかね?

Database gem Library (apt) SQL client (apt)
SQLite3 sqlite3 libsqlite3-dev sqlite3
MySQL mysql2 default-libmysqlclient-dev default-mysql-client
PostgreSQL pg libpq-dev postgresql-client

とりあえず単純なWeb+DBのプロジェクトで試したが、その他のネイティブライブラリーを参照するgemを使ったプロジェクトではlibfoo-devなどのインストールも必要。

FROM ruby:3.0.4-slim

RUN apt-get update && apt-get install -y \
    libsqlite3-dev \
    default-libmysqlclient-dev \
    libpq-dev # sqlite3 postgresql-client default-mysql-client
ビルド済みのものをコピー

上に書いた通り、builderイメージの/usr/local/bundleにgem群、/distにRubyコードや設定ファイル、変換済みJavaScript/CSSがあるので、こちらのイメージにコピーし、tmplogなどの空ディレクトリーを作成しておく。

WORKDIR /app
COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY --from=builder /dist /app
RUN mkdir log storage tmp
エントリーポイントとサーバー実行

ここは、Docker公式ドキュメントある通りにしてみる。(tmp/pids/server.pidを消すentriypoint.sh)

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["/app/bin/rails", "server", "-b", "0.0.0.0"]

Dockerfileまとめ

こんな感じか? nodeとrubyのバージョンは適宜変更し、ライブラリーのパッケージも適宜追加変更。

#
# Builder image
#   = Ruby + build-essential(gcc/g++) for native extensions
#   + Node.js for webpack
#
FROM node:16.16.0-slim as node
FROM ruby:3.0.4 as builder

COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /opt/yarn-* /opt/yarn
RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn

WORKDIR /src

# Copy source code
COPY . /src

# Ruby gem packages
RUN bundle install

# Node.js packages & webpack
RUN yarn install && \
    bin/rails webpacker:compile && \
    bin/rails assets:precompile

# Create runtime distribution
RUN mkdir -p /dist && \
    cp -pr Gemfile Gemfile.lock Rakefile config.ru app bin config db lib public \
    /dist/

#
# Runtime image
#
FROM ruby:3.0.4-slim

RUN apt-get update && apt-get install -y \
    libsqlite3-dev \
    default-libmysqlclient-dev \
    libpq-dev # sqlite3 postgresql-client default-mysql-client

WORKDIR /app
COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY --from=builder /dist /app
RUN mkdir log storage tmp

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["/app/bin/rails", "server", "-b", "0.0.0.0"]

その他

運用向け環境変数

  • RAILS_ENV: development, productionなどの環境を渡す。
  • RAILS_SERVE_STATIC_FILES: 開発環境ではnode/yarnでJavaScript/CSSをその場でビルドする様だが、上記の様にproduction用実行環境には入れないと思うので、この値をtrueにすることにより、事前生成済みのファイルを配信する。
  • RAILS_LOG_TO_STDOUT: productionだとデフォルトでログを吐かないし、コンテナ内のlogに吐かれても困るので標準出力に出したい。この値をtrueにする。
  • DATABASE_URL: コンテナ内のconfig/database.ymlusername passwordなどを直接クレデンシャル情報を記述するのも良くないのでこちらで渡す(database.ymlよりも環境変数が優先)
    • MySQL: mysql2://username:password@mysql:3306/database?encoding=utf8mb4 (mysqlでなくgemのmysql2であることに注意)
    • PostgreSQL: postgres://username:password@postgres:5432/database?encoding=unicode

bundle -> Gemfile.lock

M1/M2 Macなどarm64アーキテクチャで開発するが、実行環境はx86_64/amd64ということが多いと思う。すると次のようなlockファイルになってしまうので、

...
    nokogiri (1.13.8-arm64-darwin)
...
PLATFORMS
  arm64-darwin-21
$ bundle config set force_ruby_platform true

を実行し(~/.bundle/configに反映)。プラットフォームをrubyに強制するとCPUアーキテクチャの情報が入らないのでDocker環境には良いと思われる。

...
    nokogiri (1.13.8)
...
PLATFORMS
  ruby

その他

あと、なんかヒントになることあったら教えてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0