こんにちは。 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 を使用します。
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 アプリにアクセスして動作確認します。
前提として、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 」を追加して保存します。
アプリを実行しなおさなくてもブラウザーのリロードで変更が反映します。
ビルド済みのバイナリーファイルなど 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.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 」をクリックしてパイプライン実行結果のサマリーページを表示します。
パイプラインジョブの成功が確認できます。
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 」という項目が増えました。
テストの結果が確認できるビューです。
エラーがある場合は、このビューから確認する事が可能です。
「 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 の検証環境を作成します。
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 組み込みロールの「 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 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 基盤構築は、一人で開発から運用まで実施する事を想定したシンプルな構成です。組織やチームで開発する場合は、別ブランチで Web アプリを開発し、プルリクエストを経由してデフォルトブランチにマージしたりします。また、各ブランチによって条件分岐してフィーチャーブランチはビルドとテストまで、デフォルトブランチは検証環境へのデプロイまで行うなどルールを定め、もう少し複雑なパイプラインで標準化し Web アプリ開発を自動化していきます。
このブログ記事が誰かの何かの参考になればうれしいです。 最後まで読んで頂き、ありがとうございます。