[備忘録] [初心者] Docker Compose / Rails(公式doc.)について自分用補足#1 (Dockerfile, entrypoint.sh)

# はじめに
### 背景

一次ソース(に出来るだけ近い情報)を参照しながら学習を進める訓練をしています。
また、これまでスルーしがちだった基礎用語や概念を深掘りする学習も始めました。

今回は、下記のチュートリアルを対象にします。
**[<title>クィックスタート: Compose と Rails | Docker ドキュメント</title>](https://matsuand.github.io/docs.docker.jp.onthefly/compose/rails/)**

Dockerfileとシェルスクリプトファイル(entrypoint.sh)についてまとめた時点で文量が多くなってしまったため、これら以外の内容については次回の投稿にまとめます。

英文はまずDeepLの機械翻訳文を読んでから解釈を試みています。

### 注意
本稿は、Dockerについて理解の無い私がメモとして記している物です。
単なる個人的な感想も多く、また、解釈文であっても間違いがある可能性があります。
本稿を勉強目的でご覧になる場合は、この本文ではなく参照リンク先の方をご覧いただければと思います。
(リンク集としては少しは役に立てるかも...)
また、常識的なITの基礎用語であっても、とにかく私自身が知らなかったものには反応しているため、冗長であったり、本筋から脱線したような内容が多くなっています。

# 本編
[<title>クィックスタート: Compose と Rails | Docker ドキュメント</title>](https://matsuand.github.io/docs.docker.jp.onthefly/compose/rails/)
こちらのチュートリアルを読み進めていきます。

## Dockerfile全体

~~~dockerfile:Dockerfile
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# コンテナー起動時に毎回実行されるスクリプトを追加
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# メインプロセスの起動
CMD ["rails", "server", "-b", "0.0.0.0"]
~~~

## 1行目 : FROM ruby:2.5
~~~dockerfile:Dockerfile(1行目)
FROM ruby:2.5
~~~

### FROM

[FROM | Docker ドキュメント日本語化プロジェクト >> Docs](http://docs.docker.jp/engine/reference/builder.html#from)
FROM命令の役割は、処理ステージの初期化と、***ベースイメージ***の設定

まだ、現状の自分には概念理解が難しいと感じました。

とりあえず後日参考にしそうなリンクだけ...

- [ベース・イメージ（base image） | Docker ドキュメント日本語化プロジェクト >> Docs](http://docs.docker.jp/glossary.html#base-image)
- [親イメージ（parent image） | Docker ドキュメント日本語化プロジェクト >> Docs](http://docs.docker.jp/glossary.html#parent-image)
- [Dockerイメージの理解を目指すチュートリアル - Qiita](https://qiita.com/zembutsu/items/24558f9d0d254e33088f)

今回はこのコンテナのベースイメージとして、RubyのDockerイメージを設定するということのようです。
[<title>ruby - Docker Hub</title>](https://hub.docker.com/_/ruby/)

## ２行目 : RUN apt-get ... 
~~~dockerfile:Dockerfile(２行目)
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
~~~
### RUN
[RUN | Docker ドキュメント日本語化プロジェクト >> Docs](http://docs.docker.jp/engine/reference/builder.html#run)
コマンドの実行

### ``apt-get``コマンドについて

- [apt-get(8) - Debian Manpages](https://manpages.debian.org/unstable/apt/apt-get.8.ja.html)
- [apt-get - パッケージの操作・管理 - Linuxコマンド | <title>Webkaru - Webプログラミング入門</title>](https://webkaru.net/linux/apt-get-command/)
- [Linux豆知識 102 APT |  LPI-Japan](https://linuc.org/study/knowledge/432/)

APT(Application Packaging Tool)は、もともとDebian向けのパーケージ管理システムのことで、Debian系以外のディストリビューションでもそのコマンドを使用できる場合もあるようです。
Debianというのは、あるフリーのOSであって、``apt-get``はDebian系のOS上でパッケージを操作するためのコマンドのようです。

[Debian について | Debian](https://www.debian.org/intro/about)
> Debian は、現在 Linux カーネルか FreeBSD カーネルを利用しています。

Linux カーネル?
[About Linux Kernel | The Linux Kernel Archives](https://www.kernel.org/linux.html)
> If you're new to Linux, you don't want to download the kernel, which is just a component in a working Linux system. Instead, you want what is called a distribution of Linux, which is a complete Linux system. 

カーネルはあくまでLinuxシステムの様々な機能を担うコンポーネント(部品のような存在)であって、完全なLinuxシステムを手元で扱うのならば、出来合いのOSとして配布(或いは販売)されているDistribution of Linux（***Linuxディストリビューション***）が必要ということでしょうか。

https://mirrors.kernel.org/
このサイトで、代表的なディストリビューションのミラーが提供されているようです。
The Debian Linux distributionが提供されていることも確認できます。

[Linuxディストリビューション | Wikipedia](https://ja.wikipedia.org/wiki/Linux%E3%83%87%E3%82%A3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)
[Linuxカーネル | Wikipedia](https://ja.wikipedia.org/wiki/Linux%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB)
公式版のカーネルは、ディストリビューションのベンダーにて独自にカスタマイズされていることもあるようです。

[Linux豆知識 028 カーネル（kernel） |  LPI-Japan](https://linuc.org/study/knowledge/358/)
> これはよく言われることですが、厳密には「Linux」と言った場合、このLinux kernelのことを指します（最近では、後述する「Linuxディストリビューション」をLinuxと呼ぶケースも増えてきています）。

"Linux"と記載されている場合でも、文脈によってLinuxカーネルだったり、ディストリビューションの事を指していたりするってことか。慣れていくしかなさそうです。

ここで``apt-get``コマンドの話に戻りますが、このコマンドが使用できるということは、RubyのDockerイメージは、Debian系のLinuxディストリビューションをベースにしているのでしょうか？

[<title>ruby - Docker Hub</title>](https://hub.docker.com/_/ruby/)
Image Variants 項にて　ruby:＜version＞について
> This tag is based off of buildpack-deps. buildpack-deps is designed for the average user of Docker who has many images on their system. It, by design, has a large number of extremely common Debian packages. This reduces the number of packages that images that derive from it need to install, thus reducing the overall size of all images on your system.

Rubyの標準Dockerイメージのベースとなっている***[buildpack-deps](https://github.com/docker-library/buildpack-deps)***は、ごく一般的なDebianのパッケージを大量に含んでいるらしく、この説明文から、RubyのDockerイメージ環境下でのパッケージ操作のために``apt-get``コマンドを使用できそうな気はしてきます。
(上記引用文で言及されているDebianの一般的なパッケージ群？ -> [ buildpack-deps/Dockerfile.template](https://github.com/docker-library/buildpack-deps/blob/master/Dockerfile.template))

ただ、使用するOSについての直接的な記述を見たいです。
Stack Overflowで質問を調べてみると、同様の疑問に関する質疑がありました。
[<title>node.js - Docker Hub - Node Repo - What OS is this running? - Stack Overflow</title>](https://stackoverflow.com/questions/34583894/docker-hub-node-repo-what-os-is-this-running)
ベスト解答では、Dockerfileのベースイメージを辿れば答えに行き着くことが説明されています。

同様に探ってみます。
今回のベースイメージは、``ruby:2.5``なので、まずはそのバージョンのディレクトリへ。
https://github.com/docker-library/ruby/tree/master/2.5
/2.5以下の構成は、***alpine3.11, alpine3.12, buster, stretch***の4種類があり、alpineはLinuxディストリビューションのひとつである、[Alpine Linux project](https://alpinelinux.org/)を指します。

[<title>ruby - Docker Hub</title>](https://hub.docker.com/_/ruby/)にも記載の通り、busterやstretchは、Debianのスイート名です。
("スイート"という用語は今回が初見で、個々についてはスイートを成す派生品群のひとつという解釈をしています。間違えていたらすみません。 参照：[<title>アプリケーションスイートとは 「ソフトウェアスイート, スイート」 (application suite)： - IT用語辞典バイナリ</title>](https://www.sophia-it.com/content/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%B9%E3%82%A4%E3%83%BC%E3%83%88))

[<title>ruby - Docker Hub</title>](https://hub.docker.com/_/ruby/) > Supported tags and respective Dockerfile linksの項より、今回のようなタグなし(ruby:2.5)の場合は、buster用のDockerfileにサポートされているようなので、その内容を見ていきます。
https://github.com/docker-library/ruby/blob/master/2.5/buster/Dockerfile

~~~Dockerfile:ruby/2.5/buster/Dockerfile
FROM buildpack-deps:buster
~~~
ベースイメージとして、**buildpack-deps:buster**を指定しているようです。同様に辿ってベースイメージの記述を見ていきます。
https://github.com/docker-library/buildpack-deps/blob/master/debian/buster/Dockerfile

~~~Dockerfile:buildpack-deps/debian/buster/Dockerfile
FROM buildpack-deps:buster-scm
~~~
最終的に以下に行き着きました。
https://github.com/docker-library/buildpack-deps/blob/master/debian/buster/curl/Dockerfile

~~~Dockerfile:buildpack-deps/debian/buster/curl/Dockerfile
FROM debian:buster
~~~

これで、RubyのデフォルトのDockerイメージ上で稼働するOSがDebian系であることは確認できたので、``apt-get``コマンドが使用可能であることは腑に落ちました。

### ``apt-get update -qq``
[apt-get(8) - Debian Manpages](https://manpages.debian.org/unstable/apt/apt-get.8.ja.html)
``update`` : 利用可能なパッケージの一覧を更新するようです。
``-qq``:進捗のログを省略し、また、含まれた``-y``によってプロンプトへ自動で'yes'を応答して進めるというもののようです。

下記の質疑によると、``-qq``オプションのみで実行してしまうと、システムに予期せぬ影響を及ぼす変更がある場合に、ログが出力されないために気づけないということが起こりうるので、安全管理面で使用には注意が必要なようです。
[software installation - Why shouldn't I use apt-get install --qq without a no-action modifier? - Ask Ubuntu](https://askubuntu.com/questions/580726/why-shouldnt-i-use-apt-get-install-qq-without-a-no-action-modifier)



### ``apt-get install -y nodejs postgresql-client``
開発の都合によっては、ここでYarnなども追記してインストールします。

[<title>Getting Started with Rails — Ruby on Rails Guides</title>](https://guides.rubyonrails.org/getting_started.html#creating-a-new-rails-project-installing-rails)


## 3~8行目
~~~dockerfile:Dockerfile(3~4行目)
RUN mkdir /myapp
WORKDIR /myapp
~~~

### WORKDIR
[WORKDIR | Docker ドキュメント日本語化プロジェクト >> Docs](http://docs.docker.jp/engine/reference/builder.html#workdir)
ワークディレクトリを設定する。

Railsアプリケーションを置くフォルダを生成して、ワーキングディレクトリとして設定しています。

~~~dockerfile:Dockerfile(5~8行目)
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp
~~~

### COPY

[COPY | Docker ドキュメント日本語化プロジェクト >> Docs](http://docs.docker.jp/engine/reference/builder.html#copy)
ファイルやディレクトリを新たにコピーして、対象のパスに追加する。

チュートリアルの手順として、ローカルにGemfileとGemfile.lockを生成するので、それらをコンテナ内のRailsアプリケーションを置くパスにコピーしています。

## entrypoint.sh (シェルスクリプトファイル)

このチュートリアルでは``entrypoint.sh``というシェルスクリプトファイルを生成して使用します。
そもそも、シェルスクリプトの概念を知らなかったので、もまともに調べたのが今回初めてとなりました。

[<title>Bash - GNU Project - Free Software Foundation</title>](https://www.gnu.org/software/bash/)
[<title>The GNU Bourne-Again Shell</title>](https://tiswww.case.edu/php/chet/bash/bashtop.html)
[<title>Bash Reference Manual</title>](https://tiswww.case.edu/php/chet/bash/bashref.html)
[<title>/bin/bashとは｜「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典</title>](https://wa3.i-3-i.info/word14414.html)

今回扱うシェルスクリプトファイルの内容は以下の通りです。

~~~sh:entrypoint.sh
#!/bin/bash
set -e

# Rails に対応したファイル server.pid が存在しているかもしれないので削除する。
rm -f /myapp/tmp/pids/server.pid

# コンテナーのプロセスを実行する。（Dockerfile 内の CMD に設定されているもの。）
exec "$@"
~~~

### #!/bin/bash

１行目の``#!/bin/bash``についてはマニュアルに下記の説明があります。
[3.8 Shell Scripts - Bash Reference Manual](https://tiswww.case.edu/php/chet/bash/bashref.html#Shell-Scripts)
> If the first line of a script begins with the two characters ‘#!’, the remainder of the line specifies an interpreter for the program. 

また、

> Bash scripts often begin with #! /bin/bash (assuming that Bash has been installed in /bin), since this ensures that Bash will be used to interpret the script, even if it is executed under another shell.

つまり、``#!/bin/bash``はコメント文ではなく、``#!``によってインタプリタを``/bin/bash``に指定して、そのOSで使用されるシェルの系統に関わらずスクリプトを``/bin/bash``として解釈し、処理を進めるという意味があるようです。
次行に処理のコードが続きます。

### set -e

[4.3.1 The Set Builtin - Bash Reference Manual]
(https://tiswww.case.edu/php/chet/bash/bashref.html#The-Set-Builtin)
Bashの内部コマンドで、シェルのオプション値を変更したり、位置パラメータを設定する。
``-e``オプションの説明項
> Exit immediately if a pipeline (see Pipelines), which may consist of a single simple command (see Simple Commands), a list (see Lists), or a compound command (see Compound Commands) returns a non-zero status. 

上記解説で言うと、今回はどれも"a single simple command"なのでしょうか...?
また、ステータスがゼロの時と、ゼロでない時とは、それぞれ具体的にどのようなときを指すのでしょうか。

[3.2.1 Simple Commands - Bash Reference Manual](https://tiswww.case.edu/php/chet/bash/bashref.html#Simple-Commands)
> It’s just a sequence of words separated by blanks, terminated by one of the shell’s control operators (see Definitions). 

空白で区切られる場合や、制御演算子で終了する場合のこと。

[2 Definitions - Bash Reference Manual](https://tiswww.case.edu/php/chet/bash/bashref.html#Definitions)
制御演算子の項に"改行"も含まれているので、``entrypoint.sh``で``set -e``以降のコードは、それぞれSimple Commands(単純なコマンド？)に分類できると判断しました。

[3.7.5 Exit Status - Bash Reference Manual](https://tiswww.case.edu/php/chet/bash/bashref.html#The-Set-Builtin)
> For the shell’s purposes, a command which exits with a zero exit status has succeeded. A non-zero exit status indicates failure. 

上記引用の``set -e``の終了条件とは、終了ステータス
成功：0, 
失敗：0以外
ということでしょうか。

``set -e``について、実際の挙動を解説する動画もありました。(英語は分かりませんが...)
[Learn about scripts, functions, chmod and set -e | #2 Practical Bash - YouTube](https://www.youtube.com/watch?v=aqvc9kYnz0U)
　[5:22~](https://youtu.be/aqvc9kYnz0U?t=322)あたりから``set -e``或いは``set +e``の挙動が実演されています。
　　``set +e``以降はステータスが0の場合に直ちに終了
　　``set -e``以降はステータスが1(0以外)の場合に直ちに終了
動画では関数内で``return``によってステータスの値を渡しています。

``entrypoint.sh``内では、いずれかのコマンドが失敗すれば、直ちにプロセスを終了するために先頭で呼ばれているようです。

### rm -f /myapp/tmp/pids/server.pid

マニュアルのページは下記
[rm(1) — Linux manual page](https://man7.org/linux/man-pages/man1/rm.1.html)

削除対象のPIDファイルについて
[<title>pidfile - What are pid and lock files for? - Unix &amp; Linux Stack Exchange</title>](https://unix.stackexchange.com/questions/12815/what-are-pid-and-lock-files-for)
起動中のプロセスIDを記録して、それが実行中であることを判断するために生成されているもののようです。

従って、実行状態の判断をPIDファイルに依存している場合は、サーバーの終了方法によってはPIDファイルが削除されずに残る場合があるため、今回はこのスクリプトファイル内で削除する一文を加えることで、コンテナサーバー起動前に毎回PIDファイルを削除するようにしているようです。
``-f``オプションによって、対象ファイルが存在しなかった場合もエラーは返しません。

後日参照したい

- [<title>linux - What is a .pid file and what does it contain? - Stack Overflow</title>](https://stackoverflow.com/questions/8296170/what-is-a-pid-file-and-what-does-it-contain)
- [<title>rack/server.rb at master · rack/rack</title>](https://github.com/rack/rack/blob/master/lib/rack/server.rb#L431)
- [Prevent exception caused by a race condition on multi-threaded servers #1080](https://github.com/rack/rack/pull/1080)





その他参考

- [<title>linux - What does `exec "$@"` do? - Unix &amp; Linux Stack Exchange</title>](https://unix.stackexchange.com/questions/466999/what-does-exec-do)

- [<title>bash — Dockerエントリポイントスクリプトでexecを使用する目的は何ですか？</title>](https://www.it-swarm.dev/ja/bash/docker%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%A7exec%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E7%9B%AE%E7%9A%84%E3%81%AF%E4%BD%95%E3%81%A7%E3%81%99%E3%81%8B%EF%BC%9F/1055978342/)

- [位置パラメーターの一括展開 $* $@ "$*" "$@" の違いを知れ!! - Shell Script Advent Calendar 2016 - ダメ出し Blog](https://fumiyas.github.io/2016/12/15/positional-parameters.sh-advent-calendar.html)
  こちらは奥が深そうなので、また個別に勉強します。

- [shell - What does set -e and exec "$@" do for docker entrypoint scripts? - Stack Overflow](https://stackoverflow.com/questions/39082768/what-does-set-e-and-exec-do-for-docker-entrypoint-scripts)

### 脱線：manコマンド(macOSの場合)
調べる過程でmanページのことを初めて知りました。
macのターミナルで``$ man rm``を実行したらman7.orgにあったものと同様のものを読めました。
``set``や``exec``については、``man``が適用できないのかと思いましたが、ふざけて実行すると、いずれも[BUILTIN(1) - BSD General Commands Manual](https://www.freebsd.org/cgi/man.cgi?query=builtin&apropos=0&sektion=0&manpath=FreeBSD+12.1-RELEASE+and+Ports&arch=default&format=html)の内容が呼ばれました。
なんか違うOSの名前出てきた！と思ったらmacOSはFBDの血筋を引いている説明を見つけました。([BSD #BSDの主な子孫 - Wikipedia](https://ja.wikipedia.org/wiki/BSD#BSD%E3%81%AE%E4%B8%BB%E3%81%AA%E5%AD%90%E5%AD%AB))

macでDarwinのバージョンを調べるコマンドがあったので確認してみました。

```bsdmake
$ uname -v
Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64
```

Darwin7.0からデフォルトのシェルをBashに変更したとあるので、``man``コマンドでBashのコマンドのマニュアル呼ぼうとしたらBSDのコマンドマニュアルが呼ばれるのも、そのあたりの関係性によるものなのでしょうか。

頭が付いていかないので、今回はこのあたりにしておきます。


## 10~17行目 コンテナの起動時

~~~dockerfile:Dockerfile(10~17行目)
# コンテナー起動時に毎回実行されるスクリプトを追加
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# メインプロセスの起動
CMD ["rails", "server", "-b", "0.0.0.0"]
~~~


~~~dockerfile:Dockerfile(11行目)
COPY entrypoint.sh /usr/bin/
~~~
チュートリアルの手順にて、ローカルで作成していた``entrypoint.sh``をコンテナルート下``/usr/bin/``へコピーしています。

~~~dockerfile:Dockerfile(12行目)
RUN chmod +x /usr/bin/entrypoint.sh
~~~

[CHMOD(1) - BSD General Commands Manual](https://www.freebsd.org/cgi/man.cgi?chmod)
[chmod - Wikipedia](https://ja.wikipedia.org/wiki/Chmod)

全クラスに対して、(x)実行権限を(+)付与する
つまり、ユーザーに関わらず``/usr/bin/entrypoint.sh``に実行権限を付与して、シェルスクリプトファイルを実行可能としています。

~~~dockerfile:Dockerfile(13行目)
ENTRYPOINT ["entrypoint.sh"]
~~~
### ENTRYPOINT
[ENTRYPOINT | Docker ドキュメント日本語化プロジェクト >> Docs](http://docs.docker.jp/engine/reference/builder.html#entrypoint)

今回では、exec形式にてデフォルトで実行するコマンドライン引数として``entrypoint.sh``を指定しています。

~~~dockerfile:Dockerfile(14行目)
EXPOSE 3000
~~~
### EXPOSE
[EXPOSE | Docker ドキュメント日本語化プロジェクト >> Docs](http://docs.docker.jp/engine/reference/builder.html#expose)
コンテナの実行時にリッスンする、ネットワーク上のポートを指定します。今回は"3000"。

~~~dockerfile:Dockerfile(17行目)
CMD ["rails", "server", "-b", "0.0.0.0"]
~~~

### CMD
[CMD | Docker ドキュメント日本語化プロジェクト >> Docs](http://docs.docker.jp/engine/reference/builder.html#cmd)
> CMD 命令の主目的は、コンテナの実行時のデフォルト処理を設定することです。 

Dockerfileの命令で既にポートが3000に指定されているので、Railsのアプリケーションサーバーはポート3000で稼働します。

知りませんでしたが、"0.0.0.0"には、「特定のアドレスを指定しない」という意味があるようです。

下記リンクを参照しましたが、上記の解釈以上のことは難しくて分かりませんでした。それはまた後日。
[<title>127.0.0.1とlocalhostと0.0.0.0の違い - Qiita</title>](https://qiita.com/1ain2/items/194a9372798eaef6c5ab)
[<title>What is the Difference Between 127.0.0.1 and 0.0.0.0? </title> - How-To Geek](https://www.howtogeek.com/225487/what-is-the-difference-between-127.0.0.1-and-0.0.0.0/)

---

Dockerfileの内容についてはここで終わりです。
引き続きチュートリアルのその他の内容について、次回分にメモしていきます。