てくなべ (tekunabe)

ansible / network automation / 学習メモ

会社の本棚に追加してほしいとリクエストした技術書42冊

はじめに

先日、会社の本棚に追加してほしい本をリクエストする機会があったので、その時私がリクエストした本を掲載します。

「このラインナップなら、あの本もでしょ!」という本もあるかと思いますが、たぶんすでに本棚にあるものだと思います。あくまで、今ないものをリクエストしました。


基礎

みんなのコンピュータサイエンス

みんなのコンピュータサイエンス

開発

テスト駆動開発

テスト駆動開発

  • 作者:Kent Beck
  • 出版社/メーカー: オーム社
  • 発売日: 2017/10/14
  • メディア: 単行本(ソフトカバー)
改訂2版 みんなのGo言語

改訂2版 みんなのGo言語

Clean Architecture 達人に学ぶソフトウェアの構造と設計

Clean Architecture 達人に学ぶソフトウェアの構造と設計

現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法

現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法

アジャイルサムライ−達人開発者への道−

アジャイルサムライ−達人開発者への道−

SCRUM BOOT CAMP THE BOOK

SCRUM BOOT CAMP THE BOOK

ソフトウェア・ファースト あらゆるビジネスを一変させる最強戦略

ソフトウェア・ファースト あらゆるビジネスを一変させる最強戦略

インフラ

インフラエンジニアの教科書

インフラエンジニアの教科書

  • 作者:佐野 裕
  • 出版社/メーカー: シーアンドアール研究所
  • 発売日: 2013/10/26
  • メディア: 単行本(ソフトカバー)
インフラエンジニアの教科書2 スキルアップに効く技術と知識

インフラエンジニアの教科書2 スキルアップに効く技術と知識

  • 作者:佐野 裕
  • 出版社/メーカー: シーアンドアール研究所
  • 発売日: 2016/08/26
  • メディア: 単行本(ソフトカバー)
UNIXという考え方―その設計思想と哲学

UNIXという考え方―その設計思想と哲学

Kubernetes完全ガイド (impress top gear)

Kubernetes完全ガイド (impress top gear)

Docker/Kubernetes 実践コンテナ開発入門

Docker/Kubernetes 実践コンテナ開発入門

実践Terraform AWSにおけるシステム設計とベストプラクティス (技術の泉シリーズ(NextPublishing))

実践Terraform AWSにおけるシステム設計とベストプラクティス (技術の泉シリーズ(NextPublishing))

入門 監視 ―モダンなモニタリングのためのデザインパターン

入門 監視 ―モダンなモニタリングのためのデザインパターン

プロトコル

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

TCP技術入門 ――進化を続ける基本プロトコル (WEB+DB PRESS plusシリーズ)

TCP技術入門 ――進化を続ける基本プロトコル (WEB+DB PRESS plusシリーズ)

ネットワーク

ネットワークはなぜつながるのか 第2版 知っておきたいTCP/IP、LAN、光ファイバの基礎知識

ネットワークはなぜつながるのか 第2版 知っておきたいTCP/IP、LAN、光ファイバの基礎知識

  • 作者:戸根 勤
  • 出版社/メーカー: 日経BP
  • 発売日: 2007/04/12
  • メディア: 単行本(ソフトカバー)
インターネットのカタチ―もろさが織り成す粘り強い世界―

インターネットのカタチ―もろさが織り成す粘り強い世界―

パケットキャプチャの教科書 (Informatics&IDEA)

パケットキャプチャの教科書 (Informatics&IDEA)

マスタリングTCP/IP 入門編 第5版

マスタリングTCP/IP 入門編 第5版

ヤマハルーターでつくるインターネットVPN [第5版]

ヤマハルーターでつくるインターネットVPN [第5版]

その他

人と機械の共生のデザイン-「人間中心の自動化」を探る

人と機械の共生のデザイン-「人間中心の自動化」を探る

  • 作者:稲垣 敏之
  • 出版社/メーカー: 森北出版
  • 発売日: 2012/12/08
  • メディア: 単行本(ソフトカバー)

[Ansible] json_query フィルターの実体は JMESPath

Ansible で複雑な構造の変数から特定の値を抽出する際、selectmaplist などのフィルターを駆使することがあります。

それでもだんだん苦しくなってきたときは、json_query フィルターが便利です。

Ansible もくもく会F5編のコンテンツにもちらほら登場します。

このフィルターの実体は、JMESPathです。 (そのため、pip install jmespath のように予めインストールしておく必 要があります。)

かなり複雑な条件を指定できます。jq の書式とはまた異なるものです。

Ansible の json_query フィルターのドキュメントよりもっと詳しく知りたい場合は、JMESPath のドキュメントを参照するのが吉です。

[Terraform/ACI] Terraform の Cisco ACI Provider で APIC を設定する

はじめに

本ブログでは、これまでいくつかのAnsible と ACI 対応についての記事を書いてきましたが、 Terraform も ACI に対応していることを最近知りました。

少しだけ試してみましたのでまとめます。

