将通过 Jib 容器化的 Micronaut 应用部署到 Google Kubernetes Engine

1. 概览

Micronaut 简介

Micronaut 是一款基于 JVM 的现代全栈框架,用于构建模块化、易于测试的微服务和无服务器应用。Micronaut 旨在以最小的内存占用量实现出色的启动时间和快速的吞吐量。开发者可以使用 Java、Groovy 或 Kotlin 通过 Micronaut 进行开发。

Micronaut 提供:

  • 启动时间短,内存消耗低 - 基于反射的 IoC 框架会加载并缓存代码中每个字段、方法和构造函数的反射数据,而使用 Micronaut 时,应用启动时间和内存消耗不受代码库大小的限制。
  • 声明式、响应式、编译时 HTTP 客户端 - 以声明方式构建响应式 HTTP 客户端,这些客户端在编译时实现,可减少内存消耗。
  • 基于 Netty 构建的非阻塞 HTTP 服务器 - Micronaut 的 HTTP 服务器具有平缓的学习曲线,可让您尽可能轻松地公开可供 HTTP 客户端使用的 API。
  • 快速轻松的测试 - 在单元测试中轻松启动服务器和客户端,并立即运行。
  • 高效的编译时依赖注入和 AOP - Micronaut 提供了一个不使用反射的简单编译时面向方面的编程 API。
  • 构建完全反应式且非阻塞的应用 - Micronaut 支持任何实现 Reactive Streams 的框架,包括 RxJava 和 Reactor。

如需了解详情,请访问 Micronaut 网站

关于 Kubernetes

Kubernetes 是一个开源项目,可在从笔记本电脑到高可用性多节点集群、从公有云到本地部署以及从虚拟机到裸金属等众多不同环境中运行。

在本实验中,您将一个基于 Groovy 的简单 Micronaut 微服务部署到在 Kubernetes Engine 上运行的 Kubernetes

此 Codelab 的目标是让您以在 Kubernetes 上运行的复制服务的形式运行微服务。您需要将在机器上开发的代码转换为 Docker 容器映像,然后在 Kubernetes Engine 上运行该映像。

下图展示了此 Codelab 中涉及的各个部分,可帮助您了解这些部分如何组合在一起。在学习此 Codelab 的过程中,您可以参考此图;当您完成此 Codelab 时,您应该能够理解此图中的所有内容(但您现在可以忽略此图)。

Kubernetes Codelab 图 1 (2).png

在本 Codelab 中,借助 Kubernetes Engine(在 Compute Engine 上运行的 Google 托管式 Kubernetes 版本)这样的托管式环境,您可以把更多精力放在体验 Kubernetes 上,而不必分心去设置底层基础设施。

如果您有兴趣在本地机器(如开发笔记本电脑)上运行 Kubernetes,则应考虑了解 Minikube。它可为开发和测试目的提供单节点 Kubernetes 集群的简单设置。如果需要,您可以使用 Minikube 完成此 Codelab。

关于 Jib

Jib 是一款开源工具,可让您为 Java 应用构建 Docker 和 OCI 映像。它以 Maven 和 Gradle 插件的形式提供,也可以作为 Java 库使用。

Jib 旨在实现以下目标:

  • 快速 - 快速部署更改。Jib 会将应用拆分为多个层,将依赖项与类分开。现在,您不必等待 Docker 重建整个 Java 应用,只需部署已更改的层即可。
  • 可重现 - 使用相同内容重新构建容器映像始终会生成相同的映像。再也不会触发不必要的更新。
  • 无守护程序 - 减少 CLI 依赖项。在 Maven 或 Gradle 中构建 Docker 映像,并推送到您选择的任何注册中心。无需再编写 Dockerfile 并调用 docker build/push。

您可以在 GitHub 项目页面上详细了解 Jib。

关于本教程

本教程使用 Jib 工具中的示例代码来为 Java 应用构建容器。

此示例是一个简单的 hello world 服务,使用 Micronaut 框架和 Apache Groovy 编程语言。

学习内容

  • 如何使用 Jib 将简单的 Java 应用打包为 Docker 容器
  • 如何在 Kubernetes Engine 上创建 Kubernetes 集群。
  • 如何将 Micronaut 服务部署到 Kubernetes Engine 上的 Kubernetes 中
  • 如何扩容服务并发布升级。
  • 如何访问 Kubernetes 图形信息中心。

所需条件

  • 一个 Google Cloud Platform 项目
  • 一个浏览器,例如 ChromeFirefox
  • 熟悉标准的 Linux 文本编辑器,例如 Vim、EMACs 或 Nano

您打算如何使用本教程?

仅阅读教程内容 阅读并完成练习

