Dockerについて

#前提
Dockerについて学んだことを書いていきます。

#本題
#Dockerって何
##なぜ注目されているのか
Dockerは毎年のように人気度が増えていて、インフラとDevelopmentを融和させるDevOpsという風潮の中では、なくてはならない存在のため。


##なぜ必要
コードを速く正確にユーザーに渡すことができるため。
どういうことかというと、今まではDeveloperがコードを書いて、そのコードをインフラエンジニアに渡して、インフラが環境変数やライブライーのインストールを本番環境で行なっていた。
ここで問題なのが、開発・テスト・本番環境が同一ではないということ。
つまりOSが違えば、ライブラリーのバイナリーも違い、開発環境では動いていたのに本番では不動作が起こるということがよくあった。


##どういう風に解決するか
開発チームがコードとライブラリーをパッケージングしてdocker imageを作り（コンテナ化）、運営チームにdocker imageをそのまま渡すことで、ライブラリーやDependencyの食い違いを防げる。


##つまりコンテナ化とは
コンテナという技術を使い、コードとライブライーやパッケージをパッキングすること。


###まとめ
開発環境と運用環境を一緒にする（コードとDependencyをコンテナ化）ことで、環境の違いによる不動作を防げるようになる。


##従来の運用と何が違うのか
今までは、ファイルやフォルダーをJenkinsなどのCICDパイプラインを使い、本番環境のサーバーにコピーしていた。
Dockerでは、コードとライブラリをパッケージングしてdocker imageを作り、本番環境のサーバーにdocker imageをダウンロードし、それを実行するだけでOKになった。
つまりDeployする最小単位のアーティファクトが、コンテナ（コード＋パッケージ）に代わりOS環境問わず同一の動作をする。


##従来のOS仮想化とDockerの違い
従来の仮想化は、１つのOS上に複数の仮想OSを置いて、そこでアプリケーションを動かしていた。
Dockerの場合は、１つのOS上にDocker engineを起動し複数のコンテナを起動する。
そのためDockerの場合は、仮想OSと仮想マシンをホストOS上にコピーする必要がなくなる。


#OS仮想化と比べたDockerのメリット
・リソースが軽い
OSを複数使わない分オーバーヘッド（複数のOSイメージとカーネル）が減り、プロセッサやメモリの消費が少なくなる。

・ストレージの使用量が減る
OSイメージの通常サイズが５−１０GBに対し、Docker Imageのサイズは１−２GBのためストレージの使用量も減る。

・起動時間が速い
カーネルをいちいちロードする手間が省け、仮想マシンに比べて起動時間が短くなる。

・複数環境での運用が楽
DockerがインストールされているOSならばどこでもコンテナが起動できる。


##イメージとコンテナの違い
例えると、ClassとObject、車の設計図と実際の車のようにテンプレートVS実物といういい違い。


#Linuxの基本操作
##Linuxのカーネルとは
簡単に言うと、カーネルとはハードウェア民族と通訳ができる人（唯一のソフトウェア）のこと。

##シェルとは
カーネルに直で繋がっているチャットインターフェース的なもの。
シェル（ターミナル）はシェル言語という言語でもある。
sh、tcsh、bash、zshがよく使われるコマンド。
シェルは人間とカーネルの間の伝達マンのため、インプットとアウトプットも表示できる。

##シェルのSTDINとSTDOUTとは
ターミナルで見えるシェルは、フォアグランドプロセスで、インプット（STDIN）とアウトプット（STDOUT&STDERR）が見える。
これがバックグラウンドのプロセスは、アウトプットもインプットも見えない状態。
これを見えるようにするには、シェルのSTDINとSDTOUTをターミナルに繋げることが必要。
このフローをTTY（Tele Typewrite）をアタッチすると言う。
このTTYをターミナルにアタッチすることは（プロセスをターミナルから会話できる）、Dockerの中にシェルで入る時にdocker exec --interactive --ttyで使う。


##シェル基本コマンド
シェルでは、タスクマネージャーのようにプロセスを表示できる。
また、バックグランドも起動できる。

```
#プロセス（process status）の表示
$ ps
```

```
#プロセスをバックグランドで起動（-&）
$ sleep 50 -&
```

```
#プロセスをフォアグランドに戻す（-fg）
$ fg
```

