雲原生架構之Spring Cloud+Kubernetes配置中心方案

語言: CN / TW / HK

“我報名參加金石計劃1期挑戰——瓜分10萬獎池,這是我的第7篇文章,點選檢視活動詳情

一 方案概述

利用Kubernetes原生configmap/secret 資源作為配置中心,不用關注服務可用性,即網路問題,同時也不用引入新的組建,造成運維成本。

二 SpringCloudKubernetes 配置中心方案

2.1 方案介紹

2.1.1 spring-cloud-starter-kubernetes-config

spring-cloud-starter-kubernetes-config是spring-cloud-starter-kubernetes下的一個庫,作用是將kubernetes的configmap與SpringCloud Config結合起來。 spring-boot-actuator/ spring-boot-actuator-autoconfigure 兩個包的引入,使得應用可以進行熱更新,當configmap/secret發生變更的時候,可以不重啟Pod或程序進行熱變更配置。

2.1.2 功能簡介

SpringCloud Kubernetes Config 元件主要提供以下幾種功能:

  • 實時監控 ConfigMap、Secret 配置變化從而更新服務配置。
  • 定時輪詢載入 ConfigMap、Secret 配置從而更新服務配置。

2.2 配置發現過程

本質上配置管理利用了etcd的儲存,首先是應用呼叫 spring-cloud-kubernetes-config 包,其呼叫了底層java實現的kubernetes-client,通過呼叫k8s api來實現從etcd中獲取configmap和secret資源資訊。

2.3 方案特點

  • 優點:
  • 使用K8s內建資源,無需考慮引入配置中心服務,不用考慮配置中心服務高可用。
  • 雲原生架構,配置中心下沉至基礎設施層,無需業務程式碼耦合,減少成本。
  • 不足:
  • dev/test 環境也需要上K8s,確保環境一致性。
  • 需要將配置資源configmap/secret引入到GitOps流程中。

三 實戰

新建 bootstrap.yml 配置檔案,最終的application.yaml 使用k8s 的configmap/secret資源。

```yaml spring: profiles: ## 如果 PROFILE 為空,則使用dev active: ${PROFILE:dev} application: name: springboot-config cloud: # 監控k8s configmap/secret api獲取配置 kubernetes: reload: #自動更新配置的開關設定為開啟 enabled: true #更新配置資訊的模式:polling是主動拉取,event是事件通知 mode: polling #主動拉取的間隔時間是500毫秒 period: 500 #是否監控 Secret 的更改來執行更新 monitoring-secrets: true

  config:
    namespace: springconfig
    name: ${spring.application.name}-cm
  # 模式僅啟動configmap獲取,啟用secret使用enable-api
  secrets:
    enable-api: true
    namespace: springconfig
    name: ${spring.application.name}-secret
    #labels:                  #指定 Label 標籤名詞,根據這個標籤篩選 Secret,讀取其中配置
    #  secret: enabled        #自定義的 Label

management: endpoint: restart: enabled: true health: enabled: true info: enabled: true ```

在此模擬三種獲取配置方式:

  • Value方式
  • 建立獨立類ConfigurationProperties
  • Environment方式

3.1 Configmap

3.1.1 value方式

```javascript // -----------------value configmap----------------- @Value("${config.applicationVersion}") private String applicationVersion;

@Value("${config.app.domain}") private String domain;

// configmap value @RequestMapping("/value") public String value() { return applicationVersion + "|" + domain; } ```

3.1.2 ConfigurationProperties

```javascript @Data @ConfigurationProperties(prefix = "config") @Component public class SpringBootConfigProperties { private String applicationVersion;

public String getApplicationVersion() {
    return applicationVersion;
}

}

// -----------------properties configmap----------------- @Autowired private SpringBootConfigProperties springBootConfigProperties;

// configmap properties @RequestMapping("/properties") public String propertie() { return springBootConfigProperties.getApplicationVersion(); } ```

3.1.3 Environment

```javascript // -----------------env configmap & secret----------------- @Autowired private Environment environment;

// configmap env @RequestMapping("/env") public String env() { return environment.getProperty("config.applicationVersion") + "|" + environment.getProperty("config.app.domain"); } ```

