Skip to content

docker kill: fix bug where failed kills didnt fallback to unix kill#41588

Merged
thaJeztah merged 1 commit into
moby:masterfrom
sparrc:kill-refactor
May 25, 2021
Merged

docker kill: fix bug where failed kills didnt fallback to unix kill#41588
thaJeztah merged 1 commit into
moby:masterfrom
sparrc:kill-refactor

Conversation

@sparrc

@sparrc sparrc commented Oct 24, 2020

Copy link
Copy Markdown
  1. fixes docker kill does not execute "direct kill" if initial sigkill fails #41587
  2. removes potential infinite Wait and goroutine leak at end of kill
    function
  3. refactored container state waiting out of container_operations_unix.go
    and into kill.go for consistency

fixes #41587

Signed-off-by: Cam [email protected]

before change

docker kill would just return error if the first sigkill function failed:

% docker kill kt
Error response from daemon: Cannot kill container: kt: foo error

after change

docker falls back to unix kill and container exits:

% docker kill kt
kt

docker logs:

Oct 24 20:54:08 ip-10-0-0-157.us-west-2.compute.internal dockerd[13955]: time="2020-10-24T20:54:08.239338811Z" level=error msg="Container failed to exit within 10 seconds of kill - trying direct SIGKILL" container=f1df7d8bafba1d39124ff7c1edf32c9e1527e318f4df4332ffbcba537c5fc798 error="context deadline exceeded"
Oct 24 20:54:08 ip-10-0-0-157.us-west-2.compute.internal dockerd[13955]: time="2020-10-24T20:54:08.300068287Z" level=info msg="ignoring event" module=libcontainerd namespace=moby topic=/tasks/delete type="*events.TaskDelete"

- Description for the changelog

fix bug where docker kill would not properly fallback to unix sigkill

- A picture of a cute animal (not mandatory but encouraged)

Comment thread daemon/kill.go Outdated
@sparrc

sparrc commented Oct 27, 2020

Copy link
Copy Markdown
Author

appears to be only the s390x integration tests failing again

@sparrc

sparrc commented Feb 16, 2021

Copy link
Copy Markdown
Author

hello, we are continuing to see this issue in ECS, any chance we could get some eyes on this fix?

@zuzzas

zuzzas commented Feb 19, 2021

Copy link
Copy Markdown

Can an issue describe here manifest if a PID does not exist? We've hit the exact same problem, but there was not a single process in the crontainer even present on the system. I can see guarding ifs, but seems like there is a problem with the logic.

Server: Docker Engine - Community
 Engine:
  Version:          18.09.7
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.8
  Git commit:       2d0083d
  Built:            Thu Jun 27 17:23:02 2019
  OS/Arch:          linux/amd64
  Experimental:     false

@samuelkarp samuelkarp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the code mostly looks good, I'm just not quite clear on one of the changes that was made.

Comment thread daemon/kill.go Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It looks like this is moving from one magic number to another, but...why 10 seconds here?

Is the goal to move the 10s wait from killProcessDirectly to here? Can you tell me a bit more about why you wanted to make that change?

@sparrc sparrc Mar 8, 2021

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes you are correct about the goal. The reason that was done was just for consistency. If you look across the functions in kill.go there are 3 other calls to container.Wait.

In container_operations_unix.go there were no other calls to container.Wait except here in killProcessDirectly.

It seemed better to keep the waiting functions contained to kill.go just for consistency rather than have a call to container.Wait at two different levels on the call stack.

@mmerkes

mmerkes commented Mar 17, 2021

Copy link
Copy Markdown

@sparrc @samuelkarp Along with this change, would love to see this get merged.

1. fixes moby#41587
2. removes potential infinite Wait and goroutine leak at end of kill
function

fixes moby#41587

Signed-off-by: Cam <[email protected]>
@sparrc

sparrc commented Apr 14, 2021

Copy link
Copy Markdown
Author

thanks so much @tianon and @cpuguy83 for reviewing and merging #41586 !

Could I ask that you please take a look at this PR too while it's fresh? It is highly related and also a situation that we regularly see customers hitting at ECS.

@sparrc

sparrc commented Apr 20, 2021

Copy link
Copy Markdown
Author

Hello, just wanted to ping to see if this PR could be reviewed, thank you!

@cpuguy83 cpuguy83 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it makes the existing intent of the code more robust so that at least looks good.

However it seems like if we are getting to the point where we have to call killProcessDirectly, we should instead be calling task.Delete in containerd instead which will (should) ensure that certain sticky situations are cleaned up (like a blocked runc-init) and work more correctly for other runtimes.

@kolyshkin, thoughts?

