Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

既存のRailsアプリケーションをDockerでコンテナ化する完全ガイド

ソフトウェアのコンテナ化とは、開発やデプロイを容易にするために、アプリケーションを標準化された単位にパッケージングすることです。コンテナには、アプリケーションのコードとその依存関係がすべてまとめられています。コンテナは完全に独立して動作でき、ソフトウェア本体、ランタイム環境、システムライブラリをひとつのパッケージとして内包します。コンテナを活用すれば、開発チームと運用チームは、環境に左右されずにソフトウェアを同じように動作させられるようになります。コードとインフラを分離することで、「コンテナ化」されたアプリはローカル環境でもテスト環境でも本番環境でも、まったく同じ挙動で実行されます。

Dockerは、ソフトウェアの開発・デプロイ向けプラットフォームとして最も人気のあるもののひとつです。Dockerはソフトウェアを「イメージ」としてパッケージ化し、そのイメージを実行するときにコンテナへと変換されます。この分離性により、開発者は1台のホスト上で複数のコンテナを同時に実行できます。

既存のRailsアプリケーションをコンテナ化する際、Rails開発者は特有の課題に直面します。本記事では、実際に動作するRailsアプリをコンテナ化する手順を順を追って解説し、その過程で重要な概念や陥りやすい落とし穴についても説明します。なお、本記事はコンテナやDockerの入門解説ではなく、本番稼働中のアプリケーションをコンテナ化する際に開発者が直面する問題への対処法に焦点を当てた内容となっています。

前提条件

この記事の手順に沿って進める場合は、まだDocker化(コンテナ化のDockerにおける呼び方)されていないRailsアプリケーションが必要です。ここでは例として、私が最近リリースした本格的なサイドプロジェクト「RailsWork」を使用します。Railsで書かれた求人掲示板サービスで、Herokuにデプロイされていますが、まだコンテナ化されていません。

さらに、Dockerがインストールされている必要があります。一般的なインストール方法としては、公式サイトからダウンロードできるDocker Desktopを利用するのがおすすめです。

既存のRailsアプリケーションをDockerでコンテナ化する完全ガイド

アプリをダウンロードしたら、インストーラーを実行します。完了後、アプリケーションフォルダへドラッグするよう促されるので、指示に従ってください。その後、必ずアプリケーションフォルダからアプリを起動し、要求される特権アクセスを許可してください。最後に、Dockerが正しくインストールされているか確認するため、ターミナルで以下のコマンドを実行して、マシン上で稼働中のコンテナ一覧を表示してみましょう。

docker ps

Dockerが正しくインストールされていれば(かつコンテナが何も起動していなければ)、ヘッダーのみの空の一覧が表示されます。

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Dockerfileの作成

本題に入る前に、まず用語を明確にしておきましょう。

Railsアプリケーションが「Docker化」されると、コンテナの中で実行されることになります。コンテナは独立して動作し、置き換え可能で、頻繁に再構築されるものです。

コンテナはイメージから構築されます。イメージとは、ファイルシステムの仮想的なスナップショットにメタデータを組み合わせたものです。

Dockerfileは、イメージをどのように作成するかを記述したソースコードです。Dockerfileは通常、Docker化されたアプリのリポジトリに含まれ、他のコードと同様にバージョン管理されます。

Dockerfileの作成は、思っているよりも簡単です!Dockerはコンテナ化の面倒な作業を抽象化してくれる専用の構文を提供しています。まず、コンテナ化したいアプリのルートディレクトリに移動しましょう。作業を始める前に、gitを使っているなら新しいブランチを作成しておくと安心です。以下のコマンドで dockerize-this-app という名前のブランチを作成できます。

git checkout -b dockerize-this-app

次に、Dockerfileを作成し、Rubyアプリケーションをベースとしたイメージをビルドするよう指定します。コマンドラインから以下を実行してください。

echo "FROM ruby:3.0.0" > Dockerfile

ここでは、Dockerfileを作成し、Rubyのコンテナイメージの場所を指定する1行を追加しています。私のプロジェクトはRuby 3.0.0を使用しているため、対応するイメージを指定しました。別のバージョンのRubyをお使いでも問題ありません。Dockerはサポート対象のイメージ一覧を公開しています。

続いて、Dockerイメージの作成を手動で指示します。

docker build -t rails_work .

rails_work の部分は、任意のイメージ名に置き換えてください。また、末尾のピリオド(カレントディレクトリの指定)を忘れずにつけてください!

イメージが作成されたことを確認したい場合は、以下のコマンドでシステム上のイメージ一覧を表示できます。