環境

  • Cisco DevNet Sandbox APIC 4.1(1k)
  • Terraform v0.12.19


■ Terraform の ACI 対応

Terraform の公式ドキュメントに詳細が記載されています。カテゴリとしては、Network ではなく、Cloudという扱いのようです。

https://www.terraform.io/docs/providers/aci/index.htmlwww.terraform.io

Tenant、Bridge Domain、Subnet、Contract などのオブジェクトを扱う Data Resources や Resources がそれぞれ 40以上用意されています。

aci_any Data Source や、aci_anyaci_rest Resource もあるので、専用のものがなくても、融通が効きそうです。


■ おためし

今回は、以下のような Tenant、Bridge Domain、VRF、Subnet を作ります。

f:id:akira6592:20200114152053p:plain
各オブジェクトの作成

tf ファイルの作成

main.tfと、bd.tf の 2つのファイルを作成します。(雰囲気で分けています)

main.tf の作成

provider の定義をします。

provider "aci" {
  username = "admin"
  password = "xxxdummyxxx"
  url      = "https://sandboxapicdc.cisco.com/"
  insecure = true
}

以前の記事でも書いたとおりAPIC の認証方式には、パスワードベース認証方式と、署名ベース認証方式の2種類あります。今回は手軽さ重視でパスワードベース認証方式を利用するように、privider を定義しています。

HTTPS 接続時の証明書の検証を無効化する場合は、insecuretrue に指定します。デフォルトも true です。

その他、パラメータの説明はCisco ACI Provider の公式ドキュメントを参照してください。

bd.tf の作成

Tenant、Bridge Domain、VRF、Subnet の Resource を定義をします。

resource "aci_tenant" "test_tenant1" {
  name        = "test_tenant1"
  description = "test tenant"
}

resource "aci_vrf" "test_vrf1" {
  tenant_dn   = aci_tenant.test_tenant1.id
  name        = "test_vrf1"
  description = "test vrf"
}

resource "aci_bridge_domain" "test_bd1" {
  tenant_dn          = aci_tenant.test_tenant1.id
  name               = "test_bd1"
  description        = "test bridge domain"
  relation_fv_rs_ctx = aci_vrf.test_vrf1.name
}

resource "aci_subnet" "test_subnet1" {
  bridge_domain_dn = aci_bridge_domain.test_bd1.id
  description      = "test_subnet1"
  ip               = "10.0.3.28/27"
} 

補足

少し調べるのにつまずいたのが、Bridge Domain から VRF への関連付けの方法です。以下の部分です。

  relation_fv_rs_ctx  = aci_vrf.test_vrf1.name

aci_bridge_domain を参照しても vrf という文字が見当たらないので、対応するパラメーターは無いのかもと思っていました。 ですが、ACI 用語としての VRF は Context とも呼ばれていて、クラス名?が fvCtx であることを思いだして、relation_fv_rs_ctx で設定を試したした。

なお、よく見かけるサンプルでは、id などの参照を "${}" で囲って

  tenant_dn = "${aci_tenant.test_tenant1.id}"

のようにしていますが、

Warning: Interpolation-only expressions are deprecated

という警告が表示されるので修正しました。

初回実行

terraform init コマンドの実行

terraform init コマンドで初期化します。

実行ログ(クリックして広げる)

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aci" (terraform-providers/aci) 0.1.4...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aci: version = "~> 0.1"

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.
$

terraform plan コマンドの実行

terraform plan コマンドで確認します。

実行ログ(クリックして広げる)

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aci_bridge_domain.test_bd1 will be created
  + resource "aci_bridge_domain" "test_bd1" {
      + annotation                  = (known after apply)
      + arp_flood                   = (known after apply)
      + bridge_domain_type          = (known after apply)
      + description                 = "test bridge domain"
      + ep_clear                    = (known after apply)
      + ep_move_detect_mode         = (known after apply)
      + host_based_routing          = (known after apply)
      + id                          = (known after apply)
      + intersite_bum_traffic_allow = (known after apply)
      + intersite_l2_stretch        = (known after apply)
      + ip_learning                 = (known after apply)
      + ipv6_mcast_allow            = (known after apply)
      + limit_ip_learn_to_subnets   = (known after apply)
      + ll_addr                     = (known after apply)
      + mac                         = (known after apply)
      + mcast_allow                 = (known after apply)
      + multi_dst_pkt_act           = (known after apply)
      + name                        = "test_bd1"
      + name_alias                  = (known after apply)
      + optimize_wan_bandwidth      = (known after apply)
      + relation_fv_rs_ctx          = "test_vrf1"
      + tenant_dn                   = (known after apply)
      + unicast_route               = (known after apply)
      + unk_mac_ucast_act           = (known after apply)
      + unk_mcast_act               = (known after apply)
      + v6unk_mcast_act             = (known after apply)
      + vmac                        = (known after apply)
    }

  # aci_subnet.test_subnet1 will be created
  + resource "aci_subnet" "test_subnet1" {
      + annotation       = (known after apply)
      + bridge_domain_dn = (known after apply)
      + ctrl             = (known after apply)
      + description      = "test_subnet1"
      + id               = (known after apply)
      + ip               = "10.0.3.28/27"
      + name_alias       = (known after apply)
      + preferred        = (known after apply)
      + scope            = (known after apply)
      + virtual          = (known after apply)
    }

  # aci_tenant.test_tenant1 will be created
  + resource "aci_tenant" "test_tenant1" {
      + annotation  = (known after apply)
      + description = "test tenant"
      + id          = (known after apply)
      + name        = "test_tenant1"
      + name_alias  = (known after apply)
    }

  # aci_vrf.test_vrf1 will be created
  + resource "aci_vrf" "test_vrf1" {
      + annotation             = (known after apply)
      + bd_enforced_enable     = (known after apply)
      + description            = "test vrf"
      + id                     = (known after apply)
      + ip_data_plane_learning = (known after apply)
      + knw_mcast_act          = (known after apply)
      + name                   = "test_vrf1"
      + name_alias             = (known after apply)
      + pc_enf_dir             = (known after apply)
      + pc_enf_pref            = (known after apply)
      + tenant_dn              = (known after apply)
    }

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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

