You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Restarting runed detaches every volume on the node while the containers using them keep running. Nothing coordinates the two, and nothing needs to have gone wrong for it to happen — it is the normal path, on every upgrade.
Split out of #270. The data-loss half is fixed in #271 (the filesystem is now flushed before the detach), so this is no longer silent corruption — but a running container is still left holding a filesystem whose block device has been taken away.
The sequence
Agent.shutdown (internal/agent/agent.go:317-325) stops subsystems in reverse registration order.
The volume subsystem's Stop drains every tracked mount (internal/agent/volumes/subsystem.go:287), which unmounts and detaches each volume.
Nothing stops the containers. There is no StopInstance/ContainerStop anywhere in the shutdown path — containers are owned by the Docker daemon and deliberately outlive runed. That is a feature: a control-plane restart should not take the workloads down.
So the unmount runs against a mount point a live container still has bound. umount(2) returns EBUSY, tearDown logs "Unmount failed; will still attempt Detach", and the detach proceeds so the volume is not stranded on a node that may be going away.
Consequences
The container keeps running against a filesystem whose device is gone. Writes it makes after the detach have nowhere to go; behaviour from there is the kernel's, not ours (I/O errors, or a read-only remount).
When runed comes back it re-attaches and re-mounts at the same host path. The container is not remounted — a bind captures the mount that existed when the container was created — so from then on the container and the host are looking at two different filesystems at the same path. The host sees a freshly mounted volume; the container sees the orphaned one. Nothing reports the divergence.
I suspect this is why Rune: file contents written to a gce-pd volume are lost #270 observed stat returning 0 bytes from the host on a volume the workload believed it had written. I have not proven that specific chain, so treat it as a hypothesis, not a finding.
Why the fix is not "stop the containers first"
Containers surviving a runed restart is the desired property; taking every workload down to shut the agent down cleanly would be a much worse trade.
The better question is why shutdown detaches at all. A volume needs detaching when it is moving — the node is being drained or the volume is rebinding elsewhere — not because a process on the node restarted. The adopt-attached path (#173) already brings an attached volume back with no API calls, so leaving mounts in place across a restart is both cheaper and safer than tearing them down and rebuilding them.
Suggested direction
Distinguish "this process is stopping" from "this node is giving up its volumes". On the former, leave mounts alone; on the latter, drain as today.
If a drain must happen while instances are live, skip teardown for volumes still referenced by a running local instance and say so, rather than detaching underneath them.
Whatever the policy, tearDown's "detach even though the unmount failed" should be an explicit, documented decision. It is defensible on a dying node and wrong on a healthy one; today it is unconditional and was never stated as a choice.
Summary
Restarting
runeddetaches every volume on the node while the containers using them keep running. Nothing coordinates the two, and nothing needs to have gone wrong for it to happen — it is the normal path, on every upgrade.Split out of #270. The data-loss half is fixed in #271 (the filesystem is now flushed before the detach), so this is no longer silent corruption — but a running container is still left holding a filesystem whose block device has been taken away.
The sequence
Agent.shutdown(internal/agent/agent.go:317-325) stops subsystems in reverse registration order.Stopdrains every tracked mount (internal/agent/volumes/subsystem.go:287), which unmounts and detaches each volume.StopInstance/ContainerStopanywhere in the shutdown path — containers are owned by the Docker daemon and deliberately outliveruned. That is a feature: a control-plane restart should not take the workloads down.So the unmount runs against a mount point a live container still has bound.
umount(2)returnsEBUSY,tearDownlogs "Unmount failed; will still attempt Detach", and the detach proceeds so the volume is not stranded on a node that may be going away.Consequences
runedcomes back it re-attaches and re-mounts at the same host path. The container is not remounted — a bind captures the mount that existed when the container was created — so from then on the container and the host are looking at two different filesystems at the same path. The host sees a freshly mounted volume; the container sees the orphaned one. Nothing reports the divergence.statreturning 0 bytes from the host on a volume the workload believed it had written. I have not proven that specific chain, so treat it as a hypothesis, not a finding.Why the fix is not "stop the containers first"
Containers surviving a
runedrestart is the desired property; taking every workload down to shut the agent down cleanly would be a much worse trade.The better question is why shutdown detaches at all. A volume needs detaching when it is moving — the node is being drained or the volume is rebinding elsewhere — not because a process on the node restarted. The adopt-attached path (#173) already brings an attached volume back with no API calls, so leaving mounts in place across a restart is both cheaper and safer than tearing them down and rebuilding them.
Suggested direction
tearDown's "detach even though the unmount failed" should be an explicit, documented decision. It is defensible on a dying node and wrong on a healthy one; today it is unconditional and was never stated as a choice.Related