ごった煮

色々な事を書いてます

Bicep で App Service の Configuration にある Deployment slot setting フラグを操作する

App Service でスロットを利用する場合、スワップ時に切り替えたくない Application settings は、Deployment slot setting にチェックを入れます。

操作方法が比較的分かりづらいものだったので、備忘録として残します。

実際にやってみる

一番ミニマムなコードはこんな感じになります。

Microsoft.Web/sites/config namespace で、name に slotConfigNames を指定します。

appSettingNames には、チェックを付けたい設定のキー名を配列として指定します。

注意点

Function App では、一部 Deployment slot setting をオンに出来ない設定がある為注意してください。

具体的には、下記のようなものが、オンにしようとするとエラーになります。

まとめ

スロット周りは資料が少なくて辛い

参照元

learn.microsoft.com

Azure Pipeline 等から Bicep をデプロイする際にロール付与を行う場合の権限設定について

Bicep や ARM テンプレートでインフラをデプロイする際、ロール付与まで行いたい場合があると思います。

こういった場面で何度かハマった(設定を忘れていただけ)ので、備忘録として残します。

どんな場面を想定しているのか

App Service から Managed Identity を経由して App Configuration に接続したい場合等

どんな Bicep を書くのか

ミニマムな Bicep は、次のような感じになると思います。

このコードの 22行目以降がロール付与の記述です。

ローカルで自分が Owner のサブスクリプションにデプロイする場合などは、特に問題なく動くはずです。

エラーになるパターン

Azure Pipeline や、GitHub Actions 等、Service Principal を使用してデプロイする場合、各サービスの指示に従って Service Principal を生成するはずです。

この場合に生成される Service Principal は、ロール管理の権限が最初から割り当てられていない場合が多いので、その Service Principal を利用したデプロイでは、ロール付与でエラーになるはずです。

解決策

Service Principal に適切な権限を付与します。

2023年02月現在でしたら、Role Based Access Control Administrator を付与するのが良いかと思います。

簡単なステップとしては、次の通りです。

  1. ポータルからサブスクリプションの IAM へ移動
  2. Add > Add role assignment へ移動
  3. Role Based Access Control Administrator を選択
  4. 付与したい Service Principal を選択
  5. ロールを付与

付与が完了すれば、エラーにならずに実行できるはずです。

まとめ

Managed Identity 等、色々設定が必要なものは、Bicep でサクッと作れるようにすると、いざという時便利なので、ぜひ活用してみましょう。

2022年の振り返り

明けましておめでとうございます。

毎年年初に昨年の振り返りをしているので、今年も振り返っていこうかなと思います。

仕事

2021年は、実装60%、PM 40% くらいの動き方でした。

多分、独身時代の時間の使い方が出来ていれば余裕だったのですが、プレイングマネージャは正直辛すぎるので、実装に集中させてほしいと思う今日この頃です。

実装面では、興味の幅が広がったというか、昨年は Infrastructure as a Code が自分の中でホットだったので、いつもよりもソフトを書くというよりは、インフラ構成をコード化する作業に注力していました。

大分知見が貯まってきましたが、まだまだ悩んでいる部分が多いので、そういった点を2023年は誰かとディスカッション出来るといいなと思っています。

生活

2022年春に第一子が生まれる予定です。

より一層自分の時間が減るとは思いますが、ライフステージの変化という事で大人しく受け入れる所存です。

ガジェット

2022年は、あまりガジェットを買わなかったかなという気がします。

10年振りくらいにスマートフォンを一切買わない年でした。

正直スマートフォンの進化は限界というか、Fold だったり、ディスプレイが変形するタイプのガジェットくらいしか面白みが無いので、折り畳み式 Pixel に期待しています。

買ったガジェット

  • Surface Laptop Studio
  • Oculus Quest 2
  • Brio c100s
  • Steam Deck

買った中でも Steam Deck は、かなりのお気に入りです。

普段からテレビを見ながらゲームをするクセがあるので、Switch or Razer Kishi を接続したスマートフォンを使ったストリーミングでゲームをすることが多かったのですが、 Switch 以外のことが全て出来るので、非常に重宝しています。

そんなこともあって、年末セールで久々に大量のゲームを買いました。