$ 

もろもろオブジェクトが作成されることが確認できます。plan なのでまだ実際に作成はされれいません。

terraform apply コマンドの実行

いよいよ terraform apply コマンドで、変更を適用します。

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aci_bridge_domain.test_bd1 will be created
  + resource "aci_bridge_domain" "test_bd1" {
      + annotation                  = (known after apply)
      + arp_flood                   = (known after apply)
      + bridge_domain_type          = (known after apply)
      + description                 = "test bridge domain"
      + ep_clear                    = (known after apply)
      + ep_move_detect_mode         = (known after apply)
      + host_based_routing          = (known after apply)
      + id                          = (known after apply)
      + intersite_bum_traffic_allow = (known after apply)
      + intersite_l2_stretch        = (known after apply)
      + ip_learning                 = (known after apply)
      + ipv6_mcast_allow            = (known after apply)
      + limit_ip_learn_to_subnets   = (known after apply)
      + ll_addr                     = (known after apply)
      + mac                         = (known after apply)
      + mcast_allow                 = (known after apply)
      + multi_dst_pkt_act           = (known after apply)
      + name                        = "test_bd1"
      + name_alias                  = (known after apply)
      + optimize_wan_bandwidth      = (known after apply)
      + relation_fv_rs_ctx          = "test_vrf1"
      + tenant_dn                   = (known after apply)
      + unicast_route               = (known after apply)
      + unk_mac_ucast_act           = (known after apply)
      + unk_mcast_act               = (known after apply)
      + v6unk_mcast_act             = (known after apply)
      + vmac                        = (known after apply)
    }

  # aci_subnet.test_subnet1 will be created
  + resource "aci_subnet" "test_subnet1" {
      + annotation       = (known after apply)
      + bridge_domain_dn = (known after apply)
      + ctrl             = (known after apply)
      + description      = "test_subnet1"
      + id               = (known after apply)
      + ip               = "10.0.3.28/27"
      + name_alias       = (known after apply)
      + preferred        = (known after apply)
      + scope            = (known after apply)
      + virtual          = (known after apply)
    }

  # aci_tenant.test_tenant1 will be created
  + resource "aci_tenant" "test_tenant1" {
      + annotation  = (known after apply)
      + description = "test tenant"
      + id          = (known after apply)
      + name        = "test_tenant1"
      + name_alias  = (known after apply)
    }

  # aci_vrf.test_vrf1 will be created
  + resource "aci_vrf" "test_vrf1" {
      + annotation             = (known after apply)
      + bd_enforced_enable     = (known after apply)
      + description            = "test vrf"
      + id                     = (known after apply)
      + ip_data_plane_learning = (known after apply)
      + knw_mcast_act          = (known after apply)
      + name                   = "test_vrf1"
      + name_alias             = (known after apply)
      + pc_enf_dir             = (known after apply)
      + pc_enf_pref            = (known after apply)
      + tenant_dn              = (known after apply)
    }

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

aci_tenant.test_tenant1: Creating...
aci_tenant.test_tenant1: Creation complete after 3s [id=uni/tn-test_tenant1]
aci_vrf.test_vrf1: Creating...
aci_vrf.test_vrf1: Still creating... [10s elapsed]
aci_vrf.test_vrf1: Creation complete after 18s [id=uni/tn-test_tenant1/ctx-test_vrf1]
aci_bridge_domain.test_bd1: Creating...
aci_bridge_domain.test_bd1: Creation complete after 5s [id=uni/tn-test_tenant1/BD-test_bd1]
aci_subnet.test_subnet1: Creating...
aci_subnet.test_subnet1: Creation complete after 2s [id=uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]]

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
$ 

無事に Tenant、Bridge Domain、VRF、Subnet が作成されました。

APIC 画面でも確認します。

f:id:akira6592:20200114150045p:plain
一通り作成された


■ 宣言的であることの確認

宣言的と手続き的

ここまでで、一通りのオブジェクトが作成されました。 Terraform の特徴を掴むために、tf ファイルを少し修正して動作を確認します。

