クラウドエンジニアブログ

ASP.NET Web アプリを作って Azure DevOps から Azure App Service に CI/CD してみた

佐藤 実

佐藤 実

こんにちは。 CI/CD を含むクラウドインフラの構築や技術支援を担当している、クラウドエンジニアの佐藤です。

既存アプリの開発や運用に課題を抱えている組織やチームの皆さん!アプリ開発の高速化と品質向上の手段として CI/CD を取り入れてみませんか?

CI/CD と聞くとクラウドネイティブでアプリがコンテナ化されているイメージがありますが、そんな事はありません。誤解を恐れずに単純化して言うと、アプリのコードを管理してビルドやテストの標準化とデプロイまでを自動化すれば、立派な CI/CD です。

ちなみに CI と CD が単語として分かれますが、開発側が CI で運用側が CD の責任範囲で分けたり、SRE チームだと CI と CD 両方に責任をもっている場合もあります。

そんな CI/CD ですが、何が良いかと言うと標準化と自動化にあります。CI/CD パイプラインを書くことが標準化、コードが Git リポジトリにプッシュされた事をトリガーにパイプラインが動く事で自動化が実現します。

そこで、私の担当領域である CI/CD 基盤構築の一例として、シンプルな Web アプリと CI/CD パイプラインを作ってみます。

Web アプリには、ASP.NET と C# を使用します。
Git リポジトリは、Azure Repos を使用します。
CI/CD パイプラインは、Azure Pipelines を使用します。
Web アプリのデプロイ先は、Azure App Service を使用します。


今回作成する CI/CD 検証環境のシステム構成イメージと処理フロー

1. git push : PC で作成した Web アプリを Azure Repos の Git リポジトリへプッシュします。
2. trigger : main ブランチへのプッシュをトリガーに Azure Pipelines が自動的に動き出します。
3. deploy : Azure Pipelines の一番最後のステップで Azure App Service に Web アプリがデプロイされます。
4. browse : PC から Web アプリにアクセスして動作確認します。

簡単な ASP.NET Web アプリを作る

前提として、Azure DevOps で Personal Access Token を作成済みで Git リポジトリへのプッシュができる事。また、自分の作業環境に .NET SDK がインストール済みで .NET CLI が使える前提で進めます。

※参考サイト:
Azure DevOps の Azure Repos で検証に使用したコードを Git 管理してみた
Windows、Linux、および macOS に .NET をインストールする


私の作業環境にインストール済みの .NET CLI のバージョンは「7.0.100」です。
dotnet コマンド のドキュメントを参考にアプリを作成していきます。

$ dotnet --version
7.0.100


後ほどテスト用のプロジェクトも作成するので、.NET ソリューションファイルを最初に作成します。

dotnet new sln -o sampleapp


作成されたディレクトリに移動します。

cd sampleapp


Web アプリ用テンプレートを使用してプロジェクト「 sampleapp 」を作成します。

dotnet new webapp -o sampleapp


.NET ソリューションファイルに Web アプリのプロジェクトを追加します。

dotnet sln add sampleapp/sampleapp.csproj


Web アプリをローカルで実行します。
「 watch 」は .NET 6 から導入されたコマンドで、ホットリロード(アプリを実行しなおさなくても変更を自動で読み込む)機能を使用しています。

dotnet watch --project sampleapp


Visual Studio Code で下記のように表示されたら「ブラウザーで開く」をクリックします。


ブラウザーで Web アプリが表示されます。


ホットリロードを試すため、少し横道にそれますが、Index.cshtml の 8 行目に「 Home 」を追加して保存します。


アプリを実行しなおさなくてもブラウザーのリロードで変更が反映します。

Azure Repos に Git リポジトリを作成しコードをプッシュする

ビルド済みのバイナリーファイルなど Git で管理しないものを除外するため「 .gitignore 」ファイルを dotnet コマンドで作成します。

dotnet new gitignore


ローカルの Git リポジトリを初期化します。

