使用Spring Cloud Kubernetes基於Kubernetes、Spring Boot和Docker構建微服務架構 - Morioh

banq發表於2019-10-16

在本文中,我們將學習如何啟動Spring Boot微服務專案並使用Kubernetes和Docker快速執行它

本文涵蓋的主題是:

  • 在雲原生開發中使用Spring Boot 2.0
  • 使用Spring Cloud Kubernetes專案為所有微服務提供服務發現
  • 使用Kubernetes Config Maps和Secrets將配置設定注入到應用程式Pod中
  • 使用Docker構建應用程式映像並將其使用YAML配置檔案部署在Kubernetes上
  • 結合使用Spring Cloud Kubernetes和Zuul代理來公開所有微服務的單個Swagger API文件

當您構建微服務環境時,Spring Cloud和Kubernetes可能是兩個相互競爭的解決方案。Spring Cloud提供的諸如Eureka,Spring Cloud Config或Zuul之類的元件可以由Kubernetes內建的元件如服務,配置對映,secrets或ingresses替代,但是,即使您決定使用Kubernetes元件而不是Spring Cloud,也可以利用整個Spring Cloud專案中提供的一些有趣功能。

一個對我們有幫助的真正有趣的專案是Spring Cloud Kubernetes。儘管它仍處於孵化階段,但絕對值得花一些時間。它將Spring Cloud與Kubernetes整合在一起。我將向您展示如何使用使用它實現客戶端的發現、與Ribbon客戶端的服務間通訊以及使用Spring Cloud Kubernetes的Zipkin發現。

假設有三個獨立的應用程式(employee-service, department-service, organization-service),它們通過REST API相互通訊。這些Spring Boot微服務使用Kubernetes提供的一些內建機制:用於分散式配置的配置對映和secrets,用於服務發現的etcd以及用於API閘道器的入口。

將spring-cloud-dependency宣告為依賴項管理的BOM。

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Spring Cloud Kubernetes不在Spring Cloud Release Trains下發布,因此我們需要明確定義其版本。因為我們使用Spring Boot 2.0,所以我們必須包含spring-cloud-kubernetes工件的最新SNAPSHOT版本,即0.3.0.BUILD-SNAPSHOT。

本文中提供的示例應用程式的原始碼可在此儲存庫的GitHub找到。

前提條件

為了能夠部署和測試我們的示例微服務,我們需要準備一個開發環境。我們可以通過以下步驟來實現:

  • 您至少需要在本地計算機上執行Kubernetes(Minikube)或Openshift(Minishift)的單節點叢集例項。您應該啟動它,並公開它們提供的嵌入式Docker客戶端。在OpenShift上部署Java應用程式的快速指南
  • Spring Cloud Kubernetes要求訪問Kubernetes API,以便能夠檢索為單個服務執行的Pod的地址列表。如果您使用Kubernetes,則應該執行以下命令:

    $ kubectl create clusterrolebinding admin --clusterrole=cluster-admin --serviceaccount=default:default
    

1. 使用配置對映和secrets注入配置

使用Spring Cloud時,在系統中實現分散式配置的最明顯選擇是Spring Cloud Config;而使用Kubernetes,您可以使用Config Map。它包含可在Pod中使用或用於儲存配置資料的配置資料的鍵值對。它用於儲存和共享非敏感,未加密的配置資訊。要在群集中使用敏感資訊,必須使用Secrets。根據MongoDB連線設定的示例,可以完美地演示這兩個Kubernetes物件的使用。在Spring Boot應用程式內部,我們可以使用環境變數輕鬆地將其注入。這是application.yml 帶有URI配置的檔案片段。

spring:
  data:
    mongodb:
      uri: mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@mongodb/${MONGO_DATABASE}

儘管使用者名稱和密碼是敏感欄位,但資料庫名稱不是,因此我們可以將其放在配置對映中。

apiVersion: v1
kind: ConfigMap
metadata:
  name: mongodb
data:
  database-name: microservices

當然,使用者名稱和密碼被定義為機密secret型別。

