Skip to content

Add gitlab-orphaned-job-canceller cronjob - #1415

Open
zackgalbreath wants to merge 4 commits into
mainfrom
cancel_orphaned_jobs
Open

Add gitlab-orphaned-job-canceller cronjob#1415
zackgalbreath wants to merge 4 commits into
mainfrom
cancel_orphaned_jobs

Conversation

@zackgalbreath

Copy link
Copy Markdown
Collaborator

We recently noticed some GitLab CI jobs get stuck in a "Running" state beyond their normal job timeout. We traced this back to jobs running on our Kubernetes runners that lose their backing pod without GitLab ever finding out. In at least one instance, this was caused by Karpenter wrongly deleting a node it thinks is empty: kubernetes-sigs/karpenter#2916.

This commit adds a new cronjob to mitigate this issue. It correlates running jobs against Kubernetes pods and cancels those whose pod is missing or already terminal. This new cronjob only cancels jobs (it doesn't restart them), and it only ever touches jobs from Spack group runners (not instance runners). This corresponds to our Kubernetes runners but not our on-premises runners at the University of Oregon.

We recently noticed some GitLab CI jobs get stuck in a "Running" state beyond
their normal job timeout. We traced this back to jobs running on our Kubernetes
runners that lose their backing pod without GitLab ever finding out.
In at least one instance, this was caused by Karpenter wrongly deleting a node
it thinks is empty: kubernetes-sigs/karpenter#2916.

This commit adds a new cronjob to mitigate this issue. It correlates running
jobs against Kubernetes pods and cancels those whose pod is missing or already
terminal. This new cronjob only cancels jobs (it doesn't restart them), and
it only ever touches jobs from Spack group runners (not instance runners).
This corresponds to our Kubernetes runners but not our on-premises runners
at the University of Oregon.
@kwryankrattiger

Copy link
Copy Markdown
Collaborator

It would also be good to include canceling any jobs in general that exceed the job timeout, regardless of where they are started from.

@zackgalbreath

Copy link
Copy Markdown
Collaborator Author

It would also be good to include canceling any jobs in general that exceed the job timeout, regardless of where they are started from.

I like this idea, but I don't think the GitLab API actually tells us when a job would timeout 🫠
https://docs.gitlab.com/api/jobs/#retrieve-a-job-by-job-id

failed_projects = []
for project in projects:
try:
cancel_orphaned_jobs(v1, project, args.grace_period_minutes)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth collecting these jobs and raising an error with them for sentry to report?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I added a sentry warning for orphaned jobs that get canceled & retried, and made it an error if we exhaust our retry cap. @jjnesbitt FYI. I think this is a rare enough defect that it won't flood sentry but I wanted to make you aware.

period (to avoid racing normal pod-scheduling delays), and its backing
pod either no longer exists, or has already reached a terminal phase
without GitLab having found out."""
if not is_kubernetes_executor_job(job):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can also check here if the runtime is longer than the expected max timeout of 12h?

Then, jobs orphaned by any non-k8s runners can be cancelled. I don't think I have seen these, but it seems to be possible during updates/unexpected downtime this type of thing can happen.

Comment thread images/gitlab-orphaned-job-canceller/main.py Outdated
@jjnesbitt

jjnesbitt commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Since this doesn't retry any jobs, if a job that would have been retried on failure is cancelled due to overrun, it won't be retried at all by any other service, including gitlab. Correct?

Are we just assuming these jobs will be retried manually? It may be hard to clearly identify which jobs require this, since they won't be obviously overrunning their timeout.

@zackgalbreath

Copy link
Copy Markdown
Collaborator Author

Since this doesn't retry any jobs, if a job that would have been retried on failure is cancelled due to overrun, it won't be retried at all by any other service, including gitlab. Correct?

Yes, that is my understanding as well.

Are we just assuming these jobs will be retried manually? It may be hard to clearly identify which jobs require this, since they won't be obviously overrunning their timeout.

The issue that this PR is mitigating is that jobs are stuck in a running state indefinitely. Because of our use of deferred pipelines, when this happens on a develop pipeline (like it did this morning) it has the affect of preventing any new PRs from getting tested/merged until we manually intervene.

At the risk of stating the obvious, jobs that run forever don't get automatically retried. That being said, I can look into adding auto-retry logic to this new cronjob if you think that's a good idea.

@kwryankrattiger

Copy link
Copy Markdown
Collaborator

I can look into adding auto-retry logic to this new cronjob if you think that's a good idea.

The 30 minute grace period probably handles most of these cases where gitlab would have retried the job itself, but since this is going to catch issues much earlier the pipeline is probably still useful the the developer so retrying would be good.

@jjnesbitt

Copy link
Copy Markdown
Collaborator

The issue that this PR is mitigating is that jobs are stuck in a running state indefinitely. Because of our use of deferred pipelines, when this happens on a develop pipeline (like it did this morning) it has the affect of preventing any new PRs from getting tested/merged until we manually intervene.

At the risk of stating the obvious, jobs that run forever don't get automatically retried. That being said, I can look into adding auto-retry logic to this new cronjob if you think that's a good idea.

I was mainly just trying to make sure we weren't taking a half-measure w.r.t the mitigation. Since the goal is to allow deferred pipelines to run, I think limiting the scope as you've done is perfectly fine.

Log a warning to sentry when a job is canceled and retried, and log an
error when the retry cap is reached.
@zackgalbreath

zackgalbreath commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

I can look into adding auto-retry logic to this new cronjob if you think that's a good idea.

Our f2f conversation about canceled jobs being confusing to users was enough to convince me that this is probably a good idea. This script now attempts to retry canceled jobs up to a limit of 3 total retries per job.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants