0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kubernetes Secretのデータ変更時にSpring Bootアプリケーションへ変更を反映する

Posted at

はじめに

Kubernetes環境でSpring Bootアプリケーションを運用する際、データベースのパスワードやAPIキーなどの機密情報をKubernetes Secretで管理することが一般的です。しかし、Secretのデータを変更した際に、Spring Bootアプリケーションに変更を反映させるためには、通常Podの再起動が必要になります。

本記事では、Spring Cloud Kubernetes Configuration Watcherを使用して、Kubernetes Secretの変更を自動的にSpring Bootアプリケーションに反映する方法を自分用にまとめてみました。

問題の背景

Kubernetesは、アプリケーションのコンテナ内にSecretをボリュームとしてマウントすることができます。この機能を使えば、Secretの内容が変更されると、マウントされたボリュームも合わせて更新されます。しかし、Spring Bootはアプリケーションを再起動・再デプロイしない限り、構成・コンテキストを自動的に更新しません。つまり、Spring Bootアプリケーションには、Kubernetes Secretのデータ変更が自動的に反映されません。

Spring Cloud Kubernetes Configuration Watcherとは

Spring Cloud Kubernetes Configuration Watcherは、Kubernetes APIを通じてConfigMapやSecretの変更を監視し、変更を検知すると該当するSpring Bootアプリケーションに対してリフレッシュイベントを送信します。これにより、アプリケーションは新しい設定値を自動的に読み込みます。

環境・条件

以下は、実際に自分で実装・検証を行った環境です。

  • Java: 21
  • Spring Boot: 3.3.2
  • spring-cloud-kubernetes-configuration-watcher:3.1.6
  • Kubernetes: 1.20以上

実装方法

1. Kubernetes側の設定

Spring Cloud Kubernetes Configuration Watcher controller

Spring Bootアプリケーションが存在するCluster・NamespaceにSpring Cloud Kubernetes Configuration Watcher controllerをデプロイします。

サンプルコード

Deployment YAML - Spring Cloud Kubernetes Configuration Watcherを参考にしました。

apiVersion: v1
kind: List
items:
  - apiVersion: v1
    kind: Service
    metadata:
      name: configuration-watcher-service
      namespace: sample-namespace
      labels:
        app: configuration-watcher
    spec:
      ports:
        - name: http
          port: 8888
          targetPort: 8888
      selector:
        app: configuration-watcher
      type: ClusterIP
  - apiVersion: v1
    kind: ServiceAccount
    metadata:
      labels:
        app: configuration-watcher
      name: configuration-watcher-service-account
  - apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      labels:
        app: configuration-watcher
      name: configuration-watcher-role-binding
    roleRef:
      kind: Role
      apiGroup: rbac.authorization.k8s.io
      name: namespace-reader
    subjects:
      - kind: ServiceAccount
        name: configuration-watcher-service-account
  - apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      namespace: default
      name: namespace-reader
    rules:
      - apiGroups: [""]
        resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
        verbs: ["get", "list", "watch"]
  - apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: configuration-watcher-deployment
      namespace: sample-namespace
    spec:
      selector:
        matchLabels:
          app: configuration-watcher
      template:
        metadata:
          labels:
            app: configuration-watcher
        spec:
          serviceAccountName: configuration-watcher-service-account
          containers:
            - name: configuration-watcher
              image: springcloud/spring-cloud-kubernetes-configuration-watcher:3.1.6
              imagePullPolicy: IfNotPresent
              readinessProbe:
                httpGet:
                  port: 8888
                  path: /actuator/health/readiness
              livenessProbe:
                httpGet:
                  port: 8888
                  path: /actuator/health/liveness
              ports:
                - containerPort: 8888
              env:
                - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CONFIGURATION_WATCHER
                  value: DEBUG
                - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_CONFIG_RELOAD
                  value: DEBUG
                - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_CONFIG_RELOAD
                  value: DEBUG

Secretファイル

変更を検知したいKubernetes Secretのmetadataに、以下2つを追加します。

  • spring.cloud.kubernetes.secret.apps: このアノテーションにより、Secretデータに変更が発生したときに通知を受け取るアプリケーションの名前を指定します
  • spring.cloud.kubernetes.secret: このラベルを持つSecretが変更された場合にのみ、Configuration Watcherからイベントが発行されます
apiVersion: v1
kind: Secret
data:
  # ...
  API_KEY: <base64_encoded_api_key>
metadata:
  annotations:
    spring.cloud.kubernetes.secret.apps: <app_name>
  labels:
    spring.cloud.kubernetes.secret: "true"

2. Spring Bootアプリケーション側の設定

pom.xml

