dockerの使い方メモ

# 1. dockerとは


ネイティブな環境とほとんど変わらない高速な動作が可能な仮想環境を作れる、CPUコア数やメモリ容量を指定した複数の仮想環境を同時に起動できる、imageファイルを切り替えることで即座に環境構築ができる、などの素晴らしい機能によりデファクトスタンダードとなった仮想環境ツールです。

docker公式ページ
https://docs.docker.com/


# 2. インストール


## 2-1. Windows環境でのインストール


### docker desktopのインストール


ダウンロードしてきてインストーラーします
https://www.docker.com/products/docker-desktop

Windows版のdockerはWSL2を利用しますので、この時点で起動してもエラーが出ます
https://docs.microsoft.com/ja-jp/windows/wsl/install-win10
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/f73ec2ae-68ab-6050-8fac-26fb6cc0dcd0.png" width=60%>
上記を解決する為に以下の手順をやっていきます


### WSL2をdockerが使えるようにする


Windows版のdockerはWSL2を利用しますのでこれを使えるようにしてやります
https://docs.microsoft.com/ja-jp/windows/wsl/install-win10


#### WSLを有効にする


Windowsの設定内になる「Windowsの機能の有効化または無効化」を開きます
(設定の検索窓から探すと早いと思います)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/9dac5c57-0a91-8416-2e7e-3cd7786843ae.png" width=50%>
「Linux用Windowsサブシステム」と「仮想マシンプラットフォーム」にチェックを入れます。


#### カーネル更新プログラムパッケージを適用する


2021年4月29日現在では<a href="https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi">x64 マシン用 WSL2 Linux カーネル更新プログラム パッケージ</a>をダウンロードしてきて実行すれば適用されます。

Docker Desktopを起動して以下のように表示されたら正常です
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/03743029-31cd-f017-eac4-7b31b35adf8c.png" width=50%>
Windows版でCLIｋらdockerを使う場合、一度Docker Desktopを起動しておく必要があるようです。

docker: error during connect: This error may indicate that the docker daemon is not running.: Post http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.24/containers/create: open //./pipe/docker_engine: The system cannot find the file specified.
See 'docker run --help'.



## 2-2. Linux環境でのインストール


Ubuntu18.04でやってみます
アップデートします


```terminal:
sudo apt update
```


aptがHTTPS経由でリポジトリを使用できるようにパッケージをインストールする


```terminal:
sudo apt install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
```


dockerの公式GPG公開鍵を追加


```terminal:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
```


公開鍵のフィンがプリントを確認


```terminal:
sudo apt-key fingerprint 0EBFCD88
```


リポジトリを設定します
x86_64の場合はarch=amd64です


```terminal:
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
```

もう一度アップデートしてdockerをインストールします。docker-ce=以下にバージョンを入力して指定できますが、省略した場合は最新版がインストールされます。


```terminal:
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io
```


最後にdockerのバージョンを確認しておきましょう


```terminal:
$ sudo docker version

Client: Docker Engine - Community
 Version:           20.10.6
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        370c289
 Built:             Fri Apr  9 22:46:01 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true
```


# 3. CLIで使う


## 3-1. コンテナの生成/起動する


docker runコマンドでコンテナの生成と起動が実行されます。

localに当該dockerイメージがない場合はdocker hubから自動的にダウンロードしてきてくれます。docker pullコマンドでダウンロードのみ行うことも出来ますが、その後docker runするのは同じです。


```docker:
docker run hello-world
```


上記を実行すると以下のようにhello-worldコンテナが実行された結果が表示されます


```docker:
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```


ここで生成済みコンテナ一覧を表示してみましょう


```docker:
> docker container ls --all
CONTAINER ID   IMAGE         COMMAND    CREATED         STATUS                     PORTS     NAMES
f28a2890b770   hello-world   "/hello"   1 minutes ago   Exited (0) 1 minutes ago             frosty_keller
```


hello-worldイメージにfrosty_kellerというNAMEをつけて生成されているのが分かります。この名前はdockerがランダムに割り当てるもので、同じコンテナが複数起動しているときに見分けが付くようにしてくれているものです。
https://docs.docker.jp/engine/reference/run.html#name


## 3-2. NAMEを指定する


docker runする際にNAMEを指定することが出来ます


```docker:
docker run --name "hello-world-01" hello-world
```


