現(xiàn)華為H3C交換機(jī)靜態(tài)路由自動(dòng)化實(shí)戰(zhàn))
簡(jiǎn)介基于Java語言調(diào)用Netconf協(xié)議對(duì)華為、H3C交換機(jī)進(jìn)行靜態(tài)路由條目增刪等自動(dòng)化配置是面向網(wǎng)絡(luò)運(yùn)維工程師與Java開發(fā)者的實(shí)踐型資源。內(nèi)容圍繞Netconf標(biāo)準(zhǔn)協(xié)議展開覆蓋TCP/IP會(huì)話建立、RPC遠(yuǎn)程調(diào)用、XML報(bào)文構(gòu)造與響應(yīng)解析并針對(duì)華為VRP及H3C設(shè)備YANG模型說明了靜態(tài)路由配置節(jié)點(diǎn)的組織方式。資源包共85個(gè)文件約115KB其中62個(gè)XML文件提供設(shè)備數(shù)據(jù)模型與配置模板14個(gè)Java文件實(shí)現(xiàn)核心客戶端邏輯、測(cè)試用例及可運(yùn)行入口另有properties文件保存設(shè)備連接參數(shù)便于替換IP、用戶名和密碼后直接執(zhí)行。已有2257人學(xué)習(xí)下載適合需要掌握設(shè)備配置自動(dòng)化、降低手動(dòng)運(yùn)維風(fēng)險(xiǎn)的中高級(jí)開發(fā)者。通過該工程可直觀驗(yàn)證路由下發(fā)、刪除與查詢流程并可作為企業(yè)網(wǎng)絡(luò)自動(dòng)化平臺(tái)的二次開發(fā)基礎(chǔ)。 最近我接了一個(gè)網(wǎng)絡(luò)自動(dòng)化的小項(xiàng)目需求看起來很簡(jiǎn)單用 Java 寫一套工具通過 NETCONF 協(xié)議去操作華為和 H3C 交換機(jī)實(shí)現(xiàn)靜態(tài)路由條目的新增、刪除和查詢。但真正落地的時(shí)候才發(fā)現(xiàn)這里面牽扯的東西遠(yuǎn)比想象中多——兩家設(shè)備的 YANG 模型對(duì)不上、Java 側(cè)沒有特別順手的官方庫、報(bào)文拼錯(cuò)一個(gè) namespace 設(shè)備就給你丟一個(gè) error 回來。這篇文章把我從選型、到連上設(shè)備、再到把增刪靜態(tài)路由跑通的全過程包括踩過的坑一次性整理出來。如果你正準(zhǔn)備做交換機(jī)配置自動(dòng)化或者剛接觸 NETCONF 想找個(gè)能直接上手的 Java 例子這篇應(yīng)該能幫你少走不少?gòu)澛贰?. 需求場(chǎng)景為什么跑通 NETCONF 值得花這個(gè)力氣1.1 手搓命令的瓶頸在哪如果只是偶爾加一兩條靜態(tài)路由SSH 上去敲命令最直接誰也不會(huì)為了兩條路由專門寫個(gè)程序。但一旦路由數(shù)量變成幾十上百條或者變更需要在固定時(shí)間窗口內(nèi)完成比如割接、專線切換人肉操作就成了最大的瓶頸。我做這個(gè)項(xiàng)目的實(shí)際背景是業(yè)務(wù)系統(tǒng)需要在兩條專線之間根據(jù)流量情況切換引流路徑而切換的本質(zhì)動(dòng)作就是改交換機(jī)上的靜態(tài)路由要求一分鐘內(nèi)完成還要能自動(dòng)確認(rèn)結(jié)果。這種情況下手敲命令根本不現(xiàn)實(shí)必須把它封裝成接口。用 Java 做的好處是能直接嵌進(jìn)公司的運(yùn)維編排平臺(tái)和工單系統(tǒng)權(quán)限、審計(jì)、回滾都可以統(tǒng)一納管。再加上 NETCONF 操作的是結(jié)構(gòu)化數(shù)據(jù)配置執(zhí)行結(jié)果一目了然天然適合這種場(chǎng)景。1.2 SSH、SNMP、NETCONF 三條路線怎么選選型階段我把主流方案擺在桌面上對(duì)比了一下。技術(shù)路線優(yōu)點(diǎn)缺點(diǎn)適合場(chǎng)景SSH 命令行簡(jiǎn)單直接所有功能都能配輸出解析脆弱、沒有事務(wù)語義、并發(fā)差臨時(shí)性操作、老設(shè)備兜底SNMP監(jiān)控采集成熟老工程師熟悉配置能力弱靜態(tài)路由這類配置各家 MIB 不一致偏向監(jiān)控告警不適合配置下發(fā)NETCONF結(jié)構(gòu)化數(shù)據(jù)、RPC 原語清晰、支持能力協(xié)商學(xué)習(xí)曲線陡、不同廠商 YANG 模型有差異配置自動(dòng)化、網(wǎng)絡(luò)編排SNMP 我第一個(gè)排除雖然它的 read 能力很強(qiáng)但拿 set 去配置靜態(tài)路由各廠家 MIB 支持情況參差不齊調(diào)試成本極高。SSH 方案最大的問題是輸出是給人看的不是給程序看的。不同 VRP 版本、不同 Comware 版本display 命令的輸出格式經(jīng)常有細(xì)微差異用正則去匹配就意味著永遠(yuǎn)在為下一個(gè)版本改代碼。NETCONF 的優(yōu)勢(shì)在于操作對(duì)象是 XML 結(jié)構(gòu)化數(shù)據(jù)增刪改查有明確的操作原語程序拿到的結(jié)果也是明確的數(shù)據(jù)樹這才是為自動(dòng)化設(shè)計(jì)的協(xié)議。2. NETCONF 協(xié)議基礎(chǔ)和 YANG 模型差異2.1 一次 NETCONF 會(huì)話從頭到尾發(fā)生了什么NETCONF 不是一個(gè)簡(jiǎn)單請(qǐng)求-響應(yīng)協(xié)議它分了四層傳輸層走 SSH 的 netconf subsystem默認(rèn)端口 830、消息層、操作層、內(nèi)容層。理解這四層是后面排查問題的基礎(chǔ)。一次完整會(huì)話的流程是這樣的客戶端通過 SSH 的 subsystem 通道建立連接雙方先互發(fā) hello 報(bào)文聲明各自支持的能力服務(wù)器還會(huì)在 hello 里帶上 session-id。隨后客戶端開始發(fā)送 rpc 請(qǐng)求比如 get-config、edit-config服務(wù)器返回 rpc-reply。關(guān)鍵點(diǎn)是會(huì)話可以長(zhǎng)連接復(fù)用不需要每次操作都重新握手。這里有個(gè)新手最容易忽略的細(xì)節(jié)每個(gè) NETCONF 報(bào)文必須以]]]]這串字符結(jié)尾這是消息層的幀結(jié)束標(biāo)記。我第一次自己拼報(bào)文時(shí)漏掉了它設(shè)備那邊一直不回復(fù)我以為是 SSH 用戶權(quán)限配錯(cuò)了折騰了半天才發(fā)現(xiàn)是幀結(jié)束符的問題。這個(gè)坑建議所有第一次接觸 NETCONF 的人都記一下。2.2 華為和 H3C 的模型差異必須先搞清 namespace真正上手以后你會(huì)發(fā)現(xiàn)NETCONF 協(xié)議本身是標(biāo)準(zhǔn)的但不同廠商的內(nèi)容層模型差異很大。華為 VRP 較新的版本支持標(biāo)準(zhǔn)的 ietf-routing 模型靜態(tài)路由的 YANG 路徑是routing/control-plane-protocols/control-plane-protocol[typestatic]/static-routes/ipv4/route命名空間是urn:ietf:params:xml:ns:yang:ietf-routing整體比較規(guī)整。H3C Comware 的模型則是另一套風(fēng)格早期版本大量沿用了類似 MIB 轉(zhuǎn)換來的私有 XML 結(jié)構(gòu)靜態(tài)路由的節(jié)點(diǎn)是RouteStatic/IPv4/Route字段包括DestPrefix、MaskLength、NextHopAddress命名空間是http://www.h3c.com/netconf/config:1.0。如果拿華為那套 ietf-routing 的報(bào)文直接發(fā)給 H3C設(shè)備很可能返回 invalid element 或者干脆靜默忽略。所以動(dòng)手寫代碼之前第一件事不是寫連接而是確定目標(biāo)設(shè)備的 YANG 模型。設(shè)備上通??梢杂胐isplay netconf capability查看支持的能力也可以通過get-schemaRPC 拉取具體模型文件再不行就去廠商官網(wǎng)下載對(duì)應(yīng)版本的 NETCONF API 文檔。別偷懶把這個(gè)搞清楚了后面拼報(bào)文才有依據(jù)。3. Java 實(shí)現(xiàn)從連接到增刪靜態(tài)路由3.1 設(shè)備側(cè)開通 NETCONF 服務(wù)設(shè)備上的 NETCONF 服務(wù)默認(rèn)是不開的需要手動(dòng)開啟。華為交換機(jī)進(jìn)入系統(tǒng)視圖后執(zhí)行netconf server enable然后確認(rèn)用于 NETCONF 的 SSH 用戶存在并且服務(wù)類型包含 SSH。如果用戶權(quán)限不夠后面 edit-config 會(huì)被拒絕。H3C 的開啟命令稍有不同netconf ssh server enable這里建議用一個(gè)獨(dú)立的本地用戶專門做 NETCONF別拿管理員賬號(hào)到處用方便審計(jì)和定位問題。開啟之后可以用display netconf session之類的命令確認(rèn)會(huì)話狀態(tài)也可以從客戶端側(cè)直接發(fā) hello 測(cè)試連通性。3.2 Java 側(cè)輕量封裝一個(gè) NETCONF 客戶端Java 生態(tài)里 NETCONF 客戶端不像 HTTP 客戶端那么豐富OpenDaylight 的 netconf 模塊功能全但依賴太重對(duì)內(nèi)部工具來說有點(diǎn)殺雞用牛刀。我實(shí)際采用的是基于 JSch 自己封裝一個(gè)輕量客戶端核心邏輯并不復(fù)雜反正 NETCONF over SSH 本身就是一條 SSH subsystem 通道加 XML 報(bào)文。Maven 依賴只需要一個(gè)dependency groupIdcom.github.mwiede/groupId artifactIdjsch/artifactId version0.2.16/version /dependency連接和發(fā)送報(bào)文的核心邏輯可以這樣封裝public class NetconfSession { private Session sshSession; private ChannelSubsystem channel; private OutputStream out; private InputStream in; public void connect(String host, int port, String user, String password) throws Exception { JSch jsch new JSch(); sshSession jsch.getSession(user, host, port); sshSession.setPassword(password); sshSession.setConfig(StrictHostKeyChecking, no); sshSession.connect(30000); channel (ChannelSubsystem) sshSession.openChannel(subsystem); channel.setSubsystem(netconf); channel.connect(); out channel.getOutputStream(); in channel.getInputStream(); sendHello(); } private void sendHello() throws Exception { String hello ?xml version\1.0\ encoding\UTF-8\? hello xmlns\urn:ietf:params:xml:ns:netconf:base:1.0\ capabilities capabilityurn:ietf:params:netconf:base:1.0/capability /capabilities/hello]]]]; send(hello); readUntilDelimiter(); } public String sendRpc(String rpcXml) throws Exception { send(rpcXml ]]]]); return readUntilDelimiter(); } private void send(String data) throws Exception { out.write(data.getBytes(StandardCharsets.UTF_8)); out.flush(); } private String readUntilDelimiter() throws Exception { ByteArrayOutputStream buf new ByteArrayOutputStream(); byte[] b new byte[4096]; int delimiterHit 0; while (delimiterHit 5) { // 5 is ]] ] ]] length int len in.read(b); if (len 0) continue; buf.write(b, 0, len); String text buf.toString(UTF-8); int idx text.lastIndexOf(]]]]); if (idx 0) return text; } return buf.toString(UTF-8); } public void close() { if (channel ! null) channel.disconnect(); if (sshSession ! null) sshSession.disconnect(); } }封裝完成后業(yè)務(wù)代碼就變得很清爽拼 XML 報(bào)文調(diào) sendRpc拿到 rpc-reply 后解析結(jié)果。調(diào)試階段建議把所有發(fā)出的報(bào)文和返回的報(bào)文都打日志排查問題會(huì)輕松很多。3.3 三類 RPC 報(bào)文實(shí)戰(zhàn)模板查詢配置用 get-config以華為 ietf-routing 模型為例查靜態(tài)路由可以這樣拼rpc message-id101 xmlnsurn:ietf:params:xml:ns:netconf:base:1.0 get-config sourcerunning//source filter typesubtree routing xmlnsurn:ietf:params:xml:ns:yang:ietf-routing control-plane-protocols/ /routing /filter /get-config /rpc新增靜態(tài)路由用 edit-configoperation 是 mergerpc message-id102 xmlnsurn:ietf:params:xml:ns:netconf:base:1.0 edit-config targetrunning//target config routing xmlnsurn:ietf:params:xml:ns:yang:ietf-routing control-plane-protocols control-plane-protocol namestatic/name typestatic/type static-routes ipv4 route destination-prefix192.168.100.0/24/destination-prefix next-hop10.0.0.1/next-hop /route /ipv4 /static-routes /control-plane-protocol /control-plane-protocols /routing /config /edit-config /rpc刪除路由時(shí)我會(huì)刻意用 operationremove 而不是 delete。兩者的區(qū)別在于delete 對(duì)不存在的節(jié)點(diǎn)會(huì)直接報(bào)錯(cuò)remove 則是冪等的節(jié)點(diǎn)不存在也靜默成功。在自動(dòng)化腳本里冪等操作往往更安全重復(fù)執(zhí)行不會(huì)因?yàn)闋顟B(tài)不一致而中斷。如果目標(biāo)設(shè)備是 H3C報(bào)文要按它的私有模型來。新增一條路由大概是這樣的結(jié)構(gòu)rpc message-id103 xmlnsurn:ietf:params:xml:ns:netconf:base:1.0 edit-config targetrunning//target config xmlnshttp://www.h3c.com/netconf/config:1.0 RouteStatic IPv4 Route VrfIndex0/VrfIndex DestPrefix192.168.100.0/DestPrefix MaskLength24/MaskLength NextHopAddress10.0.0.1/NextHopAddress /Route /IPv4 /RouteStatic /config /edit-config /rpc注意 H3C 這個(gè)模型里目的地址和掩碼長(zhǎng)度是分開的華為的 ietf-routing 里則合并成了192.168.100.0/24這種寫法。字段合并方式的差異就是前面說的必須確認(rèn) YANG 模型的實(shí)際體現(xiàn)。4. 高頻問題、排查思路和避險(xiǎn)設(shè)計(jì)4.1 常見問題速查與定位方法整個(gè)過程中遇到的坑比較多我整理了一個(gè)速查表基本覆蓋了新手階段最容易碰到的幾類問題。現(xiàn)象可能原因排查與解決辦法連接超時(shí)設(shè)備 NETCONF 服務(wù)未開啟或 SSH 端口不通確認(rèn)設(shè)備側(cè)已執(zhí)行 netconf server enable用 telnet/ssh 測(cè)試連通認(rèn)證失敗用戶沒有 SSH 權(quán)限或密碼錯(cuò)誤單獨(dú)建 NETCONF 專用用戶確認(rèn)服務(wù)類型含 SSH發(fā)送 rpc 后無響應(yīng)報(bào)文沒以]]]]結(jié)尾打印原始報(bào)文檢查幀結(jié)束標(biāo)記rpc-reply 返回 errornamespace 寫錯(cuò)或節(jié)點(diǎn)路徑與設(shè)備模型不匹配用 get-schema 拉取設(shè)備實(shí)際 YANG對(duì)照節(jié)點(diǎn)路徑配置沒生效但也沒報(bào)錯(cuò)節(jié)點(diǎn)路徑錯(cuò)誤被設(shè)備靜默忽略或未 commit開啟設(shè)備 NETCONF 會(huì)話日志查看完整處理記錄message-id 重復(fù)長(zhǎng)連接里復(fù)用了相同 id用一個(gè)自增字段生成 message-id保證會(huì)話內(nèi)唯一關(guān)于模擬器我也多說一句。華為 eNSP 和 H3C 的 HCL 模擬器對(duì) NETCONF 的支持并不理想很多 YANG 能力在模擬環(huán)境里不完整經(jīng)常出現(xiàn)報(bào)文格式看起來對(duì)、但設(shè)備不理你的情況。有條件盡量用真機(jī)或者直接在當(dāng)前網(wǎng)絡(luò)的測(cè)試交換機(jī)上驗(yàn)證。真機(jī)上跑通了再挪到模擬器里復(fù)現(xiàn)問題反而更容易定位。4.2 把誤操作風(fēng)險(xiǎn)壓到最低靜態(tài)路由直接影響流量轉(zhuǎn)發(fā)這類工具最怕的不是功能實(shí)現(xiàn)不了而是誤操作。我的建議是至少做三層防護(hù)。第一層是變更前自動(dòng)備份配置?,F(xiàn)有配置先跑一遍 get-config 落到本地或數(shù)據(jù)庫這樣出問題能快速 diff 出哪里變了。第二層是增加變更確認(rèn)機(jī)制尤其是刪除路由這種操作調(diào)用方必須顯式傳入設(shè)備 IP、路由網(wǎng)段、下一跳這三個(gè)參數(shù)server 端做嚴(yán)格校驗(yàn)防止拿錯(cuò)環(huán)境或者漏傳參數(shù)導(dǎo)致誤刪。第三層是提交時(shí)機(jī)。華為設(shè)備支持 candidate 配置庫的時(shí)候優(yōu)先改 candidate檢查無誤再 commit相當(dāng)于數(shù)據(jù)庫先開事務(wù)、確認(rèn)后再提交。如果設(shè)備只支持 running 直改就在代碼層面做一個(gè)簡(jiǎn)單的改前快照 失敗回滾邏輯實(shí)測(cè)下來比裸調(diào) edit-config 穩(wěn)得多。我個(gè)人的體會(huì)是NETCONF 這種操作接口代碼寫起來其實(shí)不難難的是把邊界情況想清楚。設(shè)備側(cè)不開服務(wù)、模型不匹配、報(bào)文缺結(jié)束符、message-id 沖突這些坑每一個(gè)都真實(shí)存在而且都是文檔里不會(huì)寫明的。把這篇文章里的幾個(gè)關(guān)鍵點(diǎn)過一遍你再去連設(shè)備基本就能一次跑通。真到了生產(chǎn)環(huán)境記得把設(shè)備返回的原始報(bào)文完整記錄下來這是排查一切問題的底牌。本文還有配套的精品資源點(diǎn)擊獲取