@levinzhang
2020-04-12T13:35:43.000000Z
字数 8052
阅读 667
by
在本教程中,读者将有机会创建一个小型的Spring Boot应用,使用Skaffold和Cloud Code IntelliJ插件将其容器化并部署至Google Kubernetes Engine。
随着微服务在我们行业中的日益普及,在如何构建应用方面,相关的技术和平台得到了繁荣发展。有时候,开始该选用哪些技术是很难抉择的。在本文中,我将会向你展示如何创建一个基于Spring Boot的应用,该应用会用到Google Cloud所提供的一些服务。这些方法我们在Google已经用了很长时间了。我希望它对你有用。
我们首先来定义一下要做些什么。我们会从一个非常基础的基于Spring Boot的Java应用开始。Spring是一个成熟的框架,借助它,我们能够快速创建强大且特性丰富的应用。
随后,我们会对其进行一些修改,使用Jib(能够在不使用Docker的情况下,为Java应用构建优化的Docker和OCI镜像)和distroless版本的Java 11容器化该应用。Jib能够与Maven和Gradle协作,在样例中我们会使用Maven。
接下来,我们会创建一个Google Cloud Platform(GCP)项目,并借助Spring Cloud GCP来使用Cloud Firestore的功能。Spring Cloud GCP允许基于Spring的应用便利地消费Google服务,如数据库(Cloud Firestore、Cloud Spanner甚至Cloud SQL)、Google Cloud Pub/Sub以及用于日志和跟踪的Stackdriver等。
在此之后,我们将会对应用做一些修改,将其部署到Google Kubernetes Engine(GKE)。GKE是一个托管的、生产就绪的环境,用于部署容器化的、基于Kubernetes的应用。
最后,我们将会使用Skaffold和Cloud Code让部署过程更简便。Skaffold会处理构建、推送和部署应用的工作流。Cloud Code是一个适用于VS Code和IntelliJ的插件,它可以与Skaffold和IDE协作,这样的话,你只需要点击一个按钮就可以部署至GKE。在本文中,我们将会组合使用IntelliJ和Cloud Code。
在编写代码之前,我们首先确保有一个Google Cloud项目并且所有的工具都已经安装就绪。
搭建GCP实例非常容易。你可以按照这些指南完成该过程。这个新的项目允许我们将应用部署到GKE和访问数据库(Cloud Firestore),随后当我们容器化应用的时候,它还提供了一个让我们推送镜像的地方。
接下来,我们要安装Cloud Code。你可以按照这些指南学习如何将Cloud Code安装到IntelliJ中。Cloud Code会管理Skaffold的Google SDK安装,在本文后续的内容中,我们将会用到它们。Cloud Code还允许我们探查GKE deployment和 service。最重要的是,它还有一个很聪明的GKE开发模式,它会持续监听代码的变化,当它探测到变化时,就会构建应用、构建镜像、推送镜像到仓库、部署应用到GKE集群、启动流式日志并打开localhost通道,这样的话就可以在本地测试应用了。这个过程就像魔法一样!
为了使用Cloud Code继续处理我们的应用,首先确保你已经使用Cloud Code插件进行了登录,这可以通过点击IntelliJ窗口右上角的图标来实现:
除此之外,我们还会运行一些命令确保应用可以在你的机器上运行并且能够与Google Cloud上项目所需的服务进行通信。我们要确保指向了正确的项目并进行认证:
gcloud config set project <YOUR PROJECT ID> gcloud auth login
接下来,需要确保你的机器具有应用凭证(application credential),以便于在本地运行应用:
gcloud auth application-default login
现在,所有的准备工作都已经搭建完成,我们接下来需要启用应用中所使用的API:
通过访问项目的API Dashboard,你可以管理项目中已经启用的API。
要事优先!我们需要从一个可以本地运行的简单应用开始。我们将会创建一个Dog微服务。因为我使用的是IntelliJ Ultimate,所以可以依次访问“File -> New -> Project…”并选择“Spring Initializr”。在这里,我会选择Maven、Jar和Java 11,并且会将其命名为dog
,如下所示:
点击next并添加:Lombok、Spring Web和GCP Support,如下所示:
如果一切正常的话,我们应该会得到一个可以运行的应用。如果你不想使用IntelliJ来完成该步骤的话,那么可以使用你的IDE的对等功能或者直接使用Spring的Initilizr。
接下来,我们要为Dog服务添加一个POJO以及几个REST端点,以便于测试我们的应用。我们的Dog对象会有name和age属性,在这里,我们会使用Lombok的@Data注解来标注它们,这样就不用为它们编写setter和getter方法了。另外,我们还会使用@AllArgsConstructor注解,这样Lombok会为我们创建一个构造器。在稍后创建Dog的时候,我们将会使用该构造器。
@Data @AllArgsConstructor public class Dog { private String name; private int age; }
接下来,我们要为Dog创建控制器类和REST端点:
@RestController @Slf4j public class DogController { @GetMapping("/api/v1/dogs") public List<Dog> getAllDogs() { log.debug("->getAllDogs"); return ImmutableList.of(new Dog("Fluffy", 5), new Dog("Bob", 6), new Dog("Cupcake", 11)); } @PostMapping("/api/v1/dogs") public Dog saveDog(@RequestBody Dog dog) { log.debug("->saveDog {}", dog); return dog; } }
端点会返回预先定义好的dog列表,saveDog其实没有实现什么功能,但是作为起步来讲,这已经足够了。
现在,我们已经有了一个应用的骨架,接下来,我们会尝试使用GCP的一些服务。Spring Cloud GCP为Datastore模式的Google Cloud Firestore添加了Spring Data的支持。我们会使用该特性来存储Dog,而不是使用简单的列表。这样,用户也可以真正将Dog保存到数据库中。
首先,我们需要将Spring Cloud GCP Data Datastore依赖添加到POM文件中:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-gcp-data-datastore</artifactId> </dependency>
现在,我们需要修改Dog,使其能够进行存储。我们将会添加一个@Entity注解,并为Long值类型添加@Id注解,使其作为实体的标识符:
@Entity @Data @AllArgsConstructor public class Dog { @Id private Long id; private String name; private int age; }
随后,我们可以创建一个常规的Spring Repository类,如下所示:
@Repository public interface DogRepository extends DatastoreRepository<Dog, Long> {}
和一般的Spring Repository类似,这里没有必要编写接口的实现,因为我们只会使用非常基础的方法。
现在,可以修改控制器类了,我们会将DogRepository注入到 DogController中,然后修改这个类并按照如下的方式使用repository:
@RestController @Slf4j @RequiredArgsConstructor public class DogController { private final DogRepository dogRepository; @GetMapping("/api/v1/dogs") public Iterable<Dog> getAllDogs() { log.debug("->getAllDogs"); return dogRepository.findAll(); } @PostMapping("/api/v1/dogs") public Dog saveDog(@RequestBody Dog dog) { log.debug("->saveDog {}", dog); return dogRepository.save(dog); } }
注意,我们使用了Lombok的@RequiredArgsConstructor注解来创建构造器,以便于注入DogRepository。在运行应用的时候,端点会调用Dog服务,进而会尝试使用Cloud Firestore来检索和存储Dog。
小提示:要快速对其进行测试的话,我们可以在IntelliJ中使用如下的内容创建HTTP请求:
POST http://localhost:8080/api/v1/dogs Content-Type: application/json { "name": "bob", "age": 5 }
只需简单的几步,我们就将应用运行了起来,并且能够消费来自GCP的服务。太棒了!现在,我们将其变成一个容器并部署它。
现在,我们可以编写Dockerfile文件来容器化上述的应用。但是,我们采用一个不同的方案,也就是Jib。Jib有一点我非常喜欢,那就是它会把应用程序分割为多个层,将依赖从类中分离出来。这样做的好处在于,我们会有更快的构建速度,这样不必等待Docker重新构建整个Java应用,而是只部署那些发生了变化的层。除此之外,Jib还有一个Maven插件,我们只需修改应用的POM文件就可以轻松将其搭建起来。
要开始使用该插件,我们需要修改POM文件并添加如下的内容:
<plugin> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-maven-plugin</artifactId> <version>1.8.0</version> <configuration> <from> <image>gcr.io/distroless/java:11</image> </from> <to> <image>gcr.io/<YOUR_GCP_REGISTRY>/${project.artifactId}</image> </to> </configuration> </plugin>
注意,我们使用了Google的Java 11 distroless镜像。“Distroless”镜像只包含了你的应用及其运行时依赖。它们不包含包管理器、shell或其他在标准Linux发行版中会存在的其他程序。
我们限制运行时容器中所包含的内容恰好是应用所需,这是Google和其他在生产环境使用容器多年的其他技术巨头的最佳实践。它会改善扫描器(如CVE)的信噪比并降低确定问题出处的负担。
记住,需要将上述代码替换为你的GCP registry并匹配项目的名称。
完成之后,就可以尝试通过如下的命令构建并推送应用的镜像了:
$ ./mvnw install jib:build
运行该命令会构建并测试应用,创建镜像并最终将新创建的镜像推送至你的registry。
注意:通常来讲,使用distro和一个特定的摘要,而不是使用“latest”是一个好的实践。我将决定权留给读者,让你们根据所使用的Java版本自行确定该使用哪个基础镜像和摘要。
到这里,我们的应用几乎就可以部署了。为了实现这一点,我们先创建一个GKE集群,应用将会部署到这里面。
关于如何创建GKE集群,可以参考这些指南。基本上来讲,你需要访问GKE页面,等待API启用,然后点击按钮来创建集群。你可以使用默认的配置,但是要确保点击“More options”按钮,并启用对Cloud API的完全访问:
这将允许你的GKE节点具有访问所有其他Google Cloud服务的权限。稍等片刻之后,集群就能创建出来了:
Kubernetes会希望监控你的应用,从而确保应用处于正常运行的状态。在出现故障的时候,Kubernetes能够知道应用停机了,因此它会启动一个新的实例。为了实现这一点,我们需要确保当Kubernetes问询应用程序的时候,应用能够给出响应。我们需要添加一个actuator和Spring Cloud Kubernetes。
添加如下的依赖到POM文件中:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
如果你的应用在src/main/resources目录下存在application.properties文件的话,请删除掉它,并创建一个包含如下内容的application.yaml文件:
spring: application: name: dog-service management: endpoint: health: enabled: true
这样会给我们的应用一个名称,并且会暴露前文所述的健康端点。为了校验它是否正常运行,你可以访问应用的localhost:8080/actuator/health地址。你将会看到如下的响应:
{ "status": "UP" }
为了将应用部署到新GKE集群中,我们编写一些额外的YAML,以便于创建deployment和service。我们可以使用如下的deployment,但是需要记住要将GCR改成你自己项目的名称:
deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: dog-service spec: selector: matchLabels: app: dog-service replicas: 1 template: metadata: labels: app: dog-service spec: containers: - name: dog-service image: gcr.io/<YOUR GCR REGISTRY NAME>/dog ports: - containerPort: 8080 livenessProbe: initialDelaySeconds: 20 httpGet: port: 8080 path: /actuator/health readinessProbe: initialDelaySeconds: 30 httpGet: port: 8080 path: /actuator/health
添加一个具有如下内容的service.yaml文件:
apiVersion: v1 kind: Service metadata: name: dog-service spec: type: NodePort selector: app: dog-service ports: - port: 8080 targetPort: 8080
deployment包含了几个针对就绪状态和存活状态探针的修改。Kubernetes会使用这些端点来询问应用是否处于活跃状态。service暴露了deployment,这样其他的服务就可以消费它了。
完成之后,我们现在就可以使用在本文开始时安装的Cloud Code插件进行启动了。通过Tools菜单,选择Cloud Code -> Kubernetes -> Add Kubernetes Support。这将会自动向你的应用中添加Skaffold YAML并且会搭建一些内容,这样的话,我们只需点击一个按钮就可以将其部署到集群中了。要确认所有的这些都能正常运行,我们可以通过IntelliJ的Run/Debug Configurations探查配置。如果点击Develop on Kubernetes话,它会自动获取你的GKE和Kubernetes配置文件,如下所示:
点击Ok按钮,然后点击右上角的绿色"Play"按钮:
随后,Cloud Code会自动构建应用、创建镜像、部署应用到GKE集群并将Stackdriver的日志转发到本地机器上。它还会打开一个通道,这样的话,我们就可以通过 localhost:8080来消费该服务。你还可以在Google Cloud Console的工作负载页面查看:
祝贺你完成到了这一步!我们在本文中构建的应用展现了大多数基于微服务的应用都会用到的一些核心技术:快速的、完全托管的、serverless的、云原生NoSQL文档数据库(Cloud Firestore),以及GKE,这是一个用于部署基于Kubernetes的容器化应用的托管的、生产就绪的环境,最后我们使用Spring Boot构建了一个云原生微服务。在这个过程中,我们还学习了如何通过常见的Java模式,使用Cloud Code等工具来简化开发工作流,并使用Jib来构建容器化应用。
希望这篇文章对你有用,并且能尝试一下这些技术。如果你觉得这很有意思的话,可以看一下Google提供的codelabs,通过它们你可以学习Spring和Google Cloud产品。
Sergio Feilx是Google Cloud的一名软件工程师,他就职于Cloud Engineering Productivity团队,这是Google Cloud中的一个组织,致力于让开发更加流畅并提升产品和工程质量。
查看英文原文:Spring Boot Tutorial: Building Microservices Deployed to Google Cloud