CI/CDを極める:Azure App Serviceへのデプロイを6ヶ月で自動化した方法

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

手動デプロイの混乱

6ヶ月前、私たちのチームのデプロイプロセスは、まるでスリリングなジェンガのようでした。更新のたびに開発者がプロジェクトを手動でビルドし、ファイルをZIP形式で圧縮し、FTP経由でアップロードしながら祈るような状態でした。それは動作が遅く、ミスが発生しやすく、エンジニアの貴重な時間を浪費する精神的に辛い作業でした。

設定ファイルのたった一つの漏れが本番環境の停止を招き、頻繁に「ホットフィックスの連鎖」に陥っていました。自動化は単なる技術的なアップグレードではなく、生き残るための手段でした。本番サーバーに手で触れるのをやめたとき、初めてスケーラビリティが確保されます。

限界点:なぜデプロイが失敗したのか

失敗の原因を調査した結果、成長中のITチームによく見られる3つの要因が見つかりました。1つ目は「環境のドリフト(差異)」です。本番環境がブラックボックス化していました。手動での微調整により、本番環境とステージング環境が一致しなくなり、テストが意味をなさなくなっていたのです。

次は「アーティファクトの不一致」です。ある開発者が.NET SDK 8.0.1を使い、別の開発者が8.0.4を使っているといったケースです。こうしたわずかなバージョンの違いがバイナリの不整合を生み、ローカルでは動作してもクラウドではクラッシュするという事態を引き起こしていました。そして最後はセキュリティです。FTPの認証情報や発行プロファイルをチーム内で共有することは、管理不能な巨大な攻撃対象を生み出していました。私たちには、一貫性を強制し、アクセスを制限するシステムが必要でした。

適切なツールの選択

Azure DevOpsを盲目的に選んだわけではありません。最初に思い浮かんだのはJenkinsでしたが、サーバーの管理や絶え間ないプラグインのパッチ適用という負担は避けたかったのです。コードを管理していたのでGitHub Actionsも有力な候補でした。しかし、私たちのインフラはすでにAzureエコシステムに深く浸かっていたため、Azure DevOpsが最も抵抗の少ない道でした。

ネイティブな統合機能は無視できない魅力です。サービス接続(Service Connections)はサービスプリンシパル(Service Principals)経由で認証を処理するため、ここ半年間、パスワードや発行プロファイルに触れる必要がありません。Azure中心のスタックであれば、Repos、Pipelines、App Serviceの間のフローはシームレスです。他のツールでよく発生する「つなぎのコード」の破損もありません。

ブループリント:マルチステージYAMLパイプライン

私の現在の戦略は、マルチステージYAMLパイプラインを中心に据えています。デプロイを「Pipeline as Code(コードとしてのパイプライン)」として扱うことで、アプリケーションコードと同様にリリースロジックをバージョン管理できます。これにより、すべての変更がピアレビューされ、監査可能になります。

ステップ1:安全な接続

基礎となるのはサービス接続です。これはAzureサブスクリプションへの安全な架け橋となります。私は常に「サービスプリンシパル(自動)」を使用しています。これにより、Microsoft Entra IDにスコープを絞った共同作成者(Contributor)権限を持つアプリ登録が生成されます。個別の開発者アカウントを使用するよりも格段に安全です。

ステップ2:CIロジック(アーティファクトのビルド)

最初のステージは継続的インテグレーション(CI)です。コードがどこへ行くにしても、まずコンパイルが通り、テストをパスすることを確認する必要があります。以下は、私たちの.NETサービスで使用している効率的なYAMLで、常に5分以内に完了します。

trigger:
- main

stages:
- stage: Build
  displayName: 'ビルドとパッケージング'
  jobs:
  - job: Build
    pool:
      vmImage: 'ubuntu-latest'
    steps:
    - task: DotNetCoreCLI @2
      inputs:
        command: 'publish'
        publishWebProjects: true
        arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)'
        zipAfterPublish: true

    - task: PublishBuildArtifacts @autocontent.log.1
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'web-app'

ステップ3:CDフェーズ(本番環境へのデプロイ)

アーティファクトが検証されたら、継続的デプロイ(CD)をトリガーします。Azure DevOpsの「環境(Environments)」を使用して、どのバージョンが稼働しているかを正確に追跡します。「runOnce」戦略は、標準的なウェブアプリケーションの90%において完璧に機能します。

- stage: Deploy
  displayName: '本番環境へのデプロイ'
  dependsOn: Build
  condition: succeeded()
  jobs:
  - deployment: Deploy
    environment: 'production'
    pool:
      vmImage: 'ubuntu-latest'
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureWebApp @autocontent.log.1
            inputs:
              azureSubscription: 'My-Azure-Service-Connection'
              appType: 'webAppLinux'
              appName: 'itfromzero-prod-app'
              package: '$(Pipeline.Workspace)/web-app/**/*.zip'

本番環境向けのパイプライン強化

コードをデプロイするのは戦いの半分に過ぎません。ここ数ヶ月で、リスクを排除するためにデプロイスロット(Deployment Slots)と変数グループ(Variable Groups)を導入しました。

スロットを使えば、まず「ステージング」にプッシュできます。実際のAzureハードウェア上でスモークテストを実行し、ユーザーに影響を与えることなく確認できます。検証が終われば「インスタントスワップ」を実行します。これにより、ダウンタイムゼロ、ストレスゼロが実現します。

また、変数グループをAzure Key Vaultにリンクしました。これにより、データベース接続文字列などの機密情報をYAMLファイルから排除できます。セキュリティコンプライアンスにおいて、設定とコードを分離することは妥協できない条件です。

6ヶ月後の結論

結果はすぐに現れました。デプロイ頻度は2週間に1回から、1日4〜5回に跳ね上がりました。もう金曜日を恐れることはありません。「自分のマシンでは動く」という言い訳は通用しなくなりました。すべてのビルドがクリーンなホステッドエージェント上で行われるからです。

もし今日から始めるなら、初日にこれをセットアップするでしょう。YAMLへの最初のリサーチという3時間の投資は、自動リリースの最初の1週間で元が取れました。Microsoftのエコシステムで仕事をしているなら、このパイプラインをマスターすることが、火消し役を卒業して構築者(ビルダー)になるための最短ルートです。

Share: