在網路世界中,裝置升級猶如心臟手術,必須謹慎操作並密切監控。升級後的檢查更是重中之重,它如同術後觀察,能及時發現潛在問題,確保網路的穩定執行。本文將分享我多年來在網路自動化領域積累的經驗,並以 Python 和 Netmiko 為例,展示如何開發一個自動化的升級後檢查流程。

升級後檢查的重要性

網路裝置升級如同走鋼索,風險與收益並存。即使 meticulous 的規劃和測試,仍可能出現意料之外的狀況。升級後檢查如同安全網,能及時捕捉這些問題,避免它們演變成嚴重的故障。以下列舉一些關鍵檢查點:

  • 核心服務驗證: 確保網路的命脈(例如路由、交換、防火牆)正常運作。
  • 組態變更稽核: 檢查升級過程是否引入了非預期的組態變更,如同手術後的疤痕,必須仔細檢查。
  • 效能指標監控: 評估升級後的網路效能是否達標,如同觀察病人的心跳和血壓。
  • 安全設定檢驗: 確認升級後的安全性設定沒有漏洞,如同檢查手術後的傷口是否感染。

Python 自動化檢查實戰

我個人偏好使用 Python 的 Netmiko 函式庫,它如同手術刀般精準與靈活,能有效控制網路裝置。以下程式碼片段示範如何監控 SSH 連線,並在裝置重新上線後執行檢查:

import socket
import time
from netmiko import ConnectHandler

devices = [
    {'device_type': 'cisco_ios', 'host': '192.168.1.1', 'username': 'admin', 'password': 'password'},
    {'device_type': 'cisco_ios', 'host': '192.168.1.2', 'username': 'admin', 'password': 'password'},
]

def check_ssh(ip, port=22, retries=120, delay=10):
    """檢查 SSH 連線狀態,如同等待病人甦醒。"""
    for _ in range(retries):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(3)
            try:
                s.connect((ip, port))
                return True  # SSH 連線成功,病人醒了
            except (socket.timeout, ConnectionRefusedError):
                print(f"{ip} SSH 連線失敗,等待 {delay} 秒後重試...")
                time.sleep(delay)
    return False  # SSH 連線失敗,病人沒醒

def post_upgrade_check(device):
    """執行升級後檢查,如同術後觀察。"""
    ip = device['host']
    if check_ssh(ip):
        print(f"{ip} SSH 連線成功,開始執行檢查...")
        try:
            with ConnectHandler(**device) as conn:
                # 執行 show 命令收集資訊,如同檢查病人的各項指標
                version = conn.send_command("show version")
                running_config = conn.send_command("show running-config")
                # ... 其他 show 命令

                # 將收集到的資訊儲存或比對,如同記錄病人的病情
                # ...

        except Exception as e:
            print(f"{ip} 發生錯誤:{e}")
    else:
        print(f"{ip} SSH 連線失敗,無法執行檢查。")


for device in devices:
    post_upgrade_check(device)

check_ssh 函式如同心跳監測器,持續檢查 SSH 連線狀態。post_upgrade_check 函式則如同術後護理流程,在裝置上線後執行一系列檢查,收集關鍵資訊並進行分析。

組態差異比對

利用 difflib 模組,我們可以比較升級前後的組態差異,如同比對手術前後的 X 光片。

import difflib

def compare_configs(pre_config, post_config):
    """比較組態差異,找出潛在問題。"""
    diff = difflib.ndiff(pre_config.splitlines(), post_config.splitlines())
    for line in diff:
        if line.startswith("+ ") or line.startswith("- "):
            print(line)

# ... 在 post_upgrade_check 函式中呼叫 compare_configs 函式

compare_configs 函式使用 difflib.ndiff 比對兩個組態檔案,並輸出差異部分。

升級後檢查流程

  graph LR
    A[升級裝置] --> B{SSH 連線檢查};
    B -- 成功 --> C[執行 Show 命令];
    B -- 失敗 --> D[告警];
    C --> E[組態比對];
    E --> F[生成報告];

圖表説明:此流程圖清晰地展現了升級後檢查的步驟,從 SSH 連線檢查到最終的報告生成。

  graph LR
    A[Python] --> B(Netmiko);
    B --> C[Cisco IOS];
    B --> D[Juniper JunOS];
    B --> E[Other Devices];

