甲骨文云永久免费实例防回收方案:用 crontab | systemd timer 定时保号

📦GitHub仓库地址

注册甲骨文云免费VPS的时候,根据官方政策,当资源使用率低于某一标准时,实例会被回收————机器好好的,某天登不上去了,一看 OCI 控制台,实例已经变成了 RECLAIMED。这篇文章把回收政策的判定逻辑进行了梳理,同时给出了一套现成的定时脚本,帮你把实例长期保住。

一、Oracle 的空闲实例回收政策是什么

Oracle 官方文档里对此有明确说明:Always Free 额度下的计算实例,如果被判定为”空闲”,Oracle 有权直接回收

instance-reclaim.webp

判定”空闲”的标准是:在过去 7 天内,同时满足以下几个条件:

  • CPU 利用率的第 95 百分位低于 20%
  • 网络利用率低于 20%
  • 内存使用率低于 20%(仅适用于 A1 机型,即 Ampere ARM 架构实例)

注意这里的关键词是”同时满足“——也就是说,只要其中任意一项指标稳定超过 20%,实例就不会被判定为空闲,也就不会被回收。这一点很重要,后面的方案就是围绕它设计的。

为什么是”第 95 百分位”而不是”平均值”

这是很多人容易忽略的细节。如果判定标准是平均值,那你必须让 CPU 利用率长期维持在 20% 以上,代价不小。但第 95 百分位意味着:允许有 5% 的采样点例外——换句话说,一天 24 小时里,只要有 24X5%=1.2 小时的时间段 CPU 利用率冲到 20% 以上,剩下的时间该闲还是闲,95 百分位这个统计值照样会在阈值以上。

这就给了我们一个思路:不需要长期占用资源,只需要每天定时”脉冲”一段时间的负载即可。

二、方案一:脚本 + cron

最简单的做法是写一个压力测试脚本,用 stress-ng 制造真实的 CPU(以及内存)负载,再用 cron 定时触发。

脚本本体

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
#!/usr/bin/env bash
#
# oci-anti-reclaim.sh
# 用于避免 Oracle Cloud "Always Free" 空闲计算实例被自动回收
#
# 用法:
# ./oci-anti-reclaim.sh [持续时间(秒)] [CPU负载百分比] [内存压力模式]
#
# 内存压力模式可选:
# auto (默认)根据架构自动判断,ARM 开启,x86 关闭
# mem 强制开启内存压力
# nomem 强制关闭内存压力

set -euo pipefail

LOG_FILE="/var/log/oci-anti-reclaim.log"
DURATION="${1:-7200}" # 持续时间,默认 7200 秒 = 120 分钟
CPU_LOAD="${2:-30}" # 每个 CPU worker 的负载百分比,默认 30%
MEM_MODE="${3:-auto}" # auto / mem / nomem

timestamp() { date '+%Y-%m-%d %H:%M:%S'; }
log() { echo "[$(timestamp)] $*" | tee -a "$LOG_FILE"; }

if [[ $EUID -ne 0 ]]; then
echo "请使用 sudo 或 root 权限运行本脚本" >&2
exit 1
fi

# ---- 自动判断是否为 ARM (A1) 实例 ----
detect_arm() {
local arch
arch=$(uname -m)
case "$arch" in
aarch64|arm64) return 0 ;;
*) return 1 ;;
esac
}

case "$MEM_MODE" in
mem)
USE_MEM=1
log "内存压力模式:手动强制开启"
;;
nomem)
USE_MEM=0
log "内存压力模式:手动强制关闭"
;;
auto|*)
if detect_arm; then
USE_MEM=1
log "检测到架构 $(uname -m),判定为 ARM(A1) 机型,自动开启内存压力"
else
USE_MEM=0
log "检测到架构 $(uname -m),判定为 x86_64 机型,跳过内存压力"
fi
;;
esac

if ! command -v stress-ng &>/dev/null; then
log "未检测到 stress-ng,正在安装..."
apt-get update -qq
apt-get install -y stress-ng
fi

CPU_CORES=$(nproc)
STRESS_ARGS=(--cpu "${CPU_CORES}" --cpu-load "${CPU_LOAD}" --timeout "${DURATION}s" --metrics-brief)

if [[ "${USE_MEM}" -eq 1 ]]; then
STRESS_ARGS+=(--vm "${CPU_CORES}" --vm-bytes 128M --vm-keep)
fi

