てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Terraform] IOS XE Terraform provider で設定変更を試してみた

はじめに

Terraform の Cisco IOS XE 向けの Provider がリリースされたことを、先日知りました。

紹介ブログ https://blogs.cisco.com/developer/terraformiosxe01

内部的には CLI を実行するわけではく、RESTCONF ベースののようです。

Terraform はまだ慣れていないのですが、興味があったのでためしてみました。


■ 1. 共通 .tf ファイルの準備

1.1. terraform.tf の作成

利用するプロバイダーの指定です。Registry 上の iosxe providerUSE PROVIDER をクリックして表示されたものを参考しました。

terraform {
  required_providers {
    iosxe = {
      source  = "CiscoDevNet/iosxe"
      version = "0.1.1"
    }
  }
}

1.2. provider.tf の作成

iosxe プロバイダーの各種設定です。 GitHub リポジトリ上のサンプルを参考しました。

provider "iosxe" {
  host            = "https://ネットワーク機器のアドレス"
  device_username = "dummy_user"
  device_password = "dummy_password"

  insecure        = true
}

各種パラメーターの意味はこちらに記載があります。

接続先、ユーザー名、パスワードは、それぞれ以下の環境変数でも良いようです。

  • HOST_IOSXE
  • DEVICE_PASSWORD_IOSXE
  • DEVICE_USERNAME_IOSXE

insecure は、SSL/TLS 証明書の検証をしない指定です。デフォルトでも true です。

■ 2. NTP サーバー設定0台から3台へ (POST)

ここから、実際に実現したい処理の定義です。今回は、Terraform でネットワーク機器に参照先 NTP サーバーの設定(コマンドでいう ntp server)をしてみます。

ネットワーク機器側は NTP サーバーの設定がない状態から始めます。

csrv1000##sh run | inc ntp
csrv1000#

この状態から 3台分追加する処理を試します。

2.1. ntp_post.tf の作成

Getting Startedによると、aclbgpntpospfvlan などさまざまな設定を扱えるようです。[こちらにサンプルがたくさん]8https://github.com/CiscoDevNet/terraform-provider-iosxe/tree/main/examples/examples_tf)あります。どのサンプルを見ても、リソースは iosxe_rest なので、RESTCONF で設定できるものは設定できると思ってもいいのかもしれません。

今回はお試しということで、シンプルに試せそうという点で ntp にしました。

以下のファイルは、NTP サーバー 10.0.0.110.0.0.210.0.0.3 を追加するものです。

