運用ツールとして Docker を使う

こんにちは．

皆さんは，Docker を使ってどんなことをしていますか？
開発環境の構築，おうちサーバで遊ぶ，本番環境でアプリをバリバリ動かす．．．etc
今回，「使ってほしいコマンドラインツールを配布する」という目的で Docker を使ってみました．

## 背景

今じぶんが働いているところでは，開発環境とプロダクション環境どちらも GCP + Kubernetes (GKE) で動作しています．
開発環境においては，サーバサイドエンジニアは

1. ローカルの Docker を用いて Image を Build して，
2. Google Container Registry にアップロードして，
3. kubernetes-helm を用いてデプロイする
というサイクルを回しています．

一連の動作は，スクリプトでラップすることで，クラウドインフラに明るくない開発者でも安全に開発をすることを可能としています．いわゆるデプロイスクリプトですね．

このデプロイスクリプト，内部でいくつものコマンドを必要とします．

- gcloud
- kubectl
- helm
- stern
- etc

特に GCP の CLI である `gcloud` や，Kubernetes の CLI である `kubectl` などは，
バージョンによる差分や破壊的変更が多く，できればインフラ管理者が一括で管理したい代物です．

これらを Docker Image にまとめて配布することにより，バージョン差異を無くすことができるのではないか？と考えました．

## 試してみた

以下が試してみたリポジトリになります．
https://github.com/takutakahashi/deploy-tools

dockerhub にも `takutakahashi/deploy-tools` としてイメージを上げてあります．

## 動作原理
ファイルの全体像を図解したものが以下の絵です．
![pic1.png](https://qiita-image-store.s3.amazonaws.com/0/387167/9b1d2901-0964-bbd2-f7c3-ecd20958f538.png)

1. エンジニアは，ローカルにあるコマンドを実行します．
2. ローカルのコマンドは，リポジトリ上の`exec_cmd`を経由して実行されます．
3. コマンドは，各種コマンドが実装され，コンフィグフォルダをマウントしたコンテナ上で実行されます．

コンフィグファイルをマウントすることにより，
`gcloud` のログイン情報や `.kube/config` など，各種APIにアクセスするための情報を永続化することが可能です．

### .deploy/bin/exec_cmd の中身
中でコンテナを起動し(すでにある場合は失敗させ)，`exec` でコマンドを実行します．
コンテナ自体は `sleep` を無限ループで実行させ，起動した状態を維持させます．
`run` を無限に実行しているとゴミが溜まるのと，
処理待ち時間が許容できなかったので `exec` させています．

```
==> .deploy/bin/exec_cmd <==
#!/bin/bash
docker run --name $APPLICATION -itd \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v $PROJECT_ROOT/.direnv:/root/.config \
  -v $PROJECT_ROOT/.direnv/kube:/root/.kube \
  -v $PROJECT_ROOT/.direnv/helm:/root/.helm \
  -v $PROJECT_ROOT/.direnv/docker:/root/.docker \
  $ADDITIONAL_OPTS \
  $IMAGE 1>/dev/null 2>/dev/null
docker exec -it $APPLICATION $@
```

### .deploy/bin/[cmd] の中身
各種コマンドのフリをするシェルスクリプトになっています．
中身はシンプルに，実行されたコマンド名とオプションをそのまま `exec_cmd` の引数に与えるだけです．

```
% tail .deploy/bin/*
==> .deploy/bin/gcloud <==
#!/bin/bash

exec_cmd `basename $0` $@

==> .deploy/bin/helm <==
#!/bin/bash

exec_cmd `basename $0` $@

==> .deploy/bin/kube-prompt <==
#!/bin/bash

exec_cmd `basename $0` $@

==> .deploy/bin/kubectl <==
#!/bin/bash

exec_cmd `basename $0` $@

==> .deploy/bin/stern <==
#!/bin/bash

exec_cmd `basename $0` $@

```

## プロジェクトに適用する
実際に開発しているアプリケーションリポジトリに適用してみます．
python + bottle で開発しているアプリケーションを想定します．
`README.md` に記載のコマンドを実行すると，スクリプトは以下のディレクトリ構造を構築します．

```
$tree .
.
├── .deploy
│   ├── Dockerfile
│   ├── bin
│   │   ├── exec_cmd
│   │   ├── gcloud
│   │   ├── helm
│   │   ├── kube-prompt
│   │   ├── kubectl
│   │   └── stern
│   ├── init_project.sh
│   └── start.sh
├── .envrc
├── app.py
└── requirements.txt
```

初期構築スクリプトが行う処理は以下となります．

- `.deploy` という名称で，deploy-tools を git submodule として追加します．
- `.envrc` の中身をプロジェクトルートの同ファイルに追記します．
    - `.envrc` は，direnv コマンドの env ファイルです．この仕組は direnv に依存します．

```.envrc
export PROJECT_ROOT=$PWD #プロジェクトルートのパス
export IMAGE="takutakahashi/deploy-tools:latest" # docker container の image
export PATH=$PWD/.deploy/bin:$PATH # 上図 ① のコマンドパス
export APPLICATION=k8s-ingress-exporter # アプリケーション名 (コンテナ名に使用)
```

- 各種コマンドが `.deploy/bin/[cmd]` へと置き換わります．


## 実際やってみてどうだったか？

この方式を実用した際に気づいたメリット・デメリットはいくつかありました．

### ○ プロジェクトごとに認証情報を分離できる

各種認証情報はプロジェクトルート内部に格納され，別プロジェクトの認証情報と分離されます．
プロジェクトAのクラスタに間違えてプロジェクトBをデプロイした！といったことを原理的に防ぐことができます．

### ○ 初期構築がほぼゼロ & 構築手順のゆらぎゼロ

新メンバーがジョインした際に設定することは，docker と direnv をインストールすることのみです．
各種コマンドのインストール方法の違いによるトラブルシュートの煩雑さから開放されます．


### ✗ shell 補完が効かない
`kubectl completion` で設定した補完が効きません．
対応のために `kube-prompt` を同梱しましたが，人によっては作業に影響がありそうです．．．

## まとめ
「環境の配布」という観点で，Docker を利用することはとても有用だと思いました．
それは，開発環境，オペレーション環境，様々な環境に適用できると思いました．
ぜひ使ってみてください．