git init


ローカルの Git リポジトリに管理対象のファイルをすべて追加します。

git add -A


コミットメッセージに「 first commit 」を書いて、登録したファイルをコミット(確定)します。

git commit -m "first commit"


デフォルトブランチの名前を「 main 」に変更します。

git branch -m main


Azure Repos に「 sampleapp 」という名前の Git リポジトリを作成します。

az repos create \
  --org https://dev.azure.com/ceblog/ \
  --project ceblog \
  --name sampleapp


Azure Repos で作成された Git リポジトリを確認します。
既存の Git リポジトリ「 ceblog 」から「 sampleapp 」に切り替えます。


Git リポジトリが空の場合はコードの登録方法ガイドが表示されます。
ローカルの Git リポジトリを Azure Repos の Git リポジトリにプッシュしたいので「 Push an existing reposigory from command line 」のコマンドを実行します。


ローカルの Git リポジトリにリモートの Git リポジトリの URL を登録します。

git remote add origin https://ceblog@dev.azure.com/ceblog/ceblog/_git/sampleapp


ローカルの Git リポジトリをリモートの Git リポジトリにプッシュ(アップロード)します。

git push -u origin --all


下記のような表示になればプッシュ成功です。


Azure Repos の画面をリロードしてみると、ファイルがプッシュされたのが確認できます。

Azure Pipelines でパイプラインを作り動作を確認する

ローカルで「 azure-pipelines.yml 」ファイルを作成し、下記の YAML を書いて保存します。
.NET Core アプリをビルド、テスト、デプロイする のドキュメントを参考に YAML を作成していきます。

trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- task: UseDotNet@2
  inputs:
    version: 7.x


「 azure-pipelines.yml 」をリモートの Git リポジトリにプッシュします。

git add -A

git commit -m "add azure-pipelines.yml"

git push


az pipelines create のドキュメントを参考に Azure Pipelines を作成します。
作成直後にパイプラインが動いてしまわないように「 --skip-first-run true 」をパラメーターに追加しています。

az pipelines create \
  --name sampleapp-ci \
  --repository sampleapp \
  --repository-type tfsgit \
  --skip-first-run true \
  --yml-path azure-pipelines.yml


Azure Pipelines の画面で「 sampleapp-ci 」が作成された事を確認します。


Azure Pipelines を実行します。

az pipelines run \
  --name sampleapp-ci


パイプラインが動いていると時計のようなアイコンに変わります。
アイコンをクリックしてパイプラインの中身を確認します。


「 Stages 」列のクルクル回転しているアイコンで、ステージが進行中だという事が確認できます。
アイコンをクリックしてパイプラインジョブの中身を確認します。


「 azure-pipelines.yml 」に書いた「 UseDotNet@2 」タスクをクリックして詳細を確認します。


.NET 7 のインストール成功を確認できます。
「 20230203.1 」をクリックしてパイプライン実行結果のサマリーページを表示します。


パイプラインジョブの成功が確認できます。

Web アプリにテストコードを追加してパイプラインを更新する

DotNetCoreCLI@2 - .NET Core v2 タスク のドキュメントを参考に「 restore 」「 build 」「 test 」コマンドを「 azure-pipelines.yml 」に追加します。
タスクプロパティの「 displayName 」で「 DotNetCoreCLI@2 」のタスクの違いを表示します。

trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- task: UseDotNet@2
  inputs:
    version: 7.x
- task: DotNetCoreCLI@2
  displayName: DotNetRestore
  inputs:
    command: restore
    feedsToUse: select
- task: DotNetCoreCLI@2
  displayName: DotNetBuild
  inputs:
    command: build
- task: DotNetCoreCLI@2
  displayName: DotNetTest
  inputs:
    command: test


xUnit を使用した単体テストのプロジェクトを作成します。

dotnet new xunit -o sampleapp.tests


単体テストプロジェクトに Web アプリプロジェクトへの参照を追加します。

