ネットワークエンジニア向けのPython:はじめに

おそらく、多くのネットワークエンジニアは、CLIを介してのみネットワーク機器を管理するのは時間がかかり、非生産的であることをすでに認識しています。 特に、数十または数百のデバイスが制御されており、多くの場合、単一のテンプレートに従って構成されている場合。 すべてのデバイスからローカルユーザーを削除し、いくつかのルールに準拠しているかどうかすべてのルーターの構成を確認し、すべてのスイッチで有効なポートの数をカウントすることは、自動化なしでは解決できない典型的なタスクの例です。



この記事は、主にまだPythonに慣れていないか、Pythonが初めてのネットワークエンジニアを対象としています。 いくつかの実用的な問題を解決するためのサンプルスクリプトを検討します。これは、すぐに作業に適用できます。


まず、Pythonを選んだ理由を説明します。

まず、非常に幅広いタスクを解決できる学習しやすいプログラミング言語です。

第二に、Cisco、Juniper、Huaweiなどのネットワーク機器の大手メーカーは、自社の機器にPythonサポートを実装しています。 この言語はネットワーク分野で未来を持っています。その研究は時間の無駄ではありません。

第三に、言語は非常に一般的です。 多くの便利なライブラリが彼のために書かれており、プログラマーの大規模なコミュニティがあり、インターネット上のほとんどの質問に対する答えを検索結果の最初の行で見つけることができます。

ネットワークプロジェクトを少し設計して実装します。 そのうちの1つでは、2つの問題を一度に解決する必要がありました。

  1. 数百のブランチルーターを調べて、それらが同じ方法で構成されていることを確認します。 たとえば、Tunnel0またはTunnel99ではなく、Tunnel1インターフェースがデータセンターとの通信に使用されること。 そして、これらのインターフェースは、もちろんIPアドレスを除いて同じように構成されています。
  2. ローカルプロバイダーのIPアドレスを介した静的ルートの追加など、すべてのルーターを再構成します。 つまり、このコマンドはルーターごとに一意です。

Pythonスクリプトが助けになりました。 その開発とテストには1日かかりました。

最初に行うことは、 Pythonと非常に望ましいPyCharm CEをインストールすることです 。 Python 3(現在の最新バージョン3.6.2)をダウンロードしてインストールします。 インストール時に「インストールのカスタマイズ」を選択し、「詳細オプション」の段階で「環境変数にPythonを追加」の横にあるチェックボックスを設定します。

PyCharm CEは、非常に便利なデバッガを備えた無料の開発環境です。 ダウンロードしてインストールします。

2番目のステップは、必要なnetmikoライブラリをインストールすることです。 SSHまたはtelnetを介してデバイスと対話する必要があります。 ライブラリをコマンドラインからインストールします。

pip install netmiko

3番目のステップは、タスクのソースデータとスクリプトを準備することです。

テキストファイル「ip.txt」を入力として使用します。 ファイルの各行には、接続先のデバイスのIPアドレスが含まれている必要があります。 コンマは、特定のデバイスのユーザー名とパスワードを指定できます。 これが行われない場合、スクリプトの実行時に入力したものが使用されます。 スペースは無視されます。 文字列の最初の文字が「#」の場合、コメントと見なされ、無視されます。 有効なファイルの例を次に示します。



スクリプト自体は論理的に2つの部分で構成されています。メインプログラムとdoRouter()関数です。 内部では、ルーターに接続し、CLIにコマンドを送信し、応答を受信して​​分析します。 この機能の入力データは、ルーターのIPアドレス、ログイン、およびパスワードです。 問題が発生した場合、関数はルーターのIPアドレスを返し、別のfail.txtファイルに書き込みます。 すべてがうまくいった場合、メッセージが画面に表示されるだけです。

ルーターとの相互作用を別の機能にし、メインプログラムのループ内ですべてを実行する必要がないのはなぜですか? 主な理由は、スクリプトの期間です。 すべてのルーターに交互に接続するには、4時間かかりました。 主に、それらの一部が応答せず、スクリプトがタイムアウトの期限が切れるまで長時間待機したためです。 したがって、関数の10個のインスタンスを並行して実行します。 私の場合、これによりスクリプトの実行時間が10分に短縮されました。

ここで、メインプログラムをより詳細に検討します。