docker image list

ただし、この時点のイメージはほぼ空の状態で、アプリケーションのコードは含まれていません。Dockerfileの末尾に以下を追加して、アプリのコードを取り込むよう指示しましょう。

ADD . /rails_work
WORKDIR /rails_work
RUN bundle install

これにより、アプリケーションのファイルがコピーされ、依存関係がインストールされます(rails_work はご自身のアプリ名に置き換えてください)。

この時点で、イメージ作成コマンドを再実行してみましょう。

docker image list

ここで問題が発生する可能性があります。特に既存の本番アプリケーションに対して作業している場合は注意が必要です。Bundlerが、イメージが使用しようとしているBundlerのバージョンが、Gemfile.lock を生成したバージョンと異なるというエラーを出すことがあります。その場合の選択肢は2つあります。

  • イメージ側で使用するバージョンを変更する。
  • Gemfile.lock を完全に削除する。この場合、ロックファイルが丸ごと再生成されるため、特定のバージョンに固定したいGemがあるなら、必ずバージョンを明示的に指定しておいてください。

それでもbundle installが失敗する場合は、Dockerfileに追加のインストール処理が必要かもしれません

RUN apt-get update && apt-get install -y shared-mime-info

それでも問題が解決しない場合は、ベースとして選んだRubyイメージ自体が適切でない可能性があります。まずはそこから調査する価値があります。

ここは環境変数を設定する絶好のタイミングです。

ENV RAILS_ENV production
ENV RAILS_SERVE_STATIC_FILES true

次に、Railsがデフォルトで使用するポート3000を公開するための行を追加します。

EXPOSE 3000

最後に、コンテナ起動時にbashシェルを開くよう指示します。

CMD ["bash"]

以上をまとめると、Dockerfileは次のようになります(rails_workの部分はご自身のアプリ名に置き換えてください)。

FROM ruby:3.0.0

ADD . /rails_work
WORKDIR /rails_work
RUN bundle install

ENV RAILS_ENV production
ENV RAILS_SERVE_STATIC_FILES true

EXPOSE 3000
CMD ["bash"]

主要なDockerコマンドの解説

よく使われるDockerfileのコマンドを理解しておくと、作業がぐっと楽になります。

  • FROM → ベースとなるイメージを定義します。
  • RUN → コンテナ内部でコマンドを実行します。
  • ENV → 環境変数を定義します。
  • WORKDIR → コンテナ内の作業ディレクトリを変更します。
  • CMD → コンテナ起動時に実行するプログラムを指定します。

Docker Compose

Dockerの公式ドキュメントによると、「Compose」は複数のDockerコンテナを使うアプリケーションを作成・起動するためのツールです。アプリケーションに必要なコンテナを立ち上げるために必要な情報は、すべてYAMLファイルに記述します。誰かが docker-compose up を実行すると、コンテナが起動します。docker-composeを使えば、コンテナの構成を宣言的に記述できます。

Docker Composeファイルを作成する前に、ビルドされるイメージから除外すべきファイルをDockerに伝えておくことが重要です。.dockerignore というファイルを作成してください(先頭のピリオドに注意!)。このファイルに以下を記述します。

.git
.dockerignore
.env

Gemfileがビルドプロセスによって管理されている場合は、上記の除外リストに Gemfile.lock も追加しておきましょう。

次に、docker-compose.yml というファイルを作成します。ここにコンテナの構成を記述していきます。まずは以下の内容から始めましょう。

version: '3.8'
services:
  db:
    image: postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - postgres:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/Rails-Docker
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes:
  postgres:

このファイルは2つのサービスを定義しています。ひとつは db、もうひとつは web です。dbコンテナはPostgres用の既製イメージから構築され、POSTGRES_USERPOSTGRES_PASSWORD の値はご自身の環境に合わせて置き換えてください。本番環境のシークレット情報をこのファイルに直接書き込むのは避けてください。詳細については後述の「シークレット管理」セクションを参照してください。

webコンテナは先ほどのDockerfileから構築され、IPアドレス0.0.0.0のポート3000でRailsサーバーを起動します。内部のポート3000は、実際のポート3000にマッピングされます。

最後に、Postgresのデータを永続化するためのボリュームを定義しています。

シークレット管理

本番アプリケーションでは、ビルド時の認証が課題になることがあります。プライベートリポジトリからGemを取得している場合や、データベースの認証情報を保存する必要がある場合などが該当します。

Dockerfileに直接記述された情報は、永久にコンテナイメージに焼き込まれます。これはよくあるセキュリティ上の落とし穴です。

