- 記事一覧 >
- ブログ記事
Bicepを使ってAzure Container AppsとDaprのマイクロサービスをデプロイ
はじめに
前回記事「Node.js,Python,React で Dapr の状態管理アプリを作成してローカル環境で動作確認」のアプリを Azure Container Apps + Dapr へ Bicep、Azure Container Registry、GitHub Actions を使ってデプロイしてみました。一連の手順を紹介していきたいと思います。
Azure Container Apps:フル マネージド サーバーレス コンテナー サービスです。Azure Container Apps を使うと、Kubernetes のオーケストレーションやインフラストラクチャを気にすることなく、コンテナー化されたアプリケーションを実行できます。
Bicep:Bicep は、宣言型の構文を使用して Azure リソースをデプロイするドメイン固有言語 (DSL) です。無人で Azure をいじるときに使うプログラミング言語のようなものです。
GitHub Actions:GitHub Actions は、GitHub が提供する CI/CD サービスです。 GitHub と高度に統合されており、GitHub に公開されたコードを自動でビルド・テスト・デプロイを行うのが主目的です。
Azure Container Registry:Azure のコンテナレジストリです。Docker Hub の Azure 版のようなものです。有料です。
Web アプリ/マイクロサービス:画面は、React をビルドしたものです。node のサーバーで画面、 API 要求( Dapr 経由でアクセス)を振り分けています。マイクロサービスとは言うものの、python の /order API 一つです。これが役割毎に増えていけばマイクロサービスアーキテクチャということです。
【 コンテナレジストリ 】
Azure Container Registry、GitHub Container Registry(ghcr.io)、Docker Registry、etc...の内、今回は、Azure Container Registry を使います。
Dapr についての説明は、前回記事「Node.js,Python,ReactでDaprの状態管理アプリを作成してローカル環境で動作確認」にありますので、省略します。
基本的には、learn.microsoft.com の「チュートリアル: Azure Container Apps に GitHub Actions を使用して Dapr アプリケーションをデプロイする」を見ながら進めましたが、参考にしただけで、同一ではありません。Go で実装されたサービスは省略しました。
また、learn.microsoft.com は、GitHub Container Registry(ghcr.io)を使っていますが、今回は、Azure Container Registry に差し替えています。
本記事情報により何らかの問題が生じても、一切責任を負いません。
コンソールは、Ubuntu 20.04 LTS の bash で作業しています。
git コマンドや az コマンド、npx などはインストール済みで使えるものとします。
ソースコード準備シナリオ
作成済みの全体ソースコードは、 https://github.com/itc-lab/azure-dapr-bicep-simple-app にアップしました。
アプリ作成(前回記事「Node.js,Python,React で Dapr の状態管理アプリを作成してローカル環境で動作確認」で作成したものです。)
↓
コンテナビルドのための Dockerfile
作成
↓
GitHub Actions ワークフローファイル .github/workflows/build-and-deploy.yaml
作成
↓
Azure Container Registry デプロイ用に ./deploy/create-acr.bicep
作成
↓
Azure Container Apps 他、Azure へのリソースデプロイ用に./deploy/main.bicep
./deploy/environment.bicep
./deploy/key-vault.bicep
./deploy/cosmosdb.bicep
./deploy/key-vault-secret.bicep
./deploy/dapr-component.bicep
./deploy/container-http.bicep
作成
と準備していきます。
結果、以下のようになります。
.
├── dapr-components
│ └── local
│ └── statestore.yaml
├── deploy
│ ├── container-http.bicep
│ ├── cosmosdb.bicep
│ ├── create-acr.bicep
│ ├── dapr-component.bicep
│ ├── environment.bicep
│ ├── key-vault.bicep
│ ├── key-vault-secret.bicep
│ └── main.bicep
├── node-service
│ ├── client
│ │ ├── build
│ │ │ ├── asset-manifest.json
│ │ │ ├── favicon.ico
│ │ │ ├── index.html
│ │ │ ├── logo192.png
│ │ │ ├── logo512.png
│ │ │ ├── manifest.json
│ │ │ ├── robots.txt
│ │ │ └── static
│ │ │ ├── css
│ │ │ │ ├── main.073c9b0a.css
│ │ │ │ └── main.073c9b0a.css.map
│ │ │ └── js
│ │ │ ├── 787.c4e7f8f9.chunk.js
│ │ │ ├── 787.c4e7f8f9.chunk.js.map
│ │ │ ├── main.a92ca6f7.js
│ │ │ ├── main.a92ca6f7.js.LICENSE.txt
│ │ │ └── main.a92ca6f7.js.map
│ │ ├── package.json
│ │ ├── package-lock.json
│ │ ├── public
│ │ │ ├── favicon.ico
│ │ │ ├── index.html
│ │ │ ├── logo192.png
│ │ │ ├── logo512.png
│ │ │ ├── manifest.json
│ │ │ └── robots.txt
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── App.css
│ │ │ ├── App.test.tsx
│ │ │ ├── App.tsx
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ ├── logo.svg
│ │ │ ├── react-app-env.d.ts
│ │ │ ├── reportWebVitals.ts
│ │ │ └── setupTests.ts
│ │ └── tsconfig.json
│ ├── Dockerfile
│ ├── index.js
│ ├── package.json
│ └── package-lock.json
└── python-service
├── app.py
├── Dockerfile
└── requirements.txt
Dockerfile 作成
node サービス側の Dockerfile
を準備します。
node サービスは、/order
/delete
のアクセスの時、自身のサイドカーの Dapr へ転送して、結果、python サービスの方へリクエストが行きます。
それ以外のアクセスの時、node-service/client/build/
の生成物(html,js)を返します。
FROM node:17-alpine
WORKDIR /usr/src/app
COPY . .
RUN npm install
# Build the client
RUN \
export REACT_APP_MY_API_URL=$(cat /run/secrets/REACT_APP_MY_API_URL) && \
cd client && npm i && npm run build
EXPOSE 3000
CMD [ "npm", "run", "start" ]
python サービス側の Dockerfile
を準備します。
FROM python:3.9
COPY requirements.txt /app/
WORKDIR /app
RUN pip install -r requirements.txt
COPY . .
ENTRYPOINT ["python"]
EXPOSE 5000
CMD ["app.py"]
app.py
を起動しているだけですので、起動すると、以下の警告が出力されます。ちゃんと WSGI を使ったサーバーにした方が良いと思いますが、あくまでもお試し版ということで、このままでいきます。WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
build-and-deploy.yaml 作成
GitHub Actions パイプラインのワークフローファイル .github/workflows/build-and-deploy.yaml
を作成します。
意味の説明は、以前の記事「Azure Container Appsへbicep,Azure Container Registry,GitHub Actionsを使ってデプロイ」に詳しく書きましたので、そちらを参照してください。このときとやっていることはだいたい同じです。
name: Build and Deploy
on:
push:
branches: [main]
tags: ["v*.*.*"]
paths-ignore:
- "README.md"
- ".vscode/**"
workflow_dispatch:
jobs:
set-env:
name: Set Environment Variables
runs-on: ubuntu-latest
outputs:
version: ${{ steps.main.outputs.version }}
created: ${{ steps.main.outputs.created }}
repository: ${{ steps.main.outputs.repository }}
steps:
- id: main
run: |
echo version=$(echo ${GITHUB_SHA} | cut -c1-7) >> $GITHUB_OUTPUT
echo created=$(date -u +'%Y-%m-%dT%H:%M:%SZ') >> $GITHUB_OUTPUT
echo repository=$GITHUB_REPOSITORY >> $GITHUB_OUTPUT
package-services:
runs-on: ubuntu-latest
needs: set-env
permissions:
contents: read
packages: write
outputs:
containerImage-node: ${{ steps.image-tag.outputs.image-node-service }}
containerImage-python: ${{ steps.image-tag.outputs.image-python-service }}
strategy:
matrix:
services:
[
{ "appName": "node-service", "directory": "./node-service" },
{ "appName": "python-service", "directory": "./python-service" },
]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Log into registry ${{ secrets.REGISTRY_LOGIN_SERVER }}
if: github.event_name != 'pull_request'
uses: azure/docker-login@v1
with:
login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ secrets.REGISTRY_LOGIN_SERVER }}/${{ needs.set-env.outputs.repository }}/${{ matrix.services.appName }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=ref,event=branch
type=sha
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: ${{ matrix.services.directory }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
secrets: |
"REACT_APP_MY_API_URL=${{ secrets.REACT_APP_MY_API_URL }}"
- name: Output image tag
id: image-tag
run: |
echo image-${{ matrix.services.appName }}=$GITHUB_REPOSITORY/${{ matrix.services.appName }}:sha-${{ needs.set-env.outputs.version }} | tr '[:upper:]' '[:lower:]' >> $GITHUB_OUTPUT
deploy:
runs-on: ubuntu-latest
needs: package-services
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy bicep
uses: azure/CLI@v1
with:
inlineScript: |
az deployment group create -g ${{ secrets.RESOURCE_GROUP }} -f ./deploy/main.bicep \
-p \
minReplicas=1 \
nodeImage='${{ secrets.REGISTRY_LOGIN_SERVER }}/${{ needs.package-services.outputs.containerImage-node }}' \
nodePort=3000 \
pythonImage='${{ secrets.REGISTRY_LOGIN_SERVER }}/${{ needs.package-services.outputs.containerImage-python }}' \
pythonPort=5000 \
containerRegistry=${{ secrets.REGISTRY_LOGIN_SERVER }} \
containerRegistryUsername=${{ secrets.REGISTRY_USERNAME }} \
containerRegistryPassword='${{ secrets.REGISTRY_PASSWORD }}'
ACR デプロイ用 Bicep 作成
Azure Container Registry デプロイ用に ./deploy/create-acr.bicep
を作成します。これは、以降の手順で出てきますが、手動で実行します。
@minLength(5)
@maxLength(50)
@description('Provide a globally unique name of your Azure Container Registry')
param acrName string = 'acr${uniqueString(resourceGroup().id)}'
@description('Provide a location for the registry.')
param location string = resourceGroup().location
@description('Provide a tier of your Azure Container Registry.')
param acrSku string = 'Basic'
resource acrResource 'Microsoft.ContainerRegistry/registries@2021-06-01-preview' = {
name: acrName
location: location
sku: {
name: acrSku
}
properties: {
adminUserEnabled: false
}
}
@description('Output the login server property for later use')
output loginServer string = acrResource.properties.loginServer
ACA 他デプロイ用 Bicep 作成
Azure Container Apps 他、Azure へのリソースデプロイ用に./deploy/main.bicep
./deploy/environment.bicep
./deploy/key-vault.bicep
./deploy/cosmosdb.bicep
./deploy/key-vault-secret.bicep
./deploy/dapr-component.bicep
./deploy/container-http.bicep
を準備します。
これは、GitHub Actions の 最後にaz deployment group create -g ${{ secrets.RESOURCE_GROUP }} -f ./deploy/main.bicep・・・
で実行されています。
Key Vault について
今回、Dapr - Cosmos DB 紐付けの為だけに、Key Vault を使っています。
なぜかと言うと、
output primaryMasterKey string = listKeys(accountName_resource.id, accountName_resource.apiVersion).primaryMasterKey
の行が、
function list*(resourceNameOrIdentifier: string, apiVersion: string, [functionValues: object]): any
The syntax for this function varies by name of the list operations. Each implementation returns values for the resource type that supports a list operation. The operation name must start with list. Some common usages are listKeys, listKeyValue, and listSecrets.
と
Outputs should not contain secrets. Found possible secret: function 'listKeys'bicep corehttps://aka.ms/bicep/linter/outputs-should-not-contain-secrets
の警告になったからです。
primaryMasterKey は、daprComponents のデプロイ箇所で以下のように渡す必要があるのですが、秘密の値を output するなと怒られています。
resource stateDaprComponent 'Microsoft.App/managedEnvironments/daprComponents@2022-01-01-preview' = {
name: '${environmentName}/orders'
dependsOn: [
environment
]
properties: {
componentType: 'state.azure.cosmosdb'
version: 'v1'
secrets: [
{
name: 'masterkey'
value: cosmosdb.outputs.primaryMasterKey
}
]
これだけのために、
Key Vault デプロイ
↓
Key Vault Secret に primaryMasterKey の値登録
↓
daprComponents のデプロイ箇所で渡す
としています。
ただし、Key Vault から取り出した値を渡せば良いかと言うとそうでもなく、
resource stateDaprComponent 'Microsoft.App/managedEnvironments/daprComponents@2022-01-01-preview' = {
name: '${environmentName}/orders'
dependsOn: [
environment
]
properties: {
componentType: 'state.azure.cosmosdb'
version: 'v1'
secrets: [
{
name: 'masterkey'
value: kv.getSecret('CosmosDbPrimaryMasterKey')
}
]
のように、kv.getSecret('CosmosDbPrimaryMasterKey')
を直接渡すと、
Function "getSecret" is not valid at this location. It can only be used when directly assigning to a module parameter with a secure decorator.
のエラーになりますので、@secure()
で保護した param を渡す必要がありました。
Bicep 内容
意味の説明は、コメントとして記述しました。ただし、Dapr に関する事、Cosmos DB に関する事、Key Vault に関する事以外は省略しています。その他の部分の説明は、以前の記事「Azure Container Appsへbicep,Azure Container Registry,GitHub Actionsを使ってデプロイ」に詳しく書きましたので、そちらを参照してください。
param location string = resourceGroup().location
param environmentName string = 'env-${uniqueString(resourceGroup().id)}'
param appName string = 'hello-dapr'
param minReplicas int = 0
param nodeImage string
param nodePort int = 3000
var nodeServiceAppName = 'node-app'
param pythonImage string
param pythonPort int = 5000
var pythonServiceAppName = 'python-app'
param isPrivateRegistry bool = true
param containerRegistry string
@secure()
param containerRegistryUsername string = ''
@secure()
param containerRegistryPassword string = ''
#disable-next-line secure-secrets-in-params
param registryPassword string = 'registry-password'
// Container Apps Environment
module environment 'environment.bicep' = {
name: '${deployment().name}--environment'
params: {
environmentName: environmentName
location: location
appInsightsName: '${environmentName}-ai'
logAnalyticsWorkspaceName: '${environmentName}-la'
}
}
// Key Vault
module keyvault 'key-vault.bicep' = {
name: 'keyvault-deployment'
params: {
location: location
appName: appName
tenantId: tenant().tenantId
}
}
// Cosmosdb
module cosmosdb 'cosmosdb.bicep' = {
name: '${deployment().name}--cosmosdb'
params: {
location: location
primaryRegion: location
keyVaultName: keyvault.outputs.keyVaultName
}
}
resource kv 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
name: keyvault.outputs.keyVaultName
}
// Dapr Component
// マネージド環境で Dapr コンポーネントを作成
module daprComponent 'dapr-component.bicep' = {
name: '${deployment().name}--daprComponent'
// dependsOn: (module[] | (resource | module) | resource[])[]
// リソースをデプロイするとき、一部のリソースが他のリソースより前に確実にデプロイされるようにすることが必要な場合があります。
// たとえば、データベースをデプロイする前に論理 SQL
// このリレーションシップは、あるリソースが他のリソースに依存しているとマークすることで確立します。
// 明示的な依存関係は、dependsOn プロパティで宣言されます。
dependsOn: [
environment
keyvault
]
params: {
cosmosDbPrimaryMasterKey: kv.getSecret('CosmosDbPrimaryMasterKey')
documentEndpoint: cosmosdb.outputs.documentEndpoint
environmentName: environmentName
pythonServiceAppName: pythonServiceAppName
}
}
// Python App
module pythonService 'container-http.bicep' = {
name: '${deployment().name}--${pythonServiceAppName}'
dependsOn: [
environment
]
params: {
enableIngress: true
isExternalIngress: false
location: location
environmentName: environmentName
containerAppName: pythonServiceAppName
containerImage: pythonImage
containerPort: pythonPort
isPrivateRegistry: isPrivateRegistry
minReplicas: minReplicas
containerRegistry: containerRegistry
registryPassword: registryPassword
containerRegistryUsername: containerRegistryUsername
revisionMode: 'Single'
secrets: [
{
name: registryPassword
value: containerRegistryPassword
}
]
}
}
// Node App
module nodeService 'container-http.bicep' = {
name: '${deployment().name}--${nodeServiceAppName}'
dependsOn: [
environment
]
params: {
enableIngress: true
isExternalIngress: true
location: location
environmentName: environmentName
containerAppName: nodeServiceAppName
containerImage: nodeImage
containerPort: nodePort
minReplicas: minReplicas
isPrivateRegistry: isPrivateRegistry
containerRegistry: containerRegistry
registryPassword: registryPassword
containerRegistryUsername: containerRegistryUsername
revisionMode: 'Multiple'
env: [
{
name: 'PYTHON_SERVICE_NAME'
value: pythonServiceAppName
}
]
secrets: [
{
name: registryPassword
value: containerRegistryPassword
}
]
}
}
output nodeFqdn string = nodeService.outputs.fqdn
output pythonFqdn string = pythonService.outputs.fqdn
param environmentName string
param logAnalyticsWorkspaceName string
param appInsightsName string
param location string
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-03-01-preview' = {
name: logAnalyticsWorkspaceName
location: location
properties: any({
retentionInDays: 30
features: {
searchVersion: 1
}
sku: {
name: 'PerGB2018'
}
})
}
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: appInsightsName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
WorkspaceResourceId:logAnalyticsWorkspace.id
}
}
resource environment 'Microsoft.App/managedEnvironments@2022-03-01' = {
name: environmentName
location: location
properties: {
daprAIInstrumentationKey:appInsights.properties.InstrumentationKey
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: logAnalyticsWorkspace.properties.customerId
sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey
}
}
}
}
output location string = location
output environmentId string = environment.id
param appName string
// Key Vault名(最大長24文字)
// 今回の場合、kv-hello-dapr-q4******gv(リソースグループID)
@maxLength(24)
param vaultName string = '${'kv-'}${appName}-${substring(uniqueString(resourceGroup().id), 0, 23 - (length(appName) + 3))}' // must be globally unique
param location string = resourceGroup().location
param sku string = 'Standard'
param tenantId string // テナントID
// 下記参照(渡しているところに説明あり)
param enabledForDeployment bool = true
param enabledForTemplateDeployment bool = true
param enabledForDiskEncryption bool = true
param enableRbacAuthorization bool = true
param softDeleteRetentionInDays int = 90
// ネットワークアクセスルール(特に無し)
param networkAcls object = {
ipRules: []
virtualNetworkRules: []
}
resource keyvault 'Microsoft.KeyVault/vaults@2022-07-01' = {
name: vaultName
location: location
properties: {
tenantId: tenantId
sku: {
family: 'A'
name: sku
}
// シークレットとして格納されている証明書をキー コンテナーから取得することを
// Azure Virtual Machines に許可するかどうかを指定するプロパティ
enabledForDeployment: enabledForDeployment
// Azure Disk Encryption がコンテナーからシークレットを取得し、
// キーをアンラップすることを許可するかどうかを指定するプロパティ
enabledForDiskEncryption: enabledForDiskEncryption
// Azure Resource Manager がキー コンテナーからシークレットを
// 取得することを許可するかどうかを指定するプロパティ
enabledForTemplateDeployment: enabledForTemplateDeployment
// softDelete(論理削除)データ保持日数(7以上、90以下)
softDeleteRetentionInDays: softDeleteRetentionInDays
// データ アクションの承認方法を制御するプロパティ。
// true の場合、キー コンテナーはデータ アクションの承認に役割ベースの
// アクセス制御 (RBAC) を使用し、コンテナーのプロパティで指定された
// アクセス ポリシーは無視されます (警告: これはプレビュー機能です)。
// false の場合、キー コンテナーはコンテナーのプロパティで指定された
// アクセス ポリシーを使用し、Azure Resource Manager に格納されている
// ポリシーはすべて無視されます。 null または指定されていない場合、
// vault はデフォルト値の false で作成されます。 管理アクションは常に
// RBAC で承認されることに注意してください。
enableRbacAuthorization: enableRbacAuthorization
// 特定のネットワークの場所からキー コンテナーへのアクセスを管理する規則
networkAcls: networkAcls
}
}
output keyVaultName string = keyvault.name
output keyVaultId string = keyvault.id
// function description(text: string): any
// @description というデコレータをつけて、パラメータの説明を記載
// @description デコレータによる説明は、パラメータ入力で ? を入力した際に表示される
// VSCode拡張機能の場合、マウスオーバー時の説明に表示される。
@description('Cosmos DB アカウント名、最大長 44 文字、小文字')
param accountName string = 'cosmos-${uniqueString(resourceGroup().id)}'
@description('Cosmos DB アカウントのロケーション')
param location string
@description('Cosmos DB アカウントのプライマリ レプリカ リージョン')
param primaryRegion string
@description('Cosmos DB アカウントの既定の整合性レベル')
// @allowed
// パラメーターに使用できる値を定義できます。 使用できる値は配列で指定します。
// 使用できる値の 1 つではない値がパラメーターに渡された場合、検証時にデプロイは失敗します。
// ここに書かれているのは、Cosmos DB 整合性の種類
// Eventual(最終的)
// Consistent Prefix(一貫性のあるプレフィックス)
// Session(セッション)
// Bounded Staleness(有界整合性制約)
// Strong(強固)
@allowed([
'Eventual'
'ConsistentPrefix'
'Session'
'BoundedStaleness'
'Strong'
])
param defaultConsistencyLevel string = 'Session'
// @minValue
// @maxValue
// 文字列と配列のパラメーターの最小長と最大長を指定できます。 一方または両方の制約を設定できます。
// 文字列の場合、長さは文字数を示します。 配列の場合、長さは配列内の項目数を示します。
@description('古いリクエストの最大数。BoundedStaleness(有界整合性制約)に必要です。有効な範囲、シングル リージョン: 10 ~ 1000000。マルチ リージョン: 100000 ~ 1000000。')
@minValue(10)
@maxValue(2147483647)
param maxStalenessPrefix int = 100000
@description('最大遅延時間 (分)。BoundedStaleness(有界整合性制約)に必要です。有効な範囲、シングル リージョン: 5 ~ 84600。マルチ リージョン: 300 ~ 86400。')
@minValue(5)
@maxValue(86400)
param maxIntervalInSeconds int = 300
@description('データベースの名前')
param databaseName string = 'ordersDb'
// ここでいうコンテナとは、Cosmos DBの「コンテナ」(テーブルに相当)
@description('コンテナの名前')
param containerName string = 'orders'
// コンテナーの最大スループット
@description('Maximum throughput for the container')
@minValue(4000)
@maxValue(1000000)
param autoscaleMaxThroughput int = 4000
param keyVaultName string
// 指定された文字列を小文字に変換します。
var accountNameVar = toLower(accountName)
var consistencyPolicy = {
Eventual: {
// ConsistencyLevel=整合性レベル
// Eventual (最終的)
// Write が複数発生した場合の順序保証無し
// https://qiita.com/everpeace/items/cbbace418f7bc297631f
// 読み込めるデータが 最新である保証はない
// 読み込むプロセスによって(同一プロセスであっても)は 先祖返りするかもしれない
// でも、すべてのreplicaが いつか同じ状態に収束する
// これは5つの内一番整合性が弱いかわりに読込書込がとても速い
defaultConsistencyLevel: 'Eventual'
}
// 一貫性のあるプレフィックス
// 読み込めるデータが最新である保証はない,先祖返りするかもしれない, いつか同じ状態に収束する のはEventualと同じ
// それに加えてreplicaに適用される書込リクエストの順序は同じ
// 例えば、A, B, Cという書込リクエストが複数のreplicaに適用されるとすると、クライアントからみると、
// A, A,B, A,B,Cと書込リクエストが処理されたデータは読み込まれる可能性はあるが、
// A,Cとか、B,A,Cという風な順で書込がなされたデータが見える可能性はない
ConsistentPrefix: {
defaultConsistencyLevel: 'ConsistentPrefix'
}
// セッション
// Consistent Prefixは、先祖返りが起きたり、
// 読込リクエストごとに見えるデータの世代が違う、ことが起きるけれど、
// Sessionでは、いわゆる
// Monotonic Read, Monotonic Write, Read Your own Write(RYW)を保証する
// Monotonic Read: あるプロセスから見て読み込めるデータは先祖返りしない
// Monotonic Write: あるプロセスからなされるデータXへの書込順序は保存される
// Read Your own Write(RYW): あるプロセスが書込処理を行ったら、
// そのプロセスはすぐその書込処理完了後のデータが読める
Session: {
defaultConsistencyLevel: 'Session'
}
// 有界整合性制約
// 読み込めるデータは古いことはあるが、
// t単位時間以降はK世代以内のデータが読めることを保証する
// ユーザはこのK, tをチューニングできる。
// この整合性レベルは、強い整合性を求めたいけどデータの
// availabilityが99.99%でよくて、かつ低レイテンシが欲しい場合に有効
BoundedStaleness: {
defaultConsistencyLevel: 'BoundedStaleness'
// 古いリクエストの最大数。 BoundedStaleness に必要。
// 有効な範囲、シングル リージョン: 10 ~ 1000000。
// マルチ リージョン: 100000 ~ 1000000。
maxStalenessPrefix: maxStalenessPrefix
// 最大遅延時間 (分)。 BoundedStaleness に必要。
// 有効な範囲、シングル リージョン: 5 ~ 84600。マルチ リージョン: 300 ~ 86400。
maxIntervalInSeconds: maxIntervalInSeconds
}
// 厳密、強固
// いわゆるLinearizabileを保証する、あるプロセスが書き込めたら、
// どのプロセスが読んでも常に最新の値が読める。
// majority quorumで実装できる
// Linerizabile(線形化可能性)
// 並行プログラミングにおいて操作(または操作の集合)は、
// 呼び出しイベントと応答イベント
// (コールバック)の順序付きリストで構成されており、
// 応答イベントを追加することで以下のように拡張できる場合、線形化可能である。
// 1.拡張されたリストは逐次履歴として再表現することができる(直列化可能である)。
// 2.その逐次履歴は元の拡張されていないリストの部分集合である。
// quorumとは分散システムにおいて、分散トランザクションが処理を
// 実行するために必要な最低限の票の数である。
// quorumベースの技術は分散システムにおいて、処理の整合性をとるために実装される。
Strong: {
defaultConsistencyLevel: 'Strong'
}
}
var locations = [
{
locationName: primaryRegion
// フェールオーバー優先度
failoverPriority: 0
// ゾーン冗長
isZoneRedundant: false
}
]
// Cosmos DB 作成
// データベースアカウント
// Azure Cosmos DB リソース モデル
// https://learn.microsoft.com/ja-jp/azure/cosmos-db/account-databases-containers-items
resource accountName_resource 'Microsoft.DocumentDB/databaseAccounts@2021-01-15' = {
name: accountNameVar
// 作成する Cosmos DB データベース アカウントの種類
// GlobalDocumentDB, MongoDB, Parse
kind: 'GlobalDocumentDB'
// リソースが属するリソース グループの場所
location: location
properties: {
// consistencyPolicy=整合性についての設定
// 上で、param defaultConsistencyLevel string = 'Session' があるため、
// 今回の場合、Session。
consistencyPolicy: consistencyPolicy[defaultConsistencyLevel]
locations: locations
// 申し込みタイプ?Standardしかない?
databaseAccountOfferType: 'Standard'
}
}
// Cosmos DB 子リソース 個別のデータベース
resource accountName_databaseName 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2021-01-15' = {
parent: accountName_resource
name: databaseName
// Azure Cosmos DB SQL データベースを作成および更新するためのプロパティ
properties: {
resource: {
// Cosmos DB SQL データベースの名前
id: databaseName
}
}
}
// Cosmos DB 子リソース 個別のデータベース コンテナ―
// データが格納される場所
resource accountName_databaseName_containerName 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2021-01-15' = {
parent: accountName_databaseName
name: containerName
// Azure Cosmos DB コンテナを作成および更新するためのプロパティ
properties: {
resource: {
// Cosmos DB SQL コンテナの名前
id: containerName
// 論理パーティション(パーティションキー)
partitionKey: {
// コンテナー内のデータをパーティション分割できるパスのリスト
paths: [
'/partitionKey'
]
// kind: 'Hash' | 'MultiHash' | 'Range' | string
// パーティショニングに使用されるアルゴリズムの種類。
// MultiHash の場合、コンテナの作成で複数のパーティション キー (最大 3 つまで) が
// サポートされる。
kind: 'Hash'
}
}
options: {
autoscaleSettings: {
// maxThroughput: int
// リソースがスケールアップできる最大スループット。
// 上で定義されている4000。param autoscaleMaxThroughput int = 4000
maxThroughput: autoscaleMaxThroughput
}
}
}
}
// Key Vault に primaryMasterKey 格納
module setCosmosDbPrimaryMasterKey 'key-vault-secret.bicep' = {
name: 'setCosmosDbPrimaryMasterKey'
params: {
// Key Vault の名前
keyVaultName: keyVaultName
// キー名
secretName: 'CosmosDbPrimaryMasterKey'
// 値
secretValue: listKeys(accountName_resource.id, accountName_resource.apiVersion).primaryMasterKey
}
}
output documentEndpoint string = accountName_resource.properties.documentEndpoint
// Cosmos DB のエンドポイント。
// 以下のように Dapr の設定に使用。
// metadata: [
// {
// name: 'url'
// value: documentEndpoint
// }
param keyVaultName string
param secretName string
@secure()
param secretValue string
// 渡されたシークレットの キーと値をKey Vault に格納
// 今回は、Cosmos DB の PrimaryMasterKey のみに使用。
resource keyVaultSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
name: '${keyVaultName}/${secretName}'
properties: {
value: secretValue
}
}
param environmentName string
param documentEndpoint string
param pythonServiceAppName string
@secure()
param cosmosDbPrimaryMasterKey string = ''
// マネージド環境で Dapr コンポーネントを作成
resource stateDaprComponent 'Microsoft.App/managedEnvironments/daprComponents@2022-01-01-preview' = {
name: '${environmentName}/orders'
// Dapr コンポーネント リソース固有のプロパティ
properties: {
// コンポーネントの種類
// 他の例(何を見たら分かる?)
// secretstores.azure.keyvault
// pubsub.azure.servicebus
// state.azure.blobstorage
// bindings.cron
// bindings.smtp
componentType: 'state.azure.cosmosdb'
// コンポーネントバージョン
version: 'v1'
// Dapr コンポーネントによって使用されるシークレットのコレクション
secrets: [
{
name: 'masterkey'
value: cosmosDbPrimaryMasterKey
}
]
// コンポーネントメタデータ
// Cosmos DBの接続情報
metadata: [
{
name: 'url'
value: documentEndpoint
}
{
name: 'database'
value: 'ordersDb'
}
{
name: 'collection'
value: 'orders'
}
{
name: 'masterkey'
// メタデータ プロパティ値を取得する Dapr コンポーネント シークレットの名前。
secretRef: 'masterkey'
}
]
// この Dapr コンポーネントを使用できるコンテナー アプリの名前
scopes: [
pythonServiceAppName
]
}
}
param containerAppName string
param location string
param environmentName string
param containerImage string
param containerPort int
param isExternalIngress bool
param containerRegistry string
@secure()
param containerRegistryUsername string
param isPrivateRegistry bool
param enableIngress bool = true
@secure()
param registryPassword string
param minReplicas int = 0
param secrets array = []
param env array = []
param revisionMode string = 'Single'
resource environment 'Microsoft.App/managedEnvironments@2022-03-01' existing = {
name: environmentName
}
resource containerApp 'Microsoft.App/containerApps@2022-03-01' = {
name: containerAppName
location: location
properties: {
managedEnvironmentId: environment.id
configuration: {
activeRevisionsMode: revisionMode
secrets: secrets
registries: isPrivateRegistry ? [
{
server: containerRegistry
username: containerRegistryUsername
passwordSecretRef: registryPassword
}
] : null
ingress: enableIngress ? {
external: isExternalIngress
targetPort: containerPort
transport: 'auto'
traffic: [
{
latestRevision: true
weight: 100
}
]
} : null
dapr: {
enabled: true
appPort: containerPort
appId: containerAppName
}
}
template: {
containers: [
{
image: containerImage
name: containerAppName
env: env
}
]
scale: {
minReplicas: minReplicas
maxReplicas: 1
}
}
}
}
output fqdn string = enableIngress ? containerApp.properties.configuration.ingress.fqdn : 'Ingress not enabled'
コンテナレジストリ作成
コンテナを push できる場所のコンテナレジストリ(Azure Container Registry のインスタンス)を作成します。
Azure CLI で、サインインします。
$ az login --use-device-code
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code H*******W to authenticate.
https://microsoft.com/devicelogin
にブラウザでアクセスして、表示されているコード(H*******W
)を入力して、サインインします。
Azure CLI 最新版を使っているか確認します。
$ az upgrade
東日本リージョン japaneast
(任意)に
リソースグループ my-containerapp-store
(任意)を作成します。
$ az group create --name my-containerapp-store --location japaneast
作成したリソースに Azure Container Registry のインスタンスを作成します。
$ az deployment group create --resource-group my-containerapp-store --template-file ./deploy/create-acr.bicep --parameters acrName=hellodapracrtest
ここで、bicepを使っていますが、特に重要な意味はありません。普通にコマンドラインで作成しても良いです。
コマンドライン:
az acr create --resource-group $RES_GROUP --name $ACR_NAME --sku Basic --location japaneast
my-containerapp-store
は、先ほど作成したリソースグループです。hellodapracrtest
は任意ですが、以下の注意点があります。
注意:
・acrName=hellodapracrtest
とすると、hellodapracrtest.azurecr.io
が作成されます。これは、全世界で重複しない必要があります。重複すると、以下のエラーになります。
The registry DNS name hellodapracrtest.azurecr.io is already in use. You can check if the name is already claimed using following API:
・acrName=hello-dapr-acr-test
のように記号は使えません。アルファベットと数字のみです。また、5 ~ 50 文字である必要があります。名前がまずい場合、以下のエラーになります。
Invalid resource name: 'hello-dapr-acr-test'. Resource names may contain alpha numeric characters only and must be between 5 and 50 characters.. For more information, please refer resource name requirements at
サービスプリンシパル作成
共同作成者のロールを持ち、コンテナー レジストリのリソース グループをスコープとするサービス プリンシパルを作成します。
$ groupId=$(az group show \
--name my-containerapp-store \
--query id --output tsv)
$ az ad sp create-for-rbac \
--scope $groupId \
--role Contributor \
--sdk-auth
my-containerapp-store
は、先ほど作成したリソースグループ名です。
成功したら、以下のような出力が得られます。 この出力は後で使います。
{
"clientId": "d9******-****-****-****-**********e3",
"clientSecret": "V9************************************wS",
"subscriptionId": "ea******-****-****-****-**********80",
"tenantId": "0c******-****-****-****-**********2b",
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
"resourceManagerEndpointUrl": "https://management.azure.com/",
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}
Azure サービス プリンシパルの資格情報を更新して、コンテナー レジストリに対するプッシュとプルのアクセスを許可します。
この手順により、GitHub ワークフローでサービス プリンシパルを使用して、コンテナー レジストリに対する認証と、
Docker イメージのプッシュおよびプルを実行できます。
コンテナー レジストリのリソース ID を取得します。
環境変数 registryId
に一旦、先ほど作成した Azure Container Registry のインスタンス のリソースIDを格納します。
$ registryId=$(az acr show \
--name hellodapracrtest \
--resource-group my-containerapp-store \
--query id --output tsv)
$ echo $registryId
/subscriptions/ea0c9***-****-****-****-*******a0c80/resourceGroups/my-containerapp-store/providers/Microsoft.ContainerRegistry/registries/hellodapracrtest
my-containerapp-store
は、先ほど作成したリソースグループ名です。hellodapracrtest
は、先ほど作成した Azure Container Registry です。
az role assignment create
を使用して、レジストリに対するプッシュおよびプル アクセスを付与する AcrPush ロールを割り当てます。
$ az role assignment create \
--assignee d9******-****-****-****-**********e3 \
--scope $registryId \
--role AcrPush
--assignee d9******-****-****-****-**********e3
のところは、先ほど JSON 出力にあった clientId です。
リソースプロバイダーの登録
今回、Key Vault を使うのですが、リソースプロバイダーに登録が無いと、以下のエラーになります。
The subscription is not registered to use namespace 'Microsoft.KeyVault'.
このエラーにより、デプロイに失敗しますので、リソースプロバイダーを登録しておきます。
$ az provider register --namespace Microsoft.KeyVault
Registering is still on-going. You can monitor using 'az provider show -n Microsoft.KeyVault'
GitHub 準備
リポジトリ作成
GitHub にプライベートリポジトリ aca-app-repo
を作成します。(リポジトリの作り方については省略します。)
リポジトリ名は任意です。(サービスプリンシパルのアプリ名に合わせる必要もありません。)
Personal access tokens
・GitHub ワークフロー実行権限(workflow
)
・GitHub Container Registry への push 権限(write:packages
)
を有効にします。
Settings -> 左下の Developer settings -> Personal access tokens -> Tokens (classic)
にて、workflow
と write:packages
にチェックを入れ、Update token
をクリックします。
シークレット
シークレット(ワークフロー内で使う秘密の値)を設定します。
リポジトリ aca-app-repo
に戻って、
Settings -> Secrets -> Actions -> New repository secret をクリックします。
ここに、以下を設定します。
AZURE_CREDENTIALS:先ほどのサービス プリンシパルの作成の JSON 出力全体
REGISTRY_LOGIN_SERVER:レジストリのログイン サーバー名(今回は、hellodapracrtest.azurecr.io
)
REGISTRY_USERNAME:サービス プリンシパルの作成 JSON 出力の clientId(今回は、d9******-****-****-****-**********e3
)
REGISTRY_PASSWORD:サービス プリンシパルの作成 JSON 出力の clientSecret(今回は、V9************************************wS
)
RESOURCE_GROUP:サービス プリンシパルのスコープ指定に使用したリソース グループの名前(今回は、my-containerapp-store
)
commit & push
リポジトリ aca-app-repo
main ブランチに push します。
$ rm -rf node-service/client/.git
$ git init
$ git config --local user.name "AAAAA BBBBB"
$ git config --local user.email "xxxxx@example.com"
$ git add .
$ git commit -m "first commit"
$ git branch -M main
$ git remote add origin https://github.com/<github user name>/aca-app-repo.git
$ git push -u origin main
<github user name>
部分は、GitHub ユーザー名です。
Run workflow
Actions -> Build and Deploy -> Run workflow
をクリックして、Branch: main
のままにして、Run workflow をクリックします。
Set Environment Vairables
ジョブ
後続のジョブで使用する値を生成しています。
↓package-services
ジョブ
docker コンテナをビルドして、コンテナレジストリ(今回の場合、hellodapracrtest.azurecr.io
)にビルドしたコンテナを push しています。
今回、コンテナが2つありますので、2つ並行して実行されます。
↓deploy
ジョブ
Azure リソースをデプロイしています。
(既にデプロイ済みの場合は、Azure Container Apps アプリ node-app
のリビジョンが一つ増えます。python-app
の方は、シングルリビジョンのため、リビジョンは増えません。)
と進んで、全て緑色のチェックが付いたら、デプロイ完了です。
deploy
ジョブで作成されるリソースは、以下です。
・Key Vault
・Azure Cosmos DB
・ログ分析ワークスペース
・Application Insights
・Azure Container Apps の環境
・Azure Container Apps のアプリ(node-app
と python-app
)
Azure Container Apps の2つのアプリ名は、./deploy/main.bicep
のvar nodeServiceAppName = 'node-app'
var pythonServiceAppName = 'python-app'
に書かれています。(アプリ名は任意です。Dapr は、この名前で通信します。何でもいいように、環境変数になっています。)
API の URL について
画面 →https://<デプロイにより得られたFQDN>/order
画面 →https://<デプロイにより得られたFQDN>/delete
という API アクセスがあるのですが、<デプロイにより得られたFQDN>
はデプロイ後に決まるため、React をビルドしている最中には分かりません。
そのため、以下の作業を行い、再度、パイプライン起動が必要です。
(FQDN が最初から決まっている場合、最初から REACT_APP_MY_API_URL を Secrets に登録しておけば、問題無いです。)
node-app
の概要より、アプリケーション URL をコピーします。
リポジトリの Secrets に以下の値を登録します。
REACT_APP_MY_API_URL:<コピーしたアプリケーション URL>
再度、ワークフローを起動します。
$ git add .
$ git commit -m "second commit"
$ git push -u origin main
注意:
Re-run jobs でもう一度ワークフローを起動しても Azure Container Apps のリビジョン名が commit id に関わっているため、更新されません。ここでは、改行を増やすなど、些細な修正をしたものとします。
動作確認
1. データ無しの状態で、GET してみます。
データが無いという結果が返りました。
2. POST してデータを入れます。
実装を最小限にするため、データは固定になっています。
3. 再びデータを GET します。
データが取り出されました。
4. データを DELETE します。
5. 再びデータを GET します。 データ無しに戻ります。
一連の作成物は、以下のコマンドでリソースグループごと削除することで、まとめて削除できます。
$ az group delete \
--resource-group my-containerapp-store
Key Vault については、消したことを覚えているため、以下のように完全に削除します。
$ az keyvault list-deleted
[
{
"id": "/subscriptions/ea******-****-****-****-**********80/providers/Microsoft.KeyVault/locations/japaneast/deletedVaults/kv-hello-dapr-q4******gv",
"name": "kv-hello-dapr-q4******gv",
"properties": {
・・・
$ az keyvault purge --name kv-hello-dapr-q4******gv
削除しないと、再度同じ名前でデプロイする機会があったときに以下のエラーになります。
A vault with the same name already exists in deleted state. You need to either recover or purge existing key vault.
マルチリビジョンや、トラフィックの変更については、こちらで触れていますので、省略します。
↓
Azure Container Apps へ bicep,Azure Container Registry,GitHub Actions を使ってデプロイ - 動作確認1~
ヨシ!
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。