(1)はこちら、(2)はこちらから。
Perlとプロセスとの連携
さて、Perlでシェルやコマンドと連携するための準備は整いました。続いては、複数ホストに対して並行で処理を行うために、プロセスとの連携を見てみましょう。
複数ホストに対して並行でデプロイ作業を行う
Perlにはithreadsというスレッドの機能もあるのですが、あまり使われていません。Perlで並行処理を行う場合、forkで子プロセスを生成し、複数プロセスを利用して並行処理を行うことが多いです。今回も、Perlで子プロセスを扱う例を見てみましょう。
forkで子プロセスを作る
何はともあれ、子プロセスを作成する必要があります。Perlで子プロセスを作成するためには、ほかの言語と同じようにforkを利用します。リスト12のようなプログラムを実行すると、
と表示されます。このとき何が起こっているのかを見てみましょう。
リスト12(1)で"forking..."
が表示されるまでは普通のPerlプログラムなのでよいでしょう。fork
を呼び出すと、このプログラムが実行されているプロセスがそっくりそのまま複製されて、子プロセスでも同じプログラムがこから実行されます。ですので、次の行であるリスト12(2)からは複数プロセスで実行され、"forked!"
は2回表示されているわけです。
しかし、親プロセスで実行されているプログラムと子プロセスで実行されているプログラムには唯一の違いがあります。それがfork
の戻り値です。親プロセスでは、fork
の戻り値は生成された子プロセスのpidになりますが、子プロセスでは0が返ります。そのため、子プロセスではリスト12(3)を、親プロセスではリスト12(4)を実行することになります。
子プロセスの終了コードを取得する
子プロセスの終了コードは、IPC::Open3のときと同様に、waitpid
ののちに$?
特殊変数で取得できます。
並行で複数ホストに対してファイルをコピーする
さて、それではアーカイブした静的ファイルを、fork
を利用して複数ホストに並行してscp
でばらまいてみましょう。アーカイブする部分はリスト8ですでに見たので、その続きをリスト13に示します。
今回はhost1、host2、host3の3つのホストに対してstatic.tgzをばらまいています。手順としては、ホストの数だけ子プロセスを生成し、それぞれの子プロセスでそれぞれのホストに対して作業を行います。
それぞれの子プロセスでは、
- (1) static.tgzをscpでリモートにコピーする
- (2) デプロイ先ディレクトリに対して、タイムスタンプの名前で新しいディレクトリを作成する
- (3) そのディレクトリに対してstatic.tgzを展開する
- (4) static.tgzを削除する
という作業を、system
関数を利用して行っています(上記の丸数字はリスト13中のものに対応しています)。各コマンドに対してsystem
の戻り値を見て、作業が失敗していたら失敗の終了コードでそのままexit
しています。
親プロセスでは、@scp_pids
に子プロセスのpidを貯めておいて、waitpid
でそれらのプロセスの終了を待ち、終了コードをチェックしています。今回はひとまず「どこかのプロセスが失敗していたらそのままデプロイを中断する」という挙動にしています。
すべてのホストに対するコピーが成功したらシンボリックリンクを張る
これですべてのホストに対してファイルのコピーを無事終えました。それでは、ここですべてのホストに対して今度はシンボリックリンクを張りましょう。やることはリスト13とそんなに変わらず、今回ばらまいたディレクトリへのシンボリックリンクを張るコマンドを子プロセスで並行して実行するだけです(リスト14)。
あとはnginxなどで、今回ばらまいたstaticディレクトリを静的に配信するように設定しておけば、複数のホストに対して静的ファイルをばらまき、ユーザーのアクセスを一斉に最新のファイルへ切り替えることができます[1]。
エラーが起こったらロールバックする
これでうまくデプロイができたと思いたいところですが、そうは問屋がおろしません。というのも、もしも今回シンボリックリンクを張る作業で「どこかのホストだけ失敗してしまった」という場合はどうなるでしょうか? その場合、一部失敗したホストだけは古いファイルを配信し続けることになります。これはまずいですね。「どこかのサーバで実行に失敗したら、ロールバックを行い以前の状態に戻す」というしくみが必要そうです。
戦略としては、
- 現在のタイムスタンプのディレクトリが存在すればそれを消す
- そのあとデプロイディレクトリに残っている最新のディレクトリに対してシンボリックリンクを張りなおす
にしましょう。コードにするとリスト15のようになります。ロールバックの失敗はかなり致命的なエラーですので、わかりやすいように目立つ文字列を挿入しています。今まで「どこかのホストが失敗したらデプロイを中止する」という戦略を採っていた部分を、すべてrollback_all($timestamp, $hosts);
するようにしてやれば、失敗したときのロールバックも行えます。
まとめ
今回は簡単なデプロイスクリプトを通じてPerlが運用に適した言語であることを見てきました。今回のスクリプトは簡単のため手続きをベタ書きしたので、抽象化して改善できるところもたくさん残っています。また、今回は「古いディレクトリを削除する」という手続きを書いていないため、このままこのスクリプトを運用していくとサーバのディスク容量を圧迫してしまいます。そのあたりの改善は、ぜひみなさんの手で行ってみてください。
ほかの言語でもこのようなスクリプトを書くことは難しくないでしょうが、最初に述べたとおり、Perlは高い後方互換性と枯れた言語であるがゆえの安定性を持っています。みなさんも、日々の運用をサポートするようなツールを、安定していてUNIX系システムとの相性が抜群のPerlで書いてみてはいかがでしょうか。
さて、次回の執筆者は鍛治匠一さんで、テーマはPerl6です。お楽しみに。
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT