2
1

webアプリをdocker化してみよう

Posted at

はじめに

ホストで作成したrailsアプリケーションをDockerDockerfiledocker-composeを利用してDocker化するまでを解説します。

Dockerについて

仮想化ツールの一つで、昨今で最も聞く名前ではないでしょうか。
なぜここまでDockerと聞くように(デファクトスタンダードに)なったかというと、自分が使ってみての所感だとやはり、

  • 軽い
  • 早い
  • 環境を人に渡しやすい

といった辺りでしょうか。

実務はオフライン環境下でして…
どちらも使用するのですが、zip等に圧縮して渡すにしても全くもってサイズが違うので取られる時間が段違いです。

「これらの差はなぜ?」と言われると、カーネルを含む、含まないが一番と思います。

さて、本記事はあくまでDocker化の手順解説のため、さっくりとこの辺までとして、早速手順に入っていきます。

Dockerfileを書こう

dockerを使おうという人は、既に耳にしているだろう単語ですが、実際のところ何が書かれているのでしょうか?
ざっくり一言でまとめると、「コンテナの中で何をするのか」が書かれていると言えると思います。
ひとまず、作成したDockerfileを例に見てみましょう。

Dockerfile
# 初めに元となるDocker Imageを指定しています
FROM ruby:3.2.2

# コンテナ内でコマンドを実行して諸々インストールしています
RUN apt-get update && \
    apt-get install -y \
    build-essential \
    libpq-dev \
    nodejs \
    postgresql-client

# 今回はすでに手元にあるアプリをDocker化するので、コンテナ内に全てコピーします
COPY . /rails-docker

# 環境変数を設定しています
ENV APP_HOME /rails-docker

# 作業ディレクトリを指定しています
WORKDIR $APP_HOME

# 作業ディレクトリでコマンド実行されます
RUN bundle install

各行で何を行なっているか、コメントに簡単に記載しています。
毎行コマンドの前に決まって大文字の命令が付いていますね。
これらについて、いくつかピックアップします。

Dockerfileによく登場する命令

命令 概要
FROM 元になるイメージの指定(ローカルにない場合はリモートから取得される)
RUN コマンドの実行と結果の確定
CMD コンテナ実行時のデフォルトコマンドまたは引数を設定
ENTRYPOINT コンテナ実行時のデフォルトコマンドまたはスクリプトを設定
EXPOSE コンテナ実行時にコンテナ内で使用するポートを指定
ENV 環境変数の設定
ADD ホストからリモートへファイルやディレクトリの追加(多機能)
COPY ホストからリモートへファイルやディレクトリの追加(シンプル)
VOLUME コンテナ内にボリュームを作成する
WORKDIR コマンドを実行する場所(作業ディレクトリ)の指定

※その他のコマンドは取り扱いませんので、興味がある方は調べてみてください。

押さえておきたいポイント

  • 基本的にDockerfileの先頭にはFROMを記述する。

  • RUN、COPY、ADDは新しいレイヤーを作成する。

    • 悪戯にレイヤーが作成されていくと、イメージの肥大化やビルド時間の増加などの不都合が起きる。
    • 最終的にはコマンドをまとめるなどして、これらの命令は少なく記述できると良い。
  • Dockerfileの作成で試行錯誤の間はCacheを上手く使う。

    • 1回目のビルド
      RUN hoge
      

    • 2回目のビルド
      RUN hoge # 1回目のビルド済み
      RUN fuga
      

    • レイヤーが増えることになりますが、このように書くことで2回目のビルド時はRUN hogeはcacheが使用されるので短い時間で済みます。

    • 最終的には一行にまとめる
      RUN hoge && fuga
      
  • ENVでの環境変数設定は、元々存在する環境変数と同名を使うと意図せず上書きされてしまう可能性があることを頭に入れておく。

  • WORKDIRで最後に指定した場所がコンテナに入った時の初期位置になる。

この辺りを念頭に入れてDockerfileを書いていけると良いと思います。

作成したDockerfileからImageを作ろう

ここまでで、初めにあげたDockerfileの例や、各命令の意味合いを学んだことで、簡単なものは作ってみれるようになるかと思います。
作成したDockerfileはどのように使うのでしょうか?

先ほどの命令の概要に、さらっとビルドという単語が出てきました。
使用するコマンドの解説に入ります。

docker buildコマンド

docker build
docker build {-t [image_name] { : [tag_name] }} {-f [Dockerfile path]} [Dockerfile Directory]
{}は省略可、[]は必須として見てください。
{}の中に[]があるのは、省略部を書くならこれは必須ですよということですね。