具体的に確認したい点は、Terraform は宣言的であるという点です。tf ファイルでは、あるべき状態を宣言的に定義します。そのため、定義を削除して apply すると、差分を検出して削除処理が実行されます。

一方、Ansible の Playook は全体としては手続き型です。そのため、タスク定義を削除した場合、そのタスクは実行されないというだけで、実態は残ったままになります。

確認1: Subnet 定義の削除

db.tf の修正

まず、db.tf から、Subnet の定義を削除します。

f:id:akira6592:20200114152138p:plain
Subnet の定義は削除

resource "aci_tenant" "test_tenant1" {
  name        = "test_tenant1"
  description = "test tenant"
}

resource "aci_vrf" "test_vrf1" {
  tenant_dn   = aci_tenant.test_tenant1.id
  name        = "test_vrf1"
  description = "test vrf"
}

resource "aci_bridge_domain" "test_bd1" {
  tenant_dn          = aci_tenant.test_tenant1.id
  name               = "test_bd1"
  description        = "test bridge domain"
  relation_fv_rs_ctx = aci_vrf.test_vrf1.name
}

# Subnet の定義は削除

実行

続いて、terraform planterraform apply します。

terraform plan 実行ログ(クリックして広げる)

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

aci_subnet.test_subnet1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]]
aci_tenant.test_tenant1: Refreshing state... [id=uni/tn-test_tenant1]
aci_vrf.test_vrf1: Refreshing state... [id=uni/tn-test_tenant1/ctx-test_vrf1]
aci_bridge_domain.test_bd1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1]

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aci_subnet.test_subnet1 will be destroyed
  - resource "aci_subnet" "test_subnet1" {
      - bridge_domain_dn = "uni/tn-test_tenant1/BD-test_bd1" -> null
      - ctrl             = "nd" -> null
      - description      = "test_subnet1" -> null
      - id               = "uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]" -> null
      - ip               = "10.0.3.28/27" -> null
      - preferred        = "no" -> null
      - scope            = "private" -> null
      - virtual          = "no" -> null
    }

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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

terraform apply 実行ログ(クリックして広げる)

$ terraform apply
aci_subnet.test_subnet1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]]
aci_tenant.test_tenant1: Refreshing state... [id=uni/tn-test_tenant1]
aci_vrf.test_vrf1: Refreshing state... [id=uni/tn-test_tenant1/ctx-test_vrf1]
aci_bridge_domain.test_bd1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aci_subnet.test_subnet1 will be destroyed
  - resource "aci_subnet" "test_subnet1" {
      - bridge_domain_dn = "uni/tn-test_tenant1/BD-test_bd1" -> null
      - ctrl             = "nd" -> null
      - description      = "test_subnet1" -> null
      - id               = "uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]" -> null
      - ip               = "10.0.3.28/27" -> null
      - preferred        = "no" -> null
      - scope            = "private" -> null
      - virtual          = "no" -> null
    }

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

aci_subnet.test_subnet1: Destroying... [id=uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]]
aci_subnet.test_subnet1: Destruction complete after 1s

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

確認

APIC の画面で確認します。

f:id:akira6592:20200114150615p:plain:w400
Subnet が削除された
Subnet の定義が削除され、宣言的な動作を確認できました。

Ansible の Playbook の場合は、Subnet 定義のタスクを削除しても、Subnet 定義は残ったままになるはずです。

確認2: Tenant 定義ごとの削除

db.tf の修正

今度は、bd.tf を空にして、Tenant の定義ごと削除します。

f:id:akira6592:20200114152217p:plain
Tenant 定義ごと削除

実行

続いて、terraform planterraform apply コマンドを実行します。

terraform plan 実行ログ(クリックして広げる)

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

aci_tenant.test_tenant1: Refreshing state... [id=uni/tn-test_tenant1]
aci_vrf.test_vrf1: Refreshing state... [id=uni/tn-test_tenant1/ctx-test_vrf1]
aci_bridge_domain.test_bd1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1]

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aci_bridge_domain.test_bd1 will be destroyed
  - resource "aci_bridge_domain" "test_bd1" {
      - arp_flood                   = "no" -> null
      - bridge_domain_type          = "regular" -> null
      - description                 = "test bridge domain" -> null
      - ep_clear                    = "no" -> null
      - host_based_routing          = "no" -> null
      - id                          = "uni/tn-test_tenant1/BD-test_bd1" -> null
      - intersite_bum_traffic_allow = "no" -> null
      - intersite_l2_stretch        = "no" -> null
      - ip_learning                 = "yes" -> null
      - limit_ip_learn_to_subnets   = "yes" -> null
      - ll_addr                     = "::" -> null
      - mac                         = "00:22:BD:F8:19:FF" -> null
      - mcast_allow                 = "no" -> null
      - multi_dst_pkt_act           = "bd-flood" -> null
      - name                        = "test_bd1" -> null
      - optimize_wan_bandwidth      = "no" -> null
      - relation_fv_rs_ctx          = "test_vrf1" -> null
      - tenant_dn                   = "uni/tn-test_tenant1" -> null
      - unicast_route               = "yes" -> null
      - unk_mac_ucast_act           = "proxy" -> null
      - unk_mcast_act               = "flood" -> null
      - v6unk_mcast_act             = "flood" -> null
      - vmac                        = "not-applicable" -> null
    }

  # aci_tenant.test_tenant1 will be destroyed
  - resource "aci_tenant" "test_tenant1" {
      - description = "test tenant" -> null
      - id          = "uni/tn-test_tenant1" -> null
      - name        = "test_tenant1" -> null
    }

  # aci_vrf.test_vrf1 will be destroyed
  - resource "aci_vrf" "test_vrf1" {
      - bd_enforced_enable     = "no" -> null
      - description            = "test vrf" -> null
      - id                     = "uni/tn-test_tenant1/ctx-test_vrf1" -> null
      - ip_data_plane_learning = "enabled" -> null
      - knw_mcast_act          = "permit" -> null
      - name                   = "test_vrf1" -> null
      - pc_enf_dir             = "ingress" -> null
      - pc_enf_pref            = "enforced" -> null
      - tenant_dn              = "uni/tn-test_tenant1" -> null
    }

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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