log "开始压力测试:核心数=${CPU_CORES},时长=${DURATION}s,CPU负载=${CPU_LOAD}%,内存压力=$([[ ${USE_MEM} -eq 1 ]] && echo 开启 || echo 关闭)"

stress-ng "${STRESS_ARGS[@]}" >> "$LOG_FILE" 2>&1

log "压力测试结束"

脚本会自动用 uname -m 判断架构:aarch64 / arm64 视为 A1 机型,自动带上内存压力;x86_64 则只压 CPU,不多此一举。

部署步骤 + cron 配置(需要手动执行以下命令)

1
2
3
4
5
6
7
# 将 oci-anti-reclaim.sh 文件移动到 /usr/local/bin/ 目录下
sudo mv oci-anti-reclaim.sh /usr/local/bin/
# 给 oci-anti-reclaim.sh 可执行权限
sudo chmod +x /usr/local/bin/oci-anti-reclaim.sh

# 编辑定时任务脚本文件
sudo crontab -e

运行后在定时脚本的最后可以加一行,比如每天凌晨 3 点跑 120 分钟、CPU 负载 30%:

1
0 3 * * * /usr/local/bin/oci-anti-reclaim.sh 7200 30 auto >> /var/log/oci-anti-reclaim.log 2>&1

脚本创建说明:

sudo crontab -e 执行后会打开一个文本编辑器,把整段 crontab 当成一个纯文本文件在编辑,新的定时任务就是往这个文件里加一行。具体步骤是:

  • 运行 sudo crontab -e ,如果是第一次在这台机器上用 crontab -e,系统会提示选择一个编辑器,直接选默认的 nano(通常是选项 1)最省心。
  • 编辑器打开后,用方向键把光标移动到已有内容的最后一行下面。文件里可能已经有一些以 # 开头的注释说明,不用管它们,也不要删除或修改原有内容。
  • 在新的一行输入或粘贴:0 3 * * * /usr/local/bin/oci-anti-reclaim.sh 7200 30 auto >> /var/log/oci-anti-reclaim-cron.log 2>&1 。确保这一整行没有被拆成两行,也没有多余的空格插在中间。
  • 如果是 nano:按 Ctrl+O 保存,接着按 Enter 确认文件名,再按 Ctrl+X 退出编辑器。如果打开的是 vim:先按 Esc,再输入 :wq 然后回车保存退出。保存后终端通常会提示 crontab: installing new crontab,说明写入成功。
  • 运行 sudo crontab -l 查看当前 root 的 crontab 列表,确认刚才那一行已经出现在里面。这一步只是列出内容,不会再次打开编辑器。
  • 如果想清除任务就把 oci-anti-reclaim.sh 那一行删掉,保存退出即可。

小提醒:这一行必须是完整的一整行,0 3 * * * 是时间字段,后面接完整命令路径,中间不要换行————有时候复制粘贴时容易被终端自动换行搞乱格式,导致 cron 解析出错。如果不确定粘贴的内容有没有跑偏,sudo crontab -l 确认格式没问题就行。

手动执行脚本的话就直接运行: sudo oci-anti-reclaim.sh

执行压力测试脚本

如果是第一次运行,脚本会自动安装相关的 stress-ng 模块。

安装stress-ng模块

脚本执行完成,查看执行日志:

1
sudo tail -f /var/log/oci-anti-reclaim-cron.log

日志查看结果

cron 方案简单够用,缺点是:机器重启时如果正好错过了触发时间点,就要等到第二天,想要更稳的方案,可以换成方案二 systemd。

三、方案二(推荐):systemd service + timer

.service.timer 分别是什么

这是 systemd 里配对使用的两个 unit 文件:

文件 作用
.service 定义”要做什么”——跑哪个脚本、什么优先级
.timer 定义”什么时候触发”——相当于 systemd 自带的 cron

.timer 到点后会去启动同名.service(靠文件名对应,不需要在文件里显式声明关联)。你 enable 的是 .timer,因为你要的是”定时自动跑”;想立刻手动测试一次效果,直接运行 start .service

比起 cron,systemd 方案多了几个好处:Persistent=true 能保证错过的触发会在下次开机后自动补跑;执行状态能用 systemctl status 直接看;日志统一进 journalctl,不用自己拼日志文件。

