You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2020/06/21 06:47:26 UTC

[logging-log4j2] branch release-2.x updated: LOG4J2-2877 - Determine the container id to obtain container and image information

This is an automated email from the ASF dual-hosted git repository.

rgoers pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/release-2.x by this push:
     new dff745e  LOG4J2-2877 - Determine the container id to obtain container and image information
dff745e is described below

commit dff745ec0c98dd87342bd718712d6cace1f3c78e
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Sat Jun 20 23:47:02 2020 -0700

    LOG4J2-2877 - Determine the container id to obtain container and image information
---
 .../log4j/core/config/ConfigurationScheduler.java  |  26 +--
 .../apache/logging/log4j/docker/DockerLookup.java  |   2 +
 .../logging/log4j/kubernetes/ContainerUtil.java    |  90 +++++++++++
 .../log4j/kubernetes/KubernetesClientBuilder.java  |  12 +-
 .../logging/log4j/kubernetes/KubernetesLookup.java | 137 ++++++++++++----
 .../log4j/kubernetes/KubernetesLookupTest.java     |  98 ++++++++++++
 .../src/test/resources/clusterPod.json             | 177 +++++++++++++++++++++
 log4j-kubernetes/src/test/resources/localPod.json  | 141 ++++++++++++++++
 .../docker/app-compose.yml                         |   2 +-
 .../docker/combined-compose.yml                    |  86 ++++++++++
 .../docker/restartApp.sh                           |   2 +-
 .../k8s/sampleapp-deployment.yaml                  |  33 ++--
 .../config/sample/controller/K8SController.java    |  71 +++++++++
 .../src/main/resources/application.yml             |   2 +-
 14 files changed, 817 insertions(+), 62 deletions(-)

diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java
index dd7525c..e393d4c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java
@@ -39,7 +39,7 @@ public class ConfigurationScheduler extends AbstractLifeCycle {
     private static final String SIMPLE_NAME = "Log4j2 " + ConfigurationScheduler.class.getSimpleName();
     private static final int MAX_SCHEDULED_ITEMS = 5;
 
-    private ScheduledExecutorService executorService;
+    private volatile ScheduledExecutorService executorService;
     private int scheduledItems = 0;
     private final String name;
 
@@ -193,17 +193,21 @@ public class ConfigurationScheduler extends AbstractLifeCycle {
 
     private ScheduledExecutorService getExecutorService() {
         if (executorService == null) {
-            if (scheduledItems > 0) {
-                LOGGER.debug("{} starting {} threads", name, scheduledItems);
-                scheduledItems = Math.min(scheduledItems, MAX_SCHEDULED_ITEMS);
-                final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(scheduledItems,
-                        Log4jThreadFactory.createDaemonThreadFactory("Scheduled"));
-                executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
-                executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
-                this.executorService = executor;
+            synchronized (this) {
+                if (executorService == null) {
+                    if (scheduledItems > 0) {
+                        LOGGER.debug("{} starting {} threads", name, scheduledItems);
+                        scheduledItems = Math.min(scheduledItems, MAX_SCHEDULED_ITEMS);
+                        final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(scheduledItems,
+                                Log4jThreadFactory.createDaemonThreadFactory("Scheduled"));
+                        executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+                        executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+                        this.executorService = executor;
 
-            } else {
-                LOGGER.debug("{}: No scheduled items", name);
+                    } else {
+                        LOGGER.debug("{}: No scheduled items", name);
+                    }
+                }
             }
         }
         return executorService;
diff --git a/log4j-docker/src/main/java/org/apache/logging/log4j/docker/DockerLookup.java b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/DockerLookup.java
index c7bd4d5..f09df6e 100644
--- a/log4j-docker/src/main/java/org/apache/logging/log4j/docker/DockerLookup.java
+++ b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/DockerLookup.java
@@ -53,6 +53,8 @@ public class DockerLookup extends AbstractLookup {
         }
         if (baseUri == null) {
             LOGGER.warn("No Docker URI provided. Docker information is unavailable");
+            container = null;
+            return;
         }
         Container current = null;
         try {
diff --git a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/ContainerUtil.java b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/ContainerUtil.java
new file mode 100644
index 0000000..e414045
--- /dev/null
+++ b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/ContainerUtil.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.logging.log4j.kubernetes;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Locate the current docker container.
+ */
+public class ContainerUtil {
+    private static final Logger LOGGER = StatusLogger.getLogger();
+    private static final int MAXLENGTH = 65;
+
+/**
+ * Returns the container id when running in a Docker container.
+ *
+ * This inspects /proc/self/cgroup looking for a Kubernetes Control Group. Once it finds one it attempts
+ * to isolate just the docker container id. There doesn't appear to be a standard way to do this, but
+ * it seems to be the only way to determine what the current container is in a multi-container pod. It would have
+ * been much nicer if Kubernetes would just put the container id in a standard environment variable.
+ *
+ * @see <a href="http://stackoverflow.com/a/25729598/12916">Stackoverflow</a> for a discussion on retrieving the containerId.
+ * @see <a href="https://github.com/jenkinsci/docker-workflow-plugin/blob/master/src/main/java/org/jenkinsci/plugins/docker/workflow/client/ControlGroup.java>ControlGroup</a>
+ * for the original version of this. Not much is actually left but it provided good inspiration.
+ */
+    public static String getContainerId() {
+        try {
+            File file = new File("/proc/self/cgroup");
+            if (file.exists()) {
+                Path path = file.toPath();
+                String id = Files.lines(path).map(ContainerUtil::getContainerId).filter(Objects::nonNull)
+                        .findFirst().orElse(null);
+                LOGGER.debug("Found container id {}", id);
+                return id;
+            } else {
+                LOGGER.warn("Unable to access container information");
+            }
+        } catch (IOException ioe) {
+            LOGGER.warn("Error obtaining container id: {}", ioe.getMessage());
+        }
+        return null;
+    }
+
+    private static String getContainerId(String line) {
+        // Every control group in Kubernetes will use
+        if (line.contains("/kubepods")) {
+            // Strip off everything up to the last slash.
+            int i = line.lastIndexOf('/');
+            if (i < 0) {
+                return null;
+            }
+            // If the remainder has a period then take everything up to it.
+            line = line.substring(i + 1);
+            i = line.lastIndexOf('.');
+            if (i > 0) {
+                line = line.substring(0, i);
+            }
+            // Everything ending with a '/' has already been stripped but the remainder might start with "docker-"
+            if (line.contains("docker-")) {
+                // 8:cpuset:/kubepods.slice/kubepods-pod9c26dfb6_b9c9_11e7_bfb9_02c6c1fc4861.slice/docker-3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b.scope
+                i = line.lastIndexOf("docker-");
+                line = line.substring(i + 7);
+            }
+            return line.length() <= MAXLENGTH ? line : line.substring(0, MAXLENGTH);
+        }
+
+        return null;
+    }
+}
diff --git a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientBuilder.java b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientBuilder.java
index 414f9e7..79c942d 100644
--- a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientBuilder.java
+++ b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientBuilder.java
@@ -32,11 +32,19 @@ public class KubernetesClientBuilder {
     private static final Logger LOGGER = StatusLogger.getLogger();
 
     public KubernetesClient createClient() {
-        return new DefaultKubernetesClient(kubernetesClientConfig());
+        Config config = kubernetesClientConfig();
+        return config != null ? new DefaultKubernetesClient(config) : null;
     }
 
     private Config kubernetesClientConfig() {
-        Config base = Config.autoConfigure(null);
+        Config base = null;
+        try {
+            base = Config.autoConfigure(null);
+        } catch (Exception ex) {
+            if (ex instanceof  NullPointerException) {
+                return null;
+            }
+        }
         KubernetesClientProperties props = new KubernetesClientProperties(base);
         Config properties = new ConfigBuilder(base)
                 .withApiVersion(props.getApiVersion())
diff --git a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesLookup.java b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesLookup.java
index 44aa834..cc07b7d 100644
--- a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesLookup.java
+++ b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesLookup.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.kubernetes;
 
 import java.net.URL;
 import java.nio.file.Paths;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
@@ -57,50 +58,99 @@ public class KubernetesLookup extends AbstractLookup {
     private static Lock initLock = new ReentrantLock();
     private static boolean isSpringIncluded =
             LoaderUtil.isClassAvailable("org.apache.logging.log4j.spring.cloud.config.client.SpringEnvironmentHolder");
+    private Pod pod;
+    private Namespace namespace;
+    private URL masterUrl;
 
+    public KubernetesLookup() {
+        this.pod = null;
+        this.namespace = null;
+        this.masterUrl = null;
+        initialize();
+    }
+
+    KubernetesLookup(Pod pod, Namespace namespace, URL masterUrl) {
+        this.pod = pod;
+        this.namespace = namespace;
+        this.masterUrl = masterUrl;
+        initialize();
+    }
     private boolean initialize() {
         if (kubernetesInfo == null || (isSpringIncluded && !kubernetesInfo.isSpringActive)) {
             initLock.lock();
             boolean isSpringActive = isSpringActive();
             if (kubernetesInfo == null || (!kubernetesInfo.isSpringActive && isSpringActive)) {
                 try {
-                    KubernetesClient client = new KubernetesClientBuilder().createClient();
-                    if (client != null) {
-                        KubernetesInfo info = new KubernetesInfo();
-                        info.isSpringActive = isSpringActive;
-                        info.hostName = getHostname();
-                        Pod pod = getCurrentPod(info.hostName, client);
-                        if (pod != null) {
-                            info.app = pod.getMetadata().getLabels().get("app");
-                            final String app = info.app != null ? info.app : "";
-                            info.podTemplateHash = pod.getMetadata().getLabels().get("pod-template-hash");
-                            info.accountName = pod.getSpec().getServiceAccountName();
-                            info.clusterName = pod.getMetadata().getClusterName();
-                            info.hostIp = pod.getStatus().getHostIP();
-                            info.labels = pod.getMetadata().getLabels();
-                            info.podId = pod.getMetadata().getUid();
-                            info.podIp = pod.getStatus().getPodIP();
-                            info.podName = pod.getMetadata().getName();
-                            Container container = pod.getSpec().getContainers().stream()
-                                    .filter(c -> c.getName().equals(app)).findFirst().orElse(null);
-                            if (container != null) {
-                                info.containerName = container.getName();
-                                info.imageName = container.getImage();
-                            }
+                    KubernetesInfo info = new KubernetesInfo();
+                    KubernetesClient client = null;
+                    info.isSpringActive = isSpringActive;
+                    if (pod == null) {
+                        client = new KubernetesClientBuilder().createClient();
+                        if (client != null) {
+                            pod = getCurrentPod(System.getenv(HOSTNAME), client);
                             info.masterUrl = client.getMasterUrl();
-                            info.namespace = pod.getMetadata().getNamespace();
-                            Namespace namespace = client.namespaces().withName(info.namespace).get();
-                            if (namespace != null) {
-                                info.namespaceId = namespace.getMetadata().getUid();
+                            if (pod != null) {
+                                info.namespace = pod.getMetadata().getNamespace();
+                                namespace = namespace = client.namespaces().withName(info.namespace).get();
                             }
-                            ContainerStatus containerStatus = pod.getStatus().getContainerStatuses().stream()
-                                    .filter(cs -> cs.getName().equals(app)).findFirst().orElse(null);
-                            if (containerStatus != null) {
-                                info.containerId = containerStatus.getContainerID();
-                                info.imageId = containerStatus.getImageID();
+                        } else {
+                            LOGGER.warn("Kubernetes is not available for access");
+                        }
+                    } else {
+                        info.masterUrl = masterUrl;
+                    }
+                    if (pod != null) {
+                        if (namespace != null) {
+                            info.namespaceId = namespace.getMetadata().getUid();
+                            info.namespaceAnnotations = namespace.getMetadata().getAnnotations();
+                            info.namespaceLabels = namespace.getMetadata().getLabels();
+                        }
+                        info.app = pod.getMetadata().getLabels().get("app");
+                        info.hostName = pod.getSpec().getNodeName();
+                        info.annotations = pod.getMetadata().getAnnotations();
+                        final String app = info.app != null ? info.app : "";
+                        info.podTemplateHash = pod.getMetadata().getLabels().get("pod-template-hash");
+                        info.accountName = pod.getSpec().getServiceAccountName();
+                        info.clusterName = pod.getMetadata().getClusterName();
+                        info.hostIp = pod.getStatus().getHostIP();
+                        info.labels = pod.getMetadata().getLabels();
+                        info.podId = pod.getMetadata().getUid();
+                        info.podIp = pod.getStatus().getPodIP();
+                        info.podName = pod.getMetadata().getName();
+                        ContainerStatus containerStatus = null;
+                        List<ContainerStatus> statuses = pod.getStatus().getContainerStatuses();
+                        if (statuses.size() == 1) {
+                            containerStatus = statuses.get(0);
+                        } else if (statuses.size() > 1) {
+                            String containerId = ContainerUtil.getContainerId();
+                            if (containerId != null) {
+                                containerStatus = statuses.stream()
+                                        .filter(cs -> cs.getContainerID().contains(containerId))
+                                        .findFirst().orElse(null);
                             }
-                            kubernetesInfo = info;
                         }
+                        final String containerName;
+                        if (containerStatus != null) {
+                            info.containerId = containerStatus.getContainerID();
+                            info.imageId = containerStatus.getImageID();
+                            containerName = containerStatus.getName();
+                        } else {
+                            containerName = null;
+                        }
+                        Container container = null;
+                        List<Container> containers = pod.getSpec().getContainers();
+                        if (containers.size() == 1) {
+                            container = containers.get(0);
+                        } else if (containers.size() > 1 && containerName != null) {
+                            container = containers.stream().filter(c -> c.getName().equals(containerName))
+                                    .findFirst().orElse(null);
+                        }
+                        if (container != null) {
+                            info.containerName = container.getName();
+                            info.imageName = container.getImage();
+                        }
+
+                        kubernetesInfo = info;
                     }
                 } finally {
                     initLock.unlock();
@@ -112,13 +162,16 @@ public class KubernetesLookup extends AbstractLookup {
 
     @Override
     public String lookup(LogEvent event, String key) {
-        if (!initialize()) {
+        if (kubernetesInfo == null) {
             return null;
         }
         switch (key) {
             case "accountName": {
                 return kubernetesInfo.accountName;
             }
+            case "annotations": {
+                return kubernetesInfo.annotations.toString();
+            }
             case "containerId": {
                 return kubernetesInfo.containerId;
             }
@@ -146,9 +199,15 @@ public class KubernetesLookup extends AbstractLookup {
             case "masterUrl": {
                 return kubernetesInfo.masterUrl.toString();
             }
+            case "namespaceAnnotations": {
+                return kubernetesInfo.namespaceAnnotations.toString();
+            }
             case "namespaceId": {
                 return kubernetesInfo.namespaceId;
             }
+            case "namespaceLabels": {
+                return kubernetesInfo.namespaceLabels.toString();
+            }
             case "namespaceName": {
                 return kubernetesInfo.namespace;
             }
@@ -172,6 +231,13 @@ public class KubernetesLookup extends AbstractLookup {
         }
     }
 
+    /**
+     * For unit testing only.
+     */
+    void clearInfo() {
+        kubernetesInfo = null;
+    }
+
     private String getHostname() {
         return System.getenv(HOSTNAME);
     }
@@ -201,6 +267,7 @@ public class KubernetesLookup extends AbstractLookup {
     private static class KubernetesInfo {
         boolean isSpringActive;
         String accountName;
+        Map<String, String> annotations;
         String app;
         String clusterName;
         String containerId;
@@ -212,7 +279,9 @@ public class KubernetesLookup extends AbstractLookup {
         Map<String, String> labels;
         URL masterUrl;
         String namespace;
+        Map<String, String> namespaceAnnotations;
         String namespaceId;
+        Map<String, String> namespaceLabels;
         String podId;
         String podIp;
         String podName;
diff --git a/log4j-kubernetes/src/test/java/org/apache/logging/log4j/kubernetes/KubernetesLookupTest.java b/log4j-kubernetes/src/test/java/org/apache/logging/log4j/kubernetes/KubernetesLookupTest.java
new file mode 100644
index 0000000..7174596
--- /dev/null
+++ b/log4j-kubernetes/src/test/java/org/apache/logging/log4j/kubernetes/KubernetesLookupTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.logging.log4j.kubernetes;
+
+import java.io.File;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.fabric8.kubernetes.api.model.Namespace;
+import io.fabric8.kubernetes.api.model.ObjectMeta;
+import io.fabric8.kubernetes.api.model.Pod;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Validate the Kubernetes Lookup.
+ */
+public class KubernetesLookupTest {
+
+    private static final String localJson = "target/test-classes/localPod.json";
+    private static final String clusterJson = "target/test-classes/clusterPod.json";
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+    public static URL masterUrl;
+
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+        masterUrl = new URL("http://localhost:443/");
+    }
+
+    @Test
+    public void testLocal() throws Exception {
+        Pod pod = objectMapper.readValue(new File(localJson), Pod.class);
+        Namespace namespace = createNamespace();
+        KubernetesLookup lookup = new KubernetesLookup(pod, namespace, masterUrl);
+        try {
+            assertEquals("Incorrect container name", "sampleapp", lookup.lookup("containerName"));
+            assertEquals("Incorrect container id",
+                    "docker://818b0098946c67e6ac56cb7c0934b7c2a9f50feb7244b422b2a7f566f7e5d0df",
+                    lookup.lookup("containerId"));
+            assertEquals("Incorrect host name", "docker-desktop", lookup.lookup("host"));
+            assertEquals("Incorrect pod name", "sampleapp-584f99476d-mnrp4", lookup.lookup("podName"));
+        } finally {
+            lookup.clearInfo();;
+        }
+    }
+
+    @Test
+    public void testCluster() throws Exception {
+        Pod pod = objectMapper.readValue(new File(clusterJson), Pod.class);
+        Namespace namespace = createNamespace();
+        KubernetesLookup lookup = new KubernetesLookup(pod, namespace, masterUrl);
+        try {
+            assertEquals("Incorrect container name", "platform-forms-service", lookup.lookup("containerName"));
+            assertEquals("Incorrect container id",
+                    "docker://2b7c2a93dfb48334aa549e29fdd38039ddd256eec43ba64c145fa4b75a1542f0",
+                    lookup.lookup("containerId"));
+            assertEquals("Incorrect host name", "k8s-tmpcrm-worker-s03-04", lookup.lookup("host"));
+            assertEquals("Incorrect pod name", "platform-forms-service-primary-5ddfc4f9b8-kfpzv", lookup.lookup("podName"));
+        } finally {
+            lookup.clearInfo();
+        }
+    }
+
+    private Namespace createNamespace() {
+        Namespace namespace = new Namespace();
+        ObjectMeta meta = new ObjectMeta();
+        Map<String, String> annotations = new HashMap<>();
+        annotations.put("test", "name");
+        meta.setAnnotations(annotations);
+        Map<String, String> labels = new HashMap<>();
+        labels.put("ns", "my-namespace");
+        meta.setLabels(labels);
+        meta.setUid(UUID.randomUUID().toString());
+        namespace.setMetadata(meta);
+        return namespace;
+    }
+}
diff --git a/log4j-kubernetes/src/test/resources/clusterPod.json b/log4j-kubernetes/src/test/resources/clusterPod.json
new file mode 100644
index 0000000..7bae9c3
--- /dev/null
+++ b/log4j-kubernetes/src/test/resources/clusterPod.json
@@ -0,0 +1,177 @@
+{
+  "apiVersion": "v1",
+  "kind": "Pod",
+  "metadata": {
+    "annotations": {
+      "cni.projectcalico.org/podIP": "172.16.55.101/32",
+      "cni.projectcalico.org/podIPs": "172.16.55.101/32",
+      "flagger-id": "94d53b7b-cc06-41b3-bbac-a2d14a16d95d",
+      "prometheus.io/port": "9797",
+      "prometheus.io/scrape": "true"
+    },
+    "creationTimestamp": "2020-06-15T15:44:16Z",
+    "generateName": "platform-forms-service-primary-5ddfc4f9b8-",
+    "labels": {
+      "app": "platform-forms-service-primary",
+      "pod-template-hash": "5ddfc4f9b8"
+    },
+    "name": "platform-forms-service-primary-5ddfc4f9b8-kfpzv",
+    "namespace": "default",
+    "ownerReferences": [
+      {
+        "apiVersion": "apps/v1",
+        "kind": "ReplicaSet",
+        "blockOwnerDeletion": true,
+        "controller": true,
+        "name": "platform-forms-service-primary-5ddfc4f9b8",
+        "uid": "d2e89c56-7623-439e-a9ee-4a67e2f3a81a"
+      }],
+    "resourceVersion": "37382150",
+    "selfLink": "/api/v1/namespaces/default/pods/platform-forms-service-primary-5ddfc4f9b8-kfpzv",
+    "uid": "df8cbac1-129c-4cd3-b5bc-65d72d8ba5f0"
+  },
+  "spec": {
+    "containers": [
+      {
+        "env": [
+          {
+            "name": "APACHE_ENV",
+            "value": "tmpcrm"
+          },
+          {
+            "name": "SPRING_PROFILES_ACTIVE",
+            "value": "tmpcrm"
+          },
+          {
+            "name": "JAVA_OPTS",
+            "value": "-Dlogging.label=crm"
+          }],
+        "image": "docker.apache.xyz/platform-forms-service:0.15.0",
+        "imagePullPolicy": "Always",
+        "livenessProbe": {
+          "failureThreshold": 3,
+          "httpGet": {
+            "path": "/info",
+            "port": "http",
+            "scheme": "HTTP"
+          },
+          "periodSeconds": 10,
+          "successThreshold": 1,
+          "timeoutSeconds": 1
+        },
+        "name": "platform-forms-service",
+        "ports": [
+          {
+            "containerPort": 8080,
+            "name": "http",
+            "protocol": "TCP"
+          }],
+        "readinessProbe": {
+          "failureThreshold": 3,
+          "httpGet": {
+            "path": "/health",
+            "port": "http",
+            "scheme": "HTTP"
+          },
+          "periodSeconds": 10,
+          "successThreshold": 1,
+          "timeoutSeconds": 1
+        },
+        "resources": {
+        },
+        "securityContext": {
+        },
+        "terminationMessagePath": "/dev/termination-log",
+        "terminationMessagePolicy": "File",
+        "volumeMounts": [
+          {
+            "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+            "name": "default-token-2nqlw",
+            "readOnly": true
+          }]
+      }],
+    "dnsPolicy": "ClusterFirst",
+    "enableServiceLinks": true,
+    "nodeName": "k8s-tmpcrm-worker-s03-04",
+    "priority": 0,
+    "restartPolicy": "Always",
+    "schedulerName": "default-scheduler",
+    "securityContext": {
+    },
+    "serviceAccount": "default",
+    "serviceAccountName": "default",
+    "terminationGracePeriodSeconds": 30,
+    "tolerations": [
+      {
+        "effect": "NoExecute",
+        "key": "node.kubernetes.io/not-ready",
+        "operator": "Exists",
+        "tolerationSeconds": 300
+      },
+      {
+        "effect": "NoExecute",
+        "key": "node.kubernetes.io/unreachable",
+        "operator": "Exists",
+        "tolerationSeconds": 300
+      }],
+    "volumes": [
+      {
+        "name": "default-token-2nqlw",
+        "secret": {
+          "defaultMode": 420,
+          "secretName": "default-token-2nqlw"
+        }
+      }]
+  },
+  "status": {
+    "conditions": [
+      {
+        "lastTransitionTime": "2020-06-15T15:44:16Z",
+        "status": "True",
+        "type": "Initialized"
+      },
+      {
+        "lastTransitionTime": "2020-06-15T15:44:46Z",
+        "status": "True",
+        "type": "Ready"
+      },
+      {
+        "lastTransitionTime": "2020-06-15T15:44:46Z",
+        "status": "True",
+        "type": "ContainersReady"
+      },
+      {
+        "lastTransitionTime": "2020-06-15T15:44:16Z",
+        "status": "True",
+        "type": "PodScheduled"
+      }],
+    "containerStatuses": [
+      {
+        "containerID": "docker://2b7c2a93dfb48334aa549e29fdd38039ddd256eec43ba64c145fa4b75a1542f0",
+        "image": "docker.apache.xyz/platform-forms-service:0.15.0",
+        "imageID":
+        "docker-pullable://docker.apache.xyz/platform-forms-service@sha256:45fd19ccd99e218a7685c4cee5bc5b16aeae1cdb8e8773f9c066d4cfb22ee195",
+        "lastState": {
+        },
+        "name": "platform-forms-service",
+        "ready": true,
+        "restartCount": 0,
+        "state": {
+          "running": {
+            "startedAt": "2020-06-15T15:44:21Z"
+          }
+        },
+        "started": true
+      }],
+    "hostIP": "10.103.220.170",
+    "phase": "Running",
+    "podIP": "172.16.55.101",
+    "qosClass": "BestEffort",
+    "startTime": "2020-06-15T15:44:16Z",
+    "podIPs": [
+      {
+        "ip": "172.16.55.101"
+      }]
+  }
+}
+
diff --git a/log4j-kubernetes/src/test/resources/localPod.json b/log4j-kubernetes/src/test/resources/localPod.json
new file mode 100644
index 0000000..3aeef46
--- /dev/null
+++ b/log4j-kubernetes/src/test/resources/localPod.json
@@ -0,0 +1,141 @@
+{
+  "apiVersion": "v1",
+  "kind": "Pod",
+  "metadata": {
+    "creationTimestamp": "2020-06-14T21:50:09Z",
+    "generateName": "sampleapp-584f99476d-",
+    "labels": {
+      "app": "sampleapp",
+      "pod-template-hash": "584f99476d"
+    },
+    "name": "sampleapp-584f99476d-mnrp4",
+    "namespace": "default",
+    "ownerReferences": [
+      {
+        "apiVersion": "apps/v1",
+        "kind": "ReplicaSet",
+        "blockOwnerDeletion": true,
+        "controller": true,
+        "name": "sampleapp-584f99476d",
+        "uid": "d68146d1-17c4-486e-aa8d-07d7d5d38b94"
+      }],
+    "resourceVersion": "1200430",
+    "selfLink": "/api/v1/namespaces/default/pods/sampleapp-584f99476d-mnrp4",
+    "uid": "9213879a-479c-42ce-856b-7e2666d21829"
+  },
+  "spec": {
+    "containers": [
+      {
+        "env": [
+          {
+            "name": "JAVA_OPTS",
+            "value": "-Delastic.search.host=host.docker.internal"
+          }],
+        "image": "localhost:5000/sampleapp:latest",
+        "imagePullPolicy": "Always",
+        "name": "sampleapp",
+        "ports": [
+          {
+            "containerPort": 8080,
+            "protocol": "TCP"
+          },
+          {
+            "containerPort": 5005,
+            "protocol": "TCP"
+          }],
+        "resources": {
+        },
+        "terminationMessagePath": "/dev/termination-log",
+        "terminationMessagePolicy": "File",
+        "volumeMounts": [
+          {
+            "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+            "name": "default-token-jzq7d",
+            "readOnly": true
+          }]
+      }],
+    "dnsPolicy": "ClusterFirst",
+    "nodeName": "docker-desktop",
+    "priority": 0,
+    "restartPolicy": "Always",
+    "schedulerName": "default-scheduler",
+    "securityContext": {
+    },
+    "serviceAccount": "default",
+    "serviceAccountName": "default",
+    "terminationGracePeriodSeconds": 30,
+    "tolerations": [
+      {
+        "effect": "NoExecute",
+        "key": "node.kubernetes.io/not-ready",
+        "operator": "Exists",
+        "tolerationSeconds": 300
+      },
+      {
+        "effect": "NoExecute",
+        "key": "node.kubernetes.io/unreachable",
+        "operator": "Exists",
+        "tolerationSeconds": 300
+      }],
+    "volumes": [
+      {
+        "name": "default-token-jzq7d",
+        "secret": {
+          "defaultMode": 420,
+          "secretName": "default-token-jzq7d"
+        }
+      }],
+    "enableServiceLinks": true
+  },
+  "status": {
+    "conditions": [
+      {
+        "lastTransitionTime": "2020-06-14T21:50:09Z",
+        "status": "True",
+        "type": "Initialized"
+      },
+      {
+        "lastTransitionTime": "2020-06-14T21:50:10Z",
+        "status": "True",
+        "type": "Ready"
+      },
+      {
+        "lastTransitionTime": "2020-06-14T21:50:10Z",
+        "status": "True",
+        "type": "ContainersReady"
+      },
+      {
+        "lastTransitionTime": "2020-06-14T21:50:09Z",
+        "status": "True",
+        "type": "PodScheduled"
+      }],
+    "containerStatuses": [
+      {
+        "containerID": "docker://818b0098946c67e6ac56cb7c0934b7c2a9f50feb7244b422b2a7f566f7e5d0df",
+        "image": "sampleapp:latest",
+        "imageID":
+        "docker-pullable://localhost:5000/sampleapp@sha256:3cefb2db514db73c69854fee8abd072f27240519432d08aad177a57ee34b7d39",
+        "lastState": {
+        },
+        "name": "sampleapp",
+        "ready": true,
+        "restartCount": 0,
+        "state": {
+          "running": {
+            "startedAt": "2020-06-14T21:50:10Z"
+          }
+        },
+        "started": true
+      }],
+    "hostIP": "192.168.65.3",
+    "phase": "Running",
+    "podIP": "10.1.0.47",
+    "qosClass": "BestEffort",
+    "startTime": "2020-06-14T21:50:09Z",
+    "podIPs": [
+      {
+        "ip": "10.1.0.47"
+      }]
+  }
+}
+
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/app-compose.yml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/app-compose.yml
index 01cb293..9864e8e 100755
--- a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/app-compose.yml
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/app-compose.yml
@@ -8,7 +8,7 @@ services:
       SERVICE_PARAMS: --spring.config.location=classpath:/,classpath:/application-local-docker.yml
     ports:
       - "5005:5005"
-      - "8080:4567"
+      - "8080:8080"
     networks:
       sample_network:
         aliases:
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/combined-compose.yml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/combined-compose.yml
new file mode 100755
index 0000000..b2f5abb
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/combined-compose.yml
@@ -0,0 +1,86 @@
+version: "3"
+services:
+  socat:
+    container_name: socat
+    image: bobrik/socat
+    command: TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock
+    expose:
+      - "1234"
+    volumes:
+      - /var/run/docker.sock:/var/run/docker.sock
+    networks:
+      sample_network:
+        aliases:
+          - socat
+
+  rabbitmq:
+    container_name: rabbit
+    image: rabbitmq:3-management-alpine
+    expose:
+      - "5672"
+      - "15672"
+    ports:
+      - "5672:5672"
+      - "15672:15672"
+    volumes:
+      - ./init/rabbit/rabbitmq.config:/etc/rabbitmq/rabbitmq.config:ro
+      - ./init/rabbit/definitions.json:/etc/rabbitmq/definitions.json:ro
+    networks:
+      sample_network:
+        aliases:
+          - rabbitmq
+
+  fluent-bit:
+    container_name: fluent-bit
+    image: fluent/fluent-bit:latest
+    expose:
+      - "2020"
+      - "24221"
+      - "24224"
+    ports:
+      - "24224:24224"
+    volumes:
+      - ./init/fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
+      - ./target/logs:/var/logs
+    networks:
+      sample_network:
+        aliases:
+          - fluent-bit
+
+  flume:
+    container_name: flume
+    image: probablyfine/flume:latest
+    expose:
+      - "5050"
+    environment:
+      FLUME_AGENT_NAME: forwarder
+      FLUME_JAVA_OPTS: -Dlog4j.configuration=file:///opt/flume-config/log4j.properties
+    volumes:
+      - ./init/flume/start-flume.sh:/opt/flume/bin/start-flume
+      - ./init/flume/flume.conf:/opt/flume-config/flume.conf
+      - ./init/flume/flume-env.sh:/opt/flume-config/flume-env.sh
+      - ./init/flume/log4j.properties:/opt/flume-config/log4j.properties
+      - ~/flume-logs:/var/log/flume
+    networks:
+      sample_network:
+        aliases:
+          - flume
+
+  sampleapp:
+    container_name: sampleapp
+    image: sampleapp
+    environment:
+      DOCKER_URI: http://socat:1234
+      SERVICE_PARAMS: --spring.config.location=classpath:/,classpath:/application-local-docker.yml
+    ports:
+      - "5005:5005"
+      - "8080:8080"
+    networks:
+      sample_network:
+        aliases:
+          - sampleapp
+networks:
+  sample_network:
+
+volumes:
+  pgdata:
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/restartApp.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/restartApp.sh
index 397d5a3..6bb6fac 100755
--- a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/restartApp.sh
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/restartApp.sh
@@ -23,7 +23,7 @@ containerName=app-container
 networkName=docker_sample_network
 debug_port=5005
 #debug_expose="-p $debug_port:$debug_port"
-exposed_ports="-p 8080:4567 $debug_expose"
+exposed_ports="-p 8080:8080 $debug_expose"
 
 mvn clean package -DskipTests=true
 
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/sampleapp-deployment.yaml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/sampleapp-deployment.yaml
index fe5d2b7..ffd2851 100644
--- a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/sampleapp-deployment.yaml
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/sampleapp-deployment.yaml
@@ -15,30 +15,39 @@ spec:
         app: sampleapp
     spec:
       containers:
-        - name: sampleapp
-          image: localhost:5000/sampleapp:latest
-          imagePullPolicy: Always
-          ports:
-            - containerPort: 8080
-            - containerPort: 5005
-          env:
-            - name: JAVA_OPTS
-              value: "-Delastic.search.host=host.docker.internal"
+      - name: sampleapp
+        image: localhost:5000/sampleapp:latest
+        imagePullPolicy: Always
+        ports:
+          - containerPort: 8080
+          - containerPort: 5005
+        env:
+          - name: JAVA_OPTS
+            value: "-Delastic.search.host=host.docker.internal"
+      - name: key-value-store
+        image: redis
+        ports:
+        - containerPort: 6379
+
+
 ---
 apiVersion: v1
 kind: Service
 metadata:
   name: sampleapp
 spec:
-  type: NodePort
   selector:
     app: sampleapp
   ports:
     - protocol: TCP
       port: 8080
-      nodePort: 30011
+      targetPort: 8080
       name: http
     - protocol: TCP
+      port: 6379
+      targetPort: 6379
+      name: redis
+    - protocol: TCP
       port: 5005
-      nodePort: 30012
+      targetPort: 5005
       name: debug
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/controller/K8SController.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/controller/K8SController.java
new file mode 100644
index 0000000..5059a49
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/controller/K8SController.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020 Nextiva, Inc. to Present.
+ * All rights reserved.
+ */
+
+package org.apache.logging.log4j.spring.cloud.config.sample.controller;
+
+import java.nio.file.Paths;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.kubernetes.KubernetesClientBuilder;
+import org.apache.logging.log4j.util.Strings;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+/**
+ * Test class
+ */
+@RestController
+public class K8SController {
+
+    private static final Logger LOGGER = LogManager.getLogger(K8SController.class);
+    private static final String HOSTNAME = "HOSTNAME";
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @GetMapping("/k8s/pod")
+    public ResponseEntity<Pod> getPod() {
+        try {
+            KubernetesClient client = new KubernetesClientBuilder().createClient();
+            if (client != null) {
+                Pod pod = getCurrentPod(client);
+                if (pod != null) {
+                    LOGGER.info("Pod: {}", objectMapper.writeValueAsString(pod));
+                    return new ResponseEntity<>(pod, HttpStatus.OK);
+                }
+            }
+        } catch (Exception ex) {
+            LOGGER.error("Unable to obtain or print Pod information", ex);
+        }
+        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    private Pod getCurrentPod(KubernetesClient kubernetesClient) {
+        String hostName = System.getenv(HOSTNAME);
+        try {
+            if (isServiceAccount() && Strings.isNotBlank(hostName)) {
+                return kubernetesClient.pods().withName(hostName).get();
+            }
+        } catch (Throwable t) {
+            LOGGER.debug("Unable to locate pod with name {}.", hostName);
+        }
+        return null;
+    }
+
+    private boolean isServiceAccount() {
+        return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists()
+                && Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH).toFile().exists();
+    }
+
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml
index 7e9838e..4c6bb12 100644
--- a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml
@@ -1,5 +1,5 @@
 server:
-  port: 4567
+  port: 8080
   servlet:
     context-path: /sample