Azure で CI/CD を体験してみよう!そう、Azure DevOps!
さて、昨今注目の DevOps やアジャイル開発、これからの時代ますます重要になってきますが、それらを実現する現実的な手法としてよく聞く単語 『 CI/CD 』 ってソフトウェア開発などに関わっていないとなかなかピンとこないですよね?
CI =
Continuous Integration ( 継続的インテグレーション )
CD =
Continuous Delivery ( 継続的デリバリー )
or
Continuous Deployment ( 継続的デプロイメント )
となりますが、「 継続的 」はわかるとして
インテグレーション? デリバリー? デプロイメント?
ってなりますよね?
これらをもうちょっとかみ砕いて言うと、
「 ソースコード(ソフトウェア開発やインフラの構成コード)に変更(修正)を加えると、それをきっかけにそのコードをビルド(実行ファイルに変換)してテストして、運用しているシステムの任意の環境に配置する 」
という一連の流れを自動化すること
となります。
自動化というのがポイントで、これにより工数削減となりさらに言えば属人性の排除やヒューマンエラーを排除し、開発してから実際にサービスとして提供するまでのリードタイムを短縮できるのです。
この手法を使わない場合と比べて、バグ修正や機能追加などガツガツ行っていけるのでユーザーはもちろん、ITエンジニアにとってもありがたい仕組みです。
ちなみに、この仕組みのことを『 CI/CD パイプライン 』と言います。
ということで、この記事では
Azure のサービスを使って、CI/CD パイプラインの構築から実際の動作までご紹介しようと思います。
--- 概要 ---
PHPのアプリケーションコードに変更を加え、その変更を自動でデプロイ先のWebサーバーに反映させる
ということを行います。
使用する Azure の主なサービスは、
🔹App Service
🔸Azure DevOps
となります。
🔹App Service は、AWSでいうところの Elastic Beanstalk で、Webサーバーのマネージドサービスです。
Azure App Service は、Web アプリケーション、REST API、およびモバイル バックエンドをホストするための HTTP ベースのサービスです。 開発には、.NET、.NET Core、Java、Ruby、Node.js、PHP、Python のうち、お気に入りの言語をご利用いただけます。 アプリケーションの実行とスケーリングは、Windows ベースの環境と Linux ベースの環境の両方で容易に行うことができます。
公式ドキュメント:App Service の概要
🔸Azure DevOps は
- Azure Repos
- Azure Pipelines
- Azure Boards
- Azure Test Plans
- Azure Artifacts
で構成される CI/CD パイプライン提供サービスです。
Azure DevOps には、作業の計画、コード開発の共同作業、およびアプリケーションのビルドとデプロイを行うためのサポートチーム向けの開発者向けサービスが用意されています。 Azure DevOps は、開発者とプロジェクトマネージャーおよび共同作成者をまとめてソフトウェア開発を完了するための、カルチャとプロセスのセットをサポートしています。 これにより、組織は従来のソフトウェア開発アプローチよりも迅速に製品を作成し、改善することができます。
公式ドキュメント:Azure DevOps とは
公式ドキュメントを見ていただくと、各サービスの関係性がイメージできると思います。
出典:Azure DevOps を使用した CI/CD パイプラインの設計
🟢 事前準備 🟢
①アプリケーションデプロイ環境作成
文字列で検索をしても良いですが、「すべてのサービス」➡「Web」➡「App Service」と Azure Portal から探してみてください。
で、そこから条件を指定して作成していきます。
今回は、PHPのコード(アプリ)を Linux上にデプロイすることにしました。
ちなみに、『 App Service プラン 』というのはデプロイするアプリケーションの実行環境となるリソースのことです。
自動的に新規作成のものが選ばれますので、このままにします。
「 SKU とサイズ 」はとりあえず何も考えず『 Standard S1 』を選択してください。
App Service (Web Apps、API Apps、または Mobile Apps) では、アプリは常に App Service プラン で実行されます。 また、Azure Functions には、App Service プラン で実行するオプションもあります。 App Service プランでは、Web アプリを実行するための一連のコンピューティング リソースを定義します。 これらのコンピューティング リソースは従来の Web ホスティングの "サーバー ファーム" に似ています。 1 つまたは複数のアプリを同じコンピューティング リソース (または、同じ App Service プラン) で実行するように構成することができます。
公式ドキュメント:Azure App Service プランの概要
デプロイ、監視、はデフォルト値で問題なく、タグはお好みに応じてつけてください。
App Service 関連のリソースの作成が完了したら、ステージング スロット用の 『 デプロイスロット 』 を作成します。
以下のように、『 staging 』という名前で作成します。
作成が完了すると、『 運用スロットの名前 + staging 』という名前で作成されたことが確認できます。
そして、今回作成されたリソースを見てみると、以下の三つのリソースが作成されていることが確認できます。
💡補足
今回は、追加のデプロイスロット(ステージング環境)を使って、Webアプリケーションの環境切り替え(スワップ)をしようと思います。
実行している App Service プランのサービス レベルが Standard、Premium、または Isolated である場合は、Web アプリ、Linux 上の Web アプリ、モバイル バック エンド、または API アプリを Azure App Service にデプロイするときに、既定の運用スロットではなく別個のデプロイ スロットを使用できます。 デプロイ スロットは、固有のホスト名を持つライブ アプリです。 アプリのコンテンツと構成の各要素は、(運用スロットを含む) 2 つのデプロイ スロットの間でスワップすることができます。
公式ドキュメント:Azure App Service でステージング環境を設定する
こういったことを行う場合、App Service プランの『 SKUとサイズ 』を適当に選択して作成してしまうとできません。Standard、Premium、Isolated のいずれかでなければならないです。
なので今回は、『 Standard S1 』を選択しました。
②Azure DevOps Organizations 作成
こちらについても、文字列で検索をしても良いですが、「すべてのサービス」➡「DevOps」➡「Azure DevOps Organizations」と Azure Portal から探してみてください。
以下のページに遷移したら、『 My Azure DevOps Organizations 』 をクリックします。
『 新しい組織の作成 』 をクリックします。
そして、組織名を決める必要があるので任意の名前を付けてください。
あと、プロジェクトの作成が必要になります。
今回は、Private の 『 test-project 』 としました。
作成が完了すると 『 Welcome to the project! 』 画面が現れます。
③ソースコード管理用のリポジトリ作成
以前の記事で書いた 『 Bitbucket 』 というリモートリポジトリではなく、
せっかくなので今回は Azure のサービスである 『 Azure Repos 』 を使います。
見た目が違うだけで基本的に同じと思ってもらって問題ありません。
Azure Repos は、コードの管理に使用できるバージョン管理ツールのセットです。
公式ドキュメント:Azure Repos とは
① 『 Repos 』 をクリックし、②『先ほど作成したプロジェクト名』をクリックします。
『 New repository 』 をクリックです。
今回は 『 test-repository 』 という名前で作成しました。
ちなみに、README や .gitignore などファイルは一切作成しませんでした。
完全に空の状態でリポジトリが作成されます。
Git の使い方については、以下のマガジンの記事をぜひ参考にしてみてください。
ここで画面にはいくつかの最初の操作の指示 ( test-repository is empty. Add some code! ) というかが記載されていますが
⭐ Clone to your computer
・ Push an existing repository from command line
・ Import a repository
・ Initialize main branch with a README or gitignore
まだ記事としてはご紹介していなかった 『 Clone ( クローン ) 』 を今回使おうと思います。
本筋から外れるので詳細は割愛しますが、リモートリポジトリをローカル環境にクローン(複製)するコマンド(操作)です。
以下の画像の状態の赤矢印 ( ① ) が指す部分をクリックすると、https ではじまる URL がクリップボードにコピーされます。
この URL情報は、後ほど行う『 クローン操作 ( git cloneコマンド ) 』で必要になります。
あとは、② ( Generate Git Credential ) もクリックしてください。
すると、『 password 』情報をコピーできます。
この情報もクローン操作の際に必要になります。
なお、この password情報は、クローン操作だけでなくその後の ( push や pull といったリモートリポジトリに対する)命令時にも度々必要になります。
再発行(表示)はしてくれないようなので、必ずどこかにメモ(保存)をして後から何回も確認できるようにしてください。
これらの情報を入手したら、リポジトリ(フォルダ)を作成したい場所で 『 Git Bash 』 のコンソールを立ち上げてください。
念のため、操作するローカルPC ( Windows でも Mac でも良いですが ) には、Git の環境が整っていることが前提となります。
※今回の環境としては、Windows 10 ( Git for Windows ) で行っています。
参考までに Windows に Git 環境を整えるのに役に立つ情報を載せておきます。
Git Bash に関する情報は以下の記事でも紹介しています。
Git のコマンド操作に抵抗がある方は、SourceTree ( GUI ) での操作も可能ですので以下の記事も役に立つかもしれません。
まずは、クローンしてローカルリポジトリを作成する場所(フォルダ)に移動します。
mevius@SurfaceLaptop MINGW64 ~/Documents/AzureReposRepository
$ pwd
/c/Users/mevius/Documents/AzureReposRepository 👈今回はここに作成(クローン)することにした
『 git clone 先ほど①でコピーしたURL 』とコマンドを打ちます。
mevius@SurfaceLaptop MINGW64 ~/Documents/AzureReposRepository
$ git clone https://mevius01@dev.azure.com/mevius01/test-project/_git/test-repository
Cloning into 'test-repository'...
すると以下のように認証を求められるので、アカウントを選択します。
でも、選択しただけでは以下のようなコメントがでるので
Logon failed, use ctrl+c to cancel basic credential prompt.
別途開く、『 OpenSSH 』のウィンドウにて、先ほど②でコピーしたパスワードを入力します。
すると以下のコメントが表示され完了です。
「空のリポジトリをクローンしたみたいだけど、それでイイの?」的なことですが、実際に空と知っていて行っているので全く問題ありません。
warning: You appear to have cloned an empty repository.
クローンされた「 test-repository 」という名のフォルダに移動し、一応中身を確認してみます。
はい、見事に何もありません。「 .git 」という隠しフォルダが当然ありますがその他のファイルは何もありません。
想定通りです。
mevius@SurfaceLaptop MINGW64 ~/Documents/AzureReposRepository/test-repository (master)
$ ls -lA
total 0
drwxr-xr-x 1 mevius 197121 0 Oct 12 21:55 .git/ 👈
一応、エクスプローラーでも見てみますが当たり前ですが同じ状態です。
リモートリポジトリの登録状況も念のため見てみます。
『 git remote -v 』 とコマンドを打つとわかります。
先ほど Azure Portal ( Azure DevOps ) で作成したリポジトリが(クローンしたので当たり前ではありますが)リモートリポジトリとして登録されているのが確認できました。
mevius@SurfaceLaptop MINGW64 ~/Documents/AzureReposRepository/test-repository (master)
$ git remote -v
origin https://mevius01@dev.azure.com/mevius01/test-project/_git/test-repository (fetch)
origin https://mevius01@dev.azure.com/mevius01/test-project/_git/test-repository (push)
ここからが大事なところです。
現在はファイルが何もないので、まずは追加して Push します。
今回は、『 index.php 』 ファイル(内容は以下参照)を作成しました。
<?php
echo 'Hello world!';
?>
ちなみに、Git Bash でもファイルを作成 ( vi / vim エディタ ) できますし、メモ帳を使ってファイル作成するという方法など色々ありますが、以前に書いた記事でもご紹介した Visual Studio Code ( VScode ) に Git の拡張機能を入れた状態で操作するのがスマートかもしれません。
見やすいし、Git の操作も含めてそこで完結するので。
ということで Push まで行い、ローカル / リモートリポジトリの master ブランチにコミットが一つだけ追加された状態になりました。
🟢 CI/CDパイプラインの構築 🟢
ここまでで、大前提環境の準備が整いましたので、ここからは準備の最後の詰め、最も大事な CI/CDパイプラインの構築を行っていきます。
Azure Pipelines というサービスでパイプラインを作成していきます。
Azure Pipelinesプロジェクトを自動的にビルドしてテストし、他のユーザーが使用できるコード プロジェクトを作成します。 ほぼすべての言語やプロジェクトの種類で機能します。 Azure Pipelines統合 (CI) と継続的デリバリー (CD) を組み合わせ、コードをテストしてビルドし、任意のターゲットに出荷します。
公式ドキュメント:Azure Pipelines とは
出典:新しい Azure Pipelines ユーザー向けの主要な概念
以下の画面を表示し、『 Create Pipeline 』をクリックします。
すると、リモートリポジトリサービスを選択する画面に遷移するので、『 Azure Repos Git 』を選びます。
変更を監視する対象となるリポジトリの選択する必要があるので、先ほど作成した ( Push まで行った )『 test-repository 』を選びます。
すると、「どこでビルドやデプロイを行いますか?」と聞かれるので、『 PHP as Linux Web App on Azure 』を選びます。
最初に作成した、App Service のことです。
すると、この App Service のリソースが紐づくサブスクリプションを聞かれるので該当するものを選んでください。
あと、その App Service に付けた名前も指定します。
すると 『 azure-pipelines.yml 』 というファイルのエディタ画面に遷移します。
このファイルを編集することで、自分が行いたいパイプラインの詳細について定義していきます。
今回は割愛しますが、もっと詳しく内容や使い方を知りたい方は公式ドキュメントをご確認ください。
Azure Pipelinesエディターに基づく YAML パイプライン エディターが提供されます。このエディターを使用すると、ポータルからパイプラインを作成および編集Azure DevOpsできます。 エディターには、Intellisense のサポートや、パイプラインを編集するときにガイダンスを提供するタスク アシスタントのようなツールが提供されます。
公式ドキュメント:YAML パイプライン エディター
自動的に YAMLファイルの中身が作成されていますが、以下のように修正していきます。
大枠としては、自動的に記述された2つの 『 ステージ 』 の内容を修正し、新しく1つの 『 ステージ 』 の内容を追記します。
ステージは、パイプライン内の論理的な境界です。 これを使用すると、関心の分離 (ビルド、QA、運用など) をマークすることができます。 各ステージには、1つまたは複数のジョブが含まれます。 パイプラインで複数のステージを定義すると、既定では、1つのステージの後に複数のステージが実行されます。 ステージを実行する条件を指定できます。
公式ドキュメント:ステージ
① 『 Build 』 ステージの修正
variables:
phpVersion: '7.4'
と書き換え
- script: composer install --no-interaction --prefer-dist
workingDirectory: $(rootFolder)
displayName: 'Composer install'
の部分を丸々削除しました。
そして最終的には以下のようにしました。
- stage: Build
displayName: Build stage
variables:
phpVersion: '7.4'
jobs:
- job: BuildJob
pool:
vmImage: $(vmImageName)
steps:
- script: |
sudo update-alternatives --set php /usr/bin/php$(phpVersion)
sudo update-alternatives --set phar /usr/bin/phar$(phpVersion)
sudo update-alternatives --set phpdbg /usr/bin/phpdbg$(phpVersion)
sudo update-alternatives --set php-cgi /usr/bin/php-cgi$(phpVersion)
sudo update-alternatives --set phar.phar /usr/bin/phar.phar$(phpVersion)
php -version
workingDirectory: $(rootFolder)
displayName: 'Use PHP version $(phpVersion)'
- task: ArchiveFiles@2
displayName: 'Archive files'
inputs:
rootFolderOrFile: '$(rootFolder)'
includeRootFolder: false
archiveType: zip
archiveFile: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip
replaceExistingArchive: true
- upload: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip
displayName: 'Upload package'
artifact: drop
② 『 Test 』 ステージを追記
『 Build 』 ステージと 『 Deploy 』 ステージの間に、以下の記述を挿入しました。
- stage: Test
displayName: 'Test Stage (Deploy Stage Slot)'
dependsOn: Build
condition: succeeded()
jobs:
- deployment: DeploymentJob
pool:
vmImage: $(vmImageName)
environment: $(environmentName)-staging
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
displayName: 'Deploy Azure Web App : mevius-php'
inputs:
azureSubscription: $(azureSubscription)
appType: 'webAppLinux'
appName: 'mevius-php' 👈作成した App Service名
deployToSlotOrASE: true
resourceGroupName: 'Test-CICD-rg' 👈App Serviceに紐づくリソースグループ
slotName: 'staging' 👈ステージングスロット名
package: '$(Pipeline.Workspace)/drop/$(Build.BuildId).zip'
③ 『 Deploy 』 ステージの修正
以下のように修正しました。
- stage: Deploy
displayName: 'Deploy Stage (Swap Slots)'
dependsOn: Test
condition: succeeded()
jobs:
- deployment: DeploymentJob
pool:
vmImage: $(vmImageName)
environment: $(environmentName)
strategy:
runOnce:
deploy:
steps:
- task: AzureAppServiceManage@0
inputs:
azureSubscription: $(azureSubscription)
Action: 'Swap Slots'
WebAppName: 'mevius-php' 👈作成した App Service名
ResourceGroupName: 'Test-CICD-rg' 👈App Serviceに紐づくリソースグループ
SourceSlot: 'staging' 👈ステージングスロット名
書き換えたら、『 Save 』します。
ちなみに、以下の画面では「直接 masterブランチにコミットするよ」となっていますが、今回これで問題ありません。
普通はやらないですけどね。
すると以下の画面に遷移します。
ここからは、先ほどの YAMLファイル内に以下のように記述している箇所があるのですが、それに関連する設定を行っていきます。
jobs:
- deployment: DeploymentJob
pool:
vmImage: $(vmImageName)
environment: $(environmentName)-staging
jobs:
- deployment: DeploymentJob
pool:
vmImage: $(vmImageName)
environment: $(environmentName)
『 Environments 』をクリックし、その画面にでてくる運用スロット名をクリックしてください。
遷移した画面右上にある、縦に並んだ三つの点をクリックします。
ここで表示された『 Approvals and checks 』をクリックします。
遷移した画面にある『 Approvals 』をクリックします。
ここで、ステージングスロットと運用スロットをスワップする際に、その行為を承認する人(アカウント)を選択します。
まあ個人で作っている検証環境の場合、自分のアカウントになるかと思います。
すると、承認者が登録されました。
ここで再び『 Pipelines 』 の画面に戻ると、先ほどと様子が変わっています。
先ほど作成 ( Save ) したパイプラインの実行が始まっています。
始まっているパイプラインの表示部分をクリックしてもう少し詳しく状態を見てみます。
『 Stages 』に「 - 」でつながった三つの「 ○ 」が表示され、それぞれ状況が異なるようです。
先ほど同様に、パイプラインの表示部分をクリックしてもう少し詳しく状態を見てみます。
ここまでくると、三つの ○ の正体がわかりましたね。
そう、先ほど作成した YAMLファイルに記述したステージを表していたのでした。
左から『 Build, Test, Deploy 』です。
✅は完了を意味しており、この画面ショットでは Testステージでのデプロイ、つまりは App Service のステージングスロットにデプロイ完了したことを意味します。
Deployステージについては、『 Waiting 』ということで処理が止まっており、何やら注意コメントが表示されています。
そう、これが先ほど設定した Environments の承認の影響です。
『 Review 』をクリックします。
すると、承認するか否かを求められるので、もちろん承認します。
すると、Deployステージの処理も動き出し最後まで進みました。
承認がないと運用(本番)環境へはデプロイされないように設定したわけです。
実際に考えても最終確認してから、本番環境へは反映させたいですよね~てなもんです。
💡補足
このようなパイプライン処理を進める中で、リソースへのアクセス許可が必要になる場合もあります。
そうなった場合には、注意コメントがでてくるので、それに従って許可を出すことで止まっている処理を先に進めることができます。
さて、ここで『 Repos 』の先ほど作成したリポジトリを表示してみてください。
先ほどと少し様子が変わり、パイプライン定義ファイル ( .yml ) が追加されコミットも増えています。
もちろんこのファイルの中身は先ほど編集して Save した内容になっています。
そう、このパイプラインは masterブランチにコミットが追加されると、それをトリガーにパイプラインの処理が走るようになっていたのでした。
App Service の状況も確認してみましょう。
まずは、運用スロットのURLをブラウザで開いて、内容確認してみると先ほど作成した index.php の内容が表示されました。
では、ステージングスロットはどうでしょうか?
デフォルトページのままになっています。
つまり index.php の内容は再現されていないことになります。
ただ、
これでイイのです!
どうゆうことか解説しますね。
Testステージまでのパイプライン処理が完了した時点で、
ステージングスロット:Hello world!が表示される状態
運用スロット:デフォルトページの状態
だったわけです。
この後、Deployステージの処理でこれらをスワップ(入れ替え)させたわけです。
つまり、これがやりたかったのです。
🟢 (本格的に)パイプラインの実行 🟢
さて、ここまでの下準備で、このパイプラインがどうゆう挙動をするかわかってもらえたと思います。
ここからが大トロの部分です!
ここでは細かく解説しませんが、リモートリポジトリが進んだ状態になり、ローカルリポジトリは遅れた状態になっているので、まず git pull を行い、ローカルリポジトリをリモートリポジトリの状態と同じにします。
そのあと、ローカル側で新しいブランチを切り、 index.php ファイルに変更を加え「 ! 」がいっぱい表示されるように修正しました。
<?php
echo 'Hello world!!!!!!!!!!!!!';
?>
そして Push しました。
これでローカルとリモートの差分はなくなりまして、新しいブランチのコミットもあるのでプルリクエストも行える状態になったのです。
ということで、プルリクを作成しその内容でパイプラインを動かしてみたいと思います。
masterブランチにマージしてコミットが作成されるので、それを検知したパイプラインは処理を開始し始めるわけです。
Azure Repos の場合はまず『 Complete 』をクリックします。
そのあと、マージ処理の内容を指定し処理を行わせます。
はい、マージされた!と同時にパイプラインが動き出します。
先ほど同様に Testステージ完了時点で処理が止まりました。
この状態で、ステージングスロットの状態を除いてみましょう。
はい、バッチリ「 ! 」がいっぱいの Hello World が表示されました。
止まっている処理を承認することで進めていきます。
この後スワップが行われるわけですね。
はい、無事完了しました。
運用スロットを覗くと、当然変更が反映されています。
ステージングスロットはというと、こちらもばっちりスワップされていますね。
はい、ということで
問題なく想定通り完了です!
🟠 今回はここまで 🔚
いかがだったでしょうか?
かなり単純なものでしたが、なんとなくのイメージは掴めたのではないでしょうか?
今回の App Service の部分が実運用している Webサーバーであり、例えば
とある Webページの見た目などを修正する必要がでてきた
⇩
なのでそれに従ってソースコードを修正しその内容を一旦影響がでない環境に再現してみた
⇩
問題がなかったのでサイトビジターに実際に見せる本番環境に反映させた
と考えるとより親近感が湧くかもしれません。
最後までお読みいただきありがとうございました 😊