こんにちは.
皆さんは,Docker を使ってどんなことをしていますか?
開発環境の構築,おうちサーバで遊ぶ,本番環境でアプリをバリバリ動かす...etc
今回,「使ってほしいコマンドラインツールを配布する」という目的で Docker を使ってみました.
背景
今じぶんが働いているところでは,開発環境とプロダクション環境どちらも GCP + Kubernetes (GKE) で動作しています.
開発環境においては,サーバサイドエンジニアは
- ローカルの Docker を用いて Image を Build して,
- Google Container Registry にアップロードして,
- 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
としてイメージを上げてあります.
動作原理
- エンジニアは,ローカルにあるコマンドを実行します.
- ローカルのコマンドは,リポジトリ上の
exec_cmd
を経由して実行されます. - コマンドは,各種コマンドが実装され,コンフィグフォルダをマウントしたコンテナ上で実行されます.
コンフィグファイルをマウントすることにより,
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 に依存します.
-
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 を利用することはとても有用だと思いました.
それは,開発環境,オペレーション環境,様々な環境に適用できると思いました.
ぜひ使ってみてください.