Buildkitd logs "failed to kill process in container id <cid>: buildkit-runc did not terminate successfully: container not running" for hours #4483
Description
Hello! 👋
Problem
We are seeing the buildkitd logs full of the following lines:
level=debug msg="sending sigkill to process in container 7l2ykurlf4pziiza5pnf97raw"
level=error msg="failed to kill process in container id 7l2ykurlf4pziiza5pnf97raw: buildkit-runc did not terminate successfully: exit status 1: container not running\n"
The container id is always the same. These lines are repeated for many hours, sometimes days. The frequency is about 1000/minute (~15/s). We are seeing the buildkitd healthcheck fail, and it's not clear if the cause is these errors or if the errors are simply making it more difficult to determine the underlying cause.
A pod restart clears out the problem for a bit, but it generally happens again within a few hours.
Seems like the logs come from:
buildkit/executor/runcexecutor/executor.go
Lines 526 to 529 in f84cfe3
| bklog.G(ctx).Debugf("sending sigkill to process in container %s", k.id) | |
| defer func() { | |
| if err != nil { | |
| bklog.G(ctx).Errorf("failed to kill process in container id %s: %+v", k.id, err) |
Before the log spam starts I don't see anything particularlly interesting in the logs
debug time="2023-12-12T08:38:23Z" level=debug msg="removed snapshot" key=buildkit/10364/nekk455whng4seiauy6yz3w4k-view snapshotter=overlayfs
debug time="2023-12-12T08:38:23Z" level=debug msg="content garbage collected" d=29.017431ms
debug time="2023-12-12T08:38:23Z" level=debug msg="snapshot garbage collected" d=415.90514ms snapshotter=overlayfs
debug time="2023-12-12T08:38:23Z" level=debug msg="gc cleaned up 2099235886 bytes"
debug time="2023-12-12T08:39:20Z" level=debug msg="session finished: <nil>" spanID=e936167880750bc7 traceID=ec6a365de81b3b59b41ae2dc13e6e08c
debug time="2023-12-12T08:39:20Z" level=debug msg="sending sigkill to process in container 7l2ykurlf4pziiza5pnf97raw"
Details
Version: buildkit:v0.12.3-rootless (we also saw this problem with 0.11.0 before upgrade)
We run 4 buildkitd runner with --oci-worker-no-process-sandbox. All of these seem to manifest this behaviour at one time or another (not necessarily at the same time).
Any ideas about how to fix this? Thank you!
Activity
tonistiigi commentedon Dec 14, 2023
👋
@coryb Can you take a look? I've seen some of these logs myself but not in a loop as it is described here. I've also not been able to understand what exact case causes this. Even if there is some weird condition that stops from killing container cleanly, going to loop when the process is already gone should be avoided.
This might be related as it does change the parent-child structure for subprocess.
dnephin commentedon Dec 14, 2023
Looking at this for loop
buildkit/executor/runcexecutor/executor.go
Lines 615 to 637 in 438f472
We know that
killer.Killon line 619 is returning before the timeout with an error (the one we see in the logs). In the select on 620 that would mean it goes into thedefaultcase and does notcancelorreturn. Assumingp.endedon 631 is also not yet closed, that select on 629 would exit from thetime.After(50ms)case and also not return, and the loop would continue. I think this is roughly the frequency of logging we are seeing, and likely what's happening.p.endedI guess is closed byprocHandle.Release, which is deferred from the caller . If I'm reading this right the caller is supposed to be cancelled by thecancel()on 623, but in this flow it seems like it would be skipped. I'm not sure if there are other possible ways this context would be cancelled.I don't understand exactly the scenario that leads to this, but I believe this could explain the behaviour. Maybe before going into the select on 620 we could inspect the error and
cancelandreturnif the error indicates the container is already killed?I can also try to capture a goroutine stack dump if that would help.
tonistiigi commentedon Dec 14, 2023
I think this makes sense. Looks like the actual error is coming from runc directly (via
runc kill) so we do need either some string matching or some follow-uprunccommand to check that it doesn't see the container anymore.We do need to make sure that the container is actually cleaned up and this isn't some issue where
runc killdoes not work well with theoci-worker-no-process-sandboxflag.dnephin commentedon Jan 13, 2024
Thank you for confirming! This hasn't been a huge problem just yet, but I will be doing more work with our buildkit runners soon. I may come back to this and attempt a fix if it's getting in the way of the work I am doing.
fullykubed commentedon Jun 24, 2024
Confirmed still occurring in
v0.14.1-rootless.We are seeing this behavior occur when the
buildctlclient is terminated in the middle of aRUNstanza in a build.In addition to generating massive amounts of error messages, the buildkitd server can no longer terminate gracefully immediately. Instead it waits a few minutes before finally exiting with this message:
fullykubed commentedon Jun 24, 2024
Interestingly, it does resolve itself eventually, sometimes in as short as 30 seconds but other times continues for many minutes.
It does not appear to have any functional impact. The buildkitd daemon is still able to accept new builds even when this is occurring.
tonistiigi commentedon Jul 1, 2024
Looked into it more but still quite confusing what case this is.
Pretty clear it comes from this loop/sleep https://github.com/moby/buildkit/blob/v0.14.1/executor/runcexecutor/executor.go#L618-L633 , but this is only possible if
p.endedhas not been closed. Otherwise, you could get one or two such logs but not get stuck there.p.endedis closed in defer https://github.com/moby/buildkit/blob/v0.14.1/executor/runcexecutor/executor_linux.go#L61 . So it looks like something is blocked in this function and this goroutine is not returning.There is no stacktrace but possible candidates where the goroutine is stuck is
errgroup.Waitin https://github.com/moby/buildkit/blob/v0.14.1/executor/runcexecutor/executor_linux.go#L65-L78 or therunc.Run(). The errgroup does not seem possible, forRUNhandleSignalspath is skipped andWaitForStarthas own early return on cancellation.That leaves
runc.Run. Looking at library, I thought that maybe it hangs in https://github.com/containerd/go-runc/blob/main/runc.go#L341 channel send because the receiver side has already returned. But that doesn't seem possible because the channel is buffered https://github.com/moby/buildkit/blob/v0.14.1/executor/runcexecutor/executor_linux.go#L71 . I fixed another case #5106 when looking at this, that could have caused process cancellation to be skipped if it appeared before the process was started, but this should not affect this case in here. I also thought that maybe the stdout/stderr streams are blocked after cancellation andrunc.Runcan not return but does not look like this is possible.Then last remaining part is actual runc bug where
runc runprocess keeps running and doesn't return whilerunc killanwers "no such container running". That is possible in a small window but should not remain in error state repeatedly.So, although we could fix the log issue with some detection of specific error, it is not clear if the
runcprocess has actually returned, and if it has not then fixing the log would just hide the error and also avoid sending further kill commands. It should also mean that the build request is still hanging while the repeatedrunc killcommands are being attempted.16 remaining items
NiklasBr commentedon May 22, 2025
When I run this command:
For this Dockerfile:
I get the following output with error:
jsternberg commentedon May 14, 2026
@glightfoot if this is occurring consistently for you, can you run
kill -QUITon the dockerd process or buildkit process where the build is happening and send over the stack trace it prints? I believe the stack trace will probably end up in the log but it may also end up in another location. If you're using docker desktop, I think the process might be a little more involved and I can probably guide you through it after testing it myself.Go programs will print the stack trace of the goroutines when they are killed with that signal which is really useful for finding deadlocks.
I suspect this is occurring due to a deadlock that's happening somewhere in this area. It's possible it's earlier than that (somewhere else in
callWithIO) but that specific function is a very likely area.glightfoot commentedon May 14, 2026
@jsternberg unfortunately we don't have any indication when it's happening on the client, and run out buildkits centrally for all our users, so we have no clean way of generating that stacktrace on the client side. If you mean on the buildkit process itself, I can try to find one where its happening and generate one when I notice it again. Thanks for looking into it!
jsternberg commentedon May 14, 2026
Yea the buildkit process is where I'm looking. I'm also looking at the test case in your PR and I suspect the underlying problem might have something to do with
runtime.LockOSThread()and possiblyStartorWait. I saw in the test case in your PR that you identified things as being locked waiting forWait. Can you confirm that it is getting stuck inWait? That'll help me narrow down a bit what the underlying cause might be.jsternberg commentedon May 14, 2026
@glightfoot also what options do you run buildkit with? Are you using rootless and/or
--oci-process-no-sandboxlike the original issue creator was or in a different configuration?Looking into this more, I'm becoming more convinced the bug is in runc itself somewhere. Our current logging doesn't cover the case where the first kill signal gets sent successfully but runc fails to exit so I imagine there's a possibility that it sends kill to runc, the state somehow gets cleaned up but runc never returns or returns very slowly, and then subsequent kill requests return that the container doesn't exist anymore.
glightfoot commentedon May 14, 2026
We run the rootless version with
worker.oci.noProcessSandbox: truein the config. Yeah when I looked into it it also seemed like a bug in runc, but don't know the codebase well enough to be sureglightfoot commentedon May 14, 2026
Here is a stack trace from one of the nodes running on the latest buildkit (v0.30.0) that has spewed about 100 of these in the last hour
buildkit-stacktrace.txt
jsternberg commentedon May 14, 2026
Thanks. That was really helpful. Do you know how many cores there are on the machine that created this? Or if you've overwritten
GOMAXPROCSwhatever that value is.glightfoot commentedon May 14, 2026
It's running on an m7i.8xlarge (32 vCPU) with no overrides for GOMAXPROCS
jsternberg commentedon May 14, 2026
Since you are running this with no process sandbox, I assume that you can see the hanging
runcprocesses in the host pid namespace. If you useps -eto see all processes, can you see anyruncprocesses? If so, can you also try usingkill -QUITon them? I don't know where they'd print whatever they're going to print but maybe we'll be able to see something.glightfoot commentedon May 14, 2026
I don't see any hung runc processes - just a few
buildkit-runcthat spawn and finish over a few seconds to minutes. Although, I might have just been too late in getting to the nodeglightfoot commentedon May 14, 2026
I think that's the issue - runc exits and buildkit still thinks it's running and tries to kill something that isn't there
jsternberg commentedon May 15, 2026
@glightfoot when buildkit prints the container it failed to kill, such as in this log message from another person:
Is the id there the same for longer than 5 minutes? Or does the container id change over time? I'm wondering if there's no deadlock here. It's just runc takes a little while to shut down and we aren't patient or coerce enough with logging. As with the current iteration, we'll spam the log every 50 ms with an unnecessary "could not kill container" over and over again until runc exits properly even if it's currently in the process of exiting.
jsternberg commentedon May 15, 2026
In case my guess from the previous comment is correct, I refactored how the runc executor logs this event to either make it more clear what's happening (if this is what's happening) or to at least give us some more information in #6779.
dany74q commentedon Aug 27, 2026
Under no-process-sandbox runc kill only reaches init and surviving subprocesses can hold the container open - #7087 kills the process group instead, should prob. fix it.