はじめに
ホストで作成したrailsアプリケーションをDocker
、Dockerfile
、docker-compose
を利用してDocker化するまでを解説します。
Dockerについて
仮想化ツールの一つで、昨今で最も聞く名前ではないでしょうか。
なぜここまでDocker
と聞くように(デファクトスタンダードに)なったかというと、自分が使ってみての所感だとやはり、
- 軽い
- 早い
- 環境を人に渡しやすい
といった辺りでしょうか。
実務はオフライン環境下でして…
どちらも使用するのですが、zip等に圧縮して渡すにしても全くもってサイズが違うので取られる時間が段違いです。
「これらの差はなぜ?」と言われると、カーネルを含む、含まないが一番と思います。
さて、本記事はあくまでDocker化の手順解説のため、さっくりとこの辺までとして、早速手順に入っていきます。
Dockerfileを書こう
dockerを使おうという人は、既に耳にしているだろう単語ですが、実際のところ何が書かれているのでしょうか?
ざっくり一言でまとめると、「コンテナの中で何をするのか」が書かれていると言えると思います。
ひとまず、作成した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 {-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もあるといった形が多いかと思います。
下の画像のような形になります。
その他、
-
-f
オプションを利用することでコンテキスト外のDockerfileを参照することが可能- この時、指定したDockerfileに記述する相対パスは、そのDockerfileのディレクトリが基準
- 絶対パスで指定することで
Build Context
外のファイルへアクセスすることも可能
であることを知っておくと良いと思います。
作成したイメージからコンテナを作成してみよう
ここまででイメージが出来上がりました。
次に、作成したイメージからコンテナを作成しましょう。
docker run
コマンドを使用します。
よく使うと感じるオプションを紹介します。
オプション | 概要 |
---|---|
-d | コンテナをバックグラウンドで起動します |
-it1 | ユーザーに分かりやすい対話モードで起動します |
-p | ホスト、コンテナ間でポートマッピングします |
-v | ホスト、コンテナ間でディレクトリ共有を設定します |
--name | 起動するコンテナに任意の名前を設定します |
--rm | コンテナから出る時、自動でコンテナを削除 |
※他にも多くのオプションがあります。実際に触る際に実現したいことに合わせて調べて見てください。
例えば、下記のように使用するイメージです。
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の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コマンドでかつ、非常に短くなりましたね!
dockerfile
やdocker-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
コマンドでネットワーク一覧が見れます。
一番下に、作成されたネットワークが追加されているのが見えますね。
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
を使用してデータベースの作成、アプリーションのデータモデル更新を行う必要があります。
さいごに
dockerfile
、docker-compose.yml
を用意しておくことで、これらをソースコードと一緒にgitなりでリポジトリに置き、READMEなどに簡単なコマンドで少ない手順を記載しておけば展開が容易になります。
docker、docker-composeへの理解に少しでも役立てば幸いです。
ここまで読んでいただき、ありがとうございました!
-
ほぼセットで使うので、この形で紹介しましたが、
-i
、-t
の2つのオプションです。 ↩