本文详解Python Fabric库实现SSH远程执行、批量部署和自动化运维。
前言
运维日常:
- 登录服务器A,执行命令
- 登录服务器B,执行同样命令
- 登录服务器C...
这太痛苦了!用Python + Fabric实现批量自动化。
一、Fabric简介
1.1 什么是Fabric
diff
Fabric = Python + SSH + 批量执行
功能:
- 远程执行命令
- 文件上传下载
- 批量操作多台服务器
- 部署自动化
1.2 对比其他工具
| 工具 | 语言 | 学习成本 | 灵活性 |
|---|---|---|---|
| Fabric | Python | 低 | 高 |
| Ansible | YAML | 中 | 中 |
| SaltStack | Python/YAML | 高 | 高 |
| Shell脚本 | Bash | 低 | 低 |
Fabric适合:中小规模 、需要灵活定制的场景。
二、环境准备
2.1 安装Fabric
bash
# Python 3.6+
pip install fabric
# 验证安装
fab --version
2.2 基础用法
python
# fabfile.py
from fabric import Connection
# 连接远程服务器
conn = Connection(
host='192.168.1.100',
user='root',
connect_kwargs={
'password': 'your_password'
# 或使用密钥
# 'key_filename': '/path/to/id_rsa'
}
)
# 执行命令
result = conn.run('uname -a')
print(result.stdout)
# 关闭连接
conn.close()
三、核心功能
3.1 远程执行命令
python
from fabric import Connection
def remote_exec():
"""远程执行命令"""
conn = Connection('root@192.168.1.100')
# 基本执行
result = conn.run('hostname')
print(f"主机名: {result.stdout.strip()}")
# 隐藏输出
result = conn.run('cat /etc/os-release', hide=True)
print(result.stdout)
# 带环境变量
conn.run('echo $HOME', env={'HOME': '/root'})
# 指定工作目录
with conn.cd('/var/log'):
conn.run('ls -la')
conn.close()
3.2 文件操作
python
from fabric import Connection
def file_operations():
"""文件上传下载"""
conn = Connection('root@192.168.1.100')
# 上传文件
conn.put('local_file.txt', '/tmp/remote_file.txt')
# 下载文件
conn.get('/var/log/syslog', 'local_syslog.txt')
# 上传并设置权限
conn.put('script.sh', '/opt/script.sh')
conn.run('chmod +x /opt/script.sh')
conn.close()
3.3 Sudo执行
python
from fabric import Connection
def sudo_exec():
"""以sudo方式执行"""
conn = Connection(
'user@192.168.1.100',
connect_kwargs={'password': 'user_password'}
)
# sudo执行
conn.sudo('systemctl restart nginx', password='sudo_password')
# 以其他用户执行
conn.sudo('whoami', user='www-data')
conn.close()
四、批量操作
4.1 ThreadingGroup并行执行
python
from fabric import ThreadingGroup
def batch_exec():
"""批量并行执行"""
# 服务器列表
hosts = [
'root@192.168.1.101',
'root@192.168.1.102',
'root@192.168.1.103',
]
# 创建组(并行执行)
group = ThreadingGroup(*hosts, connect_kwargs={'password': 'password'})
# 批量执行
results = group.run('hostname')
for conn, result in results.items():
print(f"{conn.host}: {result.stdout.strip()}")
group.close()
4.2 SerialGroup串行执行
python
from fabric import SerialGroup
def serial_exec():
"""串行执行(适合有依赖的任务)"""
hosts = ['root@192.168.1.101', 'root@192.168.1.102']
group = SerialGroup(*hosts, connect_kwargs={'password': 'password'})
# 依次执行
group.run('apt update')
group.run('apt upgrade -y')
group.close()
4.3 从配置文件读取服务器
python
# servers.yaml
# web:
# - 192.168.1.101
# - 192.168.1.102
# db:
# - 192.168.1.201
import yaml
from fabric import ThreadingGroup
def load_servers():
"""从配置文件加载服务器"""
with open('servers.yaml') as f:
servers = yaml.safe_load(f)
# 批量操作web服务器
web_hosts = [f'root@{ip}' for ip in servers['web']]
group = ThreadingGroup(*web_hosts, connect_kwargs={'password': 'password'})
group.run('nginx -t')
group.run('systemctl reload nginx')
group.close()
五、实战案例
5.1 案例:批量部署应用
python
# deploy.py
from fabric import Connection, ThreadingGroup
import os
class Deployer:
def __init__(self, hosts, user='root', password=None, key_file=None):
self.hosts = [f'{user}@{h}' for h in hosts]
self.connect_kwargs = {}
if password:
self.connect_kwargs['password'] = password
if key_file:
self.connect_kwargs['key_filename'] = key_file
def deploy(self, local_path, remote_path, service_name=None):
"""部署应用"""
group = ThreadingGroup(*self.hosts, connect_kwargs=self.connect_kwargs)
print(">>> 上传文件...")
for conn in group:
conn.put(local_path, remote_path)
if service_name:
print(f">>> 重启服务 {service_name}...")
group.sudo(f'systemctl restart {service_name}')
print(">>> 部署完成!")
group.close()
def rollback(self, backup_path, remote_path, service_name=None):
"""回滚"""
group = ThreadingGroup(*self.hosts, connect_kwargs=self.connect_kwargs)
print(">>> 回滚中...")
group.run(f'cp {backup_path} {remote_path}')
if service_name:
group.sudo(f'systemctl restart {service_name}')
print(">>> 回滚完成!")
group.close()
# 使用
if __name__ == '__main__':
hosts = ['192.168.1.101', '192.168.1.102']
deployer = Deployer(hosts, password='password')
deployer.deploy('./app.jar', '/opt/app/app.jar', 'myapp')
5.2 案例:服务器健康检查
python
# health_check.py
from fabric import ThreadingGroup
from datetime import datetime
def health_check(hosts):
"""服务器健康检查"""
group = ThreadingGroup(*hosts, connect_kwargs={'password': 'password'})
print(f"\n{'='*60}")
print(f"服务器健康检查 - {datetime.now()}")
print(f"{'='*60}\n")
# 检查项
checks = {
'hostname': 'hostname',
'uptime': 'uptime -p',
'disk': "df -h / | tail -1 | awk '{print $5}'",
'memory': "free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2*100}'",
'load': "cat /proc/loadavg | awk '{print $1, $2, $3}'",
}
for conn in group:
print(f"\n--- {conn.host} ---")
for name, cmd in checks.items():
try:
result = conn.run(cmd, hide=True, warn=True)
print(f" {name}: {result.stdout.strip()}")
except Exception as e:
print(f" {name}: ERROR - {e}")
group.close()
if __name__ == '__main__':
hosts = [
'root@192.168.1.101',
'root@192.168.1.102',
'root@192.168.1.103',
]
health_check(hosts)
5.3 案例:日志收集
python
# collect_logs.py
from fabric import ThreadingGroup
from pathlib import Path
from datetime import datetime
def collect_logs(hosts, remote_log, local_dir='./logs'):
"""批量收集日志"""
Path(local_dir).mkdir(exist_ok=True)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
group = ThreadingGroup(*hosts, connect_kwargs={'password': 'password'})
for conn in group:
host_ip = conn.host
local_file = f"{local_dir}/{host_ip}_{timestamp}.log"
try:
# 压缩远程日志
conn.run(f'gzip -c {remote_log} > /tmp/log.gz', hide=True)
# 下载
conn.get('/tmp/log.gz', f'{local_file}.gz')
print(f"✓ {host_ip} -> {local_file}.gz")
except Exception as e:
print(f"✗ {host_ip}: {e}")
group.close()
print(f"\n日志已收集到: {local_dir}/")
if __name__ == '__main__':
hosts = ['root@192.168.1.101', 'root@192.168.1.102']
collect_logs(hosts, '/var/log/nginx/access.log')
六、进阶技巧
6.1 异常处理
python
from fabric import Connection
from invoke.exceptions import UnexpectedExit
def safe_exec(host, command):
"""安全执行(带异常处理)"""
conn = Connection(host)
try:
result = conn.run(command, warn=True) # warn=True 不抛异常
if result.ok:
print(f"✓ 成功: {result.stdout.strip()}")
else:
print(f"✗ 失败: {result.stderr.strip()}")
return result.ok
except Exception as e:
print(f"✗ 连接错误: {e}")
return False
finally:
conn.close()
6.2 上下文管理器
python
from fabric import Connection
from contextlib import contextmanager
@contextmanager
def remote_server(host, **kwargs):
"""上下文管理器"""
conn = Connection(host, **kwargs)
try:
yield conn
finally:
conn.close()
# 使用
with remote_server('root@192.168.1.100', connect_kwargs={'password': 'pwd'}) as conn:
conn.run('hostname')
# 自动关闭连接
6.3 SSH配置复用
python
from fabric import Connection
# 使用~/.ssh/config中的配置
# Host myserver
# HostName 192.168.1.100
# User root
# IdentityFile ~/.ssh/id_rsa
conn = Connection('myserver') # 自动读取SSH配置
conn.run('hostname')
七、使用fabfile
7.1 定义任务
python
# fabfile.py
from fabric import task, Connection
@task
def deploy(ctx, host, version='latest'):
"""部署应用
使用: fab deploy --host=192.168.1.100 --version=1.0.0
"""
conn = Connection(host)
print(f"部署版本 {version} 到 {host}")
conn.run(f'docker pull myapp:{version}')
conn.run(f'docker-compose up -d')
conn.close()
@task
def status(ctx, host):
"""检查状态
使用: fab status --host=192.168.1.100
"""
conn = Connection(host)
conn.run('docker ps')
conn.close()
@task
def logs(ctx, host, lines=100):
"""查看日志
使用: fab logs --host=192.168.1.100 --lines=200
"""
conn = Connection(host)
conn.run(f'docker logs --tail {lines} myapp')
conn.close()
7.2 执行任务
bash
# 列出所有任务
fab --list
# 执行任务
fab deploy --host=root@192.168.1.100 --version=1.0.0
fab status --host=root@192.168.1.100
fab logs --host=root@192.168.1.100 --lines=200
八、跨网络管理
8.1 问题
diff
常见场景:
- 办公室需要管理机房服务器
- 在家需要管理公司服务器
- 管理多个不同网络的服务器
传统方案:
- VPN接入 → 配置复杂
- 跳板机 → 多一跳延迟
- 公网暴露SSH → 安全风险
8.2 组网方案
使用组网软件(如星空组网)简化跨网络管理:
css
┌─────────────────────────────────────────────────────────┐
│ 组网虚拟局域网 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 服务器A │ │ 服务器B │ │ 服务器C │ │
│ │10.10.0.1 │ │10.10.0.2 │ │10.10.0.3 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ↑ │
│ │ │
│ ┌──────────┐ │
│ │ 管理电脑 │ │
│ │10.10.0.10│ │
│ │ Fabric │ │
│ └──────────┘ │
│ │
│ 所有设备在同一虚拟局域网,直接SSH互通 │
└─────────────────────────────────────────────────────────┘
python
# 配置组网后,直接用虚拟IP
hosts = [
'root@10.10.0.1', # 机房服务器A
'root@10.10.0.2', # 机房服务器B
'root@10.10.0.3', # 云服务器C
]
group = ThreadingGroup(*hosts, connect_kwargs={'key_filename': '~/.ssh/id_rsa'})
group.run('hostname')
优势:
- 不同网络的服务器统一管理
- 无需配置跳板机
- 安全加密传输
- 像局域网一样简单
九、最佳实践
9.1 安全建议
python
# 1. 使用密钥而非密码
connect_kwargs={'key_filename': '~/.ssh/id_rsa'}
# 2. 使用SSH Agent
# ssh-add ~/.ssh/id_rsa
connect_kwargs={} # 自动使用agent
# 3. 限制sudo权限
conn.sudo('systemctl restart nginx', password=get_password())
# 4. 敏感信息不写代码
import os
password = os.environ.get('SSH_PASSWORD')
9.2 代码组织
bash
automation/
├── fabfile.py # 任务定义
├── config/
│ ├── servers.yaml # 服务器配置
│ └── settings.py # 全局设置
├── tasks/
│ ├── deploy.py # 部署任务
│ ├── backup.py # 备份任务
│ └── monitor.py # 监控任务
└── utils/
└── ssh.py # SSH工具函数
十、总结
Python Fabric自动化运维要点:
| 功能 | 方法 |
|---|---|
| 单机执行 | Connection.run() |
| 文件传输 | put() / get() |
| 批量并行 | ThreadingGroup |
| 批量串行 | SerialGroup |
| sudo执行 | conn.sudo() |
| 任务定义 | @task 装饰器 |
适用场景:
- 批量部署应用
- 服务器健康检查
- 日志收集分析
- 配置同步
- 自动化运维脚本
参考资料
- Fabric官方文档:docs.fabfile.org/
- Fabric GitHub:github.com/fabric/fabr...
💡 Fabric让Python成为运维神器,配合组网软件可以轻松管理分布在不同网络的所有服务器。