うさラボ

お勉強と備忘録

ネットワークリソースモジュールで遊んでみる

ネットワークリソースモジュールを有効活用したい

ansible v2.9で追加されたネットワークリソースモジュールですが、いままでろくに使ったことがありませんでした。 少し時間ができたので、いろいろと試してみました。

ネットワークリソースモジュールとは

さまざまなネットワークデバイスの管理方法を簡素化および標準化

しているらしい
特徴としては下記のstateがあります、それぞれの説明は割愛させていただきます。

  1. merged
  2. replaced
  3. overridden
  4. deleted
  5. gathered
  6. rendered
  7. parsed

詳しいことは公式ドキュメントにお任せ docs.ansible.com

個人的にパッと見た時に特徴的だったのはreplacedでした。 今までのios_configでは設定の置き換えができなかった思いますので「これは、始まったか」と心の中でつぶやきました(嘘)

どう使うか

個人的にNW機器の構成管理をする際にPlaybook(もしくは変数ファイル)をあるべき状態に定義すればよい状態を目指しています ネットワークリソースモジュールを使って状態を目指します。

そもそもあるべき状態を定義すればよいってどんな状態なの?

今回はIOSのStaticRouteのAnsibleで設定していきます。

あるべき状態、つまり設定値は変数ファイル(static_route.yml)として host_vars配下に配置してあります

 -- inventory
    |-- group_vars
    |   `-- ios.yml
    |-- host_vars
    |   `-- ios01
    |       `-- static_route.yml
    `-- ios1.ini

中身は以下にようになっています(インデントが若干おかしいのはいったん見逃してください)。

static_route:
-   address_families:
    -   afi: ipv4
        routes:
        -   dest: 0.0.0.0/0
            next_hops:
            -   forward_router_address: 10.10.20.254
                interface: GigabitEthernet1
    -   afi: ipv4
        routes:
        -   dest: 10.17.253.101/32
            next_hops:
            -   forward_router_address: 192.168.253.45
    -   afi: ipv4
        routes:
        -   dest: 8.8.8.8/32
            next_hops:
            -   forward_router_address: 10.10.20.254

この変数ファイルに定義してあるroute=機器に設定されているrouteとしたいわけです。 追加削除もこの変数ファイルをいじるだけでOKにし、Config投入時は差分の箇所のみ変更をかけてもらいたい。 冪等性も担保したい、もし手動で設定してしまってもなんとかしたい。

さっそく考えてみる

今回はCiscoDevNetで常時開放しているIOSXEを利用します。

すでにStaticRouteが何本も入っている状態です。

まず変数ファイルを用意する必要があるんですが、手で書き起こすのはさすがにやりたくない。。

そこでネットワークリソースモジュールの出番です。

gatheredを使う

gatheredはfactsに似ています、機器の設定を取得し決まった形に変換してくれます。

こんなPlaybookを作成し実行してみました。

---
- hosts: ios01
  gather_facts: False

  tasks:
    - name: gathered
      cisco.ios.ios_static_routes:
        state: gathered
      register: gather_result

    - name: Write the Static Route configuration to a file
      copy:
        content: "{{ {'static_route': gather_result['gathered'] } | to_nice_yaml }}"
        dest: "{{ inventory_dir }}/host_vars/{{ inventory_hostname }}/static_route.yml"
      delegate_to: localhost

staticrouteの設定を取得し、parseし格納されます。 f:id:usage_automate:20210331194030p:plain

これで、実機から設定を変数ファイルに書き起こすことができました。(上に張っていたstatic_route.ymlです) (nice_to_yamlだと-のあとに余計にスペースはいったりちょっと変なんですよねぇ)

変数ファイルに追加や削除してみる

さて、先ほど出力したファイルをいじってみましょう。

設定追加

static_route.ymlに8.8.8.9/32のルートを追加してみます。 8.8.8.8/8を参考にコピペで作ります。

