Help us understand the problem. What is going on with this article?

IOS XEのModel Driven Telemetryを試してみた

More than 3 years have passed since last update.

はじめに

この記事はシスコの有志によるCisco Systems Japan Advent Calendar 2017の21日目です。

IOS XE 16.6.1からModel Driven Telemetryをサポートしましたので、今回はこれを試してみたいと思います。

ちょうど昨日のCisco Advent Calender 20日目で@muramurakamiさんがIOS XRのTelemetryに関連した投稿をしてくれているので、そちらもぜひ参考にしてみてください。

Model Driven Telemetry

Model Driven Telemetryは、(IETF NETCONF YANG Push1やOpenConfig Streaming Telemetry2のように)Push型かつストリーミング方式でリアルタイムにデバイスからコンフィグおよびステートの情報を取得するためのテクノロジー一般を指すCisco用語です。モデル駆動(Model Driven)と冠するだけあって、ネットワークデバイスから提供される情報がYANGでモデル化されています。IOS XEとIOS XRでは、(コーデックやトランスポート等の)実装方式が異なるのですが、いずれはOpenConfig Streaming Telemetryなど、現在サポートされていない仕組みも取り込んでいくことも見据えて、このような包括的なネーミングになってます。

IOS XRにおけるModel Driven Telemetryの実装は、OpenConfig Streaming Telemetryの影響を反映しているのに対して、IOS XEにおけるModel Driven Telemetryのそれは、標準化作業中のIETF NETCONF YANG Pushドラフトを基調としてます。

前提

この記事では、IOS XEプラットフォームとして、すでにModel Driven Telemetryを正式にサポートしているCatalyst系のプラットフォームではなく、CSR1000Vのベータイメージで試しています :bow:
近いうちに、ルータ系のプラットフォームでも正式にサポートされるはずです :yum:

デバイスの設定

YANGに対応したNETCONFを有効にします。

Router(config)#netconf-yang

さらに、NETCONFでアクセスするためのアカウントを作成します。privilegeレベルは15にする必要があります。

Router(config)#username admin privilege 15 password admin

今回はCPU使用率しか取得しないので、以下の設定は不要ですが、取得したいデータに応じて、デバイス内部における(IOS上で動作する)NETCONFエージェント(実態はConfD3)とIOS内部のデータストアを同期する方式と間隔を設定します。一見分かりづらいのですが、以下のような関係にあります。NETCONFクライアントはNETCONFエージェントと会話しますが、ここで設定しているのは、IOSデバイス内部におけるNETCONFエージェントとIOSデータストアの関係です。

[NETCONFクライアント]----------[NETCONFエージェント(ConfD)]----------[IOSデータストア]

Router(config)#netconf-yang cisco-odm polling-enable
Router(config)#netconf-yang cisco-odm actions OSPF 
Router(config-odm-action)#mode poll 
Router(config-odm-action)#polling-interval 1000

(具体的にどういった情報を取得できるか等、)詳細は以下のドキュメントをご参照ください。

素のSSHで試す

まずは、仕組みを知る上で、素のSSHで接続して試します(以下、デバイスとクライアントの間でやりとりしているXMLメッセージは、可読性をよくするため整形してます)。

クライアントからデバイスにSSHします。重要な点として、-sオプションでsubsystemとしてnetconfを指定します。

$ ssh admin@192.0.2.1 -p 830 -s netconf
admin@192.0.2.1's password:*****

接続すると、デバイスから<hello>が返ってきます。<capability>の量が膨大なので省略してます。

client <- device

<?xml version="1.0" encoding="UTF-8"?>
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <capabilities>
    <capability>urn:ietf:params:netconf:base:1.0</capability>
    <capability>urn:ietf:params:netconf:base:1.1</capability>
    ...
  </capabilities>
  <session-id>237955</session-id>
</hello>]]>]]>

クライアントからも<hello>を送信します。

client -> device

<?xml version="1.0" encoding="UTF-8"?>
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <capabilities>
    <capability>urn:ietf:params:netconf:base:1.0</capability>
  </capabilities>
</hello>]]>]]>

続けて、クライアントから<establish-subscription>オペレーションを送信します。このときに、xpath-filterで、取得する情報の対象を特定するのと、periodで情報をPushしてもらう間隔をセンチ秒(1/100秒)単位で指定します。以下の例では、5秒間のCPU使用率に関する情報(/ios-emul-oper-db:ios-emul-oper-db/cpu-usage/five-seconds)が10秒間隔でデバイスからPushされることになります。

client -> device

