赞
踩
摘要 本文是《Spring Microservices In Action》第三章关于配置管理的中文翻译,在微服务实践中将所有微服务的配置集中外置到配置中心统一管理,通过将配置管理抽象成独立的服务来简化在不同的环境中的微服务配置管理,帮助微服务无状态化和轻量部署以及调度,已经成为业内默认的最佳实践,Spring Cloud开放的子项目ConfigServer,阿里云免费开放的应用配置中心产品ACM都是这种最佳实践的具体体现。
Spring Microservices In Action
– John Carnell
本章涵盖
无论如何,开发者被迫将代码中的配置信息从代码中分离出来。毕竟,自从上学以来,不应该将值硬编码到代码中就已经烙印在开发者的脑海中。许多开发人员在其应用程序中使用常量类文件,以便将所有配置集中在一个地方。将应用程序配置数据直接写在代码里的通常是有问题的,因为每次必须更改配置时,都必须重新编译和/或重新部署应用程序。为了避免这种情况,开发人员将从应用程序代码中完全分离出配置信息。这使得在不经过重新编译过程的情况下对配置进行变更变得很容易,但这也会带来复杂性,因为你现在有另一个需要与应用程序一起管理和部署的工件。
许多开发人员转向低级属性文件(或YAML,JSON或XML)来存储其配置信息。这个属性文件放在一个服务器上,通常包含数据库和中间件连接信息以及那些将驱动应用程序行为的应用程序的元数据。将应用程序配置分离到一个属性文件是很容易的,大多数开发者不再对他们的应用程序配置进行更多的操作,仅只是把他们作为应用程序的一部分放在源代码版本控制之下并作为应用程序的一部分来部署。
这种方法可能适用于少数应用程序,但在处理可能包含数百个基于云的微服务的应用程序,而每个微服务又可能有多个服务实例在运行时,这种方式会很快崩溃。
突然之间,配置管理成为了一个大问题,因为基于云的环境中的应用程序开发和运维团队必须与那些配置文件所在的鼠窝搏斗。基于云的微服务开发强调
本章将向你介绍在基于云的微服务应用程序中管理应用程序配置数据所需的核心原则和模式。
管理应用程序配置对于在云中运行的微服务至关重要,因为微服务实例需要在最少的人为干预下快速启动,以应对应用程序的可扩展性挑战。而每当部署时需要人手动配置或介入时,都会出现配置漂移,意外停机和滞后时间。
让我们开始讨论应用程序配置管理,建立我们想要遵循的四个原则:
要记住的关键之一是,当你将配置信息分离到实际代码之外时,你将创建一个需要进行管理和版本控制的外部依赖关系。我必须强调应用程序配置数据需要版本控制和跟踪,因为管理不善的应用程序配置是难以察觉的错误和意外中断的肥沃滋生地。
意外的复杂性
我亲身体验过没有管理应用程序配置数据策略的危险性。在财富500强金融服务公司工作期间,我被要求帮助将一个大的WebSphere升级项目带回到正轨。该公司在WebSphere上拥有超过120个应用程序,并且需要在整个应用程序环境达到供应商维护期限结束之前将其基础结构从WebSphere 6升级到WebSphere 7。
该项目已经进行了一年,仅部署了120个应用程序中的一个。这个项目花费了一百万美元的人力和硬件成本,而目前的进度轨迹显示还需要再过两年才能完成升级。
当我开始与应用程序团队合作时,我发现的一个(也是仅仅一个)主要问题是应用程序团队用在属性文件内管理其所有数据库的配置以及服务的端点信息。这些属性文件是手动管理的,不受源代码控制。随着120个应用程序分布在四个环境和每个应用程序的多个WebSphere节点上,这个配置文件的老鼠窝导致团队需要尝试迁移分布在运行的数百台服务器和应用程序的12,000个配置文件。 (是的,你正在读取这个数字是:12,000。)这些文件还只是用于应用程序的配置,甚至不包括应用程序服务器的配置。
我说服了项目发起人要花费两个月的时间将所有的应用程序信息整合到一个由20个配置文件组成的集中版本控制的配置库中。当我向框架团队询问有关12000个配置文件的情况时,团队的首席工程师说,他们最初是围绕一小组应用程序设计配置策略的。然而,构建和部署的Web应用程序的数量在五年内爆炸性增长,尽管他们乞求花钱和时间重新修改其配置管理方法,但他们的业务合作伙伴和IT领导者从未将其视为优先事项。
不花时间来弄清楚如何进行配置管理会产生真实(而且是代价昂贵的)的负面影响。
正如你在第二章中所记得的那样,加载微服务的配置管理发生在微服务的启动(Bootstrapping)阶段。提醒一下,图3.1显示了微服务的生命周期。
让我们来看看前面3.1节(隔离,抽象,集中和硬化)中我们所阐述的四个原则,看看这四个原则在启动时如何应用。图3.2更详细地介绍了启动过程,并展示了配置服务在这一步中如何扮演了一个至关重要的角色。
在图3.2中,你会看到几个活动正在发生:
当一个微服务实例启动时,它将调用一个(配置)服务端点来读取与它正在运行的环境匹配的特定的配置信息。配置管理的连接信息(连接凭证,服务端点等等)将在启动时传入微服务。
实际的配置将驻存在一个存储库中。根据配置库的实现,你可以选择使用不同的实现来保存配置数据。实现选项可以包括源代码管理下的文件,关系数据库或kv数据库。
应用程序配置数据的实际管理独立于应用程序的部署方式。通常通过构建和部署流(pipeline)来处理对配置的变更管理,其中可以使用版本信息对配置的变更进行标记,并通过不同的环境进行部署。
当进行配置管理变更时,必须通知使用该配置的那些应用服务,同时刷新应用程序配置数据的副本。
现在我们已经完成了概念架构,它阐明了配置管理模式的不同部分以及这些部分如何组合在一起。我们现在要继续研究不同的解决方案,然后来看一个具体的实现方案。
幸运的是,你可以选择大量经过实战检验的开源项目来实现配置管理解决方案。让我们来看几个可用的不同选择并进行比较。表3.1列出了这些选择。
项目名 | 描述 | 特征 |
---|---|---|
Etcd | 用Go编写的开源项目用于服务发现和键值管理。其分布式计算模型使用raft协议(https:// raft.github.io/) | 非常快速和可扩展。分布式。命令行驱动。易于使用和设置。 |
Eureka | 由Netflix研发。经过严苛的实战检验。用于服务发现和键值管理。 | 分布式键值存储。 灵活的。学习成本较高。开箱即用的动态刷新客户端。 |
Consul | 由Hashicorp研发。类似于Etcd和Eureka的特性,但是为其分布式计算模型使用了不同的算法(SWIM 协议 https:// www.cs.cornell.edu/~asdas/research/ dsn02-swim.pdf)) | 快。 提供原生的服务发现功能,可直接与DNS集成。不提供客户端动态刷新能力。 |
ZooKeeper | 一个提供分布式锁功能的Apache项目。常常用作访问键值数据的配置管理解决方案。 | 最古老,最经过实战检验的解决方案。用起来最复杂。可以用于配置管理,但只有在你的架构中已经使用了ZooKeeper的情况下才应该考虑。 |
Spring Cloud configuration server | 开源项目,提供了一个通用的配置管理解决方案,不同的后端。它可以将Git,Eureka和Consul作为后端整合 | 非分布式密钥/值存储。为Spring和非Spring服务提供紧密集成可以使用多个后端进行故事配置数据,包括共享文件系统Eureka,Consul和Git |
ACM | 阿里云开放的免费配置中心产品,专注于应用配置管理的一站式解决方案。 | 简单易用,特性丰富。经过阿里巴巴大规模生产验证。推送性能高。高可靠的SLA。 |
表3.1中的所有解决方案均可轻松用于构建配置管理解决方案。对于本章以及本书其余部分的示例,将使用Spring Cloud config server。我选择这个解决方案的原因有很多,其中包括:
其他工具(Etcd,Consul,Eureka)不提供任何种类的本地版本,如果你想要的话,你必须自己构建它。如果你已经使用Git,则使用Spring Cloud配置服务器是一个有吸引力的选择。
对于本章的其余部分,我们将:
建立一个Spring Cloud配置服务器,并演示两种不同的机制来提供应用程序配置数据 - 一个使用filesystem,另一个使用Git仓库
继续构建许可服务,从数据库中检索数据
将Spring Cloud配置服务勾连到你的许可服务中来提供应用程序配置数据
Spring Cloud配置服务器是基于REST的应用程序,它建立在Spring Boot之上。它不是一个独立的服务器。相反,你可以选择将其嵌入到已有的Spring Boot应用程序中,或者启动一个新的Spring Boot项目。
你需要做的第一件事就是建立一个叫做confsvr的新项目目录。在consvr目录中,创建一个新的Maven文件,该文件将用于在你的Spring Cloud配置服务器上启动所需的JAR文件。我不会列全整个Maven文件,而是列出以下列表中的关键部分。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://
www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://
maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.thoughtmechanix</groupId>
<artifactId>configurationserver</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Config Server</name>
<description>Config Server demo project</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.4.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.thoughtmechanix.confsvr. ➥ConfigServerApplication </start-class>
<java.version>1.8</java.version>
<docker.image.name>johncarnell/tmx-confsvr</docker.image.name>
<docker.image.tag>chapter3</docker.image.tag>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
<!--Docker build Config Not Displayed -->
</project>
在上面的Maven文件中,首先声明要用于你的微服务的Spring Boot的版本(版本1.4.4)。Maven定义的下一个重要部分是你要使用的Spring Cloud配置父BOM(物料清单)。 Spring Cloud是一个大量独立项目的集合,都随着他们自己的版本而滚动的。此父级BOM包含云项目中使用的所有第三方库和依赖项以及组成该版本的各个项目的版本号。在本例中,你使用的是Spring Cloud的Camden.SR5版本。通过使用BOM定义,你可以保证在Spring Cloud中使用子项目的兼容版本。这也意味着你不必为你的子依赖声明版本号。清单3.1中的其余部分处理声明你将在服务中使用的特定Spring Cloud依赖关系。第一个依赖项是所有Spring Cloud项目都使用的spring-cloud-starter-config依赖项。第二个依赖项是spring-cloud-config-server启动项目。这包含spring-cloud-config-server的核心库。
Come on, ride the train, the release train
Spring Cloud使用非传统机制来标记Maven项目。 Spring Cloud是独立子项目的集合。Spring Cloud团队通过他们所谓的“发布列车”来发布他们的版本。构成Spring Cloud的所有子项目都被打包在一个Maven物料清单(BOM)中,并作为一个整体发布。Spring Cloud团队一直使用伦敦地铁站名作为发布的名称,每增加一个主要版本,伦敦地铁站就有了第二高的字母。已经有三个版本:Angel,Brixton,and Camden。Camden是迄今为止最新的版本,但其中的子项目仍有多个候选版本分支。
有一件事要注意,Spring Boot是独立于Spring Cloud发行版本发布的。因此,不同版本的Spring Boot与Spring Cloud的不同版本不兼容。通过参考Spring Cloud网站(http://projects.spring.io/spring-cloud/),你可以看到Spring Boot和Spring Cloud之间的版本依赖关系,以及版本系列中包含的不同子项目版本。
你仍然需要设置一个文件来启动并运行配置服务器。这个文件是你的application.yml文件,位于confsvr/src/main/resources目录下。application.yml文件将告诉你的Spring Cloud配置服务监听哪个端口,以及定位提供配置数据的后端在哪里。
你几乎可以开始使用Spring Cloud配置服务了。你需要将服务器指向将保存配置数据的后端存储库。在本章中,你将使用第2章中开始构建的许可服务,作为如何使用Spring Cloud Config的示例。为了简单起见,你将为三种环境设置应用程序配置数据:在本地运行服务时的默认环境,开发环境和生产环境。
在Spring Cloud配置中,一切都是按照层次结构进行的。你的应用程序配置由应用程序的名称表示,然后为每个你希望具有配置信息的环境指定一个属性文件。在每个这样的环境中,你将设置两个配置属性:
将由你的许可服务直接使用的示例属性
用于存储许可证应用数据的Postgres数据库的数据库配置
图3.3展示了如何设置和使用Spring Cloud配置服务。有一件事要注意,当你构建你的配置服务时,它将成为你的环境中运行的另一个微服务。一旦建立,服务的内容就可以通过基于http的REST端点访问。
应用程序配置文件的命名约定是appname-env.yml。从图3.3中的图中可以看到,环境名称直接转换为将被访问的URL以浏览配置信息。稍后,在启动许可微服务示例时,你要运行服务的环境由你在命令行服务启动时传入的Spring Boot profile指定。如果profile没有通过命令行传入,Spring Boot将始终默认使用包含在应用程序包中的application.yml文件中的配置数据。
以下是你将为许可服务提供的一些应用程序配置数据的示例。这是将被包含在图3.3中提到的confsvr/src/main/resources/config/licensingservice/licensingservice.yml文件中的数据。这里是这个文件的部分内容:
tracer.property: "I AM THE DEFAULT"
spring.jpa.database: "POSTGRESQL"
spring.datasource.platform: "postgres"
spring.jpa.show-sql: "true"
spring.database.driverClassName: "org.postgresql.Driver" spring.datasource.url: "jdbc:postgresql://database:5432/eagle_eye_local" spring.datasource.username: "postgres"
spring.datasource.password: "p0stgr@s"
spring.datasource.testWhileIdle: "true"
spring.datasource.validationQuery: "SELECT 1"
spring.jpa.properties.hibernate.dialect:
"org.hibernate.dialect.PostgreSQLDialect"
Think before you implement
我建议不要使用基于文件系统的解决方案来处理大中型云应用程序。使用文件系统方法意味着你需要为想要访问应用程序配置数据的所有云配置服务器实现共享文件挂载点。在云中设置共享文件系统服务器是可行的,但是这导致把维护这个环境的责任放到了你身上。
我将文件系统方法展示为使用Spring Cloud配置服务器是因为这是最容易使用的示例。在后面的章节中,我将介绍如何配置Spring Cloud配置服务器以使用基于云的Git提供程序(如Bitbucket或GitHub)来存储应用程序配置。
本书涵盖的每个Spring Cloud服务总是需要一个启动类来启动服务。这个启动类将包含两件事情:一个Java main() 方法作为服务启动的入口点,一套Spring Cloud注解告诉启动服务将启用哪些Spring Cloud行为。
下面的清单显示了用作配置服务启动类的confsvr/src/main/java/com/thoughtmechanix/confsvr/Application.java。
package com.thoughtmechanix.confsvr;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args)
}
}
接下来,将使用最简单的示例来设置你的Spring Cloud
配置服务器:文件系统。
Spring Cloud配置服务器使用confsvr/src/main/resources/application.yml文件中的条目来指向将存放应用程序配置数据的存储库。建立一个基于文件系统的存储库是完成这个任务的最简单的方法。
为此,请将以下信息添加到配置服务器的application.yml文件中。以下清单显示了你的Spring Cloud配置服务器的application.yml文件的内容。
server:
port: 8888
spring:
profiles:
active: native
cloud:
config:
server:
native:
searchLocations: file:///Users/johncarnell1/book/native_cloud_apps/ch4-config-managment/confsvr/src/main/resources/config/licensingservice
在这个清单中的配置文件中,你首先告诉配置服务器它应该监听所有配置请求的端口号:
server:
port: 8888
由于你使用文件系统来存储应用程序配置信息,因此你需要告知Spring Cloud配置服务器使用“native” profile:
profiles:
active: native
application.yml文件中的最后一部分提供了Spring Cloud配置以及应用程序配置数据所在的目录:
server:
native:
searchLocations: file:///Users/johncarnell1/book/native_cloud_apps/ch4-config-managment/confsvr/src/main/resources/config/licensingservice
配置条目中的重要参数是searchLocations属性。此属性为每个将要由配置服务器管理配置属性的应用程序提供逗号分隔的目录列表。在前面的例子中,你只配置了许可服务。
注意 如果你使用Spring Cloud Config的本地文件系统版本,那么在本地运行你的代码时,你需要修改spring.cloud.config.server.native.searchLocations属性以反映你的本地文件路径。
现在已经完成了足够的工作来启动配置服务器。使用mvn spring-boot:run命令启动配置服务器。服务器现在应该在命令行上显示Spring Boot启动画面。如果你将浏览器指向http://localhost:8888/licensingservice/default,你将看到JSON有效负载将与licensesservice.yml文件中包含的所有属性一起返回。图3.4显示了访问这个端点的结果。
如果要查看开发环境的许可服务的配置信息,请点击GET http://localhost:8888/licensingservice/dev端点。
图3.5显示了访问这个端点的结果:
如果仔细观察,你会看到当你访问开发端点时,将同时返回许可服务的默认配置属性和开发环境配置属性。Spring Cloud Config返回两组配置信息的原因是,Spring框架实现了解析属性的分层机制。当Spring框架执行属性解析时,它将始终首先在默认属性中查找属性,然后使用特定于环境的值(如果存在)覆盖默认属性。
具体而言,如果你在licensingservice.yml文件中定义一个属性,并且不在任何其他环境配置文件(例如licensingservice-dev.yml)中定义它,则Spring框架将使用默认值。
注意 这不是你直接访问Spring Cloud Config REST端点所看到的行为。 REST端点将返回被访问的默认配置值和环境特定的所有配置值。
让我们看看如何将Spring Cloud配置服务器勾联到你的许可微服务。
在前一章中,你构建了一个许可服务的简单框架,只不过是从数据库中返回代表单个许可记录的硬编码Java对象。在下一个示例中,你将构建许可服务并与存有许可数据的Postgres数据库进行交互。
你将使用Spring Data与数据库进行通信,并将数据从许可表映射到持有数据的POJO。你的数据库连接和一个简单的属性将从Spring Cloud配置服务器被读出。图3.6显示了许可服务与Spring Cloud配置服务之间发生了什么事情。
当许可服务首次启动时,你将通过命令行传递两条信息:Spring profile以及许可服务与Spring Cloud配置服务进行通信的端点。Spring profile值映射到为Spring服务检索属性的环境上。当许可服务首次启动时,它将通过传入Spring Profile的端点联系Spring Cloud Config Service。然后,Spring Cloud Config Service将使用配置的后端配置库(filesystem,Git,Consul,Eureka)来检索于URI的Spring传入的profile值匹配的特定的配置信息。然后将适当的属性值传回给许可服务。 Spring Boot框架然后将这些值注入到应用程序的相应部分。
让我们把关注点从配置服务器移到许可服务。你需要做的第一件事是在许可服务中向Maven文件添加更多条目。以下列表中显示了需要添加的条目。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901.jdbc4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
第一个和第二个依赖项spring-boot-starter-data-jpa和PostgreSQL导入了Spring Data Java Persistence API(JPA)和Postgres JDBC驱动程序。最后一个依赖项spring-cloud-config-client包含与Spring Cloud配置服务器进行交互所需的所有类。
在定义了Maven依赖关系后,你需要告知许可服务如何连接Spring Cloud配置服务器。在使用Spring Cloud Config的Spring Boot服务中,可以在两个配置文件(bootstrap.yml和application.yml)之一中设置配置信息。
bootstrap.yml文件在使用任何其他配置信息之前读取应用程序属性。通常,bootstrap.yml文件包含服务的应用名,应用程序profile以及连接到Spring Cloud Config服务器的URI。你希望保留在服务(而不是存储在Spring Cloud Config中)的任何其他本地配置信息都可以在application.yml文件中进行设置。通常,你存储在application.yml文件中的信息是即使Spring Cloud Config服务不可用,也可能希望自身服务可用的配置数据。 bootstrap.yml和application.yml文件都存储在项目的src/main/resources目录中。
要让许可服务与Spring Cloud Config服务进行通信,你需要添加一个licensing-service/src/main/resources/bootstrap.yml文件并在其中设置三个属性:spring.application.name、spring.profiles.active、spring.cloud.config.uri
以下列表中显示了许可服务的bootstrap.yml文件。
spring:
application:
name: licensingservice
profiles:
active:
default
cloud:
config:
uri: http://localhost:8888
注意 Spring Boot应用程序支持两种机制来定义属性:YAML(另一种标记语言)和一个”.”分隔的属性名称。我们选择了YAML(另一种标记语言)作为配置我们的应用程序的手段。 YAML属性值的分层格式直接映射到spring.application.name、spring.profiles.active、spring.cloud.config.uri
名称。
spring.application.name是你的应用程序名(例如licensingservice),并且必须直接映射到你的Spring Cloud Config Server中的目录名称。对于许可服务,你需要Spring Cloud Config Server上建立名为licensingservice的目录。
第二个属性spring.profiles.active用于告诉Spring Boot应该运行为哪个profile。profile是区分Spring Boot应用程序所使用的配置数据的机制。对于许可服务的profile,支持将直接映射到你的云配置环境中的环境。例如,通过传入dev作为我们的profile,Spring Cloud配置服务器将使用dev配置属性。如果你没有设置profile,则许可服务将使用默认profile。
第三个也是最后一个属性spring.cloud.config.uri是许可服务应该查找Spring Cloud配置服务器端点的位置。默认情况下,许可服务将在http://localhost:8888中查找配置服务器。本章稍后将介绍如何在应用程序启动时覆盖boostrap.yml和application.yml文件中定义的不同属性。这将允许你告诉许可微服务应该运行在哪个环境。
现在,如果你启动Spring Cloud配置服务,并在你的本地计算机上运行相应的Postgres数据库,则可以使用其默认profile启动许可服务。这是通过改变许可服务目录并发出以下命令来完成:
mvn spring-boot:run
通过运行此命令并且不设置任何属性集合,许可服务器将自动使用默认的连接端点(http://localhost:8888)和在许可服务的bootstrap.yml文件中定义的active profile(默认)来连接到Spring Cloud配置服务器。
如果要覆盖这些默认值并指向另一个环境,可以通过将licensingservice项目编译为JAR,然后使用覆盖的-D系统属性运行JAR来完成此操作。以下命令行演示了如何使用非默认配置文件启动许可服务:
java -Dspring.cloud.config.uri=http://localhost:8888 \
-Dspring.profiles.active=dev \
-jar target/licensing-service-0.0.1-SNAPSHOT.jar
使用上面的命令行,你将覆盖两个参数:spring.cloud.config.uri和spring.profiles.active。使用-Dspring.cloud.config.uri=http://localhost:8888系统属性,可以指向非本地的远程配置服务器。
注意 如果你尝试使用先前的Java命令运行从GitHub存储库(https://github.com/carnellj/spmia-chapter3)下载的许可服务,会失败,因为你没有运行Postgres服务器,GitHub仓库中的源代码在配置服务器上使用了加密。本章稍后会介绍使用加密技术。上面的例子演示了如何通过命令行覆盖Spring的属性。
使用-Dspring.profiles.active=dev系统属性,你告诉授权服务使用dev profile(从配置服务器读取)连接到数据库的dev实例。
使用环境变量传递启动信息
在这个例子中,你正在用硬编码的值传递给-D参数值。在云中,你需要的大部分应用程序配置数据都将位于你的配置服务器中。但是,对于启动服务需要的信息(例如配置服务器的信息),将通过启动VM实例或Docker容器时传入一个环境变量。
所有的章节的代码示例都可以从Docker容器中完全运行。通过Docker,你可以通过环境特定的Docker-compose文件来模拟不同的环境,这些文件可以协调所有服务的启动。容器所需的环境特定值作为环境变量传递到容器。例如,要在开发环境中启动许可服务,docker/dev/docker-compose.yml文件包含以下用于许可服务的条目:
licensingservice:
image: ch3-thoughtmechanix/licensing-service
ports:
- "8080:8080"
environment:
PROFILE: "dev"
CONFIGSERVER_URI: http://configserver:8888
CONFIGSERVER_PORT: "8888"
DATABASESERVER_PORT: "5432"
文件中的环境条目包含两个变量PROFILE的值,这是许可服务将要运行的Spring Boot profile。 CONFIGSERVER_URI传递并定义许可服务将从其读取配置数据的Spring Cloud配置服务器实例。
在由容器运行的启动脚本中,然后将这些环境变量作为-D参数传递给启动应用程序的JVMS。在每个项目中,你将烘焙Docker容器,并且在该Docker容器使用一个启动脚本来启动容器中的软件。对于许可服务,可以在licensing-service/src/main/docker/run.sh中找到容器中的启动脚本。在run.sh脚本中,以下条目将启动你的许可服务JVM:
echo "********************************************************"
echo "Starting License Server with Configuration Service :
$CONFIGSERVER_URI";
echo "********************************************************" java -Dspring.cloud.config.uri=$CONFIGSERVER_URI -Dspring.profiles.active=$PROFILE -jar /usr/local/licensingservice/
licensing-service-0.0.1-SNAPSHOT.jar
因为通过Spring Boot Actuator可以通过自检你的所有服务,可以通过访问http://localhost:8080/env来确认所运行的环境。/env端点将提供服务配置信息的完整列表,包括服务启动的属性和所在的端点,如图3.7所示。
从图3.7中要注意的是,许可服务的活动profile是dev。通过检查返回的JSON,你还可以看到正在返回的Postgres数据库是jdbc:postgresql://database:5432/eagle_eye_dev的开发URI。
暴露太多的信息
如何在服务周围实现安全,每个组织都有不同的规则。许多组织认为,服务不应该广播任何关于自己的信息,也不会允许诸如/env端点之类的东西在服务上处于活动状态,因为他们认为这样做会为潜在的黑客提供太多的信息。Spring Boot提供了丰富的功能,用于配置Spring Actuators端点所返回的信息,这些信息不在本书的范围之内。在克雷格·沃尔斯(Craig Walls)出色的书籍“Spring Boot in Action”中详细介绍了这一主题,我强烈建议你阅读公司的安全策略和Walls的书籍,以提供你希望通过Spring Actuator公开的正确安全级别的细节。
到此,你将数据库配置信息直接注入到你的微服务中。通过设置数据库配置,配置你的许可服务变成了使用标准Spring组件来从Postgres数据库构建和检索数据的练习。许可服务已经被重构为几个不同的类,每个类都有不同的职责。这些类如表3.2所示。
License类是持有从许可数据库中检索的数据的模型类。以下清单展示了License类的代码。
package com.thoughtmechanix.licenses.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "licenses")
public class License{
@Id
@Column(name = "license_id", nullable = false)
private String licenseId;
@Column(name = "organization_id", nullable = false)
private String organizationId;
@Column(name = "product_name", nullable = false)
private String productName;
/*The rest of the code has been removed for conciseness*/
}
这个类使用了几个Java Persistence Annotation(JPA),它们可以帮助Spring Data框架将Postgres数据库中licenses表中的数据映射到Java对象。 @Entity注解让Spring知道这个Java POJO将会映射到保存数据的对象。 @Table注解告诉Spring/JPA应该映射哪个数据库表。@Id注解标识数据库的主键。最后,将要映射到单个属性的数据库中的每一列标记为@Column属性。
Spring Data和JPA框架提供了访问数据库的基本CRUD方法。如果你想构建超越这个方法的方法,你可以使用Spring Data Repository接口和基本的命名约定来构建这些方法。Spring将在启动时从Repository接口解析方法的名称,并将它们转换为基于名称的SQL语句,然后生成一个动态代理类来进行工作。以下列表中展示了许可服务的repository。
package com.thoughtmechanix.licenses.repository;
import com.thoughtmechanix.licenses.model.License;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface LicenseRepository
extends CrudRepository<License,String> {
public List<License> findByOrganizationId (String organizationId);
public License findByOrganizationIdAndLicenseId (String organizationId,String licenseId);
}
repository接口LicenseRepository标有@Repository注解,告诉Spring它应该把这个接口当作一个repository,并为它生成一个动态代理。Spring为数据访问提供了不同类型的repository。你已经选择使用Spring CrudRepository基类来扩展你的LicenseRepository类。CrudRepository基类包含基本的CRUD方法。除了从CrudRepository扩展的CRUD方法之外,你还添加了两个自定义查询方法来从许可表中检索数据。Spring Data框架将拆开方法的名字来构建一个查询,来访问底层的数据。
注意 Spring Data框架在各种数据库平台上提供了一个抽象层,并不仅限于关系数据库。也支持如MongoDB和Cassandra这样的NoSQL数据库。
与第2章中许可服务的先前版本不同,现在你已将许可服务的业务和数据访问逻辑从LicenseController中分离出来,并分离到LicenseService的独立服务类中。
package com.thoughtmechanix.licenses.services;
import com.thoughtmechanix.licenses.config.ServiceConfig;
import com.thoughtmechanix.licenses.model.License;
import com.thoughtmechanix.licenses.repository.LicenseRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
public class LicenseService {
@Autowired
private LicenseRepository licenseRepository;
@Autowired
ServiceConfig config;
public License getLicense(String organizationId,String licenseId) {
License license = licenseRepository.findByOrganizationIdAndLicenseId(
organizationId, licenseId);
return license.withComment(config.getExampleProperty());
}
public List<License> getLicensesByOrg(String organizationId){
return licenseRepository.findByOrganizationId( organizationId );
}
public void saveLicense(License license){
license.withId( UUID.randomUUID().toString());
licenseRepository.save(license);
}
/*Rest of the code removed for conciseness*/
}
controller,service,和repository类使用标准的Spring @Autowired 注解连接在一起。
在上一节的LicenseService类中,你可能已经注意到你正在使用config.getExampleProperty()中获取的值在getLicense()代码中设置license.withComment()。代码如下所示:
public License getLicense(String organizationId,String licenseId) {
License license = licenseRepository.findByOrganizationIdAndLicenseId(
organizationId, licenseId);
return license.withComment(config.getExampleProperty());
}
如果你查看licensing-service/src/main/java/com/ thinkmechanix/licenses/config/ServiceConfig.java类,你将看到使用@Value注解的属性。以下列表显示正在使用的@Value注解。
package com.thoughtmechanix.licenses.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
public class ServiceConfig {
@Value("${example.property}")
private String exampleProperty;
public String getExampleProperty(){
return exampleProperty;
}
}
虽然Spring Data“自动奇迹地”将数据库的配置数据注入到数据库连接对象中,但所有其他属性必须使用@Value注解注入。在前面的示例中,@Value批注从Spring Cloud配置服务器中提取example.property,并将其注入到ServiceConfig类的example.property属性中。
提示 虽然可以直接将配置值注入到各个类中的属性中,但是我发现将所有配置信息集中到一个配置类中,然后将该配置类注入到需要的地方是很有用的。
如前所述,使用文件系统作为Spring Cloud配置服务器的后端存储库对于基于云的应用程序来说可能是不切实际的,因为开发团队必须设置和管理在Cloud配置服务器的所有实例上挂载的共享文件系统。
Spring Cloud配置服务器与不同的后端存储库集成,可用于托管应用程序配置属性。我成功使用的一个方法是将Spring Cloud配置服务器与Git源代码控制库一起使用。
通过使用Git,你可以获得将配置管理属性置于源代码控制之下的所有好处,并提供一种简单的机制,将你的属性配置文件的部署集成到构建和部署管道中。
要使用Git,你需要在配置服务的bootstrap.yml文件中使用下面的配置替换掉文件系统的配置。
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/carnellj/config-repo/
searchPaths:licensingservice,organizationservice
username: native-cloud-apps
password: 0ffended
上例中的三个关键配置是spring.cloud.config.server,spring.cloud.config.server.git.uri和spring.cloud.config.server.git.searchPaths属性。 spring.cloud.config.server属性告诉Spring Cloud配置服务器使用一个基于非文件系统的后端库。在前面的示例中,你将连接到基于云的Git存储库GitHub。spring.cloud.config.server.git.uri属性提供了你要连接到的存储库的URL。最后,spring.cloud.config.server.git.searchPaths属性告诉Spring Cloud Config服务器,当Cloud配置服务器启动时,应该在Git存储库上搜索的相对路径。像配置的文件系统版本一样,spring.cloud.config.server.git.seachPaths属性中的值将配置服务托管的每个服务的逗号分隔的列表。
开发团队希望使用Spring Cloud配置服务器时遇到的第一个问题是,如何在属性变更时动态刷新应用程序。Spring Cloud配置服务器将始终提供最新版本的属性。通过其底层存储库对属性所做的更改将是最新的。
但是,Spring Boot应用程序只会在启动时读取它们的属性,所以Spring 配置服务器中所做的属性变更将不会被Spring Boot应用程序自动获取。Spring Boot Actuator确实提供了一个@RefreshScope注解,允许开发团队访问一个/refresh端点,以强制Spring Boot应用程序重新读取其应用程序配置。下面的清单显示了@RefreshScope注解的作用。
package com.thoughtmechanix.licenses;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.context.config.annotation.RefreshScope;
@SpringBootApplication
@RefreshScope
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
请注意关于@RefreshScope注解的一些事情。首先,注解只会重载你在应用程序配置中客制的Spring属性。 Spring Data使用的数据库配置等项目不会被@RefreshScope注解重新加载。要执行刷新,可以访问http://:8080/refresh端点。
刷新微服务
将Spring Cloud配置服务与微服务一起使用时,在动态变更属性之前需要考虑的一件事情是,你可能会运行同一微服务的多个实例,并且需要使用新的应用程序配置来刷新所有这些微服务。有几种方法可以解决这个问题:
Spring Cloud配置服务确实提供了一种名为“Spring Cloud Bus”的“推送”机制,允许Spring Cloud配置服务器向所有使用配置服务的客户端发布已发生变更的情况。 Spring Cloud配置需要运行一个额外的中间件(RabbitMQ)。这是检测变化的非常有用的手段,但并不是所有的Spring云配置后端都支持“推送”机制(即Consul服务器)。
在下一章中,你将使用Spring Service Discovery和Eureka来注册服务的所有实例。我用来处理应用程序配置刷新事件的一种技术是刷新Spring Cloud配置中的应用程序属性,然后编写一个简单的脚本来查询服务发现引擎,以查找服务的所有实例并直接调用/refresh端点。
最后,你可以重新启动所有的服务器或容器来获取新的属性。这是一个微不足道的练习,特别是如果你在Docker等容器服务中运行服务时。重新启动Docker容器需要几秒钟的时间,并会强制重新读取应用程序配置。
请记住,基于云的服务器是临时的。不要害怕使用新配置启动服务的新实例,将流量引导至新服务,然后拆除旧服务。
默认情况下,Spring云配置服务器以纯文本格式将所有属性存储在应用程序的配置文件中。这包括敏感信息,如数据库凭证。
将敏感凭证作为纯文本存储在你的源代码库中是非常糟糕的做法。不幸的是,这发生得要比你想象的多得多。Spring Cloud Config可以让你轻松加密敏感属性。Spring Cloud Config支持使用对称(共享密钥)和不对称加密(公钥/私钥)。
我们将看看如何设置Spring Cloud配置服务器以使用对称密钥加密。要做到这一点,你需要
首先,你需要下载并安装Oracle的无限强度Java密码扩展(JCE)。这不是通过Maven提供的,必须从Oracle公司下载。一旦下载了包含JCE jar的zip文件,你必须执行以下操作:
自动化安装Oracle JCE文件的过程
我已经描述了在你的笔记本电脑上安装JCE所需的手动步骤。因为我们将所有服务构建为Docker容器,所以我已经在Spring Cloud Config Docker容器中脚本化了这些JAR文件的下载和安装。下面的OS X shell脚本片段显示了如何使用curl(https://curl.haxx.se/)命令行工具自动执行此操作:
cd /tmp/
curl -k-LO "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-
8.zip"
-H 'Cookie:oraclelicense = accept-securebackup-cookie' && unzip
jce_policy-8.zip
rm jce_policy-8.zip
yes | cp -v /tmp/UnlimitedJCEPolicyJDK8/*.jar /usr/lib/jvm/java-1.8-openjdk/jre/lib/security
我不打算详细介绍所有的细节,但基本上我使用CURL来下载JCE zip文件(注意curl命令中通过-H属性传递的Cookie头参数),然后解压缩文件并将其复制到我的Docker容器中的/usr/lib/jvm/java-1.8-openjdk/jre/lib/security目录。
如果你查看本章源代码中的src/main/docker/ Dockerfile文件,你可以看到这个脚本实例。
一旦JAR文件就位,你需要设置一个对称加密密钥。对称加密密钥只不过是加密器用来加密值和解密器解密值的共享密钥。使用Spring Cloud配置服务器,对称加密密钥是你选择的字符串,通过名为ENCRYPT_KEY的操作系统环境变量传递给服务。在本书中,你总是将ENCRYPT_KEY环境变量设置为
export ENCRYPT_KEY=IMSYMMETRIC
注意关于对称密钥的两件事:
管理加密密钥
在本书中,我做了两件通常我不推荐在生产环境中使用事:
��
我将加密密钥设置为一个短语。我想把密钥保持简单,这样我就可以记住它,它很适合使用可阅读的文本。在真实世界的部署中,我将为每个部署的环境使用一个单独的加密密钥,并使用随机字符作为我的密钥。
我直接在书中使用的Docker文件中硬编码了ENCRYPT_KEY环境变量。我这样做是为了让读者可以下载文件并启动它们,而不必记住设置环境变量。在真正的运行时环境中,我将在我的Dockerfile中引用ENCRYPT_KEY作为操作系统环境变量。注意这一点,不要在你的Dockerfiles里面硬编码你的加密密钥。同时记住,你的Dockerfiles应该在源代码版本控制之下。
你现在已经准备好开始加密Spring Cloud Config中使用的属性了。你将加密你用来访问EagleEye数据的许可服务Postgres数据库密码。这个名为spring.datasource.password的属性当前被设置为纯文本p0stgr@s。
当你启动Spring Cloud Config实例时,Spring Cloud Config将检测到已设置ENCRYPT_KEY环境变量,并自动为Spring Cloud Config服务添加两个新端点(/ encrypt和/decrypt)。你将使用/encrypt端点来加密p0stgr@s值。
图3.8显示了如何使用/encrypt端点和POSTMAN加密p0stgr@s的值。请注意无论何时使用/encrypt 或者/decrypt端点,你要确保对这些端点进行POST访问。
如果你想解密这个值,你可以向/decrypt端点传入加密了的字符串。
你现在可以使用以下语法将加密过的属性添加到你的GitHub或基于文件系统的授权服务的配置文件中:
spring.datasource.password:"{cipher}858201e10fe3c9513e1d28b33ff417a66e8c8411dcff3077c53cf53d8a1be360"
Spring Cloud配置服务器要求所有加密的属性前缀为{cipher}。 {cipher}值告诉Spring Cloud配置服务器正在处理的是个加密值。启动你的Spring Cloud配置服务器并点击GET http://localhost:8888/licensingservice/default端点。
图3.9显示了这个访问的结果。
你已经通过加密属性使spring.datasource.password更安全,但是仍然有问题。当你点击http://localhost:8888/licensingservice/default端点时,数据库密码将以纯文本形式公开。
默认情况下,Spring Cloud Config将在服务器上执行所有的属性解密,并将结果传回给消费属性的应用程序,是未加密的明文文本。但是,你可以告诉Spring Cloud Config在服务器上不要解密,并使其成为应用程序检索配置数据以解密加密属性的责任。
要启用客户端解密属性,你需要做三件事情:
1. 将Spring Cloud Config配置为在服务器端不解密属性
2. 在许可服务器上设置对称密钥
3. 将 spring-security-rsa JAR添加到许可服务的pom.xml文件中。
你需要做的第一件事是在Spring Cloud Config中禁用服务器端属性解密。这可以通过设置Spring Cloud Config的src/main/resources/application.yml文件来设置属性spring.cloud.config.server.encrypt.enabled:false。这就是你在Spring Cloud Config服务器上必须做的所有事情。
由于许可服务现在负责解密已加密的属性,因此你需要首先在许可服务上设置对称密钥,方法是确保ENCRYPT_KEY环境变量设置了与你的Spring Cloud Config服务器使用的相同的对称密钥(例如,IMSYMMETRIC)。
接下来,你需要在许可服务中包含spring-security-rsa JAR依赖项:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
</dependency>
这些JAR文件包含解密从Spring Cloud Config检索的加密属性所需的Spring代码。通过这些更改,你可以启动Spring Cloud Config和许可服务。如果你点击http://localhost:8888/licensingservice/default端点,你将看到在其中返回的spring.datasource.password是加密的形式。图3.10 展示了访问的输出:
应用程序配置管理看起来可能像一个平凡的话题,但在基于云的环境中,这是至关重要的。正如我们将在后面的章节中更详细地讨论的那样,关键是你的应用程序和运行的服务器是不变的,整个服务器不会在环境之间手动配置。这在传统的部署模型中可以正常工作,在这些模型中,将应用程序工件(例如JAR或WAR文件)及其属性文件部署到“固定”环境。
使用基于云的模型,应用程序配置数据应该与应用程序完全分离,并且需要在运行时注入相应的配置数据,以便在所有环境中始终如一地提供相同的服务器/应用程序工件。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。