terraform apply 実行ログ(クリックして広げる)

$ terraform apply
aci_tenant.test_tenant1: Refreshing state... [id=uni/tn-test_tenant1]
aci_vrf.test_vrf1: Refreshing state... [id=uni/tn-test_tenant1/ctx-test_vrf1]
aci_bridge_domain.test_bd1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aci_bridge_domain.test_bd1 will be destroyed
  - resource "aci_bridge_domain" "test_bd1" {
      - arp_flood                   = "no" -> null
      - bridge_domain_type          = "regular" -> null
      - description                 = "test bridge domain" -> null
      - ep_clear                    = "no" -> null
      - host_based_routing          = "no" -> null
      - id                          = "uni/tn-test_tenant1/BD-test_bd1" -> null
      - intersite_bum_traffic_allow = "no" -> null
      - intersite_l2_stretch        = "no" -> null
      - ip_learning                 = "yes" -> null
      - limit_ip_learn_to_subnets   = "yes" -> null
      - ll_addr                     = "::" -> null
      - mac                         = "00:22:BD:F8:19:FF" -> null
      - mcast_allow                 = "no" -> null
      - multi_dst_pkt_act           = "bd-flood" -> null
      - name                        = "test_bd1" -> null
      - optimize_wan_bandwidth      = "no" -> null
      - relation_fv_rs_ctx          = "test_vrf1" -> null
      - tenant_dn                   = "uni/tn-test_tenant1" -> null
      - unicast_route               = "yes" -> null
      - unk_mac_ucast_act           = "proxy" -> null
      - unk_mcast_act               = "flood" -> null
      - v6unk_mcast_act             = "flood" -> null
      - vmac                        = "not-applicable" -> null
    }

  # aci_tenant.test_tenant1 will be destroyed
  - resource "aci_tenant" "test_tenant1" {
      - description = "test tenant" -> null
      - id          = "uni/tn-test_tenant1" -> null
      - name        = "test_tenant1" -> null
    }

  # aci_vrf.test_vrf1 will be destroyed
  - resource "aci_vrf" "test_vrf1" {
      - bd_enforced_enable     = "no" -> null
      - description            = "test vrf" -> null
      - id                     = "uni/tn-test_tenant1/ctx-test_vrf1" -> null
      - ip_data_plane_learning = "enabled" -> null
      - knw_mcast_act          = "permit" -> null
      - name                   = "test_vrf1" -> null
      - pc_enf_dir             = "ingress" -> null
      - pc_enf_pref            = "enforced" -> null
      - tenant_dn              = "uni/tn-test_tenant1" -> null
    }

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

aci_bridge_domain.test_bd1: Destroying... [id=uni/tn-test_tenant1/BD-test_bd1]
aci_bridge_domain.test_bd1: Destruction complete after 2s
aci_vrf.test_vrf1: Destroying... [id=uni/tn-test_tenant1/ctx-test_vrf1]
aci_vrf.test_vrf1: Destruction complete after 0s
aci_tenant.test_tenant1: Destroying... [id=uni/tn-test_tenant1]
aci_tenant.test_tenant1: Destruction complete after 0s

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

確認

APIC の画面で確認します。

f:id:akira6592:20200114151027p:plain
test_tenant1 ごと削除された

test_tenant1 ごと削除され、宣言的な動作を確認できました。 さすがに、他の Tenant は今回の tf ファイルの管理外のため残ったままです。


おわりに

Terraform の Cisco ACI Provider を使って、簡単な設定追加、削除をためしてみました。

Ansible と比較すると、やはり宣言的である点が特徴に感じました。

Terraform は Windows からでも実行できるため、手元の業務 windows 端末から実行できる点も特徴ではないかと思います。

参考

[2021/01/23 追記] - Introduction to Terraform with Cisco ACI, Part 1 - Cisco Blogs

余談

