時間のかかるGCFへのデプロイをShellスクリプトで並列化する
こんにちは。エンジニアの @umanoda です。弊社でも開発者ブログを始めることになりました。よろしくおねがいします。
最近、新たに社内システムを構築するのにGCPを使いました。まだまだシステム運用面でできていないことも多く、例えばGoogle Cloud Functions(GCF)へのコードデプロイをローカルマシンのコンソールから手動で行っています。
# デプロイコマンド gcloud functions deploy func_a
しかし、このコマンド実行は終わるのに5分くらいかかります。
ごくたまに複数のFunctionのデプロイをしたいときがあるのですが、前のデプロイを待って次のコマンドを実行する……というやり方をすると時間がかかり非常に手間です。
gcloud functions deploy func_a # デプロイが実行され、画面にいろいろ表示される gcloud functions deploy func_b # func_aの完了を待ってから実行する…つらい
CIを使い、1-Function 1-Jobで処理させるなどすれば同時に複数のデプロイができて早そうですが、それを構築するほどの規模でもない……ということでShellを使って並列にデプロイを行いました。Shellにはパイプラインなどの並列処理をさせる仕組みが色々あって好きなのですが、バックグラウンド処理をつかった並列処理もなかなか便利だと感心したので記事にしてみます。
GCPへの並行デプロイ
デプロイするにあたり、以下のような前提がありました
- 複数のGCPプロジェクトに、同じFunctionをデプロイしたい
- プロジェクトによっては
func_a
はデプロイ済みだが、func_b
はデプロイしたくないというのがある。すでにGCFにデプロイ済みのFunctionのみを再デプロイしてアップデートしたい
これを以下のShellスクリプトでデプロイします
# my-project-1 .. GCPプロジェクト名. func_a, func_bがある # my-project-2 .. func_a, func_b, func_zがある # my-project-3 .. func_cだけある projects=$(cat << EOS my-project-1 my-project-2 my-project-3 EOS ) cmd="echo start" for project in $projects ; do for func in $(gcloud functions list --project=$project | grep ACTIVE | cut -f 1 -d" ") ; do cmd="$cmd & gcloud functions deploy --project=$project $func" done done sh -c "$cmd"
これを実行すると
echo start & \ gcloud functions deploy --project=my-project-1 func_a & \ gcloud functions deploy --project=my-project-1 func_b & \ gcloud functions deploy --project=my-project-2 func_a & \ gcloud functions deploy --project=my-project-2 func_b & \ gcloud functions deploy --project=my-project-2 func_c & \ gcloud functions deploy --project=my-project-3 func_c
というコマンドがShellスクリプト内部で実行されます。最後のコマンドはあえてバックグラウンドで実行しないようにしています。このShellスクリプトを呼んだ時にデプロイが終わるのを待つためです。
最後のフォアグラウンド処理が終わっても、その他のバックグラウンド処理が終わっているとは限らないのですが…まあ同じような処理行っているのだし最後に実行したものが最後に終わるだろうという楽観的にみこんでいます。
すべてのデプロイが終わっているか確認したければ ps | grep gcloud functions
で確認できます。
書き捨てのつもりで上記のShellスクリプトを雑に作ったのですが、別解として xargs
の -P
オプションで並列実行する事もできるねという話を同僚から教えてもらいました。以下のように xargs
を使えば並列にデプロイを実行しつつ全プロセスの完了を待ってくれるのでより良いのだろうと思います。
for project in $projects ; do for func in $(gcloud functions list --project=$project | grep ACTIVE | cut -f 1 -d" ") ; do cmd="$cmd --project=$project $func\n" done done echo -e $cmd | xargs -n 2 -P 0 gcloud functions deploy
Ref. https://linuxjm.osdn.jp/html/GNU_findutils/man1/xargs.1.html
&
を使ったコマンドの並列実行
上記スクリプトについて補足します。
Shellで複数の処理を並列実行するのは非常に簡単です。 command &
の形式でバックグラウンドでコマンド実行することができますが、これを command1 & command2 & ...
のように記述するだけでそれぞれが並列に実行されます。
実はこれは、単に複数のバックグラウンド実行をワンライナーで書いただけだったりします。
command1 & command2 & # 上と同じ command1 & command2 &
今回作ったのスクリプトのように機械的にコマンドを作るときはワンライナーでやると楽なことが多いです。最初に出したスクリプトのように sh
コマンドで実行できたり、 実行する前に echo
をコマンドの先頭に入れて内容を確認できたりするのが地味に便利。
本当に並列実行されているのか実験してみましょう。
時間がかかる処理 slow_proc
を作って、バックグラウンド実行します。
# background.sh # なんか時間がかかる処理 slow_proc () { ruby -e 'print("Start:", Process.pid, "\n"); sleep(rand); print("Finish:", Process.pid, "\n")' } slow_proc & slow_proc & slow_proc
実行すると以下のような出力になり、 slow_proc
が別々のプロセス上で並列処理されている事がわかります。いいですね。
$ bash background.sh Start:34486 Start:34488 Start:34487 Finish:34488 Finish:34487 Finish:34486
なお前述したとおり command1 & command2
が終了した場合、command2
の完了は保証されていますが、必ずしも command1
が完了しているとは限らないことには注意してください。
終わりに
Shellを使うとこのように、並列実行できるスクリプトが手軽に書けるので便利です。もっとも、長期的にShellスクリプトを使って運用してしていくと、コードベースが大きくなっていった時に辛くなりがちなので、ワンタイムスクリプトとして便利使いするのがちょうどよいでしょう。
なお今はこのようにデプロイをしましたが、おいおいは Cloud Build を使って手動でのデプロイそのものを無くしていきたいと思っています。
こんな感じで、ぬるっと Viibar Engineering Blog が始まりました。次回もお楽しみに。