apiVersion: v1
kind: Secret
metadata:
  name: mongodb
type: Opaque
data:
  database-password: MTIzNDU2
  database-user: cGlvdHI=

要將配置應用於Kubernetes叢集,我們執行以下命令。

$ kubectl apply -f kubernetes/mongodb-configmap.yaml
$ kubectl apply -f kubernetes/mongodb-secret.yaml

然後我們應該將配置屬性注入到應用程式的pod中。在Deployment YAML檔案中定義容器配置時,我們必須包括對環境變數和機密secret的引用,如下所示。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: employee
  labels:
    app: employee
spec:
  replicas: 1
  selector:
    matchLabels:
      app: employee
  template:
    metadata:
      labels:
        app: employee
    spec:
      containers:
      - name: employee
        image: piomin/employee:1.0
        ports:
        - containerPort: 8080
        env:
        - name: MONGO_DATABASE
          valueFrom:
            configMapKeyRef:
              name: mongodb
              key: database-name
        - name: MONGO_USERNAME
          valueFrom:
            secretKeyRef:
              name: mongodb
              key: database-user
        - name: MONGO_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mongodb
              key: database-password

2.使用Kubernetes構建服務發現

我們通常使用Docker容器在Kubernetes上執行微服務。一個或多個容器按Pod分組,Pod是在Kubernetes中建立和管理的最小的可部署單元。一個好的做法是在一個pod中只執行一個容器。如果您想擴充套件微服務,則只需增加正在執行的Pod的數量即可。屬於單個微服務的所有正在執行的Pod在邏輯上都與Kubernetes Service進行了分組。該服務在叢集外部可能是可見的,並且能夠在所有正在執行的Pod之間負載平衡傳入的請求。以下服務定義將標有欄位app等於的所有Pod分組為employee。

apiVersion: v1
kind: Service
metadata:
  name: employee
  labels:
    app: employee
spec:
  ports:
  - port: 8080
    protocol: TCP
  selector:
    app: employee

服務Service型別可用於訪問Kubernetes叢集外部的應用程式或叢集內部的服務間通訊。但是,使用Spring Cloud Kubernetes可以更輕鬆地實現微服務之間的通訊。首先,我們需要在專案中包括以下依賴項pom.xml

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
<version>0.3.0.BUILD-SNAPSHOT</version>
</dependency>

然後,我們應該為應用程式啟用發現客戶端,就像我們在Spring Cloud Netflix Eureka中為發現所做的一樣。這使您可以按名稱查詢Kubernetes端點(服務)。Spring Cloud Kubernetes Ribbon或Zipkin專案還使用此發現功能分別獲取為要進行負載平衡的微服務定義的Pod列表,或可用於傳送跟蹤或跨度的Zipkin伺服器。

@SpringBootApplication
@EnableDiscoveryClient
@EnableMongoRepositories
@EnableSwagger2
public class EmployeeApplication {
 public static void main(String[] args) {
  SpringApplication.run(EmployeeApplication.class, args);
 }
 // ...
}

本部分的最後一件事是確保Spring應用程式名稱與該應用程式的Kubernetes服務Service名稱完全相同。對於應用程式employee-service,它是employee。

spring:
  application:
    name: employee

3.使用Docker構建微服務並在Kubernetes上部署

可包括一些標準的Spring依賴關係,用於構建基於REST的微服務,與MongoDB整合以及使用Swagger2生成API文件。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

為了與MongoDB整合,我們應該建立一個擴充套件標準Spring Data的介面CrudRepository

public interface EmployeeRepository extends CrudRepository {
 List findByDepartmentId(Long departmentId);
 List findByOrganizationId(Long organizationId);
}

實體類應使用Mongo註釋@Document,主鍵欄位應使用 @Id。

@Document(collection = "employee")
public class Employee {
 @Id
 private String id;
 private Long organizationId;
 private Long departmentId;
 private String name;
 private int age;
 private String position;
 // ...
}

儲存庫bean已注入到控制器類中。這是員工服務內部REST API的完整實現。