この記事を書くときに、はてなブログMarkdownシンタックスハイライトが tf に対応していることを知りました。

ソースコードを色付けして表示する(シンタックスハイライト) - はてなブログ ヘルプ

testinfra の警告「UserWarning: Unknown ssh-ed25519 host key for xx」について

はじめに

Testinfra は、サーバーの状態をテストできる Python 製のツールです。

実行時に UserWarning: Unknown ssh-ed25519 host key for という警告に出会ったので、原因と対処についてまとめます。

前提

環境や使用したテストコードは以下のとおりです。

環境

f:id:akira6592:20200103210327p:plain
実行サーバーと対象サーバー

testinfra実行サーバーの状態

  • テスト対象サーバーのフィンガープリントは ~/.ssh/known_hosts に登録 あり
    • ssh コマンドでは警告なしでログイン可能
  • ~/.ssh/config なし

テストコード

httpd がインストールされていて、バージョンが 2.4 であることをテストするコードです。

def test_httpd_is_installed(host):
    httpd = host.package("httpd")
    assert httpd.is_installed
    assert httpd.version.startswith("2.4")


テスト実行と警告メッセージコマンド

コマンド py.test --hosts=172.16.0.10 -v test_httpd.py172.16.0.10 を対象にしてテスト実行します。

$ py.test --hosts=172.16.0.10 -v test_httpd.py 
================================================== test session starts ==================================================
platform linux -- Python 3.6.8, pytest-5.3.2, py-1.8.1, pluggy-0.13.1 -- /home/vagrant/a2860/bin/python3.6
cachedir: .pytest_cache
rootdir: /vagrant/testinfra
plugins: testinfra-3.3.0
collected 1 item                                                                                                        