service 文件 (文件名: oci-anti-reclaim.service ,放在 /etc/systemd/system/ 目录下)

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=Oracle Cloud 空闲实例防回收任务 (CPU/内存压力测试,自动判断ARM架构)
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
# 参数:持续时间(秒) CPU负载(%) 内存压力模式(auto/mem/nomem)
ExecStart=/usr/local/bin/oci-anti-reclaim.sh 7200 30 auto

# 避免影响系统关键服务的资源调度优先级
Nice=10
CPUSchedulingPolicy=idle

timer 文件 (文件名: oci-anti-reclaim.timer ,放在/etc/systemd/system/ 目录下)

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=每天定时触发 oci-anti-reclaim 防回收任务

[Timer]
# 每天 03:00 触发
OnCalendar=*-*-* 03:00:00
# 加入最多30分钟的随机延迟,避免所有实例同一秒发起压力测试
RandomizedDelaySec=1800
# 错过计划时间(比如实例正好在重启)也会在开机后补跑一次
Persistent=true

[Install]
WantedBy=timers.target

部署步骤(需要手动执行以下命令)

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
# 将 oci-anti-reclaim.sh 文件移动到 /usr/local/bin/ 目录下。如果文件已在该目录下,跳过
sudo mv oci-anti-reclaim.sh /usr/local/bin/
# 给 oci-anti-reclaim.sh 可执行的权限
sudo chmod +x /usr/local/bin/oci-anti-reclaim.sh

# 将两个文件移动到 /etc/systemd/system/ 目录下。如果文件已在该目录下,跳过
sudo mv oci-anti-reclaim.service /etc/systemd/system/
sudo mv oci-anti-reclaim.timer /etc/systemd/system/

# 将服务加入定时任务
sudo systemctl daemon-reload

# 启动服务
sudo systemctl enable --now oci-anti-reclaim.timer

# 确认已经排上计划
sudo systemctl list-timers oci-anti-reclaim.timer

# 想手动测试一次
sudo systemctl start oci-anti-reclaim.service

# 查看执行日志
sudo journalctl -u oci-anti-reclaim.service --since today

# 停止服务
sudo systemctl disable --now oci-anti-reclaim.timer

# 彻底清除服务和删除任务文件
sudo systemctl disable --now oci-anti-reclaim.timer
sudo rm /etc/systemd/system/oci-anti-reclaim.service
sudo rm /etc/systemd/system/oci-anti-reclaim.timer
sudo systemctl daemon-reload
sudo rm /usr/local/bin/oci-anti-reclaim.sh

四、一键安装

如果不想使用上述方式,可以使用一键安装脚本,使用下面的命令进行一键安装,这样就不必手动执行 shell 命令了:

GitHub一键安装脚本仓库主页

必须以 root 运行命令,要提前使用 sudo -i 切到 root 用户,再运行下面的命令,否则会失败

sudo-i切换到root

cron 定时一键安装

bash <(curl -Ls https://raw.githubusercontent.com/closeblog/oci-cloud-anti-reclaim/refs/heads/main/install.sh) cron

安装完成,可以使用 sudo crontab -l 查看是否已加入计划任务
查看是否已加入计划任务

systemd 定时一键安装(推荐)

bash <(curl -Ls https://raw.githubusercontent.com/closeblog/oci-cloud-anti-reclaim/refs/heads/main/install.sh) systemd

安装完成可以使用以下命令查看本次是否运行及下次运行时间:

  • 看有没有已经跑过
    journalctl -u oci-anti-reclaim.service --since today
  • 看下一次计划触发的时间
    systemctl list-timers oci-anti-reclaim.timer --no-pager

五、几点补充

  • 脚本运行过程中可以使用 sudo top 命令查看资源实时使用情况

实例资源使用情况

  • 不用天天跑很久。判定标准是 95 百分位,每天 2 小时的高负载窗口通常就足够,不会明显影响实例上跑的其他正常业务。
  • 脚本首次运行会自动 apt install stress-ng,之后不再重复安装。
  • 如果实例本身已经有稳定的常驻负载(比如跑着个人网站或博客、Docker 容器等),可以先用 top 或 OCI 控制台的监控图表观察一下实际利用率,如果本身的正常业务能达到一定的负载,也就不需要额外的压力测试。
  • 这套方案是为了保住 Always Free 额度内的实例不被误判为”空闲”而设计,压力测试产生的都是真实负载,不涉及任何流量伪造。合理使用免费额度的同时,也别把实例资源占满影响自己其他正常业务。