diff --git a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/README.md b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/README.md index 1858ee66..ad953e0b 100644 --- a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/README.md +++ b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/README.md @@ -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. + diff --git a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/leader-role.yml b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/leader-role.yml new file mode 100644 index 00000000..968fe263 --- /dev/null +++ b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/leader-role.yml @@ -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: + - '*' diff --git a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/leader-rolebinding.yml b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/leader-rolebinding.yml new file mode 100644 index 00000000..72e41216 --- /dev/null +++ b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/leader-rolebinding.yml @@ -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: "" diff --git a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/pom.xml b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/pom.xml index efac21e5..66e03e11 100644 --- a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/pom.xml +++ b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/pom.xml @@ -36,6 +36,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-actuator + org.springframework.cloud spring-cloud-kubernetes-leader diff --git a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/src/main/fabric8/rb.yaml b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/src/main/fabric8/rb.yaml deleted file mode 100644 index f3646344..00000000 --- a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/src/main/fabric8/rb.yaml +++ /dev/null @@ -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 diff --git a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/src/main/fabric8/role.yaml b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/src/main/fabric8/role.yaml deleted file mode 100644 index 3f15dd3b..00000000 --- a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/src/main/fabric8/role.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: Role -metadata: - name: leader - namespace: default -rules: - - apiGroups: - resources: - - pods - - configmaps - verbs: - - "*" diff --git a/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfiguration.java b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfiguration.java index 902821cd..e4bb868b 100644 --- a/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfiguration.java +++ b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfiguration.java @@ -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 diff --git a/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeadershipController.java b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeadershipController.java index 74490a9a..898aeb18 100644 --- a/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeadershipController.java +++ b/spring-cloud-kubernetes-leader/src/main/java/org/springframework/cloud/kubernetes/leader/LeadershipController.java @@ -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(); } diff --git a/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfigurationTests.java b/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfigurationTests.java index f16fdd35..15932f73 100644 --- a/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfigurationTests.java +++ b/spring-cloud-kubernetes-leader/src/test/java/org/springframework/cloud/kubernetes/leader/LeaderAutoConfigurationTests.java @@ -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}")