[네트워크 자동화 Ep.4] 엑셀 정량화 리포트 — 텍스트 더미를 판정된 점검표로

📊 [네트워크 자동화 Ep.4] 엑셀 정량화 리포트 — 텍스트 더미를 판정된 점검표로

여기서부터 SecureCRT가 따라올 수 없다 — 수집을 넘어 “판정”으로

#엑셀리포트 #openpyxl #정량화 #조건판정 #기종그룹 #운영자동화

📋 이 글의 흐름

  • 1. 왜 텍스트 로그로는 부족한가
  • 2. 기종별 그룹으로 인벤토리 재구성
  • 3. 출력에서 숫자 뽑기 — 파싱 규칙
  • 4. 색상 입힌 엑셀 생성 플레이북 (전체 코드)
  • 5. 완성된 점검표 미리보기
  • 6. 예상되는 시행착오 (실습 전 미리 대비)
⚠ 이 편의 성격

Ep.0~3은 내가 실제로 실습 완료한 내용이다. 이 Ep.4부터는 설계와 예상 과정이다. 즉 검증된 파싱 로직과 동작하는 코드를 제시하되, 실제 운영 데이터에 적용할 때 마주칠 시행착오를 미리 예측해서 함께 적는다. 앞선 편들에서 환경·기종 차이로 매번 막혔듯, 이 단계도 한 번에 안 될 가능성이 높기 때문이다.

1. 왜 텍스트 로그로는 부족한가

Ep.3에서 14대 점검 결과를 텍스트 파일로 모았다. 그런데 그 파일을 매일 14개씩 열어 눈으로 읽는다면, 자동화의 의미가 절반밖에 안 산다. 진짜 목표는 “어느 장비가 문제인지 한눈에 보이는 판정표”다.

🎯 핵심 차이 — 수집 vs 판정

SecureCRT 로그는 “텍스트”에서 끝난다. Ansible은 그 텍스트를 조건으로 판정해서 “정상/주의/위험”을 색으로 표시한 엑셀로 만든다. CPU 90%↑면 빨강, 온도 정상이면 초록. 사람은 “종합” 칸만 보면 된다. 이것이 텍스트 로그로는 불가능한 영역이다.

2. 기종별 그룹으로 인벤토리 재구성

Ep.3에서 L3(4500)와 L2(3650)의 명령 차이가 드러났다. 이제 인벤토리를 그룹으로 나눠, 그룹별로 다른 명령을 줄 수 있게 한다.

inventory.yml — 그룹 분리판
switches:
  children:                          # 하위 그룹들
    l3_core:                         # 4500 계열 (environment 명령 다름)
      hosts:
        sw_10_20_0_2:  { ansible_host: 10.20.0.2 }
        sw_10_20_0_3:  { ansible_host: 10.20.0.3 }
    l2_access:                       # 3650 계열 (environment all 됨)
      hosts:
        sw_10_20_0_31: { ansible_host: 10.20.0.31 }
        sw_10_20_0_32: { ansible_host: 10.20.0.32 }
        # ... 나머지 L2 장비
  vars:                              # 전체 공통 접속 설정
    ansible_connection: network_cli
    ansible_network_os: cisco.ios.ios
    ansible_port: 2002
    ansible_user: "{{ sw_user }}"
    ansible_password: "{{ sw_pass }}"
    ansible_become: yes
    ansible_become_method: enable
    ansible_become_password: "{{ sw_enable }}"

3. 출력에서 숫자 뽑기 — 파싱 규칙

판정을 하려면 텍스트에서 숫자/상태를 추출해야 한다. 아래는 실제 수집 출력에 맞춰 검증한 정규식 규칙이다.

항목추출 규칙판정 기준(예)
CPUfive minutes:\s*(\d+)% 의 최대값70%↑ 주의 / 90%↑ 위험
환경‘OK’/’Good’ 없는 라인 수1개↑ 경고
로그%\w+-3-, %\w+-4- 카운트심각도별 집계