-tから始まるオプション部分を省略すると、イメージ名、タグ名は付けられずNoneとなります。

さて、よく見るとDockerfileのあるディレクトリを書くだけで、どれをビルドしろと言った記述はないですね。
このコマンドは、指定したディレクトリ内でDockerfileというファイル名を探してビルドします。

ちょっと管理しにくいですよね。
-f [Dockerfile Path]のオプションを付けることで、DockerfileのあるディレクトリのどのDockerfileを使うか指定できます。
これで、Dockerfileという名前の縛りからも解放されるわけですね。

まだ知っておかないと困ることがあります。

Build Contextを知ろう

COPYやADDなどを行う際のファイルやディレクトリはBuild Contextに含まれている必要があります。(不足しているとエラーになります)
Build Contextとは、先ほど解説したdocker buildコマンドを実行した際のカレントディレクトリになります。

基本的には、Dockerfileに記述するパスも管理しやすいので、Build Context内にDockerfileもあるといった形が多いかと思います。
下の画像のような形になります。
スクリーンショット 2023-11-02 21.53.44.png

その他、

  • -fオプションを利用することでコンテキスト外のDockerfileを参照することが可能
    • この時、指定したDockerfileに記述する相対パスは、そのDockerfileのディレクトリが基準
  • 絶対パスで指定することでBuild Context外のファイルへアクセスすることも可能

であることを知っておくと良いと思います。

作成したイメージからコンテナを作成してみよう

ここまででイメージが出来上がりました。
次に、作成したイメージからコンテナを作成しましょう。

docker runコマンドを使用します。
よく使うと感じるオプションを紹介します。

オプション 概要
-d コンテナをバックグラウンドで起動します
-it1 ユーザーに分かりやすい対話モードで起動します
-p ホスト、コンテナ間でポートマッピングします
-v ホスト、コンテナ間でディレクトリ共有を設定します
--name 起動するコンテナに任意の名前を設定します
--rm コンテナから出る時、自動でコンテナを削除

※他にも多くのオプションがあります。実際に触る際に実現したいことに合わせて調べて見てください。

例えば、下記のように使用するイメージです。

docker run
docker run {-it} {--rm} {--name [コンテナ名]} {-v [host dir:container dir]} ¥ 
           {-p [host port:container port]} [image]

docker runコマンドでコンテナを作成したら、確認のためdocker psコマンドで現在のコンテナ一覧を見てみましょう。
使用するコマンドのオプションや、イメージによっては起動、実行されて即コンテナ終了している場合もあるので、-aオプションをつけてコンテナの状態に関わらず一覧に出しましょう。

Dockerfileの作成、それを利用してイメージを作り、イメージからコンテナを作成しました。
しかし、このdocker runコマンドで感じたかもしれません。

「毎回オプションつけてコマンド打ってたら長いし間違えそうだし面倒じゃね?」

Dockerはそもそも、人に環境を展開していくのに便利なツールと言った側面があると思いますが、手順が複雑となってしまったら薄れてしまいますね。

便利に使うためにdocker-compose.ymlを書こう

作成したイメージの使い方が決まって、コンテナ立ち上げ時のオプションによる様々な設定が決まったとします。
どこかに設定を書いておいて、短いコマンドで同じ状態を誰にでも簡単に作れるようにしたいですね。

その為には、docker-compose.ymlファイルを作成して、そこに書いていきます。
早速、今回私が作成したものを見ていきながら、解説していきます。

docker-compose.yml
# 当ファイルの形式はdocker-composeのversion3に準拠するという意味です
version: '3'

# コンテナのPostgresDBデータの永続性を確保する為
volumes:
  postgres-data:

# サービスを定義
services:
  # webという名前でコンテナサービスを定義
  web:
    # docker build . と同じ
    build: .
    # docker runに -v .:rails-docker を付けたのと同義
    volumes:
      - .:/rails-docker
    # コンテナ起動時にコンテナで実行するコマンドを指定
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    # docker runに --env POSTGRES_PASSWORD=postgres を付けたのと同義
    environment:
      - 'POSTGRES_PASSWORD=postgres'
    # docker runに -p 3000:3000 を付けたのと同義
    ports:
      - "3000:3000"
    # dbサービスが先に起動されるように記述しています
    depends_on:
      - db

  # dbという名前でコンテナサービスを定義
  db:
    # postgres version12のイメージを指定
    image: postgres:12
    # docker runに -v postgres-data:/var/lib/postgresql/data を付けたのと同義
    volumes:
      - 'postgres-data:/var/lib/postgresql/data'
    # docker runに --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=postgres を付けたのと同義
    environment:
      - 'POSTGRES_USER=postgres'
      - 'POSTGRES_PASSWORD=postgres'

