Abstruct

KubernetesのPod上で動作するSpringBootアプリケーションからConfigMapを変更することができたので、その方法をまとめておきます。

Environment

  • minikube
    • version
    1
    2
    
    minikube version: v1.16.0
    commit: 9f1e482427589ff8451c4723b6ba53bb9742fbb1
    
  • docker
    • version
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    Client: Docker Engine - Community
    Cloud integration: 1.0.4
    Version:           20.10.0
    API version:       1.41
    Go version:        go1.13.15
    Git commit:        7287ab3
    Built:             Tue Dec  8 18:55:43 2020
    OS/Arch:           darwin/amd64
    Context:           default
    Experimental:      true
    
    Server: Docker Engine - Community
    Engine:
    Version:          20.10.0
    API version:      1.41 (minimum version 1.12)
    Go version:       go1.13.15
    Git commit:       eeddea2
    Built:            Tue Dec  8 18:58:04 2020
    OS/Arch:          linux/amd64
    Experimental:     false
    containerd:
    Version:          v1.4.3
    GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
    runc:
    Version:          1.0.0-rc92
    GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
    docker-init:
    Version:          0.19.0
    GitCommit:        de40ad0
    

環境構築は以下のコマンド。

1
2
minikube start
eval $(minikube docker-env)

eval $(minikube docker-env) の必要性については、こちらのブログがわかりやすかったです。

What to create

  • ServiceAccount
    • Pod上からKubernetesのリソースにアクセスするためのアカウント。
  • RoleBinding
  • DockerImage
    • SpringBootApplicationを配置/実行するイメージ
  • Pod
    • 作成したDockerImageを読み込むPod
  • SpringBootApplication
    • ConfigMapにアクセスするプログラム。

ServiceAccount

service-account.yaml

1
2
3
4
5
apiVersion: v1
kind: ServiceAccount
metadata:
  name: configmap-access-sa
automountServiceAccountToken: true

ServiceAccountの作成方法は以下。

1
kubectl apply -f service-account.yaml

RoleBinding

role-binding.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: configmap-access-sa-view
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: edit
subjects:
- kind: ServiceAccount
  name: configmap-access-sa
  namespace: default

Roleのデフォルトで用意されているもの(cluster-admineditなど)については、Using RBAC Authorization | Kubernetes を参照するとわかりやすいです。

RoleBindingの作成方法は以下。

1
kubectl apply -f ./role-binding.yaml

DockerImage

1
2
3
4
FROM openjdk:11-jre
ARG JAR_FILE=./build/libs/app.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", "./app.jar"]S

DockerImageの作成方法は以下。

1
docker build . -t game.example/demo-game

Pod

configmap-demo-pod.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: v1
kind: Pod
metadata:
  name: configmap-demo-pod
spec:
  serviceAccountName: configmap-access-sa
  automountServiceAccountToken: true
  containers:
    - name: demo
      image: game.example/demo-game:latest
      imagePullPolicy: IfNotPresent

Podの作成方法は以下。

1
kubectl apply -f ./configmap-demo-pod.yaml

ServiceAccount作成時にできているSecretがマウントされていることを確認すること。

この設定がないと、io.kubernetes.client.ApiException: Forbidden at K8S pod · Issue #542 · kubernetes-client/java のエラーとなり、kubernetesのリソースにアクセスできない。

1
2
3
4
5
6
7
8
9
takuto-n@MacBook-Air pod-read-configmap % kubectl get pods/configmap-demo-pod -o yaml
# 省略
  serviceAccount: configmap-access-sa
  serviceAccountName: configmap-access-sa
# 省略
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: configmap-access-sa-token-2vxlm
      readOnly: true

SpringBootApplication

build.gradle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
plugins {
    id 'org.springframework.boot' version '2.4.1'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'

    implementation 'io.fabric8:kubernetes-client:4.13.0'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

ConfigMapWriter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.example.properties.k8s;

import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class ConfigMapWriter {

    public boolean update() {
        Config config = new ConfigBuilder().build();
        KubernetesClient client = new DefaultKubernetesClient(config);

        String namespace = client.getNamespace();
        if (namespace == null) {
            namespace = "default";
        }

        String name = "config-test";
        var configMapResource = client.configMaps().inNamespace(namespace).withName(name);

        var configMap = configMapResource.createOrReplace(new ConfigMapBuilder().
                withNewMetadata().withNamespace(namespace).withName(name).endMetadata().
                addToData("foo", "xxx").
                addToData("bar", "beer").
                build());
        
        log.info("Upserted ConfigMap at {} data {}", configMap.getMetadata().getSelfLink(), configMap.getData());

        return true;
    }
}

Reference