您如何评价自己在构建 HTML/CSS Web 应用方面的经验水平?

新手水平 中等水平 熟练水平

您如何评价自己在使用 Google Cloud Platform 服务方面的经验水平?

新手水平 中等水平 熟练水平

2. 设置和要求

自定进度的环境设置

  1. 登录 Cloud 控制台,然后创建一个新项目或重复使用现有项目。 (如果您还没有 Gmail 或 G Suite 账号,则必须创建一个。)

dMbN6g9RawQj_VXCSYpdYncY-DbaRzr2GbnwoV7jFf1u3avxJtmGPmKpMYgiaMH-qu80a_NJ9p2IIXFppYk8x3wyymZXavjglNLJJhuXieCem56H30hwXtd8PvXGpXJO9gEUDu3cZw

ci9Oe6PgnbNuSYlMyvbXF1JdQyiHoEgnhl4PlV_MFagm2ppzhueRkqX4eLjJllZco_2zCp0V0bpTupUSKji9KkQyWqj11pqit1K1faS1V6aFxLGQdkuzGp4rsQTan7F01iePL5DtqQ

8-tA_Lheyo8SscAVKrGii2coplQp2_D1Iosb2ViABY0UUO1A8cimXUu6Wf1R9zJIRExL5OB2j946aIiFtyKTzxDcNnuznmR45vZ2HMoK3o67jxuoUJCAnqvEX6NgPGFjCVNgASc-lg

请记住项目 ID,它在所有 Google Cloud 项目中都是唯一的名称(上述名称已被占用,您无法使用,抱歉!)。它稍后将在此 Codelab 中被称为 PROJECT_ID

  1. 接下来,您需要在 Cloud 控制台中启用结算功能,才能使用 Google Cloud 资源。

运行此 Codelab 应该不会产生太多的费用(如果有费用的话)。请务必按照“清理”部分中的所有说明操作,该部分介绍了如何关停资源,以免产生超出本教程范围的结算费用。Google Cloud 的新用户符合参与 $300 USD 免费试用计划的条件。

3. 获取 Micronaut 示例源代码

在 Cloud Shell 启动后,您可以使用命令行在主目录中克隆示例源代码,然后使用 cd 命令进入包含示例服务的目录:

$ git clone https://github.com/GoogleContainerTools/jib.git
$ cd jib/examples/micronaut/

4. 快速查看代码

我们的 Micronaut 简单服务由一个控制器组成,该控制器会输出著名的 Hello World 消息:

@Controller("/hello")
class HelloController {
    @Get("/")
    String index() {
        "Hello World"
    }
}

HelloController 控制器正在响应 /hello 路径下的请求,而 index() 方法接受 HTTP GET 请求。

我们还提供了一个 Spock 测试类,用于检查输出中是否显示了正确的消息。

class HelloControllerSpec extends Specification {
    @Shared
    @AutoCleanup
    EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)

    @Shared
    @AutoCleanup
    RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL()) 

    void "test hello world response"() {
        when:
        HttpRequest request = HttpRequest.GET('/hello')
        String rsp  = client.toBlocking().retrieve(request)

        then:
        rsp == "Hello World"
    }
}

此测试不仅仅是一个简单的单元测试,它实际上运行的是与生产环境中相同的 Micronaut 服务器堆栈(基于 Netty 框架)。因此,代码在产品中的行为与在测试中的行为完全相同。

如需运行测试,您可以运行以下命令,看看一切是否正常:

./gradlew test

5. 在本地运行应用

您可以使用以下 Gradle 命令正常启动 Micronaut 服务:

$ ./gradlew run

应用启动后,您可以点击小加号图标打开额外的 Cloud Shell 实例,然后使用 curl 检查是否获得了预期输出:

$ curl localhost:8080/hello

您应该会看到一条简单的“Hello World”消息。

6. 使用 Jib 将应用打包为 Docker 容器

接下来,准备好应用以便在 Kubernetes 上运行。为此,我们将利用 Jib 为我们完成繁重的工作,因为我们自己不必接触 Dockerfile

我们来运行命令以构建容器:

$ ./gradlew jibDockerBuild

您应该会看到以下输出:

Tagging image with generated image reference micronaut-jib:0.1. If you'd like to specify a different tag, you can set the jib.to.image parameter in your build.gradle, or use the --im
age=<MY IMAGE> commandline flag.

Containerizing application to Docker daemon as micronaut-jib:0.1...
warning: Base image 'gcr.io/distroless/java' does not use a specific image digest - build may not be reproducible
Getting base image gcr.io/distroless/java...
Building dependencies layer...
Building resources layer...
Building classes layer...
Finalizing...

Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, example.micronaut.Application]
Loading to Docker daemon...

