Azure Container Instancesでアウトバウンド時のIPアドレスを固定する

日次で外部サービスにアクセスするプログラムをAzure Container Instances(ACI)を使って動かす際に、静的IPアドレスを固定する必要があり、ひと工夫したのでそれの備忘録。

以下のドキュメントにも記載のある通り、Azure FireWall( +ルートテーブル・パブリックIPアドレス)というサービスを利用し、ACIをデプロイしているサブネットから外へのルーティングをAzure Firewallに向けてあげることで、必ず静的IPアドレスを利用して外部にアクセスすることができます。

docs.microsoft.com

ただし、Azure Firewallはリソースを作っておくだけで1時間あたり140円ほどかかり、1ヶ月で見ると約10万程度かかってしまう。 azure.microsoft.com

今回ACIは毎日決まった時間に1回動かせればよく、Azure Firewallが必要な時間は1日1時間にも満たないので割りに合わず・・・。

そこで、Azure Automationを利用し、ACIを実行をさせる前後で、Azure Firewallのデプロイ・削除を行わせることで、お金をかけずに日次バッチ処理をさせることにしました。実行スケジュールもいい感じに設定できます。

Azure Automationでは、PowerShellなどを利用してAzureリソースの作成・実行・削除などの命令を行うことができます。 事前準備として、VNet・サブネット(ACI配置用とAzure Firewall配置用)・パブリックIPアドレス・ACI(サブネットに配置して)をドキュメントを参考に作成してください。

Azure AutomationのRunbookには以下の様なコードを記述します。

# 認証関連の設定

$connection = Get-AutomationConnection -Name AzureRunAsConnection$ErrorActionPreference = 'Stop'

 
$logonAttempt = 0
while(!($connectionResult) -And ($logonAttempt -le 10))
{
    $LogonAttempt++
    $connectionResult = Connect-AzAccount `
                            -ServicePrincipal `
                            -Tenant $connection.TenantID `
                            -ApplicationId $connection.ApplicationID `
                            -CertificateThumbprint $connection.CertificateThumbprint

    Start-Sleep -Seconds 30
}

# Azure Firewallの作成
$Azfw = New-AzFirewall -ResourceGroupName <リソースグループ名> -Name testFW -Location "Japan East" -VirtualNetworkName <ACIとAzure Firewallが存在するVnet名> -PublicIpName <パブリックIPアドレス名>

#Azure FirewallのプライベートIPアドレスを取得
$AzfwPrivateIP = $Azfw.IpConfigurations.privateipaddress

# ルートテーブルを作成
$routeTableDG = New-AzRouteTable `
  -ResourceGroupName <リソースグループ名> `
  -Name Firewall-rt-table `
  -location "Japan East" `
  -DisableBgpRoutePropagation

#ルートテーブルのルールを作成
Add-AzRouteConfig `
  -Name DG-Route `
  -RouteTable $routeTableDG `
  -AddressPrefix 0.0.0.0/0 `
  -NextHopType VirtualAppliance `
  -NextHopIpAddress $AzfwPrivateIP `
 | Set-AzRouteTable

# ACIを配置しているサブネットの情報を取得
$networkProfile = Get-AzVirtualNetwork -ResourceGroupName <リソースグループ名> -Name <ACIとAzure Firewallが存在するVnet名>
$subnetProfile = Get-AzVirtualNetwork -ResourceGroupName <リソースグループ名> -Name <ACIとAzure Firewallが存在するVnet名> | Get-AzVirtualNetworkSubnetConfig -Name <ACIを配置しているサブネット名>
$delegation = Get-AzDelegation -Subnet $subnetProfile

# ルートテーブルとサブネットの関連付け
Set-AzVirtualNetworkSubnetConfig `
  -VirtualNetwork $networkProfile `
  -Name <ACIを配置しているサブネット名> `
  -AddressPrefix 10.3.1.0/24 `
  -Delegation $delegation `
  -RouteTable $routeTableDG | Set-AzVirtualNetwork

# Azure FirewallのSNATルールを作成
$AppRule1 = New-AzFirewallApplicationRule -Name Allow -SourceAddress <ACIを配置しているサブネットのアドレス空間> `
  -Protocol http, https -TargetFqdn <接続先のFQDN>

$AppRuleCollection = New-AzFirewallApplicationRuleCollection -Name App-Coll01 `
  -Priority 200 -ActionType Allow -Rule $AppRule1

$Azfw.ApplicationRuleCollections.Add($AppRuleCollection)

Set-AzFirewall -AzureFirewall $Azfw

# ACI実行
Invoke-AzResourceAction -ResourceGroupName <リソースグループ名> -ResourceName <ACI名> -Action Start -ResourceType Microsoft.ContainerInstance/containerGroups -Force

# ACI停止待ち
while ($true)
{
    Start-Sleep -Seconds 30
    
    $resultContainerStatus = Get-AzContainerGroup -ResourceGroupName <リソースグループ名> -Name<ACI名>
    if(($resultContainerStatus.State -eq 'Failed') -Or ($resultContainerStatus.State -eq 'Succeeded'))
    {
        break
    }
}
 
# ACIサブネットとルートテーブルの関連付けを解除
Set-AzVirtualNetworkSubnetConfig `
  -VirtualNetwork $networkProfile `
  -Name <ACIを配置しているサブネット名> `
  -AddressPrefix<ACIを配置しているサブネットのアドレス空間> `
  -Delegation $delegation `
  -RouteTable $null | Set-AzVirtualNetwork

# ルートテーブル削除
Remove-AzRouteTable -ResourceGroupName <リソースグループ名> -Name Firewall-rt-table -Force

# Azure Firewall削除
Remove-AzFirewall -ResourceGroupName <ACI名> -Name testFW -Force

  Azure Automationの他に、Logic Appsという同じ様にAzureリソースの管理・スケジュール実行できるサービスがあり、こちらのほうが使いやすかったりしそうなのですが、 当時はAzure Firewallのデプロイができなさそうだったため、Automationを利用しました。(現時点では不明)