resource "iosxe_rest" "ntp_post" {
  method = "POST"
  path   = "/data/Cisco-IOS-XE-native:native/ntp/server"
  payload = jsonencode(
    {
      "Cisco-IOS-XE-ntp:server-list" : [
        {
          "ip-address" : "10.0.0.1",
        },
        {
          "ip-address" : "10.0.0.2",
        },
        {
          "ip-address" : "10.0.0.3",
        }
      ]
    }
}
パラメーター名 説明
method POSTPUTPATCHDELETE` などのような RESTCONF のメソッドを指定
path 設定するためのエンドポイントを指定
payload 設定刷るための body を指定

ということで、ほとんど RESTCONF で叩くとき同じ感覚ですね。

2.2. terraform init の実行

ここまで以下のファイルを作りました。

ntp_post.tf
provider.tf
terraform.tf

同じディレクトリで、terraform init を実行します

% terraform init 

Initializing the backend...

Initializing provider plugins...
- Finding ciscodevnet/iosxe versions matching "0.1.1"...
- Installing ciscodevnet/iosxe v0.1.1...
- Installed ciscodevnet/iosxe v0.1.1 (self-signed, key ID F8EC53CAB70CD366)
...(略)...

2.3. terraform plan の実行

続いて terraform plan を実行します。3台分追加しますよ、とのことです。

% 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:

  # iosxe_rest.ntp_post will be created
  + resource "iosxe_rest" "ntp_post" {
      + id       = (known after apply)
      + method   = "POST"
      + path     = "/data/Cisco-IOS-XE-native:native/ntp/server"
      + payload  = jsonencode(
            {
              + Cisco-IOS-XE-ntp:server-list = [
                  + {
                      + ip-address = "10.0.0.1"
                    },
                  + {
                      + ip-address = "10.0.0.2"
                    },
                  + {
                      + ip-address = "10.0.0.3"
                    },
                ]
            }
        )
      + response = (known after apply)
    }

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

2.4. terraform apply の実行

いよいよ terraform 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:

  # iosxe_rest.ntp_post will be created
  + resource "iosxe_rest" "ntp_post" {
      + id       = (known after apply)
      + method   = "POST"
      + path     = "/data/Cisco-IOS-XE-native:native/ntp/server"
      + payload  = jsonencode(
            {
              + Cisco-IOS-XE-ntp:server-list = [
                  + {
                      + ip-address = "10.0.0.1"
                    },
                  + {
                      + ip-address = "10.0.0.2"
                    },
                  + {
                      + ip-address = "10.0.0.3"
                    },
                ]
            }
        )
      + response = (known after apply)
    }

Plan: 1 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    # yes を入力

iosxe_rest.ntp_post: Creating...
iosxe_rest.ntp_post: Creation complete after 1s [id=4155581422]

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

ネットワーク機器側を確認すると無事に3台分が追加されていることが確認できました。

csrv1000(config)#do sh run | inc ntp   
ntp server 10.0.0.1
ntp server 10.0.0.2
ntp server 10.0.0.3

2.5. terraform apply の「再」実行

さて、この状態からもう一度 terraform apply を実行するとどうなるのだろうと思いました。

普通に RESTCONF で考えると、すでに設定が入ってる状態に 再度 POST すると 409 Conflict あたりになるかと思います。

ということで試しました。

% terraform plan 
iosxe_rest.ntp_post: Refreshing state... [id=4155581422]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

No changes. なので、特に何もしない結果になりました。

これは、Terraform 側の処理として手元に保管されている state ファイルと比較した結果、一致しているのでリクエストすら出さなかった、という状態だと思います。このあたりは、Terraform らしさでしょうか。


■ 3. NTP サーバー設定3台から2台 (POSTのまま、エラー)

続いて、エラーになるかなと思いつつも、tf ファイルで4台目を追加して POST のまま実行にする、というのを試しました。

3.1. ntp_post.tf の修正

ntp_post.tf を以下ように、4台目の分を追記します。

resource "iosxe_rest" "ntp_post" {
  method = "POST"
  path   = "/data/Cisco-IOS-XE-native:native/ntp/server"
  payload = jsonencode(
    {
      "Cisco-IOS-XE-ntp:server-list" : [
        {
          "ip-address" : "10.0.0.1"
        },
        {
          "ip-address" : "10.0.0.2"
        },
        {
          "ip-address" : "10.0.0.3"
        },
        {
          "ip-address" : "10.0.0.4"     # 4台目追加
        }
      ]
    }
  )
}

3.2. terraform plan の実行

terraform plan を実行します。

これだけ見ると 4台目だけいい感じに追加されるようにみえますが・・・。

% terraform plan
iosxe_rest.ntp_post: Refreshing state... [id=4155581422]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # iosxe_rest.ntp_post will be updated in-place
  ~ resource "iosxe_rest" "ntp_post" {
        id      = "4155581422"
      ~ payload = jsonencode(
          ~ {
              ~ Cisco-IOS-XE-ntp:server-list = [
                    # (2 unchanged elements hidden)
                    {
                        ip-address = "10.0.0.3"
                    },
                  + {
                      + ip-address = "10.0.0.4"
                    },
                ]
            }
        )
        # (2 unchanged attributes hidden)
    }

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

3.3.terraform apply の実行

実際に terraform apply を実行するとエラーになります。

% terraform apply
iosxe_rest.ntp_post: Refreshing state... [id=4155581422]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # iosxe_rest.ntp_post will be updated in-place
  ~ resource "iosxe_rest" "ntp_post" {
        id      = "4155581422"
      ~ payload = jsonencode(
          ~ {
              ~ Cisco-IOS-XE-ntp:server-list = [
                    # (2 unchanged elements hidden)
                    {
                        ip-address = "10.0.0.3"
                    },
                  + {
                      + ip-address = "10.0.0.4"
                    },
                ]
            }
        )
        # (2 unchanged attributes hidden)
    }

Plan: 0 to add, 1 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

iosxe_rest.ntp_post: Modifying... [id=4155581422]
╷
│ Error: failed: status code: 409 - error: {
│   "errors": {
│     "error": [
│       {
│         "error-message": "object already exists: /ios:native/ios:ntp/ios-ntp:server/ios-ntp:server-list[ios-ntp:ip-address='10.0.0.1']",
│         "error-path": "/Cisco-IOS-XE-native:native/ntp/Cisco-IOS-XE-ntp:server",
│         "error-tag": "data-exists",
│         "error-type": "application"
│       }
│     ]
│   }
│ }
│ 
│ 
│   with iosxe_rest.ntp_post,
│   on ntp_post.tf line 1, in resource "iosxe_rest" "ntp_post":
│    1: resource "iosxe_rest" "ntp_post" {
│ 
╵

Terraform 的に、差分ありと判断してリクエストを出した結果、すでにある設定を POST してるので、エラーになったということだと思います。ステータスコード409、また "error-tag": "data-exists" とあります。

3.4. 仕切り直し

さて、こうなると少々厄介です。 state ファイルには 4台目がある状態で、ネットワーク機器側3台分のままです。

今回は挙動をいろいろ確認するためなので、だいぶ乱暴ですがいったん state ファイル類を削除します(通常は推奨されることではないと思います)。

rm -fr .terraform .terraform.lock.hcl terraform.tfstate terraform.tfstate.backup

ネットワーク機器側も NTP サーバーの設定を削除します。

csrv1000(config)#no ntp server 10.0.0.1
csrv1000(config)#no ntp server 10.0.0.2
csrv1000(config)#no ntp server 10.0.0.3
csrv1000(config)#do sh run | inc ntp   
csrv1000(config)#


■ 4. NTP サーバー設定0台から3台へ (PUT)

Terraform 側もネットワーク機器側も状態をもとに戻したところで、仕切り直しです。

4.1. ntp_put.tf の作成

ntp_post.tf は削除して、別途 ntp_put.tf を作成します。ntp_post.tf と比較すると、method"POST" にしている他、pathpayload も微妙に変更しています。

resource "iosxe_rest" "ntp_put" {
  method = "PUT"
  path   = "/data/Cisco-IOS-XE-native:native/ntp"
  payload = jsonencode(
    {
      "Cisco-IOS-XE-native:ntp" : {
        "Cisco-IOS-XE-ntp:server" : {
          "server-list" : [
            {
              "ip-address" : "10.0.0.1"
            },
            {
              "ip-address" : "10.0.0.2"
            },
            {
              "ip-address" : "10.0.0.3"
            }
          ]
        }
      }
    }
  )
}

4.2. terraform init の実行

先程、自動生成されたファイルを削除したので、terraform init を実行します。

4.3. terraform plan の実行

PUT 版で terraform plan を実行します。

3台分追加するよ、と示される点は POST のときとだいたい同じように見えます。

% 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:

  # iosxe_rest.ntp_put will be created
  + resource "iosxe_rest" "ntp_put" {
      + id       = (known after apply)
      + method   = "PUT"
      + path     = "/data/Cisco-IOS-XE-native:native/ntp"
      + payload  = jsonencode(
            {
              + Cisco-IOS-XE-native:ntp = {
                  + Cisco-IOS-XE-ntp:server = {
                      + server-list = [
                          + {
                              + ip-address = "10.0.0.1"
                            },
                          + {
                              + ip-address = "10.0.0.2"
                            },
                          + {
                              + ip-address = "10.0.0.3"
                            },
                        ]
                    }
                }
            }
        )
      + response = (known after apply)
    }

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

4.4 terraform apply の実行

terraform 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:

  # iosxe_rest.ntp_put will be created
  + resource "iosxe_rest" "ntp_put" {
      + id       = (known after apply)
      + method   = "PUT"
      + path     = "/data/Cisco-IOS-XE-native:native/ntp"
      + payload  = jsonencode(
            {
              + Cisco-IOS-XE-native:ntp = {
                  + Cisco-IOS-XE-ntp:server = {
                      + server-list = [
                          + {
                              + ip-address = "10.0.0.1"
                            },
                          + {
                              + ip-address = "10.0.0.2"
                            },
                          + {
                              + ip-address = "10.0.0.3"
                            },
                        ]
                    }
                }
            }
        )
      + response = (known after apply)
    }

Plan: 1 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

iosxe_rest.ntp_put: Creating...
iosxe_rest.ntp_put: Creation complete after 1s [id=4117814902]

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

ネットワーク機器側の設定を確認すると無事に設定が入っていました。

csrv1000(config)#do sh run | inc ntp
ntp server 10.0.0.1
ntp server 10.0.0.2
ntp server 10.0.0.3


■ 5. NTP サーバー設定3台から4台へ (PUT)

3台分設定されている状態から、PUTで4台分にします。

5.1. ntp_put.tf の修正

PUT で 3台追加したところで、今度は先程使った ntp_put.tf を編集して、4台の設定にします。

resource "iosxe_rest" "ntp_put" {
  method = "PUT"
  path   = "/data/Cisco-IOS-XE-native:native/ntp"
  payload = jsonencode(
    {
      "Cisco-IOS-XE-native:ntp" : {
        "Cisco-IOS-XE-ntp:server" : {
          "server-list" : [
            {
              "ip-address" : "10.0.0.1"
            },
            {
              "ip-address" : "10.0.0.2"
            },
            {
              "ip-address" : "10.0.0.3"
            },
            {
              "ip-address" : "10.0.0.4"    # 4台目
            }
          ]
        }
      }
    }
  )
}

5.2. terraform plan の実行

terraform plan を実行します。4台目 10.0.0.4 を追加しますよ、と表示されます。

% terraform plan 
iosxe_rest.ntp_put: Refreshing state... [id=4117814902]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # iosxe_rest.ntp_put will be updated in-place
  ~ resource "iosxe_rest" "ntp_put" {
        id      = "4117814902"
      ~ payload = jsonencode(
          ~ {
              ~ Cisco-IOS-XE-native:ntp = {
                  ~ Cisco-IOS-XE-ntp:server = {
                      ~ server-list = [
                            # (2 unchanged elements hidden)
                            {
                                ip-address = "10.0.0.3"
                            },
                          + {
                              + ip-address = "10.0.0.4"
                            },
                        ]
                    }
                }
            }
        )
        # (2 unchanged attributes hidden)
    }

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

5.3. terraform apply の実行

terraform apply を実行します。

% terraform apply
iosxe_rest.ntp_put: Refreshing state... [id=4117814902]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # iosxe_rest.ntp_put will be updated in-place
  ~ resource "iosxe_rest" "ntp_put" {
        id      = "4117814902"
      ~ payload = jsonencode(
          ~ {
              ~ Cisco-IOS-XE-native:ntp = {
                  ~ Cisco-IOS-XE-ntp:server = {
                      ~ server-list = [
                            # (2 unchanged elements hidden)
                            {
                                ip-address = "10.0.0.3"
                            },
                          + {
                              + ip-address = "10.0.0.4"
                            },
                        ]
                    }
                }
            }
        )
        # (2 unchanged attributes hidden)
    }

Plan: 0 to add, 1 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

iosxe_rest.ntp_put: Modifying... [id=4117814902]
iosxe_rest.ntp_put: Modifications complete after 2s [id=477754463]

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

無事に設定されました。

csrv1000(config)#do sh run | inc ntp
ntp server 10.0.0.1
ntp server 10.0.0.2
ntp server 10.0.0.3
ntp server 10.0.0.4


■ 6. NTP サーバー設定4台から1台へ (PUT)

4台分設定されている状態から、PUTで1台分にします。

6.1. ntp_put.tf の修正

ntp_put.tf を以下のように、1台分(10.0.0.1)だけになるように修正します。method"PUT" のままです。

resource "iosxe_rest" "ntp_put" {
  method = "PUT"
  path   = "/data/Cisco-IOS-XE-native:native/ntp"
  payload = jsonencode(
    {
      "Cisco-IOS-XE-native:ntp" : {
        "Cisco-IOS-XE-ntp:server" : {
          "server-list" : [
            {
              "ip-address" : "10.0.0.1"
            }
          ]
        }
      }
    }
  )
}

他の3台を削除する、といった指定はせず、とにかく指定した1台分(10.0.0.1)になってほしい、意味合いの指定です。

6.2. terraform plan の実行

terraform plan を実行します。10.0.0.210.0.0.310.0.0.4 は削除しますよと示されます。

% terraform plan
iosxe_rest.ntp_put: Refreshing state... [id=477754463]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # iosxe_rest.ntp_put will be updated in-place
  ~ resource "iosxe_rest" "ntp_put" {
        id      = "477754463"
      ~ payload = jsonencode(
          ~ {
              ~ Cisco-IOS-XE-native:ntp = {
                  ~ Cisco-IOS-XE-ntp:server = {
                      ~ server-list = [
                            {
                                ip-address = "10.0.0.1"
                            },
                          - {
                              - ip-address = "10.0.0.2"
                            },
                          - {
                              - ip-address = "10.0.0.3"
                            },
                          - {
                              - ip-address = "10.0.0.4"
                            },
                        ]
                    }
                }
            }
        )
        # (2 unchanged attributes hidden)
    }

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

6.3. terraform apply の実行

terraform apply を実行します。

% terraform apply
iosxe_rest.ntp_put: Refreshing state... [id=477754463]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # iosxe_rest.ntp_put will be updated in-place
  ~ resource "iosxe_rest" "ntp_put" {
        id      = "477754463"
      ~ payload = jsonencode(
          ~ {
              ~ Cisco-IOS-XE-native:ntp = {
                  ~ Cisco-IOS-XE-ntp:server = {
                      ~ server-list = [
                            {
                                ip-address = "10.0.0.1"
                            },
                          - {
                              - ip-address = "10.0.0.2"
                            },
                          - {
                              - ip-address = "10.0.0.3"
                            },
                          - {
                              - ip-address = "10.0.0.4"
                            },
                        ]
                    }
                }
            }
        )
        # (2 unchanged attributes hidden)
    }

Plan: 0 to add, 1 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

iosxe_rest.ntp_put: Modifying... [id=477754463]
iosxe_rest.ntp_put: Modifications complete after 1s [id=1111786887]

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

無事に 10.0.0.1 のみになりました。

csrv1000(config)#do sh run | inc ntp
ntp server 10.0.0.1
csrv1000(config)#

method としては、他にも PATCHDELETE もしてできますが、今回はこの辺にしたいと思います。




まとめ・所感

Terraform の IOS XE Provider を利用して、IOS XE の機器にのNTPサーバー設定を試してみました。

以下、まとめと所感です。

  • RESTCONF ベースなのでCLIベースの自動化の悩み(noコマンドの生成)はなさそう
    • その代わり RESTCONF の知識が必要
  • 手続き型に考えが染まっているので、宣言型の良さをまだ活かせる自身がない
    • リソースをどう扱えばよいか悩みそう
    • method をどうすればよいか悩みそう。PUT が一番相性がいいように思う
    • 本格的に使う上では考慮事項がそれなりにありそう
  • 同じ内容のPOST を2回 実行してもエラーにならなかったのは Terraform らしさを感じた
  • Terraform か RESTCONF のどちらかに慣れていないと、エラーが発生したときにどちらの問題か判断しにくい
  • Terraform なのでWindowsからも実行できるはず(今回はmacOSから)

参考資料

紹介ブログ https://blogs.cisco.com/developer/terraformiosxe01

GitHub リポジトリ https://github.com/CiscoDevNet/terraform-provider-iosxe

tf ファイルのサンプル https://github.com/CiscoDevNet/terraform-provider-iosxe/tree/main/examples/examples_tf

動画 Cisco IOS XE Terraform provider introduction and demo https://www.youtube.com/watch?v=oB_QZ2mDiW0

上記動画の資料 https://github.com/CiscoDevNet/terraform-provider-iosxe/blob/main/docs/resources/intro_to_terraform_video.pdf

Terraform Registry https://registry.terraform.io/providers/CiscoDevNet/iosxe/latest