ヾノ*>ㅅ<)ノシ帳

ノンジャンルのふりーだむなブログです。

研究生活に役立つDocker入門

f:id:katc:20171031062207p:plain

※走り書きです。

Dockerとは?+注意書き

Linux限定で書いてしまうが、WindowsmacOSでも利用可能

超軽量のコンテナ型仮想化を実現するOSSソフトウェア。VMのイメージで扱うと大変危険。 コンテナはシステムの資源を間借できる高機能chroot環境と思うのが吉。

Dockerでは、Linuxカーネルをホストとコンテナで共有する。これはコンテナがLinuxカーネルの機能で実現されているからである。 コンテナの中から見えるファイルシステムは厳密に独立していないので、/bootとかは触ってはだめ(/etcはOK)。 systemd(pid 1系)は使えないので気をつけて。 先述の注意点がクリティカルな場合は素直にVagrantなりVirtualBoxなり使うのが賢明。

Dockerがなぜ研究生活向けなのか?

研究室で受け継がれるVMイメージはときに邪悪である。 OSのインストールは良いとして、その後に何をどうビルドしてインストールしたのかが全く明らかではない。 別にドキュメントを用意することが必要になってしまう。 また起動と実行のためにCPUとメモリを多く奪われてしまうのは全くイケてない。

Dockerを使って研究を進められる環境を作ると次のようなメリットがある。

  • Dockerfile(後述)がそのまま環境構築手順書になる
  • 起動が速く、ホストとシームレスにつながる(※ホストPCがLinuxの場合に限る)
  • Dockerfileをgitなどでバージョン管理すると、いついつのときのコンテナを起動することができる(※image(後述)をビルドする時に細工が必要)
    • システム変更を記録に残す癖がついて良い
  • コンテナ起動後にした変更が破棄されるので、同じコンテナを使っている限り、システム側が原因で「前まで使えたのになぜか使えなくなった」が起こり得ない
    • 試しにいじって上手く行ったらDockerfileに反映するアプローチも良し

ただし、以下のデメリットもあるので注意が必要。

  • ウインドウマネージャの恩恵を受けられない(が、GUIアプリの画面を出すことは可能)
  • 複数のターミナルを出すのがサーバーと同じくらい少し大変(が、こんなのは慣れの問題)
  • Dockerfileを変更してコンテナを更新するまでに大変時間がかかることがある(変更方法に依存するが、覚悟が必要)
  • Dockerfile作成にあまりネットで言及されないコツが必要(後述)

インストール方法

Ubuntuは以下の記事で良いと思われる。 [Docker] ubuntu 14.04/16.04にDockerをインストール - Qiita

Arch Linuxの場合は、

sudo pacman -S docker
### 自分にdockerグループを追加
sudo usermod -aG docker `whoami`
### dockerを自動起動
sudo systemctl enable docker

ここで完了させておくことは、

  • dockerのインストール
  • ユーザーにdockerグループを追加
  • dockerデーモンの有効化(ないしは sudo docker daemonLinuxが起動する度に手動実行)

である。

コンテナ起動

コンテナ起動時に実行するプログラムを指定する。通常はbashを起動する。 後述するDockerfileにCMDとして起動するプログラムを明示していないケース/誤ったCMDの指定がされることがあるので気をつけること。 Dockerfileを信頼せず、コマンドラインで明示するのが安全である。

コンテナ起動のコマンドは次の通り:

### ubuntu 14.04の場合
### オプションは "run it" で覚えるのがおすすめ
docker run -it ubuntu:14.04 bash
#### runの前に `docker pull ubuntu:14.04` が必要かも
### プロセス終了時にコンテナを破棄したいとき(--rm)
### オプションは "run, remove it" で覚えるのがおすすめ
docker run --rm -it ubuntu:14.04 bash

動作確認のために上記コマンドを実行してUbuntubashシェルが出ることを確認してほしい。 ネットワークからimageがダウンロードされるので初回の起動は時間がかかる。 imageとはコンテナを起動する種になるものである。 これは次節で説明する。

高度なコンテナ起動オプションとDockerあるあるについては後半で述べる。

imageの作成

imageはコンテナ起動の元になる「種」のようなものである。 imageの作成は2通りの方法がある。

