-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-17431: Support invalid static configs for KRaft so long as dynamic configs are valid #18949
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
5e47e11
support invalid static configs on brokerServer if last snapshots' dyn…
kevin-wu24 caee8a5
code review
kevin-wu24 00db22f
fixing build
kevin-wu24 590f50e
removing magic number
kevin-wu24 645bcb1
code review + cleanup
kevin-wu24 4b81603
merging trunk
kevin-wu24 30e777c
handling null config record values in snapshot dynamic config load
kevin-wu24 6cd3e56
instantiate quotaManagers before reading dynamic config from snapshot…
kevin-wu24 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,23 +23,28 @@ import java.util.concurrent.CopyOnWriteArrayList | |
| import java.util.concurrent.locks.ReentrantReadWriteLock | ||
| import kafka.log.{LogCleaner, LogManager} | ||
| import kafka.network.{DataPlaneAcceptor, SocketServer} | ||
| import kafka.raft.KafkaRaftManager | ||
| import kafka.server.DynamicBrokerConfig._ | ||
| import kafka.utils.{CoreUtils, Logging} | ||
| import org.apache.kafka.common.Reconfigurable | ||
| import org.apache.kafka.network.EndPoint | ||
| import org.apache.kafka.common.config.internals.BrokerSecurityConfigs | ||
| import org.apache.kafka.common.config.{AbstractConfig, ConfigDef, ConfigException, SaslConfigs, SslConfigs} | ||
| import org.apache.kafka.common.config.{AbstractConfig, ConfigDef, ConfigException, ConfigResource, SaslConfigs, SslConfigs} | ||
| import org.apache.kafka.common.metadata.{ConfigRecord, MetadataRecordType} | ||
| import org.apache.kafka.common.metrics.{Metrics, MetricsReporter} | ||
| import org.apache.kafka.common.network.{ListenerName, ListenerReconfigurable} | ||
| import org.apache.kafka.common.security.authenticator.LoginManager | ||
| import org.apache.kafka.common.utils.{ConfigUtils, Utils} | ||
| import org.apache.kafka.common.utils.{BufferSupplier, ConfigUtils, Utils} | ||
| import org.apache.kafka.coordinator.transaction.TransactionLogConfig | ||
| import org.apache.kafka.network.SocketServerConfigs | ||
| import org.apache.kafka.raft.KafkaRaftClient | ||
| import org.apache.kafka.server.{ProcessRole, DynamicThreadPool} | ||
| import org.apache.kafka.server.common.ApiMessageAndVersion | ||
| import org.apache.kafka.server.config.{ServerConfigs, ServerLogConfigs, ServerTopicConfigSynonyms} | ||
| import org.apache.kafka.server.log.remote.storage.RemoteLogManagerConfig | ||
| import org.apache.kafka.server.metrics.{ClientMetricsReceiverPlugin, MetricConfigs} | ||
| import org.apache.kafka.server.telemetry.ClientTelemetry | ||
| import org.apache.kafka.snapshot.RecordsSnapshotReader | ||
| import org.apache.kafka.storage.internals.log.{LogConfig, ProducerStateManagerConfig} | ||
|
|
||
| import scala.collection._ | ||
|
|
@@ -185,6 +190,52 @@ object DynamicBrokerConfig { | |
| } | ||
| props | ||
| } | ||
|
|
||
| private[server] def readDynamicBrokerConfigsFromSnapshot( | ||
| raftManager: KafkaRaftManager[ApiMessageAndVersion], | ||
| config: KafkaConfig, | ||
| quotaManagers: QuotaFactory.QuotaManagers | ||
| ): Unit = { | ||
| def putOrRemoveIfNull(props: Properties, key: String, value: String): Unit = { | ||
| if (value == null) { | ||
| props.remove(key) | ||
| } else { | ||
| props.put(key, value) | ||
| } | ||
| } | ||
| raftManager.replicatedLog.latestSnapshotId().ifPresent(latestSnapshotId => { | ||
| raftManager.replicatedLog.readSnapshot(latestSnapshotId).ifPresent(rawSnapshotReader => { | ||
| val reader = RecordsSnapshotReader.of( | ||
| rawSnapshotReader, | ||
| raftManager.recordSerde, | ||
| BufferSupplier.create(), | ||
| KafkaRaftClient.MAX_BATCH_SIZE_BYTES, | ||
| true | ||
| ) | ||
| val dynamicPerBrokerConfigs = new Properties() | ||
| val dynamicDefaultConfigs = new Properties() | ||
| while (reader.hasNext) { | ||
| val batch = reader.next() | ||
| batch.forEach(record => { | ||
| if (record.message().apiKey() == MetadataRecordType.CONFIG_RECORD.id) { | ||
| val configRecord = record.message().asInstanceOf[ConfigRecord] | ||
| if (DynamicBrokerConfig.AllDynamicConfigs.contains(configRecord.name()) && | ||
| configRecord.resourceType() == ConfigResource.Type.BROKER.id()) { | ||
| if (configRecord.resourceName().isEmpty) { | ||
| putOrRemoveIfNull(dynamicDefaultConfigs, configRecord.name(), configRecord.value()) | ||
| } else if (configRecord.resourceName() == config.brokerId.toString) { | ||
| putOrRemoveIfNull(dynamicPerBrokerConfigs, configRecord.name(), configRecord.value()) | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| val configHandler = new BrokerConfigHandler(config, quotaManagers) | ||
| configHandler.processConfigChanges("", dynamicPerBrokerConfigs) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| configHandler.processConfigChanges(config.brokerId.toString, dynamicPerBrokerConfigs) | ||
| }) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| class DynamicBrokerConfig(private val kafkaConfig: KafkaConfig) extends Logging { | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be too late to address some of the use-cases we're concerned about, right?
It would be better to load all the config key/value pairs from the snapshot and overwrite the static configs with them, before the first time we create a KafkaConfig object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you illuminate some of these cases for me? It seems like we're doing this early enough in
brokerServer's startup to me. Are we instead concerned that bad static configs will prevent evenSharedServer#startup, which sets up KRaft and metadata publishers, from completing successfully? I guess I'm just confused as to how node can even get into a state where the static configs would crashSharedServer#startup, but somehow also have valid dynamic configs?I'm a bit confused as to what you're proposing. It sounds like this loading config key/value pairs from the snapshot should occur before we construct the
KafkaConfiginSharedServer's initialization (i.e. before we callSharedServer#startwhich initializesraftManager)? If so, that means we can't useraftManagerto actually perform the snapshot read, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One use case is if we have changed the network configuration in such a way that the old static configuration isn't valid. The SSL keystore file being moved is a good example. In that case we would not want to start up with the old static configuration
It shouldn't be necessary to use raftManager to read the snapshot, since the snapshot is just a file. Maybe we could have some static utility method which finds the last snapshot file (it's just a matter of finding the one that sorts last in the folder...)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test I wrote does "move" the SSL keystore file. By invalidating the static ssl configs on one of the nodes after shutting it down, we can successfully complete
brokerServer#startup()with my changes. Without them, we fail during startup.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want full parity with how ZK worked, we need to load the dynamic configurations prior to sending out the initial dynamic configurations. In ZK mode we actually fetched the broker configuration from ZK prior to doing this. It's possible we could do this in a different way but I'm not confident that it will solve all the possible cases.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused about what this means. Where in sharedServer do we "send out initial dynamic configurations"?
From a high level, I think the current implementation is doing the equivalent in KRaft, since the KRaft layer, which reads the snapshot, on the broker is acting like ZK (although possibly stale) to store broker configs (correct me if I'm wrong, I also will look more into this tmrw).
The only config I see that is used in
sharedServer/raftManagerthat is also part ofDynamicBrokerConfig#AllDynamicConfigsis theLISTENER_SECURITY_PROTOCOL_MAP_CONFIG. This config is read when building the KRaft network client for the broker during startup, but only the entry for the controller listener is used. From my understanding, the only way to invalidate this static config if it was previously valid would be to mess withserver.propertiesdirectly and then restart the broker. Is that also a case we also want to cover with this change?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SharedServer doesn't. This happens from BrokerServer or ControllerServer.
If you are confident that this will handle the case that originally motivated this JIRA then I'm OK with doing this for now. As you recall, the case was the broker SSL keystore file being statically set to a path that no longer existed.
No. That configuration is static and cannot be dynamically changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test I wrote shows that the implementation changes address this case, since it does the following: