しばやん雑記

ASP.NET Core とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core アプリケーションにも Azure Pipelines でバージョンを付ける

.NET Core から AssemblyInfo.cs に書いていたバージョンなどを、MSBuild を使ってビルド時に自動生成されるようになったので、簡単にアセンブリのバージョンを CI で埋め込めるようになりました。

NuGet 向けでは普通にビルド時にバージョンを付けてきましたが、ASP.NET Core などのアプリケーションでは付けてこなかったので、付けるメリットを考えつつ活用シナリオを探りました。

これ以降は、全体として Azure Pipelines を使って話を進めていきます。

App Service への Run From Package を使ったデプロイが簡単に書けるので、最近は App Service / Azure Functions 向けには Azure Pipelines を使うようにしています。

ビルド時にバージョンを自動でつける

基本的な考え方は NuGet パッケージ作成の時と同じですが、ASP.NET Core アプリケーションの場合はタグを付けてデプロイというより、特定のブランチにマージでデプロイというパターンが多いと思います。

なのでバージョンは Azure Pipelines が持っている情報から自動で振るようにします。

Build Number をバージョンとして使う

最初に考えられるのは Build Number をそのままバージョンとして使う方法です。非常に分かりやすいのと、設定が簡単なので大体はこれで良いと思います。

注意点としては特定のテンプレートからパイプラインを作成すると、Build Number のフォーマットが日付ベースになっている点です。これはバージョンとして使えないので、設定からシンプルな値に変更しておきます。

f:id:shiba-yan:20190630205304p:plain

日付はそのままでは使えないですが、上手くバージョンとして扱えるようにフォーマットするのも手です。

ビルド時にバージョンを付けるには -p:Version=1.0.0 のようにパラメータを追加するだけで OK です。作成した YAML は以下の通りになります。

pool:
  name: Hosted Windows 2019 with VS2019

steps:
- task: DotNetCoreCLI@2
  displayName: Build
  inputs:
    projects: '$(Parameters.RestoreBuildProjects)'
    arguments: '--configuration $(BuildConfiguration)'

- task: DotNetCoreCLI@2
  displayName: Publish
  inputs:
    command: publish
    publishWebProjects: True
    arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory) -p:Version=1.0.$(Build.BuildNumber)'
    zipAfterPublish: True

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact'
  inputs:
    PathtoPublish: '$(build.artifactstagingdirectory)'
  condition: succeededOrFailed()

ASP.NET Core 向けのテンプレートから不要なタスクを削除しています。

これでビルドされた dll にバージョンが埋め込まれます。ビルド結果をダウンロードすれば確認できます。

f:id:shiba-yan:20190630215532p:plain

色々なツールを使ったりすることなく実現出来るので、圧倒的に楽になりました。

Git のコミットハッシュも付ける

デプロイされたアプリケーションは絶対にある時点でのコミットが元になっているので、Azure Pipelines の Build Number だけではなくコミットハッシュも付けることにします。

Azure Pipelines では Git のコミットハッシュは Build.SourceVersion に入っているので、それを切り詰めて使います。10 桁まで切り詰めるために PowerShell をまた使いました。

コミットハッシュは SourceRevisionId としてビルド時に渡すことにしました。最初から Version に付けても良いのですが、この辺り色々方法があるので紹介を兼ねて使っています。

pool:
  name: Hosted Windows 2019 with VS2019

steps:
- task: DotNetCoreCLI@2
  displayName: Build
  inputs:
    projects: '$(Parameters.RestoreBuildProjects)'
    arguments: '--configuration $(BuildConfiguration)'

- powershell: 'echo "##vso[task.setvariable variable=ShortSourceVersion]$($env:Build_SourceVersion.Substring(0, 10))"'
  displayName: 'PowerShell Script'

- task: DotNetCoreCLI@2
  displayName: Publish
  inputs:
    command: publish
    publishWebProjects: True
    arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory) -p:Version=1.0.$(Build.BuildNumber);SourceRevisionId=$(ShortSourceVersion)'
    zipAfterPublish: True

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact'
  inputs:
    PathtoPublish: '$(build.artifactstagingdirectory)'
  condition: succeededOrFailed()

文字列操作が出来ればもっとシンプルになるのですが、何故関数が無いのか謎です。

こっちもビルドした結果を確認すると、ちゃんとコミットハッシュが付いていることが確認できます。

f:id:shiba-yan:20190630215241p:plain

今回は ASP.NET Core アプリケーションでしたが、最近の NuGet では SemVer 2.0 に対応しているので、上のように "+" でコミットハッシュを繋げても問題なく扱えます。

付けたバージョンの利用

完全にビルドとデプロイは Azure Pipelines で自動化されるので、バージョンを機械的に付けること自体は簡単に出来ましたが、これをどのように利用していくかという話です。

パッと思いついたのは以下の 2 つでした。Application Insights の方は利用価値がありそうです。

App Settings に追加する

以前に App Service にデプロイされているバージョンが分からなくなるという話があったので、Release パイプラインで App Settings にもバージョンを設定するようにしてみます。

App Service へのデプロイタスクには App Settings の更新機能があるので簡単です。

f:id:shiba-yan:20190630212834p:plain

これでデプロイを行うと App Settings にデプロイされているバージョンが追加されました。

f:id:shiba-yan:20190630211800p:plain

デプロイと App Settings の更新は同じタスクで行われるので、CI と Run From Package と組み合わせている限りはバージョンがずれたりしないはずです。*1

複数リージョンで App Service を展開している場合には、Azure Portal から簡単にそれぞれにデプロイされたバージョンを確認出来るので便利かもしれません。

Application Insights から参照する

あまり知られていないっぽいですが、Application Insights はデフォルトでアプリケーションのバージョンをテレメトリと一緒に送信しているので、CI でバージョニングを行っておくとデプロイされたバージョン単位での集計が行えるようになります。

ASP.NET Core 向けのデフォルトでは AssemblyVersionAttribute の値を見ているようなので、今回のようにコミットハッシュを付けている場合は、以下のようにカスタマイズが必要です。

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<ApplicationInsightsServiceOptions>(options =>
    {
        options.ApplicationVersion = Assembly.GetEntryAssembly()
                                             ?.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
                                             ?.InformationalVersion;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

単純に AssemblyInformationalVersionAttribute の値をセットしているだけなので、難しくはないです。

これでデプロイすると、Application Insights にコミットハッシュ付きのバージョンが送信されます。

f:id:shiba-yan:20190630204901p:plain

後は KQL を使ってバージョン別に集計することが出来ます。サンプルとして適当にリクエスト数と平均応答時間をバージョン別に集計してみました。

f:id:shiba-yan:20190630213208p:plain

設定しておくことで Application Insights の Smart Detection や Insights が、特定のバージョンのみエラーレート上がっていることや、パフォーマンスが改善したといった気づきを与えてくれるでしょう。

特に Release パイプラインを使ってカナリアリリースを行っている場合に、バージョンが付いていることで問題の特定を素早く行う手助けをしてくれそうです。

結論としては、バージョンを付けるコストは非常に低い割に、Application Insights と組み合わせた時のメリットが大きかったので、設定しておいて損はないということです。

*1:当然ながら手動でデプロイしたりすると狂う