Dockerhub   Dockerfile
   |            |
  pull        build
   |            |
 [      image      ]
          |
         run
          |
 [    container    ]

1つ目はDockerhubからpullする方法である。あまり使わないので説明は省略。気になる人は「docker pull dockerhub」でググって。

2つ目はDockerfileからbuildする方法である。 Dockerfileはこの名前で存在するファイルである。 Dockerfileがあるディレクトリまで移動(cd)して、

docker build -t IMAGE_NAME . # IMAGE_NAME must be lowercase
### or
docker build -t IMAGE_NAME -f DOCKERFILE_PATH
### without cache
docker build -t IMAGE_NAME . --no-cache=true

のどれかの形式で実行すると良い。IMAGE_NAMEは制約があるので小文字でいい感じに付けると良い。 buildはキャッシュを使ってされる。apt updateで不都合が起こる時はこのオプションを付けるとよい。

Dockerfileの構成

大抵の場合上の行から、

  • FROM(必ず1番上に書くこと)
  • MAINTANER(省略可)
  • RUN または ADD または ENV または WORKDIR または USER(これが複数行続く)
  • CMD または ENTRYPOINT (省略可;1行のみ)

の順に書かれている。

FROM

FROMはベースとなるimageを指定する。何も考えずに

FROM ubuntu:16.04

FROM ubuntu:14.04

と書くといい。

MAINTANER

メンテナーの情報を書く欄。自由に

RUN

ベースのimageで実行するコマンドを書く。複数行に分けたい時は行末に「半角スペース+バックスラッシュ」

例えば、

RUN sed -i.bak -e "s%http://archive.ubuntu.com/ubuntu/%http://ftp.iij.ad.jp/pub/linux/ubuntu/archive/%g" /etc/apt/sources.list && \
    apt update && \
    apt install -y git automake pkg-config libssl-dev
RUN apt install -y curl wget bash-completion net-tools strace silversearcher-ag mlocate vim 

シェルスクリプトの要領でガリガリ書いていく。コマンドはroot権限で実行されるのでsudoは付けなくて良い。 ちなみにRUNごとにキャッシュが生成される。

1つ前のRUNでcdして、次のRUNでそこの場所でコマンドを実行はできない。RUNごとにカレントディレクトリがリセットする。 cdのし忘れには気をつけよう。面倒な時は後述のWORKDIRが便利。

ADD

外部ファイルをimageに追加するコマンド。Dockerfileから見た相対パスで書く。 パッチファイルを取り込みたいときに便利。

ADD ./my-awesome.patch /home/docker
ADD ./my-awesome.patch $PATCH_FILE_DIR

面倒でなければENVで設定した環境変数を使うようにしてください

ENV

docker buildで有効な環境変数を設定するコマンド。$HOMEを変更するときはWORKDIRを使わないとだめ。 以下例。

ENV PATCH_FILE_DIR /home/docker/projects/qemu/patches

WORKDIR

カレントディレクトリを変更するコマンド。 以下例。

WORKDIR /home/docker/projects

これをするとその後のRUNから/home/docker/projectsがカレントディレクトリになる。

USER

コマンドの実行主を変更するコマンド。このときユーザーが自動的に作成されるかは知りません。

USER docker

CMD, ENTRYPOINT

両方は使えなかったはず。複数回もできなかったはず。 コンテナが起動したときに自動実行するコマンドを指定できる。 どっちかが、コマンドラインで指定したコマンドに置き換えられるはず。

Dockerfileのトラブルシューティングのコツ

  • 前もって別の環境で実行して、このコマンドで上手くいくことを確認する
  • apt updateを冒頭で必ずする
  • ファイルがあることを確認するために RUN ls * する
  • buildメッセージに載っているキャッシュされたimageのIDを控える→ docker run --rm -it CACHE_IMAGE_ID してインタラクティブに確認【一番オススメ】
  • RUNの単位を細かくする。&&で頑張って繋げない
  • RUNで複数実行するときは ; でなく && でコマンドをつなげる

日頃気をつけること

  • システムに残したい変更をしたら、そのときに実行したコマンドをDockerfileに残すのを忘れないこと。

高度なdocker run

privillegedオプション

Dockerのコンテナは触れるシステム資源が制限されている。 USBはこれに該当する。USBを使えるようにする前提で説明する。

