<目次>
Azure DevOpsからAzure App Serviceへ自動デプロイする最短CI/CD構築ガイド
この記事のねらい
STEP1:前提準備
STEP2:サービス接続の作成
STEP3:YAML パイプライン作成
STEP4:App Service の追加設定
STEP5:パイプライン初回実行
STEP6:デプロイ確認
STEP7:スワップ確認(staging → production)
その他:失敗時の切り分け
Azure DevOpsからAzure App Serviceへ自動デプロイする最短CI/CD構築ガイド
この記事のねらい
- Azure DevOps のリポジトリにコミットすると、Azure App Service(Web App)へ自動でデプロイされる“最短のしくみ”を作ります。
- Deployment Center は使わず、YAMLパイプライン+サービス接続で統一。スロット運用(staging→production)と、Free プラン直デプロイの両パターンに触れます。
できること
- Azure DevOps → App Service への YAML マルチステージ(Build → Deploy → Swap)
- サービス接続(ARM, Workload identity/手動SPどちらでも)作成の勘所
- Free プランでの妥協ポイントと設定の切替
やらないこと
- 複雑なマルチ環境(dev/test/prod)の運用設計
- IaC(Bicep/Terraform)によるフル自動化の深掘
STEP1:前提準備
● Azure App Service(Web App)を作成済み
基本はデフォルト設定で進めつつ、以下これだけ押さえれば「後で面倒にならない」設定のみを記載。
1. 基本
- Runtime stack: LTS版(例:.NET 8 / Node 18 / Python 3.11)
- OS: Linux(Windows依存があればWindows)
2. App Service プラン
- S1以上推奨(Always Onやスロットが使える)
3. デプロイ設定
- 継続的デプロイ: 無効(Deployment Centerは使わない) → Azure Pipelinesで管理するため、二重トリガー防止
4. 監視
- Application Insights: 有効(同一リージョンのワークスペース推奨)
(図111)


● Azure DevOps プロジェクト&リポジトリあり
(図112)

● テスト用の資産を登録
. ├─ azure-pipelines.yml # 既に用意したYAML ├─ app.py # Flaskアプリ本体 ├─ requirements.txt # 依存ライブラリ └─ .gitignore # 任意
① app.py
from flask import Flask
# Flaskアプリのインスタンスを作成
app = Flask(__name__)
# ルートURL用のハンドラ
@app.get("/")
def hello():
return "Hello from Azure App Service (New Production)!", 200
# ヘルスチェック用エンドポイント(App ServiceのHealth checkで利用可能)
@app.get("/health")
def health():
return "OK", 200
② requirements.txt
flask gunicorn
(図113)

● 権限:Azureのサブスクリプション共同作成者/所有者、Azure DevOpsのProject Administrator
→ 割愛
STEP2:サービス接続の作成
● Project Settings → Service connections → New service connection
(図211)

● 種別:Azure Resource Manager → Identity type: App registration (automatic)
● サブスクリプションとスコープ(該当Web AppのResource Group)を選ぶ
● 名前例:sc-azure-sub(後でYAMLに使います)
(図221)

(図222)

(図223)