同様にコンテナ一覧を出すと、今度はhello-world-01というNAMEでコンテナが実行されたことが分かります。


```
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                      PORTS     NAMES
8caa8b8325f9   hello-world   "/hello"   5 seconds ago    Exited (0) 4 seconds ago              hello-world-01
f28a2890b770   hello-world   "/hello"   4 minutes ago   Exited (0) 31 minutes ago             frosty_keller
```


## 3-3. 結果をコンソールに出力する


-itオプションによりコンテナの出力をterminalに表示してみます


```docker
docker run -it lukaszlach/merry-christmas
```


![2021-04-30_15-17-53.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/f8dde977-b231-4855-1439-3c756d598091.gif)
-itオプションはstdinを有効にする-iオプションと擬似端末を有効にする-tオプションを同時に指示するものです。これによりdockerコンテナとCLIでやりとり出来るようになります。ctrl + c でコンテナを停止してCLIに戻れます。


## 3-4. デタッチドモードで実行する


dockerを通常起動するとCLIがコンテナの標準出力を受け付ける為に待機状態になりますが、-dオプションでデタッチドモードにすることでCLIが待機しないままバックグラウンドで起動することが出来ます。
https://docs.docker.jp/engine/reference/run.html#detached-vs-foreground


```docker:
docker run -d lukaszlach/merry-christmas
```


ここで起動中のdockerコンテナを確認してみましょう。docker container lsと--allオプションを付けなければ起動中のコンテナのみを表示します。


```docker:
>docker container ls
CONTAINER ID   IMAGE                        COMMAND             CREATED             STATUS             PORTS     NAMES
17d7dedd1195   lukaszlach/merry-christmas   "/tree /tree.txt"   20 seconds ago      Up 19 seconds                condescending_johnson
```


先程起動したコンテナが動いているのが分かります


## 3-5. コンテナを停止する


NAMEまたはUUIDを指定して停止できます。


```docker:コンテナの例
> docker container ls
CONTAINER ID   IMAGE                        COMMAND             CREATED          STATUS          PORTS     NAMES
17d7dedd1195   lukaszlach/merry-christmas   "/tree /tree.txt"   25 minutes ago   Up 40 seconds             condescending_johnson
```


上記のようなコンテナを停止したい場合は以下のようにして停止できます。


```docker:UUIDで停止
docker stop 17d7dedd1195
```


```docker:NAMEで停止
docker stop condescending_johnson
```


## 3-6. 生成済みのコンテナを起動する


docker stopで停止したコンテナを再度起動することが出来ます


```docker:起動のみ
> docker start condescending_johnson
```


dockerコンテナのCLIにアクセスしたい場合は-aオプションを付けます


```docker:terminal接続で起動
> docker start -a condescending_johnson
```


## 3-7. 生成済みのコンテナを削除する


要らなくなったコンテナはdocker rmコマンドで削除できます。
削除対象はUUIDまたはNAMEで指定します。


```docker:対象を指定して削除する
docker rm condescending_johnson
```


使っていないコンテナをまとめて削除することも出来ます


```docker:起動してないコンテナをすべて削除する
docker container prune
```


使った後にいちいち消すのが面倒な場合は実行時に-rmオプションを付けて終了時にコンテナを削除するように出来ます


```docker:-rmオプションで終了時にコンテナを削除する
docker run --rm (image名) 
```


## 3-8. docker image一覧を表示する


```docker:
> docker images

REPOSITORY                   TAG       IMAGE ID       CREATED         SIZE
alpine/git                   latest    c99c7d810bc1   10 days ago     25.1MB
hello-world                  latest    d1165f221234   8 weeks ago     13.3kB
centos                       latest    300e315adb2f   4 months ago    209MB
lukaszlach/merry-christmas   latest    bd797b67c25d   16 months ago   2.65MB
```


## 3-9. docker imageを削除する


docker rmiの引数にREPOSITORYまたはIMAGE IDを指定して削除します


```docker:REPOSITORYを指定
docker rmi alpine/git
```


```docker:IMAGE_IDを指定
docker rmi c99c7d810bc1
```


起動していないimageをまとめて削除することも出来ます


```docker:
docker image prune --all
```


## 3-10. CPU配分、メモリ上限を指定する


デフォルトではCPUとメモリは無制限になります
https://docs.docker.jp/config/container/resource_constraints.html