必要な依存関係を追加します(Gradleでも同様の設定ができます)。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
    <version>3.1.6</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
    <version>4.1.6</version>
</dependency>

application.properties

アクチュエータエンドポイント/refreshを公開します(application.ymlでも同様の設定ができます)。

api.key=${API_KEY}
...

+ management.endpoints.web.exposure.include=refresh
+ management.health.defaults.enabled=false

bootstrap.properties

以下のプロパティを設定したbootstrap.propertiesを作成します(bootstrap.ymlでも同様の設定ができます)。

  • spring.cloud.kubernetes.secrets.enabled=true: Spring Cloud KubernetesのSecret機能を有効化
  • spring.cloud.kubernetes.secrets.enable-api=true: Kubernetes API経由でSecretの読み込みを可能にする
  • spring.cloud.kubernetes.secrets.name=<secret_name>: 対象のSecret名を指定
  • spring.cloud.kubernetes.secrets.useNameAsPrefix=false: プロパティ名のプレフィックスを無効化(デフォルトはtrueで、trueの場合、Secretから読み込んだプロパティにSecret名がプレフィックスとして付加される)
  • spring.cloud.kubernetes.secrets.includeProfileSpecificSources=false: 不要な検索を減らすための最適化(設定しなくても良い)
  • spring.cloud.kubernetes.config.enabled=false: ConfigMapの監視は不要のため、余計な処理をしないようにする(設定しなくても良い)
spring.application.name=@project.name@

# Spring Cloud Kubernetes
spring.cloud.kubernetes.secrets.enabled=true
spring.cloud.kubernetes.secrets.enable-api=true
spring.cloud.kubernetes.secrets.name=<secret_name>
spring.cloud.kubernetes.secrets.useNameAsPrefix=false
spring.cloud.kubernetes.secrets.includeProfileSpecificSources=false
spring.cloud.kubernetes.config.enabled=false

Secretデータを使用するBean

対象のSecretデータを使用するBeanに@RefreshScopeアノテーションを追加することで、Secretが変更された際に、そのBeanの設定値を更新します。

@Component
@RefreshScope
public class ApiService {
    
    @Value("${api.key}")
    private String apiKey;
    
    public void callApi() {
        // apiKeyを使用した処理
        System.out.println("Using API Key: " + apiKey);
    }
}

@Value以外にも、Environment@ConfigurationPropertiesを使用してプロパティを読み込む場合でも同様に機能します。

トラブルシューティング

アクチュエータエンドポイント/refreshが302を返す

事象

configuration-watcherのPodで、下記のようなログが出力されます。
Refresh sent to <app_name> at URI address http://.../actuator/refresh returned a 302 FOUND

原因

configuration-watcherからの/actuator/refreshへのPOSTリクエストがCSRFトークンなしで送信されるため、Spring Securityがデフォルトで有効にしているCSRF保護により、302 FOUNDでリダイレクトされます。

解決策

Web Securityの設定を行うクラスのsecurityFilterChainメソッドで、csrf.ignoringRequestMatchers("/actuator/refresh")を設定します。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
...

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  // ...

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
      // ...
      .csrf(csrf -> csrf.ignoringRequestMatchers("/actuator/refresh"));

    return http.build();
  }

  // ...

}

アクチュエータエンドポイント/refreshが404を返す

事象

configuration-watcherのPodで、下記のようなログが出力されます。org.springframework.web.reactive.function.client.WebClientResponseException$NotFound: 404 Not Found from POST http://<app_name>/actuator/refresh

原因

application.propertiesにおいて、server.servlet.context-path=<context_path>が設定されているため、デフォルトのエンドポイント /actuator/refresh ではなく、/<context_path>/actuator/refresh に送信する必要があります。

解決策

Configuration Watcher controllerのコンテナに環境変数SPRING_CLOUD_KUBERNETES_CONFIGURATION_WATCHER_ACTUATOR_PATHを設定します。

env:
  - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CONFIGURATION_WATCHER
    value: DEBUG
  - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_CONFIG_RELOAD
    value: DEBUG
  - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_CONFIG_RELOAD
    value: DEBUG
+  - name: SPRING_CLOUD_KUBERNETES_CONFIGURATION_WATCHER_ACTUATOR_PATH
+    value: "/<context_path>/actuator"

その他

上記以外にも、権限やネットワークポリシーなどで問題が発生する可能性もあるため、注意が必要です。

参考・引用資料

あとがき

最後までご覧いただきありがとうございました。
少しでも良い・役に立ったと感じていただけましたら、いいねしていただけますと幸いです。
もし間違ってるところがあれば、遠慮なくコメントでご教示ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?