Dockerの初歩と詰まったことへの備忘録

# Dockerの初歩と詰まったことへの備忘録

## はじめに
本記事は，私がUbuntu18.04とWindows上でDockerを学んだことの備忘録であるため，以下のとおりである．
- Dockerについての環境構築について詳しく触れるものではない
- Dockerのイメージを作成まで（Docker Hubへのアップはしていない）
- DockerでホストのGPUを活用する方法
- DockerでGUIを有効にする方法
- Windows上でGUIを有効にするために必要なXserverについて

## Dockerについて
まず初めに，簡単に学んだDockerの特徴だけ示しておく．

- Dockerは隔離されたコンテナの中で作業するイメージであり，ホスト環境を壊すような心配がない．
    - コンテナ内が壊れてもすぐに廃棄して作り直せばよい
- Dockerには``ビルド -> 実行 -> 廃棄``の**ライフサイクル**がある．
    - 初めは意味が分からなかったが，作って使っていく中で更新があれば，更新内容を控えて古いものは廃棄して，作り直していくということだと理解した．
- Dockerには基本Linuxが必要
    - WindowsやMacでも動くのは，あくまでホスト上に仮のLinuxがあり，そのうえで動いている．
- Dockerは隔離されてはいるため，中は見えないとされているが，一部サーバやマウントにより，中身へアクセスすることも可能．
    - サーバであれば，ローカルホストの指定ポートにアクセスすることでコンテナ内を見て回ることができる．
    - マウントはホスト間でファイルの共有ができる．マウントにも種類があるが，Docker側での変更をホスト側で管理しておきたい場合や，必要なシステムファイルを共有するときなどに用いるようだ．

## Dockerfile作成からイメージ作成まで
### Dockerfileとは？
Dockerfileはコンテナを作るうえで必要なイメージを作成する．
**コンテナを製品，イメージを鋳型と表現するならば，Dockerfileはその鋳型を作る設計図のようなもの**である．



```Dockerfile:Dockerfileの簡単な説明
FROM <ベースとするイメージファイル>:タグ

WORKDIR <ワークディレクトリを指定できる（shellに入ったときに始めにいるディレクトリ）>

COPY <ホスト側にあるファイルなど> <Docker内における移動先>

ENV <環境変数名> <変数に指定するもの（パスや値）>

RUN apt-get update && apt-get upgrade -y
RUN apt-get install <パッケージ> -y

CMD ["docker runしたときに実行するコマンド"]
```

他にも指定できるものはあるかもしれないが，現状，私が作成しているDockerfileはこれらで事足りている．必要に応じてまた調べると良いと思っている．

Dockerのイメージは他のイメージの上にどんどん作っていくことでより効率的に自分好みのイメージを作成していく．したがって，何をつくりたいのかと考えた時に一番近そうなものがないか，DockerHubを探索するのもおもろいかもしれない．

https://hub.docker.com/search?type=image

### Dockerfileの作成例
ここで，Dockerfileの作成例として，noetic-desktop-full（ubuntu20.04上
にROSが構築されている）をベースとして，自分のワークスペースを適用できるところまで完了させるイメージファイルを作成するDockerfileを作ってみる．（毎回ワークスペースを作るのも面倒であるため．）

```Dockerfile:Dockerfile
FROM osrf/ros:noetic-desktop-full

WORKDIR /root/

RUN echo "source /opt/ros/noetic/setup.sh" >> .bashrc
RUN mkdir -p catkin_ws/src
RUN cd catkin_ws/src && . /opt/ros/noetic/setup.sh && catkin_init_workspace
RUN cd && cd catkin_ws && . /opt/ros/noetic/setup.sh && catkin_make
RUN echo "source ./catkin_ws/devel/setup.bash" >> .bashrc
```

ここでの気づきとしては，

```
. /opt/ros/noetic/setup.sh
```
がないと，``catkin_init_workspace``がないと言われる．``catkin_make``についても同様．

ここまででとりあえず，Dockerfile作成について整理することができた．

### イメージファイルの作成
ビルドすることでイメージファイルを生成できる．
```
docker build -t <イメージファイル名>:<タグ名> .
```

Dockerfileの内容にもよるが，少し時間がかかる．

### コンテナ操作

##### 組み立て

```
docker run --name <コンテナ名> <イメージファイル名>:<タグ名>
```
 

- 組み立て時には，いろんなオプションがあるようだ．主に使っているのは以下の4つ
    - **-name**
        - ```docker run --name <コンテナ名> <イメージファイル名>:<タグ名>```とすることで，名前を付与したコンテナを作成できる．
    - **-it**
        - ```docker run -it <イメージファイル名>:<タグ名> /bin/bash```とすることで，コンテナ内のターミナルに入ることができる．itはinteractiveオプションのようだ．
    - **-p**
        - ```docker run -p 6080:80 <イメージファイル名>:<タグ名>```とすることで，80ポートをlocalhostの6080ポートに接続できる．つまりは，localhost:6080と適当なブラウザに打ち込めば，コンテナサーバにアクセスできる．
    - **-v**
        - ```docker run -v ./share <イメージファイル名>:<タグ名>```とすることで，ホスト側にあるshareフォルダをdockerにマウントして，ホストとdocker間でファイルの共有が可能になる．

##### 廃棄

```
docker rm <コンテナ名>
```

## 詰まったこと 

### 背景
- 機械学習系をdocker上でするにあたって，ホストのGPUを活用したい．
- ROSの環境（Rvizなど）やmatplotlibなどを含めたGUIをdocker上でも使いたい．

### DockerでホストのGPUを活用する方法
結論から言うと，以下のようなDockerfileと実行をすることで可能になる．

