## 远程服务器监控方案:node_exporter与内网穿透的集成实践
在远程办公和分布式系统运维日益普及的今天,如何有效监控分布在不同网络环境的服务器成为技术团队面临的共同挑战。本文将详细介绍如何通过node_exporter与cpolar内网穿透工具的结合,构建一个安全、高效的远程服务器监控方案,使运维人员能够随时随地掌握服务器运行状态。
### 远程监控的技术挑战
传统服务器监控方案通常依赖本地网络访问或复杂的VPN配置,在远程办公场景下面临诸多限制。直接暴露监控端口到公网存在安全风险,而VPN方案又增加了网络复杂性和维护成本。node_exporter作为Prometheus生态中广泛使用的节点监控工具,提供了丰富的系统指标,但其默认设计面向内网环境。
```bash
# node_exporter基础安装与运行
wget https://github.com/prometheus/node_exporter/releases/download/v1.6.0/node_exporter-1.6.0.linux-amd64.tar.gz
tar xvfz node_exporter-1.6.0.linux-amd64.tar.gz
cd node_exporter-1.6.0.linux-amd64
./node_exporter --web.listen-address=":9100"
```
上述配置仅在本地网络可访问,无法满足远程监控需求。
### 监控系统架构设计
本方案采用分层架构:数据采集层(node_exporter)、安全隧道层(cpolar)、数据聚合层(Prometheus)和可视化层(Grafana)。核心思想是在不改变服务器网络配置的前提下,通过加密隧道安全地暴露监控数据。
```yaml
# docker-compose.yml - 完整监控栈配置
version: '3.8'
services:
node_exporter:
image: quay.io/prometheus/node-exporter:latest
container_name: node_exporter
restart: unless-stopped
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
- '--web.listen-address=:9100'
network_mode: "host"
pid: "host"
cpolar:
image: cpolar/cpolar:latest
container_name: cpolar_tunnel
restart: unless-stopped
volumes:
- ./cpolar.yml:/etc/cpolar/cpolar.yml
- ./data:/data
environment:
- CPOLAR_AUTH_TOKEN=${CPOLAR_TOKEN}
command: ["serve", "--config=/etc/cpolar/cpolar.yml"]
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=30d'
ports:
- "9090:9090"
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
ports:
- "3000:3000"
volumes:
prometheus_data:
grafana_data:
```
### node_exporter深度配置与优化
基础监控往往无法满足生产需求,需要根据具体场景进行深度配置。
```yaml
# node_exporter自定义配置
# config.yml - 启用特定收集器并设置参数
---
collectors:
enabled:
- cpu
- meminfo
- diskstats
- filesystem
- loadavg
- netdev
- netstat
- stat
- time
- systemd
- process
collector:
cpu:
enabled: true
flags: "--collector.cpu.info"
filesystem:
enabled: true
mount-points-exclude: "^/(sys|proc|dev|host|etc)($$|/)"
fs-types-exclude: "^tmpfs$|^devtmpfs$|^overlay$|^aufs$"
diskstats:
enabled: true
devices-exclude: "^loop[0-9]+$|^dm-[0-9]+$"
netdev:
enabled: true
devices-exclude: "^lo$|^docker[0-9]+$|^veth[0-9a-f]+$"
process:
enabled: true
process-names: ["nginx", "mysql", "redis", "docker"]
telemetry:
max-requests: 40
enable-openmetrics: true
web:
listen-address: ":9100"
telemetry-path: "/metrics"
max-requests: 40
web-config-file: "/etc/node_exporter/web.yml"
# web.yml - TLS和安全配置
tls_server_config:
cert_file: /etc/ssl/node_exporter.crt
key_file: /etc/ssl/node_exporter.key
client_auth_type: RequireAndVerifyClientCert
client_ca_file: /etc/ssl/ca.crt
basic_auth_users:
prometheus: "$2y$10$hashed_password_here"
```
启动脚本示例:
```bash
#!/bin/bash
# start_node_exporter.sh
CONFIG_DIR="/etc/node_exporter"
LOG_DIR="/var/log/node_exporter"
DATA_DIR="/var/lib/node_exporter"
# 创建必要目录
mkdir -p ${CONFIG_DIR} ${LOG_DIR} ${DATA_DIR}
# 生成TLS证书(如需)
if [ ! -f "${CONFIG_DIR}/ssl/node_exporter.crt" ]; then
mkdir -p ${CONFIG_DIR}/ssl
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
-keyout ${CONFIG_DIR}/ssl/node_exporter.key \
-out ${CONFIG_DIR}/ssl/node_exporter.crt \
-subj "/C=CN/ST=Beijing/L=Beijing/O=Company/CN=node_exporter.local"
fi
# 创建systemd服务
cat > /etc/systemd/system/node_exporter.service << EOF
[Unit]
Description=Node Exporter
After=network.target
[Service]
Type=simple
User=node_exporter
Group=node_exporter
ExecStart=/usr/local/bin/node_exporter \
--web.config.file=${CONFIG_DIR}/web.yml \
--collector.textfile.directory=${DATA_DIR}/textfile_collector \
--collector.processes \
--collector.systemd \
--collector.perf \
--web.listen-address=127.0.0.1:9100 \
--web.max-requests=40 \
--log.level=info
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=node_exporter
[Install]
WantedBy=multi-user.target
EOF
# 创建专用用户
useradd -rs /bin/false node_exporter
# 设置权限
chown -R node_exporter:node_exporter ${CONFIG_DIR} ${LOG_DIR} ${DATA_DIR}
chmod 600 ${CONFIG_DIR}/ssl/*
# 启动服务
systemctl daemon-reload
systemctl enable node_exporter
systemctl start node_exporter
echo "Node Exporter已启动,监听在 127.0.0.1:9100"
```
### cpolar安全隧道配置
cpolar的配置需要平衡安全性与便利性,以下为生产级配置示例:
```yaml
# cpolar.yml - 完整隧道配置
version: '2.0'
<"j3.a8k1.org.cn"><"n7.a8k1.org.cn"><"w9.a8k1.org.cn">
log:
level: info
file: /data/cpolar.log
max_size: 100
max_backups: 3
max_age: 30
tunnels:
node_exporter_monitor:
proto: http
addr: 127.0.0.1:9100
hostname: node-metrics
region: cn
scheme: https
auth:
type: basic
username: "${MONITOR_USER}"
password: "${MONITOR_PASSWORD}"
tls:
crt: /data/ssl/tunnel.crt
key: /data/ssl/tunnel.key
inspect:
enabled: true
timeout: 30s
compression: true
proxy_protocol: false
prometheus_remote_write:
proto: tcp
addr: 127.0.0.1:9090
remote_port: 29090
region: cn
tls:
crt: /data/ssl/prometheus.crt
key: /data/ssl/prometheus.key
access_control:
allowed_ips:
- 0.0.0.0/0
authtoken: "${CPOLAR_AUTH_TOKEN}"
update:
enabled: true
check_interval: 24h
heartbeat:
enabled: true
interval: 60s
timeout: 10s
```
隧道管理脚本:
```python
#!/usr/bin/env python3
# tunnel_manager.py
import yaml
import os
import logging
from datetime import datetime
from pathlib import Path
class TunnelManager:
def __init__(self, config_path="cpolar.yml"):
self.config_path = Path(config_path)
self.config = self.load_config()
self.setup_logging()
def load_config(self):
"""加载配置文件"""
with open(self.config_path, 'r') as f:
return yaml.safe_load(f)
def setup_logging(self):
"""配置日志"""
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(f"logs/tunnel_{datetime.now().strftime('%Y%m%d')}.log"),
logging.StreamHandler()
]
)
self.logger = logging.getLogger(__name__)
def generate_tunnel_config(self, server_info):
"""动态生成隧道配置"""
template = {
"proto": "http",
"addr": f"{server_info['local_ip']}:{server_info['port']}",
"hostname": f"{server_info['name']}-{server_info['env']}",
"region": "cn",
"auth": {
"type": "token",
"token": os.getenv("TUNNEL_TOKEN")
}
}
# 根据服务器类型添加特定配置
if server_info.get('sensitive', False):
template['scheme'] = "https"
template['tls'] = {
"crt": f"/data/ssl/{server_info['name']}.crt",
"key": f"/data/ssl/{server_info['name']}.key"
}
return template
def monitor_tunnel_health(self):
"""监控隧道健康状态"""
import requests
import json
tunnels = self.config.get('tunnels', {})
for name, config in tunnels.items():
try:
# 检查隧道状态
status_url = f"http://localhost:4040/api/tunnels/{name}"
response = requests.get(status_url, timeout=5)
if response.status_code == 200:
status_data = response.json()
self.logger.info(f"隧道 {name} 状态: {status_data['status']}")
# 记录指标
self.record_metrics(name, status_data)
else:
self.logger.warning(f"隧道 {name} 状态检查失败")
except Exception as e:
self.logger.error(f"检查隧道 {name} 时出错: {e}")
# 尝试重启隧道
self.restart_tunnel(name)
def record_metrics(self, tunnel_name, status_data):
"""记录隧道指标到node_exporter文本收集器"""
metrics_dir = Path("/var/lib/node_exporter/textfile_collector")
metrics_dir.mkdir(parents=True, exist_ok=True)
metrics_file = metrics_dir / f"cpolar_{tunnel_name}.prom"
metrics = []
metrics.append(f'cpolar_tunnel_up{{tunnel="{tunnel_name}"}} {1}')
# 解析隧道统计信息
if 'metrics' in status_data:
metrics.append(f'cpolar_tunnel_bytes_in{{tunnel="{tunnel_name}"}} '
f'{status_data["metrics"].get("bytes_in", 0)}')
metrics.append(f'cpolar_tunnel_bytes_out{{tunnel="{tunnel_name}"}} '
f'{status_data["metrics"].get("bytes_out", 0)}')
with open(metrics_file, 'w') as f:
f.write('\n'.join(metrics))
def restart_tunnel(self, tunnel_name):
"""重启指定隧道"""
self.logger.info(f"正在重启隧道: {tunnel_name}")
# 实际实现中会调用cpolar的API或命令行
import subprocess
try:
result = subprocess.run(
["cpolar", "restart", tunnel_name],
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0:
self.logger.info(f"隧道 {tunnel_name} 重启成功")
else:
self.logger.error(f"隧道重启失败: {result.stderr}")
except subprocess.TimeoutExpired:
self.logger.error(f"重启隧道 {tunnel_name} 超时")
if __name__ == "__main__":
manager = TunnelManager()
# 定期健康检查
import time
while True:
manager.monitor_tunnel_health()
time.sleep(300) # 每5分钟检查一次
```
### Prometheus远程采集配置
Prometheus需要配置通过cpolar隧道采集远程节点指标。
```yaml
# prometheus.yml - 远程采集配置
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
monitor: 'remote-cluster'
rule_files:
- "alerts.yml"
scrape_configs:
- job_name: 'remote_servers'
scheme: https
basic_auth:
username: '${MONITOR_USER}'
password: '${MONITOR_PASSWORD}'
tls_config:
ca_file: /etc/ssl/ca.crt
cert_file: /etc/ssl/prometheus.crt
key_file: /etc/ssl/prometheus.key
insecure_skip_verify: false
static_configs:
- targets:
- 'node-metrics.cpolar.io'
labels:
location: 'home-server'
env: 'production'
metrics_path: '/metrics'
scrape_timeout: 30s
relabel_configs:
- source_labels: [__address__]
target_label: instance
- source_labels: [__meta_dns_name]
target_label: hostname
<"g2.a8k1.org.cn"><"q5.a8k1.org.cn"><"t1.a8k1.org.cn">
- job_name: 'tunnel_metrics'
static_configs:
- targets: ['localhost:9100']
metrics_path: '/metrics'
params:
collect[]:
- cpolar
alerting:
alertmanagers:
- scheme: https
basic_auth:
username: '${ALERT_USER}'
password: '${ALERT_PASSWORD}'
static_configs:
- targets: ['alert.cpolar.io:9093']
# alerts.yml - 报警规则
groups:
- name: node_alerts
rules:
- alert: HighCpuUsage
expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 5m
labels:
severity: warning
annotations:
summary: "高CPU使用率 {{ $labels.instance }}"
description: "CPU使用率超过80% (当前值: {{ $value }}%)"
- alert: LowMemoryAvailable
expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 < 20
for: 5m
labels:
severity: warning
annotations:
summary: "低可用内存 {{ $labels.instance }}"
description: "可用内存低于20% (当前值: {{ $value }}%)"
- alert: TunnelDown
expr: cpolar_tunnel_up == 0
for: 2m
labels:
severity: critical
annotations:
summary: "监控隧道断开 {{ $labels.tunnel }}"
description: "监控隧道已断开超过2分钟"
```
### 安全加固与访问控制
安全是远程监控的首要考量,必须实施多层防护措施。
```bash
#!/bin/bash
# security_setup.sh
echo "配置监控系统安全..."
# 1. 防火墙规则
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow from 127.0.0.1 to any port 9100 comment 'node_exporter local'
sudo ufw --force enable
# 2. 创建监控专用网络
docker network create --subnet=172.20.0.0/24 monitoring_net
# 3. 配置审计日志
sudo apt install -y auditd
sudo cat > /etc/audit/rules.d/monitoring.rules << EOF
-w /etc/prometheus/prometheus.yml -p wa -k prometheus_config
-w /etc/node_exporter/ -p wa -k node_exporter
-w /var/lib/node_exporter/ -p wa -k node_metrics
EOF
sudo systemctl restart auditd
# 4. 配置监控用户权限
sudo groupadd monitoring
sudo useradd -r -s /bin/false -g monitoring prometheus
sudo useradd -r -s /bin/false -g monitoring node_exporter
# 5. 设置目录权限
sudo chown -R prometheus:monitoring /etc/prometheus
sudo chown -R node_exporter:monitoring /etc/node_exporter
sudo chmod 750 /etc/prometheus /etc/node_exporter
# 6. 配置SELinux/AppArmor
if [ -f /etc/apparmor.d/docker ]; then
sudo cat >> /etc/apparmor.d/docker << 'EOF'
network inet,
network inet6,
capability net_admin,
capability net_raw,
EOF
sudo systemctl restart apparmor
fi
echo "安全配置完成"
```
### Grafana仪表板配置
通过Grafana实现监控数据的可视化展示。
```json
// dashboard.json - 服务器监控仪表板
{
"dashboard": {
"title": "远程服务器监控",
"tags": ["node_exporter", "cpolar", "monitoring"],
"timezone": "browser",
"panels": [
{
"title": "CPU使用率",
"type": "graph",
"targets": [
{
"expr": "100 - (avg by(instance) (irate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
"legendFormat": "{{instance}}"
}
],
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}
},
{
"title": "内存使用",
"type": "stat",
"targets": [
{
"expr": "(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100",
"legendFormat": "使用率"
}
],
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 8}
},
{
"title": "隧道状态",
"type": "table",
"targets": [
{
"expr": "cpolar_tunnel_up",
"instant": true,
"legendFormat": "隧道状态"
}
],
"gridPos": {"h": 6, "w": 6, "x": 6, "y": 8}
}
]
}
}
```
### 自动化部署与维护
通过Ansible等工具实现监控系统的自动化部署。
```yaml
# deploy_monitoring.yml
- hosts: all
become: yes
vars:
cpolar_token: "{{ lookup('env', 'CPOLAR_TOKEN') }}"
monitoring_user: "monitor"
monitoring_password: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}"
tasks:
- name: 安装node_exporter
ansible.builtin.copy:
src: files/node_exporter
dest: /usr/local/bin/
mode: '0755'
- name: 创建systemd服务
ansible.builtin.template:
src: templates/node_exporter.service.j2
dest: /etc/systemd/system/node_exporter.service
- name: 配置cpolar
ansible.builtin.copy:
src: files/cpolar.yml
dest: /etc/cpolar.yml
mode: '0600'
- name: 启动服务
ansible.builtin.systemd:
name: "{{ item }}"
state: started
enabled: yes
loop:
- node_exporter
- cpolar
- name: 验证服务状态
ansible.builtin.uri:
url: "http://localhost:9100/metrics"
status_code: 200
```
### 总结
通过node_exporter与cpolar的结合,可以构建一个安全、高效的远程服务器监控方案。这种方案不仅解决了远程访问的技术难题,还通过多层安全防护确保了系统的安全性。实际部署时,需要根据具体环境调整配置参数,并建立完善的监控报警机制。
随着远程办公的普及,此类混合监控方案将在更多场景中发挥作用。关键在于平衡便利性与安全性,建立标准化的部署流程和应急响应机制。通过持续优化和改进,远程服务器监控能够为分布式系统的稳定运行提供有力保障,真正实现运维工作的灵活与高效。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3109803/ ,如需转载,请注明出处,否则将追究法律责任。