>目次にもどる
STEP3:YAML パイプライン作成
● ローカルでYAML作成
• パイプラインYAMLサンプルを参考に azure-pipelines.yml という名前で保存します。
• リポジトリのルート直下に置くのが一番シンプルです。
```yaml
# ================================================
# Azure DevOps → Azure App Service (Python) CI/CD
# - main に push → ビルド → スロット(staging)へデプロイ → 本番へスワップ
# - スロットを使わない場合は末尾の「Swap」ステージを削除し、
# 変数 slotName を 'production' にしてください。
# ================================================
# main ブランチにコミットされたら自動実行
trigger:
branches:
include:
- main
# 変数(あとで上書きしやすいようまとめる)
variables:
vmImage: 'ubuntu-latest' # ビルドエージェントOS
azureServiceConnection: 'sc-azure-sub' # Service connection 名(Azure DevOpsで作成済みのもの)
webAppName: 'my-webapp-prod' # AzureのApp Service(Web App)の名前
resourceGroup: 'rg-myapp-prod' # Web App が属するリソースグループ
slotName: 'staging' # デプロイ先スロット名(直本番なら 'production')(▲)
packagePath: '$(Build.ArtifactStagingDirectory)/drop.zip' # Zip Deploy で使うパッケージの保存先
stages:
# =========================
# 1) Build & Package
# =========================
- stage: Build
displayName: Build & Package (Python)
pool:
vmImage: $(vmImage)
jobs:
- job: BuildPython
displayName: Build Python app and create zip
steps:
# 【ポイント】Azure DevOpsのビルドマシンに指定バージョンのPythonを入れる
- task: UsePythonVersion@0
displayName: 'Use Python 3.11'
inputs:
versionSpec: '3.11' # App Service 側のランタイムと合わせるのが無難
# 依存ライブラリのインストールと成果物のステージング
- script: |
set -e # 途中でエラーになったら終了(見落とし防止)
python -m pip install --upgrade pip
# ビルド成果物(後でzip化する中身)を置くフォルダを用意
mkdir -p $(Build.ArtifactStagingDirectory)/app
# requirements.txt がある場合は、その内容を "app" フォルダにインストール
# → App Service へ zip で送ると、そのまま実行に必要なライブラリが入っている状態になる
if [ -f requirements.txt ]; then
pip install -r requirements.txt -t $(Build.ArtifactStagingDirectory)/app
fi
# プロジェクトのソースコードを "app" フォルダへコピー
# rsync を使うと不要なものを除外しやすい(tests や .venv など)
rsync -av \
--exclude '.git' \
--exclude '.venv' \
--exclude 'tests' \
./ $(Build.ArtifactStagingDirectory)/app/
displayName: 'Install deps & stage app files'
# "app" フォルダの中身を zip に固める(App Service の Zip Deploy で使う形式)
- task: ArchiveFiles@2
displayName: 'Archive app → drop.zip'
inputs:
rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/app' # zipに含める中身
includeRootFolder: false # 直下のファイル/フォルダのみ含める
archiveType: 'zip'
archiveFile: '$(packagePath)' # 変数で指定した zip 出力先
# 後段の Deploy ステージから取得できるよう、ビルド成果物として発行
- publish: $(Build.ArtifactStagingDirectory)
displayName: 'Publish build artifact: drop'
artifact: drop
# =========================
# 2) Deploy to Slot (staging)
# =========================
- stage: Deploy
displayName: "Deploy to App Service (slot: $(slotName))"
dependsOn: Build # Build が成功したら実行
condition: succeeded() # 失敗したら止める
jobs:
- deployment: DeployWebApp
displayName: 'Zip Deploy to $(webAppName)/$(slotName)'
environment: 'prod-staging' # Azure DevOpsのEnvironment名(承認ゲート等に使える)
strategy:
runOnce:
deploy:
steps:
# 直前の Build ステージで発行した成果物(drop)を取得
- download: current
artifact: drop
# Azure App Service へ zip をデプロイ
- task: AzureWebApp@1
displayName: 'AzureWebApp@1 Zip Deploy'
inputs:
azureSubscription: $(azureServiceConnection) # Service connection 名
appName: $(webAppName) # Web App 名
slotName: $(slotName) # スロット(未使用なら削除)(▲)
package: '$(Pipeline.Workspace)/drop/drop.zip' # 上で作った zip のパス
# =========================
# 3) Slot Swap → production(▲)
# =========================
- stage: Swap
displayName: Slot Swap to Production
dependsOn: Deploy
condition: succeeded()
jobs:
- job: SwapSlots
displayName: 'Swap $(slotName) → production'
steps:
# スロットを production と入れ替える(Blue/Green 的な切替)
- task: AzureAppServiceManage@0
displayName: 'AzureAppServiceManage@0 Swap Slots'
inputs:
azureSubscription: $(azureServiceConnection) # Service connection 名
WebAppName: $(webAppName) # Web App 名
ResourceGroupName: $(resourceGroup) # RG名(Swapタスクは必要)
SourceSlot: $(slotName) # ここで指定したスロットを production と入替
Action: 'Swap Slots'
【重要】FreeやBasicの際は Swap ステージ ((3) Slot Swap → production(▲)の部分) を丸ごと削除し、slotName を ‘production’ に変更してください。
(図321)

● Gitでpush
git add azure-pipelines.yml git commit -m "Add Azure Pipelines definition" git push origin main
(図322)

(図323)

● Pipelines → Create pipeline → “Azure Repos Git” → “Existing Azure Pipelines YAML file”
(図311)

(図312)

(図313)

(図314)

(図324)

>目次にもどる
STEP4:App Service の追加設定
● Startup command を設定(gunicorn 起動)
Flask 例:gunicorn app:app(app.py に app = Flask(__name__) がある想定)
(図411)

● staging スロットを作成
(図412)
(図413)

STEP5:パイプライン初回実行
● Azure DevOps → Pipelines → 作成したパイプライン → Run pipeline
- Branch: main(必要なら変更)
- Parameters: なし(YAMLの変数で運用)
(図511)

(図512)

(図513)

(図514)

- Build(Pythonの依存解決→zip作成→artifact公開)
- Deploy(Zip Deploy が成功するか)
- Swap(staging→productionの入替)※スロット未使用ならこのステージは無し

STEP6:デプロイ確認
● Azure DevOps(Deployジョブ)
- AzureWebApp@1 のログに Successfully deployed が出るか
(図611)

(図612)

● Azure ポータル(App Service / 該当スロット)
- 診断 > ログストリーム:起動ログにエラーがないか
- Application Insights > Live Metrics:リクエストが来るとカウントされる
- URLにアクセス(stagingのURLでOK):アプリが応答するか
・https://-staging.azurewebsites.net/ ・直前までproductionだった“旧ビルド”が残っています
(図621)
STEP7:スワップ確認(staging → production)
●パイプラインの Swap ステージが成功することを確認
(図711)
●スワップ後、production URL でもアプリ動作を確認
(図721)

>目次にもどる
その他:失敗時の切り分け(超要約)
-
Build 失敗:requirements.txt の解決失敗/Pythonバージョン不一致 -
Deploy 失敗:サービス接続の権限不足/App 名/スロット名のタイプミス -
起動失敗(200出ない):Startup command(gunicorn の <module>:<app>)ミス/依存不足 -
Swap 後だけ不具合:本番だけの環境変数や接続先がスロット固有になっていない