dotnet add sampleapp.tests/sampleapp.tests.csproj reference sampleapp/sampleapp.csproj


.NET ソリューションファイルに単体テストのプロジェクトを追加します。

dotnet sln add sampleapp.tests/sampleapp.tests.csproj


単体テストを実行します。

dotnet test

単体テストがパスした事を確認します。


コードをコミットしてプッシュします。Azure Pipelines が自動で動き出します。

git add -A

git commit -m update

git push


Azure Pipelines の画面でパイプラインジョブを確認します。
追加したタスクが成功し、「 100% tests passed 」と表示されました。


パイプラインジョブのサマリーには「 Tests and coverage 」が「 100% passed 」と表示されます。
Summary の右に「 Tests 」という項目が増えました。


テストの結果が確認できるビューです。
エラーがある場合は、このビューから確認する事が可能です。

Web アプリの成果物生成をパイプラインに追加する

「 dotnet publish 」を「 azure-pipelines.yml 」に追加します。
また、 PublishBuildArtifacts@1 - ビルド成果物 v1 タスクを発行する のドキュメントを参考に「 PublishBuildArtifacts 」タスクを追加します。

trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- task: UseDotNet@2
  inputs:
    version: 7.x
- task: DotNetCoreCLI@2
  displayName: DotNetRestore
  inputs:
    command: restore
    feedsToUse: select
- task: DotNetCoreCLI@2
  displayName: DotNetBuild
  inputs:
    command: build
- task: DotNetCoreCLI@2
  displayName: DotNetTest
  inputs:
    command: test
- task: DotNetCoreCLI@2
  displayName: DotNetPublish
  inputs:
    command: publish
    publishWebProjects: true
    arguments: --output $(Build.ArtifactStagingDirectory)
    zipAfterPublish: True
- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: $(Build.ArtifactStagingDirectory)
    ArtifactName: sampleapp
    publishLocation: Container


コードをコミットしてプッシュします。Azure Pipelines が自動で動き出します。

git add -A

git commit -m update

git push


Azure Pipelines の画面でパイプラインジョブを確認します。
追加したタスクが成功し、「 1 artifact produced 」と表示されました。


パイプラインジョブのサマリーには「 1 published 」と表示されます。
「 1 published 」をクリックします。


sampleapp.zip が作成されました。
「 sampleapp.zip 」をクリックします。


「 sampleapp.zip 」をダウンロードし適当な場所に保存します。


私の作業環境は GitHub Codespaces なので、ブラウザーの Visual Studio Code を右クリックして「 アップロード 」から「 sampleapp.zip 」をアップロードしておきます。

Azure App Service を用意し Web アプリを手動デプロイする

Azure App Service の検証環境を作成します。

prefix=ceblogapp

az group create \
  --name ${prefix}-rg \
  --location japaneast

az appservice plan create \
  --name ${prefix}-plan \
  --resource-group ${prefix}-rg \
  --is-linux \
  --sku FREE

az webapp create \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --plan ${prefix}-plan \
  --runtime "DOTNETCORE:7.0"


Azure ポータルで作成された Azure リソースを確認します。


Azure App Service に「 sampleapp.zip 」を手動デプロイします。

az webapp deployment source config-zip \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --src sampleapp.zip

「 status code 202 」が表示されれば成功です。


Azure App Service の概要から URL をクリックして Web アプリを表示します。


初回は少々時間がかかりますが、しばらくすると Azure App Service 上で Web アプリが表示されます。

Azure Pipelines から Azure App Service にデプロイする

Azure Pipelines から Azure App Service にデプロイするためのサービスプリンシパルを作成します。
最小権限の原則 に従い、Azure 組み込みロールの「 Website Contributor 」を、先ほど検証用に作成した Azure App Service のみをスコープに設定します。

az ad sp create-for-rbac \
  --name sampleapp \
  --years 100 \
  --role "Website Contributor" \
  --scopes $(az webapp show \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --query id \
  --output tsv)

