Merge remote-tracking branch 'origin/1.0.x'

This commit is contained in:
Ryan Baxter
2019-06-14 15:45:20 -04:00
29 changed files with 153 additions and 106 deletions

View File

@@ -49,14 +49,16 @@ public class BootstrapConfiguration {
private KubernetesClient client;
@Bean
@ConditionalOnProperty(name = "spring.cloud.kubernetes.config.enabled", matchIfMissing = true)
@ConditionalOnProperty(name = "spring.cloud.kubernetes.config.enabled",
matchIfMissing = true)
public ConfigMapPropertySourceLocator configMapPropertySourceLocator(
ConfigMapConfigProperties properties) {
return new ConfigMapPropertySourceLocator(this.client, properties);
}
@Bean
@ConditionalOnProperty(name = "spring.cloud.kubernetes.secrets.enabled", matchIfMissing = true)
@ConditionalOnProperty(name = "spring.cloud.kubernetes.secrets.enabled",
matchIfMissing = true)
public SecretsPropertySourceLocator secretsPropertySourceLocator(
SecretsConfigProperties properties) {
return new SecretsPropertySourceLocator(this.client, properties);

View File

@@ -62,7 +62,8 @@ public class PollingConfigurationChangeDetector extends ConfigurationChangeDetec
this.log.info("Kubernetes polling configuration change detector activated");
}
@Scheduled(initialDelayString = "${spring.cloud.kubernetes.reload.period:15000}", fixedDelayString = "${spring.cloud.kubernetes.reload.period:15000}")
@Scheduled(initialDelayString = "${spring.cloud.kubernetes.reload.period:15000}",
fixedDelayString = "${spring.cloud.kubernetes.reload.period:15000}")
public void executeCycle() {
boolean changedConfigMap = false;

View File

@@ -39,12 +39,14 @@ import static org.assertj.core.util.Lists.newArrayList;
import static org.springframework.cloud.kubernetes.config.ConfigMapTestUtil.createFileWithContent;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = {
"spring.application.name=configmap-path-example",
"spring.cloud.kubernetes.config.enableApi=false",
"spring.cloud.kubernetes.config.paths="
+ ConfigMapsFromFilePathsSpringBootTest.FIRST_FILE_NAME_FULL_PATH + ","
+ ConfigMapsFromFilePathsSpringBootTest.SECOND_FILE_NAME_FULL_PATH })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class,
properties = { "spring.application.name=configmap-path-example",
"spring.cloud.kubernetes.config.enableApi=false",
"spring.cloud.kubernetes.config.paths="
+ ConfigMapsFromFilePathsSpringBootTest.FIRST_FILE_NAME_FULL_PATH
+ ","
+ ConfigMapsFromFilePathsSpringBootTest.SECOND_FILE_NAME_FULL_PATH })
public class ConfigMapsFromFilePathsSpringBootTest {
protected static final String FILES_ROOT_PATH = "/tmp/scktests";

View File

@@ -41,11 +41,14 @@ import static org.assertj.core.util.Lists.newArrayList;
import static org.springframework.cloud.kubernetes.config.ConfigMapTestUtil.readResourceFile;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = {
"spring.application.name=" + ConfigMapsMixedSpringBootTest.APPLICATION_NAME,
"spring.cloud.kubernetes.config.enableApi=true",
"spring.cloud.kubernetes.config.paths="
+ ConfigMapsMixedSpringBootTest.FILE_NAME_FULL_PATH })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class,
properties = {
"spring.application.name="
+ ConfigMapsMixedSpringBootTest.APPLICATION_NAME,
"spring.cloud.kubernetes.config.enableApi=true",
"spring.cloud.kubernetes.config.paths="
+ ConfigMapsMixedSpringBootTest.FILE_NAME_FULL_PATH })
public class ConfigMapsMixedSpringBootTest {
protected static final String FILES_ROOT_PATH = "/tmp/scktests";

View File

@@ -41,9 +41,9 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Charles Moulliard
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = {
"spring.application.name=configmap-example",
"spring.cloud.kubernetes.reload.enabled=false" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class, properties = { "spring.application.name=configmap-example",
"spring.cloud.kubernetes.reload.enabled=false" })
@AutoConfigureWebTestClient
public class ConfigMapsSpringBootTest {

View File

@@ -41,9 +41,10 @@ import static org.springframework.cloud.kubernetes.config.ConfigMapTestUtil.read
* Tests reading property from YAML document specified by profile expression.
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = {
"spring.application.name=configmap-with-profile-example",
"spring.cloud.kubernetes.reload.enabled=false" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class,
properties = { "spring.application.name=configmap-with-profile-example",
"spring.cloud.kubernetes.reload.enabled=false" })
@ActiveProfiles({ "production", "us-east" })
@AutoConfigureWebTestClient
public class ConfigMapsWithProfileExpressionSpringBootTest {

View File

@@ -40,9 +40,11 @@ import static org.springframework.cloud.kubernetes.config.ConfigMapTestUtil.read
* @author Charles Moulliard
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = {
"spring.application.name=configmap-with-profile-no-active-profiles-example",
"spring.cloud.kubernetes.reload.enabled=false" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class,
properties = {
"spring.application.name=configmap-with-profile-no-active-profiles-example",
"spring.cloud.kubernetes.reload.enabled=false" })
@AutoConfigureWebTestClient
public class ConfigMapsWithProfilesNoActiveProfileSpringBootTest {

View File

@@ -41,9 +41,10 @@ import static org.springframework.cloud.kubernetes.config.ConfigMapTestUtil.read
* @author Charles Moulliard
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = {
"spring.application.name=configmap-with-profile-example",
"spring.cloud.kubernetes.reload.enabled=false" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class,
properties = { "spring.application.name=configmap-with-profile-example",
"spring.cloud.kubernetes.reload.enabled=false" })
@ActiveProfiles("development")
@AutoConfigureWebTestClient
public class ConfigMapsWithProfilesSpringBootTest {

View File

@@ -38,9 +38,10 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.cloud.kubernetes.config.ConfigMapTestUtil.readResourceFile;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = {
"spring.application.name=configmap-without-profile-example",
"spring.cloud.kubernetes.reload.enabled=false" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class,
properties = { "spring.application.name=configmap-without-profile-example",
"spring.cloud.kubernetes.reload.enabled=false" })
@ActiveProfiles("development")
@AutoConfigureWebTestClient
public class ConfigMapsWithoutProfilesSpringBootTest {

View File

@@ -36,12 +36,12 @@ import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class, properties = {
"spring.application.name=testapp",
"spring.cloud.kubernetes.client.namespace=testns",
"spring.cloud.kubernetes.client.trustCerts=true",
"spring.cloud.kubernetes.config.namespace=testns",
"spring.cloud.kubernetes.secrets.enableApi=true" })
@SpringBootTest(classes = TestApplication.class,
properties = { "spring.application.name=testapp",
"spring.cloud.kubernetes.client.namespace=testns",
"spring.cloud.kubernetes.client.trustCerts=true",
"spring.cloud.kubernetes.config.namespace=testns",
"spring.cloud.kubernetes.secrets.enableApi=true" })
public class CoreTest {
@ClassRule

View File

@@ -35,8 +35,9 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import static org.hamcrest.Matchers.containsString;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = {
"management.endpoint.health.show-details=always" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class,
properties = { "management.endpoint.health.show-details=always" })
public class HealthIndicatorTest {
@ClassRule

View File

@@ -39,8 +39,9 @@ import org.springframework.test.web.reactive.server.WebTestClient;
* @author Charles Moulliard
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ExampleApp.class, properties = {
"spring.cloud.bootstrap.name=multiplecms" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = ExampleApp.class,
properties = { "spring.cloud.bootstrap.name=multiplecms" })
@AutoConfigureWebTestClient
public class MultipleConfigMapsSpringBootTest {

View File

@@ -39,7 +39,8 @@ import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class)
@TestPropertySource("classpath:/application-secrets.properties")
public class SecretsPropertySourceTest {

View File

@@ -35,8 +35,9 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import static org.hamcrest.Matchers.containsString;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = {
"management.endpoint.health.show-details=always" })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class,
properties = { "management.endpoint.health.show-details=always" })
public class HealthIndicatorTest {
@ClassRule

View File

@@ -35,7 +35,8 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import static org.hamcrest.Matchers.containsString;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class)
public class InfoContributorTest {
@ClassRule

View File

@@ -58,7 +58,8 @@ public class KubernetesCatalogWatch implements ApplicationEventPublisherAware {
this.publisher = publisher;
}
@Scheduled(fixedDelayString = "${spring.cloud.kubernetes.discovery.catalogServicesWatchDelay:30000}")
@Scheduled(
fixedDelayString = "${spring.cloud.kubernetes.discovery.catalogServicesWatchDelay:30000}")
public void catalogServicesWatch() {
try {
List<String> previousState = this.catalogEndpointsState.get();

View File

@@ -58,7 +58,8 @@ public class KubernetesDiscoveryClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.enabled", matchIfMissing = true)
@ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.enabled",
matchIfMissing = true)
public KubernetesDiscoveryClient kubernetesDiscoveryClient(KubernetesClient client,
KubernetesDiscoveryProperties properties,
KubernetesClientServicesFunction kubernetesClientServicesFunction,
@@ -85,7 +86,9 @@ public class KubernetesDiscoveryClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.catalog-services-watch.enabled", matchIfMissing = true)
@ConditionalOnProperty(
name = "spring.cloud.kubernetes.discovery.catalog-services-watch.enabled",
matchIfMissing = true)
public KubernetesCatalogWatch kubernetesCatalogWatch(KubernetesClient client) {
return new KubernetesCatalogWatch(client);
}

View File

@@ -27,9 +27,17 @@ And finally, if the leadership is yielded or revoked for some reason, the old le
## Example application usage
To begin with, build and deploy the application:
Leader election mechanism uses Kubernetes ConfigMap feature to coordinate leadership information.
To access ConfigMap user needs correct role and role binding.
Create them using the following commands:
```
mvn clean package fabric8:deploy -Pkubernetes
kubectl apply -f leader-role.yml
kubectl apply -f leader-rolebinding.yml
```
Now build and deploy the application:
```
mvn clean fabric8:deploy -Pkubernetes
```
This will deploy a single application instance to the cluster and that instance will automatically become a leader.
@@ -76,8 +84,4 @@ Thus, when trying to yield the leadership, request might go to a non-leader node
> Note: instances periodically try to acquire leadership and Spring Cloud Kubernetes doesn't decide which one of them is more worth to become one.
Thus, it is possible that the instance which just yielded the leadership, made another leadership take over request faster than another instances and became a leader again.
## Access control notice
Leader election mechanism uses Kubernetes ConfigMap feature to coordinate leadership information.
In order to access it, [Role](./src/main/fabric8/role.yaml) and [RoleBinding](./src/main/fabric8/rb.yaml) objects are defined.

View File

@@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: leader
labels:
app: kubernetes-leader-election-example
group: org.springframework.cloud
rules:
- apiGroups:
- ""
resources:
- pods
- configmaps
verbs:
- '*'

View File

@@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: kubernetes-leader-election-example
group: org.springframework.cloud
name: leader
roleRef:
apiGroup: ""
kind: Role
name: leader
subjects:
- kind: ServiceAccount
name: default
apiGroup: ""

View File

@@ -36,6 +36,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-leader</artifactId>

View File

@@ -1,13 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: leader
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: leader
subjects:
- kind: ServiceAccount
name: default
namespace: default

View File

@@ -1,12 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: leader
namespace: default
rules:
- apiGroups:
resources:
- pods
- configmaps
verbs:
- "*"

View File

@@ -41,7 +41,8 @@ import org.springframework.integration.leader.event.LeaderEventPublisher;
@Configuration
@EnableConfigurationProperties(LeaderProperties.class)
@ConditionalOnBean(KubernetesClient.class)
@ConditionalOnProperty(value = "spring.cloud.kubernetes.leader.enabled", matchIfMissing = true)
@ConditionalOnProperty(value = "spring.cloud.kubernetes.leader.enabled",
matchIfMissing = true)
public class LeaderAutoConfiguration {
@Bean

View File

@@ -149,24 +149,18 @@ public class LeadershipController {
LOGGER.debug("Leader is still '{}'", this.localLeader);
return;
}
else if (this.localLeader != null
&& this.localLeader.isCandidate(this.candidate)) {
Leader oldLeader = this.localLeader;
this.localLeader = newLeader;
if (oldLeader != null && oldLeader.isCandidate(this.candidate)) {
notifyOnRevoked();
}
else if (newLeader != null && newLeader.isCandidate(this.candidate)) {
notifyOnGranted();
}
this.localLeader = newLeader;
if (this.leaderReadinessWatcher != null) {
this.leaderReadinessWatcher.stop();
this.leaderReadinessWatcher = null;
}
if (this.localLeader != null && !this.localLeader.isCandidate(this.candidate)) {
this.leaderReadinessWatcher = new PodReadinessWatcher(
this.localLeader.getId(), this.kubernetesClient, this);
this.leaderReadinessWatcher.start();
}
restartLeaderReadinessWatcher();
LOGGER.debug("New leader is '{}'", this.localLeader);
}
@@ -203,6 +197,19 @@ public class LeadershipController {
}
}
private void restartLeaderReadinessWatcher() {
if (this.leaderReadinessWatcher != null) {
this.leaderReadinessWatcher.stop();
this.leaderReadinessWatcher = null;
}
if (this.localLeader != null && !this.localLeader.isCandidate(this.candidate)) {
this.leaderReadinessWatcher = new PodReadinessWatcher(
this.localLeader.getId(), this.kubernetesClient, this);
this.leaderReadinessWatcher.start();
}
}
private String getLeaderKey() {
return this.leaderProperties.getLeaderIdPrefix() + this.candidate.getRole();
}

View File

@@ -31,10 +31,13 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import static org.hamcrest.Matchers.containsString;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
"spring.cloud.kubernetes.leader.autoStartup=false" // Make sure test passes
// without Kubernetes cluster
})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { "spring.cloud.kubernetes.leader.autoStartup=false" // Make sure
// test passes
// without
// Kubernetes
// cluster
})
public class LeaderAutoConfigurationTests {
@Value("${local.server.port}")

View File

@@ -33,7 +33,8 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties
@ConditionalOnBean(SpringClientFactory.class)
@ConditionalOnProperty(value = "spring.cloud.kubernetes.ribbon.enabled", matchIfMissing = true)
@ConditionalOnProperty(value = "spring.cloud.kubernetes.ribbon.enabled",
matchIfMissing = true)
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = KubernetesRibbonClientConfiguration.class)
public class RibbonKubernetesAutoConfiguration {

View File

@@ -44,11 +44,11 @@ import static org.assertj.core.api.Assertions.fail;
* @author Charles Moulliard
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class, properties = {
"spring.application.name=testapp",
"spring.cloud.kubernetes.client.namespace=testns",
"spring.cloud.kubernetes.client.trustCerts=true",
"spring.cloud.kubernetes.config.namespace=testns" })
@SpringBootTest(classes = TestApplication.class,
properties = { "spring.application.name=testapp",
"spring.cloud.kubernetes.client.namespace=testns",
"spring.cloud.kubernetes.client.trustCerts=true",
"spring.cloud.kubernetes.config.namespace=testns" })
@EnableAutoConfiguration
@EnableDiscoveryClient
public class RibbonFallbackTest {

View File

@@ -41,11 +41,11 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Charles Moulliard
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class, properties = {
"spring.application.name=testapp",
"spring.cloud.kubernetes.client.namespace=testns",
"spring.cloud.kubernetes.client.trustCerts=true",
"spring.cloud.kubernetes.config.namespace=testns" })
@SpringBootTest(classes = TestApplication.class,
properties = { "spring.application.name=testapp",
"spring.cloud.kubernetes.client.namespace=testns",
"spring.cloud.kubernetes.client.trustCerts=true",
"spring.cloud.kubernetes.config.namespace=testns" })
@EnableAutoConfiguration
@EnableDiscoveryClient
public class RibbonTest {