```Dockerfile:Dockerfile
FROM nvidia/cuda:11.0-devel-ubuntu20.04

RUN apt-get update
RUN apt-get install -y python3 python3-pip
RUN pip3 install torch torchvision

WORKDIR /work

COPY train.py /work/

ENV LIBRARY_PATH /usr/local/cuda/lib64/stubs
```

nvidiaが提供するイメージファイルをベースにPyTorchを構築している．
肝としては，ENVでLIBRARY_PATHという環境変数にcudaのパスを通す部分である．
しかしながら，これだけではうまくいかない．

コンテナ組み立て時に以下のようにする必要がある．

```terminal:docker_run
docker run -it --runtime=nvidia --name pytorch_ubuntu2004_cuda pytorch_ubuntu2004_cuda:latest /bin/bash
```

もう一つの肝は **--runtime=nvidia**である．``--gpus=all``とするというような記事もよく見かけたが，私のUbuntu環境ではうまくいかなかった．ただし，Windows環境では，むしろ **``--gpus=all``** の方でないとうまくいかなかった．

参考

https://qiita.com/conankonnako/items/787b69cd8cbfe7d7cb88

### DockerでGUIを有効にする方法
GUIはホストでのGUIを扱う部分（Xserver）とdocker間でやり取りできるようにする必要がある．
色々とコンテナ組み立て時に指定する必要がある．
また，ターミナルコマンドもいくつか打つ必要があるため，シェルスクリプトが紹介されており，
それを以下に示す．

```shell:run.sh
# If not working, first do: sudo rm -rf /tmp/.docker.xauth
# It still not working, try running the script as root.
	
xhost +
XAUTH=/tmp/.docker.xauth
if [ ! -f $XAUTH ]
then
    xauth_list=$(xauth nlist :0 | sed -e 's/^..../ffff/')
    if [ ! -z "$xauth_list" ]
    then
        echo $xauth_list | xauth -f $XAUTH nmerge -
    else
        touch $XAUTH
    fi
    chmod a+r $XAUTH
fi

docker run -it \
    --env="DISPLAY=$DISPLAY" \
    --env="QT_X11_NO_MITSHM=1" \
    --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
    --env="XAUTHORITY=$XAUTH" \
    --volume="$XAUTH:$XAUTH" \
    --runtime=nvidia \
    --name=ros1_noetic \
    ros1_noetic \
    bash
echo "done"
```

```xhost +```が非常に大きな部分で，ホストのxserverに関わる権限をすべて解放するコマンドであるため，使い終わったときには，```xhost -```で権限を閉じたほうが良いと思う．

上記のようなシェルスクリプトをつくれば，コンテナ組み立ては以下のとおりにするだけである．

```terminal:
./ run.sh
```

#### Windows上でGUIを有効にするために必要なXserverについて
ubuntu上であれば，先ほどの手順でRvizなどは動くが，windows上では，```xhost```のような操作はできない．というのも，別でXserverを用意する必要があるようだ．

powershell上で以下のコマンドで，VcXsrvというXserverを提供するソフトをインストール．

```
winget install marha.VcXsrv
```

あとは，docker側で，必要なものをそろえる．

```Dockerfile:Dockerfile
FROM osrf/ros:noetic-desktop-full

WORKDIR /root/

ENV DISPLAY host.docker.internal:0.0

RUN apt-get update -y && apt-get upgrade -y
RUN apt-get install x11-apps -y
```

注目してほしいのは，ENVと最後のRUNである．

ENVでは，環境変数DISPLAYにxserverとつながるように指定している．0はディスプレイ番号を表す．

さらにRUNでは，x11-appsというソフトをインストールしている．詳しくは分からないが，これで，xserverとのやりとりが行えるようだ．


これで準備は整った．注意点としては，GUIを使いたいときには，ホスト側で先ほどインストールしたXserverを立ち上げておく必要がある．いたって簡単で，XLaunchというものを起動するだけである．特に設定とかはいじらずに指示に従っていけば，すぐに立ち上がる．

以下に実行例を示す．

<iframe width="560" height="315" src="https://www.youtube.com/embed/yG0CNRqXl8E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

動画からも分かるように，Xserverを用意していなければ，エラーが出てGUIを扱うことができない．適用後，すぐに使えていることが分かる．動画にはないが，matplotlibなどのグラフに関しても同様に使えることは確認している．ただし，```python3-tk```というパッケージが必要にはなる．

```Dockerfile:追加分
RUN apt-get install python-tk -y
```

## 感想
ようやくDockerを少し扱えるようになったため，まとめてみた．時間が非常にかかるため，今回の記事は文字だらけとなってしまった．ただ，時間をおいた時にまた同じことで詰まったりしないようにするための備忘録としておく．実は，まだDockerHubへアップしたことはないため，それも今後やっていこうと思う．ちなみに，今回，Dockerを学んでいく中で，同じようなコマンド，しかも長いもの，を打つ機会が非常に多く，Makefileやshellスクリプトのありがたみを感じた．特にMakefileはあまり扱ってこなかったが，少し勉強するよい機会となった．もはやなくてはならない存在とも感じられる．今はDockerで機械学習の環境を構築したため，機械学習を勉強中．またまとめられそうならまとめていきたい．ハードウェアをまとめていかなければ．

## 参考
- 「仕組みと使い方がわかるDocker&Kubernetesのきほんのきほん」 
    　　　　　　　　　　　　　　　　　　　　　小笠原 種高 著, 東京 : マイナビ出版, 2021.1

https://www.youtube.com/watch?v=Utvf4YmMJpk

https://www.youtube.com/watch?v=lZD1MIHwMBY&t=8863s