3.2 Secret

3.2.1 value

```javascript // -----------------value secret----------------- @Value("${secret.username}") private String username;

@Value("${secret.password}") private String password;

// secret value @RequestMapping("/valuesecret") public String values() { return username + "|" + password; } ```

3.2.2 ConfigurationProperties

```javascript @ConfigurationProperties("secret") @Component @Data public class SpringBootConfigPropertiesSecret { private String username; private String password;

}

// -----------------properties secret----------------- @Autowired private SpringBootConfigPropertiesSecret springBootConfigPropertiesSecret;

// secret properties @RequestMapping("/propertiessecret") public String properties() { return springBootConfigPropertiesSecret.getUsername() + "|" + springBootConfigPropertiesSecret.getPassword(); } ```

3.2.3 Environment

```javascript // -----------------env configmap & secret----------------- @Autowired private Environment environment;

// secret env @RequestMapping("/envsecret") public String envs() { return environment.getProperty("secret.username") + "|" + environment.getProperty("secret.password"); } ```

3.3 專案目錄結構

3.4 K8s部署檔案

3.4.1 Deployment

在此模擬一個簡單的springboot web應用,提供web介面,將獲取的configmap/secret 內容返回。

```yaml apiVersion: apps/v1 kind: Deployment metadata: namespace: springconfig name: springboot-config labels: app: springboot-config spec: replicas: 1 selector: matchLabels: app: springboot-config template: metadata: labels: app: springboot-config spec: containers: - name: springboot-config image: ccr.ccs.tencentyun.com/xxxxxxxxxxxxxops-dev/springbootconfig:img_v2 imagePullPolicy: IfNotPresent # 在此制定使用那個變數,用於指定配置檔案 env: - name: PROFILE value: prod ports: - containerPort: 8080 protocol: TCP

      livenessProbe:
        httpGet:
          port: 8080
          path: /actuator/health
        periodSeconds: 10
        initialDelaySeconds: 3
        terminationGracePeriodSeconds: 10
        failureThreshold: 5
        timeoutSeconds: 10
      readinessProbe:
        httpGet:
          port: 8080
          path: /actuator/health
        initialDelaySeconds: 5
        periodSeconds: 10
        failureThreshold: 5
        timeoutSeconds: 10

```

3.4.2 Configmap

建立configmap 資源,內部包含dev/test/prod環境內容,具體使用那個配置,在deployment中 PROFILE 環境變數傳入。

javascript kind: ConfigMap apiVersion: v1 metadata: name: springboot-config-cm namespace: springconfig data: application.yml: |- spring: profiles: dev config: applicationVersion: dev-0.0.1 app: domain: dev.web.app.com api-domain: devapi.web.app.com auth-callback-api: https://dev.web.app.com/dev/wx/bind-callback aws-proxy-host: 192.168.9.82 aws-proxy-port: 8118 cors-allowed-origins: http://local.web.app.com, https://dev.web.app.com --- spring: profiles: test config: applicationVersion: test-0.0.1 app: domain: test.web.app.com api-domain: testapi.web.app.com auth-callback-api: https://test.web.app.com/dev/wx/bind-callback aws-proxy-host: 192.168.9.82 aws-proxy-port: 8118 cors-allowed-origins: http://local.web.app.com, https://test.web.app.com --- spring: profiles: prod config: applicationVersion: prod-0.0.1 app: domain: web.app.com api-domain: api.web.app.com auth-callback-api: https://web.app.com/dev/wx/bind-callback aws-proxy-host: 192.168.9.82 aws-proxy-port: 8118 cors-allowed-origins: http://web.app.com, https://web.app.com

3.4.3 Secret

secret儲存相對敏感的資訊(儘管K8s中的secret使用的是 base64加密,後期可以配合vault 配合解決K8s配置安全性問題)

javascript apiVersion: v1 kind: Secret type: Opaque metadata: name: springboot-config-secret namespace: springconfig labels: secret: enabled data: secret.username: YWRtaW4NCg== secret.password: MTIzNDU2

3.4.4 Service

由於是web應用,提供servie暴露介面。

