Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

Status

Current state: Under DiscussionAccepted

Discussion thread: https://lists.apache.org/thread/kc87jkgyvf9x7nwmgwrhx6fs6w0tqymj

Vote thread: https://lists.apache.org/thread/rdpjmmqdxzog2m555r2wrncfn40zjf54

JIRA:

Jira
serverASF JIRA
serverId5aa69414-a9e9-3523-82ec-879b028fb15b
keyKAFKA-20395

...

However, there is currently no way to unregister a controller, like there is for brokers via UnregisterBrokerRequest and UnregisterBrokerRecord. This means a stale controller registration from a controller that no longer exists registrations can block feature upgrades. This KIP proposes adding support for operators to manually unregister controllers like they can with brokers.

One important use case for this KIP is to remove controller registrations from KRaft observers (i.e. nodes that replicate the log but do not participate in leader election or committing data) from the metadata log. This means operators can remove these stale registrations to unblock feature upgrades on their cluster.

Public Interfaces

New RPC 

...

Code Block
{
  "apiKey": 93,
  "type": "request",
  "listeners": ["broker", "controller"],
  "name": "UnregisterControllerRequest",
  "validVersions": "0",
  "flexibleVersions": "0+",
  "fields": [
    { "name": "ControllerId", "type": "int32", "versions": "0+",
      "about": "The controller ID to unregister." }
  ]
}

This request can return the following errors:

  • an UNSUPPORTED_VERSION  error if the cluster's MetadataVersion does not support UnregisterControllerRecord 
  • a CONTROLLER_ID_NOT_REGISTERED  error if no registration exists for the requested controller ID. This is similar to the BROKER_ID_NOT_REGISTERED error in the case of brokers.
  • a NOT_CONTROLLER  error if the request does not arrive at the active controller
  • an INVALID_REQUEST error if the request arrives at the active controller. This is known to be a "mistaken" request, as it is expected controllers are not running when they are unregistered. See the User Experience for more details.

UnregisterControllerResponse

Code Block
{
  "apiKey": 93,
  "type": "response",
  "name": "UnregisterControllerResponse",
  "validVersions": "0",
  "flexibleVersions": "0+",
  "fields": [
    { "name": "ThrottleTimeMs", "type": "int32", "versions": "0+",
      "about": "Duration in milliseconds for which the request was throttled due to a quota violation, or zero if the request did not violate any quota." },
    { "name": "ErrorCode", "type": "int16", "versions": "0+",
      "about": "The error code, or 0 if there was no error." },
    { "name": "ErrorMessage", "type": "string", "versions": "0+", "nullableVersions": "0+",
      "about": "The top-level error message, or `null` if there was no top-level error." }
  ]
}

Public APIs

Admin.java

Code Block
	/**
     * Unregister a controller.
     *
     * This is a convenience method for {@link #unregisterController(int, UnregisterControllerOptions)}
     *
     * @param controllerId  the controller id to unregister.
     *
     * @return the {@link UnregisterControllerResult} containing the result
     */
    default UnregisterControllerResult unregisterController(int controllerId) {
        return unregisterController(controllerId, new UnregisterControllerOptions());
    }

    /**
     * Unregister a controller.
     *
     * The following exceptions can be anticipated when calling {@code get()} on the future from the
     * returned {@link UnregisterControllerResult}:
     * <ul>
     *   <li>{@link org.apache.kafka.common.errors.TimeoutException}
     *   If the request timed out before the describe operation could finish.</li>
     *   <li>{@link org.apache.kafka.common.errors.UnsupportedVersionException}
     *   If the software is too old to support the unregistration API.
     * </ul>
     * <p>
     *
     * @param controllerId  the controller id to unregister.
     * @param options       the options to use.
     *
     * @return the {@link UnregisterControllerResult} containing the result
     */
    UnregisterControllerResult unregisterController(int controllerId, UnregisterControllerOptions options);

New Metadata Record

UnregisterControllerRecord

...

Code Block
kafka-cluster unregister-controller --controller-id 99901

When the user executes this command to unregister controller 1:

  1. UnregisterControllerRequest is sent to the active controller
  2. The active controller writes an UnregisterControllerRecord to the metadata log
  3. When this record is committed, return a response to the user for unregistering the controller
  4. The active controller's state machine removes the registration for controller 1, meaning feature upgrades no longer consider node 1's supported features
  5. The registration from controller 1 is removed from the metadata image

kafka-cluster unregister-controller  is a command for users when they want to unregister a controller from the cluster. This command should only be run after a controller is stopped, and the operator does not intend to bring it back. kafka-cluster unregister-controller  works irrespective of the quorum mode. 

kafka-metadata-quorum 

Add the --unregister flag to the kafka-metadata-quorum remove-controller command. When this flag is set, invoking this command with --unregister set will remove the controller as a KRaft voter and unregister it. Below is an example invocation.

Code Block
kafka-metadata-quorum remove-controller --controller-id 99901 --controller-directory-id EXAMPLE_UUID --unregister

