确保机子上的 ssh 始终可用

三葉Leaves Author

最近经常遇到因为执行了某条命令,导致系统的资源吃满,ssh 都连不上了,只能被迫重启机子。本文将提供一个脚本和其配置办法,从 CPU,RAM,网络带宽等多个维度分别确保 ssh 在极端负载情况下仍然可用。

使用方法

Tip

下面给出的脚本是针对我的云机配置设置的,请你参考下文的脚本说明根据你的机子情况调整,懒得管就直接照抄。
我的云机配置:
cpu:4核;ram:8G;带宽:3M;系统:ubuntu 22 LTS。

1. 创建 SSH 资源保障脚本

我命名为 ssh_resource_guard.sh

1
2
3
vim /usr/local/bin/ssh_resource_guard.sh
# 将脚本内容粘贴到文件中
# 按ESC键,然后输入:wq保存并退出

脚本内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/bin/bash
# 脚本名: ssh_resource_guard.sh
# 功能: 为SSH服务保留网络带宽、CPU和内存资源
# 使用方法: 以root用户运行此脚本

# 检查root权限
if [ "$(id -u)" -ne 0 ]; then
echo "此脚本需要root权限运行"
exit 1
fi

# 确认网卡名称
NIC="eth0"
if ! ip link show $NIC &>/dev/null; then
echo "网卡 $NIC 不存在,脚本将退出"
exit 1
fi

echo "=== 开始配置资源保障 ==="

# ===== 网络带宽保障 =====
echo "正在配置网络优先级..."

# 清除现有的tc配置
tc qdisc del dev $NIC root 2>/dev/null

# 创建根队列
tc qdisc add dev $NIC root handle 1: htb default 9999

# 为SSH预留1Mbps带宽(优先级最高)
tc class add dev $NIC parent 1: classid 1:1 htb rate 1mbps ceil 1mbps prio 0

# 标记SSH流量(端口22)
iptables -F OUTPUT -t mangle 2>/dev/null
iptables -A OUTPUT -t mangle -p tcp --sport 22 -j MARK --set-mark 0x1
tc filter add dev $NIC parent 1: protocol ip handle 0x1 fw flowid 1:1

# 其他流量分配到低优先级类(总带宽3Mbps,预留1Mbps给SSH)
tc class add dev $NIC parent 1: classid 1:9999 htb rate 2mbps ceil 2mbps prio 7

# ===== CPU优先级保障 =====
echo "正在配置CPU优先级..."

# 检查cgroups v2是否可用
if [ -d "/sys/fs/cgroup" ] && [ ! -d "/sys/fs/cgroup/memory" ]; then
echo "检测到使用cgroups v2"

# 创建SSH专用cgroup (如果不存在)
if [ ! -d "/sys/fs/cgroup/ssh_reserved" ]; then
mkdir -p /sys/fs/cgroup/ssh_reserved
fi

# 配置CPU权重
echo "100" > /sys/fs/cgroup/ssh_reserved/cpu.weight 2>/dev/null

# 将sshd服务移入cgroup
echo "正在配置systemd服务属性..."
systemctl set-property sshd.service CPUWeight=100

# 确保sshd进程在cgroup中
for pid in $(pgrep -f "/usr/sbin/sshd"); do
echo $pid > /sys/fs/cgroup/ssh_reserved/cgroup.procs 2>/dev/null
done
else
echo "检测到使用cgroups v1或混合模式"

# 使用旧版cgroups
if [ -d "/sys/fs/cgroup/cpu" ]; then
# 创建SSH专用cgroup
mkdir -p /sys/fs/cgroup/cpu/ssh_reserved
echo 1024 > /sys/fs/cgroup/cpu/ssh_reserved/cpu.shares

# 将sshd进程移入cgroup
for pid in $(pgrep -f "/usr/sbin/sshd"); do
echo $pid > /sys/fs/cgroup/cpu/ssh_reserved/cgroup.procs 2>/dev/null
done
fi
fi

# 设置sshd进程为实时优先级
for pid in $(pgrep -f "/usr/sbin/sshd"); do
chrt -rr 50 -p $pid 2>/dev/null
done

# ===== 内存保护 =====
echo "正在配置内存保护..."

# 降低SSH进程的OOM score
for pid in $(pgrep -f "/usr/sbin/sshd"); do
echo -1000 > /proc/$pid/oom_score_adj 2>/dev/null
done

# 检查cgroups版本并配置内存限制
if [ -d "/sys/fs/cgroup/memory" ]; then
# cgroups v1
echo "配置cgroups v1内存保护..."

# 创建低优先级组
mkdir -p /sys/fs/cgroup/memory/other_processes

# 为其他进程设置内存限制 (例如限制为总内存的80%)
MEM_TOTAL=$(free -b | grep "Mem:" | awk '{print $2}')
MEM_LIMIT=$(echo "$MEM_TOTAL * 0.8" | bc | cut -d. -f1)
echo $MEM_LIMIT > /sys/fs/cgroup/memory/other_processes/memory.limit_in_bytes