Linuxにはサーチバーがないため、ファイルやフォルダーをサーチする場合コマンドを使う。

```
#ファイル・フォルダを探す（-find）
$ find / -type d -name nginx
```



#Dockerの一連作業フロー
- Nginx Dockerイメージを取得

```
$ docker pull nginx
```

- Dockerイメージを表示 (List)

```
$ docker images
```
- Dockerイメージの履歴を表示 (History)

```
$ docker history nginx
```

- Dockerイメージを削除 (Delete Docker Image)

```
$ docker rmi nginx

$ docker images
```


#Hello Wold
- nginxサーバーのコンテナを起動 (Run)

```
$ docker run -p 80:80 --name nginx nginx
```
- 作動中のコンテナ一覧を表示 (List)

```
docker ps

# 停止中のコンテナも全て一覧表示
docker ps --all
```
- コンテナのログを表示 (Log)

```
docker logs nginx
```
- コンテナのメタデータを見てみる (Inspect Docker container)

```
docker inspect nginx
```
- Stop

```
docker stop nginx
```
- 停まったコンテナを削除 (Remove)

```
docker rm nginx
```
- コンテナをバックグラウンドで起動 (--detach)

```
docker run --detach -p 80:80 --name nginx nginx
```
- 作動中のコンテナの中にシェルで入る (Exec)

```
docker exec -it nginx sh

exit
docker stop nginx
```

- コンテナの環境変数を設定する `--env TEST_ENV=hellow_world`

```
docker run --env TEST_ENV=hellow_world -d --name nginx nginx

docker exec -it nginx env
docker stop nginx
```
- コンテナに繋げるホスト側のポートを変える `-p 8080:80`

```
docker run -p 8080:80 -d --name nginx nginx

curl localhost:8080
```
- NginxのConfigファイルを見つける

```
docker exec -it nginx sh

find / -type d -name nginx | xargs grep -r  html
```
- デフォルトのHTMLファイルを表示する

```
cat /usr/share/nginx/html/index.html
```
- デフォルトのHTMLファイルを”Hello World"へ上書き

```
echo "Hello World" > /usr/share/nginx/html/index.html

exit
curl localhost:8080
docker stop nginx
```
- コンテナにホストからファイルをコピー (attach volume) `--volume "$(pwd)":/usr/share/nginx/html`

```
touch index.html && echo “hello world” > index.html

docker run -d --volume "$(pwd)":/usr/share/nginx/html -p 80:80 --rm --name nginx nginx

curl localhost:80
```

- 起動中のコンテナからHello Worldイメージ作成 (commit)

```sh
docker commit nginx hello_world
docker stop nginx

docker images

# Hello Worldイメージからコンテナ起動して確認
docker run -d -p 80:80 --name hello_world hello_world

curl localhost:80

# Hello Worldが返ってこない理由は、Docker　Volumeでマウントされたファイルはコンテナ内に保存されないから

docker run  -p 80:80 --rm --name nginx -d  nginx 

docker exec -it nginx sh
echo "hello from container" > /usr/share/nginx/html/index.html
exit

curl localhost:80

# imageを作成
docker commit nginx hello_world

# Imageからコンテナ起動
docker run -p 8080:80 --rm -d --name hello_world hello_world

curl localhost:8080
# hello worldが返ってくる
```


#自作のDockerimageを作る
起動したコンテナからも１コマンドでイメージを作れる。

- 起動中のコンテナからHello Worldイメージ作成 (commit)

```sh
docker commit nginx hello_world
docker stop nginx

docker images

# Hello Worldイメージからコンテナ起動して確認
docker run -d -p 80:80 --name hello_world hello_world

curl localhost:80

# Hello Worldが返ってこない理由は、Docker Volumeでマウントされたファイルはコンテナ内に保存されないため

docker run -p 80:80 --rm --name nginx -d nginx 

docker exec -it nginx sh
echo "hello from container" > /usr/share/nginx/html/index.html
exit

curl localhost:80

# imageを作成
docker commit nginx hello_world

# Imageからコンテナ起動
docker run -p 8080:80 --rm -d --name hello_world hello_world

curl localhost:8080
# hello worldが返ってくる
```
他にも、リポからダウンロードできるイメージにえり好みの素材(パッケージ等)を<strong>Dockerfile</strong>に加えてMyイメージも作れる。