static_route:
-   address_families:
    -   afi: ipv4
        routes:
        -   dest: 0.0.0.0/0
            next_hops:
            -   forward_router_address: 10.10.20.254
                interface: GigabitEthernet1
    -   afi: ipv4
        routes:
        -   dest: 10.17.253.101/32
            next_hops:
            -   forward_router_address: 192.168.253.45
    -   afi: ipv4
        routes:
        -   dest: 8.8.8.8/32
            next_hops:
            -   forward_router_address: 10.10.20.254
    -   afi: ipv4
        routes:
        -   dest: 8.8.8.9/32
            next_hops:
            -   forward_router_address: 10.10.20.254

完成したので、設定だ!!

と、そのまえに

想定通りか確認してみる

投入するルートが想定通りか、どんなconfigを投入するのか?どんな結果になるのか?を確認したくなったので先に確認しましょう。

確認用のPlaybookを作成しました。 こちらはgatheredとrenderedの合わせ技になります。

renderedは設定時のconfigを出力してくれるstateになり、実機にログインしないでも利用可能です(Localで使える)

gatheredで取得した設定からrenderedした結果とstatic_route.ymlからrenderedした結果をdiffしてみます

diffにはfact_diffを利用しました。

---
- hosts: ios01
  gather_facts: False

  tasks:
    - name: gathered
      cisco.ios.ios_static_routes:
        state: gathered
      register: gather_result

    - name: current config rendered
      cisco.ios.ios_static_routes:
        config: "{{ gather_result['gathered']  }}"
        state: rendered
      register: current_config

    - name: asumed config rendered 
      cisco.ios.ios_static_routes:
        config: "{{ static_route }}"
        state: rendered
      register: assumed_config

    - name: fact_diff
      ansible.utils.fact_diff:
        before: "{{ current_config.rendered }}"
        after: "{{ assumed_config.rendered }}"

実行してみましょう

f:id:usage_automate:20210331195037p:plain

追加予定routeのip route 8.8.8.9 255.255.255.255 10.10.20.254が+で見えました。 これは見やすい(よね?)

今度こそ実行

それでは設定変更を実施しましょう

用意したPlaybookがこちら

---
- hosts: ios01
  gather_facts: False

  tasks:
    - name: replaced
      cisco.ios.ios_static_routes:
        config: "{{ static_route }}"
        state: replaced

ポイントはstate: replacedですね

さて実行、どーーーーん! f:id:usage_automate:20210331195918p:plain

設定後の確認をしてみる

設定後の確認をしましょう、今回はconfigが想定通り設定されたか?の観点に絞って確認をします。

こちらは先ほど作成したPlaybookを再度実施することで簡単にできます。 f:id:usage_automate:20210331200049p:plain

変更なし=gatheredで取得した設定からrenderedした結果とstatic_route.ymlからrenderedした結果に差分なし

ということで想定通りに設定ができました。Ansible最高

おまけで削除

さて、DevnetのIOSXEにゴミをいれてしまったので最後は削除といきましょう。

まずは変数ファイルからいらないrouteを削除します

static_route:
-   address_families:
    -   afi: ipv4
        routes:
        -   dest: 0.0.0.0/0
            next_hops:
            -   forward_router_address: 10.10.20.254
                interface: GigabitEthernet1
    -   afi: ipv4
        routes:
        -   dest: 10.17.253.101/32
            next_hops:
            -   forward_router_address: 192.168.253.45

8.8.8.8/32と8.8.8.9/32のルートですね、こいつは私が足したので削除します。

追加時と同じように設定前の確認をします。

f:id:usage_automate:20210331200526p:plain

削除対象は赤色で出てきます(わかりやすい!)

ではreplaceを実行 f:id:usage_automate:20210331200802p:plain

え?なんでOK?

replacedの動きがよくわからない。。