セキュリティのために、ユーザー名とパスワードをスクリプトに保存しません。 したがって、それらを入力するプロンプトが表示されます。 また、パスワードを入力すると、パスワードは表示されません。 doRouterプロシージャでこれらのグローバル変数を使用します。 WindowsのPyCharmでgetpassを使用する際に問題が発生しました。 スクリプトは、実行ではなくデバッグモードで実行した場合にのみ正常に機能しました。 コマンドラインでは、すべてが完璧に機能しました。 スクリプトはOS Xでもテストされており、PyCharmに問題はありませんでした。

 user_name = input("Enter Username: ") pass_word = getpass() 

次に、IPアドレスを使用してファイルを読み取ります。 try…exceptコンストラクトは、ファイル読み取りエラーを正しく処理します。 出力では、IPアドレス、ユーザー名、パスワードconnection_data含む接続connection_dataのデータの配列を取得します。

 try: f = open('ip.txt') connection_data=[] filelines = f.read().splitlines() for line in filelines: if line == "": continue if line[0] == "#": continue conn_data = line.split(',') ipaddr=conn_data[0].strip() username=global_username password=global_password if len(conn_data) > 1 and conn_data[1].strip() != "": username = conn_data[1].strip() if len(conn_data) > 2 and conn_data[2].strip() != "": password = conn_data[2].strip() connection_data.append((ipaddr, username, password)) f.close() except: sys.exit("Couldn't open or read file ip.txt") 

次に、プロセスのリストを作成して開始します。 スクリプトがWindowsとOS Xで同じように動作するように、プロセス作成メソッドを「spawn」として設定します。 作成されるプロセスの数は、IPアドレスの数と等しくなります。 ただし、同時に実行されるのは10個までですrouters_with_issuesリストに、 doRouter関数が返すものをdoRouterます。 私たちの場合、これらは問題を抱えていたルーターのIPアドレスです。

 multiprocessing.set_start_method("spawn") with multiprocessing.Pool(maxtasksperchild=10) as process_pool: routers_with_issues = process_pool.map(doRouter, connection_data, 1) process_pool.close() process_pool.join() 

process_pool.join()コマンドは、スクリプトdoRouter()関数のすべてのインスタンスがdoRouter()するのをdoRouter()てからメインプログラムの実行を継続するために必要です。

最後に、未構成のルーターのIPアドレスを含むテキストファイルを作成/書き換えます。 また、このリストを画面に表示します。

 failed_file = open('fail.txt', 'w') for item in routers_with_issues: if item != None: failed_file.write("%s\n" % item) print(item) 

ここで、 doRouter()プロシージャを調べてみましょう。 最初に行うことは、入力を処理することです。 ReGexを使用して、正しいIPアドレスが関数に渡されたことを確認します。

 ip_check = re.findall("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", ip_address) if ip_check == []: print(bcolors.FAIL + "Invalid IP - " + str(ip_address) + bcolors.ENDC) return ip_address 

次に、ルーターに接続して接続するために必要なデータを含む辞書を作成します。

 device = { 'device_type': 'cisco_ios', 'ip': ip_address.strip(), 'username': username, 'password': password, 'port': 22, } try: config_ok = True net_connect = ConnectHandler(**device) 

コマンドを送信し、ルーターから受信した応答を分析します。 cli_response変数に配置されます。 この例では、現在の設定を確認します。 結果が画面に表示されます。 この部分は、さまざまなタスクに合わせて変更する必要があります。 このスクリプトでは、ルーターの現在の構成を確認します。 正しい場合は、変更を加えます。 検証中に問題が検出された場合、変数config_okFalse設定し、変更を適用しません。

 cli_response = net_connect.send_command("sh dmvpn | i Interface") cli_response = cli_response.replace("Interface: ", "") cli_response = cli_response.replace(", IPv4 NHRP Details", "").strip() if cli_response != "Tunnel1": print(str(ip_address)+" - " + bcolors.WARNING + "WARNING - DMVPN not on Tunnel1. " + cli_response+ " " + bcolors.ENDC) config_ok=False 

ここでは、次の文字列操作が役立ちます。

