應用程式的配置與程式碼的嚴格分離——Kubernetes配置

應用程式的配置與程式碼的嚴格分離——Kubernetes配置

在本文中,您將瞭解有關 Spring Boot 配置的更多資訊。我將向您展示如何在不同的環境中有效地使用它。特別是,我們更多地討論了 Kubernetes 的配置。有很多可用的選項,包括屬性、YAML 檔案、環境變數和命令列引數。我們想要實現的是我們應用程式的配置與程式碼的嚴格分離。我們應該遵守十二要素應用程式的第三條規則。

如果您喜歡與 Spring Boot 配置相關的主題,您可能會對我部落格上的另外兩篇文章感興趣。要閱讀有關 Spring Boot 自動配置的資訊,請參閱以下文章。要了解有關一般配置的更多資訊,請閱讀該帖子。

原始碼

如果您想自己嘗試一下,可以隨時檢視我的原始碼。為此,您需要克隆我的 GitHub 儲存庫。之後,您應該按照我的指示進行操作。讓我們開始。

載入配置級別

配置管理是 Spring Boot 中一個非常有趣的話題。總共有 14 級排序屬性值和 4 級排序配置資料檔案。您將在此處找到所有級別的完整列表。讓我們從第一個例子開始。假設

property。default

Spring Boot中有一個屬性

application。yml

property。default: app

我們在 classpath 中有另一個配置檔案

additional。yml

。它包含相同的屬性,但具有不同的值。

property。default: additional

我們的 Spring Boot 應用程式

additional。yml

在啟動時使用

@PropertySource

註解載入檔案。那不是全部。在同一程式碼中,它還使用該

SpringApplication。setDefaultProperties

方法設定該屬性的預設值。

@SpringBootApplication@PropertySource(value = “classpath:/additional。yml”, ignoreResourceNotFound = true)public class ConfigApp { private static final Logger LOG = LoggerFactory。getLogger(ConfigApp。class); public static void main(String[] args) { SpringApplication app = new SpringApplication(ConfigApp。class); app。setDefaultProperties(Map。of(“property。default”, “default”)); app。setAllowBeanDefinitionOverriding(true); app。run(args); } @Value(“${property。default}”) String propertyDefault; @PostConstruct public void printInfo() { LOG。info(“========= Property (default): {}”, propertyDefault); }}

應用程式載入的屬性的最終值是多少?在我們回答這個問題之前,讓我們把我們的例子稍微複雜一點。我們設定環境變數

PROPERTY_DEFAULT

如下圖。

$ export PROPERTY_DEFAULT=env

最後,我們可以執行應用程式。以下程式碼片段

LOG。info(“========= Property (default): {}”, propertyDefault)

將列印該值

env

。環境變數覆蓋我們示例中使用的所有其他級別。事實上,這只是 14 的第五個配置級別。

應用程式的配置與程式碼的嚴格分離——Kubernetes配置

Kubernetes 上的 Spring 配置

通常,在雲平臺中,我們使用環境變數來配置我們的應用程式。例如,在 Kubernetes 中,我們有兩種型別的物件用於管理配置:

Secret

ConfigMap

。 我們可以在 Kubernetes 中定義我們的屬性值,

ConfigMap

如下所示。

apiVersion: v1kind: ConfigMapmetadata: name: springboot-configuration-playgrounddata: PROPERTY_DEFAULT: env

我們只需將我們的附加

ConfigMap

到 Kubernetes就可以將此值傳遞給應用程式

Deployment

apiVersion: apps/v1kind: Deploymentmetadata: name: springboot-configuration-playgroundspec: selector: matchLabels: app: springboot-configuration-playground template: metadata: labels: app: springboot-configuration-playground spec: containers: - name: springboot-configuration-playground image: piomin/springboot-configuration-playground ports: - containerPort: 8080 envFrom: - configMapRef: name: springboot-configuration-playground

實際上,對於本地作業系統上設定的環境變數,我們得到的結果與之前相同。我們的應用程式使用該值

env

作為

propertyDefault

變數。雖然我們在 Spring Boot 中有 14 級配置,但我們可以只使用其中的 5 級來有效地管理應用程式。我會說更多——小心使用其他配置級別,因為您可能會禁用環境變數的覆蓋值。當然,你可能有這樣做的理由。但重要的是在開始使用更復雜的配置之前瞭解這些級別。

kubectl logs

您可以使用以下命令在部署後驗證日誌:

應用程式的配置與程式碼的嚴格分離——Kubernetes配置

在 Kubernetes 中,我們還可以將整個配置檔案傳遞給

Deployment

via

ConfigMap

,而不僅僅是單獨的屬性。現在,讓我們假設我們只建立

application。yml

檔案而不是設定環境變數,如下所示。

apiVersion: v1kind: ConfigMapmetadata: name: springboot-configuration-playground-extdata: application。yml: | property。default: external

我們需要重新配置應用

Deployment

YAML。我們將在路徑

ConfigMap

下附加我們的安裝卷。

/config

由於我們使用 Jib Maven 外掛作為構建映象的工具,所以主應用程式執行目錄是

/

。 預設情況下,Spring Boot 從位於當前目錄或當前目錄

/config

子目錄中的檔案中讀取屬性。

apiVersion: apps/v1kind: Deploymentmetadata: name: springboot-configuration-playgroundspec: selector: matchLabels: app: springboot-configuration-playground template: metadata: labels: app: springboot-configuration-playground spec: containers: - name: springboot-configuration-playground image: piomin/springboot-configuration-playground ports: - containerPort: 8080 volumeMounts: - mountPath: /config name: app-vol volumes: - name: app-vol configMap: name: springboot-configuration-playground-ext

