はじめに
なんとな〜くdockerを使い始めてはや4年ほど。
既存のプロジェクトにアサインされた場合はdockerファイルに何が記載されているかなんて意識せずコマンドを実行するだけで、何か自分で一から作る時は、誰かが作ったものをどこからか持ってきて済ませていた。
こんな感じなのでdockerをなんとなく扱えてはいるが細かいところを全く理解できてない。
今回は人に説明できるくらい理解できるようになろうとした男の記事です。
ハンズオン形式でやっていきますので一緒に手を動かしながらやってみていただけると嬉しいです。
対象とする読者
- これからdockerをは0から理解したい人
- なんとなくdocker触っちゃってて理解していない俺みたいな人
(でもLinux多少知っていないと少し大変かもです) - Docker, docker image, docker container, docker-composeといわれてもパッとイメージがつかない俺みたいなレベル感の人
※rubyとrailsをベースに解説を行いますがエッセンスとなる部分はrubyユーザーでなくても参考になる部分は多いと思います。
Dockerとは?
※もっと説明上手い人たくさんいるのでさらっといきます
実際にdocker imageをbuild→docker containerを起動させてみよう
docker containerを起動するにはdocker image
が必要です。
docker image
とはdocker container
の雛形です。
docker image
を作成するためにはDockerfile
を用意します。
Dockerfile
はいわばdocker image
の「設計図」のようなものです。
①docker imageのビルド
では任意のディレクトリに作業ディレクトリを作りましょう。僕はdocker_sample
というディレクトリで作業しようと思います。
mkdir docker_sample
cd docker_sample
続いて必要なファイルを用意します。
$ touch Dockerfile
$ mkdir app
$ touch app/main.rb
ディレクトリが以下のようになっていればOKです。
$ find .
.
./app
./app/main.rb
./Dockerfile
実際のファイルには以下のように記述します。
FROM ruby:2.7
WORKDIR /var/www
# /var/wwwの部分は/myappなどなんでも良い
COPY ./app /var/www
以下のコマンドを実行します。
$ docker image build -t sample_app:latest .
すると以下のように表示されます。
[+] Building 3.3s (8/8) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 143B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/ruby:2.7 3.1s
=> [internal] load build context 0.0s
=> => transferring context: 57B 0.0s
=> [1/3] FROM docker.io/library/ruby:2.7@sha256:2347de892e419c7160fc21dec721d595273 0.0s
=> CACHED [2/3] WORKDIR /var/www 0.0s
=> [3/3] COPY ./app /var/www 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:4aff41a0a11e60c78693c2d4bb5b0e00cfdbe98747c725a03666e4df 0.0s
=> => naming to docker.io/library/sample_app:latest
解説
ビルドしたdocker imageを確認するにはこのコマンドを実行します
$ docker image ls
実行すると以下のようなイメージがビルドされていることがわかります。
先ほど設定したsample_app
、latest
という値が設定されていることがわかります。
②docker containerの起動
Dockerにはライフサイクルがあります
docker imageを作成してからdocker containerを作成、起動する必要があるということです。
今回はdocker containerが起動した際にapp/main.rb
の"Hello World!"
が実行されるようにしてみます。
p "Hello World!"
FROM ruby:2.7
WORKDIR /var/www
# /var/wwwの部分は/myappなどなんでも良い
COPY ./app /var/www
CMD ["ruby", "/var/www/main.rb"]
以下の一行を追加しdocker containerが起動した時に実行したいコマンドを記述しておきます。
CMD ["ruby", "/var/www/main.rb"]
あくまでdocker container上でコマンドを実行するためディレクトリはDockerfileで記述した/var/www
内のmain.rb
になっているということですね。
Dockerfile
を編集したのでdocker imageを再度ビルドします。
$ docker image build -t sample_app:latest .
ビルドが完了したらいよいよdocker containerを起動します。
$ docker container run --name sample_app sample_app:latest
run
というコマンドはコンテナの作成+起動を意味するコマンドです。(のちのdocker-composeの話にも共通します)
$ docker container run --name sample_app_container sample_app:latest
"Hello World!"
main.rb
に記述してある"Hello World!"
がコールされます。
docker containerの起動を確認するには以下のコマンドを実行します。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
あれ、起動したはずなのに何も表示されていませんね。
これはp "Hello World!"
という処理が完了したためdocker containerが自動的にストップしている状態です。
停止したコンテナも確認するには-a
オプションをつける必要があります。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
084abfd63748 sample_app:latest "ruby /var/www/main.…" 5 minutes ago Exited (0) 4 minutes ago sample_app_
Exited
となっているので停止していることがわかりますね。
docker containerの説明をしたいのに我ながら例が悪かったと思いますのでファイルを修正して再度試してみましょう。
p "Hello World!"
sleep(100)
このようにすれば100秒間はコンテナが起動し続けますのでこれで確認してみましょう。
一度docker containerを削除します。
$ docker container rm sample_app_container
sample_app_container
docker imageも再度ビルド
$ docker image build -t sample_app:latest .
再度コンテナを起動します。
$ docker container run --name sample_app_container sample_app:latest
ここでdocker container ls
を実行しようとすると...
あ、ターミナルが入力を受け付けてくれませんね。
docker containerのログが出力されるようになっているためです。
別タブを開いてもいいですが、バックグラウンドでコンテナを起動するモードがあるのでそれを試してみましょう。
docker containerコマンドを実行する際に-d
オプションをつける必要があります。
$ docker container run -d --name sample_app_container sample_app:latest
0cc709282c933d71b424b0856eef0eb12d974ca4f3fb31604aeb7eec874a28d8
バックグラウンドでコンテナが起動しているか確認してみます。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0cc709282c93 sample_app:latest "ruby /var/www/main.…" 3 minutes ago Up 8 seconds sample_app_cont
起動が確認できました。
また起動中のコンテナに対してコマンドを実行するには以下のようにします
$ docker container exec sample_app_container ruby -v
ruby 2.7.8p225 (2023-03-30 revision 1f4d455848) [aarch64-linux]
docker container exec
+ コンテナ名
+ 〇〇
とすることでコンテナの中のファイルにアクセスできます。
コンテナを停止→削除
コンテナを停止→削除するには以下の手順です。
停止
$ docker container stop sample_app_container
sample_app_container
削除
$ docker container rm sample_app_container
sample_app_container
gemを使ってみよう
もう少し実践的にしてみます。docker環境上でgemを使うとしましょう。
もうお気づきの方もいるかもしれませんが先ほどの挙げたdocker container exec
+ コンテナ名
+ 〇〇
というコマンドを応用して
docker container exec bundle install
などとすればgemが利用できそうですよね。
ただ、それだとdocker containerを起動するたびにそのコマンドを実行しなければならず、そんなことやってられないですよね。
そこでDockerfileに追記を行います。
FROM ruby:2.7
WORKDIR /var/www
# /var/wwwの部分は/myappなどなんでも良い
COPY ./app /var/www
#gemをインストールする先のパスを指定する(あまり深く考えなくていいです)
RUN bundle config --local set path 'vendor/bundle'
RUN bundle install
CMD ["ruby", "/var/www/main.rb"]
RUN
というコマンドを利用することでDockerコンテナイメージをビルドする際に実行するコマンドを指定できます。
Gemfile
も作り忘れちゃいけませんね。今回はかの有名なnokogiri
を例にとってgemが利用できることを確認していきます。
source "https://rubygems.org"
gem 'nokogiri'
main.rb
を以下のように書き換えます。
require 'nokogiri'
require 'open-uri'
# ウェブページを取得(今回はwikipediaを例に取ります)
doc = Nokogiri::HTML(open('https://ja.wikipedia.org/wiki/'))
# タイトルを表示
puts doc.title
ウェブスクレイピングは、サイトの利用規約に適合し、倫理的に行わなければならないことに注意して行う必要があります。違法行為や悪意のある活動は絶対に行わないようにしましょう。
再度イメージをビルドし、コンテナを起動してみましょう。
docker image build -t sample_app:latest .
を実行してみるとさっきDockerfileに追記したコマンドが実行されていることがわかります。
コンテナを起動します
$ docker container run --name sample_app_container sample_app:latest
/var/www/main.rb:5: warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open
Wikipedia
gemを利用してWikipedia
というtitleが取得できていることがわかります。
/var/www/main.rb:5: warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open
という警告が出ていますがあくまでサンプルアプリなのでそこはご愛嬌でお願いします。(gemが使えることをおみせしたかっただけなのです)
docker composeとは?
ここまで長々と書いてきて「実務環境で使うdocker-compose
いつ出てくるんだ????」と思われていると思います。。。
いよいよ本題のdocker-composeです
現在ではdocker-composeコマンドよりdocker composeコマンドが推奨されています。
従来の docker-compose コマンドと新しい docker compose コマンドのどちらを使用するかは、状況に依存します。ただし、Dockerは将来的には新しい docker compose コマンドを推奨する方向に進んでおり、新しいプロジェクトでは docker compose コマンドの使用を検討することが一般的です。
実務で使うアプリケーションだと
- webサーバー
- データベース
など複数のアプリケーションやミドルウェアが一つになって一つのシステムが出来上がっていると思います。
(ほかにも例を挙げるならバッチ処理やフロントエンドのアプリケーションなど...)
それらのアプリケーションごとにDockerfile用意してコンテナ起動して...なんてやっていられないですよね。相互の連携まで考えたら頭が痛くなります。
ここでdocker-compose
が必要になってくるわけです。
docker composeはdockerコンテナの集合体
実際にdocker-composeを利用してみよう
ここからはrails
を題材にwebアプリケーションをdocker環境で構築できるようになってみたいと思います。
別のディレクトリを用意していただいた方がわかりやすいと思うので僕は今回はdocker_rails_sample
というディレクトリを用意しました。以下のファイルを用意してください。
FROM ruby:2.7
WORKDIR /var/www
COPY ./app /var/www
RUN bundle config --local set path 'vendor/bundle'
RUN bundle install
Dockerfileはもう解説不要ですよね。
source "https://rubygems.org"
gem 'rails'
version: '3'
services:
db:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
volumes:
- ./app/db/mysql_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: password
web:
build: .
command: bundle exec rails s -p 3000 -b '0.0.0.0'
volumes:
- ./app:/var/www
ports:
- "3000:3000"
environment:
RAILS_ENV: development
depends_on:
- db
解説
rails newを実行
まだgemfileしかないのでrails new
をdocker環境に対して行います
$ docker-compose run web rails new . --force --database=mysql
ずらっとファイル群が生成されていますね。
(ここまでやってきて今更ですが自作したappディレクトリにrailsによって生成されたappディレクトリがあり、名前がややこしいですね...反省🙇)
これによりDockerfileやGemfileの内容が更新されているので例によって再ビルドを行います。
$ docker-compose build
# (中略)
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
# ↓ここに先ほどdocker-compose.ymlファイルで指定したMYSQL_ROOT_PASSWORDの値を入
password: password
# ↓ここに先ほどdocker-compose.ymlファイルで指定したdbというコンテナ名を指定
host: db
# (中略)
railsのdbを作成するコマンドを実行します
$ docker-compose run web rails db:create
dbの作成ができました。
いよいよです。
$ docker-compose up
アクセスしてみると
http://localhost:3000/
できました〜〜
終わりに
どうでしょう。ここまでくれば実務で見かけるDokcerfile
やdocker-compose.yml
ファイルをみて「あ!これってこういうことしてるんだ!」と今までわからなかったことがわかるようになっているのではないでしょうか。
僕自身前までは「なんだかいろいろやってんだな...」くらいの理解で自分の中で完全にブラックボックス化していましたが、今回腰据えて一から勉強してみて非常に有意義だったのでぜひ駆け出し・新卒エンジニアの皆さんは僕のように4年も放置せずキャッチアップしてもらいたいです。
最後まで読んでいただきありがとうございました。
参考文献