DDetB.Log
article thumbnail

이 글은 네이버 카페 네트워크 전문가 따라잡기에 작성했던 글입니다.

---

파이썬을 통해 네트워크 자동화 하시는 분들이 많이 계시고, 새로이 입문해보시려는 분들도 꾸준히 계실거라 생각합니다. 파이썬을 통해 자동화를 하는것이 엔서블에 비하면 더 빠르게 동작한다지만, 관리하는 장비 수량이 늘어날수록 느려지는 것은 어쩔수 없겠죠. 이를 해결(?)하기 위한 라이브러리 소개 및 간단 데모를 해볼까 해서 글을 남겨봅니다.

"다른 라이브러리로 자동화를 했기 때문에 이미 편하고 난 이정도 속도면 충분히 만족스러운데?" 라고 하신다면.. 이런것도 있구나 정도로 봐주시면 좋을 것 같습니다.

 

Agenda

자동화 라이브러리 소개에 앞서 비동기 프로그래밍에 대해 살짝 언급한 후에, 소개하려고 하는 Scrapli에 대한 이야기. 마지막으로는 Scrapli 데모순으로 진행됩니다.

- Async/Await

- Scrapli

- Demo

Async/Await

앞서 언급한 속도를 위해, 혹은 연산에 있어 여러 코어/스레드를 사용하도록하여 효율적으로 동작시키위한 방법을은 이미 있습니다. 이는 멀티프로세스/멀티스레드를 사용하는 방식과 비동기 프로그래밍(Asynchronous) 입니다. 이 중 오늘 언급할 부분은 비동기(Asynchronous) 프로그래밍입니다.

아래는 위키피디아에서 발췌한 설명입니다.

---