overriddenが適切なのかも、、(自分の検証機じゃないのでoverriddenはスキップさせてください(´;ω;`))

ソースコードを読む気になれずでここでタイムアップ

まとめ

実際に動かすことが重要だと再認識しました。。。。

最後の削除で想定通り動かず不完全燃焼ですが、ネットワークリソースモジュールについて少し理解が深まってよかったです。

gatheredやrenderedなどの機能も便利に使えそうで妄想が膨らみますね。 今回想定と違ったoverridden/replaceの動きについてはリトライして勉強しようと思います。 (そもそもreplaceって文字だけでこんな挙動かなって勝手に想定していました、よくないですね)

変数ファイルをいじくるだけで設定を変更できるならGitとの相性もよりよくなるかも?とか、CIで投入予定のconfigまで出しておいて承認待ちで止めとくとかすればみんなの恐怖減るかな?とかいろいろと考えたら楽しくなりました。

CiscoACIのvzAny設定をAnsibleで一気に作る

vzAnyの設定がしたい、したことないからしたい

そんなことを考えながら日々生き抜いていました。

なのでやりました、ホントはrole化してもっと自由に組み合わせられるように作るべきなんですが、めんどくさかったです。(意志弱め)

Playbook作成前に、手動で一通りの作業を実施し、順序や対象設定の確認をしています、いきなりPlaybookをしないで作業のイメージを掴むことは非常に重要だと思っています

AccessPolicyの設定は無視で、Tenantの設定のみ対象にしています。

どんな場面で使うか?それはSandBox環境で俺俺勉強環境を作るくらいでしか思い浮かびません(正直者)

具体的な設定手順は以下の通りです

  1. Tenant作成
  2. VRF作成
  3. Contract作成
  4. Filter作成
  5. Filete Entry作成(permit anyで作成)
  6. ContractとFilterを紐付け
  7. vzAnyのProviderContract設定(aci_rest)
  8. vzAnyのConsumedContract設定(aci_rest)

7,8はモジュールがないため、aci_restで実装しました。

aci_restで利用するパスやコンテンツはShow ACI Inspectorの機能を利用しています f:id:usage_automate:20210121225829p:plain

ACI Inspectorを起動した状態でGUI作業を実施することで対応するURLやCURLDやコンテンツなどの情報が確認できます f:id:usage_automate:20210121230149p:plain

どうやら/api/node/mo/uni/tn-テナント名/ctx-VRF名/any.jsonのURLに対して、コンテンツvzRsAnyToCons/vzRsAnyToProvでContractを指定して送ることで設定ができることが確認できました。

出来上がったPlaybookがこちらです。

---
- hosts: APIC
  gather_facts: false
  connection: local
  vars_files: vars/sample.yml
  vars:
    aci_auth:
      - &aci_auth
        host: "{{ inventory_hostname }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: false

  tasks:
    - name: add a new Tenant
      aci_tenant:
        <<: *aci_auth
        tenant: "{{ tenant.name }}"
        state: present

    - name: add a new VRF
      aci_vrf:
        <<: *aci_auth
        tenant: "{{ tenant.name }}"
        vrf: "{{ vrf.name }}"
        policy_control_preference: enforced
        policy_control_direction: ingress
        state: present

    - name: add vzAny Contract
      aci_contract:
        <<: *aci_auth
        tenant: "{{ tenant.name }}"
        scope: context
        dscp: unspecified
        priority: unspecified
        contract: "{{ vzAny_contract }}"
        state: present

    - name: add all permit filter 
      aci_filter:
        <<: *aci_auth
        tenant: "{{ tenant.name }}"
        filter: all_permit
        state: present

    - name: add all permit filter entry
      aci_filter_entry:
        <<: *aci_auth
        tenant: "{{ tenant.name }}"
        filter: all_permit
        entry: all_permit
        ether_type: unspecified
        ip_protocol: unspecified
        dst_port: unspecified
        stateful: no
        state: present

    - name: add subject to vzAny Contract 
      aci_contract_subject:
        <<: *aci_auth
        tenant: "{{ tenant.name }}"
        subject: all_permit
        contract: "{{ vzAny_contract }}"
        state: present

    - name: add vzAny Contracts(Provided)
      aci_rest:
        <<: *aci_auth
        path: /api/node/mo/uni/tn-{{ tenant.name }}/ctx-{{ vrf.name }}/any.json
        method: post
        content:
          vzRsAnyToProv:
            attributes:
              tnVzBrCPName: "{{ vzAny_contract }}"

    - name: add vzAny Contracts(Consumed)
      aci_rest:
        <<: *aci_auth
        path: /api/node/mo/uni/tn-{{ tenant.name }}/ctx-{{ vrf.name }}/any.json
        method: post
        content:
          vzRsAnyToCons:
            attributes:
              tnVzBrCPName: "{{ vzAny_contract }}"

テナント単位で設定ファイルを用意し、このファイルを見れば大体構成わかるよね?という状態を目指しています。なのでこのような構成でAnsibleを作ってブログに載せることも多いかと思います。 sample.yml(今回はAP/BD/EPGの設定部分は利用していません)

tenant:
  name: sample_tenant
ap:
  name: sample_ap
epgs:
  - { name: sample_epg, bd: sample_bd, vlan: 100 }
vrf: 
  name: sample_vrf
bds: 
  - { name: sample_bd, ip: 10.0.0.1, mask: 28 }

vzAny_contract: vzAny_contract

一応実行ログ

PLAY [APIC] ************************************************************************************************************************************************************

TASK [add a new Tenant] ************************************************************************************************************************************************
[WARNING]: Platform darwin on host sandboxapicdc.cisco.com 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.

changed: [sandboxapicdc.cisco.com]

TASK [add a new VRF] ***************************************************************************************************************************************************
changed: [sandboxapicdc.cisco.com]

TASK [add vzAny Contract] **********************************************************************************************************************************************
changed: [sandboxapicdc.cisco.com]

TASK [add all permit filter] *******************************************************************************************************************************************
changed: [sandboxapicdc.cisco.com]

TASK [add all permit filter entry] *************************************************************************************************************************************
changed: [sandboxapicdc.cisco.com]

TASK [add subject to vzAny Contract] ***********************************************************************************************************************************
changed: [sandboxapicdc.cisco.com]

TASK [add vzAny Contracts(Provided)] ***********************************************************************************************************************************
changed: [sandboxapicdc.cisco.com]

TASK [add vzAny Contracts(Consumed)] ***********************************************************************************************************************************
changed: [sandboxapicdc.cisco.com]

PLAY RECAP *************************************************************************************************************************************************************
sandboxapicdc.cisco.com    : ok=8    changed=8    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

実行も正常に完了し、2回流すと全てOKのステータスで冪等性も担保されています。

まぁ、便利!作ってよかったなぁ!(大声)

CMLでUbuntu触る時初期セットアップはEDIT CONFIGでcloud-initを使うと非常に楽

CMLで構築してるいるときにあった小ネタvol.3

cloud-initは画像赤枠のEDIT CONFIGから設定が可能です

EDIT CONFIGの変更には条件があるようです

  1. サーバが停止している
  2. サーバーが初期化されている

直接書き込むことで変更が可能です

変更後は忘れずに [ save ] を実施しましょう f:id:usage_automate:20210121215740p:plain

初期化はWIPE NODEをクリックすることで可能です。 f:id:usage_automate:20210121215757p:plain

cloud-initの記載方法は下記Qiitaを参考にさせていただきました! qiita.com

実際に作成したconfigは下記です。

#cloud-config
password: PASSWORD
chpasswd: { expire: False }
hostname: intenal-sv4
ssh_pwauth: True
ssh_authorized_keys:
   - your-ssh-pubkey-line-goes-here
timezone: Asia/Tokyo
locale: ja_JP.utf8
write_files:
 - path: /etc/netplan/50-cloud-init.yaml
   content: |
    network:
      ethernets:
        ens2:
          addresses:
            - 10.0.4.2/24
          gateway4: 10.0.4.1
          dhcp4: false
          nameservers:
            addresses:
              - 192.168.100.1
      version: 2
runcmd:
  - sudo netplan apply

UbuntuのIPaddressは/etc/netplan/50-cloud-init.yamlを変更することで設定が可能です。

cloud-initは起動の度に走るわけではなく、初回起動時にのみ動作するようで、

runcmdでip add add 10.0.4.2/24 dev ens2のように設定をしてしまうと、サーバを停止→起動すると設定が飛んでしまいます。

なので、/etc/netplan/50-cloud-init.yamlを変更するようにしました。

CML-PのUbuntuでSSHにつまづいた

CMLで構築してるいるときにあった小ネタvol.2

CMLで立てたUbuntuiosSSHできなかったときにごちゃごちゃやっていたことの備忘録

検証環境を作成時に、マネジメントアクセス用でUbuntuを作成しました。

SSHの設定を終えて、IOSvにSSHしようとしたとき下記のログが出てきてアクセスできませんでした

f:id:usage_automate:20210117202320p:plain

ログを見るにかきkeyがなかったことが原因だったようです。 diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1

/etc/ssh/ssh_configにKexAlgorithms diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1を追記したところSSHが可能になりました。

iosvの設定方法がよくなかったのかもしれませんが、解決できたので書き残しておきます。

/etc/ssh/ssh_config

# This is the ssh client system-wide configuration file.  See
# ssh_config(5) for more information.  This file provides defaults for
# users, and the values can be changed in per-user configuration files
# or on the command line.

# Configuration data is parsed as follows:
#  1. command line options
#  2. user-specific file
#  3. system-wide file
(~snip~)
    KexAlgorithms diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1

CML-P Breakout-toolの使い方

CMLで構築してるいるときにあった小ネタvol.1

Breakout-toolは、ラボ環境で動作しているデバイスのコンソールポートへの接続性を提供する機能です。

イメージ図 f:id:usage_automate:20210117201118p:plain

Breakout-toolの使い方の話

インストールはCMLGUI画面から移動するサイトで可能です。

Windows/Mac/Linuxとそれぞれで実行ファイルがダウンロード可能です

f:id:usage_automate:20210117195241p:plain

Macで利用する場合は信頼されていないアプリケーションの実行がデフォルトで許可されていないので、自分で信頼ずみにする必要があります。

ダウンロードページに移動するとドキュメントがあるので基本はそちらを読んでいけば大丈夫です。

f:id:usage_automate:20210117195451p:plain

自分でもtweetしてますが、実行権限がたりたくて実行できずに結構ハマりました。

実行時にオプションでuiをつけるとGUIで起動可能です

breakout-macos-x86_amd64 ui

起動後、ページアクセスし、Configの設定をします。

f:id:usage_automate:20210117200247p:plain

最後に、対象LabのStatusをOff->Onに変更します。

f:id:usage_automate:20210117200332p:plain

対象Labをクリックし、アクセスしたいデバイスLinkをクリックするとアクセス可能です。

f:id:usage_automate:20210117200557p:plain

Cisco Certified DevNet Associate 合格体験記

Cisco Certified DevNet Associate 取得しました

2020年中になんとかDevNetの資格が欲しい!と思っていたので年内ギリギリで取得して来ました!

参考になるかわかりませんが自分がやった準備を書き残したいと思います

ちなみに、825点合格で825点でした(ギリギリすぎ)

なんとか合格できたもののもうちょっと勉強しておけばよかったなぁって気持ちもあります。

出題範囲

まず、最初に出題範囲の確認をしました。

公式サイトからざっくりと出題範囲をまとめてみました

15% ソフトウェア開発と設計
20% API の理解と使用
15% シスコ プラットフォームと開発
15% アプリケーションの展開とセキュリティ
20% インフラストラクチャと自動化
15% ネットワークの基礎

www.cisco.com

自分の知識レベル

  • Pythonは少し知ってる(Python 3 エンジニア認定基礎試験は合格してます)
  • 業務で簡単なスクリプトを書いたりしている
  • NWエンジニア歴5年目

とまぁ、目立ったスキルはありませんが数年前に勉強したPythonで空きあらば自作スクリプトを作成し、ちょっとしたことを効率化したりしてます。

勉強方法

試験の予約をしてから、「さて、どうやって勉強しようか」と迷いまくってました。

有料のコンテンツは使わないでなんとかしたいなぁと考えていたのでDevNet Associate Fundamentals Course という公式の教材がありましたが利用しませんでした(おそらくこれを利用するのが一番良いっぽい、しかし英語)。

とりあえず、APIの理解とソフトウェア開発についてを重点的に勉強をしました。

次にシスコ プラットフォームと開発とインフラストラクチャと自動化を手を動かしながら学習、Devnetのサンドボックス最高

  • Meraki,DNA,USC ManagerのAPIの叩き方も勉強(認証方式などなど)
  • Ansibleの出ると買いてあったのでIOS,ASAの設定変更系のモジュール確認
  • NETCONF,RESTCONF,gNMI,YANGのキーワードは勉強(私はちょっと勉強が足りてませんでした...)

他にもDevnetのLearning?のの日本語化されているトラックも参考にしました。

Cisco DevNet: APIs, SDKs, Sandbox, and Community for Cisco Developers

あまり参考にならないと思いますが読んだ本(さらっと読みしかしてないっす)

AmazonKidleunlimitede最高

アジャイル/ウォーターフールの説明を読んだ

APIのところだけ読みました

体系的に学ぶには何がいいのだろうか。。といまだに思う

試験中

試験が始まって、見直しができないことに気づきました。

Microsoftの試験を受けたときは見直しができたので、軽い気持ちで問題を解いてから「後で見直そ〜」と思ってませんでしたがそんなこともできずに ドキドキしながら問題を解いていきました。

日本語翻訳は若干苦しかったです。(私の読解力が足りない可能性は大)

感想

感想としては「なんとか取れた。」でした、正直試験中は「あー、これは来年再挑戦かもなぁ」と思うくらいの自信でした。

結果的に取れたから良いものの勉強不足を痛感したので、継続して勉強を続けようと思える試験になりました。

考えるのが嫌なのでIPアドレス計算ツール作ってみました

192.168.99.43/28のネットワークアドレスってなんだっけ

IPアドレスをパッとみたときにネットワークアドレスとか利用IPなどを頭の中で計算することってありませんか?

私はよくやっていて、その度に念のためググって確認しようってなっていました。

そこでPythonのipaddressモジュールを使いIPアドレスの計算をしてくれるCLIツールを作ってみました。

f:id:usage_automate:20201225183812p:plain

f:id:usage_automate:20201225183821p:plain

簡単なものですが紹介します。

構成

(ipcal) usalab!:ipcal-1.0 $ tree
.
├── ipcal
│   ├── __init__.py
│   ├── main.py
│   └── util.py
└── setup.py

main.pyはutil.pyを呼び出しのとclickで--ipのオプションで実行時にもIPセグメントの入力ができます。

from . import util
import click

@click.command()
@click.option('--ip',default='', type=str, help='ipaddress')
def main(ip):
  ipnetwork = util.input_value_cast_to_ipv4network(ip)
  util.ipnetwork_information(ipnetwork)

if __name__ == '__main__':
  main()

util.py(名前つけるセンスがない)ではipaddressモジュールを利用して、入力値をIPv4Networkオブジェクトにして返すinput_valueと計算結果を表示するipnetwork_informationの2つのメソッドを作成してます。

import ipaddress
import sys

def input_value_cast_to_ipv4network(input_ip):
  '''
  入力値をipaddressオブジェクトにキャストして戻す
  '''
  if input_ip:
    try:
      ipnetwork = ipaddress.ip_network(input_ip,strict=False)
      return ipnetwork
    except ValueError:
      print(f"Error: Value Error")
      sys.exit(1)
    except Exception as e:
      print(f"Error: {e}")
      sys.exit(1)
  else:
    value = input('IPアドレス入力[X.X.X.X/X]>> ')
    try:
      ipnetwork = ipaddress.ip_network(value,strict=False)
      return ipnetwork
    except ValueError:
      print(f"Error: Value Error")
      sys.exit(1)
    except Exception as e:
      print(f"Error: {e}")
      sys.exit(1)

def ipnetwork_information(ipnetwork):
  print(f'Prefix          : {ipnetwork.with_prefixlen}')
  print(f'SubnetMask      : {ipnetwork.netmask}')
  print(f'WildCardMask    : {ipnetwork.with_hostmask}')
  print(f'NetworkAddress  : {ipnetwork.network_address}')
  print(f'BroadCastAddress: {ipnetwork.broadcast_address}')

ipaddressモジュールのメソッドを呼び出すだけで簡単にネットワークアドレスやブロードキャストアドレスを表示することができます。

どうやって公開するのがいいのかよくわからない。。

python setup sdistを実施してpip install dist/ipcal-1.0.tar.gzすることでipcalコマンドで実行することができます。

ググる時間が減ったのでヨシ!!

参考にさせていただいたサイト

qiita.com

blog.amedama.jp

buildersbox.corp-sansan.com

docs.python.org