@RestController
public class EmployeeController {
 private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeController.class);
 @Autowired
 EmployeeRepository repository;
 @PostMapping("/")
 public Employee add(@RequestBody Employee employee) {
  LOGGER.info("Employee add: {}", employee);
  return repository.save(employee);
 }
 @GetMapping("/{id}")
 public Employee findById(@PathVariable("id") String id) {
  LOGGER.info("Employee find: id={}", id);
  return repository.findById(id).get();
 }
 @GetMapping("/")
 public Iterable findAll() {
  LOGGER.info("Employee find");
  return repository.findAll();
 }
 @GetMapping("/department/{departmentId}")
 public List findByDepartment(@PathVariable("departmentId") Long departmentId) {
  LOGGER.info("Employee find: departmentId={}", departmentId);
  return repository.findByDepartmentId(departmentId);
 }
 @GetMapping("/organization/{organizationId}")
 public List findByOrganization(@PathVariable("organizationId") Long organizationId) {
  LOGGER.info("Employee find: organizationId={}", organizationId);
  return repository.findByOrganizationId(organizationId);
 }
}

為了在Kubernetes上執行我們的微服務,我們應該首先使用Maven來構建整個專案:

mvn clean install 

每個微服務在根目錄中都有一個Dockerfile。這是employee-service的Dockerfile定義。

FROM openjdk:8-jre-alpine
ENV APP_FILE employee-service-1.0-SNAPSHOT.jar
ENV APP_HOME /usr/apps
EXPOSE 8080
COPY target/$APP_FILE $APP_HOME/
WORKDIR $APP_HOME
ENTRYPOINT ["sh", "-c"]
CMD ["exec java -jar $APP_FILE"]

讓我們為所有三個示例微服務構建Docker映像:

$ cd employee-service
$ docker build -t piomin/employee:1.0 .
$ cd department-service
$ docker build -t piomin/department:1.0 .
$ cd organization-service
$ docker build -t piomin/organization:1.0 .

最後一步是在Kubernetes上將Docker容器與應用程式一起部署。為此,只需基於YAML配置檔案上執行命令kubectl apply。employee-service已經在步驟1中演示了。所有必需的部署欄位在儲存庫中的 kubernetes目錄中找到。

$ kubectl apply -f kubernetes\employee-deployment.yaml
$ kubectl apply -f kubernetes\department-deployment.yaml
$ kubectl apply -f kubernetes\organization-deployment.yaml

4.使用Spring Cloud Kubernetes Ribbon進行微服務之間的通訊

所有微服務都部署在Kubernetes上。現在,有必要討論與服務間通訊有關的某些方面。employee-service與其他微服務相反,該應用程式未呼叫任何其他微服務。讓我們看一下其他微服務,這些微服務呼叫了員工服務公開的API,並且彼此之間進行通訊(organization-service 呼叫 department-service API)。

首先,我們需要在專案中包括一些其他依賴項。我們使用Spring Cloud Ribbon和OpenFeign。另外,您也可以使用Spring @LoadBalancedRestTemplate。

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
<version>0.3.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

這是department-service的主要類。它通過使用@EnableFeignClients註釋作為Feign客戶端。它的工作原理與基於Spring Cloud Netflix Eureka的發現相同。OpenFeign使用Ribbon進行客戶端負載平衡。Spring Cloud Kubernetes Ribbon提供了一些bean,它們迫使Ribbon通過Fabric8 KubernetesClient與Kubernetes API進行通訊。

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableMongoRepositories
@EnableSwagger2
public class DepartmentApplication {
 public static void main(String[] args) {
  SpringApplication.run(DepartmentApplication.class, args);
 }
 // ...
}

這是Feign客戶端的實現,用於呼叫employee-service公開的方法。

@FeignClient(name = "employee")
public interface EmployeeClient {
 @GetMapping("/department/{departmentId}")
 List findByDepartment(@PathVariable("departmentId") String departmentId);
}

