教程:OpenBayes 容器延时关闭工具
这是一个用于设置 OpenBayes 容器延时关闭的工具。当你需要在特定时间后自动停止容器时特别有用,比如在长时间训练任务完成后自动关闭容器。
主要功能
- 设置容器在指定时间后自动关闭
- 支持小时、分钟和秒的时间设置
- 实时显示当前时间和倒计时(每秒更新)
- 支持随时取消预定的关闭
- 支持非容器环境下的模拟运行模式
使用方法
推荐使用 tmux 运行此程序,这样可以:
- 防止 SSH 连接断开导致程序退出
- 方便随时重新连接查看状态
- 需要时可以取消预定的关闭
创建或连接到 tmux 会话
要创建一个新的 tmux 会话,使用以下命令:
tmux new -s delayed-shutdown # 统一会话名称与后续示例一致
执行命令
# 代码实际使用 delayed_stop.py 而非 delayed_close.py
python delayed_stop.py 2h # 30m示例保持单位一致性
python delayed_stop.py 30m # 原文档使用30min不符合代码要求
python delayed_stop.py 45s
输出示例
运行脚本后,你将看到如下输出:
Scheduled shutdown details:
Shutdown scheduled at: 2024-03-14 17:30:00 (UTC+8)
Press Ctrl+C to cancel.
Current time: 2024-03-14 15:30:00 (UTC+8)
Time until shutdown: 2 hours
Press Ctrl+C to cancel.
显示会实时更新:
- 当前时间每秒更新一次
- 剩余时间每秒更新一次
- 取消提示始终显示
- 使用动态刷新方式,保持屏幕整洁
注意事项
- 无需安装额外依赖
- 时间值必须是整数(例如:
2h
有效,但2.5h
无效) - 每次只能指定一个时间单位
- 在容器环境外运行时会进入模拟模式,不会真正执行关闭操作
- 重要:直接在 SSH 中运行程序时,如果连接断开,程序会退出,预定的关闭任务将不会执行
- 建议:使用 tmux 运行程序,这样即使 SSH 连接断开,程序也会继续在后台运行
tmux 常用操作
tmux new -s delayed-shutdown
: 创建新的会话tmux attach -t delayed-shutdown
: 连接到已有会话Ctrl+B
然后D
: 分离当前会话(程序继续在后台运行)Ctrl+B
然后[
: 进入滚动模式查看历史输出tmux ls
: 列出所有会话tmux kill-session -t delayed-shutdown
: 结束会话(会终止正在运行的程序)
附件
delayed_stop.py
代码
import time
import os
import sys
import datetime
import signal
import subprocess
from datetime import datetime, timedelta
import re
def upgrade_bayes_cli():
"""Upgrade bayes CLI to the latest version"""
try:
print("Upgrading bayes CLI...")
subprocess.run(["bayes", "upgrade"], check=True)
print("Upgrade completed.")
except subprocess.CalledProcessError:
print("Warning: Failed to upgrade bayes CLI. Continuing with current version.")
except FileNotFoundError:
print("Warning: bayes CLI not found. Please make sure it's installed.")
def parse_time(delay):
"""Parse time format, e.g., 2h, 30m, 45s"""
# Define valid time units
units = {
"h": 3600, # hours
"m": 60, # minutes
"s": 1 # seconds
}
# Validate format using regex
pattern = r'^\d+[hms]$'
if not re.match(pattern, delay):
print(f"Error: Invalid time format '{delay}'")
print("Valid formats: <number>[h|m|s] (e.g., 2h, 30m, 45s)")
sys.exit(1)
# Extract number and unit
number = int(delay[:-1])
unit = delay[-1]
if unit not in units:
print(f"Error: Invalid time unit '{unit}'")
print("Valid units: h (hours), m (minutes), s (seconds)")
sys.exit(1)
return number * units[unit]
def format_time(timestamp):
"""Format timestamp to local time string (UTC+8)"""
utc_time = datetime.utcfromtimestamp(timestamp)
local_time = utc_time + timedelta(hours=8)
return local_time.strftime('%Y-%m-%d %H:%M:%S (UTC+8)')
def shutdown():
"""Execute shutdown command"""
print("\nStopping Service...")
if OPENBAYES_JOB_ID:
subprocess.run(["bayes", "gear", "stop", OPENBAYES_JOB_ID], check=True)
else:
print("This is a dummy run - no actual shutdown will be performed")
def signal_handler(sig, frame):
"""Capture Ctrl+C to cancel the task"""
print("\nShutdown canceled.")
sys.exit(0)
def format_time_remaining(seconds):
"""Format remaining time in a human-readable way"""
hours = seconds // 3600
minutes = (seconds % 3600) // 60
secs = seconds % 60
parts = []
if hours > 0:
parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
if minutes > 0:
parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
if secs > 0:
parts.append(f"{secs} second{'s' if secs != 1 else ''}")
return " ".join(parts)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: delayed_stop.py <time_delay> (e.g., 2h, 1h, 30min, 45s)")
sys.exit(1)
delay = sys.argv[1]
OPENBAYES_JOB_ID = os.getenv("OPENBAYES_JOB_ID", "")
# Upgrade bayes CLI first
if OPENBAYES_JOB_ID:
upgrade_bayes_cli()
# Get current and shutdown timestamps
current_timestamp = time.time()
wait_seconds = parse_time(delay)
shutdown_timestamp = current_timestamp + wait_seconds
if not OPENBAYES_JOB_ID:
print("Notice: Running in dummy mode - no actual shutdown will be performed")
# Initial display
print(f"\nScheduled shutdown details:")
print(f"Shutdown scheduled at: {format_time(shutdown_timestamp)}")
print("\nPress Ctrl+C to cancel.\n")
print("\033[?25l", end="") # Hide cursor
# Listen for Ctrl+C
signal.signal(signal.SIGINT, signal_handler)
try:
while wait_seconds > 0:
# Clear previous lines and move cursor up
print("\033[2K\033[A" * 3, end="")
# Update current time and remaining time
current_time = time.time()
print(f"Current time: {format_time(current_time)}")
print(f"Time until shutdown: {format_time_remaining(wait_seconds)}")
print("Press Ctrl+C to cancel.")
time.sleep(1)
wait_seconds -= 1
print("\033[?25h", end="") # Show cursor
shutdown()
except KeyboardInterrupt:
print("\033[?25h", end="") # Show cursor
signal_handler(None, None)
except Exception as e:
print("\033[?25h", end="") # Show cursor
raise e