サーバーサイドエンジニアに知っておいてほしい文脈ごとのdockerコマンドとトラブルシューティング

# これはなに
- docker自体は知っているし，「なんとなく起動と停止は使えるけどそれ以上はあまりやったことない」という人向け
- これらを抑えておけば普段の開発作業はもちろん，サーバー作業もおおよそ大丈夫でしょう！というのを想定作業ごとにまとめた

# 前提
- シェルスクリプトの構文がある程度頭に入っている
- dockerの環境整備が済んでいる
- dockerの基礎的な単語を抑えている
    - dockerにおけるイメージ，コンテナの区別がついている
    - buildをやったことはある

# 想定している作業
- dockerイメージの構築
    - Dockerfileの作成および最適化
    - CIでのdocker build
- 複数コンテナを利用したローカルでの環境の構築
    - docker-compose.ymlの作成
- サーバー作業
    - dockerコンテナのリソース利用率の確認
    - コンテナに入って諸々の調査

それでは個別にみていきましょう．

# 作業ごとの解説
## dockerイメージの構築作業
### Dockerfileの作成および最適化
ここで想定しているのは，Dockerfileを編集しつつ，`docker build`と`docker run`を繰り返すような作業
使うコマンドとしては主に以下になる

```
# build
docker build .
docker build -f /path/to/Dockerfile .
docker build --build-arg HOGE=hoge
docker build --no-cache

# debugで使う
docker run $IMAGE_ID
docker run -it $IMAGE_ID sh
docker exec -it $CONTAINER_ID sh

# モリモリストレージ容量が食われるので適当なタイミングで片付ける
docker images
docker images -a
docker images -aq
docker rmi -f $(docker images -aq)
docker system prune
```

#### Dockerfileの作成
- docker buildで抑えておくべきなのは，コンテキストという概念である
    - `docker build .`の最後の`.`が，コンテキスト．この場合カレントディレクトリを指定している．
    - 一言でいえば，Dockerfileの実行はどのディレクトリで行われているとみなすのか？ということ．
    - Dockerfileの配置場所と，コンテキストがずれる場合は理解必須．
    - 参考: [Dockerのビルドコンテキスト(build context)について確認したときのメモ](https://qiita.com/toshihirock/items/c85f3eb5f4752b15ca3d)
- docker build自体が失敗するとき
    - docker buildでは`RUN`コマンドひとつ一つに対してimageが作成されている．
        - 2021/03/18 追記:        
            - バージョンが上がって，デフォルトのbuildコマンドの出力が変わっている．
            - この手法をそのまま使うにはこれ `DOCKER_BUILDKIT=0 docker build .`
            - ref. https://devops.stackexchange.com/questions/12771/no-longer-see-docker-build-output-properly
    - エラー直前に出力されたimage idに対して，`docker run`して，失敗したコマンドを実行＆ログ確認して解決していく．
- docker buildは成功するが，docker runが失敗するとき
    - image idに対して，`docker run -it $IMAGE_ID sh`で起動してからコマンドを実行してエラーを確認する．
    - ENTRYPOINTを指定しているときは，`docker run -it --entrypoint='' $IMAGE_ID sh`としてentrypointを上書きすれば入れる．

#### Dockerfileの最適化
- `--build-arg VAR=var`というオプションはDockerfileのARG構文と同時に理解すべき．
    - 基本的に引数をDockerfileに渡せる，というだけ．それだけではdocker imageの中には記録されないので，注意．
    - 記録したい場合は`ENV`構文を利用する．
    - ARGで気を付けるべきは，FROMとの位置関係．FROMよりあとに書かないと，うまく動かなくてハマる．
    - 参考: [Docker ARG vs ENV](https://vsupalov.com/docker-arg-vs-env/)
- イメージサイズの削減にはmulti stage buildが効く
    - わかっている人向けに端的にいえば，FROM句が二度でてきて，それぞれbuild時のイメージと実行時イメージに対応しており，buildが完了したら，実行時に必要なものだけ，実行時イメージにCOPYする手法．
    - サービスやデプロイの規模によるが，イメージが1GBを超えないくらいなら優先度はまったく高くない．
- もしやるときには[dive](https://github.com/wagoodman/dive)というコマンドが便利．
    - `brew install dive`

### CIでのdocker build
ここでのコマンドはこれ

```
docker pull $PREV_IMAGE_ID
docker build --cache-from $PREV_IMAGE_ID .
```

- CIでのポイントはとてもシンプル．buildキャッシュを効かせるだけ！
    - ローカル環境でのbuildとは違って，CI環境ではなにもしないとキャッシュが基本効かない．
    - ただし，通信コストが発生するので依存を一からダウンロードするコストとの比較にはなる．


## 複数コンテナを利用したローカルでの環境の構築
### docker-compose.ymlの作成
ここでよく使うコマンドは以下の通り

```
docker-compose up
docker-compose up --build
docker-compose down
docker-compose restart
docker-compose exec [service] sh
```

- ymlの細かい解説はしない．[公式doc](https://docs.docker.com/compose/)を読むべし．
    - ローカル環境構築のポイントは下記．これらの項目を理解すればだいたいやりたいことはやれるはず．
        - ポート転送設定(`posts:`)
        - コンテナとの共有ストレージの設定(`volumes:`)
        - コンテナ間ネットワークの設定（`links:`と`depends_on:`）
- コンテナ間の通信ができないとき
    - コンテナ間はサービス名(`services:`配下のkey)をホスト名として指定すれば通信できる
    - もちろん，ポートをEXPOSEなどしていることなどは確認が必要
- 環境変数を渡したい
    - `environments:`での定義はもちろん，コミットしたくないような秘匿情報も，`.env`形式のファイルを渡すことで解決できる！便利！
- 変更が反映されない
    - ymlファイル自体を更新したときは，`docker-compose down`もしくは`docker-compose restart`をおこなったほうがよい．
    - dockerにはイメージやコンテナとは別にネットワークの設定があり，一度落とさないと反映されない．

## サーバー作業
基本はこちら

```
# 稼働コンテナ一覧表示
docker container ls
docker ps
docker ps -a

# 稼働中コンテナへ入る
docker exec -it $CONTAINER_ID sh

# 稼働コンテナの強制停止
docker kill $CONTAINER_ID
docker rm -f $(docker ps -aq)
```

### dockerコンテナのリソース利用率の確認
```
docker stats
```

### dockerコンテナでの作業後のファイル取得
地味に便利．

```
docker cp $CONTAINER_ID:/path/from/root/ ./
```