運営説明
+行連結s3 = s1 + s2
>>>印刷( 'Happy New' + str(2017)+ 'Year')
新年あけましておめでとうございます2017
len(s)行の長さを決定する
[]サブストリングを強調表示します(インデックスはゼロから始まります)s [5]は6番目の文字です
s [5:7]-6番目から8番目の文字
s [-1]は最後の文字で、s [len(s)-1]と同じです。
s.split()
s.join()
分割線
行を結合
>>> 'Petya、Lesha、Kolya'.split('、 ')
['Petya'、 'Lesha'、 'Kolya']

>>> '、'。join({'Petya'、 'Lesha'、 'Kolya'})
「レシャ、ペティア、コリャ」
str(L)
リスト
リストを文字列に変換
文字列をリストに変換
>>> str(['1'、 '2'、 '3'])
「['1'、 '2'、 '3']」

>>>リスト(「テスト」)
['T'、 'e'、 's'、 't']
テンプレートのフォーマット>>> s1、s2 =「ミティア」、「ヴァシリサ」
>>> '%s +%s = love'%(s1、s2)
「ミティア+ヴァシリサ=愛」
f変数置換>>> a = 'Maxim'
>>> f'Name {a} '
「名前マキシム」
str.find(substr)strでsubstr substrを見つける
見つかった最初の部分文字列の位置を返します
>>> 'これはテキストです' .find( 'a')
8
str.replace(古い、新しい)文字列strの部分文字列oldを部分文字列newで置き換える>>> newstr = 'これはテキストです' .replace( 'is'、 'is not')
>>>印刷(newstr)
これはテキストではありません
str.strip()
str.rstrip()
先頭と末尾(または末尾のみ)でスペースとタブを削除します>>> 'これはテキストです\ t \ t \ t'.strip()
「これはテキストです」

静的ルートを追加する問題を解決するには、最初にnext-hop IPアドレスを決定する必要があります。 私の場合、最も簡単な方法は、既存の静的ルートのnext-hopアドレスを調べることです。

 cli_response2=net_connect.send_command("sh run | i ip route 8.8.8.8 255.255.255.255") if cli_response2.strip() == "": print(str(ip_address)+" — " + bcolors.FAIL + "WARNING — couldn't find static route to 8.8.8.8" + bcolors.ENDC) config_ok=False ip_next_hop = "" if cli_response2 != "": ip_next_hop = cli_response2.split(" ")[4] if ip_next_hop == "": print(str(ip_address)+" — " + bcolors.FAIL + "WARNING — couldn't find next-hop IP address " + bcolors.ENDC) config_ok=False 

1つ以上の構成コマンドを一度に送信できます。 同時に5つ以上のコマンドを送信してもうまくいきませんでした。必要に応じて、設計を数回繰り返すことができます。

 config_commands = ['ip route 1.1.1.1 255.255.255.255 '+ip_next_hop, 'ip route 2.2.2.2 255.255.255.255 '+ip_next_hop] net_connect.send_config_set(config_commands) 