圖表説明:此圖表展示了 Netmiko 支援的多種網路裝置型別,體現了其靈活性。

透過以上技術和方法,我們可以構建一個高效的自動化升級後檢查系統,降低風險,提升效率,並確保網路的穩定執行。這是我多年經驗的結晶,希望能對各位讀者有所啟發。

```python
import re
import getpass
import time
import pandas as pd
import netmiko
import difflib
import os

def get_credentials():
    """取得使用者憑證"""

    p1 = r"^[a-zA-Z0-9]{4,30}$"  # 使用者ID的正規表示式
    p2 = r"^[a-zA-Z].{7,49}$"  # 密碼的正規表示式

    while True:
        user_id = input("請輸入使用者ID:")
        if re.match(p1, user_id):
            break
        else:
            print("使用者ID格式錯誤,請重新輸入。")

    while True:
        password = getpass.getpass("請輸入密碼:")
        if re.match(p2, password):
            secret = get_secret(p2)
            if password == secret:
                break
            else:
                print("密碼與確認密碼不符,請重新輸入。")
        else:
            print("密碼格式錯誤,請重新輸入。")

    enable_secret = getpass.getpass("請輸入啟用密碼:")


    return user_id, password, enable_secret

def get_secret(p2):
    """取得確認密碼"""
    while True:
        secret = getpass.getpass("請再次輸入密碼進行確認:")
        if re.match(p2, secret):
            return secret
        else:
            print("確認密碼格式錯誤,請重新輸入。")


def upgrade_ios(device, new_ios_image):
    """升級 IOS"""
    try:
        with netmiko.ConnectHandler(**device) as conn:
            conn.enable()
            # 檢查目前 IOS 版本
            current_ios = conn.send_command("show version | i Version")
            print(f"目前 IOS 版本:{current_ios}")

            # 檢查新 IOS 映像檔是否存在
            conn.send_command(f"copy flash: {new_ios_image}")
            output = conn.send_command(f"verify /md5 flash:{new_ios_image}")
            print(output)
            # 設定開機設定
            conn.send_config_set([f"boot system flash:{new_ios_image}"])
            # 儲存設定
            conn.send_command("write memory")

            print(f"{device['ip']} 的 IOS 已設定為 {new_ios_image}")
            # 確認是否重新啟動
            while True:
                reload_confirm = input(f"是否重新啟動 {device['ip']} (y/n)? ")
                if reload_confirm.lower() == 'y':
                    conn.send_command_timing("reload", delay_factor=2)
                    print(f"已重新啟動 {device['ip']}")
                    break
                elif reload_confirm.lower() == 'n':
                    print(f"未重新啟動 {device['ip']}")
                    break
                else:
                    print("無效的輸入。請輸入 y 或 n。")

    except netmiko.ssh_exception.NetMikoTimeoutException:
        print(f"無法連線到 {device['ip']}")
    except Exception as e:
        print(f"發生錯誤:{e}")


def compare_route(device):
    """比較升級前後的路由表"""
    try:
        with netmiko.ConnectHandler(**device) as conn:
            conn.enable()
            pre_route = conn.send_command("show ip route")

            # 模擬升級後的路由表 (實際應用中需要在升級後取得)
            post_route = pre_route # 此處僅為示範,實際應用中需要修改

            diff = difflib.HtmlDiff().make_file(pre_route.splitlines(), post_route.splitlines())
            filename = f"{device['ip']}_route_diff.html"
            filepath = os.path.join("route_diff_reports", filename)  # 使用 os.path.join 建立檔案路徑
            os.makedirs("route_diff_reports", exist_ok=True)  # 建立目錄,若目錄已存在則不報錯

            with open(filepath, "w") as f:
                f.write(diff)
            print(f"差異報告已儲存至 {filepath}")

    except netmiko.ssh_exception.NetMikoTimeoutException:
        print(f"無法連線到 {device['ip']}")
    except Exception as e:
        print(f"發生錯誤:{e}")



if __name__ == "__main__":
    user_id, password, enable_secret = get_credentials()
    devices_info = pd.read_csv("devices_info.csv")
    new_ios_image = "c8000v-universalk9.17.06.05a.SPA.bin" #  新 IOS 映像檔名

    for index, row in devices_info.iterrows():
        device = {
            'device_type': 'cisco_ios',
            'ip': row['IP'],
            'username': user_id,
            'password': password,
            'secret': enable_secret,  # 使用者提供的啟用密碼
        }

        upgrade_ios(device, new_ios_image)
        time.sleep(300) # 等待5分鐘
        compare_route(device)

此程式碼整合了多個功能,包含取得使用者憑證、升級 IOS 和比較路由表差異。

  • get_credentials(): 取得使用者ID、密碼和啟用密碼,並進行輸入驗證。
  • get_secret(): 確認使用者輸入的密碼。
  • upgrade_ios(): 升級指定裝置的 IOS 版本,包含檢查版本、複製映像檔、設定開機設定、儲存設定和重新啟動裝置等步驟。 新增了錯誤處理機制,可以捕捉連線逾時和其它異常。
  • compare_route(): 比較升級前後的路由表,並生成 HTML 格式的差異報告。 使用 os.path.joinos.makedirs 確保報告儲存到指定目錄,即使目錄不存在也能自動建立。
  • 主程式碼塊: 讀取 CSV 檔案中的裝置資訊,並依次對每個裝置執行升級和路由表比較操作。 程式碼中加入了等待時間 time.sleep(300),讓裝置有足夠時間重新啟動。

改進説明:

  • 安全性提升: 加入了啟用密碼的取得和使用,提升了安全性。
  • 錯誤處理: 完善了錯誤處理機制,可以處理連線逾時等異常情況。
  • 檔案路徑處理: 使用 os.path.join 構建檔案路徑,避免了硬編碼路徑,提高了程式碼的可移植性。
  • 目錄自動建立: 使用 os.makedirs(exist_ok=True) 自動建立儲存報告的目錄,避免了手動建立目錄的麻煩。
  • 程式碼結構: 程式碼結構更加清晰,函式功能更加單一,提高了程式碼的可讀性和可維護性。

圖表: 由於程式碼邏輯已在內容解密中詳細説明,因此省略了 圖表。

title: “Cisco IOS XE 自動化升級方案” date: 2025-06-16T18:00:00+08:00 author: “玄貓(BlackCat)” categories: [“網路自動化”, “Python”, “Cisco”] tags: [“IOS XE”, “升級”, “自動化”, “Netmiko”, “Pandas”] draft: false math: true summary: “本文分享如何使用 Python 和 Netmiko 開發一個完整的 Cisco IOS XE 自動化升級方案,涵蓋憑證取得、IOS 升級、路由表比較和 HTML 報告生成等功能,有效提高升級效率和降低風險。”


在瞬息萬變的網路世界中,保持網路裝置的韌性至關重要。我,玄貓,將多年來在網路自動化領域的實戰經驗濃縮於此,分享如何使用 Python 建立一個穩固的網路裝置升級流程。這個流程將涵蓋連線測試、MD5 校驗、儲存空間檢查等關鍵環節,確保升級過程萬無一失。

```python
import getpass
import re
import time
import pandas as pd
import netmiko