Built image to Docker daemon as micronaut-jib:0.1

现在,我们的映像已构建完成,接下来在 Cloud Shell 的第一个标签页中运行 Docker 映像,检查是否能看到友好的问候消息:

$ docker run -it -p 8080:8080 micronaut-jib:0.1
16:57:20.255 [main] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [cloud, gcp]
16:57:23.203 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 2926ms. Server Running: http://97b7d76ccf3f:8080

我们的服务正在运行,因此我们现在可以在第二个 Cloud Shell 标签页中启动 curl 命令,看看它是否按预期运行:

$ curl localhost:8080/hello
Hello World

您可以在 Cloud Shell 中按 Ctrl+C 停止容器。

7. 将容器化服务推送到注册表

现在,映像已按预期运行,您可以将其推送到 Google Container Registry,这是一个私有代码库,用于存储您的 Docker 映像,可从每个 Google Cloud 项目(也可从 Google Cloud Platform 外部)访问。

在能够推送到注册表之前,我们先前往工具 > Container Registry,确保已为我们的项目启用 Container Registry。如果未启用,您应该会看到以下对话框,然后请点击“启用 Container Registry API”以启用它:

ac812e6260ac7dfb.png

注册表准备就绪后,如需将映像推送到注册表,请运行以下命令:

$ gcloud auth configure-docker
$ docker tag micronaut-jib:0.1 \
         gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1
$ docker push gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1

上述命令可让 gcloud SDK 配置并授权 Docker 将映像推送到您的 Container Registry 实例,将映像标记为指向其在注册表中的位置,然后将其推送到注册表。

如果一切顺利,过一会儿您应该就能在控制台中看到列出的容器映像:工具 > Container Registry。现在,您拥有一个适用于整个项目的 Docker 映像,Kubernetes 可对其进行访问和编排(几分钟后您便会看到)。

12224c4e42183b4e.png

8. 创建集群

现在,您可以创建 Kubernetes Engine 集群了,但在此之前,请前往 Web 控制台的 Google Kubernetes Engine 部分,等待系统初始化(只需几秒钟)。

20c0587c0108b8ba.png

集群由 Google 管理的 Kubernetes 主 API 服务器和一组工作器节点组成。工作器节点是 Compute Engine 虚拟机。我们来使用 CloudShell 会话中的 gcloud CLI 创建一个包含两个 n1-standard-1 节点的集群(此过程需要几分钟才能完成):

$ gcloud container clusters create hello-cluster \
  --num-nodes 2 \
  --machine-type n1-standard-1 \
  --zone us-central1-c

最后,您应该会看到创建的集群。

Creating cluster hello-cluster in us-central1-c...done.
Created [https://container.googleapis.com/v1/projects/mn-gke-test/zones/us-central1-c/clusters/hello-cluster].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-c/hello-cluster?project=mn-gke-test
kubeconfig entry generated for hello-cluster.
NAME           LOCATION       MASTER_VERSION  MASTER_IP       MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
hello-cluster  us-central1-c  1.9.7-gke.7     35.239.224.115  n1-standard-1  1.9.7-gke.7   2          RUNNING

现在,您应该拥有一个由 Google Kubernetes Engine 提供支持且功能齐全的 Kubernetes 集群:

d9e1e314769753e7.png

现在是时候将您自己的容器化应用部署到 Kubernetes 集群了!从现在开始,您将使用 kubectl 命令行(已在 Cloud Shell 环境中设置)。此 Codelab 的其余部分要求 Kubernetes 客户端和服务器版本均为 1.2 或更高版本。kubectl version 将显示该命令的当前版本。

9. 将应用部署到 Kubernetes

Kubernetes Deployment 可以使用您刚刚创建的容器映像创建、管理和扩缩应用的多个实例。我们来使用 kubectl create deployment 命令在 Kubernetes 中创建应用的部署:

$ kubectl create deployment hello-micronaut \
  --image=gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1

如需查看您刚刚创建的部署,只需运行以下命令:

$ kubectl get deployments
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-micronaut   1         1         1            1           5m

如需查看该部署创建的应用实例,请运行以下命令:

$ kubectl get pods
NAME                               READY     STATUS    RESTARTS   AGE
hello-micronaut-5647fb98c5-lh5h7   1/1       Running   0          5m

此时,您应该让容器在 Kubernetes 的控制下运行,但您仍须允许外部世界访问该容器。

10. 允许外部流量

默认情况下,只能通过 pod 在集群内的内部 IP 访问该 pod。为了使 hello-micronaut 容器可以从 Kubernetes 虚拟网络外部进行访问,您必须将 Pod 公开为 Kubernetes 服务。

在 Cloud Shell 中,您可以结合使用 kubectl expose 命令和 --type=LoadBalancer 标志向公共互联网公开 pod。此标志在创建外部可访问的 IP 时是必需的:

$ kubectl expose deployment hello-micronaut --type=LoadBalancer --port=8080

此命令中使用的标志指定您将使用底层基础架构(在本例中为 Compute Engine 负载平衡器)提供的负载平衡器。请注意,您公开的是 Deployment,而不是直接公开 Pod。这会使生成的服务对 Deployment 管理的所有 Pod 的流量进行负载均衡(在本例中,只有一个 Pod,但稍后您将添加更多副本)。

Kubernetes 主实例会创建负载平衡器和相关的 Compute Engine 转发规则、目标池和防火墙规则,以使该服务完全可从 Google Cloud Platform 外部进行访问。

如需查找服务的可公开访问的 IP 地址,只需请求 kubectl 即可列出所有集群服务:

$ kubectl get services
NAME              TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)          AGE
hello-micronaut   LoadBalancer   10.39.243.251   aaa.bbb.ccc.ddd 8080:30354/TCP   1m
kubernetes        ClusterIP      10.39.240.1     <none>          443/TCP          31m

