📊 [네트워크 자동화 Ep.4] 엑셀 정량화 리포트 — 텍스트 더미를 판정된 점검표로
여기서부터 SecureCRT가 따라올 수 없다 — 수집을 넘어 “판정”으로
📋 이 글의 흐름
- 1. 왜 텍스트 로그로는 부족한가
- 2. 기종별 그룹으로 인벤토리 재구성
- 3. 출력에서 숫자 뽑기 — 파싱 규칙
- 4. 색상 입힌 엑셀 생성 플레이북 (전체 코드)
- 5. 완성된 점검표 미리보기
- 6. 예상되는 시행착오 (실습 전 미리 대비)
Ep.0~3은 내가 실제로 실습 완료한 내용이다. 이 Ep.4부터는 설계와 예상 과정이다. 즉 검증된 파싱 로직과 동작하는 코드를 제시하되, 실제 운영 데이터에 적용할 때 마주칠 시행착오를 미리 예측해서 함께 적는다. 앞선 편들에서 환경·기종 차이로 매번 막혔듯, 이 단계도 한 번에 안 될 가능성이 높기 때문이다.
1. 왜 텍스트 로그로는 부족한가
Ep.3에서 14대 점검 결과를 텍스트 파일로 모았다. 그런데 그 파일을 매일 14개씩 열어 눈으로 읽는다면, 자동화의 의미가 절반밖에 안 산다. 진짜 목표는 “어느 장비가 문제인지 한눈에 보이는 판정표”다.
SecureCRT 로그는 “텍스트”에서 끝난다. Ansible은 그 텍스트를 조건으로 판정해서 “정상/주의/위험”을 색으로 표시한 엑셀로 만든다. CPU 90%↑면 빨강, 온도 정상이면 초록. 사람은 “종합” 칸만 보면 된다. 이것이 텍스트 로그로는 불가능한 영역이다.
2. 기종별 그룹으로 인벤토리 재구성
Ep.3에서 L3(4500)와 L2(3650)의 명령 차이가 드러났다. 이제 인벤토리를 그룹으로 나눠, 그룹별로 다른 명령을 줄 수 있게 한다.
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. 출력에서 숫자 뽑기 — 파싱 규칙
판정을 하려면 텍스트에서 숫자/상태를 추출해야 한다. 아래는 실제 수집 출력에 맞춰 검증한 정규식 규칙이다.
| 항목 | 추출 규칙 | 판정 기준(예) |
|---|---|---|
| CPU | five minutes:\s*(\d+)% 의 최대값 | 70%↑ 주의 / 90%↑ 위험 |
| 환경 | ‘OK’/’Good’ 없는 라인 수 | 1개↑ 경고 |
| 로그 | %\w+-3-, %\w+-4- 카운트 | 심각도별 집계 |
4. 색상 입힌 엑셀 생성 플레이북 (전체 코드)
두 부분으로 나뉜다. ① 점검 데이터 수집(플레이북) ② 수집 데이터를 파싱해 엑셀 생성(Python 스크립트). 엑셀 생성에는 openpyxl 라이브러리를 쓴다.
pip install openpyxl --break-system-packages
- 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"
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. 완성된 점검표 미리보기
위 스크립트가 만들어낼 엑셀의 모습이다 (예시 데이터).
| 장비 | IP | CPU(5분) | 환경 | sev3 | sev4 | 종합 |
|---|---|---|---|---|---|---|
| CAMPUS-2F-L2-01 | 10.20.0.52 | 6% | OK | 0 | 312 | ⚠ 점검 |
| CAMPUS-3F-L2-07 | 10.20.0.57 | 8% | OK | 0 | 2 | ✅ 정상 |
| CAMPUS-5F-L3-01 | 10.20.0.2 | 12% | OK | 0 | 0 | ✅ 정상 |
“종합” 칸만 보면 14대 중 문제 장비가 즉시 보인다. Ep.3에서 발견한 MAC Flapping(sev4 312건)이 빨간 셀로 자동 부각된다. 수동 점검 1~2시간이 0분으로, 그리고 놓치던 이상이 색으로 튀어나온다.
6. 예상되는 시행착오 (실습 전 미리 대비)
Ep.3에서 show memory statistics가 거부됐듯, 메모리 항목을 추가하면 또 막힐 가능성이 크다. 실제 적용 전 장비에서 show memory ?로 유효 명령을 먼저 확인하고 그룹별로 다른 명령을 매핑해야 한다.
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의 판정 규칙을 짜는 순서가 정답이다. 안 보고 규칙부터 짜면 헛다리를 짚는다.
