🐡

Azure設定をAnsibleで実行する~NSG設定編~

2023/01/05に公開

はじめに

本記事では、AzureのNSG設定をAnsibleで実行する方法について記載します。
Ansibleの基本的な使い方、AzureのNSGの機能紹介は記載しません。

イメージ図

イメージ図 mermaidソース
flowchart LR
    subgraph "Azure"
        subgraph "AzureCLI"
            Ansible
        end
        Ansible --> NSG1
        Ansible --> NSG2
        subgraph "ResourceGroup"
            NSG1["NSG</br>rules1</br>rules2"]
            NSG2["NSG</br>rules1</br>rules2"]
        end
    end

環境

azure-cli: 2.43.0
ansible: core 2.13.3
使用するAnsible module: azure_rm_securitygroup

バージョン確認 コマンド結果
r_ota [ ~ ]$ az version
{
  "azure-cli": "2.43.0",
  "azure-cli-core": "2.43.0",
  "azure-cli-telemetry": "1.0.8",
  "extensions": {
    "ai-examples": "0.2.5",
    "ml": "2.11.0",
    "ssh": "1.1.3"
  }
}
r_ota [ ~ ]$ ansible --version
ansible [core 2.13.3]
  config file = None
  configured module search path = ['/home/r_ota/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/ansible/lib/python3.9/site-packages/ansible
  ansible collection location = /home/r_ota/.ansible/collections:/usr/share/ansible/collections
  executable location = /opt/ansible/bin/ansible
  python version = 3.9.14 (main, Oct 29 2022, 22:18:10) [GCC 11.2.0]
  jinja version = 3.1.2
  libyaml = True
r_ota [ ~ ]$ 

AnsibleでNSG設定の実施

本記事で記載する検証に必要なファイルを下記リポジトリに格納しています。

事前準備

AzureCLIで下記を実行してgit cloneします。

cd clouddrive
git clone https://github.com/roota5666/AzurexAnsible-NSGset.git
cd AzurexAnsible-NSGset

リポジトリに格納しているテスト用コードでは、ResourceGroupをexampleとして記載しています。
実行の際は、1. または 2. の対応を実施ください。

  1. AzureCLIで下記を実行してResourceGroup「example」を作成する
    az group create --location japaneast --name example
    
  2. ResourceGroup名をご自分の環境に合わせてimportファイル内の「example」を置換する
    code import.json
    

設定実施

  1. AzureCLIで下記を実行します(dry-run)

    ansible-playbook run.yml --extra-vars "importjson=./import.json" --check
    

    結果は、ok=4 changed=4 が出力され、実際の変更はされていない想定です。

    実行結果例
    r_ota [ ~/clouddrive/AzurexAnsible-NSGset ]$ ansible-playbook run.yml --extra-vars "importjson=./import.json" --check
    [WARNING]: No inventory was parsed, only implicit localhost is available
    [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
    
    PLAY [localhost] **********************************************************************************************************************************************************************************************
    
    TASK [Azure Security Group] ***********************************************************************************************************************************************************************************
    
    TASK [az_securitygroup : Network Security group "example-nsg_test1" Rule "AllowAzureStorageInbound" modify] ***************************************************************************************************
    changed: [localhost]
    
    TASK [az_securitygroup : Network Security group "example-nsg_test1" Rule "AllowAzureLoadBalancerInbound" modify] **********************************************************************************************
    changed: [localhost]
    
    TASK [az_securitygroup : Network Security group "example-nsg_test1" Rule "AllowClientInbound" modify] *********************************************************************************************************
    changed: [localhost]
    
    TASK [az_securitygroup : Network Security group "example-nsg_test2" Rule "AllowClientInbound" modify] *********************************************************************************************************
    changed: [localhost]
    
    PLAY RECAP ****************************************************************************************************************************************************************************************************
    localhost                  : ok=4    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
    
    r_ota [ ~/clouddrive/AzurexAnsible-NSGset ]$ 
    
  2. AzureCLIで下記を実行(変更実施1回目)

    ansible-playbook run.yml --extra-vars "importjson=./import.json"
    

    結果は、ok=4 changed=4 が出力され、NSGの作成/ruleの設定がされている想定です。

    実行結果例
    r_ota [ ~/clouddrive/AzurexAnsible-NSGset ]$ ansible-playbook run.yml --extra-vars "importjson=./import.json"
    [WARNING]: No inventory was parsed, only implicit localhost is available
    [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
    
    PLAY [localhost] **********************************************************************************************************************************************************************************************
    
    TASK [Azure Security Group] ***********************************************************************************************************************************************************************************
    
    TASK [az_securitygroup : Network Security group "example-nsg_test1" Rule "AllowAzureStorageInbound" modify] ***************************************************************************************************
    changed: [localhost]
    
    TASK [az_securitygroup : Network Security group "example-nsg_test1" Rule "AllowAzureLoadBalancerInbound" modify] **********************************************************************************************
    changed: [localhost]
    
    TASK [az_securitygroup : Network Security group "example-nsg_test1" Rule "AllowClientInbound" modify] *********************************************************************************************************
    changed: [localhost]
    
    TASK [az_securitygroup : Network Security group "example-nsg_test2" Rule "AllowClientInbound" modify] *********************************************************************************************************
    changed: [localhost]
    
    PLAY RECAP ****************************************************************************************************************************************************************************************************
    localhost                  : ok=4    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
    
    r_ota [ ~/clouddrive/AzurexAnsible-NSGset ]$ 
    
  3. AzureCLIで下記を実行(変更実施2回目)

    ansible-playbook run.yml --extra-vars "importjson=./import.json"
    

    結果は、ok=4 changed=0 が出力され、NSGの作成/ruleの変更はされていない想定です。
    これは、Ansibleの冪等性により、設定しようとする値とすでに設定されている値が同じためokを返し、設定変更されない結果となります。

    実行結果例
    r_ota [ ~/clouddrive/AzurexAnsible-NSGset ]$ ansible-playbook run.yml --extra-vars "importjson=./import.json"
    [WARNING]: No inventory was parsed, only implicit localhost is available
    [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
    
    PLAY [localhost] **********************************************************************************************************************************************************************************************
    
    TASK [Azure Security Group] ***********************************************************************************************************************************************************************************
    
    TASK [az_securitygroup : Network Security group "example-nsg_test1" Rule "AllowAzureStorageInbound" modify] ***************************************************************************************************
    ok: [localhost]
    
    TASK [az_securitygroup : Network Security group "example-nsg_test1" Rule "AllowAzureLoadBalancerInbound" modify] **********************************************************************************************
    ok: [localhost]
    
    TASK [az_securitygroup : Network Security group "example-nsg_test1" Rule "AllowClientInbound" modify] *********************************************************************************************************
    ok: [localhost]
    
    TASK [az_securitygroup : Network Security group "example-nsg_test2" Rule "AllowClientInbound" modify] *********************************************************************************************************
    ok: [localhost]
    
    PLAY RECAP ****************************************************************************************************************************************************************************************************
    localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
    
    r_ota [ ~/clouddrive/AzurexAnsible-NSGset ]$ 
    
  4. テストリソース削除
    3まででAzure×Ansibleの設定実施は終わりです。
    テストで作成/使用したリソースをAzureポータルより削除ください

    • NSG
    • ResourceGroup(テスト用にexampleを作成した場合)

各パラメータについて

今回使用した az_securitygroup role のパラメータ例

変数名 入力規則 入力例
ResourceGroup 文字列(ResourceGroup名) example
NetworkSecurityGroups 文字列(NSG名) example-nsg_test2
SecurityRulesName 文字列(任意) AllowAnyCustom8080Inbound
Access Allow/Deny Allow
Protocol Tcp/Udp/Icmp/* Tcp
Direction Inbound/Outbound Inbound
Priority 数字(100~4096) 555
SourceAddressPrefix */IPアドレス/サービスタグ名 192.168.1.1
["1.1.1.1","8.8.8.8"]
"AzureLoadBalancer"
SourcePortRange 数字/*/範囲(数字-数字) *
DestinationAddressPrefix */IPアドレス/サービスタグ名 192.168.1.1/24
DestinationPortRange 数字/*/範囲(数字-数字) *
description 任意の文字列 "test2"

Protocolの指定

Protocolの指定は、Tcp/Udp/Icmp/* となっています。※公式ドキュメント
頭文字のアルファベットのみが大文字、2文字目以降が小文字です。
Azureポータルから手動でNSG ruleを設定した際はProtocolはすべて大文字となります。

上記通り、Ansibleで設定したProtocolの値と、Azureポータルから設定されたProtocolの値は違います。

仕様変更される可能性もありますが、現時点(2022/12/31現在)で「Ansibleで設定したNSG rule」と「Azureポータルから設定されたNSG rule」2つの見分け方として覚えておいて損はないと思います。

IPアドレスの指定

IPアドレスの指定は、SourceAddressPrefixとDestinationAddressPrefixがあり、*(アスタリスク)指定、IPアドレスを指定します。
例)192.168.1.1/32 や 192.168.1.0/24 のようなCIDR記法

IPアドレスを指定する場合、1つだけ設定することを推奨します。

理由は、仕様上 ["1.1.1.1","8.8.8.8"] のような記載で複数アドレスを1つのruleに記載可能ですが、設定後に格納される場所が違うからです。
格納される場所というのは、AzureCLIでaz network nsg rule listコマンドを実施、出力された結果です。

name Denytest Denytest2
priority 3012 3013
sourceAddressPrefix "1.1.1.1"
sourceAddressPrefixes ["1.1.1.1","8.8.8.8"] []

アドレスを*(アスタリスク)指定、IPアドレス1つのみ設定した場合の格納場所は
sourceAddressPrefix 及び destinationAddressPrefix になります。
上の表ではrule Denytest2のIPアドレス設定は"1.1.1.1" がsourceAddressPrefixに格納されます

IPアドレス2つ以上設定した場合の格納場所は
sourceAddressPrefixes 及び destinationAddressPrefixes になります。
上の表ではrule DenytestのIPアドレス設定は["1.1.1.1","8.8.8.8"] がsourceAddressPrefixesに格納されます

Denytest と というNSG ruleを作成して確認します。

r_ota [ ~ ]$ az network nsg rule list --nsg-name r_ota-nsg_test1 --resource-group r_ota
[
  {
    "access": "Deny",
    "destinationAddressPrefix": "*",
    "destinationAddressPrefixes": [],
    "destinationPortRange": "8080",
    "destinationPortRanges": [],
    "direction": "Inbound",
    "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"",
    "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/r_ota/providers/Microsoft.Network/networkSecurityGroups/r_ota-nsg_test1/securityRules/Denytest",
    "name": "Denytest",
    "priority": 3012,
    "protocol": "*",
    "provisioningState": "Succeeded",
    "resourceGroup": "r_ota",
    "sourceAddressPrefixes": [
      "1.1.1.1",
      "8.8.8.8"
    ],
    "sourcePortRange": "*",
    "sourcePortRanges": [],
    "type": "Microsoft.Network/networkSecurityGroups/securityRules"
  },
  {
    "access": "Deny",
    "destinationAddressPrefix": "*",
    "destinationAddressPrefixes": [],
    "destinationPortRange": "8080",
    "destinationPortRanges": [],
    "direction": "Inbound",
    "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"",
    "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/r_ota/providers/Microsoft.Network/networkSecurityGroups/r_ota-nsg_test1/securityRules/Denytest",
    "name": "Denytest2",
    "priority": 3013,
    "protocol": "*",
    "provisioningState": "Succeeded",
    "resourceGroup": "r_ota",
    "sourceAddressPrefix": "1.1.1.1",
    "sourceAddressPrefixes": [],
    "sourcePortRange": "*",
    "sourcePortRanges": [],
    "type": "Microsoft.Network/networkSecurityGroups/securityRules"
  }
]
r_ota [ ~ ]$ 

AnsibleでIaCをするのであれば、設定後の状態取得→どこかに保存して構成管理 なんてことをやるのであれば、IPアドレスの指定は、 *(アスタリスク)指定、IPアドレスの単体指定を実施し、sourceAddressPrefixのみ取得/確認すれば設定が確認できるようにすることを推奨 します。

descriptionの指定

descriptionの指定は、NSG ruleの説明を記載することで可読性があがるので、できるだけ記載したほうがよいと思います。
NSG ruleの説明、設定に至った経緯、作業依頼のあったチケット番号、作成した日などなど。

description内の改行は LF(\n)で記述が可能ですが改行を使わないことを推奨します。
設定後の状態取得→どこかに保存して構成管理 すると思います。私自身、取得した設定をcsvで出力したのですがdescriptionにLFがあることで、出力した行がズレてハマったことがありました。。
そのため、改行しないで使用できるのであれば、使わないことを強く推奨します。

他のIaCとの比較

AzureCLIコマンド、az network nsg rule createの使用

AzureCLIコマンド az network nsg rule create を使用することで、NSGの作成及びruleの作成ができます。

出力結果が「すでに設定されている場合」「設定されておらず、設定追加する場合」の実行結果がほぼ同じため、冪等性の結果が出力されるAnsibleのほうが利便性が高い と思います。

az network nsg rule createを検証してみた

実行コマンド

az network nsg rule create --name "AllowAzureStorageInbound" \
                           --nsg-name "example-nsg_test1" \
                           --priority "3001" \
                           --resource-group "example" \
                           --access "Allow" \
                           --description "Storageからのアクセス許可" \
                           --destination-address-prefixes  "*" \
                           --destination-port-ranges "25" \
                           --direction "Inbound" \
                           --protocol "*" \
                           --source-address-prefixes "Storage" \
                           --source-port-ranges "*"

出力結果

r_ota [ ~/clouddrive/AzurexAnsible-NSGset ]$ az network nsg rule create --name "AllowAzureStorageInbound" \
                           --nsg-name "example-nsg_test1" \
                           --priority "3001" \
                           --resource-group "example" \
                           --access "Allow" \
                           --description "Storageからのアクセス許可" \
                           --destination-address-prefixes  "*" \
                           --destination-port-ranges "25" \
                           --direction "Inbound" \
                           --protocol "*" \
                           --source-address-prefixes "Storage" \
                           --source-port-ranges "*"
{
  "access": "Allow",
  "description": "Storageからのアクセス許可",
  "destinationAddressPrefix": "*",
  "destinationAddressPrefixes": [],
  "destinationPortRange": "25",
  "destinationPortRanges": [],
  "direction": "Inbound",
  "etag": "W/\"c415f367-c110-4dc3-b76b-1bcea911843f\"",
  "id": "/subscriptions/e12ffbde-ddf4-4ac7-8aea-66e9e4419e23/resourceGroups/example/providers/Microsoft.Network/networkSecurityGroups/example-nsg_test1/securityRules/AllowAzureStorageInbound",
  "name": "AllowAzureStorageInbound",
  "priority": 3001,
  "protocol": "*",
  "provisioningState": "Succeeded",
  "resourceGroup": "example",
  "sourceAddressPrefix": "Storage",
  "sourceAddressPrefixes": [],
  "sourcePortRange": "*",
  "sourcePortRanges": [],
  "type": "Microsoft.Network/networkSecurityGroups/securityRules"
}
r_ota [ ~/clouddrive/AzurexAnsible-NSGset ]$ 

また、すでに設定されていても、動き的には新たに書き込む動作のようで、etag というパラメータだけ変更されます。

Terraform

Azure Cloud Shell には、Terraform もあるため使用できます。
筆者がTerraformよりAnsibleの経験が多いため Terraform でのNSG設定を実施していないため比較については省略します。

r_ota [ ~ ]$ terraform --version
Terraform v1.3.2
on linux_amd64

Your version of Terraform is out of date! The latest version
is 1.3.6. You can update by downloading from https://www.terraform.io/downloads.html
r_ota [ ~ ]$ 

Terraformは宣言モデル、Ansibleは命令実行モデル、という比較をよくされます。
今回使用したAnsible module azure_rm_securitygroup に purge_rules というオプションがあり、こちらを有効(purge_rules: true)にすることで、設定時にコマンド内に記載のないNSG ruleを削除することも可能です。

詳しくは公式サイトを参照ください

Azure×Ansible使用の注意点

私が2021年10月くらいよりAzure×Ansibleを使い始めた所感になりますが、たまにAzure×Ansibleの使用ができなくなる時があります。
おそらく、Azure Cloud Shellで使用しているコンテナがアップデートや変更された際に発生する?と感じます。
過去に発生した際は記事にし、一応の解決もしましたが、いつ発生するかは不明のため、利用する際はAzure×Ansibleが使えなくなる可能性も考慮したほうがよいかと思います。

先に挙げたTerraformや、ARM/Bicepなどの使用もご検討ください。

感想&まとめ

本記事を書いた/NSG設定をAnsibleでやってみようと思ったキッカケは、やはりAzureポータルからNSGの設定をポチポチするのがダルかった、というのが一番の理由です。
2つ3つであればいいのですが、10や20になると時間もかかりますし、人間はミスをするいきものです。自動化してなんぼのもんじゃい!と思い立ち、仕事で使った経験を今回記事にしてみました。
他にもいいやり方や使い方があるかもしれませんが、本記事を読んだ誰かの作業の一助になれば幸いです。

最後までお読みいただき、ありがとうございました。

参考サイト

Discussion