javascript apiVersion: v1 kind: Service metadata: name: springbootconfig namespace: springconfig labels: app: springboot-config spec: ports: - port: 8080 protocol: TCP targetPort: 8080 type: ClusterIP selector: app: springboot-config

3.5 專案原始碼

專案位置:https://github.com/redhatxl/cloudnative-java

3.6 配置引數

3.6.1 ConfigMap 可配置引數

| 引數名稱 | 型別 | 預設值 | 引數描述 | | :--------------------------------------- | :------ | :------------------------- | :-------------------------------------------------- | | spring.cloud.kubernetes.config.enabled | Boolean | true | 是否啟動 Config 動態配置 | | spring.cloud.kubernetes.config.name | String | ${spring.application.name} | 設定要查詢的 ConfigMap 的名稱 | | spring.cloud.kubernetes.config.namespace | String | Client名稱空間 | 設定在哪個 Kubernetes Namespace 查詢 ConfigMap 資源 | | spring.cloud.kubernetes.config.paths | List | null | 設定ConfigMap裝入例項的路徑 | | spring.cloud.kubernetes.config.enableApi | Boolean | true | ConfigMap通過API 啟用或禁用使用例項 |

3.6.2 Secret 可配置引數

| 引數名稱 | 型別 | 預設值 | 引數描述 | | :---------------------------------------- | :------ | :------------------------- | :----------------------------------------------- | | spring.cloud.kubernetes.secrets.enabled | Boolean | true | 是否啟動 Secret 動態配置 | | spring.cloud.kubernetes.secrets.name | String | ${spring.application.name} | 設定要查詢的 Secret 的名稱 | | spring.cloud.kubernetes.secrets.namespace | String | Client名稱空間 | 設定在哪個 Kubernetes Namespace 查詢 Secret 資源 | | spring.cloud.kubernetes.secrets.labels | Map | null | 設定用於查詢 Secret 的標籤 | | spring.cloud.kubernetes.secrets.paths | List | null | 設定安裝 Secret 的路徑 | | spring.cloud.kubernetes.secrets.enableApi | Boolean | false | 啟用或禁用通過 API 監聽 Secret |

3.6.3 Reload 可配置引數

| 引數名稱 | 型別 | 預設值 | 引數描述 | | :---------------------------------------------------- | :------- | :------ | :----------------------------------------------------------- | | spring.cloud.kubernetes.reload.enabled | Boolean | false | 啟動動態配置監控 | | spring.cloud.kubernetes.reload.monitoring-config-maps | Boolean | true | 允許監視 ConfigMap 中的更改 | | spring.cloud.kubernetes.reload.monitoring-secrets | Boolean | false | 允許監控 Secret 的變化 | | spring.cloud.kubernetes.reload.strategy | Enum | refresh | 配置更新策略: – refresh(更新) – restart_context(重啟Spring容器) – shutdown(殺死應用使Kubernetes對其重啟)) | | spring.cloud.kubernetes.reload.mode | Enum | event | 指定監聽策略: – event:配置發生變化就執行更新 – polling:定時檢測配置變化從而更新配置 | | spring.cloud.kubernetes.reload.period | Duration | 15s | 使用 polling 策略時檢測間隔時間 |

四 測試

服務部署完畢在springconfig 名稱空間下

4.1 Configmap

  • 檢視value

  • 檢視properties

  • 檢視env

修改cm內容(k edit cm -n springconfig springboot-config-cm),檢視內容

結論:value 使用pull模式,無法完成內容變更,使用env/properties方式獲取內容,都會隨著cm修改,根據pull週期理解生效。

4.2 Secret

  • 檢視value

  • 檢視properties

  • 檢視env

修改secret內容(k edit secret -n springconfig springboot-config-secret)

  • 結論:value 使用pull模式,無法完成內容變更,使用env/properties方式獲取內容,都會隨著cm修改,根據pull週期理解生效。

五 其他

還是需要根據業務場景選擇pull模式及拉去頻率,以及是否需要重啟IOC容器,控制影響範圍。

參考連結

  • https://www.infvie.com/ops-notes/kubernetes-springcloud-dynamic-configuration.html
  • https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/#spring-cloud-kubernetes-configuration-watcher