落とし穴に立て札を立てるが如く

ハマりどころの解決が少しでも早くなることを願って書いていきます

Azureパイプライン実行の流れをまとめてみる

f:id:nam_yu_sql:20210322063550j:plain この記事はcloud.config tech blogにもマルチポストしています。

tech-blog.cloud-config.jp

はじめに

パイプライン、回してますか?
今回もAzure Pipelineのネタで一席打ちます。
Azure Pipelineを回すとき、普段はなんとなしに「あー回ってんなーうわ止まったなんで」と眺めているのですが、Expressionの話を調べているとExpressionはコンパイルされるタイミングやタスクの開始時に評価されていることが分かり、「あーパイプラインも後ろでは順を追って回ってるんだなー」等考えるようになりました。
なので今回は、Azure Pipelineの実行の流れを調べたので、それを独断と偏見で重要そうなところをある程度かいつまんでまとめてみたいと思います。

Azure Pipelineのパイプライン実行の流れ

パイプライン実行の流れは大きく分けて「実行の最初に行われること」「stage単位で行われること」「job単位で行われること」「step単位で行われること」に分けられます。
stage,job,stepはYAML schema referenceで解説されているとおりの処理の単位で、stage、job、stepの順に細かくなっていきます。
しかし、これらの単位ごとの役割は単純に処理を括るだけのものではないのです。
順に見ていきましょう。

まず最初に

Azure DevOpsのパイプラインの実行APIを叩いたり、あるいはAzure DevOps上からパイプラインを実行したりした際、stepやjob等の手順に従って処理を始める前に、いくつかの前処理が走ります。
まず最初に、templateとtemplate expressionを処理します。
Templateとは大雑把に言うとパイプラインの一部を別のyamlファイルとして切り出すことで複数のパイプラインで使いまわせるようにする便利な仕組みなのですが、詳しくはまた今度解説したいと思います。
template expressionについては以前の記事で解説したとおりで、別名Compile time expressionと呼ばれ、つまるところパイプラインが回り始めるこのタイミングでyamlの文字列から実際の値に置き換わります。
それらが終わってから、一つ目のstageの処理に入ります。

stage単位での処理

stageは一番最初のものから順番に処理されていきます。
それぞれのstageの処理が始まるタイミングで、そのstageで使用するリソースが使える状態か、また、それらを利用する権限があるかをチェックします。
例えば、特定のazure リソースに接続するためのservice connectionで本当に対象のリソースにアクセスできるかをチェックします。
stageが始まるタイミングでこの処理が走るので、リソース名や権限関連の情報をvariableとして指定したい場合は、stageよりも上の層で指定する必要があります。そしてそうなると、runtime parameterで指定するか、あるいはpipeline層のvariable出しかこれらは設定できないことになります。
何か処理を行ってその結果で特定の場面で使用する認証情報を変えたい場合もあるかもしれませんが、その場合はstageを区切ってstage dapendenciesの仕組みを使って前のstageから認証情報を渡してやる必要があります。

一つのstageが終わると、前のstageの処理が成功していれば次のstageに移り、またはじめに戻って処理を始めます。
デフォルトでは前のstageが失敗していると残りのstageは実行されず、パイプライン全体として失敗とみなされます。

job単位での処理

jobは、agent単位で実行される処理の単位です。
一つのstageの中に複数のjobがあった場合、それらはそれぞれ別のagentで並列に実行されます。
stage内では、それぞれのjobのdependson等の値から、最初に実行できるjobを選び、それから実行していきます。
行うjobが決まると、まずはそのjobで使用するagentを決めます。yamlの中では pool: の部分を参照し、どの種類のagentを使用するか決めます。
agentはそれぞれの処理を物理的に行うもので、実体はself-hostedにしろMicrosoft-hostedにしろVMです。
ユーザーが構成したVMを使う場合をself-hostedと呼んだり、microsoftが作成したテンプレ構成のものを使う場合はMicrosoft-hostedと呼んだりする形です。
agentについては以前の記事で触れているのでそちらも見てみてください。
使用できるagentがあればそれを使用して処理を始めますが、無ければ使えるagentが出てくるまでこの状態で待ちになります。
使用するagentが確保できれば、そのagentにjobで行う処理の内容を送信し、それぞれのtaskで指定された処理を始めます。

そのjobが終わったら、他のjobのdependsOnやconditionから次に行うjobを決定し、実行します。
なお、job毎にagentを確保するという点ですが、これは別のjobに移るごとにそのagentのVMの中身がリセットされるということでもあります。
なので、生成したファイルに依存する処理は、できる限り同じjobに含めるようにしましょう。
あるいは、blob storageなどの共通で使える外部リソースを利用してjob間でファイルを受け渡す必要があります。

step単位での処理

stepはjobとは違い、上から順に処理されていきます。
ここまでくるとyamlに記述された個別のtaskを実行するのみで、事前準備として特筆することはあまりありません。
ちなみに、Azure Pipelineのタスクはこちらで纏められているとおり多種多様なのですが、個々のagentで行われる実体としてはpowershellコマンドかNode.jsの形になるようです。これは豆ですね。
また、jobの間ではマシン内に保存したファイルは引き継がれないという話をしましたが、task間では同じマシンを連続で使うためファイルが保持されます。

一つのstepが終わると、その結果を記録してから次のstepを開始します。
しかし、jobの中で複数のstepを順番に実行しているときに一つでも失敗した場合、そのjobの中のそれ以降のstepはデフォルトでは実行されなくなります。
conditionでalways()を指定しているstepは例外的に実行されるので、処理が途中で一度転んでも実行したいような処理(例えば変更のクリーンアップとか)は最後のstepとしてalways()の設定をして実行するといいです。

おわりに

今回はAzure Pipelineのパイプライン実行の流れについて纏めて共有しました。
yamlを書いて回せば回る」くらいの認識でもAzure Pipelineを運用することはできますが、その後ろで起きていることを知ることでその仕組みについてもう少し理解を深めることができるかと思います。
Azure Pipelineを極めて最強のDevOpsエンジニアを目指しましょう。

参考