When the user executes this command, kafka tries to remove 1 as a voter AND unregister it:

  1. RemoveRaftVoterRequest  is sent to the active controller
  2. The KRaft leader writes a VotersRecord without voter 1 to the metadata log
  3. When this record is committed, return a response to the user for removing the voter
  4. UnregisterControllerRequest is sent to the active controller if steps 1-3 were successful
    1. If steps 1-3 were not successful, return the error and direct the user to use kafka-cluster unregister-controller instead.
  5. The active controller writes an UnregisterControllerRecord to the metadata log
  6. When this record is committed, return a response to the user for unregistering the controller
  7. The active controller's state machine removes the registration for controller 1, meaning feature upgrades no longer consider node 1's supported features
  8. The registration from controller 1 is removed from the metadata image

The main use case for this command is to remove a node from the voter set in a dynamic KRaft quorum, AND unregister it from the cluster all within the same CLI command. Since this is a common usage pattern, running this command with --unregister can be thought of as a "built-in" script that provides a smooth UX for decommissioning voters in a dynamic quorum. Running the command with --unregister will fail when the cluster does not support dynamic quorum to be consistent with the behavior of the command when --unregister is not set.

User Experience

The main use cases of these CLI tools are listed below. When trying to unregister a controller, it is assumed that the operator has stopped a node before unregistering it and does not intend to bring that node back in the near future. This is because after unregistering a node, the active controller no longer checks its supported feature levels when validating a feature upgrade. 

Remove and unregister a KRaft voter in a dynamic quorum

  1. Stop the voter
  2. Run kafka-metadata-quorum remove-controller with the --unregister flag

Remove a KRaft voter in a dynamic quorum and keep it registered as an observer controller

  1. Run kafka-metadata-quorum remove-controller without the --unregister flag

Unregister an observer controller in a static or dynamic quorum

  1. Stop the observer
  2. Run kafka-cluster unregister-controller

Unregister a voter in a static KRaft quorum when the static voter set is mistakenly configured

  1. Stop the voter who was mistakenly put in controller.quorum.voters 
  2. Run kafka-cluster unregister-controller
  3. Ensure the stopped voter is not part of controller.quorum.voters on every Kafka node

Proposed Changes

Controller Changes

...

This new flag means that the remove-controller command could send two RPCs to the active controller, one to remove the node from the KRaft voter set, and another to remove the node's registration. The implementation of this command should be able to handle cases where:

  • The node is part of the voter set and is registered
  • The node is part of the voter set and is not registered
  • The node is not part of the voter set and is registered
  • The node is not part of the voter set and is not registered

To maintain consistency with this command only being supported with dynamic quorum, running remove-controller --unregister will fail if the cluster does not support dynamic quorum reconfiguration. Instead, the user should be directed to use the kafka-cluster unregister-controller  command.

Compatibility, Deprecation, and Migration Plan

Because this KIP is introducing a new metadata record alongside a new MetadataVersion, it means that existing clusters who have a stale controller registration will not be able to unregister it, and unblock feature upgrades thereafter.  

One approach is for the controller to allow unregistering controllers non-durably if the metadata version does not support the UnregisterControllerRecord. This gives operators a path to migrate existing clusters onto a MV that supports this record, where they can persist the unregister record. One issue with this approach is that the UX is not good, requiring the user to unregister the controller twice.

Another approach is to allow the active controller to unregister observer controllers that the user specifies as part of a MV upgrade to support UnregisterControllerRecord. The main issue with this approach is that it makes the UX for kafka-features upgrade pretty complex. Additionally, this approach may be overkill in non-managed deployments, where operators can simply provision a controller to update its stale registrationThe main reason for not supporting this in existing clusters is that in many environments, operators can simply bring up another controller node with the same node ID to "refresh" its registration. Additionally, the interest of keeping this design simple, some of the potential workarounds for existing clusters have been moved to the Rejected Alternatives section.

Test Plan

Add an integration test for unregistering a controller in both static quorum and dynamic quorum clusters. 

...

This approach would be one way existing clusters could support unregistering stale controller registrations without updating the MV. However, the main issue with this approach is that it is unsafe. A user who unregisters a controller before updating the software versions on all controllers to support this feature would crash the controllers with an older software version.
 .

Additionally, combined mode deployments where the broker and controller use the same ID make reusing the same metadata record for unregistering both brokers and controller too complex compared to introducing a new record for unregistering controllers.

Non-durably unregister controllers

This would be a workaround for existing clusters with a stale registration, where the active controller's state machine could unregister a controller, but the on-disk data does not change. The main reason against implementing this would be a confusing UX during scenarios with node restarts. Additionally, it is confusing for operators to run the unregister CLI command again after updating the MV to support the UnregisterControllerRecord.

Allow the active controller to unregister observer controllers as part of a MV upgrade

Another approach to clear stale registrations from existing clusters is to allow the active controller to unregister observer controllers that the user specifies as part of a MV upgrade to support UnregisterControllerRecord. The main issue with this approach is that it makes the UX for kafka-features upgrade very complex. Additionally, this approach may be overkill in non-managed deployments, where operators can simply provision a controller to update its stale registration.