また、Linux でゲームをするという体験が中々面白かった(ガジェット感がすごく強い)ので、Steam Deck は、後継機が出たら毎回買おうと心に決めました。

自宅のテレビをもう2年使ったので、今年こそは新しいブラビアを買い替え検討中です。

健康面

明らかに目が悪くなりました。

元々円錐角膜を持っていたのですが、映画館で映画を見る際などに少し辛くなってきたので、思い切って片目だけコンタクトレンズを作りました。(円錐角膜は、ハードコンタクトでの矯正のみなので)

確かに装着すると見え方は変わるのですが、元々生活に支障は無かったので、本当に映画館で映画を観るときだけ使うという感じで、週に一度程度しか使ってません。

これからもどんどん目が悪くなるのかと思うと、どこかで手術も視野に入れないとと思う今日この頃です。

それ以外だと、コロナ禍もあってか体重が人生 MAX を更新しそうになったので、少し減量しました。

74 kg → 57 kg まで落としましたが特に苦になる事も無く、まだまだ目以外は健康そのものだと感じました。

コミュニティ

妻が妊娠していることもあり、家のことは基本自分がやっているので、あまり外に出る時間が取れずオフラインイベントのお手伝い等が手薄になっていました。

もう少し時間を作ってお手伝いに行こうかなと思っています。

登壇したイベントとしては、座談会方式のセッションをやりましたが、個人的にはそれが中々良い感触だったので、定期的にやっていきたいなと思っています。

やっぱり、実際に運用している方の運用話を聞くのはとても良いことだなと思いました。

2022年は、もう少し時間の使い方を最適化して、時間をたくさん作れるようにしようと考えています。

まとめ

2022年は、個人的にはチャレンジングな年だったかなという気がしています。

仕事面では、いつもよりもマネージメント寄りな内容が増えて、改めて自分はマネージメントをやるべきではない人間だという事がはっきりしました。(ので、2023年はもう少しポジションを考えたい)

プライベートでも、子供が出来たり色々ライフステージの移り変わりを感じているので、どうにかなる精神で流れに身を任せようかと思っています。

App Service を Bicep でデプロイする際に、ポータルで設定した Settings の値が消えないようにしたい

この記事は、Qiita Azure Advent Calendar 2022 の 12/20 分の投稿になります。

はじめに

タイトルの通りですが、Bicep を使って App Service を作成・更新する際、Application settings を更新すると、Bicep でデプロイした値のみが Application settings にセットされます。

常に Bicep で Application settings の値が管理されていて、それがすべてという場合は困らないのですが、実際の運用で Application settings の値を一つだけ更新する為だけにわざわざ Bicep でデプロイするというのは、 過剰なのかなという気もします。

また、Bicep には含めないが一時的に設定しておきたい値というのも、実際の運用では発生し得ると思います。

そこで、ポータルで設定された値と Bicep で設定された値を共存させる方法をご紹介します。

実際の動きを見てみよう

はじめに、大前提である Bicep で Application settings を更新すると本当に Bicep に含まれていない値が消えるのか確かめてみましょう。

前提条件

  • 任意のリソースグループを作っておいてください

次の Bicep を使います。

resource plan 'Microsoft.Web/serverfarms@2022-03-01' = {
  name: 'plan-qiita-2022'
  location: resourceGroup().location
  kind: 'app'
  sku: {
    name: 'B1'
  }
}

resource app 'Microsoft.Web/sites@2022-03-01' = {
  name: 'app-qiita-2022'
  location: resourceGroup().location
  kind: 'app'
  properties: {
    serverFarmId: plan.id
    clientCertEnabled: false
    httpsOnly: true
    clientAffinityEnabled: false
    siteConfig: {
      ftpsState: 'Disabled'
      http20Enabled: true
      alwaysOn: true
      netFrameworkVersion: 'v7.0'
      appSettings: [
        {
          name: 'From_Bicep_01'
          value: 'hoge'
        }
        {
          name: 'From_Bicep_02'
          value: 'huga'
        }
      ]
    }
  }
}

// ランタイムが dotnet 7 になるようする設定(気分)
resource metadata 'Microsoft.Web/sites/config@2022-03-01' = {
  name: 'metadata'
  kind: 'string'
  parent: app
  properties: {
    CURRENT_STACK: 'dotnetcore'
  }
}

デプロイコマンドは、覚えてられないので ps1 ファイルにしておきます。

$resourceGroupName = 'rg-qiita-2022'

az deployment group create `
--resource-group $resourceGroupName `
-f main.bicep

この定義をデプロイすると、App Service が作られて、Application settings に次のように2つの設定が入ります。

では、次にポータルから設定を設定しましょう。

次のように、From_Portal_01From_Portal_02 を設定します。

それでは、もう一度 Bicep をデプロイします。

デプロイすると、やはり Bicep に定義されたもののみになります。

解決編

では、ポータルで設定された値と Bicep からデプロイされた値を共存させる方法の解説です。

まず Application settings を更新する為にモジュールを作成します。

このモジュールでは、Bicep から設定する設定と既にセットされている設定をマージして、デプロイする処理を行います。

param appName string

param currentSettings object
param setting object

resource sitConfig 'Microsoft.Web/sites/config@2021-03-01' = {
  name: '${appName}/appsettings'
  properties: union(currentSettings, setting)
}

次に、App Service を作成する Bicep を次のように変更します。

resource plan 'Microsoft.Web/serverfarms@2022-03-01' = {
  name: 'plan-qiita-2022'
  location: resourceGroup().location
  kind: 'app'
  sku: {
    name: 'B1'
  }
}

resource app 'Microsoft.Web/sites@2022-03-01' = {
  name: 'app-qiita-2022'
  location: resourceGroup().location
  kind: 'app'
  properties: {
    serverFarmId: plan.id
    clientCertEnabled: false
    httpsOnly: true
    clientAffinityEnabled: false
    siteConfig: {
      ftpsState: 'Disabled'
      http20Enabled: true
      alwaysOn: true
      netFrameworkVersion: 'v7.0'
    }
  }
}

// ランタイムが dotnet 7 になるようする設定(気分)
resource metadata 'Microsoft.Web/sites/config@2022-03-01' = {
  name: 'metadata'
  kind: 'string'
  parent: app
  properties: {
    CURRENT_STACK: 'dotnetcore'
  }
}

// 現在 settings に設定されている値を取得する
var currentSettings = list('${app.id}/config/appsettings', '2021-03-01').properties
var newSettings = {
  From_Bicep_01: 'hoge'
  From_Bicep_02: 'huga'
}

module settings 'settings.bicep' = {
  name: 'appsettings'
  params: {
    appName: app.name
    setting: newSettings
    currentSettings: currentSettings
  }
}

変更後の Bicep では、list 関数を使って、既存の Application settings に設定されている値を取得しています。

その値と、Bicep からデプロイしたい設定をマージしてデプロイするモジュールに渡すことによって、ポータルで設定された値と Bicep でデプロイする値の共存をしています。

これをデプロイしてみましょう。

共存できていることが確認できるはずです。

なぜモジュールを使うのか?

同一の Bicep ファイルで自身の情報(今回は、Microsoft.Web/sites/config) を取得して、設定する場合、自身に依存することとなり、循環参照のエラーが返されます。

モジュールに分割して、呼び出し元の Bicep ファイルで取得した値を渡すことで、呼び出し先のモジュールにはオブジェクトが渡される為、循環参照に陥らず処理が進みます。

まとめ

ちょっとしたミニプロジェクト程度でしたらこういった問題はあまり発生しないと思います。

ですが、実際に運用しているとポータルでも設定したいといった場面が出てくるかと思います。

そういった時の為に、この記事が役に立ってくれると幸いです。

App Service を Bicep で設定した際に、ポータルで Stack settings に明示的な値が出るようにしたい

App Service を Bicep で構成している際に、Stack settings を表示するのに色々試したので、備忘録を残します。

どういう状況なのか

下の画像のように、何も選択された状態になる

どうなったら正解なのか

次のようにしたい

早速やってみる

パターン1

次のような感じで、siteConfig/metadata に、name : CURRENT_STACKvalue : 'dotnetcore' と入れてあげると、思った通りの挙動になった(色々ごちゃごちゃやってますが、ほとんど関係ないです)

