こんにちは。LAPRASでテクニカルサポートを担当しているdenzowです。
この記事は、LAPRAS Advent Calendar 2020 5日目の記事です。1週間以内の遅刻は遅刻じゃないのでセーフです。
前置き
LAPRASではDjangoを使用してサービスを開発しています。Djangoにはmanagements
配下にCommandクラス
を継承したクラスを定義することで独自のコマンドを実装することができます。
$ python manage.py hogehoge_command
LAPRASでも当然、多くのコマンドが実装されておりバッチ処理や有事の際のオペレーション等に利用されています。現在は独自に実装したコマンドが70程あります。
一方で、できるだけ避けたいことですが日々リリースされ続けているため、意図しない挙動でお客様にご迷惑をおかけしてしまいデータの修正等のオペレーションが発生することがあります。
これらを対処するときには通常、アプリケーションが稼働しているpodにログインしmanage.py shell
でDjangoShellを起動した上で手作業をします。しかし、修正までに時間がかかる場合はこれらの処理を簡略化、あるいは誰でも出来るように共通化したい要望がでてきます。
それってコマンドじゃないの
たしかに現在やっている処理をDjangoのコマンドとして定義できれば一番ですが、実際は難しいことが多いです。
- 今すぐ必要な処理であることが多くリリースまで待てない
- リリース後にメインコードの変更によりそのまま使うことができない可能性が高い
- そもそもバグ修正により不要になるコマンドであるためライフサイクルも短い
このような問題があります。そのため、理想としては
- メインコードとは独立した、より早いリリースサイクル
- 書き換えが容易
- 他のメンバーも共有できる
これらを満たす方法が必要でした。
で、どうしたか
メインコードとは別のsupport-commands
というリポジトリを用意しました。以下はディレクトリ構成の一部抜粋です。
support-commands
└── bin
├── .common
│ └── scout.sh -- podログインのための環境情報等
├── .nanika_syori
│ └── script.py -- 実際に行う処理内容
├── scoutshell -- kubectl経由でmanage.py shellに処理を流し込むスクリプト
└── nanika_syori -- 実際に行いたい処理のエントリーポイント
LAPRASではアプリケーションの実行基盤にk8sを使用しており、テクニカルサポートを行うメンバーはいずれもpodにログインする権限を持っています。そこでkubectl
の標準入力経由でスクリプトをmanage.py shell
に流し込む方針を録りました。direnv
を利用しているなら.envrc
にexport PATH=$PATH:$(pwd)/bin
を書いておけば、このリポジトリ配下に移動すると以下の様にそのままコマンドを実行することができます。
$ nanika_syori 12345
もう少し詳細
scout.sh
scout.shは作業を行うpodを選択するためのシェルスクリプトです。
#!/bin/bash
POD_NAMESPACE="k8sのネームスペース"
SCOUT_POD_PREFIX='podのprefix'
MANAGE='poetry run ./manage.py'
SHELL_PLUS='poetry run ./manage.py shell_plus'
POD_NAME=$(...miserarenaiyo...)
if [[ -z "${POD_NAME}" ]]; then
echo '[ERROR] POD not found.'
exit 2
fi
scoutshell
#!/bin/bash
SCRIPT_DIR=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd)
source ${SCRIPT_DIR}/.common/scout.sh
echo "EXEC: kubectl exec -n ${POD_NAMESPACE} -it ${POD_NAME} ${SHELL_PLUS}"
envsubst < $1 | kubectl exec -n ${POD_NAMESPACE} -it ${POD_NAME} ${SHELL_PLUS}
引数で渡されたファイルにenvsubst
を使って一部書き換えた上でmanage.py shell
に流し込みます。envsubst
を使っているのは、処理に与える引数が必要な場合に動的に書き換える必要があるためです。
nanika_syori
これはサンプルなので実際には違いますが、大抵このような処理になっています。
#!/bin/bash
SCRIPT_DIR=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd)
SCRIPT_NAME=`basename "$0"`
export ARG1=$1
if [[ -z "${ARG1}" ]]; then
echo '[ERROR] ARG1 not found.'
exit 2
fi
scoutshell ${SCRIPT_DIR}/.${SCRIPT_NAME}/script.py
引数をARG1
といった変数に詰め替えとチェックを行い、実際の処理がかかれたscript.py
を先程のscoutshell
に渡して起動します。script.py
をこのスクリプトに内包させることもできますが管理画面道なので分ける形式を取っています。
.${SCRIPT_NAME}/script.py
hoge_id = ${ARG1}
print(f'HELLO {hoge_id}')
処理内容はあくまで一例ですが、${ARG1}
は最終的にscoutshell
のenvsubst
によって置換されてからmanage.py shell
に流し込まれます。
以下の様に実行した場合を例とします。
$ nanika_syori 12345
そうすると、12345
はARG1
という変数に詰め替えが行われ、最終的にmanage.py shell
に渡されるスクリプトは以下になります。
hoge_id = 12345
print(f'HELLO {hoge_id}')
これで任意の引数を取らせつつ、処理が実行できるようになっています。
まとめ
LAPRASでのテクニカルサポートが利用するリポジトリの一部をご紹介しました。なかなか刺さる人もいない内容ですがどなたかに伝われば幸いです。また、このリポジトリのblameの結果に自分以外の名前を刻んでくれるメンバーが増えればなお幸いです。