# 将非关键进程移入该组
for pid in $(ps -eo pid --no-headers); do
if ! pgrep -f "sshd|systemd|init|journald|udevd|bash" | grep -q "$pid"; then
echo $pid > /sys/fs/cgroup/memory/other_processes/cgroup.procs 2>/dev/null
fi
done
elif [ -d "/sys/fs/cgroup" ]; then
# cgroups v2
echo "配置cgroups v2内存保护..."

# 创建低优先级组
mkdir -p /sys/fs/cgroup/other_processes

# 启用内存控制器
echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null

# 设置内存限制
MEM_TOTAL=$(free -b | grep "Mem:" | awk '{print $2}')
MEM_LIMIT=$(echo "$MEM_TOTAL * 0.8" | bc | cut -d. -f1)
echo $MEM_LIMIT > /sys/fs/cgroup/other_processes/memory.max 2>/dev/null

# 将非关键进程移入该组
for pid in $(ps -eo pid --no-headers); do
if ! pgrep -f "sshd|systemd|init|journald|udevd|bash" | grep -q "$pid"; then
echo $pid > /sys/fs/cgroup/other_processes/cgroup.procs 2>/dev/null
fi
done
fi

echo "=== 资源保障配置完成 ==="

# 打印当前状态
echo ""
echo "当前配置状态:"
echo "网络带宽配置:"
tc -s qdisc show dev $NIC
tc -s class show dev $NIC

echo ""
echo "SSH进程的OOM分数:"
for pid in $(pgrep -f "/usr/sbin/sshd"); do
echo "PID $pid: $(cat /proc/$pid/oom_score_adj)"
done

echo ""
echo "SSH进程的调度策略:"
for pid in $(pgrep -f "/usr/sbin/sshd"); do
chrt -p $pid
done

echo ""
echo "已完成所有配置。"

2. 设置执行权限:

1
chmod +x /usr/local/bin/ssh_resource_guard.sh

3. 创建systemd服务,使脚本在系统启动时自动运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat > /etc/systemd/system/ssh-resource-guard.service << 'EOF'
[Unit]
Description=SSH Resource Guard Service
After=network.target sshd.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/ssh_resource_guard.sh
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

4. 启用并启动服务:

1
2
3
systemctl daemon-reload
systemctl enable ssh-resource-guard.service
systemctl start ssh-resource-guard.service

5. 检查systemd服务状态:

1
systemctl status ssh-resource-guard.service

如果需要检查运行日志:

1
journalctl -u ssh-resource-guard.service

如果需要手动启动脚本,直接执行:

1
bash /usr/local/bin/ssh_resource_guard.sh

脚本说明

以下是对上述所有命令的详细解释,按功能模块分类说明其作用及实现原理:


一、网络带宽预留 (tc + iptables)

1. 创建根队列

1
tc qdisc add dev eth0 root handle 1: htb default 9999
  • 作用:在网卡 eth0 上创建流量控制根队列(qdisc),使用 HTB(Hierarchical Token Bucket) 算法。
  • 参数解析
    • root: 表示根队列。
    • handle 1:: 队列的标识符,格式为 主ID:子ID
    • htb: 使用 HTB 算法实现带宽分层管理。
    • default 9999: 未分类的流量默认发送到 1:9999 类。

2. 为 SSH 预留带宽

1
tc class add dev eth0 parent 1: classid 1:1 htb rate 1mbps ceil 1mbps prio 0
  • 作用:创建子类 1:1,为 SSH 预留 1Mbps 固定带宽(最低保障 + 上限)。
  • 参数解析
    • parent 1:: 父队列为根队列 1:
    • classid 1:1: 子类的标识符。
    • rate 1mbps: 保证带宽(必须满足的最低速率)。
    • ceil 1mbps: 最大带宽(硬性上限)。
    • prio 0: 最高优先级(数值越小优先级越高)。

3. 标记 SSH 流量

1
iptables -A OUTPUT -t mangle -p tcp --sport 22 -j MARK --set-mark 0x1
  • 作用:使用 iptablesOUTPUT 链的 mangle 表中标记所有源端口为 22(SSH 默认端口)的流量,设置标记值 0x1
  • 关键点tc 根据此标记识别 SSH 流量。

4. 将标记流量分配到预留类

1
tc filter add dev eth0 parent 1: protocol ip handle 0x1 fw flowid 1:1
  • 作用:将标记为 0x1 的流量(SSH)分配到预留的 1:1 类。
  • 参数解析
    • handle 0x1: 匹配 iptables 设置的标记值。
    • flowid 1:1: 流量指向 1:1 类。

5. 其他流量分配

1
tc class add dev eth0 parent 1: classid 1:9999 htb rate 1000mbps ceil 1000mbps prio 7
  • 作用:创建默认子类 1:9999,用于处理非 SSH 流量。
  • 参数解析
    • prio 7: 最低优先级(数值越大优先级越低)。
    • rateceil 设为网卡最大带宽(假设为 1Gbps)。

二、CPU 优先级保障

1. cgroups v2 硬性隔离

