Enterprise Ready (?) な Minecraft サーバーを構築する
変更履歴
- 2025-12-20: Bicep テンプレート全体と、監視について追記
こんにちわ。
Microsoft Advent Calendar 2025 の 12 月 19 日の記事です🎄

2年前に Azure 上へ Minecraft サーバーを構築する話をしてました [1]。
前回は VM に展開しただけでセキュリティ面、管理性など考慮が薄かったと思います。
今回は、よりセキュアで、Azure のベストプラクティスにばっちり沿った設定がされている Azure Verified Module (AVM) [2] と呼ばれる Infrastructure-as-Code (IaC) モジュールを使って構築します。
Azure Verified Module とは
はじめに、Azure Verified Module (AVM) とは、Microsoft が主導する Infrastructure-as-Code (IaC) モジュールの標準化プロジェクトです。Bicep と Terraform の両方に対応しており、Azure リソースをデプロイするための再利用可能なモジュールが公式レジストリで公開されています。
このモジュールは Microsoft によって開発・保守されており、Well-Architected Framework [3] の推奨事項に沿った設定がデフォルトで適用されます。これにより、セキュリティやパフォーマンスのベストプラクティスを意識しなくても、ある程度の品質が担保された状態でリソースをデプロイできます。
また、各モジュールは設計、実装、テスト、ドキュメント生成、レビューという一連のプロセス[4]を経て公開されています。その点においてもプロジェクトなどで採用しやすいのではないでしょうか。
Bicep の場合、AVM モジュールは Bicep Public Module Registry で公開されており、br/public:avm/res/<provider>/<resource>:<version> の形式で参照できます。たとえば仮想ネットワークのモジュールであれば br/public:avm/res/network/virtual-network:0.5.2 となります。
なお、Visual Studio Code の Bicep 拡張機能を使用している場合、モジュールは参照時にローカルにキャッシュされます [5]。

Visual Studio Code で使っている場合、カーソルを対象モジュールの箇所に重ねることで、モジュールのドキュメントにジャンプすることもできますので、必須/オプションパラメータの説明やサンプルコードを確認できます。

アーキテクチャの全体像
それでは今回構築する全容を紹介します。
アーキテクチャとしては、Azure Container Apps 上で Minecraft サーバーを動作させ、インターネットからのアクセスを Azure Firewall で受け付ける構成となっています。