### CPU


```docker:CPUコア数を1.5に指定する
docker run --cpus=1.5 centos
```


```docker:CPU配分を50%にする(1024で100%)
docker run --cpu-shares=512 centos
```


### メモリ


--memoryでメモリ使用量上限、--memory-swapでメモリスワップの上限＋メモリ使用量上限を指定します


```docker:メモリ上限512MB、メモリスワップ上限512MB
docker run --memory=512m --memory-swap=1g centos
```


--memory-swapを省略すると-memoryの値をスワップ上限に使います。
つまり上記の場合は--memory-swapを省略しても同じになります。


```docker:メモリ上限512MB、メモリスワップ上限512MB
docker run --memory=512m centos
```


## 3-11. ポートに静的サイトを待機する


Nginxで静的サイトを待機してくれるimageを使ってポート操作を練習してみましょう。prakhar1989/static-site を起動すると80番ポート (html) に静的サイトが待機してくれますが、dockerを起動しただけではコンテナのポートとホストのポートが繋がらないのでサイトが開けません。なので-Pオプションまたは-pオプションでコンテナのポートをホストのポートに割り当ててやります。


### ポートをランダムに割り当てる


-Pオプションをつけるとランダムなポートにサイトを割り当てられます


```docker:
> docker run -d -P --name static-site prakhar1989/static-site
> docker port static-site

443/tcp -> 0.0.0.0:49153
80/tcp -> 0.0.0.0:49154
```


docker portでcontainerを指定するとポートの割当が見られます。上記の場合、80/tcpがhttpポートですので上記の場合は http://localhost:49154/ を開けばサイトが見られる筈です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/bc4e9286-9e30-3c6a-639b-99582222d313.png" width=50%  border="">


### ポート番号を指定して割り当てる


-pオプションを使って、-p ホスト側のポート:コンテナ側のポートと指定したポートを繋げることが出来ます。以下のようにするとコンテナの80番ポートがホストの8080番ポートに繋がります。


```docker:
> docker run -d -p 8080:80 --name static-site prakhar1989/static-site
```


以下を開くと画面が開きます
http://localhost:8080/
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/bc4e9286-9e30-3c6a-639b-99582222d313.png" width=50%  border="">


## 3-12. ホストとコンテナでファイルをやりとりする


docker cpコマンド
https://docs.docker.jp/engine/reference/commandline/cp.html


### コンテナからホストにコピーする


「docker cp コンテナPATH ホストPATH」でコンテナの指定PATHからホストの指定PATHへのコピーが実行されます。PATHで指定する対象はフォルダでもファイルでも構いません。hoge名のコンテナの/home/pictures/フォルダからホストのpictures/フォルダにコピーする場合は以下のようになります。


```docker:
docker cp hoge:/home/pictures/ pictures/
```


### ホストからコンテナにコピーする


上記の逆にしてやれば同じようにホストからコンテナにコピーしてくれます


```docker:
docker cp pictures/ hoge:/home/pictures/
```


## 3-13. imageを探す


dockerがimageファイルの共有用にdocker hubというページを用意してくれています。Ubuntu、centos、django、nodejsなどのdocker公式リポジトリと、各ユーザーが作った公開リポジトリを利用することが出来ます。
https://hub.docker.com/search?type=image

試しにgolang公式のimageを使ってみます
https://hub.docker.com/_/golang
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/f59ff898-6f7f-9b86-fbc2-44a3eb17e86a.png" width=50%>


```docker:ダウンロードのみ
docker pull golang
```


```docker:コンテナを生成して起動する
docker run golang
```


## 3-14. imageを作る


既にあるimageをベースにライブラリなどを追加して自分のimageを作ることが出来ます。


### Dockerfileをつくる


まず、新規imageをつくる条件を書いたテキストファイルをつくりファイル名「Dockerfile」で保存します。FROMがベースとなるイメージ、RUNがimage作成時に実行するコマンド、EXPOSEがポート番号でCMDがコンテナ起動時に実行するコマンドになりますので、以下の場合はUbuntu18.04の公式imageにminicondaをインストールしてjupyter notebookを起動待機するimageをつくるDockerfileになります。

なお、&& \で繋げてるコマンドが途中で処理を失敗したときにどこまで進んだか分からないと困るので、set -xを入れて実行したコマンドそのものを標準エラー出力に出すようにしておきましょう。