# 全域變數宣告
uid = None
pwd = None
secret = None
device_list = []
device_list_netmiko = []

def get_secret(p2):
    """
    此函式用於取得第二組密碼,並確認其格式有效與與確認密碼相符。
    提供使用者選擇是否使用與登入密碼相同的密碼。
    """
    global secret
    resp = input("密碼與登入密碼相同嗎? (y/n) : ")
    resp = resp.lower()
    if resp in ("yes", "y"):
        secret = pwd
    elif resp in ("no", "n"):
        secret = None
    while not secret:
        secret = getpass.getpass("請輸入密碼 : ")
        while not p2.match(secret):
            print("使用者ID:至少 4 個字母,以字母開頭")
            secret = getpass.getpass("*請輸入密碼 : ")
        secret_verify = getpass.getpass("確認密碼 : ")
        if secret != secret_verify:
            print("密碼不符! 請重試。")
            secret = None


def get_credentials():
    """
    此函式用於取得使用者的網路管理員 ID、密碼和另一組密碼,並使用正規表示式驗證其格式,同時提示使用者確認密碼。
    """
    p1 = re.compile(r'^[a-zA-Z][a-zA-Z0-9_-]{2,28}[a-zA-Z0-9]$')
    p2 = re.compile(r'^[a-zA-Z][a-zA-Z0-9!@#$%^&*()_+=\-[\]{};:\'",.<>?]{7,49}')
    global uid, pwd
    uid = input("請輸入網路管理員 ID : ")
    while not p1.match(uid):
        print("使用者ID:至少 4 個字母,以字母開頭")
        uid = input("*請輸入網路管理員 ID : ")
    pwd = None
    while not pwd:
        pwd = getpass.getpass("請輸入網路管理員密碼 : ")
        while not p2.match(pwd):
            print("密碼:至少 7 個字元,以字母開頭。")
            pwd = getpass.getpass("*請輸入網路管理員密碼 : ")
        pwd_verify = getpass.getpass("確認網路管理員密碼 : ")
        if pwd != pwd_verify:
            print("密碼不符! 請重試。")
            pwd = None
    get_secret(p2)
    return uid, pwd, secret


