|
| 1 | +# rock/admin/scheduler/tasks/container_cleanup_task.py |
| 2 | +from rock import env_vars |
| 3 | +from rock.admin.proto.request import SandboxCommand as Command |
| 4 | +from rock.admin.scheduler.task_base import BaseTask, IdempotencyType, TaskStatusEnum |
| 5 | +from rock.common.constants import PID_PREFIX, PID_SUFFIX, SCHEDULER_LOG_NAME |
| 6 | +from rock.logger import init_logger |
| 7 | +from rock.sandbox.remote_sandbox import RemoteSandboxRuntime |
| 8 | +from rock.utils.system import extract_nohup_pid |
| 9 | + |
| 10 | +logger = init_logger(name="container_cleanup", file_name=SCHEDULER_LOG_NAME) |
| 11 | + |
| 12 | + |
| 13 | +class ContainerCleanupTask(BaseTask): |
| 14 | + """Scheduled task for cleaning up stopped Docker containers older than a specified age.""" |
| 15 | + |
| 16 | + def __init__( |
| 17 | + self, |
| 18 | + interval_seconds: int = 86400, |
| 19 | + max_age_hours: int = 24, |
| 20 | + ): |
| 21 | + """ |
| 22 | + Initialize container cleanup task. |
| 23 | +
|
| 24 | + Args: |
| 25 | + interval_seconds: Execution interval in seconds, default 24 hours (86400s) |
| 26 | + max_age_hours: Max container age in hours since it stopped, default 24 hours |
| 27 | + """ |
| 28 | + super().__init__( |
| 29 | + type="container_cleanup", |
| 30 | + interval_seconds=interval_seconds, |
| 31 | + idempotency=IdempotencyType.IDEMPOTENT, |
| 32 | + ) |
| 33 | + self.max_age_hours = max_age_hours |
| 34 | + |
| 35 | + @classmethod |
| 36 | + def from_config(cls, task_config) -> "ContainerCleanupTask": |
| 37 | + """Create task instance from config.""" |
| 38 | + max_age_hours = task_config.params.get("max_age_hours", 24) |
| 39 | + return cls( |
| 40 | + interval_seconds=task_config.interval_seconds, |
| 41 | + max_age_hours=max_age_hours, |
| 42 | + ) |
| 43 | + |
| 44 | + async def run_action(self, runtime: RemoteSandboxRuntime) -> dict: |
| 45 | + """Run container cleanup action. |
| 46 | +
|
| 47 | + Removes exited Docker containers whose finish time exceeds max_age_hours. |
| 48 | + """ |
| 49 | + log_dir = env_vars.ROCK_LOGGING_PATH if env_vars.ROCK_LOGGING_PATH else "/tmp" |
| 50 | + command = ( |
| 51 | + f"nohup bash -c '" |
| 52 | + f"docker ps -aq --filter status=created | xargs -r docker rm; " |
| 53 | + f'cutoff=$(date -d "{self.max_age_hours} hours ago" +%s); ' |
| 54 | + f"docker ps -aq --filter status=exited | " |
| 55 | + f'xargs -r docker inspect --format "{{{{.Id}}}} {{{{.State.FinishedAt}}}}" | ' |
| 56 | + f"while read -r id finished; do " |
| 57 | + f'[ "$finished" = "0001-01-01T00:00:00Z" ] && continue; ' |
| 58 | + f'finished_ts=$(date -d "$finished" +%s 2>/dev/null) || continue; ' |
| 59 | + f'[ "$finished_ts" -lt "$cutoff" ] && docker rm "$id"; ' |
| 60 | + f"done" |
| 61 | + f"' > {log_dir}/container_cleanup.log 2>&1 & echo {PID_PREFIX}${{!}}{PID_SUFFIX}" |
| 62 | + ) |
| 63 | + |
| 64 | + result = await runtime.execute(Command(command=command, shell=True)) |
| 65 | + |
| 66 | + pid = extract_nohup_pid(result.stdout) |
| 67 | + logger.info( |
| 68 | + f"container cleanup task [{pid}] run successfully on worker[{runtime._config.host}], " |
| 69 | + f"max_age_hours={self.max_age_hours}" |
| 70 | + ) |
| 71 | + |
| 72 | + return { |
| 73 | + "pid": pid, |
| 74 | + "max_age_hours": self.max_age_hours, |
| 75 | + "status": TaskStatusEnum.RUNNING, |
| 76 | + } |
0 commit comments