test_httpd.py::test_httpd_is_installed[paramiko://172.16.0.10] PASSED                                           [100%]

=================================================== warnings summary ====================================================
test_httpd.py::test_httpd_is_installed[paramiko://172.16.0.10]
  /home/vagrant/a2860/lib64/python3.6/site-packages/paramiko/client.py:837: UserWarning: Unknown ssh-ed25519 host key for 172.16.0.10: b'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    key.get_name(), hostname, hexlify(key.get_fingerprint())

-- Docs: https://docs.pytest.org/en/latest/warnings.html
============================================= 1 passed, 1 warning in 1.20s ==============================================

タイトルにもあるように以下の警告が表示されました。

UserWarning: Unknown ssh-ed25519 host key for 172.16.0.10: b'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

原因

テスト対象のフィンガープリントが登録されていないことによる警告です。

前提で記載したとおり、テスト対象サーバーのフィンガープリントは ~/.ssh/known_hostsに登録してあるため、ssh コマンドでは警告なしでログイン可能でした。 にもかかわらず、登録されていない扱いなのは、testinfra のデフォルト Connection backendsが、paramikoPythonSSHクライアント)であることに起因しているようです。

対処

対処方法1: Connection backends を ssh に変更する

--hosts オプションの指定の仕方で、Connection backends を ssh に変更できます。ssh に変更することで、~/.ssh/known_hosts を参照するため、警告が出ずに実行できます。

テスト実行

$ py.test --hosts=ssh://172.16.0.10 -v test_httpd.py
================================================== test session starts ==================================================
platform linux -- Python 3.6.8, pytest-5.3.2, py-1.8.1, pluggy-0.13.1 -- /home/vagrant/a2860/bin/python3.6
cachedir: .pytest_cache
rootdir: /vagrant/testinfra
plugins: testinfra-3.3.0
collected 1 item                                                                                                        

test_httpd.py::test_httpd_is_installed[ssh://172.16.0.10] PASSED                                                [100%]

=================================================== 1 passed in 3.04s ===================================================

警告なしで実行できました。

対処方法2: フィンガープリントのチェックをしない(要注意)

ここまでは、SSH クライアントとしての設定ファイル ~/.ssh/config は用意しませんでした。 この設定ファイルに、フィンガープリントのチェックをしないように定義しておくと、paramiko で接続詞ても警告が出ずに実行できます。

ただし、セキュリティ上のリスクを増やす設定になるので、限定的な使用にとどめておくことをおすすめします。

  • ~/.ssh/config
host 172.16.0.10
  StrictHostKeyChecking no

テスト実行

$ py.test --hosts=172.16.0.10 -v test_httpd.py 
================================================== test session starts ==================================================
platform linux -- Python 3.6.8, pytest-5.3.2, py-1.8.1, pluggy-0.13.1 -- /home/vagrant/a2860/bin/python3.6
cachedir: .pytest_cache
rootdir: /vagrant/testinfra
plugins: testinfra-3.3.0
collected 1 item                                                                                                        

test_httpd.py::test_httpd_is_installed[paramiko://172.16.0.10] PASSED                                           [100%]

=================================================== 1 passed in 1.08s ===================================================

警告なしで実行できました。

もし、SSH クライアント設定ファイルを ~/.ssh/config 以外のファイル名にしたば場合は、py.test コマンドの --ssh-config オプションで指定します。


おわりに

正直、paramiko~/.ssh/known_hosts を参照する挙動だと思っていたので、少しはまりました。 もしかしたら、testinfra の設定やテストコードの工夫次第で参照できるようになるかもしれません。

参考

[Ansible] "[WARNING]: Ignoring timeout(10) for ios_facts" の正体とタイムアウトの設定方法

はじめに

Ansible 2.9 から、ネットワークモジュールの fact 収集は、gather_fact の指定(デフォルト yes)に基づくように仕様変更されました。 有効の場合は、内部で ios_factseos_facts などの、ベンダー別の *_facts モジュールが呼ばれます。

tekunabe.hatenablog.jp

gather_fact によってネットワーク機器の fact 収集する場合、Playbook 実行ログに以下の警告が表示されます。(ここでは、Cisco IOS を対象にした場合を想定)

TASK [Gathering Facts] ******************************************************
[WARNING]: Ignoring timeout(10) for ios_facts

これは、私なりに解釈、整理すると以下のとおりです。

当初私は意味を気にしていなかったのですが、同僚に聞かれたので調べることにしました。

この記事では、この意味が分かるまでに検証したことをまとめます。

検証環境

Ansible 2.9.2


■ 検証1: 無視されるタイムアウトの設定はどこか

警告メッセージ

[WARNING]: Ignoring timeout(10) for ios_facts

(10)10 という設定値はどこからきているのでしょうか。

ためしに、ansible.cfgdefaults セクションの gather_timeout の値をデフォルトの 10 から 20 に変更します。

[defaults]
gather_timeout = 20

設定変更が反映されているか確認します。

$ ansible-config dump --only-changed
DEFAULT_GATHER_TIMEOUT(/home/ansible/ansible.cfg) = 20

実行する Playbook を準備します。

- hosts: ios
  gather_facts: yes   # デフオルト

  vars:
    ansible_network_os: ios
    ansible_network_connection: network_cli

  tasks:
    - name: show command test
      ios_command:
        commands:
          - show version

Playbook を実行します。

$ ansible-playbook -i ../inventory.ini ios_show_test.yml 

PLAY [ios] ***********************************************************************************************

TASK [Gathering Facts] *******************************************************************************************
[WARNING]: Ignoring timeout(20) for ios_facts

ok: [ios1]
...(略)...

狙い通り、timeout(20) というメッセージに変わりました。これにより、無視されるタイムアウト値は、DEFAULT_GATHER_TIMEOUTansible.cfggather_timeout などで設定可)であることが分かりました。

ansible.cfg の設定は元に戻して、次の検証にいきます。


■ 検証2: 適用されるタイムアウトの設定はどこか

それでは、どのタイムアウト値が適用されるのでしょうか。

前述の Playbook では、コネクションプラグインとして、network_cli を利用しています。そのため、公式ドキュメントの network_cli のページを確認してみます。

いくつかタイムアウト関連の設定があります。ここでは 2つの設定を検証します。

設定名 変数名 概要
persistent_connect_timeout ansible_connect_timeout 接続タイムアウト
persistent_command_timeout ansible_command_timeout コマンド実行タイムアウト

接続 timeout

先ほどの Playbook に、接続タイムアウトの変数である ansible_connect_timeout の指定を追加します。検証のため、極端に 1秒にします。

- hosts: ios
  gather_facts: yes   # デフオルト

  vars:
    ansible_network_os: ios
    ansible_network_connection: network_cli
    ansible_connect_timeout: 1   # point

  tasks:
    - name: show command test
      ios_command:
        commands:
          - show version

Playbook を実行します。

$ ansible-playbook -i ../inventory.ini ios_show_test.yml 

PLAY [ios] ***********************************************************************************************

TASK [Gathering Facts] *******************************************************************************************
[WARNING]: Ignoring timeout(10) for ios_facts

fatal: [ios1]: FAILED! => {"ansible_facts": {}, "changed": false, "failed_modules": {"ios_facts": {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "failed": true, "invocation": {"module_args": {"auth_pass": null, "authorize": null, "gather_network_resources": null, "gather_subset": ["all"], "host": null, "password": null, "port": null, "provider": null, "ssh_keyfile": null, "timeout": null, "username": null}}, "msg": "socket_path does not exist or cannot be found.\nSee the socket_path issue category in Network Debug and Troubleshooting Guide", "warnings": ["Platform darwin on host ios1 is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information."]}}, "msg": "The following modules failed to execute: ios_facts\n"}

エラーになりました。変数 ansible_connect_timeout による 接続タイムアウトの設定が適用されたようです。

コマンド実行 timeout

今度は、コマンド実行タイムアウトの変数である ansible_command_timeout の指定を追加します。検証のため、極端に 1秒にします。

- hosts: ios
  gather_facts: yes   # デフオルト

  vars:
    ansible_network_os: ios
    ansible_network_connection: network_cli
    ansible_command_timeout: 1   # point

  tasks:
    - name: show command test
      ios_command:
        commands:
          - show version

Playbook を実行します。

$ ansible-playbook -i ../inventory.ini ios_show_test.yml 

PLAY [ios] ***********************************************************************************************

TASK [Gathering Facts] *******************************************************************************************
[WARNING]: Ignoring timeout(10) for ios_facts

fatal: [ios1]: FAILED! => {"ansible_facts": {}, "changed": false, "failed_modules": {"ios_facts": {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "failed": true, "invocation": {"module_args": {"auth_pass": null, "authorize": null, "gather_network_resources": null, "gather_subset": ["all"], "host": null, "password": null, "port": null, "provider": null, "ssh_keyfile": null, "timeout": null, "username": null}}, "msg": "command timeout triggered, timeout value is 1 secs.\nSee the timeout setting options in the Network Debug and Troubleshooting Guide.", "warnings": ["Platform darwin on host ios1 is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information."]}}, "msg": "The following modules failed to execute: ios_facts\n"}

エラーになりました。エラーメッセージを見ると

command timeout triggered, timeout value is 1 secs

とあります。変数 ansible_command_timeout による 接続タイムアウトの設定が適用されたようです。


まとめ

自動化よりも設計・管理対象を減らすということ

はじめに

「業務の整理、手順書整備、体制作り・・」など、自動化の以前にすることとしてよく聞きます。

今年は「技術面で自動化以前にできること」に気付かされた年でした。それは、自動化よりも設計・管理対象を減らすということです。

ピンときた2つの資料をご紹介します。

いずれもネットワークでの事例で、設定や管理を自動化するのではなく、そもそも減らす、なくすという観点です。

⽬指せ︕Goodbye IPv4on L3 ToR

JANOG 44 Meeting in Kobe での「ぺちゃくちゃ」内での発表です。

https://www.janog.gr.jp/meeting/janog44/application/files/1015/6429/4082/janog44-pecha-mixi.pdf

P2 で思いとして「自動化よりも管理対象削減」と記載されています。

WhiteBoxSwitch NOSの変遷

第2回Cumulusユーザー会での発表です。

P11 で BGP unnumbered について触れ、「プロトコル自体で運用の負荷軽減を図ることが可能」「Ansibleで設定変更!すらも無くせるメリット」と記載されています。

おわりに

設計や管理対象を減らすと、自動化も楽になりますし、そもそも自動化するまでもなくなるかもしれませんね。

参考資料

BGP unnumbered について

foobaron.hatenablog.com

[Ansible] Pull Request のコードを試す方法

はじめに

Ansible は GitHub 上で日々開発が進められています。 バグや要望などは Issue にあげられ、実装は Pull Request にあげられます。

Pull Request にあげられた追加、修正コードのうち、ansible/ansible リポジトリ にマージされていないものは、そのままで試すことはできません。ちょっとした手順が必要になります。

手順は、以下の公式ドキュメントに記載されています。

docs.ansible.com

先日、ある Issue にコメントしたところ「直した Pull Request を出したから、試してみて(意訳)」と依頼ががあって、実際試しました。 手順例として具体的な値を利用して手順をまとめておきます。


試したい対象の Pull Request

対象は以下の Pull Request です。

github.com

きっかけの issue はこちら


手順

リポジトリのクローン

まず、普段実行している Ansible とは別の県境環境を用意するためにクローンします。

git clone https://github.com/ansible/ansible.git ansible-pr-testing
cd ansible-pr-testing

ブランチの作成と修正コードのマージ

作業用のブランチを作成して、修正コードをマージします。

対象 Pull Request #66119

Akasurde wants to merge 1 commit into ansible:devel from Akasurde:fortios_fix

と記載があるように、Akasurde さんのリポジトリの fortios_fix ブランチで修正されたものを、ansible/ansible の devel ブランチにマージしてほしいという、Pull Request です。

これらの情報から、以下の指定をします。

  • 作業用のブランチ名は testing_PR66119
    • 本当はなんでもいいのですが、公式ドキュメントの testing_PRXXXX という名前の付け方にあわせています
  • pull 元は https://github.com/Akasurde/ansible.gitfortios_fix ブランチ
git checkout -b testing_PR66119 devel
git pull https://github.com/Akasurde/ansible.git fortios_fix

環境のセットアップ

以下のコマンドで開発環境のセットアップを行います。各種パスの設定などが行われます。

source ./hacking/env-setup

テスト と Pull Request へのコメント

ここまでで準備ができたので、実際に試したい Playbook を実行。 うまくいったので、その旨を Pull Request へコメントしました。

Fortios: Correct underscore_to_hyphen API by Akasurde · Pull Request #66119 · ansible/ansible · GitHub


おわりに

まだマージされていない Pull Request のコードを試す方法をまとめました。 Git / GitHub の使い方を知っていれば、そんなに特別なことはないかも知れませんが、公式ドキュメントとして手順がまとめられいるのはとても親切だと思いました。

Ansible は、自動化対象のソフトウェア、機器、プラットフォームが多岐に渡ります。そのため、コード修正者自身では検証しきれないケースもあります。 今回のように、修正自体はしていないものの、自分の環境で試すことも貢献のひとつと考えて今後も OSS と付き合っていきたいと思います。