1
2
mkdir /sys/fs/cgroup/ssh_reserved
echo "cpu.weight: 100" > /sys/fs/cgroup/ssh_reserved/cpu.weight
  • 作用:创建 cgroup ssh_reserved,设置 CPU 权重为 100(默认值为 100,更高权重会分配更多 CPU 时间)。
  • 原理:在 cgroups v2 中,cpu.weight 控制 CPU 时间片的分配比例。例如,若其他进程总权重为 100,SSH 的 100 权重将确保其至少获得 100/(100+100)=50% 的 CPU 时间。
1
systemctl set-property sshd.service CPUWeight=100
  • 作用:通过 systemd 直接修改 sshd 服务的 CPU 权重,使其始终运行在 ssh_reserved cgroup 中。

2. 实时调度策略(RT Priority)

1
chrt -rr 99 /usr/sbin/sshd -D
  • 作用:强制 sshd 以实时调度策略运行,优先级为 99(最高为 99)。
  • 参数解析
    • -rr: 使用 SCHED_RR 调度策略(时间片轮转实时调度)。
    • 风险提示:实时进程可能阻塞系统关键任务,需谨慎使用。

三、内存保护

1. OOM Killer 调整

1
echo -1000 > /proc/$(pgrep -f "/usr/sbin/sshd")/oom_score_adj
  • 作用:将 sshd 进程的 OOM(Out-Of-Memory)评分调整为 -1000,使其成为 OOM Killer 最后杀死的进程。
  • 原理oom_score_adj 范围是 -10001000,值越低越不易被杀死。

2. cgroups 内存硬限制

1
2
mkdir /sys/fs/cgroup/memory/limited_group
echo 8G > /sys/fs/cgroup/memory/limited_group/memory.limit_in_bytes
  • 作用:创建 cgroup limited_group,限制其内存使用上限为 8GB
1
2
3
for pid in $(pgrep -v -f "sshd|systemd"); do
echo $pid > /sys/fs/cgroup/memory/limited_group/cgroup.procs
done
  • 作用:将所有非 sshdsystemd 进程移入 limited_group,限制它们的内存使用,从而为 SSH 预留内存。

四、Systemd 服务强化

1
2
3
4
5
6
# /etc/systemd/system/sshd.service.d/override.conf
[Service]
CPUWeight=100
MemoryHigh=2G
OOMScoreAdjust=-1000
IOSchedulingClass=realtime
  • 作用:通过 systemd 覆盖配置为 sshd 服务设置:
    • CPUWeight=100: CPU 权重。
    • MemoryHigh=2G: 内存使用软限制(允许短暂超出,但会触发回收)。
    • OOMScoreAdjust=-1000: 免疫 OOM Killer。
    • IOSchedulingClass=realtime: 磁盘 I/O 优先级为实时。

五、终端响应优化

1. SSH 保活配置

1
2
3
4
# /etc/ssh/sshd_config
ClientAliveInterval 30
ClientAliveCountMax 3
TCPKeepAlive yes
  • 作用
    • ClientAliveInterval 30: 每 30 秒向客户端发送保活消息。
    • ClientAliveCountMax 3: 连续 3 次无响应后断开连接。
    • TCPKeepAlive yes: 启用 TCP 层保活机制。

2. TTY 资源隔离

1
2
3
cgcreate -g cpu,memory:/tty_priority
cgset -r cpu.shares=1024 /tty_priority
cgset -r memory.limit_in_bytes=512M /tty_priority
  • 作用:创建 tty_priority cgroup,为 TTY 分配独立的 CPU 和内存资源。
1
cgexec -g cpu,memory:/tty_priority /bin/bash
  • 作用:启动一个 Shell 进程,并将其移入 tty_priority cgroup。

六、持久化配置

1. 固化 tc 规则

tc 命令写入 /etc/rc.local 或 NetworkManager 脚本,确保重启后规则仍生效。

2. cgroups 固化

1
2
# 通过 systemd-tmpfiles 创建配置
echo "d /sys/fs/cgroup/ssh_reserved 0755 root root" > /etc/tmpfiles.d/ssh-reserved.conf
  • 作用:系统启动时自动创建 cgroup 目录。

3. 内核参数调整

1
echo "vm.overcommit_memory=2" >> /etc/sysctl.conf
  • 作用:设置内存分配策略为 2(严格模式),禁止超额分配内存。

注意事项

  1. 内核支持:需确认内核支持 cgroups v2HTB 算法等特性。
  2. 硬件资源:预留资源需根据实际带宽、CPU 核心数、内存大小调整。
  3. 实时调度风险SCHED_RR 可能阻塞系统关键进程,建议仅在极端场景下启用。
  4. 兼容性:部分命令在不同发行版中可能有差异(如 systemd 版本、tc 语法)。

通过以上配置,SSH 服务将在网络、CPU、内存等多层资源竞争中获得最高优先级,确保其在极端负载下仍可用。

  • 标题: 确保机子上的 ssh 始终可用
  • 作者: 三葉Leaves
  • 创建于 : 2025-02-27 00:00:00
  • 更新于 : 2025-04-25 00:07:45
  • 链接: https://blog.oksanye.com/a394d5a50b1b/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论