4

I have a spring boot web app which simply prints a property that is passed in a Kubernetes' ConfigMap.

This is my main class:

@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class DemoApplication {

    private MyConfig config;
    private DiscoveryClient discoveryClient;

    @Autowired
    public DemoApplication(MyConfig config, DiscoveryClient discoveryClient) {
        this.config = config;
        this.discoveryClient = discoveryClient;
    }

    @RequestMapping("/")
    public String info() {
        return config.getMessage();
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @RequestMapping("/services")
    public String services() {
        StringBuilder b = new StringBuilder();
        discoveryClient.getServices().forEach((s) -> b.append(s).append(" , "));
        return b.toString();
    }

}

and the MyConfig class is:

@Configuration
@ConfigurationProperties(prefix = "bean")
public class MyConfig {

    private String message = "a message that can be changed live";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

Basically, by invoking root resource I always get:

a message that can be changed live

And invoking /services I actually get a list of Kubernetes services.

I'm creating the ConfigMap with kubectl create -f configmap-demo.yml being the content:

apiVersion: v1
kind: ConfigMap
metadata:
  name: demo
data:
    bean.message: This is an info from k8

And the deployment with kubecetl create -f deploy-demo.yml and the content is:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
  labels:
    app: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      # this service account was created according to
      # https://kubernetes.io/docs/reference/access-authn-authz/rbac/#service-account-permissions
      # point 5 - Grant super-user access to all service accounts cluster-wide (strongly discouraged)
      serviceAccountName: i18n-spring-k8
      containers:
      - name: demo
        image: aribeiro/sck-demo
        imagePullPolicy: Never
        env:
        - name: JAVA_OPTS
          value:
        ports:
        - containerPort: 8080
      volumes:
      - name: demo
        configMap:
          name: demo

The problem is that when accessing the root resource / I always get the default hardcoded value and never what is defined in Kubernetes' ConfigMap.

Example project also with yaml files and Docker file available at https://drive.google.com/open?id=107IcwnYIbVpmwVgdgi8Dhx4nHEFAVxV8 .

Also checked the startup DEBUG logs and I don't see any error or clue why it should not work.

Miguel Ribeiro
  • 8,057
  • 20
  • 51
  • 74

2 Answers2

7

The Spring Cloud Kubernetes documentation is incomplete. It lacks the instruction to include this dependency to enable loading application properties from ConfigMap's:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-kubernetes-config</artifactId>
</dependency>
Chin Huang
  • 12,912
  • 4
  • 46
  • 47
  • Thanks for this but in the end I wouldn’t be using Spring Cloud Kubernetes - this works without it. Plus, I want to take advantage of the config reload if the ConfigMap is changed. – Miguel Ribeiro Dec 29 '18 at 08:47
  • 1
    I understand now you want to use Spring Cloud Kubernetes to read the ConfigMap. I updated my answer. – Chin Huang Jan 01 '19 at 18:12
  • This was the problem. Documentation indeed does not refer this. Now I have problem reloading the config but I'll open a different question for that. It's a different topic I believe. – Miguel Ribeiro Jan 02 '19 at 13:29
  • Is volumes in the deployment required? – Pavan Kumar Apr 07 '20 at 17:30
  • In newer versions of Spring Boot artifactId of starter was a little bit changed. Now it is spring-cloud-starter-kubernetes-fabric8-config or spring-cloud-starter-kubernetes-client-config depending on Kubernates Java Client you use. – piphonom Dec 26 '22 at 11:22
  • How about secrets? I am having issues with reading properties from secrets – xbmono Apr 06 '23 at 01:38
1

You are close:

1) Define the ConfigMap a bit differently so it contains a properties file. For example:

apiVersion: v1
kind: ConfigMap
metadata:
  name: demo
data:
  demo.properties: |
    bean.message: This is an info from k8

2) Mount the ConfigMap as a volume:

...
spec:
  containers:
  - name: demo
    ...
    volumeMounts:
    - name: config
      mountPath: /demo/config
  volumes:
  - name: config
    configMap:
      name: demo

As result, a demo.properties file as defined in the ConfigMap will "appear" in the /demo/config directory inside the running container.

3) Add @PropertySource annotation to the MyConfig class:

@Configuration
@PropertySource("file:/demo/config/demo.properties")
@ConfigurationProperties(prefix = "bean")
public class MyConfig {
  ...
}
apisim
  • 4,036
  • 1
  • 10
  • 16
  • 2
    Thanks @apisim! But I wouldn’t need Spring Cloud Kubernetes for this approach. Plus, afaik, passing on the configuration file path doesn’t suffice to reload the configs once they change. – Miguel Ribeiro Dec 30 '18 at 20:04