```docker:miniconda/Dockerfile
FROM continuumio/miniconda3

# conda create
RUN conda create -n chemodel python==3.7 

# install conda package
SHELL ["conda", "run", "-n", "chemodel", "/bin/bash", "-c"]
RUN conda install django=3.* -c conda-forge --override-channels
RUN conda install pytorch==1.7.1 torchvision==0.8.2 torchaudio==0.7.2 cpuonly -c pytorch -c conda-forge -c tboyer --override-channels 
RUN conda install -c rdkit -c conda-forge rdkit --override-channels

# install pip package
RUN pip3 install --upgrade pip
RUN pip3 install gunicorn
```


上記以外にも、Dockerfileのコマンドはいろいろあります。


```docker:Dockerfileのコマンド
FROM 元になるimage名
RUN docker build時に実行するコマンド
CMD docker コンテナ起動時に実行するコマンド
ENTRYPOINT コンテナ起動時に実行するコマンド
ONBUILD imageを再利用してbuildするときに実行するコマンド
ENV 環境変数=値
WORKDIR 作業ディレクトリ
USER ユーザ名
LABEL ラベル名=値
EXPOSE ポート番号
ADD ホストのパス イメージのパス
COPY ホストのパス イメージのパス
VOLUME イメージのマウントポイントパス
```


詳しくは公式ページを参照して下さい
https://docs.docker.jp/engine/reference/builder.html

また、Dockerfileのベストプラクティスをdocker公式が公開してくれてますので読んでおきましょう
http://docs.docker.jp/engine/articles/dockerfile_best-practice.html


### buildする


「docker build -t 作成するimage名 DockerfileがあるフォルダPATH」を実行することでDockerfileの内容通りにimageを作ってくれます。例えば、./minicondaフォルダにDockerfileがあって、testという名前でimageを作りたい場合は以下のようになります。


```docker:terminal
docker build test ./miniconda
```


### コンテナを起動する


docker imagesで確認すると先程作ったimageが表示されます


```docker:
> docker images

REPOSITORY                TAG       IMAGE ID       CREATED             SIZE
test                latest    fc72cb7f0dd7   About 30 seconds ago   3.41GB
```


他のコンテナと同様、docker runで起動できます


```docker
docker run -it test
```


### docker hubにアップロードする


作ったimageをdocker hubに登録することも出来ます。無料アカウントでは公開イメージしか作れませんが、有料アカウントであればイメージを非公開にすることも出来ます
https://docs.docker.jp/engine/userguide/dockerrepos.html


### Dockerfileのデバッグ


docker公式のimageは軽量化を図るためか最小限のライブラリしか入っていませんので、普段使っているOSでインストールに使ってるコマンドが走らないというのが普通に起こります。しかも、エラー停止したときにRUNコマンドのどこで止まったのかイマイチ分かりにくいし、docker buildを実行する度にRUNコマンドの最初からやり直しになって時間がかかるなどの問題があるので、docker buildでデバッグしていくのは効率がよくありません。

そこで、docker run -itでコンソールに入ってコマンドを1つずつ実行しながら必要なライブラリをインストールしていって、上手くいったコマンドをDockerfileに書き移すというやり方が良いと思います。
https://qiita.com/pottava/items/452bf80e334bc1fee69a


# 4. docker desktopで使う


WindowsやMacではdocker desktopを使うことで、GUI上でdockerコンテナを管理することができます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/564eeb4e-cee8-bd34-e14c-c28784eb24bd.png" width=50%>
コンテナ一覧はアイコンの色でコンテナが起動中か分かるようになっていて、起動/停止の切り替え、CLI接続、再起動やコンテナの削除が出来るようになっています。image一覧から新たなコンテナを生成して起動することも出来ますし、docker hubに登録することも出来ますので複数のコンテナの起動/停止をちょこちょこ切り替えるような場合には便利だと思います。


# 5. まとめ


dockerめちゃめちゃ便利ですね。
Dockerfileで自前のimageをつくる辺りは結構苦戦しましたがマスターして使いこなしたいです。


# 参考にしたページ


Windows環境でvscode Remote Containers接続環境をつくる手順を解説してくれています
https://qiita.com/zaki-lknr/items/db99909ba1eb27803456


チートシートを作ってくれています
https://qiita.com/wMETAw/items/34ba5c980e2a38e548db