Skip to content

Commit d4d18b7

Browse files
rdesgroppesgrollingerPavol Gressa
authored
Use ConfigMapList's resourceVersion for watching (#300)
Rationale: avoid `ERROR` notifications during rolling upgrades. Using the greatest `resourceVersion` of all the `ConfigMap`s returned within the `ConfigMapList` works as expected for *fresh* deployments. But, when performing a *rolling upgrade* (and depending on the upgrade strategy), the watcher happens to frequently stop after having received an `ERROR` notification: > [ERROR] [KubernetesConfigMapWatcher] [] Kubernetes API returned an error for a ConfigMap watch event: ConfigMapWatchEvent{type=ERROR, object=ConfigMap{metadata=Metadata{name='null', namespace='null', uid='null', labels={}, resourceVersion=null}, data={}}} What's actually streamed in that case is a `Status` object such as: ```json { "type": "ERROR", "object": { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Expired", "message": "too old resource version: 123 (456)", "reason": "Gone", "code": 410 } } ``` A few references: * ManageIQ/kubeclient#452 * https://www.baeldung.com/java-kubernetes-watch#1-resource-versions It's possible to recover by adding some logic to reinstall the watcher starting with the newly advertised `resourceVersion`, but this may be avoided at all by starting the initial watch at the `resourceVersion` of the `ConfigMapList` itself: this one won't expire. The proposed implementation consists in storing last received `resourceVersion` as an additional `PropertySource` (through a dedicated name and version key) and later using it when installing the watcher. Co-authored-by: Regis Desgroppes <[email protected]> Co-authored-by: Pavol Gressa <[email protected]> Co-authored-by: Georg Rollinger <[email protected]> Co-authored-by: Pavol Gressa <[email protected]>
1 parent f9de203 commit d4d18b7

File tree

4 files changed

+34
-14
lines changed

4 files changed

+34
-14
lines changed

kubernetes-discovery-client/src/main/java/io/micronaut/kubernetes/client/v1/configmaps/ConfigMapList.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.micronaut.kubernetes.client.v1.configmaps;
1717

1818
import io.micronaut.core.annotation.Introspected;
19+
import io.micronaut.kubernetes.client.v1.KubernetesObject;
1920

