├── .circleci
└── config.yml
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── pom.xml
├── readme.md
├── renovate.json
├── spring-cloud-kubernetes-discovery-ext-client
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── org
│ │ │ └── springframework
│ │ │ └── cloud
│ │ │ └── kubernetes
│ │ │ └── discovery
│ │ │ └── ext
│ │ │ ├── KubernetesAutoServiceRegistration.java
│ │ │ ├── KubernetesAutoServiceRegistrationAutoConfiguration.java
│ │ │ ├── KubernetesAutoServiceRegistrationListener.java
│ │ │ ├── KubernetesRegistration.java
│ │ │ ├── KubernetesRegistrationProperties.java
│ │ │ ├── KubernetesServiceRegistry.java
│ │ │ └── KubernetesServiceRegistryAutoConfiguration.java
│ └── resources
│ │ └── META-INF
│ │ ├── spring.factories
│ │ └── spring
│ │ └── org.springframework.boot.AutoConfiguration.imports
│ └── test
│ └── java
│ └── org
│ └── springframework
│ └── cloud
│ └── kubernetes
│ └── discovery
│ └── ext
│ ├── KubernetesServiceRegistryTests.java
│ └── KubernetesServiceRegistryTestsConfig.java
└── spring-cloud-kubernetes-discovery-ext-watcher
├── k8s
└── deployment.yaml
├── pom.xml
├── skaffold.yaml
└── src
└── main
└── java
└── org
└── springframework
└── cloud
└── kubernetes
└── discovery
└── ext
└── watcher
├── SpringCloudKubernetesDiscoveryExtWatcherApp.java
├── config
└── KubernetesWatcherProperties.java
├── model
└── KubernetesRegistration.java
├── schedule
└── LivenessScheduler.java
└── task
└── DeactivateServiceTask.java
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: '2.1'
2 |
3 | jobs:
4 | analyze:
5 | docker:
6 | - image: 'cimg/openjdk:21.0.6'
7 | steps:
8 | - checkout
9 | - run:
10 | name: Analyze on SonarCloud
11 | command: mvn verify sonar:sonar -DskipTests
12 | test:
13 | executor: machine_executor_amd64
14 | steps:
15 | - checkout
16 | - run:
17 | name: Install OpenJDK 21
18 | command: |
19 | java -version
20 | sudo apt-get update && sudo apt-get install openjdk-21-jdk
21 | sudo update-alternatives --set java /usr/lib/jvm/java-21-openjdk-amd64/bin/java
22 | sudo update-alternatives --set javac /usr/lib/jvm/java-21-openjdk-amd64/bin/javac
23 | java -version
24 | export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
25 | - run:
26 | name: Maven Tests
27 | command: mvn test
28 |
29 | orbs:
30 | maven: circleci/maven@2.0.0
31 |
32 | executors:
33 | machine_executor_amd64:
34 | machine:
35 | image: ubuntu-2204:2023.10.1
36 | environment:
37 | architecture: "amd64"
38 | platform: "linux/amd64"
39 |
40 | workflows:
41 | maven_test:
42 | jobs:
43 | - test
44 | - analyze:
45 | context: SonarCloud
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at piotr.minkowski@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Piotr Mińkowski
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.github.piomin
8 | spring-cloud-kubernetes-discovery-ext-starter
9 | pom
10 | 1.1-SNAPSHOT
11 |
12 |
13 | piomin_spring-cloud-kubernetes-discovery-ext
14 | piomin
15 | https://sonarcloud.io
16 |
17 |
18 |
19 | spring-cloud-kubernetes-discovery-ext-client
20 | spring-cloud-kubernetes-discovery-ext-watcher
21 |
22 |
23 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## Spring Cloud Library for External Kubernetes Discovery [](https://twitter.com/piotr_minkowski)
2 |
3 | [](https://circleci.com/gh/piomin/sample-spring-cloud-kubernetes-discovery-ext)
4 |
5 | [](https://sonarcloud.io/dashboard?id=piomin_spring-cloud-kubernetes-discovery-ext)
6 | [](https://sonarcloud.io/dashboard?id=piomin_spring-cloud-kubernetes-discovery-ext)
7 | [](https://sonarcloud.io/dashboard?id=piomin_spring-cloud-kubernetes-discovery-ext)
8 | [](https://sonarcloud.io/dashboard?id=piomin_spring-cloud-kubernetes-discovery-ext)
9 |
10 | ### Motivation
11 |
12 | The article which describes motivation for creating library and showing use case for it with source code example:
13 | [Spring Cloud Kubernetes For Hybrid Microservices Architecture](https://piotrminkowski.com/2020/01/03/spring-cloud-kubernetes-for-hybrid-microservices-architecture/)
14 |
15 | ### Usage
16 |
17 | The library is published on Maven Central. Current version is `1.0.0.RELEASE`
18 | ```
19 |
20 | com.github.piomin
21 | spring-cloud-kubernetes-discovery-ext
22 | 1.0.0.RELEASE
23 |
24 | ```
25 |
26 | The registration is still disabled, since we won't set property `spring.cloud.kubernetes.discovery.register` to `true`.
27 | ```
28 | spring:
29 | cloud:
30 | kubernetes:
31 | discovery:
32 | register: true
33 | ```
34 | It might be usable to set static IP address in configuration, in case you would have multiple network interfaces.
35 | ```
36 | spring:
37 | cloud:
38 | kubernetes:
39 | discovery:
40 | ipAddress: 192.168.99.1
41 | ```
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:base",":dependencyDashboard"
5 | ],
6 | "packageRules": [
7 | {
8 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
9 | "automerge": true
10 | }
11 | ],
12 | "prCreation": "not-pending"
13 | }
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | spring-cloud-kubernetes-discovery-ext-starter
7 | com.github.piomin
8 | 1.1-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | spring-cloud-kubernetes-discovery-ext-client
13 | spring-cloud-kubernetes-discovery-ext-client
14 | Library for Spring Cloud Kubernetes external registration
15 | https://github.com/piomin/spring-cloud-kubernetes-discovery-ext
16 |
17 |
18 |
19 | Piotr Mińkowski
20 | piotr.minkowski@gmail.com
21 | https://github.com/piomin
22 |
23 |
24 |
25 |
26 |
27 | MIT License
28 | http://www.opensource.org/licenses/mit-license.php
29 | repo
30 |
31 |
32 |
33 |
34 | 21
35 | ${project.artifactId}
36 |
37 |
38 |
39 | scm:git:git://github.com/piomin/spring-cloud-kubernetes-discovery-ext.git
40 | scm:git:git@github.com/piomin/spring-cloud-kubernetes-discovery-ext.git
41 | https://github.com/piomin/spring-cloud-kubernetes-discovery-ext
42 |
43 |
44 |
45 |
46 | ossrh
47 | https://oss.sonatype.org/content/repositories/snapshots
48 |
49 |
50 | ossrh
51 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | org.apache.maven.plugins
60 | maven-compiler-plugin
61 | 3.14.0
62 |
63 | ${java.version}
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | org.springframework.cloud
72 | spring-cloud-starter-kubernetes-fabric8
73 |
74 |
75 | org.springframework.boot
76 | spring-boot-starter-test
77 | 3.5.0
78 | test
79 |
80 |
81 | ch.qos.logback
82 | logback-classic
83 |
84 |
85 |
86 |
87 | io.fabric8
88 | kubernetes-server-mock
89 | 6.10.0
90 | test
91 |
92 |
93 |
94 |
95 |
96 |
97 | org.springframework.cloud
98 | spring-cloud-dependencies
99 | 2023.0.5
100 | pom
101 | import
102 |
103 |
104 |
105 |
106 |
107 |
108 | release
109 |
110 |
111 |
112 | org.apache.maven.plugins
113 | maven-gpg-plugin
114 | 3.2.7
115 |
116 |
117 | sign-artifacts
118 | verify
119 |
120 | sign
121 |
122 |
123 |
124 |
125 |
126 | org.apache.maven.plugins
127 | maven-source-plugin
128 | 3.3.1
129 |
130 |
131 | attach-sources
132 |
133 | jar-no-fork
134 |
135 |
136 |
137 |
138 |
139 | org.apache.maven.plugins
140 | maven-javadoc-plugin
141 | 3.11.2
142 |
143 |
144 | attach-javadocs
145 |
146 | jar
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/KubernetesAutoServiceRegistration.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext;
2 |
3 | import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
4 | import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
5 | import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
6 | import org.springframework.cloud.kubernetes.commons.PodUtils;
7 | import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
8 | import org.springframework.core.env.Environment;
9 |
10 | import java.net.InetAddress;
11 | import java.net.UnknownHostException;
12 |
13 | public class KubernetesAutoServiceRegistration extends AbstractAutoServiceRegistration {
14 |
15 | private KubernetesDiscoveryProperties properties;
16 | private KubernetesRegistrationProperties registrationProperties;
17 | private KubernetesRegistration registration;
18 | private PodUtils podUtils;
19 |
20 | KubernetesAutoServiceRegistration(ServiceRegistry serviceRegistry,
21 | AutoServiceRegistrationProperties autoServiceRegistrationProperties,
22 | KubernetesRegistration registration, KubernetesDiscoveryProperties properties,
23 | KubernetesRegistrationProperties registrationProperties, PodUtils podUtils) {
24 | super(serviceRegistry, autoServiceRegistrationProperties);
25 | this.properties = properties;
26 | this.registrationProperties = registrationProperties;
27 | this.registration = registration;
28 | this.podUtils = podUtils;
29 | }
30 |
31 | public void setRegistration(int port) throws UnknownHostException {
32 | String ip = registrationProperties.getIpAddress() != null ? registrationProperties.getIpAddress() : InetAddress.getLocalHost().getHostAddress();
33 | registration.setHost(ip);
34 | registration.setPort(port);
35 | registration.setServiceId(getAppName(properties, getContext().getEnvironment()) + "." + getNamespace(getContext().getEnvironment()));
36 | registration.getMetadata().put("namespace", getNamespace(getContext().getEnvironment()));
37 | registration.getMetadata().put("name", getAppName(properties, getContext().getEnvironment()));
38 | this.registration = registration;
39 | }
40 |
41 | @Override
42 | protected Object getConfiguration() {
43 | return properties;
44 | }
45 |
46 | @Override
47 | protected boolean isEnabled() {
48 | return !podUtils.isInsideKubernetes();
49 | }
50 |
51 | @Override
52 | protected KubernetesRegistration getRegistration() {
53 | return registration;
54 | }
55 |
56 | @Override
57 | protected KubernetesRegistration getManagementRegistration() {
58 | return registration;
59 | }
60 |
61 | public String getAppName(KubernetesDiscoveryProperties properties, Environment env) {
62 | // TODO - think about a replacement
63 | // final String appName = properties.getServiceName();
64 | // if (StringUtils.hasText(appName)) {
65 | // return appName;
66 | // }
67 | return env.getProperty("spring.application.name", "application");
68 | }
69 |
70 | public String getNamespace(Environment env) {
71 | return env.getProperty("KUBERNETES_NAMESPACE", "external");
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/KubernetesAutoServiceRegistrationAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Qualifier;
5 | import org.springframework.boot.autoconfigure.AutoConfigureAfter;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 | import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
9 | import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
10 | import org.springframework.cloud.kubernetes.commons.PodUtils;
11 | import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
12 | import org.springframework.context.annotation.Bean;
13 | import org.springframework.context.annotation.Configuration;
14 |
15 | import java.net.UnknownHostException;
16 |
17 | @Configuration
18 | @ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.register", havingValue = "true")
19 | @AutoConfigureAfter({AutoServiceRegistrationConfiguration.class, KubernetesServiceRegistryAutoConfiguration.class})
20 | public class KubernetesAutoServiceRegistrationAutoConfiguration {
21 |
22 | @Autowired
23 | AutoServiceRegistrationProperties autoServiceRegistrationProperties;
24 |
25 | @Bean
26 | @ConditionalOnMissingBean
27 | public KubernetesAutoServiceRegistration autoServiceRegistration(
28 | @Qualifier("serviceRegistry") KubernetesServiceRegistry registry,
29 | AutoServiceRegistrationProperties autoServiceRegistrationProperties,
30 | KubernetesDiscoveryProperties properties,
31 | KubernetesRegistrationProperties registrationProperties,
32 | KubernetesRegistration registration, PodUtils podUtils) {
33 | return new KubernetesAutoServiceRegistration(registry,
34 | autoServiceRegistrationProperties, registration, properties, registrationProperties, podUtils);
35 | }
36 |
37 | @Bean
38 | public KubernetesAutoServiceRegistrationListener listener(KubernetesAutoServiceRegistration registration) {
39 | return new KubernetesAutoServiceRegistrationListener(registration);
40 | }
41 |
42 | @Bean
43 | public KubernetesRegistration registration(KubernetesDiscoveryProperties properties) throws UnknownHostException {
44 | return new KubernetesRegistration(properties);
45 | }
46 |
47 | @Bean
48 | public KubernetesRegistrationProperties kubernetesRegistrationProperties() {
49 | return new KubernetesRegistrationProperties();
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/KubernetesAutoServiceRegistrationListener.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext;
2 |
3 | import java.net.UnknownHostException;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import org.springframework.boot.web.context.WebServerInitializedEvent;
9 | import org.springframework.context.ApplicationEvent;
10 | import org.springframework.context.event.SmartApplicationListener;
11 |
12 | public class KubernetesAutoServiceRegistrationListener implements SmartApplicationListener {
13 |
14 | private static final Logger LOGGER = LoggerFactory.getLogger(KubernetesAutoServiceRegistrationListener.class);
15 |
16 | private final KubernetesAutoServiceRegistration autoServiceRegistration;
17 |
18 | KubernetesAutoServiceRegistrationListener(KubernetesAutoServiceRegistration autoServiceRegistration) {
19 | this.autoServiceRegistration = autoServiceRegistration;
20 | }
21 |
22 | @Override
23 | public boolean supportsEventType(Class extends ApplicationEvent> eventType) {
24 | return WebServerInitializedEvent.class.isAssignableFrom(eventType);
25 | }
26 |
27 | @Override
28 | public boolean supportsSourceType(Class> sourceType) {
29 | return true;
30 | }
31 |
32 | @Override
33 | public int getOrder() {
34 | return 0;
35 | }
36 |
37 | @Override
38 | public void onApplicationEvent(ApplicationEvent applicationEvent) {
39 | if (applicationEvent instanceof WebServerInitializedEvent) {
40 | WebServerInitializedEvent event = (WebServerInitializedEvent) applicationEvent;
41 | try {
42 | autoServiceRegistration.setRegistration(event.getWebServer().getPort());
43 | autoServiceRegistration.start();
44 | } catch (UnknownHostException e) {
45 | LOGGER.error("Error registering to kubernetes", e);
46 | }
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/KubernetesRegistration.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext;
2 |
3 | import org.springframework.cloud.client.serviceregistry.Registration;
4 | import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
5 |
6 | import java.net.URI;
7 | import java.util.HashMap;
8 | import java.util.Map;
9 |
10 | public class KubernetesRegistration implements Registration {
11 |
12 | private KubernetesDiscoveryProperties properties;
13 |
14 | private String serviceId;
15 | private String instanceId;
16 | private String host;
17 | private int port;
18 | private Map metadata = new HashMap<>();
19 |
20 | public KubernetesRegistration(KubernetesDiscoveryProperties properties) {
21 | this.properties = properties;
22 | }
23 |
24 | @Override
25 | public String getInstanceId() {
26 | return instanceId;
27 | }
28 |
29 | @Override
30 | public String getServiceId() {
31 | return serviceId;
32 | }
33 |
34 | @Override
35 | public String getHost() {
36 | return host;
37 | }
38 |
39 | @Override
40 | public int getPort() {
41 | return port;
42 | }
43 |
44 | @Override
45 | public boolean isSecure() {
46 | return false;
47 | }
48 |
49 | @Override
50 | public URI getUri() {
51 | return null;
52 | }
53 |
54 | @Override
55 | public Map getMetadata() {
56 | return metadata;
57 | }
58 |
59 | @Override
60 | public String getScheme() {
61 | return "http";
62 | }
63 |
64 | public void setServiceId(String serviceId) {
65 | this.serviceId = serviceId;
66 | }
67 |
68 | public void setInstanceId(String instanceId) {
69 | this.instanceId = instanceId;
70 | }
71 |
72 | public void setHost(String host) {
73 | this.host = host;
74 | }
75 |
76 | public void setPort(int port) {
77 | this.port = port;
78 | }
79 |
80 | public void setMetadata(Map metadata) {
81 | this.metadata = metadata;
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/KubernetesRegistrationProperties.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | @ConfigurationProperties("spring.cloud.kubernetes.discovery")
6 | public class KubernetesRegistrationProperties {
7 |
8 | /**
9 | * Used only for external app - IP address to use when accessing service (must also set preferIpAddress to use).
10 | */
11 | private String ipAddress;
12 |
13 | /**
14 | * Used only for external app - Hostname to use when accessing server.
15 | */
16 | private String hostname;
17 |
18 | /**
19 | * Used only for external app - Use ip address rather than hostname during registration.
20 | */
21 | private boolean preferIpAddress;
22 |
23 | /**
24 | * Used only for external app - Port to register the service under (defaults to listening port).
25 | */
26 | private Integer port;
27 |
28 | /**
29 | * Used only for external app - enable registration
30 | */
31 | private boolean register;
32 |
33 | public String getIpAddress() {
34 | return ipAddress;
35 | }
36 |
37 | public void setIpAddress(String ipAddress) {
38 | this.ipAddress = ipAddress;
39 | }
40 |
41 | public String getHostname() {
42 | return hostname;
43 | }
44 |
45 | public void setHostname(String hostname) {
46 | this.hostname = hostname;
47 | }
48 |
49 | public boolean isPreferIpAddress() {
50 | return preferIpAddress;
51 | }
52 |
53 | public void setPreferIpAddress(boolean preferIpAddress) {
54 | this.preferIpAddress = preferIpAddress;
55 | }
56 |
57 | public Integer getPort() {
58 | return port;
59 | }
60 |
61 | public void setPort(Integer port) {
62 | this.port = port;
63 | }
64 |
65 | public boolean isRegister() {
66 | return register;
67 | }
68 |
69 | public void setRegister(boolean register) {
70 | this.register = register;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/KubernetesServiceRegistry.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext;
2 |
3 | import io.fabric8.kubernetes.api.model.*;
4 | import io.fabric8.kubernetes.client.KubernetesClient;
5 | import io.fabric8.kubernetes.client.dsl.Resource;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
9 | import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
10 |
11 | import java.util.Collections;
12 |
13 | public class KubernetesServiceRegistry implements ServiceRegistry {
14 |
15 | private static final Logger LOG = LoggerFactory.getLogger(KubernetesServiceRegistry.class);
16 | private static final String LABEL_IS_EXTERNAL_NAME = "external";
17 |
18 | private final KubernetesClient client;
19 | private KubernetesDiscoveryProperties properties;
20 |
21 | public KubernetesServiceRegistry(KubernetesClient client, KubernetesDiscoveryProperties properties) {
22 | this.client = client;
23 | this.properties = properties;
24 | }
25 |
26 | @Override
27 | public void register(KubernetesRegistration registration) {
28 | LOG.info("Registering service with kubernetes: " + registration.getServiceId());
29 | Resource resource = client.endpoints()
30 | .inNamespace(registration.getMetadata().get("namespace"))
31 | .withName(registration.getMetadata().get("name"));
32 | Endpoints endpoints = resource.get();
33 | if (endpoints == null) {
34 | Service s = new ServiceBuilder().withNewMetadata()
35 | .withName(registration.getMetadata().get("name"))
36 | .withLabels(Collections.singletonMap("app", registration.getMetadata().get("name")))
37 | .withAnnotations(Collections.singletonMap("healthUrl", "/actuator/health"))
38 | .endMetadata()
39 | .withNewSpec()
40 | .addNewPort().withPort(registration.getPort()).withProtocol("TCP").endPort()
41 | .addToSelector(Collections.singletonMap("app", registration.getMetadata().get("name")))
42 | .endSpec().build();
43 |
44 | Service ss = client.services().inNamespace(registration.getMetadata().get("namespace"))
45 | .create(s);
46 | LOG.info("New service: {}", ss);
47 | Endpoints e = client.endpoints().createOrReplace(create(registration));
48 | LOG.info("New endpoint: {}", e);
49 | } else {
50 | try {
51 | for (EndpointSubset s : endpoints.getSubsets()) {
52 | if (s.getPorts().contains(registration.getPort())) {
53 | s.getAddresses().add(new EndpointAddressBuilder().withIp(registration.getHost()).build());
54 | }
55 | }
56 | resource.edit().setSubsets(endpoints.getSubsets());
57 |
58 | LOG.info("Endpoint updated: {}", resource.get());
59 | } catch (RuntimeException e) {
60 | EndpointSubset es = new EndpointSubsetBuilder().addNewPort().withPort(registration.getPort()).endPort()
61 | .addNewAddress().withIp(registration.getHost()).endAddress()
62 | .build();
63 | resource.edit().getSubsets().add(es);
64 | // Endpoints updatedEndpoints = resource.edit().getSubsets();
65 | // .addNewSubset()
66 | // .withPorts(new EndpointPortBuilder().withPort(registration.getPort()).build())
67 | // .withAddresses(new EndpointAddressBuilder().withIp(registration.getHost()).build())
68 | // .endSubset()
69 | // .done();
70 | LOG.info("Endpoint updated: {}", resource.get());
71 | }
72 | }
73 |
74 | }
75 |
76 | @Override
77 | public void deregister(KubernetesRegistration registration) {
78 | LOG.info("De-registering service with kubernetes: " + registration.getInstanceId());
79 | Resource resource = client.endpoints()
80 | .inNamespace(registration.getMetadata().get("namespace"))
81 | .withName(registration.getMetadata().get("name"));
82 |
83 | EndpointAddress address = new EndpointAddressBuilder().withIp(registration.getHost()).build();
84 | for (EndpointSubset s : resource.get().getSubsets()) {
85 | if (s.getPorts().contains(registration.getPort())) {
86 | s.getAddresses().remove(address);
87 | }
88 | }
89 | resource.edit().setSubsets(resource.get().getSubsets());
90 | // .editMatchingSubset(builder -> builder.hasMatchingPort(v -> v.getPort().equals(registration.getPort())))
91 | // .removeFromAddresses(address)
92 | // .endSubset()
93 | // .done();
94 | LOG.info("Endpoint updated: {}", resource.get().getSubsets());
95 |
96 | // TODO - remove empty subset
97 | // resource.get().getSubsets().stream()
98 | // .filter(subset -> subset.getAddresses().size() == 0)
99 | // .forEach(subset -> resource.edit()
100 | // .removeFromSubsets(subset)
101 | // .done());
102 | }
103 |
104 | @Override
105 | public void close() {
106 |
107 | }
108 |
109 | @Override
110 | public void setStatus(KubernetesRegistration registration, String status) {
111 |
112 | }
113 |
114 | @Override
115 | public T getStatus(KubernetesRegistration registration) {
116 | return null;
117 | }
118 |
119 | private Endpoints create(KubernetesRegistration registration) {
120 | EndpointAddress address = new EndpointAddressBuilder().withIp(registration.getHost()).build();
121 | EndpointPort port = new EndpointPortBuilder().withPort(registration.getPort()).build();
122 | EndpointSubset subset = new EndpointSubsetBuilder().withAddresses(address).withPorts(port).build();
123 | ObjectMeta metadata = new ObjectMetaBuilder()
124 | .withName(registration.getMetadata().get("name"))
125 | .withNamespace(registration.getMetadata().get("namespace"))
126 | .withLabels(Collections.singletonMap(LABEL_IS_EXTERNAL_NAME, "true"))
127 | .build();
128 | Endpoints endpoints = new EndpointsBuilder().withSubsets(subset).withMetadata(metadata).build();
129 | return endpoints;
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/KubernetesServiceRegistryAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext;
2 |
3 | import io.fabric8.kubernetes.client.KubernetesClient;
4 | import org.springframework.boot.autoconfigure.AutoConfigureBefore;
5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
7 | import org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration;
8 | import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | @Configuration
13 | @ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.register", havingValue = "true")
14 | @AutoConfigureBefore(ServiceRegistryAutoConfiguration.class)
15 | public class KubernetesServiceRegistryAutoConfiguration {
16 |
17 | @Bean
18 | @ConditionalOnMissingBean
19 | public KubernetesServiceRegistry serviceRegistry(KubernetesClient client, KubernetesDiscoveryProperties properties) {
20 | return new KubernetesServiceRegistry(client, properties);
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 | org.springframework.cloud.kubernetes.discovery.ext.KubernetesServiceRegistryAutoConfiguration,\
3 | org.springframework.cloud.kubernetes.discovery.ext.KubernetesAutoServiceRegistrationAutoConfiguration
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/main/resources/META-INF/spring/org.springframework.boot.AutoConfiguration.imports:
--------------------------------------------------------------------------------
1 | org.springframework.cloud.kubernetes.discovery.ext.KubernetesServiceRegistryAutoConfiguration
2 | org.springframework.cloud.kubernetes.discovery.ext.KubernetesAutoServiceRegistrationAutoConfiguration
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/test/java/org/springframework/cloud/kubernetes/discovery/ext/KubernetesServiceRegistryTests.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext;
2 |
3 | import io.fabric8.kubernetes.api.model.Service;
4 | import io.fabric8.kubernetes.client.Config;
5 | import io.fabric8.kubernetes.client.DefaultKubernetesClient;
6 | import io.fabric8.kubernetes.client.KubernetesClient;
7 | import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
8 | import org.junit.jupiter.api.BeforeAll;
9 | import org.junit.jupiter.api.Test;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.test.context.SpringBootTest;
12 | import org.springframework.boot.test.context.TestConfiguration;
13 | import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
14 | import org.springframework.context.annotation.Bean;
15 | import org.springframework.context.annotation.Import;
16 |
17 | import java.util.List;
18 | import java.util.Map;
19 |
20 | import static org.junit.jupiter.api.Assertions.assertEquals;
21 | import static org.junit.jupiter.api.Assertions.assertTrue;
22 |
23 | @SpringBootTest(classes = KubernetesServiceRegistry.class)
24 | @Import(KubernetesServiceRegistryTestsConfig.class)
25 | @EnableKubernetesMockClient(crud = true)
26 | public class KubernetesServiceRegistryTests {
27 |
28 | static KubernetesClient client;
29 |
30 | @Autowired
31 | KubernetesServiceRegistry registry;
32 | @Autowired
33 | KubernetesDiscoveryProperties properties;
34 |
35 | @BeforeAll
36 | static void setup() {
37 | System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, client.getConfiguration().getMasterUrl());
38 | System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
39 | System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
40 | System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "default");
41 | }
42 |
43 | @Test
44 | void shouldRegisterNew() {
45 | KubernetesRegistration r = new KubernetesRegistration(properties);
46 | r.setMetadata(Map.of("namespace", "default", "name", "test"));
47 | r.setServiceId("test");
48 | r.setHost("192.168.1.20");
49 | r.setPort(8080);
50 | r.setInstanceId("test-1");
51 | registry.register(r);
52 |
53 | List services = client.services().inNamespace("default")
54 | .list().getItems();
55 | assertTrue(services.size() > 0);
56 | assertEquals("test", services.get(0).getMetadata().getName());
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-client/src/test/java/org/springframework/cloud/kubernetes/discovery/ext/KubernetesServiceRegistryTestsConfig.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext;
2 |
3 | import io.fabric8.kubernetes.client.DefaultKubernetesClient;
4 | import io.fabric8.kubernetes.client.KubernetesClient;
5 | import org.springframework.boot.test.context.TestConfiguration;
6 | import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Primary;
9 |
10 | @TestConfiguration
11 | public class KubernetesServiceRegistryTestsConfig {
12 |
13 | @Bean
14 | KubernetesClient kubernetesClient() {
15 | return new DefaultKubernetesClient();
16 | }
17 |
18 | @Bean
19 | @Primary
20 | KubernetesDiscoveryProperties kubernetesDiscoveryProperties() {
21 | return KubernetesDiscoveryProperties.DEFAULT;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-watcher/k8s/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: spring-cloud-discovery-watcher
5 | spec:
6 | selector:
7 | matchLabels:
8 | app: spring-cloud-discovery-watcher
9 | template:
10 | metadata:
11 | labels:
12 | app: spring-cloud-discovery-watcher
13 | spec:
14 | containers:
15 | - name: watcher
16 | image: piomin/spring-cloud-discovery-watcher
17 | resources:
18 | requests:
19 | memory: "32Mi"
20 | cpu: "100m"
21 | limits:
22 | memory: "320Mi"
23 | cpu: "1000m"
24 | ports:
25 | - containerPort: 8080
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-watcher/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 3.5.0
9 |
10 |
11 | 4.0.0
12 |
13 | spring-cloud-kubernetes-discovery-ext-watcher
14 | com.github.piomin
15 | 1.1-SNAPSHOT
16 |
17 |
18 | 21
19 |
20 |
21 |
22 |
23 | org.springframework.cloud
24 | spring-cloud-starter-kubernetes-fabric8
25 |
26 |
27 | org.springframework.boot
28 | spring-boot-starter-web
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-actuator
33 |
34 |
35 |
36 |
37 |
38 |
39 | org.springframework.cloud
40 | spring-cloud-dependencies
41 | 2023.0.5
42 | pom
43 | import
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-maven-plugin
53 |
54 |
55 |
56 | build-info
57 |
58 |
59 |
60 |
61 |
62 | com.google.cloud.tools
63 | jib-maven-plugin
64 | 3.4.5
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-watcher/skaffold.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v2alpha1
2 | kind: Config
3 | metadata:
4 | name: employee-service
5 | build:
6 | artifacts:
7 | - image: piomin/spring-cloud-discovery-watcher
8 | jib: {}
9 | tagPolicy:
10 | gitCommit: {}
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-watcher/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/watcher/SpringCloudKubernetesDiscoveryExtWatcherApp.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext.watcher;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.springframework.boot.SpringApplication;
7 | import org.springframework.boot.autoconfigure.SpringBootApplication;
8 | import org.springframework.boot.web.client.RestTemplateBuilder;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.scheduling.annotation.EnableAsync;
11 | import org.springframework.scheduling.annotation.EnableScheduling;
12 | import org.springframework.web.client.RestTemplate;
13 |
14 | @SpringBootApplication
15 | @EnableScheduling
16 | @EnableAsync
17 | public class SpringCloudKubernetesDiscoveryExtWatcherApp {
18 |
19 | public static void main(String[] args) {
20 | SpringApplication.run(SpringCloudKubernetesDiscoveryExtWatcherApp.class, args);
21 | }
22 |
23 | @Bean
24 | RestTemplate restTemplate() {
25 | return new RestTemplateBuilder().build();
26 | }
27 |
28 | @Bean
29 | List watchedUrls() {
30 | return new ArrayList<>();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-watcher/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/watcher/config/KubernetesWatcherProperties.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext.watcher.config;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | @ConfigurationProperties("spring.cloud.kubernetes.watcher")
6 | public class KubernetesWatcherProperties {
7 |
8 | private String targetNamespace;
9 | private Boolean allNamespaces = false;
10 | private int retries = 3;
11 | private int retryTimeout = 1000;
12 |
13 | public String getTargetNamespace() {
14 | return targetNamespace;
15 | }
16 |
17 | public void setTargetNamespace(String targetNamespace) {
18 | this.targetNamespace = targetNamespace;
19 | }
20 |
21 | public Boolean getAllNamespaces() {
22 | return allNamespaces;
23 | }
24 |
25 | public void setAllNamespaces(Boolean allNamespaces) {
26 | this.allNamespaces = allNamespaces;
27 | }
28 |
29 | public int getRetries() {
30 | return retries;
31 | }
32 |
33 | public void setRetries(int retries) {
34 | this.retries = retries;
35 | }
36 |
37 | public int getRetryTimeout() {
38 | return retryTimeout;
39 | }
40 |
41 | public void setRetryTimeout(int retryTimeout) {
42 | this.retryTimeout = retryTimeout;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-watcher/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/watcher/model/KubernetesRegistration.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext.watcher.model;
2 |
3 | import org.springframework.cloud.client.serviceregistry.Registration;
4 |
5 | import java.net.URI;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | public class KubernetesRegistration implements Registration {
10 |
11 | private String serviceId;
12 | private String instanceId;
13 | private String host;
14 | private int port;
15 | private Map metadata = new HashMap<>();
16 |
17 | @Override
18 | public String getInstanceId() {
19 | return instanceId;
20 | }
21 |
22 | @Override
23 | public String getServiceId() {
24 | return serviceId;
25 | }
26 |
27 | @Override
28 | public String getHost() {
29 | return host;
30 | }
31 |
32 | @Override
33 | public int getPort() {
34 | return port;
35 | }
36 |
37 | @Override
38 | public boolean isSecure() {
39 | return false;
40 | }
41 |
42 | @Override
43 | public URI getUri() {
44 | return null;
45 | }
46 |
47 | @Override
48 | public Map getMetadata() {
49 | return metadata;
50 | }
51 |
52 | @Override
53 | public String getScheme() {
54 | return "http";
55 | }
56 |
57 | public void setServiceId(String serviceId) {
58 | this.serviceId = serviceId;
59 | }
60 |
61 | public void setInstanceId(String instanceId) {
62 | this.instanceId = instanceId;
63 | }
64 |
65 | public void setHost(String host) {
66 | this.host = host;
67 | }
68 |
69 | public void setPort(int port) {
70 | this.port = port;
71 | }
72 |
73 | public void setMetadata(Map metadata) {
74 | this.metadata = metadata;
75 | }
76 |
77 | @Override
78 | public String toString() {
79 | return "KubernetesRegistration{" +
80 | "serviceId='" + serviceId + '\'' +
81 | ", host='" + host + '\'' +
82 | ", port=" + port +
83 | '}';
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-watcher/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/watcher/schedule/LivenessScheduler.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext.watcher.schedule;
2 |
3 | import io.fabric8.kubernetes.api.model.EndpointSubset;
4 | import io.fabric8.kubernetes.api.model.EndpointsList;
5 | import io.fabric8.kubernetes.client.KubernetesClient;
6 |
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.boot.actuate.health.Health;
10 | import org.springframework.cloud.kubernetes.discovery.ext.watcher.model.KubernetesRegistration;
11 | import org.springframework.cloud.kubernetes.discovery.ext.watcher.task.DeactivateServiceTask;
12 | import org.springframework.http.HttpStatus;
13 | import org.springframework.http.ResponseEntity;
14 | import org.springframework.scheduling.annotation.Scheduled;
15 | import org.springframework.stereotype.Component;
16 | import org.springframework.web.client.RestTemplate;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | @Component
22 | public class LivenessScheduler {
23 |
24 | private static final Logger LOGGER = LoggerFactory.getLogger(LivenessScheduler.class);
25 | private static final String LABEL_IS_EXTERNAL_NAME = "external";
26 |
27 | private DeactivateServiceTask task;
28 | private KubernetesClient kubernetesClient;
29 | private RestTemplate restTemplate;
30 | private List watchedUrls;
31 |
32 | public LivenessScheduler(KubernetesClient kubernetesClient, RestTemplate restTemplate, DeactivateServiceTask task, List watchedUrls) {
33 | this.kubernetesClient = kubernetesClient;
34 | this.restTemplate = restTemplate;
35 | this.task = task;
36 | this.watchedUrls = watchedUrls;
37 | }
38 |
39 | @Scheduled(fixedRate = 10000)
40 | public void watch() {
41 | EndpointsList endpointsList = kubernetesClient.endpoints()
42 | .inNamespace(kubernetesClient.getNamespace())
43 | .list();
44 | endpointsList.getItems().stream()
45 | .filter(endpoints -> endpoints.getMetadata().getLabels().containsKey(LABEL_IS_EXTERNAL_NAME))
46 | .forEach(it -> {
47 | if (!it.getSubsets().isEmpty()) {
48 | EndpointSubset subset = it.getSubsets().get(0);
49 | subset.getAddresses().forEach(endpointAddress -> {
50 | String url = "http://" + endpointAddress.getIp() + ":" + subset.getPorts().get(0)
51 | .getPort() + "/actuator/health";
52 | if (!watchedUrls.contains(url)) {
53 | ResponseEntity responseEntity = null;
54 | try {
55 | responseEntity = restTemplate.getForEntity(url, String.class);
56 | LOGGER.info("Active endpoint check: url->{}, status->{}", url, responseEntity.getStatusCodeValue());
57 | } catch (Exception e) {
58 | LOGGER.info("Error connecting to endpoint: {}", url);
59 | }
60 | if (responseEntity == null || responseEntity.getStatusCode() != HttpStatus.OK) {
61 | task.process(url, create(endpointAddress.getIp(), subset.getPorts().get(0)
62 | .getPort(), it.getMetadata().getName()));
63 | watchedUrls.add(url);
64 | }
65 | }
66 | });
67 | }
68 | });
69 | }
70 |
71 | private KubernetesRegistration create(String ip, int port, String name) {
72 | KubernetesRegistration registration = new KubernetesRegistration();
73 | registration.setServiceId(name);
74 | registration.setHost(ip);
75 | registration.setPort(port);
76 | registration.getMetadata().put("name", name);
77 | return registration;
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/spring-cloud-kubernetes-discovery-ext-watcher/src/main/java/org/springframework/cloud/kubernetes/discovery/ext/watcher/task/DeactivateServiceTask.java:
--------------------------------------------------------------------------------
1 | package org.springframework.cloud.kubernetes.discovery.ext.watcher.task;
2 |
3 | import io.fabric8.kubernetes.api.model.EndpointAddress;
4 | import io.fabric8.kubernetes.api.model.EndpointAddressBuilder;
5 | import io.fabric8.kubernetes.api.model.EndpointSubset;
6 | import io.fabric8.kubernetes.api.model.Endpoints;
7 | import io.fabric8.kubernetes.client.KubernetesClient;
8 | import io.fabric8.kubernetes.client.dsl.Resource;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.cloud.kubernetes.discovery.ext.watcher.model.KubernetesRegistration;
12 | import org.springframework.http.ResponseEntity;
13 | import org.springframework.scheduling.annotation.Async;
14 | import org.springframework.stereotype.Service;
15 | import org.springframework.web.client.RestTemplate;
16 |
17 | import java.util.ArrayList;
18 | import java.util.List;
19 |
20 | @Service
21 | @Async
22 | public class DeactivateServiceTask {
23 |
24 | private static final Logger LOGGER = LoggerFactory.getLogger(DeactivateServiceTask.class);
25 | private static final int RETRIES = 3;
26 | private static final int RETRY_PERIOD = 3000;
27 |
28 | private KubernetesClient kubernetesClient;
29 | private RestTemplate restTemplate;
30 | private List watchedUrls;
31 |
32 | public DeactivateServiceTask(KubernetesClient kubernetesClient, RestTemplate restTemplate, List watchedUrls) {
33 | this.kubernetesClient = kubernetesClient;
34 | this.restTemplate = restTemplate;
35 | this.watchedUrls = watchedUrls;
36 | }
37 |
38 | public void process(String url, KubernetesRegistration registration) {
39 | boolean ok = false;
40 | ResponseEntity entity = null;
41 | for (int i = 0; i < RETRIES; i++) {
42 | try {
43 | entity = restTemplate.getForEntity(url, String.class);
44 | } catch (Exception e) {
45 |
46 | }
47 | if (entity == null || entity.getStatusCodeValue() != 200) {
48 | try {
49 | Thread.sleep(RETRY_PERIOD);
50 | } catch (InterruptedException e) {
51 | e.printStackTrace();
52 | }
53 | } else {
54 | ok = true;
55 | break;
56 | }
57 | }
58 | if (!ok) {
59 | deregister(registration);
60 | watchedUrls.remove(url);
61 | }
62 | }
63 |
64 | private void deregister(KubernetesRegistration registration) {
65 | LOGGER.info("De-registering endpoint: {}", registration);
66 | Resource resource = kubernetesClient.endpoints()
67 | .inNamespace(kubernetesClient.getNamespace())
68 | .withName(registration.getMetadata().get("name"));
69 |
70 | EndpointAddress address = new EndpointAddressBuilder().withIp(registration.getHost()).build();
71 | List addressList = resource.get().getSubsets().get(0).getAddresses();
72 | addressList.remove(address);
73 | if (addressList.size() > 0) {
74 | Endpoints e = resource.get();
75 | for (EndpointSubset s : e.getSubsets()) {
76 | if (s.getPorts().contains(registration.getPort())) {
77 | s.getAddresses().add(new EndpointAddressBuilder().withIp(registration.getHost()).build());
78 | }
79 | }
80 | resource.edit().setSubsets(e.getSubsets());
81 | LOGGER.info("Endpoint updated: {}", e);
82 | } else {
83 | resource.edit().setSubsets(new ArrayList<>());
84 | LOGGER.info("Endpoint updated: {}", resource.get());
85 | }
86 |
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------