最後,我們必須將Feign客戶的Bean注入REST控制器。現在,我們可以呼叫EmployeeClient內部定義的方法,呼叫該方法等效於呼叫REST端點。

@RestController
public class DepartmentController {
 private static final Logger LOGGER = LoggerFactory.getLogger(DepartmentController.class);
 @Autowired
 DepartmentRepository repository;
 @Autowired
 EmployeeClient employeeClient;
 // ...
 @GetMapping("/organization/{organizationId}/with-employees")
 public List findByOrganizationWithEmployees(@PathVariable("organizationId") Long organizationId) {
  LOGGER.info("Department find: organizationId={}", organizationId);
  List departments = repository.findByOrganizationId(organizationId);
  departments.forEach(d -> d.setEmployees(employeeClient.findByDepartment(d.getId())));
  return departments;
 }
}

5.使用Kubernetes Ingress入口構建API閘道器

入口Ingress是一組規則,這些規則允許傳入的請求能夠訪問到達下游服務。在我們的微服務架構中,入口扮演著API閘道器的角色。要建立它,我們首先應該準備一個YAML描述檔案。描述符檔案應包含主機名,閘道器將在該主機名下可用,並將規則對映到下游服務。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: gateway-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  backend:
    serviceName: default-http-backend
    servicePort: 80
  rules:
  - host: microservices.info
    http:
      paths:
      - path: /employee
        backend:
          serviceName: employee
          servicePort: 8080
      - path: /department
        backend:
          serviceName: department
          servicePort: 8080
      - path: /organization
        backend:
          serviceName: organization
          servicePort: 8080

必須執行下面命令將以上配置應用於所有kubernetes叢集:

$ kubectl apply -f kubernetes\ingress.yaml

要在本地測試該解決方案,我們必須在主機檔案內的入口定義中設定的IP地址和主機名之間插入對映。之後,我們可以使用定義的主機名通過入口測試服務,如下所示:http://microservices.info/employee。

192.168.99.100 microservices.info

 執行命令 kubectl describe ing gateway-ingress可檢查ingress細節。

6.使用Swagger2在閘道器上啟用API規範

如果我們想為Kubernetes上部署的所有微服務公開一個Swagger文件怎麼辦?好吧,這裡的事情變得複雜了……我們可以使用Swagger UI執行一個容器,並手動對映入口暴露的所有路徑,但這不是一個好的解決方案……

在這種情況下,我們可以再次使用Spring Cloud Kubernetes Ribbon,這一次可以與Spring Cloud Netflix Zuul一起使用。Zuul僅充當服務於Swagger API的閘道器。這是我的gateway-service專案中使用的依賴項列表。

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
<version>0.3.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
<version>0.3.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>

Kubernetes發現客戶端將檢測群集上公開的所有服務。我們只想顯示三個微服務的文件。這就是為什麼我為Zuul定義以下路由。

zuul:
  routes:
    department:
      path: /department/**
    employee:
      path: /employee/**
    organization:
      path: /organization/**

現在我們可以使用 ZuulPropertiesBean從Kubernetes發現中獲取路由的地址,並將其配置為Swagger資源,如下所示:

@Configuration
public class GatewayApi {
 @Autowired
 ZuulProperties properties;
 @Primary
 @Bean
 public SwaggerResourcesProvider swaggerResourcesProvider() {
  return () -> {
   List resources = new ArrayList();
   properties.getRoutes().values().stream()
   .forEach(route -> resources.add(createResource(route.getId(), "2.0")));
   return resources;
  };
 }
 private SwaggerResource createResource(String location, String version) {
  SwaggerResource swaggerResource = new SwaggerResource();
  swaggerResource.setName(location);
  swaggerResource.setLocation("/" + location + "/v2/api-docs");
  swaggerResource.setSwaggerVersion(version);
  return swaggerResource;
 }
}

應用程式閘道器服務應與其他應用程式一樣部署在群集上。您可以通過執行命令kubectl get svc來檢視正在執行的服務的列表。Swagger文件可從以下地址獲得:http://192.168.99.100:31237/swagger-ui.html。

 

相關文章