完全なスクリプト。
 import sys from netmiko import ConnectHandler from getpass import getpass import time import multiprocessing import re start_time = time.time() class bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def doRouter(connection_data): ip_address = connection_data[0] username = connection_data[1] password = connection_data[2] ip_check = re.findall("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", ip_address) if ip_check == []: print(bcolors.FAIL + "Invalid IP - " + str(ip_address) + bcolors.ENDC) return ip_address device = { 'device_type': 'cisco_ios', 'ip': ip_address.strip(), 'username': username, 'password': password, 'port': 22, } try: config_ok = True net_connect = ConnectHandler(**device) cli_response = net_connect.send_command("sh dmvpn | i Interface") cli_response = cli_response.replace("Interface: ", "") cli_response = cli_response.replace(", IPv4 NHRP Details", "").strip() if cli_response != "Tunnel1": print(str(ip_address)+" - " + bcolors.WARNING + "WARNING - DMVPN not on Tunnel1. " + cli_response+ " " + bcolors.ENDC) config_ok=False cli_response2=net_connect.send_command("sh run | i ip route 1.1.1.1 255.255.255.255") if cli_response2.strip() == "": print(str(ip_address)+" - " + bcolors.WARNING + "WARNING - couldn't find static route to 8.8.8.8" + bcolors.ENDC) config_ok=False ip_next_hop = "" if cli_response2 != "": ip_next_hop = cli_response2.split(" ")[4] if ip_next_hop == "": print(str(ip_address)+" - " + bcolors.WARNING + "WARNING - couldn't find next-hop IP address " + bcolors.ENDC) config_ok=False if config_ok: config_commands = ['ip route 1.1.1.1 255.255.255.255 '+ip_next_hop, 'ip route 2.2.2.2 255.255.255.255 '+ip_next_hop] net_connect.send_config_set(config_commands) print(str(ip_address) + " - " + "Static routes added") else: print(str(ip_address) + " - " + bcolors.FAIL + "Routes weren't added because config is incorrect" + bcolors.ENDC) return ip_address if config_ok: net_connect.send_command_expect('write memory') print(str(ip_address) + " - " + "Config saved") net_connect.disconnect() except: print(str(ip_address)+" - "+bcolors.FAIL+"Cannot connect to this device."+bcolors.ENDC) return ip_address print(str(ip_address) + " - " + bcolors.OKGREEN + "Router configured sucessfully" + bcolors.ENDC) if __name__ == '__main__': # Enter valid username and password. Note password is blanked out using the getpass library global_username = input("Enter Username: ") global_password = getpass() try: f = open('ip.txt') connection_data=[] filelines = f.read().splitlines() for line in filelines: if line == "": continue if line[0] == "#": continue conn_data = line.split(',') ipaddr=conn_data[0].strip() username=global_username password=global_password if len(conn_data) > 1 and conn_data[1].strip() != "": username = conn_data[1].strip() if len(conn_data) > 2 and conn_data[2].strip() != "": password = conn_data[2].strip() connection_data.append((ipaddr, username, password)) f.close() except: sys.exit("Couldn't open or read file ip.txt") multiprocessing.set_start_method("spawn") with multiprocessing.Pool(maxtasksperchild=10) as process_pool: routers_with_issues = process_pool.map(doRouter, connection_data, 1) # doRouter - function, iplist - argument process_pool.close() process_pool.join() print("\n") print("#These routers weren't configured#") failed_file = open('fail.txt', 'w') for item in routers_with_issues: if item != None: failed_file.write("%s\n" % item) print(item) #Completing the script and print running time print("\n") print("#This script has now completed#") print("\n") print("--- %s seconds ---" % (time.time() - start_time)) 

スクリプトを準備したら、コマンドラインまたはPyCharm CEから実行できます。 コマンドラインから次のコマンドを実行します:

python script.py

PyCharm CEの使用をお勧めします。 そこで、新しいプロジェクトであるPythonファイルを作成し(ファイル→新規...)、スクリプトを貼り付けます。 スクリプトのあるフォルダーにip.txtファイルを入れてスクリプトを実行します(実行→実行)

次の結果が得られます。

 bash ~/PycharmProjects/p4ne $ python3 script.py Enter Username: cisco Password: Invalid IP - 10.1.1.256 127.0.0.1 - Cannot connect to this device. 1.1.1.1 - Cannot connect to this device. 10.10.100.227 - Static routes added 10.10.100.227 - Config saved 10.10.100.227 - Router configured sucessfully 10.10.31.170 - WARNING - couldn't find static route to 8.8.8.8 10.10.31.170 - WARNING - couldn't find next-hop IP address 10.10.31.170 - Routes weren't added because config is incorrect 2.2.2.2 - Cannot connect to this device. #These routers weren't configured# 10.1.1.256 127.0.0.1 217.112.31.170 1.1.1.1 2.2.2.2 #This script has now completed# 

スクリプトのデバッグ方法に関するいくつかの言葉。 これはPyCharmで行うのが最も簡単です。 スクリプトの実行を停止する行をマークし、デバッグモードで実行を開始します。 スクリプトが停止すると、すべての変数の現在の値を確認できます。 正しいデータが送受信されていることを確認してください。 [ステップイン]または[ステップインマイコード]ボタンを使用して、スクリプトをステップごとに実行し続けることができます。



スクリプトの説明されたバージョンの制限:


このスクリプトは、特定の問題を解決するために作成されました。 しかし、それは普遍的であり、他の人の仕事を助けることになると思います。 そして最も重要なことは、Pythonを学ぶための最初のステップになることです。

スクリプトを記述するときに、次のリソースが使用されました。


アレクサンダー・ガルシン、Jet Infosystems、データシステムズ、リーディングデザインエンジニア

Source: https://habr.com/ru/post/J336608/


All Articles