Add a profile listener that will automatically add the kubernetes profile when running inside kubernetes.

This commit is contained in:
Ioannis Canellos
2016-03-25 20:54:36 +02:00
parent 505493fa9c
commit 45b48c2d0f
8 changed files with 304 additions and 33 deletions

View File

@@ -80,10 +80,18 @@ public class KubernetesAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public KubernetesHealthIndicator kubernetesHealthIndicator(KubernetesClient client) {
return new KubernetesHealthIndicator(client);
StandardPodUtils kubernetesPodUtils(KubernetesClient client) {
return new StandardPodUtils(client);
}
@Bean
@ConditionalOnMissingBean
public KubernetesHealthIndicator kubernetesHealthIndicator(KubernetesClient client, StandardPodUtils podUtils) {
return new KubernetesHealthIndicator(client, podUtils);
}
private static <D> D or(D dis, D dat) {
if (dis != null) {
return dis;

View File

@@ -17,61 +17,37 @@
package io.fabric8.spring.cloud.kubernetes;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import java.nio.file.Paths;
public class KubernetesHealthIndicator extends AbstractHealthIndicator {
private static final String HOSTNAME = "HOSTNAME";
private String hostName;
private KubernetesClient client;
private PodUtils utils;
public KubernetesHealthIndicator(KubernetesClient client) {
public KubernetesHealthIndicator(KubernetesClient client, PodUtils utils) {
this.client = client;
this.hostName = System.getenv(HOSTNAME);
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
try {
Pod current = getCurrentPod();
Pod current = utils.currentPod().get();
if (current != null) {
builder.up()
.withDetail("internal", true)
.withDetail("podName", hostName)
.withDetail("inside", true)
.withDetail("podName", current.getMetadata().getName())
.withDetail("podIp", current.getStatus().getPodIP())
.withDetail("serviceAccount", current.getSpec().getServiceAccountName())
.withDetail("nodeName", current.getSpec().getNodeName())
.withDetail("hostIp", current.getStatus().getHostIP());
} else {
builder.up()
.withDetail("internal", false);
.withDetail("inside", false);
}
} catch (Exception e) {
builder.down(e);
}
}
private Pod getCurrentPod() {
if (isServiceAccountFound() && isHostNameEnvVarPresent()) {
return client.pods().withName(hostName).get();
} else {
return null;
}
}
private boolean isHostNameEnvVarPresent() {
return hostName != null && !hostName.isEmpty();
}
private boolean isServiceAccountFound() {
return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists() &&
Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH).toFile().exists();
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2016 to the original authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.spring.cloud.kubernetes;
import java.util.function.Supplier;
public class LazilyInstantiate<T> implements Supplier<T> {
public static <T> LazilyInstantiate<T> using(Supplier<T> supplier) {
return new LazilyInstantiate<T>(supplier);
}
public synchronized T get() {
return current.get();
}
private LazilyInstantiate(Supplier supplier) {
this.supplier = supplier;
this.current = () -> swapper();
}
private final Supplier<T> supplier;
private Supplier<T> current;
private T swapper() {
T obj = supplier.get();
current = () -> obj;
return obj;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2016 to the original authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.spring.cloud.kubernetes;
import io.fabric8.kubernetes.api.model.Pod;
import java.util.function.Supplier;
public interface PodUtils {
/**
* @return A supplier of the currentPod {@link Pod}. The supplier will hold the currentPod {@Pod} if inside Kubernetes
* or false, otherwise.
*/
Supplier<Pod> currentPod();
/**
* @return true if called from within Kubernetes, false otherwise.
*/
Boolean isInsideKubernetes();
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2016 to the original authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.spring.cloud.kubernetes;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import java.nio.file.Paths;
import java.util.function.Supplier;
public class StandardPodUtils implements PodUtils {
public static final String HOSTNAME = "HOSTNAME";
private final KubernetesClient client;
private final String hostName;
private Supplier<Pod> current;
public StandardPodUtils(KubernetesClient client) {
this.client = client;
this.hostName = System.getenv(HOSTNAME);
this.current = LazilyInstantiate.using(() -> internalGetPod());
}
@Override
public Supplier<Pod> currentPod() {
return current;
}
@Override
public Boolean isInsideKubernetes() {
return currentPod().get() != null;
}
private synchronized Pod internalGetPod() {
if (isServiceAccountFound() && isHostNameEnvVarPresent()) {
return client.pods().withName(hostName).get();
} else {
return null;
}
}
private boolean isHostNameEnvVarPresent() {
return hostName != null && !hostName.isEmpty();
}
private boolean isServiceAccountFound() {
return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists() &&
Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH).toFile().exists();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2016 to the original authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.spring.cloud.kubernetes.profile;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.spring.cloud.kubernetes.PodUtils;
import io.fabric8.spring.cloud.kubernetes.StandardPodUtils;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
public class KubernetesApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private final KubernetesProfileApplicationListener listener;
private static final int ORDER = 100;
public KubernetesApplicationContextInitializer() {
//If we are inside Kubernetes this should be perfectly valid.
//If not then we won't add the Kubernetes profile anyway.
this(new StandardPodUtils(new DefaultKubernetesClient()));
}
public KubernetesApplicationContextInitializer(PodUtils utils) {
this(new KubernetesProfileApplicationListener(utils));
}
public KubernetesApplicationContextInitializer(KubernetesProfileApplicationListener listener) {
this.listener = listener;
}
@Override
public int getOrder() {
return ORDER;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
listener.addKubernetesProfile(applicationContext.getEnvironment());
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2016 to the original authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.spring.cloud.kubernetes.profile;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.spring.cloud.kubernetes.PodUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
public class KubernetesProfileApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(KubernetesProfileApplicationListener.class);
private static final String KUBERNETES_PROFILE = "kubernetes";
private static final int OFFSET = 1;
private static final int ORDER = Ordered.HIGHEST_PRECEDENCE + OFFSET;
private final PodUtils utils;
public KubernetesProfileApplicationListener(PodUtils utils) {
this.utils = utils;
}
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
addKubernetesProfile(environment);
}
void addKubernetesProfile(ConfigurableEnvironment environment) {
Pod current = utils.currentPod().get();
if (current != null) {
if (!hasKubernetesProfile(environment)) {
environment.addActiveProfile(KUBERNETES_PROFILE);
}
}
if (utils.isInsideKubernetes()) {
if (hasKubernetesProfile(environment)) {
LOGGER.debug("'kubernetes' already in list of active profiles");
} else {
LOGGER.debug("Adding 'kubernetes' to list of active profiles");
environment.addActiveProfile(KUBERNETES_PROFILE);
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.warn("Not running inside kubernetes. Skipping 'kuberntes' profile activation.");
}
}
}
private boolean hasKubernetesProfile(Environment environment) {
for (String activeProfile : environment.getActiveProfiles()) {
if (KUBERNETES_PROFILE.equalsIgnoreCase(activeProfile)) {
return true;
}
}
return false;
}
@Override
public int getOrder() {
return ORDER;
}
}

View File

@@ -1,2 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration
io.fabric8.spring.cloud.kubernetes.KubernetesAutoConfiguration
org.springframework.context.ApplicationContextInitializer=\
io.fabric8.spring.cloud.kubernetes.profile.KubernetesApplicationContextInitializer