下記のような JSON が出力されます。

{
  "appId": "2ce8d0cd-9999-9999-9999-53ed909bdf57",
  "displayName": "sampleapp",
  "password": "CWt8Q~KCb6iFJvcUXKwHlWHEPlpXcZ57_GO8ade0",
  "tenant": "d81a851c-9999-9999-9999-329b0a596e82"
}


Azure サブスクリプションの ID も確認します。

az account show \
  --query id

下記のような情報が出力されます。

"0487608b-9999-9999-9999-8779015aa368"


Azure DevOps のプロジェクトの「 Project settings 」をクリックします。


「 Service connections 」の「 Create service connection 」をクリックします。


New service connection から「 Azure Resource Manager 」をクリックします。


下までスクロールし「 Next 」をクリックします。


New Azure service connection から「 Service principal (manual) 」をクリックします。
「 Next 」をクリックします。


それぞれの項目に値を入力し「 Verify 」をクリックします。


「 Verification Succeeded 」と表示されたら「 Service connection name 」と「 Grant access permission to all pipelines 」にチェックを入れ「 Verify and save 」をクリックします。


「 ceblogapp 」という名前の Service connection が作成されました。


AzureRmWebAppDeployment@4 - v4 タスクのデプロイ のドキュメントを参考に「 AzureRmWebAppDeployment 」タスクを「 azure-pipelines.yml 」のステップの一番最後に追加します。
デプロイタスクはリリースパイプラインで行う方法もありますが、今回は Azure App Service 検証環境へのデプロイまで CI 側のパイプラインで行ってみます。

trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- task: UseDotNet@2
  inputs:
    version: 7.x
- task: DotNetCoreCLI@2
  displayName: DotNetRestore
  inputs:
    command: restore
    feedsToUse: select
- task: DotNetCoreCLI@2
  displayName: DotNetBuild
  inputs:
    command: build
- task: DotNetCoreCLI@2
  displayName: DotNetTest
  inputs:
    command: test
- task: DotNetCoreCLI@2
  displayName: DotNetPublish
  inputs:
    command: publish
    publishWebProjects: true
    arguments: --output $(Build.ArtifactStagingDirectory)
    zipAfterPublish: True
- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: $(Build.ArtifactStagingDirectory)
    ArtifactName: sampleapp
    publishLocation: Container
- task: AzureRmWebAppDeployment@4
  inputs:
    ConnectionType: AzureRM
    azureSubscription: ceblogapp
    appType: webAppLinux
    WebAppName: ceblogapp
    packageForLinux: $(Build.ArtifactStagingDirectory)/**/*.zip


Azure Pipelines からデプロイされた事を確認するため、「 sampleapp/Pages/Index.cshtml 」の h1 タグにある文字列を「 Welcome Home 」から「 Hello Pipelines!」変更します。

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="text-center">
    <h1 class="display-4">Hello Pipelines!</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>


コードをコミットしてプッシュします。Azure Pipelines が自動で動き出します。

git add -A

git commit -m update

git push


Azure Pipelines の画面でパイプラインジョブを確認します。
追加したタスクが成功しました。


Azure ポータルから Azure App Service の「デプロイセンター」開きデプロイ状態を確認します。


Web アプリにアクセスし「 Hello Pipelines! 」が表示されいる事を確認します。

リリースパイプラインで別環境の Azure App Serivce にデプロイする

別環境の Azure App Service 「 ceblogweb 」を作成します。

az webapp create \
  --name ceblogweb \
  --resource-group ${prefix}-rg \
  --plan ${prefix}-plan \
  --runtime "DOTNETCORE:7.0"


Azure ポータルで「 ceblogweb 」が作成された事を確認します。


先ほど作成したサービスプリンシパルが、組み込みロール「 Website Contributor 」で「 ceblogweb 」にアクセスできるようにします。

