移転しました。

約3秒後に自動的にリダイレクトします。

時間のかかる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 が始まりました。次回もお楽しみに。