<?xml version="1.0" encoding="utf-8"?>
<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <establish-subscription xmlns="urn:ietf:params:xml:ns:yang:ietf-event-notifications" xmlns:yp="urn:ietf:params:xml:ns:yang:ietf-yang-push">
    <stream>yp:yang-push</stream>
    <yp:xpath-filter>/ios-emul-oper-db:ios-emul-oper-db/cpu-usage/five-seconds</yp:xpath-filter>
    <yp:period>1000</yp:period>
  </establish-subscription>
</rpc>]]>]]>

<establish-subscription>オペレーションに問題がなければ、以下のような<rpc-reply>がデバイスから返ってきます。
このとき通知される<subscription-id>は、のちにSubscriptionを停止する際に必要になります。

client <- device

<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101"><subscription-result xmlns='urn:ietf:params:xml:ns:yang:ietf-event-notifications' xmlns:notif-bis="urn:ietf:params:xml:ns:yang:ietf-event-notifications">notif-bis:ok</subscription-result>
  <subscription-id xmlns='urn:ietf:params:xml:ns:yang:ietf-event-notifications'>2147483648</subscription-id>
</rpc-reply>]]>]]>

以降、periodで指定した間隔で、以下のような情報が継続的にPushされてきます。

client <- device

<?xml version="1.0" encoding="UTF-8"?>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2017-12-15T05:01:44.32Z</eventTime>
  <push-update xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-push">
    <subscription-id>2147483648</subscription-id>
    <datastore-contents-xml>
      <ios-emul-oper-db xmlns="http://cisco.com/ns/iosxe/ios-emul-oper-db">
        <cpu-usage>
          <five-seconds>0</five-seconds>
        </cpu-usage>
      </ios-emul-oper-db>
    </datastore-contents-xml>
  </push-update>
</notification>

一応、デバイス側でSubscriptionの状態を確認しておきましょう。

Router#show telemetry ietf subscription all
  Telemetry subscription brief

  ID               Type        State       Filter type
  -----------------------------------------------------
  2147483648       Dynamic     Valid       xpath

<subscription-id>を指定することでセッション毎の詳細を確認することができます。

Router#show telemetry ietf subscription 2147483714 detail
Telemetry subscription detail:

  Subscription ID: 2147483648
  Type: Dynamic
  State: Valid
  Stream: yang-push
  Filter:
    Filter type: xpath
    XPath: /ios-emul-oper-db:ios-emul-oper-db/cpu-usage/five-seconds
  Update policy:
    Update Trigger: periodic
    Period: 1000
  Encoding: encode-xml
  Source VRF:
  Source Address:
  Notes:

<delete-subscription>オペレーションでSubscriptionを停止します。<subscription-id>には、 <establish-subscription>オペレーションの際に通知されらものを使用します。

client -> device

<?xml version="1.0" encoding="utf-8"?>
<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <delete-subscription xmlns="urn:ietf:params:xml:ns:yang:ietf-event-notifications" xmlns:netconf="urn:ietf:params:xml:ns:netconf:base:1.0">
    <subscription-id>2147483648</subscription-id>
  </delete-subscription>
</rpc>]]>]]>

<delete-subscription>オペレーションに問題がなければ、以下のような<rpc-reply>がデバイスから返ってきます。

client <- device

<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
  <subscription-result xmlns='urn:ietf:params:xml:ns:yang:ietf-event-notifications' xmlns:notif-bis="urn:ietf:params:xml:ns:yang:ietf-event-notifications">notif-bis:ok</subscription-result>
</rpc-reply>]]>]]>

最後に、<close-session>オペレーションでNETCONFセッション自体を終了します。

client -> device

<?xml version="1.0" encoding="utf-8" ?>
<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <close-session/>
</rpc>]]>]]>

デバイスからは以下のような<rpc-reply>が返って、SSHセッションが終了します。

client <- device

<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101"><ok/>
</rpc-reply>]]>]]>

Rubyで試す

次に、プログラミングを用いた例として、Rubyで試してみます。なぜRubyなのかといえば、最後に紹介するようにFluentdのプラグインを作りたかったからです。

サンプルコードは以下のとおりです。後述する理由により、subscription-idを取得するためにXMLメッセージをパースすると、割と込み入ったサンプルコードになってしまうため、<delete-subscription>オペレーションと<close-session>オペレーションをしないままで、SSHセッションを終了してます。showコマンド見る限り、SSHセッション自体を終了すれば、それに紐づくNETCONF YANG PushのSubscriptionはIOS上では解放されているようでしたが、プロダクション環境ではくれぐれもマネしないでください :sweat_smile:

require 'net/ssh'

int_pressed = false

trap :INT do
  int_pressed = true
