てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Azure] いろんなツールでNSGを作ってみた(Azure CLI / Bicep / ARM テンプレート / Ansible / Terraform / CDKTF)

はじめに

Azure の操作を自動化するとき、みなさんどんなツール使うことが多いのかなと思い、先日こんなアンケートをとってみました。

Terraform が多いですね。

候補に上げてみたものの、触ってみたことが無いものもあったので、色々触ってみようと思いました。

今回は、ネットワークセキュリティグループとそのルールを定義する簡単な内容を以下の6個のツール類でやってみました。

ツールごとに別々のリソースグループを予め作成していた状態からはじめました。ツールのインストールや認証情報などの設定も済んでいる状態です。

かんたんなサンプルを掲載します。まだちょっと触っただけなので、うまく有効活用した書き方になっていないところもあると思います。また構文の説明も省略します。あくまで雰囲気、ということでご了承ください。

1. Azure CLI

まず、普段遣いとしてもとても便利な印象の Azure CLIでやってみます。

  • 環境
    • Azure CLI 2.43.0

NSG 自体の作成を az network nsg create で、ルールの作成を az network nsg rule create で行いました。

サンプルと実行

NSG の作成:

% az network nsg create -g sakana-cli -n testnsg01
{
  "NewNSG": {
    "defaultSecurityRules": [
      {
        "access": "Allow",
  // ...(略)...

ルールの作成:

% az network nsg rule create -g sakana-cli --nsg-name testnsg01 \
                           --name "Allow 443" \
                           --direction Inbound \
                           --priority 110 \
                           --destination-port-ranges 443 \
                           --protocol Tcp \
                           --destination-address-prefixes "*" \
                           --source-address-prefixes 10.0.0.0/16 \
                           --access Allow

{
  "access": "Allow",
  "destinationAddressPrefix": "*",
  // ...(略)...
}

手軽に使えて、繰り返し実行もしやすくて便利です。

2. Bicep

ARM テンプレートより簡単に書ける構文で書けるもので、今回初めて書きました。

内部的には、一度 ARM テンプレートに変換されるようです。

サンプル

param location string = resourceGroup().location

resource testnsg01 'Microsoft.Network/networkSecurityGroups@2022-07-01' = {
  name: 'testnsg01'
  location: location
}

resource Allow443 'Microsoft.Network/networkSecurityGroups/securityRules@2022-07-01' = {
  name: 'Allow443'
  parent: testnsg01
  properties: {
    direction: 'Inbound'
    priority: 110
    destinationPortRange: '443'
    protocol: 'Tcp'
    destinationAddressPrefix: '*'
    sourcePortRange: '*'
    sourceAddressPrefix: '10.0.0.0/16'
    access: 'Allow'
  }
}

VS Code の拡張「Bicep」を利用して書きましたが、補完が効いてとても書きやすかったです。

実行

ARM テンプレートと同じく Azure CLIaz deployment group create でデプロイします。Azure CLI の内部的には、指定したファイル名の拡張子が .bicep だと ARM テンプレートに変換(az bicep build -f ファイル名.bicep --stdout)する処理を挟むようでした。

% az deployment group create -f nsg.bicep -g sakana-bicep
{
  "id": "/subscriptions/xxxx/resourceGroups/sakana-bicep/providers/Microsoft.Resources/deployments/nsg",
  "location": null,
  "name": "nsg",
  "properties": {
...(略)...

参考URL

3. ARM テンプレート

一番 Azure ネイティブな方法と言えるかなと思います。

サンプル

一から書くのが手間に感じたため、先程作成した Bicep のファイルから生成します。az bicep build -f nsg.bicep コマンドで nsg.json というファイル名の ARM テンテプレートができます。

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "metadata": {
    "_generator": {
      "name": "bicep",
      "version": "0.13.1.58284",
      "templateHash": "4078294825981448537"
    }
  },
  "parameters": {
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Network/networkSecurityGroups",
      "apiVersion": "2022-07-01",
      "name": "testnsg01",
      "location": "[parameters('location')]"
    },
    {
      "type": "Microsoft.Network/networkSecurityGroups/securityRules",
      "apiVersion": "2022-07-01",
      "name": "[format('{0}/{1}', 'testnsg01', 'Allow443')]",
      "properties": {
        "direction": "Inbound",
        "priority": 110,
        "destinationPortRange": "443",
        "protocol": "Tcp",
        "destinationAddressPrefix": "*",
        "sourcePortRange": "*",
        "sourceAddressPrefix": "10.0.0.0/16",
        "access": "Allow"
      },
      "dependsOn": [
        "[resourceId('Microsoft.Network/networkSecurityGroups', 'testnsg01')]"
      ]
    }
  ]
}

どうしても少し長くなります。Bicep の有り難みが分かりました。

実行

構文は、Bicep と同じです。今回はツールごとにリソースグループを分けているので -g は異なります。

$ az deployment group create -f nsg.json -g sakana-arm
{
  "id": "/subscriptions/xxxx/resourceGroups/sakana-arm/providers/Microsoft.Resources/deployments/nsg",
  "location": null,
  "name": "nsg",
  "properties": {
...(略)...

4. Ansible

azure.azcollection というコレクションの中に、さまざまなリソースを操作するモジュールがあります。

NSG の作成自体も、ルールの作成も同じく azure/azcollection/azure_rm_securitygroupモジュールを利用します。

  • 環境
    • ansible-core 2.12.1
    • azure.azcollection 1.14.0

サンプル

---
- hosts: localhost
  gather_facts: false
  connection: local

  vars:
    resource_group: sakana-ansible
    ansible_python_interpreter: "{{ ansible_playbook_python }}"

  tasks:
    - name: Create NSG
      azure.azcollection.azure_rm_securitygroup:
        resource_group: "{{ resource_group }}"
        name: testnsg01
        rules:
          - name: Allow 443
            direction: Inbound
            priority: 110
            destination_port_range: 443
            protocol: Tcp
            source_address_prefix: 10.0.0.0/16
            access: Allow

実行

% ansible-playbook -i localhost, nsg.yml 

PLAY [localhost] ***********************************************************************************

TASK [Create NSG] **********************************************************************************
changed: [localhost]

PLAY RECAP *****************************************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

5. Terraform

次は、今回のアンケートでは使っている人が多かった Terraform です。

  • 環境
    • Terraform v1.3.7

サンプル

provider "azurerm" {
  features {}
}

resource "azurerm_network_security_group" "testnsg01" {
  name                = "testnsg01"
  location            = "japaneast"
  resource_group_name = "sakana-tf"
}

resource "azurerm_network_security_rule" "Allow443" {
  name                        = "Allow 443"
  network_security_group_name = azurerm_network_security_group.testnsg01.name
  resource_group_name         = "sakana-tf"
  direction                   = "Inbound"
  priority                    = 110
  destination_port_range      = "443"
  protocol                    = "Tcp"
  source_address_prefix       = "10.0.0.0/16"
  source_port_range           = "*"
  destination_address_prefix  = "*"
  access                      = "Allow"
}

実行

init:

% terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/azurerm...
- Installing hashicorp/azurerm v3.39.1...
- Installed hashicorp/azurerm v3.39.1 (signed by HashiCorp)

...(略)...

plan:

 % terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions
are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_network_security_group.testnsg01 will be created
  + resource "azurerm_network_security_group" "testnsg01" {
      + id                  = (known after apply)
      + location            = "japaneast"
      + name                = "testnsg01"
      + resource_group_name = "sakana-tf"
      + security_rule       = (known after apply)
    }

  # azurerm_network_security_rule.Allow443 will be created
  + resource "azurerm_network_security_rule" "Allow443" {
      + access                      = "Allow"
      + destination_address_prefix  = "*"
      + destination_port_range      = "443"
      + direction                   = "Inbound"
      + id                          = (known after apply)
      + name                        = "Allow 443"
      + network_security_group_name = "testnsg01"
      + priority                    = 110
      + protocol                    = "Tcp"
      + resource_group_name         = "sakana-tf"
      + source_address_prefix       = "10.0.0.0/16"
      + source_port_range           = "*"
    }

Plan: 2 to add, 0 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take
exactly these actions if you run "terraform apply" now.

apply:

% terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions
are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_network_security_group.testnsg01 will be created
  + resource "azurerm_network_security_group" "testnsg01" {
      + id                  = (known after apply)
      + location            = "japaneast"
      + name                = "testnsg01"
      + resource_group_name = "sakana-tf"
      + security_rule       = (known after apply)
    }

  # azurerm_network_security_rule.Allow443 will be created
  + resource "azurerm_network_security_rule" "Allow443" {
      + access                      = "Allow"
      + destination_address_prefix  = "*"
      + destination_port_range      = "443"
      + direction                   = "Inbound"
      + id                          = (known after apply)
      + name                        = "Allow 443"
      + network_security_group_name = "testnsg01"
      + priority                    = 110
      + protocol                    = "Tcp"
      + resource_group_name         = "sakana-tf"
      + source_address_prefix       = "10.0.0.0/16"
      + source_port_range           = "*"
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_network_security_group.testnsg01: Creating...
azurerm_network_security_group.testnsg01: Creation complete after 4s [id=/subscriptions/xxxx/resourceGroups/sakana-tf/providers/Microsoft.Network/networkSecurityGroups/testnsg01]
azurerm_network_security_rule.Allow443: Creating...
azurerm_network_security_rule.Allow443: Still creating... [10s elapsed]
azurerm_network_security_rule.Allow443: Creation complete after 10s [id=/subscriptions/xxxx/resourceGroups/sakana-tf/providers/Microsoft.Network/networkSecurityGroups/testnsg01/securityRules/Allow 443]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

ステートを持つのが特徴ですね、

6. CDKTF (CDK for Terraform)

CDK 経由で Terraform を書けるようなツールです。言語は一番サンプルが多そうな TypeScript にしました。

できるまで苦戦してしまいました。何か指摘があれば Twittter までご連絡いただけるとありがたいです。

  • 環境
    • cdktf 0.14.3

サンプル

main.ts (cdktf init で生成されたものに追記):

// Copyright (c) HashiCorp, Inc
// SPDX-License-Identifier: MPL-2.0
import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AzurermProvider } from '@cdktf/provider-azurerm/lib/provider';
import { NetworkSecurityGroup } from '@cdktf/provider-azurerm/lib/network-security-group';
import { NetworkSecurityRule } from '@cdktf/provider-azurerm/lib/network-security-rule';

class MyStack extends TerraformStack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // define resources here
    new AzurermProvider(this, "azure", { features: {} })

    const nsg = new NetworkSecurityGroup(this, "nsg", {
      location: "japaneast",
      resourceGroupName: "sakana-cdktf",
      name: "sakana-cdktf"
    })

    new NetworkSecurityRule(this, "rule", {
      name: "allow 443",
      networkSecurityGroupName: nsg.name,
      resourceGroupName: "sakana-cdktf",
      direction: "Inbound",
      priority: 110,
      destinationPortRange: "443",
      protocol: "Tcp",
      sourceAddressPrefix: "10.0.0.0/16",
      sourcePortRange: "*",
      destinationAddressPrefix: "*",
      access: "Allow"
    })
  }
}

const app = new App();
new MyStack(app, "cdk");
app.synth();

実行

synth:

% cdktf synth 

Generated Terraform code for the stacks: cdk

deploy:

% cdktf deploy
cdk  Initializing the backend...
cdk  
     Successfully configured the backend "local"! Terraform will automatically
     use this backend unless the backend configuration changes.

     Initializing provider plugins...
     - Finding hashicorp/azurerm versions matching "3.39.1"...
cdk  - Using hashicorp/azurerm v3.39.1 from the shared cache directory
cdk  Terraform has created a lock file .terraform.lock.hcl to record the provider
     selections it made above. Include this file in your version control repository
     so that Terraform can guarantee to make the same selections by default when
     you run "terraform init" in the future.
cdk  ╷
     │ Warning: Incomplete lock file information for providers
     │ 
     │ Due to your customized provider installation methods, Terraform was forced
     │ to calculate lock file checksums locally for the following providers:
     │   - hashicorp/azurerm
     │ 
     │ The current .terraform.lock.hcl file only includes checksums for
     │ darwin_amd64, so Terraform running on another platform will fail to install
     │ these providers.
     │ 
     │ To calculate additional checksums for another platform, run:
     │   terraform providers lock -platform=linux_amd64
     │ (where linux_amd64 is the platform to generate)
     ╵
     
     Terraform has been successfully initialized!
     
     You may now begin working with Terraform. Try running "terraform plan" to see
     any changes that are required for your infrastructure. All Terraform commands
     should now work.

     If you ever set or change modules or backend configuration for Terraform,
     rerun this command to reinitialize your working directory. If you forget, other
     commands will detect it and remind you to do so if necessary.
cdk  Terraform used the selected providers to generate the following execution
     plan. Resource actions are indicated with the following symbols:
       + create
     
     Terraform will perform the following actions:
cdk    # azurerm_network_security_group.nsg (nsg) will be created
       + resource "azurerm_network_security_group" "nsg" {
           + id                  = (known after apply)
           + location            = "japaneast"
           + name                = "sakana-cdktf"
           + resource_group_name = "sakana-cdktf"
           + security_rule       = (known after apply)
         }

       # azurerm_network_security_rule.rule (rule) will be created
       + resource "azurerm_network_security_rule" "rule" {
           + access                      = "Allow"
           + destination_address_prefix  = "*"
           + destination_port_range      = "443"
           + direction                   = "Inbound"
           + id                          = (known after apply)
           + name                        = "allow 443"
           + network_security_group_name = "sakana-cdktf"
           + priority                    = 110
           + protocol                    = "Tcp"
           + resource_group_name         = "sakana-cdktf"
           + source_address_prefix       = "10.0.0.0/16"
           + source_port_range           = "*"
         }

     Plan: 2 to add, 0 to change, 0 to destroy.
     
     ─────────────────────────────────────────────────────────────────────────────

     Saved the plan to: plan

     To perform exactly these actions, run the following command to apply:
         terraform apply "plan"

(ここで Approve を選択)

     To perform exactly these actions, run the following command to apply:
         terraform apply "plan"
cdk  azurerm_network_security_group.nsg (nsg): Creating...
cdk  azurerm_network_security_group.nsg (nsg): Creation complete after 4s [id=/subscriptions/xxxx/resourceGroups/sakana-cdktf/providers/Microsoft.Network/networkSecurityGroups/sakana-cdktf]
cdk  azurerm_network_security_rule.rule (rule): Creating...
cdk  azurerm_network_security_rule.rule (rule): Still creating... [10s elapsed]
cdk  azurerm_network_security_rule.rule (rule): Creation complete after 10s [id=/subscriptions/xxxx/resourceGroups/sakana-cdktf/providers/Microsoft.Network/networkSecurityGroups/sakana-cdktf/securityRules/allow 443]
cdk  
     Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
     

No outputs found.

参考URL

今回 CDKTF を触ったの初めてでした。以下のページを参考にさせていただきました。

Terraform 直接の場合と同じくステートを持ちます。

おわりに

とても簡単ではありますが、一通り触ってみました。触ったことがないものも少しだけ雰囲気を感じることができました。

もちろんコード類の書きっぷりだけでは簡単に比較はできず、運用スタイルや課題、組織が持っているスキルるなどにもよるかと思います。

第一印象としては、Azure 限定でよいなら、Bicep は良いなと思いました。