這是我們在使用最新的

ConfigMap

。 位於打包 jar 之外的

application。yml

檔案會覆蓋類路徑中的相同檔案。

應用程式的配置與程式碼的嚴格分離——Kubernetes配置

將配置傳遞給 Spring Boot 應用程式的另一種有趣方式是透過內聯 JSON 屬性。當您的應用程式啟動時,任何

spring。application。json

SPRING_APPLICATION_JSON

屬性都將被解析並新增到

Environment

。 此方法將覆蓋所有先前描述的負載級別設定的值。為了在 Kubernetes 上測試它,讓我們修改我們

ConfigMap

的環境變數,然後重新載入應用程式。現在日誌中的輸出應該是

json

apiVersion: v1kind: ConfigMapmetadata: name: springboot-configuration-playgrounddata: PROPERTY_DEFAULT: env SPRING_APPLICATION_JSON: ‘{“property”:{“default”:“json”}}’

設定雲平臺特定配置

讓我們考慮另一個與雲平臺相關的有趣功能。您認為是否可以根據雲環境僅載入部分配置?是的!Spring Boot 能夠檢測我們是否在 Kubernetes 上執行我們的應用程式。我們唯一需要做的就是正確標記配置的那部分。有專門的財產

spring。config。activate。on-cloud-platform

。我們需要在那裡設定目標平臺的名稱。在我們的例子中是 Kubernetes,但 Spring Boot 也支援 Heroku 或 Azure。讓我們

property。activation

application。yml

。 只有當我們在 Kubernetes 上執行我們的應用程式時,才會啟用該屬性:

property。default: app——-spring: config: activate: on-cloud-platform: “kubernetes”property。activation: “I‘m on Kubernetes!”

然後我們需要更新負責在日誌中列印屬性的應用程式主類的一部分:

@Value(“${property。default}”)String propertyDefault;@Value(“${property。activation:none}”)String propertyActivation;@PostConstructpublic void printInfo() { LOG。info(“========= Property (default): {}”, propertyDefault); LOG。info(“========= Property (activation): {}”, propertyActivation);}

如果您使用 Skaffold,您可以使用命令輕鬆地在 Kubernetes 上執行示例應用程式

skaffold dev

。否則,只需使用我的 Docker Hub 儲存庫中的映像進行部署:

piomin/springboot-configuration-playground

。 結果如下:

應用程式的配置與程式碼的嚴格分離——Kubernetes配置

如果我們使用命令在本地執行相同的應用程式,

mvn spring-boot:run

它將列印預設值

none

應用程式的配置與程式碼的嚴格分離——Kubernetes配置

使用 Spring Boot Test 測試配置

配置屬性可能會對應用程式產生巨大影響。例如,我們可以根據帶有

@Conditional

註釋的配置屬性載入不同的 bean。因此,我們應該仔細測試它。假設我們有以下

@Configuration

包含

MyBean2

and

MyBean3

bean 的類:

public class MyConfiguration { @Bean @ConditionalOnProperty(“myBean2。enabled”) public MyBean2 myBean2() { return new MyBean2(); } @Bean @ConditionalOnJava(range = ConditionalOnJava。Range。EQUAL_OR_NEWER, value = JavaVersion。EIGHTEEN) public MyBean3 myBean3() { return new MyBean3(); }}

僅當我們定義

myBean2。enabled

屬性時,此 bean 才會在上下文中註冊。另一方面,有一個

@Configuration

類覆蓋了上面可見的 bean:

public class MyConfigurationOverride { @Bean public MyBean2 myBean2() { MyBean2 b = new MyBean2(); b。setMe(“I’m MyBean2 overridden”); return b; }}

如何使用 JUnit 測試對其進行測試?幸運的是,有一個

ApplicationContextRunner

類允許我們使用各種配置選項輕鬆測試 bean 載入。首先,我們需要將這兩個類作為

@Configuration

測試中的類傳遞。預設情況下,自 2。1 版以來,Spring Boot 不允許覆蓋 bean。我們需要直接使用屬性來啟用它

spring。main。allow-bean-definition-overriding=true

。最後,我們必須設定測試屬性的值。

@Testpublic void testOrder() { final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); contextRunner 。withAllowBeanDefinitionOverriding(true) 。withUserConfiguration(MyConfiguration。class, MyConfigurationOverride。class) 。withPropertyValues(“myBean2。enabled”) 。run(context -> { MyBean2 myBean2 = context。getBean(MyBean2。class); Assertions。assertEquals(“I‘m MyBean2 overridden”, myBean2。me()); });}

MyBean3

然後讓我們驗證我們為bean使用了正確的 Java 版本。至少應該是 18。如果我使用的是早期版本的 Java,會發生什麼情況?

@Testpublic void testMyBean3() { final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); Assertions。assertThrows(NoSuchBeanDefinitionException。class, () -> { contextRunner 。withUserConfiguration(MyConfiguration。class) 。run(context -> { MyBean3 myBean3 = context。getBean(MyBean3。class); Assertions。assertEquals(“I’m MyBean3”, myBean3。me()); }); });}

最後的想法

Spring Boot 是一個非常強大的框架。它提供了許多可以在雲平臺上有效使用的配置選項。在本文中,您可以瞭解如何正確使用它們,例如在 Kubernetes 上。您還可以看到如何使用不同級別的載入屬性以及如何在它們之間切換。