resource app 'Microsoft.Web/sites@2022-03-01' = {
  name: 'app-${workload}-${tenant}-${env}-${location}'
  tags: tags
  location: location
  kind: kind
  properties: {
    serverFarmId: planId
    clientCertEnabled: false
    httpsOnly: true
    clientAffinityEnabled: false
    siteConfig: {
      ftpsState: 'Disabled'
      http20Enabled: true
      alwaysOn: true
      netFrameworkVersion: 'v7.0'
      metadata: [
        {
          name: 'CURRENT_STACK'
          value: 'dotnetcore'
        }
      ]
    }
  }
}

これだと実現できるのですが、siteConfig/metadata は、未定義プロパティなので、警告が出ます。 これはちょっといただけない

パターン2

警告が出ないようにしてみます。

次のような感じで、metadata を分離します。

resource app 'Microsoft.Web/sites@2022-03-01' = {
  name: 'app-${workload}-${tenant}-${env}-${location}'
  tags: tags
  location: location
  kind: kind
  properties: {
    serverFarmId: planId
    clientCertEnabled: false
    httpsOnly: true
    clientAffinityEnabled: false
    siteConfig: {
      ftpsState: 'Disabled'
      http20Enabled: true
      alwaysOn: true
      netFrameworkVersion: 'v7.0'
    }
  }
}

resource symbolicname 'Microsoft.Web/sites/config@2022-03-01' = {
  name: 'metadata'
  kind: 'string'
  parent: app
  properties: {
    CURRENT_STACK: 'dotnetcore'
  }
}

これだと警告が出ません。

まとめ

一件落着

別に設定されて無くてもアプリケーションは動くんですが、一応設定しておくと良いかなと思います。

設定しなかったらパフォーマンスに影響が出るとかがあったら誰か教えてください

Azure Pipeline で YAML が保存されているリポジトリと異なるリポジトリをチェックアウトする

Azure Pipeline でパイプラインを作成する際に、パイプライン定義を保存するリポジトリと、実際のアプリケーションコードのリポジトリを分離したい状況があったので、やり方を備忘録として残します。

どういう状況なのか

  • 複数のアプリケーションが、モノレポではなく、それぞれのリポジトリに分かれている
  • フレームワーク、テクニカルスタックがほぼイコールなので、CI/CD の設定は、ほとんど使い回しが出来る
  • それぞれのリポジトリに別々に定義を書くのは、可能な限り避けたかった

やってみる

一番ミニマムな定義(Starter kit をそのまま使ってます)

pool: {エージェントプール名}

steps:
- checkout: git://{Azure DevOps のプロジェクト名}/{リポジトリ名}

- script: echo Hello, world!
  displayName: 'Run a one-line script'

- script: |
    echo Add other tasks to build, test, and deploy your project.
    echo See https://aka.ms/yaml
  displayName: 'Run a multi-line script'

これで、checkout に指定したリポジトリがチェックアウトされます。

トリガーを使えるようにする

ミニマムな設定だと、トリガーが使えないので、変更でトリガーされるようにしてみます。

resources:
  repositories:
    - repository: {リポジトリをパイプライン内で識別する為の Prefix}
      name: {Azure DevOps のプロジェクト名}/{リポジトリ名}
      ref: main
      type: git
      trigger:
      - main

pool: {エージェントプール名}

steps:
- checkout: {リポジトリをパイプライン内で識別する為の Prefix}

- script: echo Hello, world!
  displayName: 'Run a one-line script'

- script: |
    echo Add other tasks to build, test, and deploy your project.
    echo See https://aka.ms/yaml
  displayName: 'Run a multi-line script'

これで、main ブランチに対する変更でトリガーされるようになります。

次のように checkout を並べれば、複数のリポジトリをチェックアウトすることも出来ます。

resources:
  repositories:
    - repository: {リポジトリをパイプライン内で識別する為の Prefix}
      name: {Azure DevOps のプロジェクト名}/{リポジトリ名}
      ref: main
      type: git
      trigger:
      - main

pool: {エージェントプール名}

steps:
- checkout: self
- checkout: {リポジトリをパイプライン内で識別する為の Prefix}

- script: echo Hello, world!
  displayName: 'Run a one-line script'

- script: |
    echo Add other tasks to build, test, and deploy your project.
    echo See https://aka.ms/yaml
  displayName: 'Run a multi-line script'

チェックアウトすると、各リポジトリ名のフォルダ下に、リポジトリの内容がチェックアウトされます。

まとめ