def read_info(uid, pwd, secret):
    """
    此函式從 devices_info.csv 檔案讀取裝置資訊,並將其轉換為程式可用的列表和字典格式。
    它會建立兩個全域變數:device_list 和 device_list_netmiko,分別儲存所有裝置資訊的列表和 Netmiko 連線所需的裝置資訊字典列表。
    """
    df = pd.read_csv(r'./devices_info.csv')
    global device_list, device_list_netmiko
    device_list = []
    for index, rows in df.iterrows():
        device_append = [rows.devicename, rows.device, rows.devicetype, rows.host, rows.newios, rows.newiosmd5]
        device_list.append(device_append)

    device_list_netmiko = []
    for i, x in enumerate(device_list, 1):
        if x:  # 檢查裝置資訊是否為空
            name = f'device{str(i)}'
            devicetype, host = x[2], x[3]
            device = {
                'device_type': devicetype,
                'host': host,
                'username': uid,
                'password': pwd,
                'secret': secret,
            }
            device_list_netmiko.append(device)


t1 = time.mktime(time.localtime())
uid, pwd, secret = get_credentials()
read_info(uid, pwd, secret)
tt = time.mktime(time.localtime()) - t1
print("總執行時間 : {0} 秒".format(tt))

程式碼解析:

我將程式碼拆解成數個函式,提升程式碼的可讀性和模組化程度。get_credentials 函式負責取得使用者憑證,並利用正規表示式驗證輸入的有效性,確保資料的正確性。read_info 函式則讀取裝置資訊,並將其轉換成 Netmiko 可用的格式,為後續的裝置連線做準備。

  graph LR
    B[B]
    C[C]
    D[D]
    E[E]
    A[開始] --> B{取得使用者憑證};
    B --> C{驗證憑證};
    C -- 有效 --> D{讀取裝置資訊};
    C -- 無效 --> B;
    D --> E{轉換資料格式};
    E --> F[結束];

圖表説明: 此流程圖展示了程式碼的主要執行流程,從取得使用者憑證到讀取並轉換裝置資訊。

  graph LR
    D[D]
    G[G]
    subgraph "get_credentials 函式"
        A[輸入使用者 ID] --> B{驗證 ID 格式};
        B -- 有效 --> C[輸入密碼];
        B -- 無效 --> A;
        C --> D{驗證密碼格式};
        D -- 有效 --> E[確認密碼];
        D -- 無效 --> C;
        E -- 比對 --> F[取得第二組密碼];
        E -- 不比對 --> C;
        F --> G{驗證第二組密碼};
        G -- 有效 --> H[傳回憑證];
        G -- 無效 --> F;
    end

圖表説明: 此流程圖詳細説明瞭 get_credentials 函式的內部邏輯,包含使用者 ID 和密碼的驗證過程,以及第二組密碼的處理方式。

我的思考:

在設計這個流程時,我特別注重安全性,因此使用 getpass 模組來安全地取得密碼,避免密碼洩露。此外,我也考慮到程式碼的彈性和擴充套件性,採用模組化設計,方便日後新增功能,例如加入連線測試、裝置升級等功能。


我認為,未來的網路自動化將更加智慧化和個人化。我希望可以將機器學習技術融入到網路管理中,讓網路裝置可以根據自身的狀態和環境自動調整組態,實作真正的自主化管理。

我的建議:

建議各位讀者在實作時,根據自己的網路環境調整程式碼和引數,例如修改 devices_info.csv 檔案中的裝置資訊。同時,也建議定期測試和更新程式碼,以確保其穩定性和安全性。