今日の目標
ローカル環境に構築していたRailsとMySQLを、1コマンド叩くだけで実行できるようにするための方法を学ぶ
そもそもDockerって何?
具体的な導入手順を解説するまえに、まずはDockerについておさらいしておきましょう。
Dockerは、インフラ関係やDevOps界隈で注目されている技術の一つで、Docker社が開発している、コンテナ型の仮想環境を作成、配布、実行するためのプラットフォームです。
こ〜んな説明がされていることが多いかと思いますが
Docker何それ?美味しいの???
となってしまうのでもっと身近な例で例えると
パソコン上でファミコンを動かすエミュレータのようなもの
-マンガでわかるDockerより-
ファミコンでカセットを入れ替えると違うゲームが遊べるように、dockerではDocker container(以後コンテナ)を入れ替えれば、また違う開発環境を作ることができます。
(ちょっと例えが古くてすみませんwww)
そして、コンテナをどのパソコンでも動かすためにパッケージ化したものをDocker Image(以後イメージ)と呼んでいるんですね。
プログラミングチックいえば、イメージはオブジェクトで、コンテナは実行中のインスタンスに相当します。
Dockerが便利である理由~冪等性について~
なぜこんなことが実現できるかというと、Dockerには**冪等性(べきとうせい)**があるからですね。
冪等性とは、何回実行しても結果が変わらない
ということ。
この冪等性のおかげで、同じDockerfileから作られたコンテナは、何回立ち上げても同じ開発環境が立ち上がるので、差異が生じることはほとんどありません。
という悩みから開放されるので、環境構築で躓くことがなくなるはずです。
Dockerfileについて
コンテナを作成するには、
• DockerHubから公開されているイメージををダウンロードする
• Dockerfileを使って作成する
この2パターンあります。
Dockerfile というのは、コンテナを作成するためのレシピのようなものです。
コマンド一つ一つの一つが、コンテナという料理を完成させるための原材料と考えればわかりやすいかもしれません。
docker-compose build といったような、所定のdockerコマンドを実行することでDockerはDockerfile を読み込み、それを元にコンテナを作成します。
docker-composeについて
Docker composeというのは複数のコンテナを同時に動かすためのツールのこと。
そして、そのDocker composeを利用するためにYAML形式で書いたファイルがdocker-compose.ymlです。
docker-compose.ymlの書き方
参考にするべきリファレンス
どんな場合でも公式ドキュメントが一番正確な一次情報となるわけですが、
Dockerの場合は下記2つが公式なサイトとなります。
当然英語となるわけですが、実は有志の方が作成してくれている日本語のドキュメントもあります。それが、下記のサイトですね。
ただし、こちらは情報が古い場合があるので、最初の情報を得る場合は、やはりDocker公式ドキュメントを参照するようにしましょう。
Qiitaやテックブログなどを参考にする場合は、リファレンスの該当箇所も合わせて読んでおくといいかもしれません。
##dockerのリファレンスの読み方
ここまでで、基本的なDockerfileやDockerComposeの概念について説明してきましたが、そうは言ってもすべてを網羅しているわけではないので、より深く理解していくためにはリファレンスを参照することになるでしょう。
リファレンスを読む場合のポイントは以下です。
###BestPracticeを参照する
動かすだけであればDockerfileやDockerComposeは適当に書いても問題ないですが、BestPracticeを参照すると、よりイメージを軽くすることができたり、パフォーマンスを上げることができます。
Best practices for writing Dockerfiles
否定の見出しが書かれている箇所は注意深く読み込む
全部に目を通す必要はないですが、少なくとも否定の見出しで書かれいる箇所は注意深く読み込んでおく必要があるかもしれません。
例えば、上記のベストプラクティス内で、
Don’t install unnecessary packages
という箇所があります。
イメージを作る際には余計なライブラリなどを入れるなという内容なのですが、これを知らないと、重くて遅いコンテナになってしまいます。
#本題 RailsアプリをDockerizeしてみる
Dockerizeする方法は色々とあるのですが、今回はDockerfileとdocker-compose.ymlを使用します。
Dockerfileとdocker-comoose.ymlは、すごく噛み砕いていうと、Dockerにアプリケーションを立ち上げさせるための指示書です。
Dockerfileとdocker-comoose.ymlに書かれている内容を元に、Dockerは上から順番にプログラムを実行していきます。
Dockerの導入
手元のPCにdockerを導入する
dockerコマンドを使うためには、 Dockerクライアントというものを使用します。
Dockerクライアントは、一言でいえば、Dockerのコマンドや便利機能をひとまとめにしてパッケージ化したものです
$ brew install docker
$ brew cask install docker
これだけでインストールはOKです。
$ docker -v
docker command not foundが表示された場合
もし、 docker command not found のような表示がされた場合は、
• dockerクライアントが導入されていない
• dockerクライアントは導入されているけど、何かしらが原因で動いていない
この2つが考えられます。
後者の場合は一旦dockerクライアントをアンインストールして
のページからブラウザ経由でダウンロードしてみてください。
docker-composeコマンドを使うためには、caskまたはパッケージからインストールしてきたDocker Desktopを起動する必要があります。
インストール後にLaunchpadからDockerアプリを起動しておきましょう!
##MySQLをセットアップする
Dockerではコンテナ内でMySQLを動かすので、ローカルマシンに別途インストールする必要はありません。
ですが、場合によってはローカルで確認したいことがあるのと思うので、brewを使ってインストールしておきます。
$ brew install mysql@5.7
brew link mysql@5.7 --force
こんな感じで@バージョン名とすることでバージョンを指定してインストールすることができます。
brew link mysql@5.7 --forceについてはforceオプションをつけてMySQL5.7を強制的に有効化するように指定しています。
おまけ 最新版のMySQLのバージョンを使用する場合
ローカルに最新版のMySQLをインストールする場合も基本的にbrewでのインストール実行コマンドは同じ。
$ brew install mysql
$ brew link mysql --force
バージョン指定がなくなるだけですね。
MySQLにおけるローカルでの作業は以上です。
config/database.ymlを編集する
Railsアプリケーションではデータベースに接続するためのファイルとして、YAMLという形式が記述したconfig/database.ymlがあります。
Railsでアプリケーションを作成する際にもdatabase.ymlをいじったと思いますが、dockerizeするときにも、このファイルに手を加える必要があるんですね。
さっそくみていきましょう。
default: &default
adapter: mysql2
encoding: utf8
pool: 5
username: root
password:
socket: /tmp/mysql.sock
development:
<<: *default
database: chat-space_development
test:
<<: *default
database: chat-space_test
production:
<<: *default
database: chat-space_production
username: root
password: <%= ENV['DATABASE_PASSWORD'] %>
socket: /var/lib/mysql/mysql.sock
こんな感じになっているかと思います
このファイルをdockerizeするため、現在のconfig/database.ymlを次のように書き換えてください。
default: &default
adapter: mysql2
encoding: utf8
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: <%= ENV.fetch("MYSQL_USERNAME", "root") %>
password: <%= ENV.fetch("MYSQL_PASSWORD", "root") %>
host: <%= ENV.fetch("MYSQL_HOST", "database") %>
development:
<<: *default
database: docker_example_for_chat-space_development
test:
<<: *default
database: docker_example_for_chat-space_test
production:
<<: *default
database: docker_example_for_chat-space_production
違いとしては2つあって、一つはrootやpasswordなどを環境変数にセットしつつfetchしているところですね。
(まあ、これに関してはdockerizeするというよりも単純に書き方の一つではあるのですが)
で、もう一つはMYSQL_HOSTにdatabaseを指定していることです。
これで、config/database.ymlの修正は終わりです。
Dockerfileの書き方について
さて、ここからはDockerfileの書き方について整理していきます。
$ mkdir -p docker/development
$ touch docker/development/Dockerfile
復習しておくとDockerfileとは、
• コンテナを作成するためのレシピに相当
• Dockerにアプリケーションを立ち上げさせるための指示書
でした
Dockerfileについてみていきましょう。
# docker_example_for_chat-space(Rails/MySQL)
FROM ruby:2.5.1-alpine
LABEL maintainer="Keita Higaki<higaki.keichan@gmail.com>"
# 文字コードの設定
ENV LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \^
LC_CTYPE="utf-8"
# 環境変数
ENV APP="/docker_example_for_chat-space" \
CONTAINER_ROOT="./" \
NOKOGIRI_OPTION="--use-system-libraries \
--with-xml2-config=/usr/bin/xml2-config \
--with-xslt-config=/usr/bin/xslt-config" \
MYSQL_PORT=4306 \
SERVER_PORT=4000
# ライブラリのインストール(追加したい場合は以下に追記)
RUN apk update \
&& apk upgrade --no-cache \
&& apk add --update --no-cache \
alpine-sdk \
build-base \
bash \
imagemagick \
jq \
less \
libgcrypt-dev \
libxml2-dev \
libxslt-dev \
mariadb-dev \
mysql-client \
nodejs \
redis \
tzdata \
wget \
xvfb \
yaml-dev \
yarn \
zlib-dev \
&& gem install -q -N bundler \
&& gem install -q -N pkg-config \
&& gem install -q -N rails -v 5.0.7 \
&& gem install -q -N nokogiri -v 1.10.1 -- $NOKOGIRI_OPTION \
&& gem install -q -N mysql2 -v 0.4.10
# 実行するディレクトリの指定
WORKDIR $APP
COPY Gemfile Gemfile.lock $CONTAINER_ROOT
RUN bundle install --jobs=4 --retry=3
# RAILS_SERVE_STATIC_FILES=trueにすることで、rails serverを起動時にpublic/assetsを読み込む
ENV RAILS_SERVE_STATIC_FILES=true \
PORT=$SERVER_PORT \
TERM=xterm
EXPOSE $SERVER_PORT
EXPOSE $MYSQL_PORT
ファイル内で記述されているFROMやRUNといった環境変数のようなものが、処理を実行するためのコマンドになります。
種類自体は、そこまで数が多くないので、主要なコマンドを整理してみましょう。
FROM
FROMは、ビルドする際にどのImageをベースにするかという意味を持つコマンドです。
というような命令をDockerにしていると思ってもらえればいいでしょう。
単純に使う言語の設定だと思ってくれれば大丈夫です。
alpine は数あるLinuxのディストリビューションのひとつで、余計なライブラリがデフォルトで入っていない分、非常に軽量なイメージです。
そのため、イメージのサイズを軽くしたい場合は指定する場合が多いです。
リファレンスリンク
LABEL
LABELは、Dockerfileのメタデータを記述 する意味を持つコマンドです。
イメージの詳細内容を確認する docker image inspect イメージ名 を実行してみると、冒頭出力されるイメージ情報の中の 「ContainerConfig」という欄に Labels が表示。
このようにイメージにメモ書きを残すことができる機能が LABEL になります。
例えば次のように記述すると、このDockerfileが誰がメンテナンスするかを指定する意味合いになります。
LABEL maintainer="Keita Higaki<higaki.keichan@gmail.com>"
ぶっちゃけ、なくてもいいのですが、書いていた方が責任の所在がわかります。
ENV
ENVは、環境変数のkeyとvalueを設定するコマンドです。書き方は2種類あって、
ENV LANG="C.UTF-8"....
このようにKeyとValueをイコールで結び付ける方法と、
ENV LANG C.UTF-8
上記のようにKeyとValueの間にスペースを挿れて書く方法があります。
前者の書き方の方が、一つのENVに対して以下のようにバックスラッシュを使い、一度に複数の環境変数を改行して定義することができるので便利です。
ENV APP="/docker_example_for_chat-space" \
CONTAINER_ROOT="./" \
NOKOGIRI_OPTION="--use-system-libraries \
--with-xml2-config=/usr/bin/xml2-config \
--with-xslt-config=/usr/bin/xslt-config" \
MYSQL_PORT="4306" \
SERVER_PORT="4000"
また、実はこの記述をする理由として2つの意味があって、
• 一つずつ定義するよりもまとめて定義する方が記述が冗長にならない
• ENVを実行するたびにイメージのサイズが増幅するので、少しでも軽くしたいから
です。このような理由から、今回のDockerfileではバックスラッシュによる記述をしています。
なお、定義した環境変数を使うには
$CONTAINER_ROOT
上記のように、変数名の前に$を付けてあげればOKです。
###RUN
RUNは、 引数に設定したコマンドをビルド時に実行します。引数の書き方がshell形式とexec形式があり、今回紹介しているのはshell形式ですね。
# ライブラリのインストール(追加したい場合は以下に追記)
RUN apk update \
&& apk upgrade --no-cache \
&& apk add --update --no-cache \
alpine-sdk \
build-base \
bash \
imagemagick \
jq \
less \
libgcrypt-dev \
libxml2-dev \
libxslt-dev \
mariadb-dev \
mysql-client \
nodejs \
redis \
tzdata \
wget \
xvfb \
yaml-dev \
zlib-dev \
&& gem install -q -N bundler \
&& gem install -q -N pkg-config \
&& gem install -q -N rails -v 5.2.2.1 \
&& gem install -q -N nokogiri -v 1.10.1 -- $NOKOGIRI_OPTION \
&& gem install -q -N mysql2 -v 0.4.10
RUNもENVと同じようにバックスラッシュを使ってまとめて定義することで、イメージ化された際の最終的な容量を軽くすることができます。
Railsを動かすためには、さまざまなライブラリが必要になるので、そのためにRUNコマンドを使ってインストールしている箇所になります。
また記述する際にはABCのアルファベット順にすることがリファレンスで推奨されているので、保守性・可読性を高めるために意識しましょう。
また、後半でgem installをしている箇所は、それぞれが比較的インストールに時間がかかる重いgemなので、予めインストールすることで、後のbundle installをキャッシュにより高速化させる効果があります。
###COPY
COPYはプロジェクトソースにあるファイルやディレクトリを指定先にコピーします。
COPY Gemfile Gemfile.lock $CONTAINER_ROOT
ENVで先程CONTAINER_ROOTという環境変数を定義したので、そこにGemfileとGemfile.lockをコピーするように指定しています。
コンテナのルートディレクトリにローカルからコピーしてきたGemfileとGemfile.lockが配置されるようになるので、これでRailsアプリを動かすための各種コマンドが使えるようになっているはずです。
WORKDIR
WORKDIRは、文字通りRUNやCMDなどの命令を実行するためのディレクトリを指定するコマンドです。
WORKDIR $APP
このように環境変数と組み合わせれば、スッキリ書けます
###EXPOSE
EXPORSEは、コンテナ実行時に指定したポートで接続用意ができていることをDockerに伝えるコマンドです。
EXPOSE $SERVER_PORT
EXPOSE $MYSQL_PORT
つまり、Dockerに対して、
というようなイメージになります。これも環境変数に置き換えておくと管理がラクです。
###まとめて定義すると、イメージ容量削減になる理由
先ほど
RUNもENVと同じようにバックスラッシュを使ってまとめて定義することで、イメージ化された際の最終的な容量を軽くすることができます。
こんな感じの記述をしましたが
実はDockerfileに記述したそれぞれのコマンドは、記述されるたびにコンテナ内に処理を実行するための場所を確保していきます。
上記の画像のように、それぞれのコマンドが実行されるたびに実行するためのスペースがテトリスのブロックのように積み上げられていくようなイメージを持つとわかりやすいかもしれません。
特に、RUNやENVは他のコマンドよりもたくさんのスペースを必要とするので、書けば書くほどコンテナも重くなります。
コンテナが重くなると、その分処理速度やアプリケーション立ち上げ時にかなり時間がかかってしまうんですね。
だからこそ、バックスラッシュ(/)やアンパサンド(&)などを使い、一つのコマンドに対して複数の処理を記述していくことが公式でも推奨されています。
docker-compose.ymlの書き方について
続いてdocker-compose.yml について解説していきます。
*別記事でも解説してます
Docker入門編 docker-compose.ymlの書き方
まずは、プロジェクトのルートディレクトリでdocker-compose.ymlを作成
$ touch docker-compose.yml
復習しておくとDocker Composeは、
• それぞれのコンテナを同時に動かすためのツール
• それを利用するためにYAML形式で書いたファイルがdocker-compose.yml
でした
# 開発環境用のdocker-compose
version: '3.2'
services:
database:
restart: always
image: mysql:5.7
ports:
- 3000:3000
volumes:
- mysql-datavolume:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: root
app:
build:
context: .
dockerfile: ./docker/development/Dockerfile
command: >
bash -c "
rm -f tmp/pids/server.pid &&
bundle install --quiet &&
bundle exec rails db:migrate:reset &&
bundle exec rails s -p 3000 -b '0.0.0.0'
"
ports:
- "4000:4000"
volumes:
- .:/docker_example_for_chat-space
- "bundle:/usr/local/bundle"
depends_on:
- database
volumes:
bundle:
driver: local
mysql-datavolume:
driver: local
ポイントとしては、それぞれのコンテナについての記述をインデントを用いてひとまとめにしている点ですね。色分けすると、次のようなイメージです。
docker-compose.ymlはYAML形式で書かれているため、インデントに対して厳格です。そのため、インデントが一つずれるだけで、ビルドしたい際にエラーしてしまいます。
記述する際には、
• TAB文字の場合はスペース二つ分になっているか
• SPACEで入力する場合は、余計なスペースが入っていないか
上記の設定が使用しているエディタに反映されているか確認が必要です。
それでは主要な箇所を見ていきましょう。
###version: '3.2'
version: '3.2'
ここはComposefileのバージョンを指定しています。
このメジャーバージョンによって、書き方が微妙に異なるので注意が必要です。
###services
services:
# ズラッと各要素が並ぶ
database:
# database要素の内容を定義
app:
# app要素の内容を定義
Docker-Composeでは、アプリケーションを動かすための各要素をServiceと読んでいます。
そのため、ComposeFileにも、serviceとして、それぞれのServicesの内容をネストさせて記述していきます。(書き方自体はYAMLの書き方でOK)
完成形をみてみると、databaseとappがServiceとして定義されていますね。
各要素には任意の名前をつけることができ、わかりやすければなんでもOKですl
で、実はconfig/database.ymlでMYSQL_HOSTがdatabaseと記述していたのはこれが理由です。
「MYSQLはdatabaseとしてServiceを定義したから、接続するときはそこを参照してね」
といったことをDockerに伝えているわけです。
ちなみに、ここで定義した名前は、dockerのログに表示されるので、立ち上げたら確認してみてくださいね。
###volumes:
volumesはローカルにあるファイルを同期して読み込むための設定を行っています。
volumes:
bundle:
driver: local
mysql-datavolume:
driver: local
例えば、新しくgemをインストールしたときにいちいちdocker buildしていては、いくら軽いとはいえ時間がかかります。
しかし、このvolumes:を設定することで、指定したパスのディレクトリをコンテナ内に設置され、Dockerコンテナ内でローカルにあるファイルを参照することができるようになるんですね。
これを設置するという意味からmount(マウント)するといいます)
で、このマウント機能を使うと、gemを新しく追加した際にはビルドしなくてもコンテナ内でbundle installするだけでよくなるんですね。
その設定が、以下の箇所になります、
bundle:
driver: local
###driver
driverは、volumeに使用するドライバ(動かすための接続先)の指定するためのkeyになり、そのvalueとしてlocalを指定することで、マウント機能を実現しています。
リファレンスリンク
###restart
restartは、実行時に再起動するかどうかを指定しています。
restart: always
今回でいうと、dockerをビルド時や立ち上げる際にMySQLを再起動させるようにしているという意味合いになっています。
###environment
evrironmentは、環境変数を設定します。DBのパスワードなどを記述したりします。
environment:
MYSQL_ROOT_PASSWORD: root
なお、MYSQL_ROOT_PASSWORDという環境変数は、MySQLのimageを使用する際に予め決められているものになります。この変数をTypoするとMySQL起動時にエラーするので、注意しましょう。
リファレンス
MySQLの環境変数について
https://hub.docker.com/_/mysql
###ports
portsは、DBなどのDockerImageを立ち上げる際に使用するポート番号 です。
database:
restart: always
image: mysql:5.7
ports:
- 4306:3306
(中略)
ここでは、「databaseを使う時に4306というポート番号にアクセスするから、3306に転送してね」という意味を表しています。これがポートフォワードと呼ばれる仕組みですね。
また、今回DockerfileでRailsのアプリケーションサーバーPumaが使用するポート番号に4000を指定しています。
#docker/development/Dockerfile
(中略)
ENV (中略)
SERVER_PORT=4000
(中略)
EXPOSE $SERVER_PORT
(中略)
そのため、docker-compose.ymlでもappコンテナで使用するポート番号を次のように設定する必要があるんですね。
# docker-compose.yml
app:
build:
context: .
dockerfile: ./docker/development/Dockerfile
command: >
bash -c "
rm -f tmp/pids/server.pid &&
bundle install --quiet &&
bundle exec rails db:migrate:reset &&
bundle exec rails s -p 4000 -b '0.0.0.0'
"
ports:
- "4000:4000"
もし仮にDockerfileとdocker-compose.ymlのポート番号で整合性が合わないと、puma自体は立ちがりますがブラウザで動作確認した際、以下のようにERR_EMPTY_RESPONSEとなるので注意しましょう!
リファレンスリンク
###volumes
serviceの各要素にも、実はvolumeを定義することができます。
volumes:
- mysql-datavolume:/var/lib/mysql
# 中略
volumes:
- .:/hello_world_rails
- "bundle:/usr/local/bundle"
復習になりますが、volumesはマウントする設定ファイルのパスを指定する記述になります。
mysqlのconfigファイルなどを指定したりします。
リファレンスリンク
###build
buildはdocker-compose.ymlで対象のコンテナを起動させる際に、対応するDockerfileの置き場所を相対パスで指定します。
もしDockerfileをプロジェクトのルートディレクトリに作成している場合は、buildの書き方は次のようになります。
build: .
例えば Dockerfileを Local・Staging・Productionなどのように、アプリの実行環境ごとに分割した場合
build:
context: .
dockerfile: ./docker/development/Dockerfile
と書くことができます。
contextは現在のディレクトリ(docker-compose.ymlの置き場所)を表していて、dockerfileというkeyに対して参照先("./docker/development/Dockerfile")をvalueで渡せば、任意の位置にDockerfileを置くことができます。
###depends_on
depends_onは、Service同士の依存関係を指定します。
ここで指定されたSeriviceは、その指定元のSeriviceが起動する前に実行されるようになります。
app:
# 中略
depends_on:
- database
今回でいうと、app内でdatabaseがdepends_onとして指定されているので、appよりも先にdetabaseが起動するという意味になります。
docker-compose.ymlについては以上です
#最後に某スクールで開発し、デプロイしたアプリケーションをDockerizeしたコードを掲載しておきます。
こちらはDockerコンテナを作成しただけで、デプロイはCapistranoを直にアプリケーションに導入しEC2に接続しています。(コンテナデプロイではない)
FROM ruby:2.5.1
# 必要なパッケージのインストール(基本的に必要になってくるものだと思うので削らないこと)
RUN apt-get update -qq && \
apt-get install -y build-essential \
libpq-dev \
nodejs
# 作業ディレクトリの作成、設定
RUN mkdir /app_name
##作業ディレクトリ名をAPP_ROOTに割り当てて、以下$APP_ROOTで参照
ENV APP_ROOT /app_name
WORKDIR $APP_ROOT
# ホスト側(ローカル)のGemfileを追加する
ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock
# Gemfileのbundle install
RUN bundle install
ADD . $APP_ROOT
version: "3"
services:
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: root
ports:
- "4306:3306"
web:
build: .
command: rails s -p 3000 -b '0.0.0.0'
volumes:
- .:/app_name
ports:
- "3000:3000"
links:
- db
コンテナの外枠を作るだけなので、非常にシンプルな記述に仕上げました。
docker-compose.ymlの書き方についてはこちら
Docker入門編 docker-compose.ymlの書き方
檜垣慶太
#長々とありがとうございました!