DockerfileはUMLダイアグラムみたいなもので（Classデザイン）、Dockerイメージがクラス、コンテナがオブジェクトみたいなもの。

つまり、Dockerfileは<strong>Dockerイメージ（たい焼き機）を作るレシピ（設計書）</strong>。

__Dockerfileでレシピの作成__

```sh
# Dockerfileを作成
touch Dockerfile
```

```sh
＃ Dockerfileにマルチラインテクストを保存

cat > Dockerfile <<EOF
# FROMでイメージを指定
FROM nginx:latest 

WORKDIR /usr/share/nginx/html

# COPYでホストからファイルをイメージにコピー
COPY index.html index.html 

# RUNでシェルコマンドを実行
RUN apt update && apt install -y curl

# 他にもWORKDIR,ENV,CMDなど変数がある
EOF
```
実はDockerイメージは、このDockerfileの各ラインが追加されるごとに、薄いReadOnlyイメージレイヤーを追加している。

その証拠に、docker buildコマンドのアウトプットで、各コマンドステップで新たなコンテナイメージ層が作られているのが確認できる。

- Dockerfileからイメージ作成 (build)

```
$ docker build --tag dockerfile_hello .

# 自作イメージからコンテナを起動
$ docker run -d -p 9090:80 --name hello_v2 dockerfile_hello

curl localhost:9090
```

イメージレイヤーはDockerfileで指定したコマンドと一致している。
つまり、Dockerfileの１コマンドごとにReadOnlyイメージレイヤーが上乗せされていく。

- ドッカーイメージの履歴を表示 (History)

```
$ docker history dockerfile_hello
```


- 自作イメージをリポで公開

Dockerイメージを作ったら、あとはタグ付けをして、DockerHubなどのコンテナレジストリーにプッシュ。

- 作成したイメージをタグ付けする（ユーザーネーム/リポ名）

```
docker tag dockerfile_hello ユーザーネーム/dockerfile_hello_world_nginx
```
- Docker Hubにログイン

```
docker login
```
- DockerHubの自分のリポにイメージをアップロード

```
docker push cscareerkaizen/dockerfile_hello_world_nginx
```


***コンテナ・イメージのクリーンアップ***

- 停止中のコンテナと無名のイメージを削除

```
docker system prune
```
- 起動中のコンテナ含め全コンテナを削除(注意)

```
docker rm -vf $(docker ps -a -q)
```
- 全てのイメージを削除(注意)

```
docker rmi -f $(docker images -a -q)
```

#DockerComposeで複数コンテナ管理
例えば、Nginxの他にNodeコンテナも起動したい場合などに登場するのがdocker-compose。
docker runとdocker-composeの対応関係について、
docker-composeはYML形式のファイルで、複数のコンテナを一括管理することができる。

YMLファイルとは、インデントで区切りをつけるJSONの兄弟のようなもの。
さらに、トップダウンのヒエラルキーになっている。

***docker-compose CLIを使って起動・停止***

- docker-composeのYAMLに定義されたサービスを起動する

```
docker-compose -f docker-compose.yaml up

docker ps
```
- 複数コンテナのログをまとめてみる

```
docker-compose -f docker-compose.yaml logs -f
```
- docker-composeのYAMLに定義されたサービスを停止

```
docker-compose -f docker-compose.yaml down
```

***Docker-composeでコンテナを大量生産***
他にも、docker-composeで各コンテナのレプリカを複数起動できる。

まずContainerNameを除き、ホスト側のポートをレンジに変更 (重複を避けるため)。

- upコマンドに--scaleオプションでレプリカ数を指定

```
docker-compose -f docker-compose.replicas.yaml up --scale nginx=3
```

***Inspect docker network***
Docker-composeで定義されたコンテナ同士で連絡取れるようになっている。

ただYAMLの外でコンテナを起動すると、NGINXコンテナに繋がらない。

- docker_compose_ubuntuコンテナに入って、NginxにCurl。

```
docker exec -it docker_compose_ubuntu sh
apt update && apt install -y curl

curl docker_compose_nginx:80
Hello World
```
- UbuntuコンテナをDocker runで起動してCurlをインストール

```
docker run --name test -it --rm curlimages/curl:7.68.0 sh

curl docker_compose_nginx:80
curl:(6) Could not resolve host:
docker_compose_nginx
```