juju-controller-evict removes a permanently dead Juju 3.6 HA controller from a live controller cluster without starting the dead machine again.
This is a recovery tool for affected Juju 3.6 controllers. It edits Juju state in MongoDB and removes a Dqlite member. Use it only when the controller machine is permanently unavailable.
Use this tool only when all of these conditions are true:
- The controller machine will not return.
- You already ran
juju remove-machine <id> -m <controller>:controller --force. - At least one controller is still running.
- The dead MongoDB member reports
DOWNorUNKNOWNin repeated checks.
If the machine can be recovered, start it and let Juju remove it normally.
Run the binary from a Juju client logged in as a controller administrator.
# Check the removal plan for machine 1 without changing anything.
juju-controller-evict -controller mycontroller -machine 1
# Apply the plan.
juju-controller-evict -controller mycontroller -machine 1 -yes
Without -yes, the tool does not change MongoDB or Dqlite.
The client copies the binary to a surviving controller and runs it there with sudo. The binary must be built for the controller architecture, usually Linux amd64 or arm64.
You can also run it directly on a surviving controller:
sudo ./juju-controller-evict -machine 1 -yes
Run it directly on a surviving controller when MongoDB has no primary. Client mode depends on the Juju API, which may be unavailable after MongoDB loses quorum.
If the dead MongoDB member still has a vote and blocks the peer grouper, the tool first tries a normal replica-set reconfig. It uses a forced reconfig only when MongoDB rejects the normal attempt with a quorum-check failure. If no primary exists, the tool connects directly to the local MongoDB member and plans a forced reconfig. Every status sample must show that no primary exists. The tool samples the replica-set status again before any forced attempt. The remaining healthy voters must retain a majority and include a member that can become primary.
If the replica-set change succeeds but the command stops before Juju cleanup, run the same command again. The tool matches the machine addresses to the remaining Dqlite node and continues after the MongoDB member has gone.
The tool then removes the dead controller unit documents that block Juju cleanup. Juju removes the controller reference, and the tool marks the machine Dead so the provisioner can finish removing it.
It also removes the matching Dqlite node from the cluster.
Before changing MongoDB, the tool writes the original replica-set config when applicable, the selected unit documents, the original machine document, and the application documents to a JSON file. The controller-side file is created with permissions restricted to its owner. In client mode, it is copied to the path passed with -backup, which defaults to juju-controller-evict-backup.json.
The tool refuses to apply changes unless:
-yesis present.- The target is not the controller running the tool.
- The MongoDB member is unhealthy and remains
DOWNorUNKNOWNacross three checks. - A direct MongoDB connection is used only when every check reports no primary.
- A forced replica-set reconfig has no persistently unhealthy voter outside the removal target.
- The remaining healthy voters can form a majority and include a primary-eligible member.
- The target Dqlite node is not the current leader.
- The target no longer answers on its Dqlite port.
This tool changes controller state directly. The JSON file is a record of the affected MongoDB state, not an automatic rollback.
Watch juju status until the machine disappears. Then restore the controller voter count:
juju enable-ha -c mycontroller
-controllerselects the controller in client mode. It defaults to the current controller.-machineis required and selects the dead controller machine.-yesapplies the plan. Without it, the tool only reports the plan.-backupselects the JSON backup path. The default isjuju-controller-evict-backup.json.-timeoutsets the timeout shared by Dqlite calls. The default is two minutes.-versionprints the build version.
Run juju-controller-evict -help for the exact option syntax.
- Tested with Juju 3.6 only.
- Removes one dead controller at a time.
- Requires a live primary-eligible MongoDB member and a Dqlite leader on the surviving controllers.
Do not run these tests on a production controller. The tests remove controller machines and may force a MongoDB replica-set reconfig.
Test the normal reconfig path with three voting controllers:
- Stop one controller machine at the provider.
- Run
juju remove-machine <id> -m <controller>:controller --force --no-promptwhile the other two controllers still have quorum. - Run
juju-controller-evict -controller <controller> -machine <id>. An immediate run may refuse while the member is still transitioning. Wait until all samples report it down, then check that the plan saysremove replica set member. - Re-run with
-yes. - Check that
juju status -m <controller>:controllerno longer lists the machine. - Run
juju enable-ha -c <controller>and check that three controllers become available again.
Test the no-primary force path from a disposable fixture snapshot that has exactly two voting MongoDB members and a pending forced machine-removal request. This topology is only for exercising the recovery path. Do not create it on a controller that holds useful models.
- Stop one voting controller so the survivor becomes
SECONDARYand the stopped member becomesDOWN. - Copy the binary to the surviving controller through the provider. The Juju API may be unavailable.
- Run
sudo ./juju-controller-evict -machine <id>on the survivor. - Check that the plan says
force-remove replica set memberandno primary is available. - Re-run with
-yesand keep the JSON backup. - Check that the survivor becomes
PRIMARYand that MongoDB and Dqlite each contain only the survivor. - Check that the removed machine is absent from
juju status -m <controller>:controller. - Run
juju enable-ha -c <controller>and check that three controllers become available again.
Do not reproduce every rejection case by damaging a cluster. Run go test ./.... The unit tests exercise sampling decisions for a recovering target, another unhealthy voter, an unstable majority, no primary-eligible member, and a primary that appears in one sample. They also check that authentication and TLS errors are not mistaken for a topology failure, reject ambiguous Dqlite addresses, and reject a cleanup plan when its captured state changes.
CGO_ENABLED=0 go build -o juju-controller-evict .
Apache-2.0. See LICENSE.