まず Dockerfile 後半に以下の行を追記する必要がある。

VOLUME /dev/bus/usb:/dev/bus/usb

これは、ホストとコンテナの間でUSBを共有する設定。

privilegedモードでの起動は

docker run --rm -it --privileged IMAGE_NAME[:tag] bash 

特権が付いているかどうかは次のように確認できる。

コンテナの外から:

docker inspect --format='{{.HostConfig.Privileged}}' CONTAINER_ID

コンテナの中から:

ip link add dummy0 type dummy

デタッチしたコンテナを復活させる

--rmを付けずにdocker runした場合のみ。

docker run で起動したbashをexitしたとき、コンテナはdocker ps(※起動中のコンテナを確認するコマンド)には表示されなくなる。 次の手順でコンテナIDを取得し、re-attachすることができる。

### check container name or ID
docker ps -a
### wakeup contianer with its id or its name
docker start CONTAINER_ID/CONTAINER_NAME
### you can check your container restarted
docker ps
### now you can re-attach to it
docker attach CONTAINER_ID/CONTAINER_NAME
# hit ENTER

docker attachしたターミナルで、アタッチ後にEnterを押さないとプロンプトが返らないことがある。

ショート版の手順がこちら:

docker ps -a
docker attach `docker start CONTAINER_ID/CONTAINER_NAME`

tmux

シェルが1つしかないのは不便なので、tmuxのようなターミナルマルチプレクサを使うとシェルをいくつも開けて便利。

VirtualBoxみたいにファイル共有(フォルダ共有)したい

起動時に「-v マウント元(ホスト上のパス):マウント先(コンテナ上のパス)」のオプションを与える。 -v A:B -v C:D の感じで複数並べることも可能

### read-write mode
docker -it -v VOLUME_NAME:/home/guest
docker -it -v `pwd`/HOST_DIR_NAME:/home/guest
### read-only mode
docker -it -v VOLUME_NAME:/home/guest:ro
docker -it -v `pwd`/HOST_DIR_NAME:/home/guest:ro

高度なdocker build

docker build を一々叩くのは面倒なので ./build.sh なり、ビルド用のシェルスクリプトを用意すると便利である。 docker buildに成功したらタグを付けるようにしたほうがいい。タグ名はちゃんと管理できる自信があるならバージョン番号でも良いし、 gitのコミットハッシュでもよい。後者を前提とした build.sh のテンプレートが以下:

count_x_files()
{
    COUNT=$(git status --porcelain | grep -v "/patch/" | grep $1 | wc -l)
    echo $COUNT # return $COUNT
}

IMAGE="my-ubuntu"

docker build -t $IMAGE . || (echo "[!] ERROR: docker build. exit" ; exit)
echo "[*] DONE: docker build"

if [ $(count_x_files "MM") -ne "0" ]; then
    echo "[!] There's not staged changes. git add it now"; exit
fi
if [ $(count_x_files "??") -ne "0" ]; then
    echo "[!] There's untracked patch files. git add it now"; exit
fi
docker tag $IMAGE $IMAGE:$(git rev-parse --short HEAD)
echo "[*] DONE: docker tag"

このテンプレはgit add -u && git commit のし忘れを指摘してくれる。

Dockerあるあると小ネタ

コンテナーのIDを知りたい

docker ps 

(docker buildで作成された)特定のコンテナのログを見たい

docker log CONTAINER_ID

ID は docker build のログ Running in dc043f01ca19 から dc043f01ca19 を拾ってくる

imageの一覧を見たい

docker images

イメージを消したい

### remove specific image
docker rmi IMAGE_NAME [IMAGE_NAME ...]
### force remove 
docker rmi -f IMAGE_NAME [IMAGE_NAME ...]

イメージ名を改名したい

### copy
docker tag OLD_IMAGE_NAME OLD_IMAGE_NAME
### and remove
docker rmi OLD_IMAGE_NAME

停止したコンテナだけ一覧で出したい

docker ps --filter "status=exited"

使ってないコンテナ(exitしたコンテナ)を消したい

docker rm `docker ps --filter "status=exited" -q`
### or
docker rm $(docker ps -a -q) 

タグがついてないimageを消したい (like <none>:<none>)

docker rmi $(docker images | egrep "^<none>" | awk '{print $3}')