- Async/Await (https://ko.wikipedia.org/wiki/Async/await)

컴퓨터 프로그래밍에서 async/await 패턴은 비동기, 비차단 기능이 일반 동기 기능과 유사한 방식으로 구조화되도록 하는 많은 프로그래밍 언어의 구문 기능 구현이다. 이는 의미상 코루틴(coroutine)의 개념과 관련이 있으며 종종 유사한 기술을 사용하여 구현되며, 주로 약속이나 약속으로 표시되는 유사한 데이터 구조에서 장기(long-running) 실행 비동기 작업이 완료되기를 기다리는 동안 프로그램이 다른 코드를 실행할 수 있는 기회를 제공하기 위한 것이다.

---

쉽게 말해 Synchronous는 하나의 작업이 완전히 끝난 후에 다른작업을 시작한다면, Asynchronous 는 응답 유무에 상관없이 요청을 보낸 후 응답을 기다리는 형태입니다. 아래 그림을 보시면 비동기방식이 왜 빠른지에 대해 이해하실 수 있으실겁니다.

 

Scrapli

네트워크 자동화를 위한 파이썬 라이브러리들이 이미 많이 존재합니다.

- ssh 연결의 기본이 되는 paramiko

- netconf 를 위한 ncclient

- 네트워크 자동화에 초점이 맞춰진 netmiko, napalm

- 아웃풋 파싱을 위한 textfsm

등이 있으며, 이외에도 각 벤더별 자동화 라이브러리들이 존재하죠.

Scrapli는 파이썬의 Asyncio를 통해 Async를 지원하는 네트워크 자동화 라이브러리 중 하나입니다. Scrapli는 Netmiko 와 같은 포지션의 라이브러리라고 할 수 있습니다.

https://github.com/carlmontanari/scrapli

 

GitHub - carlmontanari/scrapli: Fast, flexible, sync/async, Python 3.7+ screen scraping client specifically for network devices

Fast, flexible, sync/async, Python 3.7+ screen scraping client specifically for network devices - GitHub - carlmontanari/scrapli: Fast, flexible, sync/async, Python 3.7+ screen scraping client spec...

github.com

 

사실, Netmiko를 통해 Async 구현이 가능한지를 확인하던 중, 지원하지 않는다는 것을 확인했습니다. (이전에 Netmiko base의 async를 지원하는 프로젝트가 있었으나, 현재는 유지되지 않고 있습니다.)

Netmiko github에서 해당 개발자인 Kirk Byers 가 언급한 내용을 보면, 다중 연결을 위한 표준 방식은 멀티 쓰레드나 멀티 프로세싱이라고 합니다. 즉, Netmiko에서 Async 지원을 기대하긴 어려워 보입니다.

공식적으로 지원하는 드라이버는 Cisco IOS-XE/NX-OS/IOS-XR, Arista EOS, Juniper JunOS 이며, 지원하지 않는 OS는 scrapli community 를 통해 다른사용자가 만든 드라이버를 사용하거나, 직접 작성할 수 있습니다.

Demo

Netmiko(Sync) vs Scrapli(Async)

동기와 비동기방식에 대한 차이를 보기위해서 Scrapli의 동기방식과 비동기방식을 비교하는것이 더 정확할 수 있겠지만, Netmiko를 많이 사용하고 계실거라 생각하여 Netmiko와 비교하는 테스트를 진행했습니다.

demo.py

테스트에 사용한 코드입니다. 준비된 장비가 없어 AsyncGenericDriver 드라이버를 통해 일반 우분투 리눅스에 접속하는 형태로 테스트 했습니다. 네트워크 장비를 대상으로 하는 경우에는 각 벤더 드라이버로 변경해서 사용하면 됩니다.

실제 장비를 다수를 사용하지는 않고, 한대의 타겟을 여러번 접속하도록 HOST_NUM 을 변경하며 여러장비에 접속하는 시나리오를 시뮬레이션 했습니다. Netmiko와 Scrapli 전체 사이클 후 각각 소요된 시간을 출력하는 코드입니다.

from timeit import Timer

import asyncio

from netmiko import ConnectHandler
from scrapli.driver.generic import AsyncGenericDriver

HOST_NUM = 100
REPEAT = 1
COMMAND = "uname -a"

host_info = {"host": "YOUR_HOSTNAME", "port": 22, "user": "YOUR_USERNAME", "password": "YOUR_PASSWORD"}
hosts = [host_info for i in range(HOST_NUM)]

def netmiko_test():
    for i in range(len(hosts)):
        entry = hosts[i]
        dev_connect = {
            "device_type": "autodetect",
            "host": entry["host"],
            "port": entry.get("port"),
            "username": entry.get("user"),
            "password": entry.get("password"),
        }

        net_connect = ConnectHandler(**dev_connect)
        output = net_connect.send_command(COMMAND, use_textfsm=False)
        net_connect.disconnect()
        # print(output)

async def scrapli_conn(host="", port=22, user="", password=""):
    dev_connect = {
        "host": host,
        "auth_username": user,
        "auth_password": password,
        "port": port,
        "auth_strict_key": False,
        "transport": "asyncssh",
    }

    async with AsyncGenericDriver(**dev_connect) as conn:
        output = await conn.send_command(COMMAND)
        # print(output.result)

async def scrapli_test(func):
    tasks = []
    for i in range(len(hosts)):
        entry = hosts[i]
        tasks.append(
            func(
                entry["host"],
                port=entry.get("port"),
                user=entry.get("user"),
                password=entry.get("password"),
            )
        )

    await asyncio.gather(*tasks)

if __name__ == "__main__":
    print(f"Connection test will preceed with {len(hosts)} hosts")

    print("netmiko proceeding...")
    t = Timer("""netmiko_test()""", globals=globals())
    netmiko_time = t.timeit(number=REPEAT)

    print("scrapli proceeding...")
    t = Timer("""asyncio.run(scrapli_test(scrapli_conn))""", globals=globals())
    scrapli_time = t.timeit(number=REPEAT)

    print(f"\nResult of {REPEAT} times repeat:")
    print("- netmiko: {:.3f}s".format(netmiko_time))
    print("- scrapli: {:.3f}s".format(scrapli_time))

결과

- 1 host

호스트 1대에 대해서 테스트한 결과입니다. 역시나 한대는 별반 차이가 없네요. 오히려 미세하게 netmiko가 빠릅니다

 

- 10 hosts

다음은 10대의 호스트입니다. 4초와 1초 점점 차이가 벌어지네요.

 

- 100 hosts

마지막 100대의 호스트에 대한 비교입니다. 40초와 6초.. 어마어마한 차이죠? 1000대 이상 넘어가게되면 얼마나 차이가 벌어질까요?

 

위 시험은 명령어 한줄 입력한 결과를 가지고 시간을 측정한 것이기 때문에, 실제 운영하는 망에서는 더 차이가 클것으로 예상됩니다. 자동화 하는데 있어서 속도가 전부는 아니지만, 누군가는 속도가 중요한 작업을 할 수도 있을것 같다는 생각으로 라이브러리 소개와 함께 테스트 결과를 공유합니다. 모쪼록 필요하신 분들은 많은 도움이 되시길 바랍니다!

profile

DDetB.Log

@DDetMok

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!