在多节点运维场景中,我们经常需要分析主机与外部网络的连接情况。
本文介绍一种方法,通过 Ansible + Netstat + OpenPyXL 批量收集受控主机的外部连接(仅保留特定网段),并将结果导出为 Excel 报表。
特点如下:
🔍 基于
netstat,兼容老版本系统;✅ 自动识别 IPv6 映射地址(如
::ffff:192.168.1.1);📊 输出格式清晰:主机 IP、外部 IP 数量、外部 IP 列表;
📄 即使某台主机无连接,也会显示在报表中;
🧩 纯 Ansible 实现,无需人工拼 JSON。
一、场景背景
在日常巡检或安全审计中,常需要了解每台主机当前的外部连接情况,例如哪些 IP 与目标网段(如 100.*)有交互。
传统做法是逐台登录主机执行 netstat,再人工整理结果,这样效率低且容易出错。
本文提供一个更自动化的方案:
使用 Ansible 在批量主机上执行
netstat;提取特定网段的连接;
在控制节点上汇总、去重;
最终生成带合并单元格的
.xlsx报表。
二、实现思路
采集层:在每台主机执行
netstat -ntu,抓取所有已建立的连接;过滤层:仅保留符合条件的外部 IPv4 地址(例如
100.*),并过滤掉已知例外;汇总层:Ansible 在控制端汇总所有主机结果,转为 JSON;
展示层:使用
openpyxl将 JSON 渲染为 Excel 文件,自动合并单元格。
Excel 格式如下:
三、核心命令逻辑
netstat 输出示例:
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 10.0.1.10:5000 100.58.70.170:1556 ESTABLISHED
tcp 0 0 10.0.1.10:4200 ::ffff:100.58.230.144:53217 ESTABLISHED
可以看到有 IPv6 映射格式 ::ffff:100.58.230.144:53217,
我们只需提取其中的 IPv4 部分。
过滤命令如下:
ansible.builtin.shell: |
set -o pipefail
EXCLUDE_RE="{{ excluded_ips | map('regex_escape') | join('|') }}"
netstat -ntu 2>/dev/null \
| awk 'NR>2 && ($6=="ESTABLISHED" || $6=="ESTAB"){print $5}' \
| sed -E 's/^::ffff:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):[0-9]+$/\1/; s/:([0-9]+)$//' \
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' \
| grep -v -E "^($EXCLUDE_RE)$" \
| grep -E '^100\.' \
| sort -u || true
四、完整 Playbook 示例
---
- name: Collect external connections
hosts: all
gather_facts: no
vars:
excluded_ips:
- 127.0.0.7
tasks:
- name: Collect established peer IPv4 via netstat
ansible.builtin.shell: |
set -o pipefail
EXCLUDE_RE="{{ excluded_ips | map('regex_escape') | join('|') }}"
netstat -ntu 2>/dev/null \
| awk 'NR>2 && ($6=="ESTABLISHED" || $6=="ESTAB"){print $5}' \
| sed -E 's/^::ffff:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):[0-9]+$/\1/; s/:([0-9]+)$//' \
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' \
| grep -v -E "^($EXCLUDE_RE)$" \
| grep -E '^100\.' \
| sort -u || true
args:
executable: /bin/bash
register: ext_ips_cmd
changed_when: false
- name: Save facts (include empty)
ansible.builtin.set_fact:
ext_ips: "{{ ext_ips_cmd.stdout_lines | default([]) }}"
ext_ip_count: "{{ (ext_ips_cmd.stdout_lines | default([])) | length }}"
host_display_ip: "{{ hostvars[inventory_hostname].ansible_host | default(inventory_hostname) }}"
- name: Build Excel report on controller
hosts: localhost
gather_facts: no
vars:
json_path: "./external_links.json"
xlsx_path: "./external_links.xlsx"
tasks:
- name: Combine results
ansible.builtin.set_fact:
collected: >-
{{ groups['all'] | map('extract', hostvars, ['host_display_ip', 'ext_ip_count', 'ext_ips'])
| map('combine', [{'host': None, 'count': None, 'ips': None}])
| list }}
- name: Write JSON
ansible.builtin.copy:
dest: "{{ json_path }}"
mode: '0644'
content: "{{ collected | to_nice_json }}"
- name: Generate Excel
ansible.builtin.copy:
dest: "./build_xlsx.py"
mode: '0755'
content: |
import json, sys
from openpyxl import Workbook
from openpyxl.styles import Alignment, Font
from openpyxl.utils import get_column_letter
jpath, xpath = sys.argv[1], sys.argv[2]
data = json.load(open(jpath, encoding='utf-8'))
wb = Workbook()
ws = wb.active
ws.title = "external_links"
headers = ["受控主机IP", "外部链接唯一IP个数", "外部链接IP(逐行)"]
ws.append(headers)
for c in range(1, len(headers)+1):
cell = ws.cell(row=1, column=c)
cell.font = Font(bold=True)
cell.alignment = Alignment(horizontal="center", vertical="center")
row = 2
for item in data:
host = item.get("host", "")
ips = item.get("ips", []) or []
cnt = int(item.get("count", 0))
if not ips:
ws.append([host, cnt, ""])
row += 1
continue
start = row
for ip in ips:
ws.cell(row=row, column=3, value=ip)
ws.cell(row=row, column=3).alignment = Alignment(vertical="center")
row += 1
end = row - 1
ws.merge_cells(start_row=start, start_column=1, end_row=end, end_column=1)
ws.merge_cells(start_row=start, start_column=2, end_row=end, end_column=2)
ws.cell(row=start, column=1, value=host)
ws.cell(row=start, column=2, value=cnt)
widths = {1: 18, 2: 18, 3: 24}
for c, w in widths.items():
ws.column_dimensions[get_column_letter(c)].width = w
wb.save(xpath)
- name: Build XLSX
ansible.builtin.command:
cmd: python3 ./build_xlsx.py {{ json_path }} {{ xlsx_path }}
五、执行结果
执行:
ansible-playbook -i inventory.ini collect_external_conns.yml输出:
external_links.json:原始数据;external_links.xlsx:可直接打开查看的 Excel 报表。
即使部分主机没有外部连接,也会在报表中显示,如:
六、总结
使用
netstat兼容性好,适用于老旧系统;::ffff:IPv6 映射需特别处理,否则会丢数据;Excel 通过
openpyxl自动合并单元格,阅读更直观;结果即使为空,也能完整展示所有主机,适合定期巡检或审计输出。
✅ 本方案结构清晰、兼容性强,可直接复用于运维巡检或安全合规报告中。
复制整段 YAML 后执行,即可快速生成企业级外部连接报表。