4. 색상 입힌 엑셀 생성 플레이북 (전체 코드)

두 부분으로 나뉜다. ① 점검 데이터 수집(플레이북) ② 수집 데이터를 파싱해 엑셀 생성(Python 스크립트). 엑셀 생성에는 openpyxl 라이브러리를 쓴다.

사전 준비 — openpyxl 설치
pip install openpyxl --break-system-packages
make_report.yml — 수집 + JSON 저장
L2/L3 그룹에 각각 맞는 명령을 보내고, 결과를 파싱하기 쉬운 JSON으로 저장한다.
- name: 점검 데이터 수집 후 JSON 저장
  hosts: switches
  gather_facts: no
  tasks:
    # L2/L3 공통으로 받는 명령
    - name: 공통 점검 명령
      cisco.ios.ios_command:
        commands:
          - show processes cpu | include CPU
          - show inventory
          - show interfaces status
          - show logging | include %
      register: common
      ignore_errors: yes

    # L2(3650)만 show environment all
    - name: L2 환경 점검
      cisco.ios.ios_command:
        commands: ["show environment all"]
      register: env_l2
      ignore_errors: yes
      when: "'l2_access' in group_names"   # L2 그룹에서만 실행

    # 장비별 결과를 JSON 한 줄로 저장 (파싱용)
    - name: 결과 JSON 저장
      delegate_to: localhost
      copy:
        content: "{{ {'host': inventory_hostname, 'ip': ansible_host, 'common': common.stdout | default([]), 'env': env_l2.stdout | default([])} | to_json }}"
        dest: "./data_{{ inventory_hostname }}.json"
build_excel.py — JSON → 색상 엑셀
위에서 저장한 JSON들을 읽어 판정하고, 색상이 입혀진 엑셀 한 장을 만든다.
import json, glob, re
from openpyxl import Workbook
from openpyxl.styles import PatternFill, Font, Alignment

# 색상 정의
GREEN  = PatternFill("solid", fgColor="D4EDDA")
YELLOW = PatternFill("solid", fgColor="FFF3CD")
RED    = PatternFill("solid", fgColor="F8D7DA")
HEADER = PatternFill("solid", fgColor="1A3A5C")

wb = Workbook()
ws = wb.active
ws.title = "일일점검"

# 헤더 행
cols = ["장비", "IP", "CPU(5분)", "환경", "이상로그(sev3)", "이상로그(sev4)", "종합"]
ws.append(cols)
for c in range(1, len(cols)+1):
    cell = ws.cell(row=1, column=c)
    cell.fill = HEADER
    cell.font = Font(color="FFFFFF", bold=True)
    cell.alignment = Alignment(horizontal="center")

# 각 장비 JSON 처리
for f in sorted(glob.glob("data_*.json")):
    d = json.load(open(f, encoding="utf-8"))
    common = "\n".join(d.get("common", []))
    env    = "\n".join(d.get("env", []))

    # CPU 5분 평균 최대값
    cpu_vals = [int(x) for x in re.findall(r'five minutes:\s*(\d+)%', common)]
    cpu = max(cpu_vals) if cpu_vals else None

    # 환경 비정상 라인 (L3는 env가 비어있을 수 있음)
    env_bad = len([l for l in env.splitlines()
                   if l.strip() and 'OK' not in l and 'Good' not in l
                   and ('FAN' in l or 'POWER' in l or 'TEMP' in l)])

    # 로그 심각도 카운트
    sev3 = len(re.findall(r'%\w+-3-', common))
    sev4 = len(re.findall(r'%\w+-4-', common))

    # 행 추가
    row = [d["host"], d["ip"],
           f"{cpu}%" if cpu is not None else "-",
           "OK" if env_bad == 0 else f"{env_bad}건 이상",
           sev3, sev4, ""]
    ws.append(row)
    r = ws.max_row

    # CPU 셀 색칠
    if cpu is not None:
        ws.cell(r, 3).fill = RED if cpu >= 90 else YELLOW if cpu >= 70 else GREEN

    # 로그 sev4(MAC flap 등) 다량이면 경고
    ws.cell(r, 6).fill = RED if sev4 >= 50 else YELLOW if sev4 >= 10 else GREEN

    # 종합 판정
    warn = (cpu is not None and cpu >= 70) or env_bad > 0 or sev4 >= 50
    ws.cell(r, 7).value = "⚠ 점검" if warn else "✅ 정상"
    ws.cell(r, 7).fill = YELLOW if warn else GREEN