コメントでどう言った意味合いがあるか記述していますが、触れるべき部分にはもう少し詳しく見ていきましょう。

volumes
postgres-dataという名前のボリュームを作成しています。
その後、services > db > volumes でホストに作成したpostgres-dataとコンテナの/var/lib/postgres/dataを紐づけています。
/var/lib/postgres/dataは、postgresデータのデフォルト保存先です。
こうしておかないと、コンテナの削除等によりデータを損失したり、バックアップの取得が難しくなったりとよろしくない状況になります。

services > web > volumes
ホストのカレントディレクトリとコンテナのrails-dockerディレクトリを紐づけています。
これは、ホスト側でコードを編集するとコンテナ内のコードも編集することが可能となります。
修正や変更などでコンテナに入る必要がなくなるわけですね。

services > web > depends_on
dbを指定しています。
これはコメントにも書きましたが、先にpostgresを立ち上げるためです。
それが何故なのか、というのはwebコンテナからdbコンテナに接続する際、先に立ち上がっていてくれないとエラーになってしまうためです。

肝要な部分はこの辺りかなと思います。

念のため触れておきますが、webについてはdockerfileからイメージを作成していますが、dbについてはimage: postgres:12と書いてあるだけですね。
これはホストにない場合、DockerHubからイメージをプルしてくれます。

docker-composeコマンドでまとめてコンテナ作成しよう

先の項目でdocker-compose.ymlを作成したおかげで、簡単に立ち上げることが可能になりました。

dockerコマンドではなく、docker-composeコマンドを使用します。

まず、ymlからコンテナを作成、起動するには
docker-compose upを使用します。

このコマンドがやってくれることとしては、イメージの作成とコンテナの起動です。
どちらもymlに記載した設定で行われます。
今回の場合、webコンテナ(rails)とdbコンテナ(postgres)の2つをまとめて処理してくれます。

docker build .
docker run --name ... --env ... -v ... postgres:12
docker run --name ... --env ... -v ... -p ... web command...
といった手順を踏む必要があったのが、1コマンドでかつ、非常に短くなりましたね!

dockerfiledocker-compose.ymlを編集した場合には、--buildオプションを付けると最新の状態で状態で再ビルドしてくれます。
-dオプションでバックグラウンドにもできます。

docker-compose downでまとめてコンテナの停止
docker-compose psでコンテナの一覧を表示
といったコマンドも併せて覚えておくと良いですね。

また、同時に複数コンテナを立ち上げられるなら、DockerHubからのプルでなく、複数のdockerfileを用意しておいてイメージ、コンテナ作成を行いたいとも考えると思います。
ここで、docker buildコマンドの-fオプションが活躍してくるわけですね。

2つのコンテナはどう繋がっている?

dockerfileにもdocker-compose.ymlにも特にネットワークに関する記述がなかったのに、繋がってるの?繋がってるとしたらなんで?
と疑問に思ったでしょうか。

docker-compose upを行った際に、dockerが自動でネットワークを作成し、webとdbを繋げてくれています。

docker network lsコマンドでネットワーク一覧が見れます。
スクリーンショット 2023-11-04 22.09.25.png

一番下に、作成されたネットワークが追加されているのが見えますね。

docker inspectでネットワークIDか名前を指定すると詳しい内容が見れますので、

  • ネットワーク
  • webコンテナ
  • dbコンテナ

それぞれのinspectの内容を見ると繋がっているのが見て取れるかと思います。

docker-compose exec web bashコマンドでwebコンテナに入り、
psql -U postgres -h <dbコンテナのIP> -p <dbコンテナのポート>コマンドで
dbコンテナのpostgresにアクセスできることなんかも見てみると良いかもしれません。

補足

私がDocker化した環境では、手順としては完了ではありません。

しかし、ここはrailsのお話になるのであまり言及しませんが、docker-compose upの後、コマンドとしては
docker-compose run web rails db:create
docker-compose run web rails db:migrate
を使用してデータベースの作成、アプリーションのデータモデル更新を行う必要があります。

さいごに

dockerfiledocker-compose.ymlを用意しておくことで、これらをソースコードと一緒にgitなりでリポジトリに置き、READMEなどに簡単なコマンドで少ない手順を記載しておけば展開が容易になります。

docker、docker-composeへの理解に少しでも役立てば幸いです。

ここまで読んでいただき、ありがとうございました!

  1. ほぼセットで使うので、この形で紹介しましたが、-i-tの2つのオプションです。

2
1
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
2
1