end

hello = <<-EOS
<?xml version="1.0" encoding="utf-8"?>
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <capabilities>
    <capability>urn:ietf:params:netconf:base:1.0</capability>
  </capabilities>
</hello>]]>]]>
EOS

establish_subscription = <<-EOS
<?xml version="1.0" encoding="utf-8"?>
<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <establish-subscription xmlns="urn:ietf:params:xml:ns:yang:ietf-event-notifications" xmlns:yp="urn:ietf:params:xml:ns:yang:ietf-yang-push">
    <stream>yp:yang-push</stream>
    <yp:xpath-filter>/ios-emul-oper-db:ios-emul-oper-db/cpu-usage/five-seconds</yp:xpath-filter>
    <yp:period>1000</yp:period>
  </establish-subscription>
</rpc>]]>]]>
EOS

Net::SSH.start("192.0.2.1", "admin", :port => 830, :password => "admin", :timeout => 10) do |ssh|
  ssh.open_channel do |channel|
    channel.subsystem("netconf") do |ch, success|
      raise "subsystem could not be started" unless success
      ch.on_data do |ch, data|
        puts "on data..."
        puts data
      end
      ch.on_close do |ch|
        puts "on close ..."
      end
      ch.on_eof do |ch|
        puts "on eof ..."
      end
      puts "send hello ..."
      ch.send_data(hello)
      puts "send subscription ..."
      ch.send_data(establish_subscription)
    end
  end

  ssh.loop(1) { not int_pressed }
  puts "close ssh ..."
  ssh.close
end

subsystemnetconfにする以外は、通常のnet-sshの使い方のとおりです。

実行すると、以下のような結果になります。<hello>の中身は今回も省略してます。

send hello ...
send subscription ...
on data...
<?xml version="1.0" encoding="UTF-8"?>
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capabilities>
<capability>urn:ietf:params:netconf:base:1.0</capability>
<capability>urn:ietf:params:netconf:base:1.1</capability>
...
</capabilities>
<session-id>30628</session-id></hello>]]>]]><?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101"><subscription-result xmlns='urn:ietf:params:xml:ns:yang:ietf-event-notifications' xmlns:notif-bis="urn:ietf:params:xml:ns:yang:ietf-event-notifications">notif-bis:ok</subscription-result>
<subscription-id xmlns='urn:ietf:params:xml:ns:yang:ietf-event-notifications'>2147483653</subscription-id>
</rpc-reply>]]>]]>
on data...
<?xml version="1.0" encoding="UTF-8"?>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2017-12-19T04:00:51.70Z</eventTime><push-update xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-push"><subscription-id>2147483653</subscription-id><datastore-contents-xml><ios-emul-oper-db xmlns="http://cisco.com/ns/iosxe/ios-emul-oper-db"><cpu-usage><five-seconds>0</five-seconds></cpu-usage></ios-emul-oper-db></datastore-contents-xml></push-update></notification>
]]>]]>
on data...
<?xml version="1.0" encoding="UTF-8"?>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2017-12-19T04:01:01.70Z</eventTime><push-update xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-push"><subscription-id>2147483653</subscription-id><datastore-contents-xml><ios-emul-oper-db xmlns="http://cisco.com/ns/iosxe/ios-emul-oper-db"><cpu-usage><five-seconds>0</five-seconds></cpu-usage></ios-emul-oper-db></datastore-contents-xml></push-update></notification>
on data...
]]>]]>
on data...
<?xml version="1.0" encoding="UTF-8"?>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2017-12-19T04:01:11.70Z</eventTime><push-update xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-push"><subscription-id>2147483653</subscription-id><datastore-contents-xml><ios-emul-oper-db xmlns="http://cisco.com/ns/iosxe/ios-emul-oper-db"><cpu-usage><five-seconds>1</five-seconds></cpu-usage></ios-emul-oper-db></datastore-contents-xml></push-update></notification>
]]>]]>
...

実行結果を注意深くみると分かりますが、一つのRPCメッセージが大きいと、デバイスからは複数回に分断されてメッセージが届きます(つまり、複数回on_dataが呼び出されます)。サンプルコードの複雑化を避けるため今回は割愛しましたが、XML形式の文字列をパースしたいときは、何らかの変数にいったん格納してから、デリミタ(]]>]]>)が現れたタイミングでまるっとパースする等の工夫が必要です。

おまけ

記事の中でも前振りしたとおり、IOS XEのModel Driven Telemetry用にFluentdのInputプラグインを作ってみましたー :sushi:
具体的な使い方は、近日、別な記事で紹介するようにします!

おしまい

tetsusat
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away