# 열 너비 자동
for col in ws.columns:
    width = max(len(str(c.value or "")) for c in col) + 4
    ws.column_dimensions[col[0].column_letter].width = width

wb.save("switch_daily_report.xlsx")
print("리포트 생성 완료: switch_daily_report.xlsx")
실행 순서
# 1) 데이터 수집
ansible-playbook -i inventory.yml make_report.yml \
  -e 'sw_user=admin sw_pass=P@ssw0rd!2030 sw_enable=En@bleKey'
# 2) 엑셀 생성
python3 build_excel.py
# 3) Windows 탐색기에서 \\wsl$\Ubuntu\home\사용자\net 으로 접근해 열기

5. 완성된 점검표 미리보기

위 스크립트가 만들어낼 엑셀의 모습이다 (예시 데이터).

장비IPCPU(5분)환경sev3sev4종합
CAMPUS-2F-L2-0110.20.0.526%OK0312⚠ 점검
CAMPUS-3F-L2-0710.20.0.578%OK02✅ 정상
CAMPUS-5F-L3-0110.20.0.212%OK00✅ 정상

“종합” 칸만 보면 14대 중 문제 장비가 즉시 보인다. Ep.3에서 발견한 MAC Flapping(sev4 312건)이 빨간 셀로 자동 부각된다. 수동 점검 1~2시간이 0분으로, 그리고 놓치던 이상이 색으로 튀어나온다.

6. 예상되는 시행착오 (실습 전 미리 대비)

🔮 예상 ① — 메모리 명령 불일치

Ep.3에서 show memory statistics가 거부됐듯, 메모리 항목을 추가하면 또 막힐 가능성이 크다. 실제 적용 전 장비에서 show memory ?로 유효 명령을 먼저 확인하고 그룹별로 다른 명령을 매핑해야 한다.

🔮 예상 ② — L3 환경 명령

4500 계열은 show environment all 대신 show environment status / show power로 나뉠 가능성이 높다. L3 그룹용 별도 task를 when: "'l3_core' in group_names" 조건으로 추가해야 완전해진다.

🔮 예상 ③ — 한글 엑셀 인코딩

openpyxl은 유니코드를 직접 다루므로 CSV처럼 깨질 일은 적지만, JSON 저장 시 to_json이 한글을 이스케이프할 수 있다. 읽을 때 encoding="utf-8"을 명시(위 코드 반영)하면 해결된다.

🔮 예상 ④ — 정규식이 출력 변형에 깨짐

Ep.2에서 말한 “CLI 파싱의 취약점”이 여기서 현실화된다. IOS 버전이 다르면 “five minutes” 문구나 띄어쓰기가 미묘하게 달라 정규식이 빗나갈 수 있다. 장비군이 다양하면 버전별로 규칙을 분기하거나, 장기적으로 NETCONF 전환을 검토해야 한다.

💡 그래도 이 단계를 거쳐야 하는 이유

위 예상 시행착오들은 모두 “출력 형식을 먼저 봐야 파싱 규칙이 정해진다”는 하나의 원칙으로 수렴한다. 그래서 Ep.3의 원시 수집(텍스트 저장)을 먼저 하고, 그 출력을 본 뒤 이 Ep.4의 판정 규칙을 짜는 순서가 정답이다. 안 보고 규칙부터 짜면 헛다리를 짚는다.