请注意,系统针对您的服务列出了 2 个 IP 地址,它们均通过端口 8080 提供服务。一个是仅在您的云虚拟网络中可见的内部 IP;另一个是外部负载均衡 IP。在此示例中,外部 IP 地址为 aaa.bbb.ccc.ddd

现在,通过将浏览器指向以下地址,您应该能够访问该服务:http://<EXTERNAL_IP>:8080/hello

11. 扩容服务

Kubernetes 提供了许多强大的功能,其中一个就是可以轻松扩缩应用。假设您突然需要为您的应用增加容量,您只需指示复制控制器为您的应用实例管理新数量的副本即可:

$ kubectl scale deployment hello-micronaut --replicas=3
deployment.extensions "hello-micronaut" scaled

$ kubectl get deployment
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-micronaut   3         3         3            3           16m

请注意此处的声明式方法 - 您无需启动或停止新实例,而是声明应始终运行的实例数量。Kubernetes 协调循环只是确保现实符合您的要求并视需要采取措施。

12. 发布服务升级

在某些情况下,您部署到生产环境的应用将需要进行问题修复或需要其他功能。Kubernetes 可帮助您将新版本部署到生产环境,而不会影响您的用户。

首先,我们来修改应用。从 Cloud Shell 中打开代码编辑器。

5aee8f3d1e003571.png

前往 /jib/examples/micronaut/src/main/groovy/example/micronaut/HelloController.groovy,然后更新响应的值:

@Controller("/hello")
class HelloController {
    @Get("/")
    String index() {
        "Hello Kubernetes World"
    }
}

/jib/examples/micronaut/build.gradle 中,我们将通过更新以下行,将映像的版本从 0.1 升级到 0.2:

version '0.2'

然后,使用最新更改重新构建并打包应用:

$ ./gradlew jibDockerBuild

然后标记映像并将其推送到容器映像注册表中:

$ docker tag micronaut-jib:0.2 \
         gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2
$ docker push gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2

您现在可以让 Kubernetes 顺利地将您的复制控制器更新到新版应用了。如需更改正在运行的容器的映像标签,您需要修改现有的 hello-micronaut deployment,并将映像从 gcr.io/PROJECT_ID/micronaut-jib:0.1 更改为 gcr.io/PROJECT_ID/micronaut-jib:0.2

您可以使用 kubectl set image 命令要求 Kubernetes 通过滚动更新在整个集群中部署新版应用(每次一个实例):

$ kubectl set image deployment/hello-micronaut \
          micronaut-jib=gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2

deployment.apps "hello-micronaut" image updated

再次检查 http://EXTERNAL_IP:8080 以查看它是否返回了新响应。

13. 回滚

糟糕 - 您是否在使用新版应用时出错?可能是因为新版本包含错误,您需要快速回滚。借助 Kubernetes,您可以轻松地回滚到先前的状态。我们来运行以下命令以回滚应用:

$ kubectl rollout undo deployment/hello-micronaut

如果您查看该服务的输出,会发现我们又回到了最初的“Hello World”消息。

14. 总结

在此步骤中,您设置了一个基于 Apache Groovy 的简单 Micronaut“Hello World”服务,并直接从 Cloud Shell 中运行该服务,使用 Jib 将其打包为容器,然后将其部署到 Google Kubernetes Engine。

15. 恭喜!

您已了解如何构建基于 Apache Groovy / Micronaut 的新 Web 微服务并将其部署到 Google Kubernetes Engine 上的 Kubernetes。

了解详情

许可

此作品已获得 Creative Commons Attribution 2.0 通用许可授权。