意外とこういうことをしたい時ってありますよね

Bicep の条件付きデプロイで App Service へ Key Vault の権限を付与する

状況に応じて App Service へ Key Vault の権限を付与する Bicep を書いていたところ、ちょっと詰まったので備忘録

何がしたいのか?

App Service 作成時、パラメータで Key Vault の名前が渡された時だけ Key Vault の権限を設定したい

うまく動かなかった Bicep

keyVaultName が空じゃない場合のみ、アクセスポリシーをデプロイしたいという気持ちで書いたので、次のようにしてみました。

resource kvAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = if(!empty(keyVaultName)) {
  name: '${keyVaultName}/add'
  properties: {
    accessPolicies: [
      {
        objectId: app.identity.principalId
        tenantId: app.identity.tenantId
        permissions: {
          secrets: [
            'get'
            'list'
          ]
          keys: [
            'get'
            'list'
          ]
          certificates: [
            'get'
            'list'
          ]
        } 
      }
    ]
  }
}

何で詰まったのか

ARM テンプレート上で Key Vault の名前を使ってリソースを組み立てようとするので、 Key Vault 名が空だとフォーマットエラーになるようです。

アクセスポリシーは、子要素なので、通常 KeyVault名/add のようになるところが、/add と組み立てられるため、フォーマットエラーとして認識されます。

A nested resource type must have identical number of segments as its resource name. A root resource type must have segment length one greater than its resource name.

ARM テンプレートは、次のように展開されます。

            {
              "condition": "[not(empty(parameters('keyVaultName')))]",
              "type": "Microsoft.KeyVault/vaults/accessPolicies",
              "apiVersion": "2022-07-01",
              "name": "[format('{0}/{1}', parameters('keyVaultName'), 'add')]",
              "properties": {
                "accessPolicies": [
                  {
                    "objectId": "[reference(resourceId('Microsoft.Web/sites', format('app-{0}-{1}-{2}-{3}', parameters('workload'), parameters('tenant'), parameters('env'), parameters('location'))), '2022-03-01', 'full').identity.principalId]",
                    "tenantId": "[reference(resourceId('Microsoft.Web/sites', format('app-{0}-{1}-{2}-{3}', parameters('workload'), parameters('tenant'), parameters('env'), parameters('location'))), '2022-03-01', 'full').identity.tenantId]",
                    "permissions": {
                      "secrets": [
                        "get",
                        "list"
                      ]
                    }
                  }
                ]
              },
              "dependsOn": [
                "[resourceId('Microsoft.Web/sites', format('app-{0}-{1}-{2}-{3}', parameters('workload'), parameters('tenant'), parameters('env'), parameters('location')))]"
              ]
            },

解決策

module に実装を押し込みました。

モジュールは次の通り

param keyVaultName string
param objectId string
param tenantId string
@allowed([
  'get'
  'list'
  'all'
  'backup'
  'delete'
  'purge'
  'recover'
  'restore'
  'set'
])
param secrets array
@allowed([
  'get'
  'list'
  'all'
  'backup'
  'create'
  'decrypt'
  'delete'
  'encrypt'
  'getrotationpolicy'
  'import'
  'list'
  'purge'
  'recover'
  'release'
  'restore'
  'rotate'
  'setrotationpolicy'
  'sign'
  'unwrapKey'
  'update'
  'verify'
  'wrapKey'
])
param keys array
@allowed([
  'get'
  'list'
  'all'
  'backup'
  'create'
  'delete'
  'deleteissuers'
  'getissuers'
  'import'
  'listissuers'
  'managecontacts'
  'manageissuers'
  'purge'
  'recover'
  'restore'
  'setissuers'
  'update'
])
param certificates array

resource kvAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = {
  name: '${keyVaultName}/add'
  properties: {
    accessPolicies: [
      {
        objectId: objectId
        tenantId: tenantId
        permissions: {
          secrets: secrets
          keys: keys
          certificates: certificates
        }
      }
    ]
  }
}

これだと、ARM テンプレートに展開された際に、モジュール呼び出し時の name が name プロパティに差し込まれるのでフォーマットエラーにならないようです。

※ ほんとにこれが根本原因なのかは、ちょっと自信ない

まとめ

Bicep は、結構はまりどころが多いですが、慣れると便利