az role assignment create \
  --role "Website Contributor" \
  --assignee $(az ad sp list \
  --display-name sampleapp \
  --query "[0].appId" \
  --output tsv) \
  --scope $(az webapp show \
  --name ceblogweb \
  --resource-group ${prefix}-rg \
  --query id \
  --output tsv)

Azure ポータルで Azure App Service 「 ceblogweb 」のアクセス制御 (IAM) からロール割り当てを開き、sampleapp サービスプリンシパルに「 Web サイト共同作成者」ロールが割り当てられているのを確認します。


Azure Pipelines の Release を開き「 New pipeline 」をクリックします。


「 New release pipeline 」をクリックし、パイプライン名を「 sampleapp-cd 」に変更します。


パイプライン名が「 sampleapp-cd 」に変わった事を確認します。
Artifacts の「 + Add 」をクリックします。


Project、Source、Default version を選択し、Source alias はデフォルトのままにします。 「 Add 」をクリックします。


Source alias が Artifacts に設定された事を確認します。
ちなみにイナズマのようなアイコンで、CI パイプラインから自動で CD パイプラインが動くようにする事も可能です。今回は手動で CD パイプラインを動かすので設定しません。
Stages の「 Add 」をクリックします。


Azure App Service deployment を選択してから「 Apply 」をクリックします。


Stage name を「 ceblogweb 」に変え、右上のバツマークをクリックします。


Stages の「 1 job, 1 task 」をクリックします。


Azure subscription で「 ceblogapp 」という名前の Service connection を選択します。
App type で「 Web App on Linux 」を選択します。
App service name で「 ceblogweb 」を選択します。
「 Save 」をクリックします。


「 OK 」をクリックします。


「 Create release 」をクリックします。


「 Create 」をクリックします。


「 Release-1 」をクリックします。


「 Deploy 」をクリックします。


「 Deploy 」をクリックします。


「 Deploy 」をクリックします。


下記のようにデプロイが動き出したらマウスオーバーして「 Logs 」をクリックします。


Deploy Azure App Service が成功した事を確認します。


プロジェクトの Summary ページを開きます。
Pipelines の項目に CI パイプラインの成功率と CD パイプラインの成功率が表示されます。


Azure App Service のデプロイセンターでデプロイが成功した事を確認します。


Azure App Service の概要から ceblogweb の URL を開きます。


リリースパイプラインで別環境の Azure App Service ceblogweb に、最新のビルド済み Web アプリをデプロイする事ができました。


Azure App Service は今回のブログ記事用に作成した検証環境です。放置しておくのは良くないため、Azure のリソースグループごと削除しておきます。Git リポジトリのソースコードやパイプラインは今後の参考のため残しておくことにします。

az group delete \
  --name ${prefix}-rg \
  --yes

sampleapp サービスプリンシパルも不要になるので削除しておきます。

az ad sp delete \
  --id $(az ad sp list \
  --display-name sampleapp \
  --query "[0].appId" \
  --output tsv)

まとめ

  • CI パイプラインと CD パイプラインを作成しました。
  • CI パイプラインでビルドとテストを行い、テスト結果が確認できる事がわかりました。
  • CI パイプラインでデプロイする事も可能だとわかりました。

今回の CI/CD 基盤構築は、一人で開発から運用まで実施する事を想定したシンプルな構成です。組織やチームで開発する場合は、別ブランチで Web アプリを開発し、プルリクエストを経由してデフォルトブランチにマージしたりします。また、各ブランチによって条件分岐してフィーチャーブランチはビルドとテストまで、デフォルトブランチは検証環境へのデプロイまで行うなどルールを定め、もう少し複雑なパイプラインで標準化し Web アプリ開発を自動化していきます。

このブログ記事が誰かの何かの参考になればうれしいです。 最後まで読んで頂き、ありがとうございます。

お問い合わせ

製品・サービスに関するお問い合わせはお気軽にご相談ください。

ピックアップ

セミナー情報
クラウドエンジニアブログ
clouXion
メールマガジン登録