初めまして。@s1061123と申します。
#はじめに
本記事はNetOpsCoding Advent Calendar 2015の8日目の記事となります。
昨日は@niku4iさんの"Nagiosの監視変更作業をGitHubを使って楽にした話"でした。ネットワーク装置は今までサーバとは異なる(UI的にもそれ以外も)歴史だったので最近の流れでそれが少しでも解消してくれると嬉しいですね。
僕自身は仕事でたまにネットワーク装置をいじるのですが、その際にやはりスクリプト言語を使って操作の単純化だったり、半自動化だったりすることがあります。もちろん最新の機械だけ対象にする訳でもないのでNETCONF/YangとかRESTとか使えません。そんなインターフェイスは未来のモノです。森山未来です。イケメンですよ。もう。
イケメンではない僕は、未来に生きれない僕は、そして過去の遺物に生きる僕はそんなことはできません。Net::Telnet最強説の暗黒面とは良く言ったものですね。暗黒の、沼の中の経験をベースに本日は現実を直視する感じの泥臭い話として"スクリプト言語でtelnet使う時の落とし穴"について書いてみたいと思います。そんなにコアな話ではないので適当に読んで頂けると幸いです。
まぁ、ぶっちゃけて言ってしまうと"Telnetを話すライブラリを使うか、expect系を使うか?"という話です。スクリプトでTelnetを話す場合には主に2つのパターンがあると思います。一つはTelnetプロトコルを話すライブラリを使うパターンで、もう一つはexpect系のライブラリを使うパターンです。
#Telnetプロトコルを話すライブラリの場合
PerlだとNet::Telnet, Pythonだとtelnetlib, RubyではNet::Telnetのように主な言語ではTelnetプロトコルをライブラリとしてサポートしています。Telnetプロトコルは主にRFC854から861にかけて定義されています。このライブラリを使うことでネットワーク装置にログインして操作することが可能です。PerlのNet::Telnet等だとNet::Telnet::CiscoやNet::Telnet::Netscreen等各社のプロンプトと親和性を持たせた拡張ライブラリも存在します。
#expect系
もう一つはexpect系です。ベースとなっているのはスクリプト言語TclのライブラリであるExpectであり、スクリプトから指定したコマンドを実行し、入出力をスクリプト側でハンドルするライブラリです。PerlだとExpect.pm, Pythonだとpyexpect (python3だとpyexpect-u、後述)、Rubyだとexpectといった形でサポートされています。
ネットワーク装置を操作する場合、Unix等のOSに存在するtelnetコマンドをスクリプト言語から実行して、入出力をスクリプト側でハンドルすることで操作することが可能です。
もちろんどちらでも操作できるのはもちろんですが、いくつかの穴が存在する場合もあります。多くは基本的と言えば基本的なことなのですが、忘れやすいのでまとめてみました。
(もし他にも何か知っているかたがいらっしゃいましたらコメント頂けると幸いです)
#端末のエスケープシーケンスにまつわる問題
例えば一画面で捌けない行数のアウトプットを出す場合、装置側で"---More---"みたいな形でアウトプットを小分けに出すページャー(Pager)という機能があります。ANSIで規定されている端末のエスケープシーケンスを利用して実装している機能です。人が見る際には便利ですが、スクリプト等から呼ぶ場合には使いたくない機能の一つですね。通常装置側の設定やCLIのコマンドでページャーは無効化できます。ですが、いくつかの装置では長時間の処理を行なう際のプログレスバー等を表示する際に同様にエスケープシーケンスを利用して出力を行なう場合があります。
いくつかのTelnet実装では端末のエスケープシーケンスをフルに実装していない為、こういった出力を上手くハンドルできない可能性があるので、その装置の処理の際の出力をよく確認してライブラリを選ぶと幸せになれます。
#expectモジュールを使った場合のプロセスの切り忘れについて
expectモジュールを使った場合、telnet自身はscriptから呼ばれるtelnetコマンド
(/usr/bin/telnet)になります。通常は特段気にすることはありませんが、まれにtelnetコマンドがゾンビプロセスになったまま残る可能性が存在します。実験ではなく実際に使う場合には終了時にそれらの子プロセスが残っていないか確認するのを忘れないようにしましょう。
#Python2/Python3でpyexpectを使う場合
Pythonのexpectのモジュールであるpyexpectを使う場合、その中でもPython3系列でpyexpectを利用しようとすると、Python2とPython3の文字列実装(主にunicode周り)の違いで正常に動きません。もしPython3でexpect系ライブラリを使う場合、pyexpectを使わずにpyexpect-uを使うことで可能です。pyexpect-uはAPI的にはpypexpectと同じなので特に移植には問題はないと思われます。
#おわりに
ということで本日は"スクリプト言語でtelnet使うときの落とし穴"についてお話しました。
明日(12/9)は@taijijijiさんの"PeeringDB2.0 APIから情報を取得する"です。ISP等でルータ運用の方には必須のpeeringの情報をまとめる話でしょうか。楽しみです。
個人的には明後日(12/10)の@pitanさんの"まだexpectで消耗してるの?"が楽しみでなりません。上のような苦労をした人間にとっての天啓がきっと来るのだと思って楽しみにしてます。