2021
import java.util.Collections;
2122
import java.util.List;
@@ -28,7 +29,7 @@
2829
* @since 1.0.0
2930
*/
3031
@Introspected
31-
public class ConfigMapList {
32+
public class ConfigMapList extends KubernetesObject {
3233

3334
private List<ConfigMap> items;
3435

kubernetes-discovery-client/src/main/java/io/micronaut/kubernetes/configuration/KubernetesConfigMapWatcher.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
import java.util.Map;
4040
import java.util.concurrent.ExecutorService;
4141

42-
import static io.micronaut.kubernetes.configuration.KubernetesConfigurationClient.KUBERNETES_CONFIG_MAP_NAME_SUFFIX;
42+
import static io.micronaut.kubernetes.configuration.KubernetesConfigurationClient.KUBERNETES_CONFIG_MAP_LIST_NAME;
4343
import static io.micronaut.kubernetes.util.KubernetesUtils.computePodLabelSelector;
4444

4545
/**
@@ -109,8 +109,8 @@ private long computeLastResourceVersion() {
109109
long lastResourceVersion = environment
110110
.getPropertySources()
111111
.stream()
112-
.filter(propertySource -> propertySource.getName().endsWith(KUBERNETES_CONFIG_MAP_NAME_SUFFIX))
113-
.map(propertySource -> propertySource.get(KubernetesConfigurationClient.CONFIG_MAP_RESOURCE_VERSION))
112+
.filter(propertySource -> propertySource.getName().equals(KUBERNETES_CONFIG_MAP_LIST_NAME))
113+
.map(propertySource -> propertySource.get(KubernetesConfigurationClient.CONFIG_MAP_LIST_RESOURCE_VERSION))
114114
.map(o -> Long.parseLong(o.toString()))
115115
.max(Long::compareTo)
116116
.orElse(0L);

kubernetes-discovery-client/src/main/java/io/micronaut/kubernetes/configuration/KubernetesConfigurationClient.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
import static io.micronaut.kubernetes.client.v1.secrets.Secret.OPAQUE_SECRET_TYPE;
4747
import static io.micronaut.kubernetes.util.KubernetesUtils.computePodLabelSelector;
48+
import static java.util.Collections.singletonMap;
4849

4950
/**
5051
* A {@link ConfigurationClient} implementation that provides {@link PropertySource}s read from Kubernetes ConfigMap's.
@@ -59,7 +60,9 @@
5960
@BootstrapContextCompatible
6061
public class KubernetesConfigurationClient implements ConfigurationClient {
6162

63+
public static final String CONFIG_MAP_LIST_RESOURCE_VERSION = "configMapListResourceVersion";
6264
public static final String CONFIG_MAP_RESOURCE_VERSION = "configMapResourceVersion";
65+
public static final String KUBERNETES_CONFIG_MAP_LIST_NAME = "Kubernetes ConfigMapList";
6366
public static final String KUBERNETES_CONFIG_MAP_NAME_SUFFIX = " (Kubernetes ConfigMap)";
6467
public static final String KUBERNETES_SECRET_NAME_SUFFIX = " (Kubernetes Secret)";
6568

@@ -154,15 +157,30 @@ private Flowable<PropertySource> getPropertySourcesFromConfigMaps() {
154157
LOG.debug("Found {} config maps. Applying includes/excludes filters (if any)", configMapList.getItems().size());
155158
}
156159
})
157-
.flatMapIterable(ConfigMapList::getItems)
158-
.filter(includesFilter)
159-
.filter(excludesFilter)
160-
.doOnNext(configMap -> {
161-
if (LOG.isDebugEnabled()) {
162-
LOG.debug("Adding config map with name {}", configMap.getMetadata().getName());
163-
}
164-
})
165-
.map(KubernetesUtils::configMapAsPropertySource);
160+
.flatMap(configMapList -> Flowable.just(configMapListAsPropertySource(configMapList))
161+
.mergeWith(Flowable.fromIterable(configMapList.getItems())
162+
.filter(includesFilter)
163+
.filter(excludesFilter)
164+
.doOnNext(configMap -> {
165+
if (LOG.isDebugEnabled()) {
166+
LOG.debug("Adding config map with name {}", configMap.getMetadata().getName());
167+
}
168+
})
169+
.map(KubernetesUtils::configMapAsPropertySource)));
170+
}
171+
172+
/**
173+
* Converts a {@link ConfigMapList} into a {@link PropertySource}.
174+
*
175+
* @param configMapList the ConfigMapList
176+
* @return A PropertySource
177+
*/
178+
private static PropertySource configMapListAsPropertySource(ConfigMapList configMapList) {
179+
String resourceVersion = configMapList.getMetadata().getResourceVersion();
180+
if (LOG.isDebugEnabled()) {
181+
LOG.debug("Adding config map list with version {}", resourceVersion);
182+
}
183+
return PropertySource.of(KUBERNETES_CONFIG_MAP_LIST_NAME, singletonMap(CONFIG_MAP_LIST_RESOURCE_VERSION, resourceVersion), EnvironmentPropertySource.POSITION + 100);
166184
}
167185

168186
private Flowable<PropertySource> getPropertySourcesFromSecrets() {

kubernetes-discovery-client/src/test/groovy/io/micronaut/kubernetes/configuration/KubernetesConfigurationClientLabelsSpec.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ class KubernetesConfigurationClientLabelsSpec extends KubernetesSpecification {
4949
def propertySources = Flowable.fromPublisher(configurationClient.getPropertySources(applicationContext.environment)).blockingIterable()
5050

5151
then:
52-
propertySources.size() == 0
52+
propertySources.size() == 1
53+
propertySources.first().name == KubernetesConfigurationClient.KUBERNETES_CONFIG_MAP_LIST_NAME
5354
}
5455

5556
void "it can filter secrets by labels"() {

0 commit comments

Comments
 (0)