Railsのクレデンシャル管理機能(credentials)を使っている場合、Docker(またはその他のホスト)にアクセス権を与えるのは比較的簡単です。Docker Composeファイルで RAILS_MASTER_KEY 環境変数を指定するだけです。対象のcomposeサービス配下に environment ヘッダーを設け、そこにキーを指定します。まだ存在しない場合は新規に作成してください。先ほどのdocker-composeファイルは次のようになります。

version: '3.8'
services:
  db:
    image: postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - postgres:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/Rails-Docker
    ports:
      - "3000:3000"
    depends_on:
      - db
    environment:
      - RAILS_MASTER_KEY=this_would_be_the_key
volumes:
  postgres:

さて、ここで重要な判断を迫られます。このファイルはソース管理にコミットしたいところですが、マスターキーやデータベースのパスワードをソース管理で追跡させては絶対にいけません。これも危険なセキュリティ問題につながります。現時点でのベストな解決策は、dotenv gemを活用することです。これにより認証情報へプロキシ経由でアクセスでき、実体はソース管理の対象外にある別のファイルに安全に保管できます。

コンテナ化したアプリケーションの実行

いよいよ、以下のコマンドでDocker化したアプリケーションを実行できます。

docker compose up

信じられないかもしれませんが、これだけです!docker-composeのおかげで、コマンドライン引数を意識することなく、コンテナを簡単に立ち上げられます。

稼働中のコンテナ一覧を確認したい場合は、以下を実行するだけです。

docker ps

Railsコンテナの名前が web であれば、非常にシンプルな方法でコンテナ内のコマンドを実行できます。たとえばRailsコンソールを起動したい場合は、次のコマンドを実行するだけです。

docker exec -it web rails console

コンテナ内でbashシェルだけを使いたい場合は、代わりに以下を実行します。

docker exec -it web bash

その他の落とし穴

本番環境のDocker化されたRailsアプリケーションでよくある問題のひとつがログの扱いです。ログをコンテナシステム内に長期保存すべきではありません。Dockerでは、ログをSTDOUTへリダイレクトすることが推奨されています。これは config/application.rb で明示的に設定できます。

もうひとつのよくある問題がメーラー(Action Mailer)です。アプリケーションでメーラーを使用している場合、接続設定を必ず明示的に定義してください。SMTPは十分に信頼できる配信方式で、デフォルト設定でも概ね問題なく動作しますが、サーバーの所在地などの設定がコンテナの構成と一致するよう慎重に設定する必要があります。

また、Sidekiq などのワーカーやバックグラウンドジョブを使用している場合は、専用の独立したコンテナで実行する必要があります。

まとめ

おわかりいただけたように、本番稼働中のRailsアプリケーションをコンテナ化するには、いくつかの課題が伴います。アプリケーションが成長する過程で蓄積された多数の依存関係が、こうした移行を難しくしているのです。バックグラウンドワーカー、メーラー、シークレットなど、ほとんどの落とし穴には確立された対処パターンが存在します。本番アプリケーションをDockerで動かすための初期作業を一度完了してしまえば、以降の変更やデプロイの手軽さが、投資した労力以上の見返りをもたらしてくれるでしょう。

  1. Rails5でのAngularの使用

    あなたは前にその話を聞いたことがあります。分散型で完全に機能するバックエンドAPIと、通常のツールセットで作成されたフロントエンドで実行されているアプリケーションがすでにあります。 次に、Angularに移動します。または、AngularをRailsプロジェクトと統合する方法を探しているだけかもしれません。これは、この方法を好むためです。私たちはあなたを責めません。 このようなアプローチを使用すると、両方の世界を活用して、たとえばRailsとAngularのどちらの機能を使用してフォーマットするかを決定できます。 構築するもの 心配する必要はありません。このチュートリアルは、この目的のた

  2. RailsアプリでTwitterログインを実装する方法:OmniAuth-Twitter完全ガイド

    このチュートリアルでは、RailsアプリケーションのユーザーがTwitterアカウントを使ってログインできるようにする方法を解説します。OAuthなどのツールのおかげで、この実装はとても簡単に行えるようになりました。 今回は、OmniAuth用のTwitterストラテジーを含む「OmniAuth-Twitter」を使用します。 それでは始めましょう! はじめに:プロジェクトの準備 まずはRailsアプリケーションを生成します。ターミナルで以下のコマンドを実行してください。 rails new Tuts-Social -T 次にGemfileを開き、bootstrap gemを追加します。