この構成のポイントは、Container Apps 環境を内部 (internal) モードでデプロイしている点にあります。Container Apps の内部環境は直接インターネットに公開されないため、外部からのアクセスは Azure Firewall の DNAT ルールを経由して受け付けます。これにより、ファイアウォールでトラフィックを一元的に制御しつつ、Minecraft サーバーへの接続を実現しています。
また、ワールドデータを永続化するために Azure Files を使用し、Private Endpoint 経由でセキュアにアクセスする構成としています。
使用する AVM モジュール
この構成では以下の AVM モジュールを使用します。
| モジュール | バージョン | 用途 |
|---|---|---|
| avm/res/network/virtual-network | 0.7.2 | 仮想ネットワークとサブネットの作成 |
| avm/res/network/private-dns-zone | 0.8.0 | Storage Account 用のプライベート DNS ゾーン |
| avm/res/network/public-ip-address | 0.10.0 | Azure Firewall 用のパブリック IP アドレス |
| avm/res/network/azure-firewall | 0.9.2 | Azure Firewall |
| avm/res/network/firewall-policy | 0.3.4 | ファイアウォールポリシーとルール |
| avm/res/operational-insights/workspace | 0.9.1 | Log Analytics ワークスペース |
| avm/res/storage/storage-account | 0.30.0 | Storage Account (Azure Files) |
| avm/res/app/managed-environment | 0.8.1 | Container Apps 環境 |
| avm/res/app/container-app | 0.12.0 | Minecraft コンテナアプリ |
仮想ネットワークの構成
仮想ネットワークは 4 つのサブネットで構成されます。
module vnet 'br/public:avm/res/network/virtual-network:0.7.2' = {
name: '${time}-privateVnet'
params: {
name: vnetName
addressPrefixes: vnetAddressPrefixes
subnets: [
{
name: subnetAzureFirewallName
addressPrefix: subnetAzureFirewallPrefix
}
{
name: subnetStorageName
addressPrefix: subnetStoragePrefix
privateLinkServiceNetworkPolicies: 'Enabled'
defaultOutboundAccess: false
}
{
name: subnetWebName
addressPrefix: subnetWebPrefix
delegation: 'Microsoft.App/environments'
}
{
name: subnetAzureFirewallManagementName
addressPrefix: subnetAzureFirewallManagementPrefix
}
]
}
}
AzureFirewallSubnet は Azure Firewall のデータプレーン用のサブネットです。Azure Firewall はこの名前のサブネットを必要とするため、名前を変更することはできません。
storage サブネットは Storage Account の Private Endpoint を配置するためのサブネットです。
app サブネットは Container Apps 環境用のサブネットであり、delegation: 'Microsoft.App/environments' を指定して Container Apps への委任を設定しています。Container Apps のワークロードプロファイル環境では /27 以上のサイズが必要となります。
Azure Firewall とファイアウォールポリシー
Azure Firewall は、仮想ネットワークのトラフィックを制御するためのクラウドネイティブなファイアウォールサービスです。この構成では Standard SKU を使用し、ファイアウォールポリシーでルールを定義しています。
module firewallPolicy 'br/public:avm/res/network/firewall-policy:0.3.4' = {
name: 'firewallPolicyDeployment'
params: {
// Required parameters
name: azfwpName
// Non-required parameters
allowSqlRedirect: true
snat: {
autoLearnPrivateRanges: 'Enabled'
}
location: location
enableTelemetry: true
enableProxy: true
ruleCollectionGroups: [
// 省略
]
tier: 'Standard'
}
}
ファイアウォールポリシーには 2 つのルールコレクショングループを定義しています。
1 つ目の outbound グループでは、Container Apps サブネットからのアウトバウンドトラフィックを制御します。HTTP/HTTPS のアウトバウンドを許可するアプリケーションルールと、Azure のサービスタグ (MicrosoftContainerRegistry、AzureContainerRegistry など) への接続を許可するネットワークルールを設定しています。
2 つ目の minecraft-server グループでは、Minecraft サーバーへのインバウンドトラフィックを制御します。ここで重要なのは DNAT ルールの設定です。
{
ruleCollectionType: 'FirewallPolicyNatRuleCollection'
action: {
type: 'Dnat'
}
rules: [
{
ruleType: 'NatRule'
name: 'minecraft-server'
translatedAddress: managedEnvironment.outputs.staticIp
translatedPort: '25565'
ipProtocols: [
'TCP'
]
sourceAddresses: [
'*'
]
destinationAddresses: [
fwpip.outputs.ipAddress
]
destinationPorts: [
'25565'
]
}
]
name: 'nat-minecraft-server'
priority: 100
}
DNAT (Destination Network Address Translation) ルールにより、Azure Firewall のパブリック IP アドレス宛の 25565 番ポートへのトラフィックが、Container Apps 環境の静的 IP アドレスに転送されます。なお、25565 は Minecraft Java Edition サーバーのデフォルトポートです。
Storage Account と Private Endpoint
Minecraft サーバーのワールドデータを永続化するために、Azure Files を使用します。Container Apps のコンテナは本来ステートレスであり、再起動時にデータが失われるため、外部ストレージへのデータ保存が必要となります。
module storageAccount 'br/public:avm/res/storage/storage-account:0.30.0' = {
name: '${time}-storageAccountDeployment'
params: {
name: storageAccountName
fileServices: {
shares: [
{
accessTier: 'Hot'
name: 'mcjavashare'
shareQuota: 5120
}
]
}
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Deny'
}
privateEndpoints: [
{
privateDnsZoneGroup: {
privateDnsZoneGroupConfigs: [
{
privateDnsZoneResourceId: pdnssto.outputs.resourceId
}
]
}
service: 'file'
subnetResourceId: vnet.outputs.subnetResourceIds[1]
}
]
skuName: 'Standard_ZRS'
}
}
Storage Account は networkAcls.defaultAction: 'Deny' により、パブリックアクセスをデフォルトで拒否する設定としています。Container Apps からのアクセスは Private Endpoint 経由で行います。
Private Endpoint を作成すると、仮想ネットワーク内からは Storage Account のプライベート IP アドレスでアクセスできるようになります。この名前解決を行うために、プライベート DNS ゾーン privatelink.file.core.windows.net を作成し、仮想ネットワークにリンクしています。
Container Apps 環境
Container Apps 環境は、コンテナアプリを実行するための分離された境界を提供します。この構成では内部モード (internal: true) でデプロイし、インターネットからの直接アクセスを遮断しています。
module managedEnvironment 'br/public:avm/res/app/managed-environment:0.8.1' = {
name: '${time}-managedEnvironmentDeployment'
params: {
// Required parameters
name: mngEnvName
// Non-required parameters
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: workspace.outputs.logAnalyticsWorkspaceId
sharedKey: workspace.outputs.primarySharedKey
}
}
infrastructureSubnetResourceId: vnet.outputs.subnetResourceIds[2]
internal: true
location: location
storages: [
{
accessMode: 'ReadWrite'
storageAccountName: storageAccount.outputs.name
shareName: 'mcjavashare'
kind: 'SMB'
}
]
platformReservedCidr: '172.17.17.0/24'
platformReservedDnsIP: '172.17.17.17'
workloadProfiles: [
{
maximumCount: 1
minimumCount: 0
name: 'CAW01'
workloadProfileType: 'D4'
}
]
}
}
storages パラメータで Azure Files 共有をマウントし、Container Apps から利用できるようにしています。kind: 'SMB' は SMB プロトコルでのマウントを意味します。
workloadProfiles では D4 タイプのワークロードプロファイルを定義しています。Minecraft サーバーはある程度のメモリと CPU を必要とするため、従量課金の Consumption プランではなく、Dedicated プランのワークロードプロファイルを使用しています。
Minecraft Container App
最後に、Minecraft サーバー本体の Container App を定義します。
module minecraft 'br/public:avm/res/app/container-app:0.12.0' = {
name: '${time}-${cappsName}'
params: {
name: cappsName
containers: [
{
image: 'docker.io/itzg/minecraft-server'
name: 'minecraft-server'
resources: {
cpu: json('2')
memory: '4Gi'
}
volumeMounts: [
{
volumeName: 'mcjavashare'
mountPath: '/data'
}
]
env: [
{ name: 'EULA', value: 'true' }
{ name: 'MEMORY', value: '3G' }
{ name: 'OPS', value: 'mattffffff' }
{ name: 'VERSION', value: '1.21.7' }
{ name: 'VIEW_DISTANCE', value: '16' }
]
}
]
volumes: [
{
name: 'mcjavashare'
storageName: 'mcjavashare'
storageType: 'AzureFile'
}
]
ingressTargetPort: 25565
workloadProfileName: 'CAW01'
environmentResourceId: managedEnvironment.outputs.resourceId
scaleMinReplicas: 1
scaleMaxReplicas: 1
exposedPort: 25565
ingressTransport: 'tcp'
}
}
使用しているコンテナイメージは itzg/minecraft-server であり、Docker Hub で公開されている定番の Minecraft サーバーイメージです。環境変数で Minecraft のバージョン、メモリ割り当て、オペレーター権限を持つプレイヤー名などを設定できます。
volumeMounts で Azure Files 共有を /data にマウントしています。Minecraft サーバーのワールドデータや設定ファイルは /data ディレクトリに保存されるため、コンテナが再起動してもデータが永続化されます。
ingressTransport: 'tcp' と exposedPort: 25565 により、TCP 通信を受け付ける設定としています。通常の Container Apps は HTTP/HTTPS のみを受け付けますが、TCP ingress を有効にすることで Minecraft のプロトコルにも対応できます。
Bicep テンプレートの全体はこちら。
Bicep テンプレート
@description('ShortName is required for a unique storage account name. Only 5 characters.')
param shortName string
@description('Address prefixes for the virtual network.')
param vnetAddressPrefixes array = [
'192.168.1.0/24'
]
@description('Name of the Azure Firewall subnet.')
param subnetAzureFirewallName string = 'AzureFirewallSubnet'
@description('Address prefix for the Azure Firewall subnet.')
param subnetAzureFirewallPrefix string = '192.168.1.0/26'
@description('Name of the storage subnet.')
param subnetStorageName string = 'storage'
@description('Address prefix for the storage subnet.')
param subnetStoragePrefix string = '192.168.1.64/27'
@description('Name of the web subnet.')
param subnetWebName string = 'app'
@description('Address prefix for the web subnet.')
param subnetWebPrefix string = '192.168.1.96/27'
@description('Name of the Azure Firewall management subnet.')
param subnetAzureFirewallManagementName string = 'AzureFirewallManagementSubnet'
@description('Address prefix for the Azure Firewall management subnet.')
param subnetAzureFirewallManagementPrefix string = '192.168.1.128/26'
@description('Private DNS zone name for the storage account.')
param pdnsName string = 'privatelink.file.core.windows.net'
@description('Blob name for the storage account.')
param blobName string = 'mcjavablob'
@description('Location for all resources.')
param location string = resourceGroup().location
@description('Current UTC time, used for resource naming.')
param time string = utcNow()
var mngEnvName = 'cenv-mcjava-${shortName}'
var cappsName = 'capp-mcjava-${shortName}'
var vnetName = 'vnet-mcjava-${shortName}'
var storageAccountName = 'mcjavafiles${shortName}'
var workspaceName = 'law-mcjava-${shortName}'
var cenvpipName = 'cenvpip-mcjava-${shortName}'
var azfwName = 'azfw-mcjava-${shortName}'
var azfwpName = 'azfwp-mcjava-${shortName}'
var azfwpipName = 'azfwpip-mcjava-${shortName}'
// Virtual Network (AVM Module)
module vnet 'br/public:avm/res/network/virtual-network:0.7.2' = {
name: '${time}-privateVnet'
params: {
name: vnetName
addressPrefixes: vnetAddressPrefixes
subnets: [
{
name: subnetAzureFirewallName
addressPrefix: subnetAzureFirewallPrefix
}
{
name: subnetStorageName
addressPrefix: subnetStoragePrefix
privateLinkServiceNetworkPolicies: 'Enabled'
defaultOutboundAccess: false
}
{
name: subnetWebName
addressPrefix: subnetWebPrefix
delegation: 'Microsoft.App/environments'
}
{
name: subnetAzureFirewallManagementName
addressPrefix: subnetAzureFirewallManagementPrefix
}
]
}
}
// Private DNS Zone for Storage Account (AVM Module)
module pdnssto 'br/public:avm/res/network/private-dns-zone:0.8.0' = {
name: '${time}-storagedns'
params: {
name: pdnsName
virtualNetworkLinks: [
{
virtualNetworkResourceId: vnet.outputs.resourceId
}
]
}
}
// Azure Firewall Public IP (AVM Module)
module fwpip 'br/public:avm/res/network/public-ip-address:0.10.0' = {
name: '${time}-azfwpipDeployment'
params: {
// Required parameters
name: azfwpipName
// Non-required parameters
diagnosticSettings: []
publicIPAddressVersion: 'IPv4'
publicIPAllocationMethod: 'Static'
roleAssignments: []
skuName: 'Standard'
skuTier: 'Regional'
}
}
// Azure Firewall (AVM Module)
module azfw 'br/public:avm/res/network/azure-firewall:0.9.2' = {
name: '${time}-azureFirewallDeployment'
params: {
// Required parameters
name: azfwName
azureSkuTier: 'Standard'
virtualNetworkResourceId: vnet.outputs.resourceId
location: location
threatIntelMode: 'Alert'
firewallPolicyId: firewallPolicy.outputs.resourceId
publicIPResourceID: fwpip.outputs.resourceId
}
}
// Firewall Policy (AVM Module)
module firewallPolicy 'br/public:avm/res/network/firewall-policy:0.3.4' = {
name: 'firewallPolicyDeployment'
params: {
// Required parameters
name: azfwpName
// Non-required parameters
allowSqlRedirect: true
snat: {
autoLearnPrivateRanges: 'Enabled'
}
location: location
enableTelemetry: true
enableProxy: true
ruleCollectionGroups: [
{
priority: 1000
name: 'outbound'
ruleCollections: [
{
ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
action: {
type: 'Allow'
}
rules: [
{
ruleType: 'ApplicationRule'
name: 'vnet-outbound'
protocols: [
{
protocolType: 'Https'
port: 443
}
{
protocolType: 'Http'
port: 80
}
]
fqdnTags: []
webCategories: []
targetFqdns: [
'*'
]
targetUrls: []
terminateTLS: false
sourceAddresses: [
subnetWebPrefix
]
destinationAddresses: []
sourceIpGroups: []
httpHeadersToInsert: []
}
]
name: 'vnet-outbound'
priority: 300
}
{
ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
action: {
type: 'Allow'
}
rules: [
{
ruleType: 'NetworkRule'
name: 'nrc-containerapp-out'
ipProtocols: [
'TCP'
'UDP'
]
sourceAddresses: [
subnetWebPrefix
]
sourceIpGroups: []
destinationAddresses: [
'MicrosoftContainerRegistry'
'AzureFrontDoorFirstParty'
'AzureContainerRegistry'
'AzureActiveDirectory'
'AzureKeyVault'
]
destinationIpGroups: []
destinationFqdns: []
destinationPorts: [
'80'
'443'
]
}
]
name: 'container-app-outbound'
priority: 400
}
]
}
{
priority: 1100
name: 'minecraft-server'
ruleCollections: [
{
ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
action: {
type: 'Allow'
}
rules: [
{
ruleType: 'NetworkRule'
name: 'nrc-minecraft-server-in'
ipProtocols: [
'TCP'
]
sourceAddresses: [
'0.0.0.0/0'
]
sourceIpGroups: []
destinationAddresses: [
managedEnvironment.outputs.staticIp
]
destinationIpGroups: []
destinationFqdns: []
destinationPorts: [
'25565'
]
}
]
name: 'minecraft-server-in'
priority: 200
}
{
ruleCollectionType: 'FirewallPolicyNatRuleCollection'
action: {
type: 'Dnat'
}
rules: [
{
ruleType: 'NatRule'
name: 'minecraft-server'
translatedAddress: managedEnvironment.outputs.staticIp
translatedPort: '25565'
ipProtocols: [
'TCP'
]
sourceAddresses: [
'*'
]
sourceIpGroups: []
destinationAddresses: [
fwpip.outputs.ipAddress
]
destinationPorts: [
'25565'
]
}
]
name: 'nat-minecraft-server'
priority: 100
}
]
}
]
tier: 'Standard'
}
}
// Log Analytics Workspace (AVM Module) with Saved Searches
module workspace 'br/public:avm/res/operational-insights/workspace:0.9.1' = {
name: '${time}-workspaceDeployment'
params: {
// Required parameters
name: workspaceName
// Non-required parameters
location: location
}
}
// Storage Account (AVM Module)
module storageAccount 'br/public:avm/res/storage/storage-account:0.30.0' = {
name: '${time}-storageAccountDeployment'
params: {
// Required parameters
name: storageAccountName
// Non-required parameters
allowSharedKeyAccess: true
allowBlobPublicAccess: true
blobServices: {
automaticSnapshotPolicyEnabled: true
containerDeleteRetentionPolicyDays: 10
containerDeleteRetentionPolicyEnabled: true
containers: [
{
enableNfsV3AllSquash: true
enableNfsV3RootSquash: true
name: blobName
publicAccess: 'None'
}
]
deleteRetentionPolicyDays: 9
deleteRetentionPolicyEnabled: true
diagnosticSettings: [
{
metricCategories: [
{
category: 'AllMetrics'
}
]
name: 'customSetting'
workspaceResourceId: workspace.outputs.resourceId
}
]
lastAccessTimeTrackingPolicyEnabled: true
}
diagnosticSettings: [
{
metricCategories: [
{
category: 'AllMetrics'
}
]
name: 'customSetting'
workspaceResourceId: workspace.outputs.resourceId
}
]
enableHierarchicalNamespace: true
enableNfsV3: true
enableSftp: true
fileServices: {
diagnosticSettings: [
{
metricCategories: [
{
category: 'AllMetrics'
}
]
name: 'customSetting'
workspaceResourceId: workspace.outputs.resourceId
}
]
shares: [
{
accessTier: 'Hot'
name: 'mcjavashare'
shareQuota: 5120
}
]
}
largeFileSharesState: 'Enabled'
localUsers: []
location: location
managedIdentities: {
systemAssigned: true
}
managementPolicyRules: []
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Deny'
ipRules: []
virtualNetworkRules: []
}
privateEndpoints: [
{
privateDnsZoneGroup: {
privateDnsZoneGroupConfigs: [
{
privateDnsZoneResourceId: pdnssto.outputs.resourceId
}
]
}
service: 'file'
subnetResourceId: vnet.outputs.subnetResourceIds[1]
}
]
requireInfrastructureEncryption: true
sasExpirationPeriod: '180.00:00:00'
skuName: 'Standard_ZRS'
tags: {
SecurityControl : 'Ignore'
}
}
}
// Container Apps Managed Environment (AVM Module)
module managedEnvironment 'br/public:avm/res/app/managed-environment:0.8.1' = {
name: '${time}-managedEnvironmentDeployment'
params: {
// Required parameters
name: mngEnvName
logAnalyticsWorkspaceResourceId: workspace.outputs.resourceId
// Non-required parameters
infrastructureSubnetId: vnet.outputs.subnetResourceIds[2]
internal: true
location: location
storages: [
{
accessMode: 'ReadWrite'
storageAccountName: storageAccount.outputs.name
shareName: 'mcjavashare'
kind: 'SMB'
}
]
platformReservedCidr: '172.17.17.0/24'
platformReservedDnsIP: '172.17.17.17'
workloadProfiles: [
{
maximumCount: 1
minimumCount: 0
name: 'CAW01'
workloadProfileType: 'D4'
}
]
}
}
// Minecraft Container App (AVM Module)
module minecraft 'br/public:avm/res/app/container-app:0.12.0' = {
name: '${time}-${cappsName}'
params: {
name: cappsName
containers: [
{
image: 'docker.io/itzg/minecraft-server'
name: 'minecraft-server'
resources: {
cpu: json('2')
memory: '4Gi'
}
volumeMounts: [
{
volumeName: 'mcjavashare'
mountPath: '/data'
}
]
env: [
{ name: 'EULA', value: 'true' }
{ name: 'MEMORY', value: '3G' }
{ name: 'OPS', value: 'mattffffff' }
{ name: 'VERSION', value: '1.21.11' }
{ name: 'VIEW_DISTANCE', value: '16' }
]
}
]
volumes: [
{
name: 'mcjavashare'
storageName: 'mcjavashare'
storageType: 'AzureFile'
}
]
ingressTargetPort: 25565
workloadProfileName: 'CAW01'
environmentResourceId: managedEnvironment.outputs.resourceId
scaleMinReplicas: 1
scaleMaxReplicas: 1
exposedPort: 25565
ingressTransport: 'tcp'
trafficWeight: 100
trafficLatestRevision: true
}
}
// Output the Minecraft server endpoint
output minecraftEndpoint string = fwpip.outputs.ipAddress
デプロイと接続
このテンプレートをデプロイすると、出力として Azure Firewall のパブリック IP アドレスが返されます。
output minecraftEndpoint string = fwpip.outputs.ipAddress
Minecraft クライアントからは、このパブリック IP アドレスに対してポート 25565 で接続することでサーバーに参加できます。