return err
}
if isZombie {
return errdefs.System(errors.Errorf("container %s PID %d is zombie and can not be killed. Use the --init option when creating containers to run an init inside the container that forwards signals and reaps processes", stringid.TruncateID(container.ID), pid))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

side note: I don't think this error is accurate since the container PID would itself only be a zombie if the shim exited early for whatever reason.

@sparrc

sparrc commented Apr 20, 2021

Copy link
Copy Markdown
Author

@cpuguy83 would it be possible to consider that question in a follow-up PR? I think it's worth breaking that out into two changes. Reason being that this PR mostly retains the same behavior as before while removing the potential for the docker kill function to hang forever.

Changing from a unix kill -9 to task.Delete seems like a larger conceptual change to me that maybe shouldn't be bundled in with this refactor?

@cpuguy83

Copy link
Copy Markdown
Member

@sparrc IMO, if we are going to introduce a behavior change here (which has been mostly static for awhile), I'd prefer to do it once.

@sparrc

sparrc commented Apr 22, 2021

Copy link
Copy Markdown
Author

This looks like it makes the existing intent of the code more robust so that at least looks good.

However it seems like if we are getting to the point where we have to call killProcessDirectly, we should instead be calling task.Delete in containerd instead which will (should) ensure that certain sticky situations are cleaned up (like a blocked runc-init) and work more correctly for other runtimes.

@kolyshkin, thoughts?

OK, just to clarify what you mean by task.Delete, is your suggestion that in kill.go instead of falling back to killProcessDirectly (ie here on master), we instead call out to daemon.containerd.DeleteTask() ? Or would containerd.Delete() be better? (Im not sure exactly the distinction between these).

If that's the case then I guess we could just delete killProcessDirectly entirely, as it wouldn't be used anywhere anymore.

@kzys

kzys commented Apr 22, 2021

Copy link
Copy Markdown
Member

containerd's task deletion is doing some checks and asks clients to stop the task beforehand.

If your container is still running;

% sudo ctr image pull docker.io/library/debian:latest
...                             
unpacking linux/amd64 sha256:ba4a437377a0c450ac9bb634c3754a17b1f814ce6fa3157c0dc9eef431b29d1f...
done
% sudo ctr run --rm --tty docker.io/library/debian:latest debian
root@ip-172-31-25-68:/# 

Deletion wouldn't work.

% sudo ctr t rm debian
ERRO[0000] unable to delete debian                       error="task must be stopped before deletion: running: failed precondition"
ctr: task must be stopped before deletion: running: failed precondition
%

Falling back to containerd-level task deletion may introduce more error cases, like what if containerd is unhealthy. Using task.Kill() is possible but containerd's health is still a concern.

Since this is a fallback case, sending SIGKILL seems much robust to me.

@sparrc

sparrc commented Apr 28, 2021

Copy link
Copy Markdown
Author

@cpuguy83 @kolyshkin thoughts?

@sparrc

sparrc commented May 11, 2021

Copy link
Copy Markdown
Author

@cpuguy83 @kolyshkin thoughts on changing this pid kill -9 to containerd task delete? I asked @kzys to take a look since he is a coworker of mine who is a containerd maintainer, in case you were wondering the context of his comment above.

@cpuguy83 cpuguy83 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

}

// In case there were some exceptions(e.g., state of zombie and D)
if system.IsProcessAlive(pid) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems redundant/not needed with the zombie check.

@sparrc

sparrc commented May 13, 2021

Copy link
Copy Markdown
Author

@tianon could you take a look at this? it is similar in concept to #41586 but applies to docker kill instead of docker stop

@sparrc

sparrc commented May 20, 2021

Copy link
Copy Markdown
Author

very keen to get this merged if anyone from docker has time to take a look, happy to walk through the changes if it would help, you can find me in the Gophers and Docker Community slack channels.

@samuelkarp samuelkarp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@tianon tianon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, although I would love (as I've noted in #41587) if we could find a way to reproduce so we can more definitively answer @cpuguy83's questions about whether the fallback block is necessary. 😅

@thaJeztah

Copy link
Copy Markdown
Member

@tonistiigi interesting error on s390x; https://ci-next.docker.com/public/blue/rest/organizations/jenkins/pipelines/moby/branches/PR-41588/runs/6/nodes/181/log/?start=0

[2021-05-20T18:45:51.977Z] + docker build --force-rm --build-arg APT_MIRROR -t docker:593c9f3edde77558d171fd029ef9773426204ef3 .
[2021-05-20T18:45:51.977Z] #1 [internal] load .dockerignore
[2021-05-20T18:45:51.977Z] #1 ERROR: proto: CacheRecords: wiretype end group for non-group
[2021-05-20T18:45:51.977Z] 
[2021-05-20T18:45:51.977Z] #2 [internal] load build definition from Dockerfile
[2021-05-20T18:45:51.977Z] #2 transferring dockerfile: 17.07kB done
[2021-05-20T18:45:51.977Z] #2 DONE 0.0s
[2021-05-20T18:45:51.977Z] ------
[2021-05-20T18:45:51.977Z]  > [internal] load .dockerignore:
[2021-05-20T18:45:51.977Z] ------
[2021-05-20T18:45:51.977Z] failed to solve with frontend dockerfile.v0: failed to build LLB: proto: CacheRecords: wiretype end group for non-group
script returned exit code 1

@sparrc

sparrc commented May 21, 2021

Copy link
Copy Markdown
Author

@thaJeztah could that be related to this change or do you think it's a flakey failure?

@thaJeztah

Copy link
Copy Markdown
Member

Definitely not related, but hadn't seen that failure before (we were discussing some breaking changes in gRPC recently though, and this felt possibly related)

Let me check CI and restart

@thaJeztah thaJeztah left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

func killProcessDirectly(container *container.Container) error {
pid := container.GetPID()
// Ensure that we don't kill ourselves
if pid == 0 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not for this PR, but we should consider making this

Suggested change
if pid == 0 {
if pid <= 0 {

I recall a situation where -1 was returned (by containerd?) because it was initialised with -1, and killing -1 does "fun things" 😅

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docker kill does not execute "direct kill" if initial sigkill fails

9 participants