管理
これで構築して遊べるまでにはなりました。
ここからは管理面でのトピックを挙げていきます。
バックアップ
今回の構成では、Minecraft サーバーのワールドデータは Azure Files に保存されます。
サーバーのお引越しが必要な時は、こちらのデータを移行しましょう。
Azure Portal や Azure CLI を使用して手動でバックアップを取得できます。また、Azure Backup サービスを利用して自動バックアップを設定することも可能です。
バージョンアップ
Minecraft は年に1度のバージョンアップから、年に複数回の更新を行う方針に変わったことが昨年発表されました。そのため、バージョンアップは必須となります。
このコンテナイメージでは、Minecraft のバージョンとして、環境変数 VERSION で指定しています。これによって指定されたバージョンで Minecraft サーバーが起動できます。
env: [
{ name: 'EULA', value: 'true' }
{ name: 'MEMORY', value: '3G' }
{ name: 'OPS', value: 'mattffffff' }
{ name: 'VERSION', value: '1.21.7' }
{ name: 'VIEW_DISTANCE', value: '16' }
]
注意点としては、Bicep テンプレートの対象環境変数を更新して、再度 Bicep テンプレートを流せばよいのですが、前のリビジョンでファイル共有をつかんでいる関係で、新規リビジョンのコンテナで Minecraft サーバーの起動が失敗します。

その場合、一度 Container App を停止、その後起動することで正常にリビジョンが反映されるかと思います。
監視
Log Analytics ワークスペースを Container Apps 環境に紐づけることで、コンテナのアプリケーションにかかわるログを転送できます。
このログには、Minecraft サーバーへログインしたユーザーや、ゲーム内で入力したチャットメッセージが含まれます。たとえば、以下の KQL クエリを使用して、Minecraft サーバーの起動が完了した際のメッセージを取得できます。
ContainerAppConsoleLogs_CL
| where Log_s has "0.0.0.0"

また、Azure Monitor に含まれる Grafana ダッシュボードが用意されています。
そしてなんと無料。ざっとパフォーマンスの傾向を見るのに便利ですね。
Minecraft サーバーのパフォーマンス戦略としてはスケールアップになるので、傾向をつかんで早めに対処できるようになりましょう。

IaC リソースのメンテナンス
AVM は継続的にアップデートされているため、新しいバージョンがリリースされた際にはテンプレートのバージョン番号を更新することで、最新の機能やセキュリティ改善を取り込めます。Bicep の bicepconfig.json で use-recent-module-versions を有効にすると、古いバージョンのモジュールを使用している場合に警告が表示されるため、アップデートの見落としを防ぐことができます [6]。
まとめ
いかがでしたでしょうか⛏️
本記事では、Azure Verified Module を使って Minecraft サーバーのインフラストラクチャを構築する方法を解説しました。AVM を使用することで、Microsoft が推奨する設定が適用された状態でデプロイできます。また、一から作るよりも開発効率が大幅に向上します。
ぜひ、Azure での構築を検討される際には、まず AVM に対応するモジュールがないか確認してみてください。
Discussion