Skaffold-毫不费力的云原生应用开发指南-全-

Skaffold:毫不费力的云原生应用开发指南(全)

原文:zh.annas-archive.org/md5/12FE92B278177BC9DBE7FCBCECC73A83

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

多年来,围绕 Kubernetes 的工具已经发生了巨大变化,鉴于其所带来的热潮。越来越多的开发者正在寻找可以帮助他们快速开始使用 Kubernetes 的工具。这也给开发者带来了一些困惑:他们应该使用哪种工具来减少配置本地设置的时间,或者编写脚本来自动化他们的内部开发循环工作流程?此外,开发者在使用 Kubernetes 时需要更好的工具,因为焦点应该放在手头的任务上,即编码,而不是苦恼于如何以及在哪里部署应用程序。理想情况下,您会希望有一个提供可扩展性以支持各种用例的工具。

本书将向您展示如何通过使用 Skaffold 自动化构建、推送和部署样板来解决云原生应用中的内部开发循环复杂性。

本书的受众

本书适用于云原生应用开发人员、与 Kubernetes 合作的软件工程师以及寻找简化其内部开发循环并改进云原生应用的 CI/CD 流程的 DevOps 专业人员。在阅读本书之前,需要具备初级水平的 Java、Docker、Kubernetes 和容器生态系统知识。

本书涵盖内容

第一章《编码、构建、测试和重复——应用开发的内部循环》定义了应用开发的内部循环及其重要性。它还将内部循环与外部开发循环进行了比较,并涵盖了传统单体应用程序和容器原生微服务应用程序的典型开发工作流程。

第二章《使用 Kubernetes 开发云原生应用——开发者的噩梦》解释了开发者在使用 Kubernetes 开发云原生应用时所面临的问题。

第三章《Skaffold——简单易用的云原生 Kubernetes 应用开发》提供了 Skaffold 的高级概述。我们还将通过构建和部署一个 Spring Boot 应用程序来演示 Skaffold 的基本特性。

第四章《了解 Skaffold 的特性和架构》通过查看其架构、工作流程和配置文件skaffold.yaml来探讨 Skaffold 的特性和内部结构。

[第五章],安装 Skaffold 并揭秘其流水线阶段,解释了 Skaffold 的安装以及在不同流水线阶段中使用的常见 CLI 命令。

[第六章],使用 Skaffold 容器镜像构建器和部署器,介绍了用于使用 Skaffold 将容器镜像(Docker、Jib、kaniko、Buildpacks)构建和部署(Helm、kubectl、kustomize)到 Kubernetes 的各种工具。

[第七章],使用 Cloud Code 插件构建和部署 Spring Boot 应用,向您介绍了由 Google 开发的 Cloud Code 插件。它解释了如何使用 Cloud Code 插件和诸如 IntelliJ 之类的 IDE 将 Spring Boot 应用构建和部署到 Kubernetes 集群。

[第八章],使用 Skaffold 将 Spring Boot 应用部署到 Google Kubernetes Engine,解释了如何使用 Skaffold 将 Spring Boot 应用部署到 Google Kubernetes Engine,这是 Google Cloud Platform 提供的托管 Kubernetes 服务。

[第九章],使用 Skaffold 创建一个生产就绪的 CI/CD 流水线,解释了如何使用 Skaffold 和 GitHub 操作创建一个 Spring Boot 应用的生产就绪的持续集成和部署流水线。

[第十章],探索 Skaffold 替代方案、最佳实践和陷阱,介绍了 Skaffold 替代工具,如 Telepresence,并介绍了 Skaffold 的最佳实践和陷阱。

要充分利用本书

如果您正在使用本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库(链接在下一节中提供)访问代码。这样做将有助于您避免与复制和粘贴代码相关的任何潜在错误。

下载示例代码文件

您可以从 GitHub 上下载本书的示例代码文件github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-Using-Skaffold。如果代码有更新,将在现有的 GitHub 存储库中进行更新。

我们还有来自我们丰富的图书和视频目录的其他代码包,可在github.com/PacktPublishing/上找到。快去看看吧!

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在这里下载:

static.packt-cdn.com/downloads/9781801077118_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这是一个例子:“在内部,Skaffold 创建一个tar文件,其中包含与我们在skaffold.yaml文件中定义的同步规则匹配的更改文件。”

代码块设置如下:

profiles:
  - name: userDefinedPortForward
    portForward:
      - localPort: 9090
        port: 8080
        resourceName: reactive-web-app
        resourceType: deployment

任何命令行输入或输出都以以下方式编写:

curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 && \sudo install skaffold /usr/local/bin/

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会在文本中以这种方式出现。这是一个例子:“现在我们有一个可用的项目,点击Run/Debug Configurations下拉菜单,然后选择Edit Configurations。”

提示或重要说明

会以这种方式出现。

第一部分:Kubernetes 的噩梦 – Skaffold 来拯救

在这一部分,我们将描述使用 Kubernetes 开发应用程序的痛苦和苦难。在本地开发 Kubernetes 应用程序时,存在多个手动触点,这降低了开发人员的生产力。焦点应该放在编写代码和为产品添加更多功能上,而不是担心在您的工作站上复制基础架构以调试问题或测试功能。谷歌的工程师们将这称为无尽的痛苦和苦难循环。我们将向您介绍 Skaffold 以及它如何帮助您自动化构建、推送和部署在 Kubernetes 上运行的应用程序的工作流程。

在这一部分,我们有以下章节:

  • 第一章, 编码、构建、测试和重复 – 应用程序开发的内部循环

  • 第二章, 使用 Kubernetes 开发云原生应用程序 – 开发者的噩梦

  • 第三章, Skaffold – 简单易用的云原生 Kubernetes 应用程序开发

第一章:编码、构建、测试和重复 - 应用程序开发内部循环

构建和部署云原生应用程序可能会对本地和远程开发造成麻烦,如果您没有使用适当的工具。开发人员经历了很多痛苦来自动化构建、推送和部署步骤。在本书中,我们将向您介绍Skaffold,它可以帮助自动化这些开发工作流程步骤。您将学习如何使用 Skaffold CLI 来加速内部开发循环,以及如何创建有效的持续集成/持续部署CI/CD)流水线,并执行构建和部署以管理 Kubernetes 实例,如Google Kubernetes EngineGKE)、Microsoft 的 Azure Kubernetes ServiceAKS)和 Amazon 的Elastic Kubernetes ServiceEKS)。

本章将定义应用程序开发的内部循环及其重要性,比较内部与外部开发循环,并涵盖传统单体应用程序和容器本地微服务应用程序的典型开发工作流程。我们将深入讨论这两种方法之间的差异。

在本章中,我们将涵盖以下主要主题:

  • 了解应用程序开发内部循环是什么

  • 内部与外部开发循环

  • 探索传统应用程序开发内部循环

  • 检查容器本地应用程序开发内部循环

到本章结束时,您将了解传统和容器本地应用程序内部开发循环。

技术要求

要跟着本章的示例进行,您需要以下内容:

您可以从 GitHub 存储库github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-Using-Skaffold/tree/main/Chapter01下载本章的代码示例

理解应用程序开发的内部循环

应用程序开发的内部循环是一个迭代过程,在这个过程中,开发人员更改代码,开始构建,运行应用程序,然后测试它。如果出了问题,那么我们就重复整个循环。

因此,基本上,这是开发人员在本地完成更改之前与他人分享更改的阶段。无论您的技术堆栈、使用的工具和个人偏好如何,内部循环过程可能会有所不同,但理想情况下,可以总结为以下三个步骤:

  1. 代码

  2. 构建

  3. 测试

以下是内部开发循环的快速可视化表示:

图 1.1 - 内部循环

图 1.1 - 内部循环

如果您仔细想想,编码是唯一增加价值的步骤,其余步骤都像是对您的工作进行验证,即确认您的代码是否正在编译和测试是否通过。由于开发人员大部分时间都花在内部循环上,他们不喜欢在任何步骤上花费太多时间。它应该迅速。此外,作为开发人员,我们渴望快速反馈。

到目前为止,我们定义的所有步骤都是在开发人员的机器上本地发生的,然后再将代码提交到源代码存储库。一旦开发人员提交并推送更改到源代码存储库,通常会启动他们的 CI/CD 管道,称为外部开发循环(拉取请求、CI、部署等)。无论您是开发传统的单体应用程序还是容器化的微服务应用程序,都不应忽视内部开发循环的重要性。以下是您应该关注内部开发循环的原因:

  • 如果您的内部开发循环缓慢且缺乏自动化,那么开发人员的生产力将会下降。

  • 最好始终致力于优化它,因为慢的内部循环会影响其他依赖团队,并且将需要更长的时间将新功能交付给用户。

现在我们已经快速概述了应用程序开发的内部循环,让我们比较一下内部和外部开发循环。

内部与外部开发循环

正如前面讨论的那样,只要开发人员在本地环境中测试,他们就处于内部循环中。一般来说,开发人员大部分时间都在内部循环中,因为它快速并且能够立即反馈。通常涉及以下步骤:

  1. 开发人员开始处理新的功能请求。此时进行一些代码更改。

  2. 一旦开发人员对更改感到自信,就会启动构建。

  3. 如果构建成功,开发人员将运行单元测试。

  4. 如果测试通过,开发人员将在本地启动应用程序的一个实例。

  5. 他们将切换到浏览器验证更改。

  6. 开发人员将跟踪日志或附加调试器。

  7. 如果出现问题,开发人员将重复前面的步骤。

但是,一旦开发人员提交并将代码推送到源代码存储库,就会触发外部开发循环。外部开发循环与 CI/CD 流程密切相关。它涉及以下步骤:

  1. CI 检出源代码

  2. 构建项目

  3. 运行功能和集成测试套件

  4. 创建运行时构件(JAR、WAR 等)

  5. 部署到目标环境

  6. 测试和重复

所有前面的步骤通常都是自动化的,开发人员几乎不需要参与。当 CI/CD 流水线因测试失败或编译问题而中断时,开发人员应该收到通知,然后开始在内部开发循环上再次工作以解决这个问题。以下是内循环与外循环的可视化:

图 1.2 - 内循环与外循环

图 1.2 - 内循环与外循环

很容易将 CI/CD 用作内部开发循环的替代品。让我们讨论一下这是否是一个好方法。

为什么不使用 CI/CD?

与我们刚讨论的内循环相反,一些开发人员可能会说他们不关心他们的内部开发循环,因为他们有一个 CI/CD 流程,这应该足够了。他们并不完全错误,因为这些流水线是为了使现代应用程序开发过程可重复和简单而构建的。但是,您的 CI/CD 流程只能解决一组独特的问题。

使用 CI/CD 替代你的内部开发循环将使整个过程变得更慢。想象一下,不得不等待整个 CI/CD 系统运行你的构建和测试套件,然后部署,只发现你犯了一个小错误;这将是相当恼人的。现在,你必须等待并重复整个过程,只是因为一些愚蠢的错误。如果我们可以避免不必要的迭代,那将会更容易。对于你的内部开发循环,你必须快速迭代并预览更改,就好像它们发生在一个实时集群上一样。

我们已经涵盖了关于应用程序开发内部循环的足够基础知识,现在我们将介绍 Java 开发人员的传统应用程序开发内部循环。

探索传统应用程序开发内部循环

在容器变得流行之前,我们被内部开发循环的选择所宠坏。你的集成开发环境可以在后台运行构建,然后你可以部署你的应用程序并在本地测试你的更改。典型的传统应用程序开发内部循环涉及以下步骤:

  1. 开发人员在集成开发环境中进行代码更改

  2. 构建和打包应用程序

  3. 部署,然后在本地服务器上运行

  4. 最后,测试更改并重复步骤

这是传统应用程序开发内部循环的可视化。

图 1.3 - 传统应用程序开发内部循环

图 1.3 - 传统应用程序开发内部循环

对于 Java 开发人员,有许多选项可用于自动化此过程。其中一些最受欢迎的选项如下:

  • Spring Boot 开发者工具

  • JRebel

让我们简要讨论这些选项。

Spring Boot 开发者工具

Spring Boot 首次在 1.3 版中引入了开发者工具。Spring Boot 开发者工具提供快速反馈循环和自动重新启动应用程序以适应任何代码更改。它提供以下功能:

  • 它提供了热重载功能。一旦在classpath上进行了任何文件更改,它将自动重新启动应用程序。自动重新启动可能会根据你的集成开发环境而有所不同。请查看官方文档(docs.spring.io/spring-boot/docs/1.5.16.RELEASE/reference/html/using-boot-devtools.html#using-boot-devtools-restart)以获取更多关于此的详细信息。

  • 它提供与LiveReload插件(livereload.com)的集成,以便在资源更改时自动刷新浏览器。在内部,Spring Boot 将启动一个嵌入式 LiveReload 服务器,每当资源更改时都会触发浏览器刷新。该插件适用于大多数流行的浏览器,如 Chrome、Firefox 和 Safari。

  • 它不仅支持本地开发过程,还可以选择更新并重新启动远程在服务器或云上运行的应用程序。您也可以选择启用远程调试。但是,在生产中使用此功能存在安全风险。

以下是如何向 Maven 和 Gradle 项目添加相关依赖项以添加对 Spring Boot 开发工具的支持的简短片段。Maven/Gradle 应该首先有一个介绍部分:

Maven pom.xml

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
  </dependency>
</dependencies>

Gradle build.gradle

以下是 Gradle 的代码:

dependencies {
compileOnly("org.springframework.boot:spring-boot-devtools")
}

但这不是我们将如何添加依赖项来测试开发工具的自动重新加载功能。我们将使用Spring Initializr网站(start.spring.io/)根据您选择的选项生成项目存根。以下是我们将遵循的步骤:

  1. 您可以选择默认选项,也可以自行选择。您可以选择构建工具(Maven 或 Gradle)、语言(Java、Kotlin 或 Groovy)和您选择的 Spring Boot 版本。

  2. 之后,您可以通过点击“ADD DEPENDENCIES…”按钮并选择应用程序所需的依赖项来添加必要的依赖项。

  3. 我选择了默认选项,并将spring-boot-starter-webspring-boot-dev-tools和 Thymeleaf 作为我的演示 Hello World Spring Boot 应用程序的依赖项。

  4. 现在,继续点击“GENERATE”按钮,以下载在您的计算机上生成的源代码。这是您应该看到的屏幕:图 1.4 – Spring Initializr 首页

图 1.4 – Spring Initializr 首页

  1. 下载后,您可以将项目导入到您的 IDE 中。

下一个逻辑步骤是构建一个简单的 Hello World Spring Boot web 应用程序。让我们开始吧。

Spring Boot web 应用程序的解剖

了解 Spring Boot 应用程序的工作部分的最佳方法是看一个例子。在这个例子中,我们将创建一个简单的Spring Web MVC应用程序,它将在http://localhost:8080/hello接受 HTTP GET 请求。我们将得到一个 HTML 网页,其中响应中的 HTML 主体中有"Hello, John!"。我们将允许用户通过在http://localhost:8080/hello?name=Jack URL 中输入查询字符串来自定义默认响应,以便我们可以更改默认消息。让我们开始:

  1. 首先,让我们使用@Controller注解创建一个HelloController bean 来处理传入的 HTTP 请求。@GetMapping注解将 HTTP GET 请求绑定到hello()方法:
@Controller
public class HelloController {
   @GetMapping("/hello")
   public String hello(@RequestParam(defaultValue =     "John", name = "name", required = false) String name,     Model model) {
      model.addAttribute("name", name);
      return "index";
}
}

这个控制器返回视图的名称,在我们的例子中是index。我们在这里使用的视图技术是 Thymeleaf,它负责服务器端渲染 HTML 内容。

  1. 在源代码模板中,index.html位于src/main/resources/的 templates 文件夹中。以下是文件的内容:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
      <meta charset="UTF-8"/>
      <title>Welcome</title>
</head>
<body>
<p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>
  1. Spring Boot 为您的应用程序提供了一个默认的设置,其中包括一个main类:
@SpringBootApplication
public class Application {
   public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
   }
}
  1. 我们将使用mvn spring-boot:run maven goal来运行我们的应用程序,这是由spring-boot-maven-plugin提供的:图 1.5 - 应用程序的输出

图 1.5 - Spring Boot 应用程序启动日志

注意

为了减少日志的冗长,我们已经将它们缩减到只显示与我们讨论相关的部分。

如果您仔细观察日志,我们已经启用了开发者工具支持,一个嵌入式的 Tomcat 服务器在端口8080上监听,并且一个运行在端口35279上的嵌入式 LiveReload 服务器。到目前为止,看起来很不错。一旦应用程序启动,您可以访问 http://localhost:8080/hello URL。

图 1.6 - REST 端点响应

图 1.6 - REST 端点响应

  1. 现在我们将在 Java 文件中进行一个小的代码更改并保存,您可以从日志中看到嵌入式 Tomcat 服务器已经重新启动。在日志中,您还可以看到生成应用程序的线程不是主线程,而是一个restartedMain线程:
2021-02-12 16:28:54.500   INFO 53622 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]          : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-02-12 16:28:54.500   INFO 53622 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet            : Initializing Servlet 'dispatcherServlet'
2021-02-12 16:28:54.501   INFO 53622 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet            : Completed initialization in 1 ms
2021-02-12 16:29:48.762   INFO 53622 --- [          Thread-5] o.s.s.concurrent.ThreadPoolTaskExecutor   : Shutting down ExecutorService 'applicationTaskExecutor'
2021-02-12 16:29:49.291   INFO 53622 --- [   restartedMain] c.e.helloworld.HelloWorldApplication       : Started HelloWorldApplication in 0.483 seconds (JVM running for 66.027)
2021-02-12 16:29:49.298   INFO 53622 --- [   restartedMain] .ConditionEvaluationDeltaLoggingListener : Condition evaluation unchanged
2021-02-12 16:29:49.318   INFO 53622 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]          : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-02-12 16:29:49.319   INFO 53622 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet            : Initializing Servlet 'dispatcherServlet'
2021-02-12 16:29:49.320   INFO 53622 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet            : Completed initialization in 1 ms

这完成了 Spring Boot 开发者工具自动重启功能的演示。出于简洁起见,我们没有涵盖 LiveReload 功能,因为在这里很难解释,因为这一切都是实时发生的。

JRebel

JRebel (www.jrebel.com/products/jrebel) 是 Java 开发人员加速内部循环开发过程的另一个选择。它是一个 JVM 插件,有助于减少本地开发步骤的时间,如构建和部署。这是一个由名为Perforce的公司开发的付费工具。但是,如果您想尝试一下,有 10 天的免费试用期。它提供以下功能:

  • 它允许开发人员跳过重建和重新部署,并通过刷新浏览器即可看到其更改的实时更新。

  • 它将使开发人员在保持应用程序状态的同时更加高效。

  • 它提供了即时反馈循环,允许您在开发过程中早期测试和修复问题。

  • 它与流行的框架、应用服务器、构建工具和 IDE 有良好的集成。

有许多不同的方法可以使 JRebel 支持您的开发过程。我们将考虑使用它与 Eclipse 或 IntelliJ 这样的 IDE 的可能性。对于这两个 IDE,您可以安装插件,就这样。正如我之前所说,这是一个付费选项,您只能免费试用 10 天。

对于 IntelliJ IDEA,您可以从市场安装插件。

图 1.7 – IntelliJ IDEA 安装 JRebel

图 1.7 – IntelliJ IDEA 安装 JRebel

对于 Eclipse IDE,您可以从 Eclipse Marketplace 安装插件。

图 1.8 – Eclipse IDE 安装 JRebel

图 1.8 – Eclipse IDE 安装 JRebel

由于 JRebel 是一个付费选项,我们将不会在本书中探讨它,但您可以自行测试。

我们已经介绍了传统应用程序开发内部循环生命周期和工具,如 Spring Boot Developer Tools 和 JRebel,它们允许快速应用程序开发。现在让我们来看一下基于容器的应用程序开发内部循环生命周期。

检查基于容器的应用程序开发内部循环

Kubernetes 和容器为内部开发循环引入了一系列新的挑战和复杂性。现在在开发应用程序时,内部循环中添加了一组额外的步骤,这是耗时的。开发人员更愿意花时间解决业务问题,而不是等待构建过程完成。

它涉及以下步骤:

  1. 在 IDE 中进行代码更改的开发人员

  2. 构建和打包应用程序

  3. 创建一个容器镜像

  4. 将镜像推送到容器注册表

  5. Kubernetes 从注册表中拉取镜像

  6. Kubernetes 创建和部署 pod

  7. 最后,测试和重复

谷歌的工程师称之为“无尽的痛苦和苦难”。这是一个容器本地应用开发内部循环的可视化:

图 1.9 - 容器本地应用开发内部循环

图 1.9 - 容器本地应用开发内部循环

正如你所看到的,我们现在在内部开发循环中增加了三个步骤,即创建应用程序的容器镜像,将其推送到容器注册表,最后,在部署到 Kubernetes 等容器编排工具时拉取镜像。

容器镜像可以是 Docker 或 OCI 格式的镜像,这取决于您用来构建镜像的工具。您可以选择 Docker Hub、AWS 容器注册表、谷歌容器注册表或 Azure 容器注册表作为容器注册表。然后,在部署时,对于容器编排,您可以使用 Kubernetes 等工具,它将首先从容器注册表中拉取镜像并部署您的应用程序。

这里涉及许多手动步骤。这也取决于您在本地开发工作流程中使用了什么工具。例如,您将使用以下命令:

docker build 
docker tag
docker push 
kubectl apply

以下是开发人员在开发容器本地应用程序时必须经历的详细步骤:

  1. 使用 Dockerfile 定义如何配置容器的操作系统

  2. 通过向 Dockerfile 添加指令来定义将应用程序打包成容器镜像

  3. 使用 Docker 命令(如docker builddocker tag)创建一个容器镜像

  4. 使用命令(如docker push)将容器镜像上传到容器注册表

  5. 在 YAML 中编写一个或多个 Kubernetes 资源文件

  6. 使用命令(如kubectl apply -f myapp.yaml)将应用程序部署到集群

  7. 使用命令(如kubectl apply -f mysvc.yaml)将服务部署到集群

  8. 编写配置,使应用程序可以通过命令(如kubectl create configmap)协同工作

  9. 使用命令(如kubectl apply -f myappconfigmap.yaml)配置应用程序以正确地协同工作

哇哦!这是很多步骤和耗时的过程。您可以使用脚本或docker compose来在一定程度上自动化它,但很快您会意识到,如果没有 Skaffold 这样的工具,它是无法完全自动化的,Skaffold 可以抽象出许多与构建和部署相关的事情。

第三章中,Skaffold – 简单易用的云原生 Kubernetes 应用开发,我们将介绍 Skaffold,它可以用单个命令简化我们在这里涵盖的过程。我在这里的唯一目的是让您了解涉及的步骤。我们将在下一章中通过一些实际示例来介绍这些步骤。

摘要

在本章中,我们涵盖了许多主题,比如典型的内部开发循环及其重要性。我们还讨论了内部和外部开发循环的不同之处,然后探讨了 CI/CD 过程是否可以替代内部开发循环。

然后,我们讨论了传统应用程序开发内部循环涉及的步骤,并介绍了诸如 Spring 开发者工具和 JRebel 之类的工具,这些工具使应用程序开发变得更加容易。为了进一步解释这一点,我们创建了一个简单的 Spring Boot web MVC 应用程序。最后,在最后一节中,我们涵盖了容器本地应用程序开发内部循环。我们还介绍了容器本地应用程序开发涉及的步骤。

在本章中,重点是向您介绍内部和外部开发等概念。您可以使用 Spring Boot 开发者工具和 JRebel 来加速/自动化传统应用程序开发生命周期。

在下一章中,我们将介绍开发人员在使用 Kubernetes 开发应用程序时面临的问题。

进一步阅读

第二章:使用 Kubernetes 开发云原生应用程序-开发者的噩梦

在上一章中,我们介绍了开发人员在开发容器原生应用程序时面临的困难。我们还介绍了开发生命周期中引入的新步骤。我们可能已经简化了解释概念,但在本章中我们将详细介绍每个步骤。

本章将涵盖开发人员在使用 Kubernetes 开发云原生应用程序时面临的问题。我们将介绍 Kubernetes 的整体开发体验为什么如此痛苦,以及为什么开发人员不是 Kubernetes 专家,他们在使用 Kubernetes 开发应用程序时寻求简化的工作流程。

在本章中,我们将涵盖以下主要主题:

  • 开发者体验不佳

  • 开发者希望简化 Kubernetes 的工作流程

  • 开发者不是 Kubernetes 专家

通过本章的学习,您将了解开发人员在使用 Kubernetes 开发云原生应用程序时面临的常见挑战。随后,在下一章中,我们将学习如何通过使用 Skaffold 来克服这些挑战,以改善您的开发工作流程。

技术要求

要跟随本章中的示例,您需要以下内容:

您可以从 GitHub 存储库github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-Using-Skaffold/tree/main/Chapter02下载本章的代码示例。

开发者体验不佳

现代开发人员正在寻找能够让他们在快节奏的今天世界中保持竞争力并交付符合客户期望的软件的工具和技术。进入 Kubernetes!Kubernetes 于 2014 年开源,自诞生以来,已成为全球众多企业选择的容器编排平台。Kubernetes 极大地简化了运维人员的工作,但对于构建和部署应用程序到 Kubernetes 的开发人员来说,情况并非如此。

我们在本章中详细介绍了这一点。根据最近的一项研究,大约 59%的企业组织正在使用 Kubernetes 运行其生产工作负载。对于一个只有 5 年历史的技术来说,这是非常出色的。企业采用 Kubernetes 的主要原因是为了增加敏捷性,加快软件交付,并支持数字化转型。

在讨论使用 Kubernetes 的痛点之前,让我们以一个真实的例子来了解 Kubernetes 如何帮助组织进行数字化转型。让我们以一个电子商务网站为例。大多数时候,网站都能正常运行。该网站利用微服务架构,并拥有多个服务协同工作,以提供更好的用户体验。然而,由于即将到来的假期,IT 团队预计网站的使用量会激增,团队担心这可能会导致停机,因为底层的微服务可能无法处理负载。但是有了 Kubernetes,很容易进行扩展而不会带来太多麻烦。例如,您可以利用 Kubernetes 的自动缩放功能以及其水平 Pod 自动缩放器(HPA)。HPA 根据观察到的 CPU 利用率自动调整 Pod 的数量。

此外,容器和 Kubernetes 确实改变了我们打包、部署和大规模运行云原生应用程序的方式。容器化后,您可以在任何地方运行应用程序,即在虚拟机、物理机或云上。并且借助 Kubernetes 等容器编排工具,您可以更有效地扩展、部署和管理云原生应用程序。它减少了生产中的停机时间,并使运维团队的工作更加轻松。然而,与传统应用程序相比,开发人员的体验和实践自 Kubernetes 问世以来并没有多大进步。让我们通过一个例子来了解云原生应用程序开发流程。

了解云原生应用程序开发工作流程

我们将使用在第一章中创建的相同的Hello-World Spring Boot Web MVC应用程序,代码、构建、测试和重复 – 应用程序开发内部循环;但是,这次我们将对其进行容器化并部署到 Kubernetes。我们的想法是经历开发人员在开发云原生 Spring Boot 应用程序时所经历的困难。以下是我们将要遵循的步骤:

  1. 我们将使用Docker Desktop作为 macOS 和 Windows 的工具,因为它支持 Kubernetes,并且我们不必为此示例单独下载minikube。但是,如果您不使用 macOS,那么您也可以为其他操作系统安装 minikube (v1-18.docs.kubernetes.io/docs/tasks/tools/install-minikube/#installing-minikube)。按照步骤在 macOS 和 Windows 上启用 Docker Desktop 的 Kubernetes 支持。

  2. 在 Docker 菜单栏中导航到首选项。然后,在Kubernetes选项卡上,单击启用 Kubernetes复选框,以启动单节点功能性 Kubernetes 集群。启动集群需要一些时间。这不是强制性的,但您也可以将 Kubernetes 启用为docker stack命令的默认编排器。图 2.1 – 启用 Kubernetes

图 2.1 – 启用 Kubernetes

  1. 启用后,您将在 Docker Desktop 菜单栏上看到以下屏幕。这证实了 Kubernetes 集群已经启动和运行:图 2.2 – 验证设置

图 2.2 – 验证设置

  1. 接下来,请确保 Kubernetes 上下文设置为docker-desktop,如果您在本地运行多个集群或环境:图 2.3 – 上下文设置为 docker-desktop

图 2.3 – 上下文设置为 docker-desktop

  1. 顺便说一句,Docker Desktop 带有kubectl支持;您不必单独下载它。kubectl 是 Kubernetes 的命令行工具,您可以使用它来针对您的集群运行命令。在 macOS 上,它通常位于路径/usr/local/bin/kubectl。对于 Windows,它位于C:\>Program Files\Docker\Docker\Resources\bin\kubectl.exe。您可能希望将其添加到您的PATH变量中。让我们使用以下命令验证设置:
kubectl get nodes
NAME             STATUS   ROLES    AGE   VERSION
docker-desktop   Ready    master   59d   v1.19.3
  1. 以下是我们用于此示例的 Dockerfile:
FROM openjdk:16
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

我们这里有一个非常基本的 Dockerfile。让我简要解释一下指令:

a. FROM指令指定了我们的 Spring Boot 应用程序的基础镜像,即 OpenJDK 16。

b. COPY用于将文件或目录从主机系统移动到容器内部的文件系统。在这里,我们将.jar文件从目标目录复制到容器内部的根路径。

c. ENTRYPOINT作为容器的运行时可执行文件,将启动我们的应用程序。

  1. 现在我们有了 Dockerfile,接下来我们需要创建一个可执行的.jar文件。我们将使用mvn clean install命令为我们的应用程序创建一个可执行的.jar文件。让我们运行docker build命令来创建一个容器镜像。在这里,我们将我们的镜像命名为helloworlddocker build命令的输出将如下所示:
docker build -t hiashish/helloworld:latest .
[+] Building 4.9s (8/8) FINISHED
 => [internal] load build definition from Dockerfile
0.1s
 => => transferring dockerfile: 36B
0.0s
 => [internal] load .dockerignore
0.0s
 => => transferring context: 2B
0.0s
 => [internal] load metadata for docker.io/library/openjdk:16
4.3s
 => [auth] library/openjdk:pull token for registry-1.docker.io
0.0s
 => [internal] load build context
0.1s
 => => transferring context: 86B
0.0s
 => [1/2] FROM docker.io/library/openjdk:11@sha256:3805f5303af58ebfee1d2f5cd5a897e97409e48398144afc223 3f7b778337017
0.0s
 => CACHED [2/2] COPY target/*.jar app.jar
0.0s
 => exporting to image
0.0s
 => => exporting layers
0.0s
 => => writing image sha256:65b544ec877ec10a4dce9883b3 766fe0d6682fb8f67f0952a41200b49c8b0c50
0.0s
 => => naming to docker.io/hiashish/helloworld:latest
  1. 我们已经为应用程序创建了一个镜像。现在我们准备使用docker push命令将镜像推送到 DockerHub 容器注册表,如下所示:
docker push hiashish/helloworld
Using default tag: latest
The push refers to repository [docker.io/hiashish/helloworld]
7f517448b554: Pushed 
ebab439b6c1b: Pushed 
c44cd007351c: Pushed 
02f0a7f763a3: Pushed 
da654bc8bc80: Pushed 
4ef81dc52d99: Pushed 
909e93c71745: Pushed 
7f03bfe4d6dc: Pushed 
latest: digest: sha256:16d3d9db1ecdbf21c69bc838d4a a7860ddd5e212a289b726ac043df708801473 size: 2006
  1. 这个练习的最后一部分是创建 Kubernetes 资源(部署和服务),以便在 Kubernetes 上启动和运行我们的应用程序。服务和部署的声明性 YAML 文件位于源代码的K8s目录中。让我们首先创建部署资源,它负责动态创建和运行一组 Pod:
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: helloworld
  name: helloworld
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
        - image: docker.io/hiashish/helloworld
          name: helloworld

让我澄清一下我们用来创建 Kubernetes 部署对象的 YAML 文件的一些事情:

a. metadata.name指定要创建的部署对象的名称。

b. spec.replicas字段表示 Kubernetes 部署对象将创建一个副本。

c. template.spec字段表示 Pod 将运行一个名为helloworld的单个容器,该容器运行我们应用程序的 DockerHub 镜像。

这是创建 Deployment 对象的kubectl命令:

kubectl create -f mydeployment.yaml
deployment.apps/helloworld created
  1. 服务为一组 Pod 提供单一的 DNS 名称,并在它们之间进行负载平衡。让我们创建 Service 资源,以便可以从集群外部访问应用程序:
apiVersion: v1
kind: Service
metadata:
  labels:
    app: helloworld
  name: helloworld
spec:
  ports:
    - port: 8080
      protocol: TCP
      targetPort: 8080
  selector:
    app: helloworld
  type: NodePort

让我们谈谈我们用来创建 Kubernetes Service 对象的 YAML 文件:

a. metadata.name指定要创建的 Service 对象的名称。

b. spec.selectors允许 Kubernetes 将名为helloworld的 Pod 分组,并将请求转发给它们。

c. type: Nodeport为每个节点创建一个静态 IP,以便我们可以从外部访问 Service。

d. targetPort是容器端口。

e. port是在集群内部暴露的端口。

以下是创建 Service 对象的kubectl命令:

kubectl create -f myservice.yaml   
service/helloworld created
  1. 现在让我们验证一下我们是否有一个正在运行的 Pod:图 2.4 - Pod 运行

图 2.4 - Pod 运行

  1. 正如您所看到的,我们现在已经在 Kubernetes 上运行我们的应用程序。让我们来验证一下:

图 2.5 - REST 端点响应

图 2.5 - REST 端点响应

这是很多步骤,即使您的更改很小,也需要太多的按键,而且您甚至不知道它是否有效。现在想象一下,每次您推送更改时都需要这样做!如果您有多个微服务相互通信,这个工作流程可能会更加复杂。您可以选择不在本地开发时部署到 Kubernetes,而是依赖于 CI/CD 流程。或者您可能正在使用类似于docker-compose或者使用 Docker 进行隔离测试。想象一下,您需要以这种方式运行多个微服务。

要真实地测试一切,您需要使开发环境与部署环境相匹配,以测试您的微服务依赖关系。这是容器本地开发的缺点,因为开发人员花费更少的时间编码,而更多的时间担心配置、设置环境和等待部署完成。在本书的后面章节中,我们将介绍如何使用 Skaffold 构建和部署多个微服务。

由于 Kubernetes 带来的固有复杂性,开发人员正在寻找简单的工作流程。让我们在下一节讨论这个问题。

开发人员希望简化 Kubernetes 的工作流程。

在上一章中,我们讨论了开发人员在内部开发循环中开发传统的 Spring Boot 应用程序时经历的步骤。我们还讨论了如何使用spring-dev-tools等工具轻松自动化整个流程。一旦开发人员对更改感到满意,他们可以保存更改,更改将自动部署。

开发云原生应用程序的开发人员正在寻找类似的工作流程,他们可以保存更改。在后台进行一些魔术操作后,应用程序应该部署到他们选择的本地或远程集群。此外,之前曾在传统单片应用程序上工作过的开发人员在转向开发云原生应用程序时会期望类似的工作流程。从开发人员的角度来看,期望是云原生应用程序开发的额外步骤应该可以通过单个命令或点击来抑制。

开发人员期望在 Kubernetes 中有简化的工作流程,如下图所示:

图 2.6 - 使用 Kubernetes 的 Ctrl + S 工作流程

图 2.6 - 使用 Kubernetes 的 Ctrl + S 工作流程

为解决这些问题,企业需要为开发人员提供可以抽象一般 Kubernetes 复杂性的工具。具体而言,开发人员正在寻找可以满足以下要求的平台或工具:

  • 开发人员应该能够在不经过支持经理批准的官僚主义的情况下连接到 Kubernetes。

  • 开发人员不应该浪费时间和精力来配置环境。

  • 开发人员在使用 Kubernetes 时应该能够快速开始工作。

  • 开发人员可以通过单个命令快速部署更改到 Kubernetes 集群。

  • 开发人员应该在开发过程中调试云原生应用程序,就像他们习惯于调试传统应用程序一样。

开发人员不应该被绑定在一个用于构建和部署图像的工具上。好消息是,许多企业已经意识到开发人员在 Kubernetes 上的体验有多痛苦,并正在提出他们自己的解决方案来改进它。在本书的后面,我们将介绍一个名为 Skaffold 的工具,它简化了开发人员在处理云原生应用程序时的内部开发循环。Skaffold 实现了Ctrl + Save的工作流,并自动化了构建和部署过程。Skaffold 还赋予了开发人员选择构建工具(Docker/Jib/Buildpacks)和部署工具(kubectl/Helm/kustomize)的自由。

拥有这样的技能集会很不错,但我们真的希望开发人员成为 Kubernetes 专家吗?让我们在下一节讨论这个问题。

开发人员不是 Kubernetes 专家

Kubernetes 最初是为运维人员开发的,而不是为开发人员开发的。有许多原因,开发人员可能对了解 Kubernetes 并不感兴趣。一个合理的理由是,开发人员更感兴趣的是解决业务问题,为他们正在开发的产品添加功能,而不关心目标环境,也就是他们将部署应用的地方。而且,说实话,Kubernetes 很复杂,这不仅对初学者而言很难,对经验丰富的人也很难。我在哪里看到过这个笑话,可能是在 Twitter 上,关于理解 Kubernetes 有多难:“有一次我试图向某人解释 Kubernetes。然后我们俩都没搞懂。”

这需要一种与开发人员日常任务不同的技能水平。由于其复杂性,通常需要很长时间才能让普通开发人员掌握 Kubernetes。

在企业环境中工作的开发人员往往会处理以下任务:

  • 参与设计讨论

  • 为产品添加新功能

  • 编写单元测试用例

  • 提高代码质量

  • 致力于改进应用程序的性能

  • 修复错误

  • 重构代码

开发人员只想编码,而不想担心他们的应用程序将如何部署在哪里。

关键是,我们需要不断告诉自己,Kubernetes 对开发人员来说并不是一个容易的工具。此外,开发人员更感兴趣的是创建应用程序,使用可以处理构建并为其部署样板的工具。

总结

本章涵盖了开发人员在使用 Kubernetes 开发云原生应用程序时必须经历的困难。我们首先描述了部署到 Kubernetes 的应用程序的云原生应用程序开发工作流程。我们通过一些编码示例介绍了开发人员在开发云原生应用程序时必须经历的额外步骤。然后我们解释了开发人员正在寻找一个简化的工作流程,以便在 Kubernetes 上轻松开发。随后在本章中,我们展示了开发人员并不是 Kubernetes 专家,他们应该配备诸如 Skaffold 之类的工具,以改善他们在 Kubernetes 上的开发体验。

在本章中,主要目标是为您介绍开发人员在开发容器本地应用程序时遇到的问题。阅读完本章后,您应该能够理解这些问题,同时我也给出了 Skaffold 如何帮助解决这些问题的提示。

在下一章中,我们将快速介绍 Skaffold,并通过一些编码示例来更好地理解这些提示。

进一步阅读。

第三章:Skaffold ——轻松开发云原生 Kubernetes 应用程序

在上一章中,我们了解到使用 Kubernetes 开发应用是繁琐的,并提供了一些编码示例。本章将概述 Skaffold 的高级概述。您还将学习和了解 Skaffold 基本命令行界面(CLI)命令以及这些命令如何简化开发人员在 Skaffold 中开发云原生微服务的痛点。我们将通过构建和部署一个 Spring Boot 应用程序来演示 Skaffold 的基本功能。

在本章中,我们将涵盖以下主要主题:

  • 什么是 Skaffold?

  • 使用 Skaffold 构建和部署 Spring Boot 应用程序

通过本章结束时,您将对 Skaffold 有基本的了解,并能够利用 Skaffold 加速内部开发循环,同时开发云原生应用程序。

技术要求

为了跟着本章的例子,你需要以下内容:

您可以从 GitHub 存储库github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-Using-Skaffold/tree/main/Chapter03下载本章的代码示例。

什么是 Skaffold?

像大多数开发人员一样,Google 工程师 Matt Rickard 在构建和部署 Kubernetes 应用程序时也遇到了同样的痛点。Matt 决定自己动手,创建了 Skaffold。

Skaffold是一个 CLI 工具,它可以自动化构建、推送和部署本地或远程 Kubernetes 集群上运行的云原生应用程序的步骤。Skaffold 并不是 Docker 或 Kubernetes 的替代品。它与它们一起工作,并为您处理构建、推送和部署的样板部分。

Skaffold 是由 Google 开发的开源工具。它于 2019 年 11 月 7 日正式发布,并在 Apache 2.0 许可下发布。Skaffold 是用 Go 编程语言编写的。您可以访问 Skaffold 主页skaffold.dev/。Skaffold 文档可在skaffold.dev/docs/找到。

如果您使用的是 macOS,那么您可以使用homebrew软件包管理器通过brew install skaffold命令安装 Skaffold。然而,在第五章安装 Skaffold 并揭秘其流水线阶段中,我们将介绍安装 Skaffold 的各种方法。

Skaffold 在开发者社区中广受欢迎,因为它提供了合理的默认设置,易于使用,并具有可插拔的架构。这是官方 Skaffold 账号最近的一条推文,证实了这一点:

图 3.1 – Skaffold Twitter 账号在 GitHub 上通过 11k 星标推文

图 3.1 – Skaffold Twitter 账号在 GitHub 上通过 11k 星标推文

如推文中所述,Skaffold GitHub 仓库的星标和分支数量本身就说明了它的受欢迎程度,如下所示:

图 3.2 – Skaffold GitHub 仓库

图 3.2 – Skaffold GitHub 仓库

Skaffold GitHub 页面可在github.com/GoogleContainerTools/skaffold找到。

让我们尝试通过构建和部署一个 Spring Boot 应用程序来理解 Skaffold 的工作原理。

使用 Skaffold 构建和部署 Spring Boot 应用程序

为了更好地理解 Skaffold 命令和概念,在本节中,我们将使用 Skaffold 构建和部署一个 Spring Boot Java 应用程序到本地单节点 Kubernetes 集群。

注意

每当我们在本书中谈论用于本地开发的 Kubernetes 集群时,我们指的是具有 Docker 桌面版的 Kubernetes 集群,除非另有说明。然而,Docker 桌面版或 minikube 并不是今天用于运行本地 Kubernetes 集群的唯一工具。Skaffold 还支持 Kind github.com/kubernetes-sigs/kind和 k3d github.com/rancher/k3d作为本地开发的目标 Kubernetes 集群。

由于这将是 Skaffold 的预览,我们不会详细介绍 Skaffold 的所有内容,因为我们将在接下来的章节中介绍这一点。但是,我会尝试解释所使用的命令,以便您可以理解确切的流程。在我们深入研究 Skaffold 之前,让我们先谈谈我们将使用 Skaffold 构建和部署的 Spring Boot 应用程序。

创建一个 Spring Boot 应用程序

我们将要创建的这个 Spring Boot 应用程序将暴露两个表述状态转移REST)端点。/states REST 端点将返回所有印度各邦及其首府,而/state?name=statename REST 端点将返回特定的印度邦及其首府。该应用程序使用内存中的H2数据库,在应用程序启动时插入行。与之前的章节类似,我们将使用start.spring.io生成项目的存根。以下屏幕截图显示了我们将用于构建此应用程序的依赖项:

图 3.3 – Spring Boot 应用程序所需的依赖项

图 3.3 – Spring Boot 应用程序所需的依赖项

将以下依赖项添加到 Maven 的pom.xml文件中:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency> 
<dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <scope>runtime</scope>
</dependency>
<plugin>
   <groupId>com.google.cloud.tools</groupId>
   <artifactId>jib-maven-plugin</artifactId>
   <version>2.8.0</version>
   <configuration>
      <from>
         <image>openjdk:16-jdk-alpine</image>
      </from>
      <to>
         <image>docker.io/hiashish/skaffold-introduction            </image>
      </to>
   </configuration>
</plugin>

除了我们已经讨论过的依赖项之外,我还在pom.xml中添加了jib-maven-plugin插件,它将 Spring Boot 应用程序容器化为一个容器镜像。Jib 将您的源代码作为输入,并输出一个准备就绪的应用程序容器镜像。顺便说一句,Gradle 也有一个等效的插件。对于 Gradle,请使用以下代码:

plugins {  
  id 'com.google.cloud.tools.jib' version '2.8.0'
} 

提示

Jib可以在没有 Docker 守护程序的情况下创建镜像。这意味着您不必安装和配置 Docker,也不必创建或维护 Dockerfile。

我们将在第六章中更多地介绍 Jib,使用 Skaffold 容器镜像构建器和部署器

让我们开始吧:

  1. 这是源代码目录的布局:图 3.4 - 项目布局

图 3.4 - 项目布局

  1. 以下是用@RestController注解的 REST 控制器类,用于处理传入的超文本传输协议HTTP)请求。getAllStates()方法上的@GetMapping注解在访问/states REST 端点时绑定所有 HTTP GET请求。同样,getSpecificState()方法处理了传入 REST 统一资源定位符URL)的/state的 HTTP GET请求,当州名作为查询参数传递时。如果没有传递参数,则它将采用Maharashtra州的默认值:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class StateController {
    private final StateService stateService;
    private static final Logger LOGGER =    LoggerFactory.getLogger(Controller.class);
    public StateController(StateService stateService) {
        this.stateService = stateService;
    }
    @GetMapping("/states")
    private List<State> getAllStates() {
        LOGGER.info("Getting all state");
        return stateService.findAll();
    }
    @GetMapping(value = "/state")
    private String getSpecificState(@      RequestParam(required = false, name = "name",         defaultValue = "Maharashtra") String name) {
        return stateService.findByName(name);
    }
}
  1. 在撰写本书时,Java 16 已经普遍可用。我还有幸向您介绍了一些其新功能。现在让我们谈谈记录。我们有以下数据载体record类:
public record State(String name, String capital) {}

类类型是record,它是 Java 16 中作为特性添加的特殊类型。根据Java Enhancement Proposal 395 (openjdk.java.net/jeps/395),记录是 Java 语言中的一种新类型的类。它们作为不可变数据的透明载体,比普通类的仪式少。记录可以被视为名义元组。record类声明包括名称、可选类型参数、头部和主体。关于record类的另一个值得一提的有趣特性是编译器会为我们隐式生成hashcode()equals()toString()和一个规范构造函数。

  1. 以下是由StateService类实现的StateRepository接口:
import java.util.List;
public interface StateRepository {
    List<State> findAll();
    String findByName(String name);
}
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StateService implements StateRepository{
    private final JdbcTemplate;
    public StateService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    private final RowMapper<State>  rowMapper = (rs,    rowNum) -> new State(rs.getString("name"),
    rs.getString("capital"));
    @Override
    public List<State> findAll() {
        String findAllStates = """
                select * from States
                """;
        return jdbcTemplate.query(findAllStates,
          rowMapper);
    }
    @Override
    public String findByName(String name) {
        String findByName = """
                select capital from States where name
                  = ?;
                """;
        return jdbcTemplate.queryForObject(findByName,          String.class, name);
    }
}

StateService类中,我们使用 Spring 的JdbcTemplate来访问H2数据库。findAll()方法返回所有州和它们的首府。在与findAll()方法相同的类中,我使用了RowMapper函数接口。JdbcTemplate使用它来映射ResultSet对象的行,并为当前行返回一个Row对象。

我相信您可能也注意到我另外使用了new关键字来初始化record类,这意味着我可以像在 Java 中初始化普通类一样初始化record类。findByName()方法返回一个String,这是在query参数请求中传入的州的首府。

在声明结构化查询语言SQL)查询时,我还使用了Java 15 文本块openjdk.java.net/jeps/378)功能,这有助于提高 SQL 查询和JavaScript 对象表示JSON)字符串值的可读性。

  1. 正如我之前解释的,我们使用内存中的H2数据库来保存数据,该数据在应用程序运行时插入。它使用以下 SQL 语句在应用程序启动时插入:
INSERT INTO States VALUES ('Andra Pradesh','Hyderabad');
INSERT INTO States VALUES ('Arunachal Pradesh','Itangar');
INSERT INTO States VALUES ('Assam','Dispur');
INSERT INTO States VALUES ('Bihar','Patna');
INSERT INTO States VALUES ('Chhattisgarh','Raipur');
INSERT INTO States VALUES ('Goa','Panaji');
INSERT INTO States VALUES ('Gujarat','Gandhinagar');
INSERT INTO States VALUES ('Haryana','Chandigarh');
INSERT INTO States VALUES ('Himachal Pradesh','Shimla');
INSERT INTO States VALUES ('Jharkhand','Ranchi');
INSERT INTO States VALUES ('Karnataka','Bengaluru');
INSERT INTO States VALUES ('Kerala','Thiruvananthapuram');
INSERT INTO States VALUES ('Madhya Pradesh','Bhopal');
INSERT INTO States VALUES ('Maharashtra','Mumbai');
INSERT INTO States VALUES ('Manipur','Imphal');
INSERT INTO States VALUES ('Meghalaya','Shillong');
INSERT INTO States VALUES ('Mizoram','Aizawl');
INSERT INTO States VALUES ('Nagaland','Kohima');
INSERT INTO States VALUES ('Orissa','Bhubaneshwar');
INSERT INTO States VALUES ('Rajasthan','Jaipur');
INSERT INTO States VALUES ('Sikkim','Gangtok');
INSERT INTO States VALUES ('Tamil Nadu','Chennai');
INSERT INTO States VALUES ('Telangana','Hyderabad');
INSERT INTO States VALUES ('Tripura','Agartala');
INSERT INTO States VALUES ('Uttarakhand','Dehradun');
INSERT INTO States VALUES ('Uttar Pradesh','Lucknow');
INSERT INTO States VALUES ('West Bengal','Kolkata');
INSERT INTO States VALUES ('Punjab','Chandigarh');
  1. 数据使用以下模式定义:
DROP TABLE States IF EXISTS;
CREATE TABLE States(name VARCHAR(255), capital VARCHAR(255));
  1. Kubernetes 清单,即部署和服务,可在源代码的k8s目录下找到,如下面的代码片段所示:

mydeployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: skaffold-introduction
  name: skaffold-introduction
spec:
  replicas: 1
  selector:
    matchLabels:
      app: skaffold-introduction
  template:
    metadata:
      labels:
        app: skaffold-introduction
    spec:
      containers:
        - image: docker.io/hiashish/skaffold-introduction
          name: skaffold-introduction

myservice.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: skaffold-introduction
  name: skaffold-introduction
spec:
  ports:
    - port: 8080
      protocol: TCP
      targetPort: 8080
  selector:
    app: skaffold-introduction
  type: LoadBalancer

到目前为止,我们已经涵盖了 Skaffold 的所有必需构建块。现在,让我们谈谈 Skaffold 配置。

了解 Skaffold 配置

让我们谈谈skaffold.yaml Skaffold 配置文件,在其中我们描述了工作流的构建和部署部分。该文件是使用skaffold init命令生成的。我们将在第五章中探讨这个以及许多其他 Skaffold CLI 命令,安装 Skaffold 和揭秘其流水线阶段。Skaffold 通常期望skaffold.yaml配置文件在当前目录中,但您可以通过传递--filename标志来覆盖它。

这是配置文件的内容:

apiVersion: skaffold/v2beta20
kind: Config
metadata:
  name: indian-states
build:
  artifacts:
    - image: docker.io/hiashish/skaffold-introduction
      jib: {}
deploy:
  kubectl:
    manifests:
      - k8s/mydeployment.yaml
      - k8s/myservice.yaml

让我解释一下这个文件中的关键组件,如下所示:

  • apiVersion:这指定了应用程序编程接口API)模式版本。

  • build:这指定了如何使用 Skaffold 构建图像。

  • artifacts:这里有要构建的图像。

  • image:这是要构建的图像的名称。

  • jib:这指定了使用 Jib Maven 插件构建图像。

  • deploy:这指定了图像将如何部署到本地或远程 Kubernetes 集群。

  • kubectl:这指定了要使用kubectl CLI 来创建和更新 Kubernetes 清单。

  • manifests:这指定了 Kubernetes 清单文件路径,即部署和服务。

现在您已经了解了 Skaffold 配置,下一个逻辑步骤是使用 Skaffold 构建和部署我们的 Spring Boot 应用程序。

构建和部署 Spring Boot 应用程序

在继续构建和部署我们的 Spring Boot 应用程序之前,请确保在运行skaffold命令之前 Docker 已经启动并运行。否则,您将收到以下错误:

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

现在唯一剩下的就是运行skaffold dev命令并启动持续开发CD)工作流。如果您在没有启用 Docker Desktop 的情况下运行此命令,它将失败,并显示以下错误。因此,请注意这些先决条件:

Deploy Failed. Could not connect to cluster docker-desktop due to "https://kubernetes.docker.internal:6443/version?timeout=32s": dial tcp 127.0.0.1:6443: connect: connection refused. Check your connection for the cluster.

如果满足了所有的先决条件,那么当您输入该命令时,Skaffold 将会使用其文件监视器机制来监视源代码目录中的更改。它将构建一个图像,将其推送到本地 Docker 注册表,部署您的应用程序,并从运行中的 pod 中流式传输日志。

这多酷啊!!您应该看到以下输出:

$ skaffold dev
Listing files to watch...
- docker.io/hiashish/skaffold-introduction
Generating tags...
- docker.io/hiashish/skaffold-introduction -> docker.io/hiashish/skaffold-introduction:22f18cc-dirty
Checking cache...
- docker.io/hiashish/skaffold-introduction: Not found. Building
Starting build...
Found [docker-desktop] context, using local docker daemon.
Building [docker.io/hiashish/skaffold-introduction]...
[INFO] --- jib-maven-plugin:2.8.0:dockerBuild (default-cli) @ skaffold-introduction ---
[INFO] Containerizing application to Docker daemon as hiashish/skaffold-introduction:22f18cc-dirty...
[WARNING] Base image 'openjdk:16-jdk-alpine' does not use a specific image digest - build may not be reproducible
[INFO] Building dependencies layer...
[INFO] Building resources layer...
[INFO] Building classes layer...
[INFO] The base image requires auth. Trying again for openjdk:16-jdk-alpine...
[INFO] Using credentials from Docker config (/Users/ashish/.docker/config.json) for openjdk:16-jdk-alpine
[INFO] Using base image with digest: sha256:49d822f4fa4deb 5f9d0201ffeec9f4d113bcb4e7e49bd6bc063d3ba93aacbcae
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, com.example.indianstates.IndianStatesApplication]
[INFO] Loading to Docker daemon...
[INFO] Built image to Docker daemon as hiashish/skaffold-introduction:22f18cc-dirty
[INFO] BUILD SUCCESS

注意

为了减少日志的冗长,我们已经将它们裁剪,只显示与我们讨论相关的部分。

由于生成了大量日志,并且一次性解释它们将会很困难,我故意将它们分成几部分,以帮助您通过这些日志更好地理解 Skaffold 的工作。到目前为止,我们可以从日志中得出以下结论:

  • Skaffold 首先尝试根据skaffold.yaml文件中定义的构建器来确定它需要监视的源代码依赖关系。

  • 然后,它会为图像生成一个标签,如skaffold.yaml文件中的build部分所述。您可能想知道为什么在构建图像之前会生成图像标签。我们将在第五章中专门介绍 Skaffold 的标记机制,安装 Skaffold 并揭秘其流水线阶段

  • 然后,它尝试在本地缓存中找到图像。图像被本地缓存以提高执行时间,如果不需要编译的话。由于图像在本地不可用,Skaffold 开始构建。

在进行实际构建之前,Skaffold 确定了 Kubernetes 上下文设置为docker-desktop。它将使用本地 Docker 守护程序来创建图像。您是否看到它所采取的巧妙猜测以加快内部开发循环?您可以使用以下命令验证当前的kube-context状态:

   $kubectl config current-context
   docker-desktop

由于我们使用了jib-maven-plugin插件,并且 Kubernetes 上下文设置为docker-desktop,Skaffold 将在内部使用jib:dockerBuild命令来创建映像。我们使用了openjdk:16-jdk-alpine作为基础映像,因为它很轻量级。

首先,Jib 将尝试使用位于/Users/ashish/.docker/config.json路径下的config.json文件中的凭据进行身份验证,并从 Docker Hub 容器注册表下载基础映像;然后,它将创建映像层,并最终将其上传到本地 Docker 守护程序,如下例所示:

Starting test...
Tags used in deployment:
- docker.io/hiashish/skaffold-introduction -> docker.io/hiashish/skaffold-introduction:adb6df1b0757245bd08f790e93ed5f8cc621a8f7e500e3c5ad18505a8b677139
Starting deploy...
- deployment.apps/skaffold-introduction created
- service/skaffold-introduction created
Waiting for deployments to stabilize...
- deployment/skaffold-introduction is ready.
Deployments stabilized in 3.771 seconds
Press Ctrl+C to exit
Watching for changes...
[skaffold-introduction]  :: Spring Boot ::                (v2.4.4)
[skaffold-introduction] 2021-03-25 21:17:49.048  INFO 1 --- [           main] c.e.i.IndianStatesApplication            : Starting IndianStatesApplication using Java 16-ea on skaffold-introduction-85bbfddbc9-bfxnx with PID 1 (/app/classes started by root in /)
[skaffold-introduction] 2021-03-25 21:17:55.895  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
[skaffold-introduction] 2021-03-25 21:17:55.936  INFO 1 --- [           main] c.e.i.IndianStatesApplication            : Started IndianStatesApplication in 8.315 seconds (JVM running for 9.579)

我们可以从日志中得出以下结论:

  • 在第一行的Starting test...日志中,Skaffold 运行 container-structure 测试来验证构建的容器映像在部署到我们的集群之前。

  • 在那之后,Skaffold 将创建 Kubernetes 清单 - 即,在k8s目录下可用的部署和服务。

  • 一旦清单创建完成,意味着 Pod 在一段时间后已经启动并运行。然后,它还将在您的控制台上开始从 Pod 中流式传输日志。

现在,我们将进行一些验证,以确保 Pod 实际上正在运行。我们将运行以下kubectl命令进行验证:

图 3.5 - 创建的 Kubernetes 资源

图 3.5 - 创建的 Kubernetes 资源

正如您所看到的,我们有一个名为skaffold-introduction-667786cc47-khx4q的 Pod,状态为RUNNING。让我们访问/states REST 端点,看看我们是否得到了期望的输出,如下所示:

$ curl localhost:30368/states
[{"name":"Andra Pradesh","capital":"Hyderabad"},{"name":"Arunachal Pradesh","capital":"Itangar"},{"name":"Assam","capital":"Dispur"},{"name":"Bihar","capital":"Patna"},{"name":"Chhattisgarh","capital":"Raipur"},{"name":"Goa","capital":"Panaji"},{"name":"Gujarat","capital":"Gandhinagar"},{"name":"Haryana","capital":"Chandigarh"},{"name":"Himachal Pradesh","capital":"Shimla"},{"name":"Jharkhand","capital":"Ranchi"},{"name":"Karnataka","capital":"Bengaluru"},{"name":"Kerala","capital":"Thiruvananthapuram"},{"name":"Madhya Pradesh","capital":"Bhopal"},{"name":"Maharashtra","capital":"Mumbai"},{"name":"Manipur","capital":"Imphal"},{"name":"Meghalaya","capital":"Shillong"},{"name":"Mizoram","capital":"Aizawl"},{"name":"Nagaland","capital":"Kohima"},{"name":"Orissa","capital":"Bhubaneshwar"},{"name":"Rajasthan","capital":"Jaipur"},{"name":"Sikkim","capital":"Gangtok"},{"name":"Tamil Nadu","capital":"Chennai"},{"name":"Telangana","capital":"Hyderabad"},{"name":"Tripura","capital":"Agartala"},{"name":"Uttarakhand","capital":"Dehradun"},{"name":"Uttar Pradesh","capital":"Lucknow"},{"name":"West Bengal","capital":"Kolkata"},{"name":"Punjab","capital":"Chandigarh"}]

确实,我们得到了预期的输出。让我们也访问另一个/state?name=statename REST 端点,看看我们是否得到了期望的输出,如下所示:

$ curl -X GET "localhost:30368/state?name=Karnataka"
Bengaluru

是的 - 我们确实得到了期望的输出!

当您运行skaffold dev命令时,它将创建一个 CD 流水线。例如,在此模式下进行任何代码更改时,Skaffold 将自动重新构建和重新部署映像。

Skaffold dev模式下,由于我们使用的是本地 Kubernetes 集群,并且 Kubernetes 上下文设置为docker-desktop,Skaffold 将不会将映像推送到远程容器注册表,而是将其加载到本地 Docker 注册表中。这将进一步帮助加快内部开发循环。

最后,为了清理我们迄今为止所做的一切,我们只需按下Ctrl + C,Skaffold 将处理其余的事情。

因此,我们到达了这个演示的结束,我们已成功地构建并部署了一个 Spring Boot 应用程序到一个带有 Docker Desktop 的单节点 Kubernetes 集群,使用 Skaffold。

总结

在本章中,我们向您介绍了 Skaffold 及其一些命令和概念。在示例中,我们只向您介绍了一个 Skaffold 命令,即skaffold dev。然而,还有许多类似的命令,例如skaffold runskaffold render,我们将在接下来的章节中介绍。您还学会了如何使用诸如skaffold dev这样的命令来构建和部署应用程序到本地 Kubernetes 集群。

在下一章中,我们将学习 Skaffold 的特性和架构。

进一步阅读

第二部分:开始使用 Skaffold

在这一部分,我们将介绍 Skaffold 的特性和内部架构。我们将尝试理解一些图表,展示 Skaffold 的工作原理。我们还将学习如何使用 Skaffold 启动我们的项目,涵盖 Skaffold 配置文件的一些基础知识。我们将了解 Skaffold 的安装以及在不同流水线阶段(即initbuilddeploy)中可以使用的各种命令。最后,在这一部分中,我们将解释使用 Skaffold 构建和部署容器镜像的不同方法。

在这一部分,我们有以下章节:

  • 第四章, 理解 Skaffold 的特性和架构

  • 第五章, 安装 Skaffold 并揭秘其流水线阶段

  • 第六章, 使用 Skaffold 容器镜像构建器和部署器工作

第四章:[第四章]:理解 Skaffold 的功能和架构

在上一章中,我们通过一些编码示例对 Skaffold 有了基本的了解。本章将介绍 Skaffold 提供的功能。此外,我们将通过查看其架构、工作流程和skaffold.yaml配置文件来探索 Skaffold 的内部。

在本章中,我们将涵盖以下主要主题:

  • 理解 Skaffold 的功能

  • 揭秘 Skaffold 的架构

  • 理解 Skaffold 的工作流程

  • 使用skaffold.yaml解密 Skaffold 的配置

通过本章结束时,您将对 Skaffold 提供的功能有扎实的了解,并通过查看其工作流程和架构来了解它是如何完成所有魔术的。

技术要求

要跟随本章的示例,您需要安装以下软件:

您可以从 GitHub 存储库github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-Using-Skaffold下载本章的代码示例。

理解 Skaffold 的功能

在[第三章],Skaffold – Easy-Peasy Cloud-Native Kubernetes Application Development中,我们介绍了 Skaffold。我们通过构建和部署 Spring Boot 应用程序到本地 Kubernetes 集群来揭示了一些功能。然而,Skaffold 能做的远不止这些,让我们来看看它的一些功能。

Skaffold 具有以下功能:

  • 易于共享:在同一团队或不同团队之间共享项目非常简单,只要他们已经安装了 Skaffold,就可以运行以下命令继续开发活动:
git clone repository URL
skaffold dev
  • 与 IDE 集成:许多 IDE,如 IntelliJ 和 VS Code,支持由 Google 开发的Cloud Code插件,该插件内部使用 Skaffold 及其 API,在开发 Kubernetes 应用程序时提供更好的开发者体验。使用 IntelliJ 或 VS code 的Google Cloud Code Extension插件可以更轻松地使用其代码补全功能创建、编辑和更新skaffold.yaml文件。例如,为了让您对此有更多的上下文,插件可以通过查看skaffold.yaml配置文件来检测项目是否正在使用 Skaffold 进行构建和部署:

图 4.1 – IntelliJ Cloud code 插件检测到 Skaffold 配置

图 4.1 – IntelliJ Cloud code 插件检测到 Skaffold 配置

您还可以使用代码补全功能查找 Skaffold 支持的构建器和部署器。我们将在第七章中专门介绍 Cloud Code 插件,使用 Cloud Code 插件构建和部署 Spring Boot 应用程序

  • 文件同步:Skaffold 具有出色的文件同步功能。它可以直接将更改的文件复制到已经运行的容器中,以避免重新构建、重新部署和重新启动容器。

我们将在第五章中了解更多信息,安装 Skaffold 并揭秘其流水线阶段

  • 超快速本地开发:在上一章中,您了解到使用 Skaffold 构建和部署应用程序非常快速,因为它可以确定您的 Kubernetes 上下文是否设置为本地 Kubernetes 集群,并且将避免将镜像推送到远程容器注册表。因此,您可以避免昂贵的网络跳跃,同时也可以延长笔记本电脑的电池寿命。

不仅如此,Skaffold 实时检测您的代码更改,并自动化构建、推送和部署工作流程。因此,您可以在内部开发循环中继续工作,专注于编码,而无需离开该循环,直到您完全确定所做的更改。这不仅加快了您的内部开发循环,还使您更加高效。

  • 轻松的远程开发:到目前为止,在阅读本书时,你可能会认为 Skaffold 只能加速内部开发循环。哦,天哪!你会惊喜地发现 Skaffold 也可以处理外部开发循环工作流。例如,你可以使用 Skaffold 创建成熟的生产就绪的 CI/CD 流水线。我们将在第九章中具体介绍这一点,使用 Skaffold 创建生产就绪的 CI/CD 流水线。不仅如此,你还可以使用命令如kubectl config use-context context-name在本地开发环境中切换 Kubernetes 上下文,并将部署到你选择的远程集群。

由于我们正在讨论远程开发,我想强调另一点——如果你正在使用jib-maven插件进行远程构建(即,如果你要推送到远程容器注册表),你就不需要运行 Docker 守护进程。你也可以使用像Google Cloud Build这样的工具进行远程构建。Cloud Build 是Google Cloud Platform提供的一项服务,你可以使用它在云中执行构建,并为云原生应用程序创建无服务器 CI/CD 流水线。如果你从本地系统运行它可能会比较慢,但值得探索。

  • 内置镜像标签管理:在上一章中,在声明 Kubernetes 部署清单时,我们只提到了镜像名称,而没有在构建和部署 Spring Boot 应用程序时提到镜像标签。例如,在上一章的以下片段中,在image:字段中,我们只提到了镜像名称:
 spec:
      containers:
        - image: docker.io/hiashish/skaffold-introduction
          name: skaffold-introduction

通常,我们必须在推送之前给镜像打标签,然后在拉取时使用相同的镜像标签。例如,你还必须以以下格式指定镜像标签:

- image: imagename:imagetag

这是因为 Skaffold 会在每次重新构建镜像时自动生成镜像标签,这样你就不必手动编辑 Kubernetes 清单文件。Skaffold 的默认标记策略是gitCommit

我们将在第五章中更详细地介绍这一点,安装 Skaffold 并揭秘其流水线阶段

  • 轻量级:Skaffold 完全是一个 CLI 工具。在使用 Skaffold 时不需要寻找服务器端组件。这使得它非常轻量、易于使用,而且没有维护负担。Skaffold 二进制文件的大小约为 63MB。

  • 可插拔架构:Skaffold 具有可插拔架构。这最终意味着您可以选择构建和部署工具。自带您自己的工具,Skaffold 将相应地调整自己。

  • 专为 CI/CD 流水线而设计:Skaffold 可以帮助您创建有效的 CI/CD 流水线。例如,您可以使用skaffold run命令执行端到端的流水线,或者使用诸如skaffold buildskaffold deploy之类的单独命令。

此外,通过诸如skaffold renderskaffold apply之类的命令,您可以为应用程序创建GitOps风格的持续交付工作流程。GitOps 允许您将应用程序的期望状态存储在 Git 存储库中,以 Kubernetes 清单的形式。它还允许其他人将您的基础架构视为代码。

  • 轻松的环境管理:Skaffold 允许您为不同的环境定义、构建、测试和部署配置。您可以为开发或分段保留一组配置,为生产保留另一组配置。此外,您可以根据每个环境保持完全不同的配置。您可以通过使用 Skaffold 配置文件来实现这一点。这与为 Spring Boot 应用程序提供的profiles功能相对类似。

请参考以下截图:

图 4.2 – Skaffold 配置文件

图 4.2 – Skaffold 配置文件

典型的 Skaffold 配置文件包括以下部分:

  • 构建

  • 测试

  • 激活

  • 部署

  • 名称

  • 补丁

其中一些部分是相当明显的,因为它们解释了配置文件的唯一名称、构建步骤、部署步骤以及如何测试图像。让我们继续讨论补丁和激活。

首先,让我们了解补丁。

Skaffold 配置文件补丁

顾名思义,补丁是一种更详细的方式,用于覆盖skaffold.yaml文件中的单个值。例如,在以下代码片段中,dev配置文件定义了第一个构件的不同Dockerfile,而不是覆盖整个构建部分:

build:
  artifacts:
    - image: docker.io/hiashish/skaffold-example
      docker:
        dockerfile: Dockerfile
    - image: docker.io/hiashish/skaffold2
    - image: docker.io/hiashish/skaffold3
deploy:
  kubectl:
    manifests:
      - k8s-pod
profiles:
  - name: dev
    patches:
      - op: replace 
        path: /build/artifacts/0/docker/dockerfile
        value: Dockerfile_dev

在这里,patches部分下面的op字符串指定了此补丁要执行的操作。path字符串指定了.yaml文件中您在op字符串中定义的操作发生的位置,value对象指定了应替换的值。

支持以下操作:

  • 添加

  • 删除

  • 替换

  • 移动

  • 复制

  • 测试

总之,在这里,我们指示 Skaffold 使用名为Dockerfile_dev的不同Dockerfile替换用于构建第一个docker.io/hiashish/skaffold-example镜像的Dockerfile

现在,让我们讨论配置文件中的激活对象。

Skaffold 配置文件激活

您可以通过以下两种方式之一在 Skaffold 中激活配置文件:

  • 使用 CLI

  • 使用skaffold.yaml激活

首先,让我们讨论如何使用 CLI 激活配置文件。例如,在下面的skaffold.yaml文件中,在profiles部分下面,我们声明了一个名为gcb的配置文件名称:

apiVersion: skaffold/v2beta20
kind: Config
metadata:
  name: skaffold-introduction
build:
  artifacts:
    - image: docker.io/hiashish/skaffold-introduction
      jib: { }
deploy:
  kubectl:
    manifests:
      - k8s/mydeployment.yaml
      - k8s/myservice.yaml
profiles:
  - name: gcb
    build:
      googleCloudBuild:
        projectId: gke_projectid

当运行skaffold runskaffold dev命令时,可以通过传递--profile-pCLI 标志来激活此配置文件。如果运行以下命令,则 Skaffold 将使用Google Cloud Build来构建这些构件:

skaffold run -p gcb

请注意,我们在gcb配置文件下面没有指定deploy部分。这意味着 Skaffold 将继续使用kubectl进行部署。如果您的用例需要多个配置文件,您可以多次使用-p标志或传递逗号分隔的配置文件,如下面的命令所示:

skaffold dev -p profile1,profile2

让我们尝试使用另一个例子来理解这个。在这个例子中,我们将使用我们在第三章中构建的 Spring Boot 应用程序,Skaffold – Easy-Peasy Cloud-Native Kubernetes Application Development。在那种情况下,我们使用 Jib 来将应用程序容器化;然而,在这个例子中,我们将使用多阶段 Docker 构建来创建我们应用程序的精简 Docker 镜像。以下是我们 Spring Boot 应用程序的Dockerfile

FROM maven:3-adoptopenjdk-16 as build
RUN mkdir /app
COPY . /app
WORKDIR /app
RUN mvn clean package
FROM adoptopenjdk:16-jre
RUN mkdir /project
COPY --from=build /app/target/*.jar /project/app.jar
WORKDIR /project
ENTRYPOINT ["java","-jar","app.jar"]

我们可以解释多阶段Dockerfile构建如下:

  • 在构建的第一阶段中,我们使用maven:3-adoptopenjdk-16镜像使用mvn clean package Maven 命令构建和创建了我们的应用程序的jar

  • 在第二阶段,我们复制了在上一个构建阶段中制作的jar并基于一个明显更小的Java 16 JRE 基础镜像创建了一个新的最终镜像。

  • 最终的 Docker 镜像不包括 JDK 或 Maven 镜像,只包括 JRE 镜像。这种方法的唯一缺点是构建时间更长,因为在构建的第一阶段需要下载所有必需的依赖项。

提示

您可以使用 Docker 多阶段构建来创建更小的应用程序 Docker 镜像。典型的 JDK 镜像大小约为 650 MB,通过使用 JRE 作为多阶段构建的最后阶段的基础镜像,我们可以将其大小减半。

此外,您还可以使用 Java 工具如jdepsjlink(在 Java 9 中引入)进一步减小镜像的大小。jdeps帮助您识别所需的 JVM 模块,jlink允许您创建定制的 JRE。通过这些工具的组合,您可以创建一个定制的 JRE,从而使您的应用程序的 Docker 镜像更加精简。

为了演示配置文件的使用,我们将对skaffold.yaml文件进行以下更改。以下是我们在skaffold.yaml文件中添加了一个名为docker的新配置文件:

apiVersion: skaffold/v2beta20
kind: Config
metadata:
  name: skaffold-introduction
build:
  artifacts:
    - image: docker.io/hiashish/skaffold-introduction
      jib: { }
deploy:
  kubectl:
    manifests:
      - k8s/mydeployment.yaml
      - k8s/myservice.yaml
profiles:
  - name: docker
    build:
      artifacts:
        - image: docker.io/hiashish/skaffold-introduction
          docker:
            dockerfile: Dockerfile

我们将使用skaffold run --profile docker命令来构建和部署我们的 Spring Boot 应用程序。以下是输出:

Generating tags...
- docker.io/hiashish/skaffold-introduction -> docker.io/hiashish/skaffold-introduction:fcda757-dirty
Checking cache...
- docker.io/hiashish/skaffold-introduction: Not found. Building
Starting build...
Found [minikube] context, using local docker daemon.
Building [docker.io/hiashish/skaffold-introduction]...
Sending build context to Docker daemon  128.5kB
Step 1/10 : FROM maven:3-adoptopenjdk-16 as build
3-adoptopenjdk-16: Pulling from library/maven
........
ecf4fc483ced: Pull complete
Status: Downloaded newer image for maven:3-adoptopenjdk-16
---> 8bb5929b61c3
Step 2/10 : RUN mkdir /app
---> Running in ff5bf71356dc
---> 83040b88c925
Step 3/10 : COPY . /app
---> 5715636b31d8
Step 4/10 : WORKDIR /app
---> Running in 6de38bef1b56
---> ca82b0631625
Step 5/10 : RUN mvn clean package -DskipTests
---> Running in 91df70ce44fa
[INFO] Scanning for projects...
Downloading from repository.spring.milestone: https://repo.spring.io/milestone/org/springframework/boot/spring-boot-starter-parent/2.5.0-M1/spring-boot-starter-parent-2.5.0-M1.pom
........
[INFO] BUILD SUCCESS

在前面的日志中,您可以看到,首先,Skaffold 开始使用 Docker 构建我们的镜像。此外,我们使用了多阶段构建,然后在步骤 1 到 6 中,我们进入了构建的第一阶段,在其中我们在容器内创建了我们应用程序的jar

Step 6/10 : FROM adoptopenjdk:16-jre
16-jre: Pulling from library/adoptopenjdk
c549ccf8d472: Already exists
........
23bb7f46497d: Pull complete
Digest: sha256:f2d0e6433fa7d172e312bad9d7b46ff227888926f2fe526 c731dd4de295ef887
Status: Downloaded newer image for adoptopenjdk:16-jre
---> 954409133efc
Step 7/10 : RUN mkdir /project
---> Running in abfd14b21ac6
---> 2ab11f2093a3
Step 8/10 : COPY --from=build /app/target/*.jar /project/app.jar
---> 52b596edfac9
Step 9/10 : WORKDIR /project
---> Running in 473cbb6d878d
---> b06856859039
Step 10/10 : ENTRYPOINT ["java","-jar","app.jar"]
---> Running in 6b22aee242d2
---> f62822733ebd
Successfully built f62822733ebd
Successfully tagged hiashish/skaffold-introduction:fcda757-dirty

步骤 6 到 10中,我们处于构建的第二阶段,我们使用adoptopenjdk:16-jre作为基础镜像,因为我们只需要 JRE 来运行我们的应用程序。通常,JRE 镜像比 JDK 镜像要小。

这个最终的输出是我们的容器化应用程序,应该如下所示:

Starting test...
Tags used in deployment:
- docker.io/hiashish/skaffold-introduction -> docker.io/hiashish/skaffold-introduction:f62822733ebd832cab
 058e5b0282af6bb504f60be892eb074f980132e3630d88
Starting deploy...
- deployment.apps/skaffold-introduction created
- service/skaffold-introduction created
Waiting for deployments to stabilize...
- deployment/skaffold-introduction is ready.
Deployments stabilized in 4.378 seconds

最后,Skaffold 将我们的容器化应用部署到本地 Kubernetes 集群。

激活配置文件的另一种方法是在skaffold.yaml中使用激活对象数组自动激活配置文件,具体取决于以下内容:

  • kubeContext

  • 一个环境变量:env

  • 一个 Skaffold 命令

请参考以下截图:

图 4.3 - 使用 skaffold.yaml 文件激活 Skaffold 配置文件

图 4.3 - 使用 skaffold.yaml 文件激活 Skaffold 配置文件

让我们尝试使用一个例子来理解这个激活选项。

在下面的代码示例中,我们有两个配置文件——profile-stagingprofile-production。正如它们的名称所暗示的,profile-staging将用于分段环境,而profile-production将用于生产环境:

build:
  artifacts:
    - image: docker.io/hiashish/skaffold-introduction
      jib: { }
deploy:
  kubectl:
    manifests:
      - k8s/mydeployment.yaml
      - k8s/myservice.yaml
profiles:
  - name: profile-staging
    activation:
      - env: ENV=staging
  - name: profile-production
    build:
      googleCloudBuild:
        projectId: gke_projectid
    activation:
      - env: ENV=production
      - kubeContext: gke_cluster
        command: run

在这里,如果ENV环境变量键匹配值分段,profile-staging将自动激活。我们没有为此特定配置文件指定构建、测试和部署步骤,因此它将继续使用我们在skaffold.yaml文件的主要部分中提供的选项。除此之外,只有在满足以下条件时,profile-production才会自动激活。请注意,只有在满足所有这些条件时,它才会运行配置文件生产阶段:

  • ENV环境变量键匹配值生产。

  • Kubernetes 上下文设置为GKE(即Google Kubernetes Engine的缩写)。

  • 使用的 Skaffold 命令是scaffold run

请注意,profile-production将使用 Google 的 Cloud Build 进行构建,并默认使用kubectl进行部署(因为没有明确指定)。

这种分离还允许您在不同的环境中使用各种工具进行构建和部署。例如,您可能会在本地开发中使用 Docker 创建映像,而在生产中使用Jib。在部署的情况下,您可能会在开发中使用kubectl,而在生产中使用 Helm。

在上一章中,我解释了 Skaffold 默认情况下会从位于${HOME}/.kube/config路径的kube config文件中查找当前的 Kubernetes 上下文。如果您希望更改它,可以在运行skaffold dev命令时进行更改:

skaffold dev --kube-context <myrepo>

您还可以在skaffold.yaml文件中提到kubeContext,如下所示:

deploy:
  kubeContext: docker-desktop

通过 CLI 传递的标志优先于skaffold.yaml文件。

接下来,让我们讨论 Skaffold 如何配置或调整自己以适应不同的本地 Kubernetes 集群。

本地 Kubernetes 集群

到目前为止,您应该已经意识到 Skaffold 提供了明智的、智能的默认值,使开发过程更加轻松,而无需告诉它要做什么。如果您的 Kubernetes 上下文设置为本地 Kubernetes 集群,那么就没有必要将映像推送到远程 Kubernetes 集群。相反,Skaffold 将映像移动到本地 Docker 守护程序,以加快开发周期。

到目前为止,我们只讨论了与 Docker Desktop 一起提供的 Kubernetes 集群,但这并不是您唯一的选择。有多种方式可以设置和运行本地 Kubernetes 集群。例如,在创建本地 Kubernetes 集群时,您有以下选择:

如果这些支持的 Kubernetes 安装可用于本地开发,则 Skaffold 期望 Kubernetes 的上下文如下表所示。否则,它将假定我们正在部署到远程 Kubernetes 集群。

根据以下表格中描述的 Kubernetes 上下文名称,Skaffold 会检测本地集群:

表 4.1 - Skaffold 支持的 Kubernetes 上下文

表 4.1 - Skaffold 支持的 Kubernetes 上下文

然而,对于其他非标准的本地集群设置,比如使用自定义配置运行minikube(例如,minikube start -p my-profile),您可以使用以下命令告诉 Skaffold 您正在使用本地 Kubernetes 集群:

  1. 首先,使用以下命令为 Skaffold 设置 Docker 环境:
source <(minikube docker-env -p my-profile)
  1. 然后,使用以下命令指示 Skaffold 将my-profile视为本地集群:
$ skaffold config set --kube-context my-profile local-cluster true

在本节中,我们深入探讨了 Skaffold 提供的功能。现在,让我们讨论 Skaffold 的架构。

解密 Skaffold 的架构

如前所述,Skaffold 的设计考虑了可插拔性。以下是 Skaffold 架构的可视化:

图 4.4 - Skaffold 架构

图 4.4 - Skaffold 架构

从这个架构图中,您可以得出结论,Skaffold 具有模块化设计。但是,什么是模块化设计?

嗯,模块化设计,或者说设计中的模块化,是一种将系统细分为称为模块的较小部分的设计原则,这些模块可以独立创建、修改、替换或与其他模块或不同系统之间交换。

有了这个定义,我们可以为 Skaffold 定义以下模块:

  • 容器镜像构建器

  • 容器测试工具/策略

  • 容器映像标签器

  • 容器部署工具

现在,让我们更详细地讨论每个这些工具/模块。目前,Skaffold 支持以下容器映像构建器:

  • Dockerfile

  • Jib(Maven 和 Gradle)

  • Bazel

  • Cloud-Native Buildpacks

  • 自定义脚本

对于部署到 Kubernetes,Skaffold 支持以下工具:

  • Helm

  • kubectl

  • kustomize

我们将在第六章中更详细地讨论这些选项,使用 Skaffold 容器映像构建器和部署器

Skaffold 支持管道阶段之间的以下类型测试:

  • 自定义测试

  • 容器结构测试

我们将在第五章中进一步探讨这些选项,安装 Skaffold 并揭秘其流水线阶段

如前所述,在 Skaffold 的功能部分下,Skaffold 提供了内置的映像标签管理。目前,Skaffold 支持多个标签器和标签策略来对映像进行标记:

  • gitCommit标签器

  • inputDigest标签器

  • envTemplate标签器

  • datetime标签器

  • customTemplate标签器

  • sha256标签器

通过 IntelliJ Cloud Code 插件的代码完成功能,很容易知道支持哪种映像标签策略。假设您没有在skaffold.yaml文件中指定映像标签策略;在这种情况下,默认策略是gitCommit标签器:

看一下以下截图:

图 4.5 – Skaffold 支持的映像标签策略

图 4.5 – Skaffold 支持的映像标签策略

现在,考虑到 Skaffold 的可插拔架构,您可以使用本地 Docker 守护程序来构建映像,使用kubectl部署到minikube,或者任何其他支持的本地 Kubernetes 集群。在这种情况下,Skaffold 将不会将映像推送到远程注册表,您甚至可以通过使用-skipTests标志跳过容器结构测试。

以下图表显示了在这种情况下用于本地开发的工具:

图 4.6 – Skaffold 在开发中

图 4.6 – Skaffold 在开发中

而在生产场景中,您可能会使用 Jib Maven 或 Gradle 插件来构建映像,测试构件,将其推送到远程注册表,最后使用 Helm 将其部署到远程 Kubernetes 集群。

以下图表显示了生产场景中使用的工具:

图 4.7–生产中的 Skaffold

图 4.7–生产中的 Skaffold

这完成了我们对 Skaffold 架构的深入分析。现在,让我们讨论 Skaffold 的工作流程。

理解 Skaffold 的工作流程

通常,Skaffold 以两种模式工作,即连续开发端到端管道,通过命令如skaffold devskaffold run。例如,当您运行skaffold dev命令时,Skaffold 将执行以下步骤:

  1. 接收并监视您的源代码更改。

  2. 如果用户将更改的文件标记为可复制,则直接将其复制到build中。

  3. 从源代码构建您的构件。

  4. 使用container-structure-tests或自定义脚本测试您构建的构件。

  5. 为您的构件打标签。

  6. 推送您的构件(仅当 Kubernetes 上下文设置为远程集群时)。

  7. 使用正确的标签更新 Kubernetes 清单。

  8. 部署您的构件。

  9. 使用内置的健康检查监视部署的构件。

  10. 从正在运行的 pod 中流式传输日志。

  11. 通过按下Ctrl + C清除退出时部署的任何构件。

skaffold run命令的情况下,工作流程相对类似。唯一的区别是以下内容:

  • Skaffold 不会持续监视代码更改。

  • 默认情况下,Skaffold 不会从正在运行的 pod 中流式传输日志。

  • 在端到端管道模式结束后,Skaffold 将在步骤 9之后退出。

以下图表说明了我们在前面步骤中解释的连续开发和端到端管道:

图 4.8–Skaffold 工作流程

图 4.8–Skaffold 工作流程

现在,您应该了解了 Skaffold 在连续开发和端到端管道模式下的工作方式。让我们看一下skaffold.yaml文件中可用的组件。

使用 skaffold.yaml 解析 Skaffold 的配置

Skaffold 需要执行的任何操作都应在skaffold.yaml配置文件中明确定义。在此配置文件中,您必须指定 Skaffold 必须使用哪个工具来构建图像,然后将其部署到 Kubernetes 集群。Skaffold 通常期望在当前目录中找到配置文件skaffold.yaml;但是,我们可以使用--filename标志覆盖位置。

提示

我们建议您将 Skaffold 配置文件保存在项目的根目录中。

配置文件包括以下主要组件:

表 4.2 - skaffold.yaml 文件组件

表 4.2 - skaffold.yaml 文件组件

Skaffold 还支持一个全局配置文件,位于~/.skaffold/config路径下。以下是它支持的选项,可以在全局级别定义:

表 4.3 - Skaffold 全局配置选项

表 4.3 - Skaffold 全局配置选项

您可以使用以下命令轻松在命令行中列出、设置和取消这些选项:

$ skaffold config
Interact with the Skaffold configuration
Available Commands:
  list        List all values set in the global Skaffold config
  set         Set a value in the global Skaffold config
  unset       Unset a value in the global Skaffold config

例如,您可以将本地集群选项设置为 false。这将允许您在构建图像后将图像推送到远程注册表。请参考以下命令:

$ skaffold config set --global local-cluster false
set global value local-cluster to false
$ cat ~/.skaffold/config
global:
  local-cluster: false
  survey:
    last-prompted: "2021-03-20T13:42:49+05:30"
  collect-metrics: true

同样,您可以使用以下命令取消配置:

$ skaffold config unset --global local-cluster
unset global value local-cluster
$ cat ~/.skaffold/config
global:
  survey:
    last-prompted: "2021-03-20T13:42:49+05:30"
  collect-metrics: true
kubeContexts: []

在本节中,我们介绍了skaffold.yaml配置文件的组件。我们还看了一些可以通过 Skaffold CLI 命令设置的全局配置设置。

总结

本章向您介绍了 Skaffold 的一些特点,例如超快速的本地开发、轻松的远程开发、内置标签管理、轻量级能力和文件同步能力等。这些是令人信服的功能,将帮助您改善开发人员体验。此外,我们还看了 Skaffold 的架构,并发现 Skaffold 具有可插拔的架构。这意味着您可以随时携带自己的工具来构建和部署应用程序。接下来,我们介绍了 Skaffold 开发工作流程中通常发生的步骤。最后,在本章末尾,我们研究了 Skaffold 的主要组件和一些通过 Skaffold 配置支持的全局配置。

在本章中,主要目标是通过查看其架构和典型的开发工作流程,让您深入了解 Skaffold 的特性和内部工作原理。您已经对 Skaffold 有了深入的了解,现在您将更容易地连接前后章节之间的关系。

在下一章中,我们将介绍安装 Skaffold 的不同方法。此外,我们将探索 Skaffold CLI 命令。

参考

第五章:安装 Skaffold 并揭秘其流水线阶段

在上一章中,我们深入了解了 Skaffold 的架构和工作流程。我们还看了 Skaffold 的配置。本章将介绍如何在不同操作系统上安装 Skaffold,如 Linux、Windows 和 macOS。我们还将探讨常见的 CLI 命令以及如何在 Skaffold 的不同流水线阶段中使用这些命令。

在本章中,我们将讨论以下主要主题:

  • 安装 Skaffold

  • 理解常见的 CLI 命令

  • 理解 Skaffold 的流水线阶段

  • 使用 Skaffold 进行调试

在本章结束时,您将了解如何在不同平台上安装 Skaffold。您还将对 Skaffold 最常用的 CLI 命令有扎实的理解。

技术要求

要跟着本章的示例进行操作,您需要以下内容:

安装 Skaffold

Skaffold 作为一个 CLI 工具,需要首先在您喜欢的操作系统上下载和安装。以下是支持的平台,您可以在这些平台上下载和安装 Skaffold:

  • Linux

  • macOS

  • Windows

  • Docker

  • Google Cloud SDK

让我们详细讨论这些选项。

在 Linux 上安装 Skaffold

对于 Linux,您可以使用以下 URL 来下载最新的稳定版本 Skaffold:

下载二进制文件后,您可以将其添加到PATH变量中。或者,您可以使用以下命令。

对于 AMD64 上的 Linux,请使用以下命令:

curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 && \sudo install skaffold /usr/local/bin/

对于 ARM64 上的 Linux,请使用以下命令:

curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-arm64 && \sudo install skaffold /usr/local/bin/

还有一个最新版本的 Skaffold,它是使用最新提交构建的。它可能不是一个稳定的版本,所以在使用时要小心。您可以使用以下 URL 来下载最新版本的 Skaffold。

对于 AMD64 上的 Linux,请执行以下操作:

curl -Lo skaffold https://storage.googleapis.com/skaffold/builds/latest/skaffold-linux-amd64 && \sudo install skaffold /usr/local/bin/

对于 ARM64 架构的 Linux,请执行以下操作:

curl -Lo skaffold https://storage.googleapis.com/skaffold/builds/latest/skaffold-linux-arm64 && \sudo install skaffold /usr/local/bin/

在本节中,我们查看了在 Linux 操作系统(OS)上安装 Skaffold 的命令。

在 macOS 上安装 Skaffold

对于 macOS,您可以使用以下 URL 下载 Skaffold 的最新稳定版本:

下载二进制文件后,您可以将其添加到PATH变量中。或者,您可以使用以下命令。

对于 AMD64 架构的 macOS,请使用以下命令:

curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-darwin-amd64 && \sudo install skaffold /usr/local/bin/

对于 ARM64 架构的 macOS,请使用以下命令:

curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-darwin-amd64 && \sudo install skaffold /usr/local/bin/

要下载具有最新提交的构建,可以使用以下命令。

对于 AMD64 架构的 macOS,请使用以下命令:

curl -Lo skaffold https://storage.googleapis.com/skaffold/builds/latest/skaffold-darwin-amd64 && \sudo install skaffold /usr/local/bin/

对于 ARM64 架构的 macOS,请使用以下命令:

curl -Lo skaffold https://storage.googleapis.com/skaffold/builds/latest/skaffold-darwin-amd64 && \sudo install skaffold /usr/local/bin/

特别是对于 macOS,您可以使用以下软件包管理器下载 Skaffold。

对于 Homebrew,请使用以下命令:

brew install skaffold

对于 MacPorts,请使用以下命令:

sudo port install skaffold

在本节中,我们探讨了在 macOS 上安装 Skaffold 的各种命令。

在 Windows 上安装 Skaffold

对于 Windows,您可以使用以下 URL 下载 Skaffold 的最新稳定版本:

storage.googleapis.com/skaffold/releases/latest/skaffold-windows-amd64.exe

下载 EXE 文件后,您可以将其添加到PATH变量中。

要下载具有最新提交的构建,可以使用以下 URL:

storage.googleapis.com/skaffold/builds/latest/skaffold-windows-amd64.exe

特别是对于 Windows,您可以使用以下 Chocolatey 软件包管理器命令下载 Skaffold:

choco install -y skaffold

以下是输出:

图 5.1 - 在 Windows 上安装 Skaffold

图 5.1 - 在 Windows 上安装 Skaffold

注意

skaffold dev命令存在已知问题(https://github.com/chocolatey/shimgen/issues/32),在 Windows 上使用 Chocolatey 软件包管理器安装时,按下Ctrl + C时 Skaffold 不会清理部署。问题与 Skaffold 无关,而是与 Chocolatey 在安装过程中如何干扰Ctrl + C处理有关。

本节介绍了如何在 Windows 上安装 Skaffold。

使用 Docker 安装 Skaffold

您还可以下载并在 Docker 容器中运行 Skaffold。要做到这一点,您可以使用以下docker run命令:

docker run gcr.io/k8s-skaffold/skaffold:latest skaffold <command>

要使用最新提交的边缘构建,您可以使用以下命令:

docker run gcr.io/k8s-skaffold/skaffold:edge skaffold <command>

我想强调一个关于使用 Docker 图像的 Skaffold 的要点。 Docker 图像的大小约为~3.83 GB,这对于 Skaffold 来说似乎过大,因为在第三章中,Skaffold – Easy-Peasy Cloud-Native Kubernetes Application Development,我们了解到 Skaffold 的二进制大小约为~63 MB。这可以在以下截图中看到:

图 5.2 – Skaffold Docker 图像大小

图 5.2 – Skaffold Docker 图像大小

那么,为什么图像大小如此之大?这是因为图像还包含其他工具,如 gcloud SDK,kind,minikube,k3d,kompose 和 bazel 等。

您可以使用 Dive CLI 验证容器图像中的内容。

提示

Dive 允许您检查图像层的内容,并建议不同的方法来缩小图像的大小,如果您浪费了任何空间。

您可以按照github.com/wagoodman/dive#installation上的说明下载 Dive。运行以下命令以查看容器图像的内部视图:

$ dive image tag/id/digest

以下是 Skaffold docker 图像的输出,其中包含一个图像层:

图 5.3 – Skaffold Docker 图像层

图 5.3 – Skaffold Docker 图像层

从图像内部的层可以看出,我们有许多可用的工具,而不仅仅是 Skaffold。使用此 Docker 图像的另一个优势是,您不必单独安装这些工具,而且可以使用相同的图像来玩耍或尝试这些工具。

本节介绍了如何使用 Docker 图像安装 Skaffold。

使用 gcloud 安装 Skaffold

Google 开发了 Skaffold,因此它很好地适应了 Google 产品生态系统。如果您的机器上安装了Google 的 Cloud SDK,您可以使用gcloud components install skaffold命令来安装 Skaffold。

我们将在第八章中介绍如何安装 gcloud SDK,使用 Skaffold 将 Spring Boot 应用部署到 Google Kubernetes Engine。目前,我们可以假设 Cloud SDK 已经安装。您可以使用gcloud list命令查看已安装和未安装的组件。以下是输出:

图 5.4 – gcloud list 命令输出

图 5.4 – gcloud list 命令输出

从前面的输出可以清楚地看出,Skaffold 未安装。虽然这不是强制性的,但在我们继续安装之前,请确保已安装gcloud并且其组件是最新的。我们可以运行以下命令来执行此操作:

gcloud components update

最后,我们可以使用以下gcloud命令安装 Skaffold:

gcloud components install skaffold

以下是输出:

图 5.5 – 通过 gcloud 安装 Skaffold

图 5.5 – 通过 gcloud 安装 Skaffold

在本节中,我们讨论了安装 Skaffold 的不同方法。现在,让我们讨论 Skaffold CLI 命令。

理解常见的 CLI 命令

到目前为止,我们已经向您介绍了诸如skaffold devskaffold run之类的命令,但是还有许多类似的命令,您可以在 CI/CD 流水线中使用这些命令来创建端到端的流水线或单独使用。我们将把这些命令分类如下。您还可以通过启用skaffold completion bash/zsh命令并在输入命令后按Tab键来发现这些命令的支持选项:

  • 端到端流水线的命令

  • skaffold run:此命令允许您构建和部署一次。

  • skaffold dev:此命令允许您触发用于构建和部署的持续开发循环。此工作流将在退出时清理。

  • skaffold debug:此命令允许您以调试模式触发用于构建和部署流水线的持续开发循环。此工作流也将在退出时清理。

  • CI/CD 流水线的命令

  • skaffold build:此命令允许您只构建、标记和推送您的镜像。

  • skaffold test:此命令允许您针对构建的应用程序镜像运行测试。

  • skaffold deploy:此命令允许您部署给定的镜像。

  • skaffold delete:此命令允许您清理已部署的构件。

  • skaffold render:此命令允许您构建应用程序映像,然后将经过填充(使用新构建的映像标签)的 Kubernetes 清单导出到文件或终端。

  • skaffold apply:此命令以模板化的 Kubernetes 清单作为输入,在目标集群上创建资源。

  • 入门命令

  • skaffod init:此命令允许您引导 Skaffold 配置。

  • skaffold fix:此命令允许您升级模式版本。

  • 其他命令

  • skaffold help:此命令允许您打印帮助信息。使用skaffold options获取全局命令行选项的列表(适用于所有命令)。

  • skaffold version:此命令允许您获取 Skaffold 的版本。

  • skaffold completion:此命令允许您为 CLI 设置选项卡完成。它支持与skaffold version相同的选项。

  • skaffold config:此命令允许您管理特定上下文的参数。它支持与skaffold version相同的选项。

  • skaffold credits:此命令允许您将第三方通知导出到指定路径(默认为./skaffold-credits)。它支持与skaffold version相同的选项。

  • skaffold diagnose:此命令允许您运行对 Skaffold 在您的项目中的诊断。

  • skaffold schema:此命令允许您列出并打印用于验证skaffold.yaml配置的 JSON 模式。它支持与skaffold version相同的选项。

在本节中,我们讨论了 Skaffold 命令及其用法。在下一节中,我们将尝试了解 Skaffold 的不同流水线阶段。

了解 Skaffold 流水线阶段

到目前为止,我们已经对 Skaffold 的工作原理有了基本的了解。从前面的章节中,我们知道它会选择项目中的源代码更改,并使用您选择的工具创建容器映像;一旦成功构建,这些映像将根据您的要求进行标记,并推送到您指定的存储库。Skaffold 还可以帮助您在工作流程结束时将这些构件部署到您的 Kubernetes 集群中,再次使用您喜欢的工具。

Skaffold 允许您跳过阶段。例如,如果您在本地使用 Minikube 或 Docker 桌面运行 Kubernetes,Skaffold 足够智能,会为您做出选择,并不会将构件推送到远程存储库。

让我们详细了解 Skaffold 的流水线阶段,以了解每个流水线阶段中我们还有哪些选择。Skaffold 流水线阶段可以大致分为以下几个领域:

  • 初始化

  • 构建

  • 标签

  • 测试

  • 部署

  • 文件

  • 日志尾随

  • 端口转发

  • 清理

让我们详细讨论每个。

初始化阶段

在这个阶段,我们通常会生成一个基本的 Skaffold 配置文件,以便在几秒钟内启动和运行您的项目。Skaffold 会查看您的项目目录中是否有任何构建配置文件,比如Dockerfilebuild.gradlepom.xml,然后自动生成构建和部署配置。

Skaffold 目前支持以下构建工具的构建检测:

  • Docker

  • Jib

  • Buildpacks

如果 Skaffold 检测到多个构建配置文件,它将提示您将构建配置文件与在部署配置中检测到的任何镜像配对。

提示

从 Skaffold v1.27.0 开始,您不再需要在skaffold init命令中提供XXenableJibInitXXenableBuildpacksInit标志,因为它们的默认值已设置为true。这也意味着init命令将检测您是否应该根据项目使用 Jib 或 Buildpacks,无需指定这些标志。

例如,在运行skaffold init命令后,您可能会被要求从以下选项中选择。在这个例子中,我们在根目录中有一个Dockerfile,所以 Skaffold 要求您选择此项目的构建配置:

图 5.6 – skaffold init 提示

图 5.6 – skaffold init 提示

同样,对于部署,Skaffold 将查看您的项目目录,如果检测到一些 Kubernetes 清单 – 即deployment.yamlsevice.yaml – 已经存在,那么它将自动将它们添加到skaffold.yaml文件的deploy部分:

图 5.7 – 生成 Skaffold 配置文件

图 5.7 – 生成 Skaffold 配置文件

如果您没有准备好清单,但希望 Skaffold 处理清单生成部分,那么不用担心 – 您可以在skaffold init命令中传递--generate-manifests标志。

构建阶段

Skaffold 支持各种工具进行镜像构建。

从下表中,您可以了解到镜像构建可以在本地、集群中或远程使用 Google Cloud Build 进行:

表 5.1– Skaffold 支持的容器镜像构建工具

表 5.1– Skaffold 支持的容器镜像构建器

我们将在第六章中了解更多关于这些选项的内容,使用 Skaffold 容器镜像构建器和部署器。在集群中,构建由 kaniko 或使用自定义脚本支持。远程构建仅支持使用 Cloud Build 的 Dockerfile、Jib 和 Buildpacks。对于本地构建,您几乎可以使用任何受支持的图像构建方法。

您可以通过skaffold.yaml文件的build部分设置构建配置。以下是一个示例:

build:
  artifacts:
    - image: docker.io/hiashish/skaffold-introduction
      jib: {}

既然我们已经讨论了构建阶段,接下来,我们将看一下标记阶段。

标记阶段

Skaffold 支持以下图像标记策略:

  • 标记可通过gitCommit 标记器进行,它利用 Git 提交来标记图像。

  • 标记可通过sha256 标记器进行,该标记器使用最新标记来标记图像。

  • 标记可通过envTemplate 标记器进行,它使用环境变量来标记图像。

  • 标记可通过dateTime 标记器进行,它接受当前的日期和时间以及可配置的模式。

  • 标记可通过customTemplate 标记器进行,它使用现有标记器作为模板的组件组合。

可以使用skaffold.yamlbuild部分中的tagPolicy字段来配置图像标记策略。如果未指定tagPolicy,则默认为gitCommit策略。请参考以下代码片段:

build:
  artifacts:
    - image: docker.io/hiashish/skaffold-introduction
      jib: {}
  tagPolicy: 
    sha256: {}

既然我们已经了解了 Skaffold 的不同图像标记策略,我们将进入测试阶段。

测试阶段

Skaffold 在构建和部署之间有一个集成测试阶段。它支持容器结构测试和集成测试的自定义测试。让我们详细讨论一下。

容器结构测试

Skaffold 支持在使用 Skaffold 构建的容器镜像上运行容器结构测试(https://github.com/GoogleContainerTools/container-structure-test)。容器结构测试框架主要旨在验证容器的内容和结构。例如,我们可能想在容器内运行一些命令,以测试它是否成功执行。我们可以在 Skaffold 配置中为每个图像定义测试。构建完毕后,Skaffold 将在该图像上运行相关的结构测试。如果测试失败,Skaffold 将不会继续部署。

自定义测试

使用 Skaffold 自定义测试,开发人员可以在其开发循环的一部分运行自定义命令。自定义测试将在将镜像部署到 Kubernetes 集群之前运行。该命令将在执行 Skaffold 的本地机器上执行,并与所有支持的 Skaffold 平台一起工作。您可以使用--skip-tests标志选择不运行自定义测试。您可以使用skaffold test命令单独运行测试。

以下是自定义测试的一些用例:

  • 运行单元测试

  • 使用 GCP Container Analysis 或 Anchore Grype 在图像上运行验证和安全扫描

  • 我们还可以使用kubevalgithub.com/instrumenta/kubeval)或kubeconformgithub.com/yannh/kubeconform)等工具,在部署前验证 Kubernetes 清单。

  • 在 Helm 图表的情况下,我们可以在部署前使用helm lint命令。

在以下示例中,我们有一个名为test的配置文件,并且我们正在使用mvn test命令运行各种测试。我们将在此处使用skaffold dev --profile=test命令,该命令在构建后和部署前运行测试:

profiles:
  - name: test
    test:
      - image: docker.io/hiashish/skaffold-introduction
        custom:
          - command: mvn test -Dmaven.test.skip=false

在日志中,您将看到以下内容,其中说明测试已经开始,并且没有失败:

Starting test...
Testing images...
Running custom test command: "mvn test -Dmaven.test.skip
=false"
[INFO] Results:
[INFO] 
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0

有了这些,我们已经学会了如何使用 Skaffold 执行自定义测试。在部署阶段,我们将学习如何使用 Skaffold 部署应用程序。

部署阶段

Skaffold 部署阶段通常通过将 Kubernetes 清单中的未标记的镜像名称替换为最终标记的镜像名称来呈现 Kubernetes 清单。它还可能通过扩展 helm 的模板或计算 kustomize 的叠加来进行额外的中间步骤。然后,Skaffold 将最终的 Kubernetes 清单部署到集群中。为了确保部署发生,理想情况下,它将通过健康检查等待部署的资源稳定。

健康检查默认启用,并且是 CI/CD 流水线用例的一个重要功能,以确保部署的资源健康,并且可以在流水线中进一步进行。Skaffold 内部使用kubectl rollout status命令来测试部署的状态。

例如,在以下日志中,您可以看到 Skaffold 等待部署稳定:

Starting test...
Tags used in deployment:
 - docker.io/hiashish/skaffold-introduction -> docker.io/hiashish/skaffold-introduction:fcda757-dirty@sha256:f07c1dc192 cf5f391a1c5af8dd994b51f7b6e353a087cbcc49e754367c8825cc
Starting deploy...
 - deployment.apps/skaffold-introduction created
 - service/skaffold-introduction created
Waiting for deployments to stabilize...
 - deployment/skaffold-introduction: 0/4 nodes are available: 2 Insufficient memory, 4 Insufficient cpu.
    - pod/skaffold-introduction-59b479ddcb-f8ljj: 0/4 nodes are available: 2 Insufficient memory, 4 Insufficient cpu.
 - deployment/skaffold-introduction is ready.
Deployments stabilized in 56.784 seconds
Press Ctrl+C to exit
Watching for changes...

Skaffold 目前支持以下工具,用于将应用程序部署到本地或远程 Kubernetes 集群:

  • kubectl

  • helm

  • kustomize

您可以通过skaffold.yaml文件的deploy部分设置部署配置,如下所示:

deploy:
  kubectl:
    manifests:
      - k8s/mydeployment.yaml
      - k8s/myservice.yaml

通过这样,我们学会了如何使用 Skaffold 将镜像部署到 Kubernetes。接下来,我们将探讨如何使用文件同步直接将更改同步到 pod,而无需重新构建和重新部署镜像。

文件同步

Skaffold 具有一个很棒的功能,可以将更改的文件复制到部署的容器中,而无需重新构建、重新部署和重新启动相应的 pod。我们可以通过在skaffold.yaml文件的构件中添加带有同步规则的sync部分来启用此文件复制功能。在内部,Skaffold 创建一个包含与我们在skaffold.yaml文件中定义的同步规则匹配的更改文件的.tar文件。然后,这个.tar文件被传输到相应的容器中并在其中解压。

Skaffold 支持以下类型的同步:

  • manual:在此模式下,我们需要指定本地源文件路径和运行容器的目标路径。

  • infer:在此模式下,Skaffold 将通过查看您的 Dockerfile 来推断目标路径。在同步规则下,您可以指定哪些文件适合同步。

  • auto:在此模式下,Skaffold 将自动生成已知文件类型的同步规则。

为了理解文件同步功能,我们将使用我们在第三章中构建的 Spring Boot 应用程序,Skaffold – Easy-Peasy Cloud-Native Kubernetes Application Development。Spring Boot 应用程序公开了一个/states REST 端点,将返回所有印度各邦及其首府。我们在skaffold.yaml文件中添加了一个名为 sync 的新配置文件。

在下面的skaffold.yaml文件中,我们使用jib作为镜像构建器。Jib 与 Skaffold 集成允许您在更改后自动同步类文件、资源文件和 Jib 的额外目录文件到远程容器。但是,它只能与 Jib 一起在默认构建模式(exploded)下用于非 WAR 应用程序,因为存在一些限制。您还需要在项目中添加 Spring Boot 开发工具依赖项才能使其工作。它还可以与任何能够重新加载或重启的嵌入式服务器一起工作:

apiVersion: skaffold/v2beta20
kind: Config
metadata:
  name: skaffold-introduction
build:
  artifacts:
    - image: docker.io/hiashish/skaffold-introduction
      jib: { }
deploy:
  kubectl:
    manifests:
      - k8s/mydeployment.yaml
      - k8s/myservice.yaml
profiles:
  - name: sync
    build:
      artifacts:
        - image: docker.io/hiashish/skaffold-introduction
          jib: {}
          sync: 
            auto: true

在 Spring Boot 应用程序中,我们故意将班加罗尔的名称更改为班加罗尔。在运行skaffold dev --profile=sync命令后,您将在输出中看到以下内容:

图 5.8 - 同步前的输出

图 5.8 - 同步前的输出

现在,由于我们将 Jib 的自动同步设置为true,对schema.sql文件所做的任何更改都将直接与在 Kubernetes 集群内运行的 pod 同步。我们对schema.sql文件进行了更改,它们通过重新启动应用程序直接与运行中的 pod 同步。在这里,我们不必重新构建镜像、推送镜像、重新部署镜像或重新启动 pod。在进行此更改后,您将在控制台的流式日志中看到以下输出:

: Completed initialization in 3 ms
[skaffold-introduction] 2021-07-18 21:07:03.279  INFO 1 --- [nio-8080-exec-1] c.p.c.indianstates.StateController       : Getting all states.
Syncing 1 files for docker.io/hiashish/skaffold-introduction:fcda757-dirty@sha256:f07c1dc192cf5f391a1c5af8d
d994b51f7b6e353a087cbcc49e754367c8825cc
Watching for changes...

再次访问 URL 后,您将看到更改后的输出:

图 5.9 - 同步后的输出

图 5.9 - 同步后的输出

schema.sql在我们的资源下,所以让我们看看当我们对 Java 类文件进行更改时,是否也会被同步。让我们试一试。

为了测试这一点,我将调整我们在StateController类中的日志记录语句。我们有以下日志记录语句:

LOGGER.info("Getting all states.");

我们将其更改为以下内容:

LOGGER.info("Getting all Indian states and their capitals.");

在进行这些更改后,您应该在控制台的流式日志中看到以下内容。您可能会想知道为什么有五个文件被同步,因为我们只改变了一个文件。嗯,原因是 Jib 传输了整个层,其中包含您的类文件:

: Completed initialization in 3 ms
[skaffold-introduction] 2021-07-18 21:19:52.941  INFO 1 --- [nio-8080-exec-2] c.p.c.indianstates.StateController       : Getting all states.
Syncing 5 files for docker.io/hiashish/skaffold-introduction:fcda757-dirty@sha256:f07c1dc192cf5f391a1c5af
8dd994b51f7b6e353a087cbcc49e754367c8825cc
Watching for changes...

同样,在流式日志中,我们将看到更改后的日志记录语句。

[skaffold-introduction] 2021-07-18 21:40:46.868  INFO 1 --- [nio-8080-exec-1] c.p.c.indianstates.StateController       : Getting all Indian states and their capitals.

通过这样,我们已经了解了 Skaffold 的直接文件同步功能。现在,让我们了解如何使用各种 Skaffold 命令尾随日志。

日志尾随

Skaffold 可以为其构建和部署的容器尾随日志。有了这个功能,当您执行skaffold devskaffold debugskaffold run时,您可以从集群尾随日志到本地机器。

默认情况下,skaffold devskaffold debug模式启用了日志尾随。对于 skaffold run,您可以使用--tail标志显式启用日志尾随。

对于典型的 Spring Boot 应用程序,您将在使用skaffold dev构建和部署后,在尾随日志中看到以下内容。

在下面的日志中,您可以看到成功构建并部署到集群后,应用程序日志被流式传输到控制台:

Starting test...
Tags used in deployment:
 - docker.io/hiashish/skaffold-introduction -> docker.io/hiashish/skaffold-introduction:fcda757-dirty@sha256:f07c1dc1 92cf5f391a1c5af8dd994b51f7b6e353a087cbcc49e754367c8825cc
Starting deploy...
 - deployment.apps/skaffold-introduction created
 - service/skaffold-introduction created
Waiting for deployments to stabilize...
 - deployment/skaffold-introduction: 0/4 nodes are available: 2 Insufficient memory, 4 Insufficient cpu.
    - pod/skaffold-introduction-59b479ddcb-f8ljj: 0/4 nodes are available: 2 Insufficient memory, 4 Insufficient cpu.
 - deployment/skaffold-introduction is ready.
Deployments stabilized in 56.784 seconds
Press Ctrl+C to exit
Watching for changes...
[skaffold-introduction]  
[skaffold-introduction] 2021-07-18 21:06:44.072  INFO 1 --- [  restartedMain] c.p.c.i.IndianStatesApplication          : Starting IndianStatesApplication using Java 16-ea on skaffold-introduction-59b479ddcb-f8ljj with PID 1 (/app/classes started by root in /)

此时,我们知道了如何使用 Skaffold 从运行的容器中尾随日志。接下来,让我们讨论 Skaffold 的端口转发。

端口转发

Skaffold 支持在开发、调试、部署或运行模式下自动转发服务和用户定义的端口转发。您不必暴露端点来访问您的应用程序。端口转发对于本地开发非常有帮助。Skaffold 在内部使用kubectl port-forward来实现端口转发。您可以在skaffold.yaml中明确定义自定义端口转发,或者在运行skaffold devdebugrundeploy时传递--port-forward标志。

以下是用户定义的端口转发的示例。在这个例子中,Skaffold 将尝试将端口8080转发到localhost:9000。如果由于某种原因端口9000不可用,那么 Skaffold 将转发到一个随机开放的端口:

profiles:
  - name: userDefinedPortForward
    portForward:
      - localPort: 9090
        port: 8080
        resourceName: reactive-web-app
        resourceType: deployment

在完成工作后,清理我们使用 Skaffold 创建的资源是一个好习惯。现在,让我们学习如何使用 Skaffold 清理和删除 Kubernetes 资源。

清理

通过skaffold runskaffold dev命令,我们可以在 Kubernetes 集群中创建资源,在本地 Docker 守护程序上创建图像,并有时将图像推送到远程注册表。做所有这些工作可能会对您的本地和部署环境产生副作用,您可能会在本地环境中占用大量磁盘空间。

Skaffold 提供了清理功能来中和其中一些副作用:

  • 您可以通过运行skaffold delete来清理 Kubernetes 资源,或者通过使用Ctrl + C来执行自动清理skaffold devskaffold debug

  • 可以通过传递--no-prune=false标志来为本地 Docker 守护程序镜像启用图像修剪。由于默认情况下启用了工件缓存,您需要禁用该功能才能进行清除。您需要运行的实际命令是skaffold dev --no-prune=false --cache-artifacts=false。通过按下skaffold devskaffold debugCtrl + C,Skaffold 将自动清理存储在本地 Docker 守护程序上的图像。

  • 对于已推送到远程容器注册表的图像,用户必须负责清理工作。

例如,为了测试图像修剪,我们可以使用以下docker配置文件来使用我们的本地 Docker 守护程序构建图像:

  - name: docker
    build:
      artifacts:
        - image: docker.io/hiashish/skaffold-introduction
          docker:
            dockerfile: Dockerfile

然后,我们可以运行skaffold dev --no-prune=false --cache-artifacts=false命令。构建和部署后,我们可以按下Ctrl + C,这应该清除图像并删除任何 Kubernetes 资源。在以下日志中,您可以看到按下Ctrl + C后,Skaffold 开始删除 Kubernetes 资源并清除图像:

Cleaning up...
 - deployment.apps "skaffold-introduction" deleted
 - service "skaffold-introduction" deleted
Pruning images...

在本节中,我们深入探讨了 Skaffold 流水线阶段,如 init、build 和 deploy 等。在下一节中,我们将讨论使用 Skaffold 部署到 Kubernetes 集群的应用程序的调试。

使用 Skaffold 进行调试

Skaffold 支持在 Kubernetes 上运行的容器化应用程序进行调试,使用skaffold debug命令。Skaffold 为不同容器的运行时技术提供调试支持。一旦启用了调试,相关的调试端口将被暴露和标记为要转发到本地机器。IntelliJ IDE 的插件,比如 Cloud Code,内部使用 Skaffold 为您的语言添加和附加正确的调试器。

然而,在调试模式下,skaffold debug将禁用图像重建和同步,因为这可能会导致调试会话在保存文件更改时意外终止。您可以使用--auto-build--auto-deploy--auto-sync标志允许图像重建和同步。

skaffold debug命令支持以下语言和运行时:

  • Go 1.13+(运行时 ID:go)并使用 Delve

  • Node.js(运行时 ID:nodejs)并使用 Node.js Inspector Chrome DevTools

  • Java 和 JVM 语言(运行时 ID:jvm)并使用 JDWP

  • Python 3.5+(运行时 ID:python)并使用debugpy(调试适配器协议)或pydevd

  • .NET Core(运行时 ID:netcore)使用vsdbg

在 IDE 中,比如 IntelliJ,一旦启动应用程序,您需要将远程 Java 应用程序配置添加到您的运行/调试配置中。您还必须选择在启动应用程序时定义的端口/地址。然后,您就可以开始调试了:

[skaffold-introduction] Picked up JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n,quiet=y
Port forwarding pod/skaffold-introduction-766df7f799-dmq4t in namespace default, remote port 5005 -> 127.0.0.1:5005

在 IntelliJ 中,设置断点后,您应该看到以下内容。在断点处,一旦调试会话已激活,您应该看到对号:

图 5.10 – 已启用断点

图 5.10 – 已启用断点

调试控制台日志中,一旦调试会话开始,您应该看到以下内容。现在,您已经准备好调试您的应用程序了:

图 5.11 – 调试器已连接

图 5.11 – 调试器已连接

在本节中,我们深入探讨了 Skaffold 的调试能力。我们还学习了如何使用skaffold debug命令调试我们应用程序的容器化版本。您还可以使用 Cloud Code IntelliJ 插件进行调试,我们将在第七章中介绍,即使用 Cloud Code 插件构建和部署 Spring Boot 应用程序。

总结

在本章中,我们首先发现了在不同操作系统上安装 Skaffold 的各种方法。我们涵盖了流行操作系统(如 macOS、Windows 和 Linux)的安装。然后,我们看了一些 Skaffold 支持的帮助构建和部署 Kubernetes 应用程序的各种命令。我们还涵盖了一些杂项和日常命令。然后,我们发现了不同的 Skaffold 流水线阶段,比如 init、build 和 deploy 等。最后,我们讨论了如何使用skaffold dev等命令调试应用程序。

在下一章中,我们将讨论 Skaffold 容器镜像构建器(Dockerfile、kaniko、Buildpacks、Jib)和部署器(Helm、kubectl、kustomize)。

进一步阅读

如果您想了解更多关于 Skaffold 的信息,请查看其文档 https://skaffold.dev/docs/。

第六章:使用 Skaffold 容器映像构建器和部署器

在上一章中,我们深入研究了 Skaffold CLI 及其流水线阶段。我们还研究了 Skaffold 配置。在本章中,我们将通过创建一个 Reactive Spring Boot CRUD 应用程序来向您介绍响应式编程。然后,我们将了解 Skaffold 的可插拔架构,该架构支持不同的构建和部署容器映像到 Kubernetes 集群的方法。

在本章中,我们将涵盖以下主要主题:

  • 创建一个 Reactive Spring Boot CRUD 应用程序

  • 使用 Skaffold 容器映像构建器

  • 探索 Skaffold 容器映像部署器

在本章结束时,您将对 Skaffold 支持的容器映像构建器(包括 Jib、Docker 和 Buildpacks)有了扎实的理解。您还将了解到 Helm、kubectl 和 Kustomize,这些工具由 Skaffold 支持,帮助您将容器化的应用程序部署到 Kubernetes。

技术要求

要跟随本章中的示例,您将需要以下内容:

您可以从本书的 GitHub 存储库github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-Using-Skaffold/tree/main/Chapter06下载本章的代码示例。

创建一个 Reactive Spring Boot CRUD 应用程序

为了演示使用 Skaffold 支持的各种容器镜像构建器,我们将创建一个简单的 Reactive Spring Boot CRUD REST 应用程序。当应用程序通过 curl 或 Postman 等 REST 客户端在本地访问时,我们会暴露一个名为/employee的 REST 端点,它将返回员工数据。

首先,为了建立一些上下文,让我们讨论一下构建应用程序的反应式方式。反应式编程(https://projectreactor.io/)是构建非阻塞应用程序的一种新方式,它是异步的、事件驱动的,并且需要少量线程来扩展。它们与典型的非反应式应用程序的另一个区别是,它们可以提供背压机制,以确保生产者不会压倒消费者。

Spring WebFlux 是一个反应式 Web 框架,是在 Spring 5 中引入的。Spring WebFlux 不需要 servlet 容器,可以在非阻塞容器(如 Netty 和 Jetty)上运行。我们需要添加spring-boot-starter-webflux依赖项来添加对 Spring WebFlux 的支持。使用 Spring MVC 时,我们有 Tomcat 作为默认的嵌入式服务器,而使用 WebFlux 时,我们得到 Netty。Spring WebFlux 控制器通常返回反应式类型,即 Mono 或 Flux,而不是集合或领域对象。

以下是将用于此 Spring Boot 应用程序的 Maven 依赖项:

图 6.1 - Maven 依赖项

图 6.1 - Maven 依赖项

让我们从应用程序的代码开始讲解:

  1. 在这里,我们有一个包含五列的员工表:idfirst_namelast_nameagesalaryid列是自动递增的。其他列遵循默认的蛇形命名方案。以下的schema.sql SQL 文件位于源代码目录中的src/main/resources/schema.sql路径下:
DROP TABLE IF EXISTS employee ;
CREATE TABLE employee ( id SERIAL PRIMARY KEY, first_name VARCHAR(100) NOT NULL,last_name VARCHAR(100) NOT NULL, age integer,salary decimal);

由于 H2 驱动程序位于类路径上,我们不必指定连接 URL,Spring Boot 会在应用程序启动时自动启动嵌入式 H2 数据库。

  1. 为了在应用程序启动时初始化数据库架构,我们还需要注册ConnectionFactoryInitializer来获取schema.sql文件,如下面我们应用程序的主类所述。在这里,我们还保存了一些Employee实体,以便以后使用:
@SpringBootApplication
public class ReactiveApplication {
    private static final Logger logger =LoggerFactory.
      getLogger(ReactiveApplication.class);
    public static void main(String[] args) {
      SpringApplication.run(ReactiveApplication.class,
        args);
    }
    @Bean
    ConnectionFactoryInitializer initializer
      (ConnectionFactory connectionFactory) {
      ConnectionFactoryInitializer initializer = new
      ConnectionFactoryInitializer();
      initializer.setConnectionFactory
        (connectionFactory);
        initializer.setDatabasePopulator(new
        ResourceDatabasePopulator(new
        ClassPathResource("schema.sql")));
        return initializer;
    }
    @Bean
    CommandLineRunner init(EmployeeRepository
      employeeRepository) {
        return args -> {
            List<Employee> employees =  List.of(
                new Employee("Peter", "Parker", 25,
                      20000),
                new Employee("Tony", "Stark", 30,
                      40000),
                new Employee("Clark", "Kent", 31,
                      60000),
                new Employee("Clark", "Kent", 32,
                      80000),
                    new Employee("Bruce", "Wayne", 33,
                      100000)
            );
            logger.info("Saving employee " +
              employeeRepository.saveAll
                (employees).subscribe());
        };
    }
}
  1. 使用 Spring Data R2DBC,您不必编写存储库接口的实现,因为它会在运行时为您创建一个实现。EmployeeRepository扩展了ReactiveCrudRepository,并继承了使用响应式类型保存、删除和查找员工实体的各种方法。以下是 CRUD 存储库:
import com.example.demo.model.Employee;
import org.springframework.data.repository.reactive.Reactive
  CrudRepository;
    public interface EmployeeRepository extends
      ReactiveCrudRepository<Employee,Long> {
}

以下是EmployeeService类:

import com.example.demo.model.Employee;
import com.example.demo.repository.EmployeeRepository;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class EmployeeService {
    private final EmployeeRepository 
      employeeRepository;
    public EmployeeService(EmployeeRepository
      employeeRepository) {
        this.employeeRepository = employeeRepository;
    }
    public Mono<Employee> createEmployee(Employee
      employee) {
        return employeeRepository.save(employee);
    }
    public Flux<Employee> getAllEmployee() {
        return employeeRepository.findAll();
    }
    public Mono<Employee> getEmployeeById(Long id) {
        return employeeRepository.findById(id);
    }
    public Mono<Void> deleteEmployeeById(Long id) {
        return employeeRepository.deleteById(id);
    }
}
  1. 在以下 REST 控制器类中,您可以看到所有端点都返回 Flux 或 Mono 响应式类型:
import com.example.demo.model.Employee;
import com.example.demo.service.EmployeeService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/employee")
public class EmployeeController {
    private final EmployeeService employeeService;
    public EmployeeController(EmployeeService 
      employeeService) {
        this.employeeService = employeeService;
    }
    @GetMapping
    public Flux<Employee> getAllEmployee() {
        return employeeService.getAllEmployee();
    }
    @PostMapping
    public Mono<Employee> createEmployee(@RequestBody
      Employee employee) {
        return
          employeeService.createEmployee(employee);
    }
    @GetMapping("/{id}")
    public Mono<ResponseEntity<Employee>> 
      getEmployee(@PathVariable Long id) {
        Mono<Employee> employee =
          employeeService.getEmployeeById(id);
        return employee.map(e -> ResponseEntity.ok(e))
          .defaultIfEmpty(ResponseEntity.
            notFound().build());
    }
    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> 
      deleteUserById(@PathVariable Long id) {
        return employeeService.deleteEmployeeById(id)
            .map(r ResponseEntity.ok().
               <Void>build())
            .defaultIfEmpty(ResponseEntity.notFound()
.              build());
    }
}

以下是Employee领域类:

public class Employee {
    @Id
    private Long id;
    private String firstName;
    private String lastName;
    private int age;
    private double salary;
    public Employee(String firstName, String lastName,
      int age, double salary) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.salary = salary;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
}
  1. 让我们使用mvn spring-boot:run命令运行此应用程序。一旦应用程序启动运行,您将看到以下日志:
2021-07-13 20:40:12.979  INFO 47848 --- [           main] com.example.demo.ReactiveApplication     : No active profile set, falling back to default profiles: default
2021-07-13 20:40:14.268  INFO 47848 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2021-07-13 20:40:14.379  INFO 47848 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 102 ms. Found 1 R2DBC repository interfaces.
2021-07-13 20:40:17.627  INFO 47848 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080
2021-07-13 20:40:17.652  INFO 47848 --- [           main] com.example.demo.ReactiveApplication     : Started ReactiveApplication in 5.889 seconds (JVM running for 7.979)
2021-07-13 20:40:17.921  INFO 47848 --- [           main] com.example.demo.ReactiveApplication     : Saving employee reactor.core.publisher.LambdaSubscriber@7dee835

访问/employee REST 端点后的输出如下:

图 6.2 – REST 端点响应

图 6.2 – REST 端点响应

在本节中,我们了解了响应式编程模型,并创建了一个响应式 Spring Boot CRUD 应用程序。在下一节中,我们将看看使用 Skaffold 将您的 Java 应用程序容器化的不同方法。

使用 Skaffold 容器镜像构建器

第三章Skaffold – 简单易用的云原生 Kubernetes 应用开发,我们知道 Skaffold 目前支持以下容器镜像构建器:

  • Dockerfile

  • Jib(Maven 和 Gradle)

  • Bazel

  • 云原生 Buildpacks

  • 自定义脚本

  • kaniko

  • Google Cloud Build

在本节中,我们将通过在上一节中构建的 Spring Boot 应用程序中详细介绍它们。让我们先谈谈 Dockerfile。

Dockerfile

Docker 多年来一直是创建容器的黄金标准。即使今天有许多 Docker 的替代品,但它仍然活跃。Docker 架构依赖于必须运行以服务所有 Docker 命令的守护进程。然后有一个 Docker CLI,它将命令发送到 Docker 守护进程以执行。守护进程执行所需的操作,如推送、拉取、运行容器镜像等。Docker 期望一个名为 Dockerfile 的文件,由您手动编写,其中包含它理解的步骤和指令。然后使用诸如docker build之类的命令使用此 Dockerfile 创建应用程序的容器镜像。这里的优势在于,这允许根据您的需求对应用程序的容器镜像进行不同的定制级别。

要使用 Docker 构建镜像,我们需要向 Dockerfile 添加一些指令。这些指令作为输入,然后 Docker 守护进程使用这些指令创建镜像。让我们看一个示例,以了解典型 Dockerfile 用于 Java 应用程序的工作原理。

图 6.3 – Docker 构建流程

图 6.3 – Docker 构建流程

我们将使用以下 Dockerfile 来容器化我们的应用程序:

FROM openjdk:16
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

从上述代码块中,我们可以看到以下内容:

  • FROM指令表示我们应用程序的基础镜像。

  • COPY指令,顾名思义,将由 Maven 构建的本地.jar 文件复制到我们的镜像中。

  • ENTRYPOINT指令在容器启动时充当可执行文件。

skaffold.yaml文件中,我们添加了一个名为docker的新配置文件。以下是docker配置文件的相关部分:

profiles:
  - name: docker
    build:
      artifacts:
        - image: reactive-web-app
      local: {}

我们可以使用skaffold dev –profile=docker命令运行构建。输出应该与我们之前在图 6.2中看到的类似。

Jib

Jib (https://github.com/GoogleContainerTools/jib)代表Java Image Builder,纯粹由 Java 编写。您已经知道 Jib 允许 Java 开发人员使用诸如 Maven 和 Gradle 之类的构建工具构建容器。但是,它还有一个 CLI 工具,可用于非 Java 应用程序,如 Python 或 Node.js。

使用 Jib 的重要优势是您无需了解安装 Docker 或维护 Dockerfile 的任何内容。要使您的 Java 应用程序容器化,您无需阅读无数的 Docker 教程。Jib 是无守护进程的。此外,作为 Java 开发人员,我们只关心构件(即 jar 文件),并且使用 Jib,我们不必处理任何 Docker 命令。使用 Jib,Java 开发人员可以将插件添加到他们选择的构建工具(Maven/Gradle)中,并且只需进行最少的配置,即可使应用程序容器化。Jib 将您的应用程序源代码作为输入,并输出您的应用程序的容器镜像。以下是使用 Jib 构建您的 Java 应用程序的流程:

图 6.4 – Jib 构建流程

图 6.4 – Jib 构建流程

让我们尝试使用 Jib 构建上一节中创建的应用程序:

  1. 首先,我们将使用 Skaffold 的init命令创建skaffold.yaml文件,如下所示:
apiVersion: skaffold/v2beta20
kind: Config
metadata:
  name: reactive-web-app
build:
  artifacts:
  - image: reactive-web-app
    jib:
      fromImage: adoptopenjdk:16-jre
      project: com.example:reactive-web-app
      args:
        - -DskipTests
deploy:
  kubectl:
    manifests:
    - k8s/manifest.yaml

提示

Jib 巧妙地将应用程序图像层分成以下几个部分,以加快重建速度:

-类

-资源

-项目依赖项

-快照和所有其他依赖项

目标是将经常更改的文件与很少更改的文件分开。直接的好处是,您不必重建整个应用程序,因为 Jib 只重新构建包含更改文件的层,并重用未更改文件的缓存层。

使用 Jib,如果您不指定镜像摘要,您可能会在日志中看到以下警告:

[警告] 基础镜像 'adoptopenjdk/openjdk16' 没有使用特定的镜像摘要 - 构建可能不可重现。

您可以通过使用正确的镜像摘要来克服这一点。例如,在 maven-jib-plugin 中,您可以进行以下更改,而在 skaffold.yaml 文件中,您可以指定镜像摘要:

<plugin>

<groupId>com.google.cloud.tools</groupId>

<artifactId>jib-maven-plugin</artifactId>

<version>3.1.1</version>

<configuration>

<image>adoptopenjdk/openjdk16@           sha256:b40f81a9f7e7e4533ed0c            6ac794ded9f653807f757e2b8b4e1            fe729b6065f7f5</image>

<image>docker.io/hiashish/image</image>

</configuration>

</plugin>

以下是 Kubernetes 服务清单:

apiVersion: v1
kind: Service
metadata:
  name: reactive-web-app
spec:
  ports:
    - port: 8080
      protocol: TCP
      targetPort: 8080
  type: Loadbalancer
  selector:
    app: reactive-web-app

以下是 Kubernetes 部署清单:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: reactive-web-app
spec:
  selector:
    matchLabels:
      app: reactive-web-app
  template:
    metadata:
      labels:
        app: reactive-web-app
    spec:
      containers:
        - name: reactive-web-app
          image: reactive-web-app
  1. 现在,我们必须运行 skaffold dev 命令。以下是输出:
skaffold dev
Listing files to watch...
 - reactive-web-app
Generating tags...
 - reactive-web-app -> reactive-web-app:fcda757-dirty
Checking cache...
 - reactive-web-app: Found Locally
Starting test...
Tags used in deployment:
 - reactive-web-app -> reactive-web-app:3ad471bdebe8e0606040300c9b7f1af4bf6d0a9d014d7cb62d7ac7b884dcf008
Starting deploy...
 - service/reactive-web-app created
 - deployment.apps/reactive-web-app created
Waiting for deployments to stabilize...
 - deployment/reactive-web-app is ready.
Deployments stabilized in 3.34 seconds
Press Ctrl+C to exit
Watching for changes...

使用 minikube service reactive-web-app 命令可以在 minikube 中打开暴露的服务。我们将使用以下截图中提到的 URL 来访问我们的应用程序:

图 6.5 - 暴露的服务 URL

图 6.5 - 暴露的服务 URL

访问 http://127.0.0.1:55174/employee URL 后,我们应该得到类似于 图 6.2 的输出。

Bazel

Bazel 是一个类似于 Maven 和 Gradle 的开源、多语言、快速和可扩展的构建工具。Skaffold 支持 Bazel,并且可以将镜像加载到本地 Docker 守护程序中。Bazel 需要两个文件:WORKSPACEBUILD

WORKSPACE 文件通常位于项目的根目录。此文件指示 Bazel 工作区。它查找构建输入,并将构建输出存储在创建 WORKSPACE 文件的目录中。

BUILD文件指示 Bazel 要构建什么以及如何构建项目的不同部分。以下是一个 Java 应用程序的BUILD文件示例。在这个例子中,我们指示 Bazel 使用java_binary规则为我们的应用程序创建一个.jar文件:

java_binary(    
name = "ReactiveWebApp",    
srcs = glob(["src/main/java/com/example/*.java"]),)

要构建您的项目,您可以运行诸如build //: ReactiveWebApp之类的命令。以下是包含bazel配置文件的skaffold.yaml文件:

profiles:
  - name: bazel
    build:
      artifacts:
        - image: reactive-web-app
          bazel:
            target: //:reactive-web-app.tar

接下来我们有 Buildpacks。

Buildpacks

Heroku 在 2011 年首次创建了 Buildpacks(buildpacks.io/)。它现在是 CNCF 基金会的一部分。就像 Jib 一样,Buildpacks 也可以在不需要 Dockerfile 的情况下工作。但是,您需要一个正在运行的 Docker 守护程序进程才能使其工作。使用 Buildpacks,输入是您的应用程序源代码,输出是容器镜像。在这方面,它与 Jib 非常相似,尽管 Jib 可以在没有 Docker 守护程序的情况下工作。

在后台,Buildpacks 做了很多工作,包括检索依赖项,处理资产,处理缓存以及为应用程序使用的任何语言编译代码:

图 6.6 - Buildpacks 构建流程

图 6.6 - Buildpacks 构建流程

正如前面所解释的,Skaffold 需要一个本地的 Docker 守护程序来使用 Buildpacks 构建镜像。Skaffold 将在容器内部使用skaffold.yaml文件中 Buildpacks 配置中指定的构建器执行构建。此外,您不必安装 pack CLI,因为 Google Cloud Buildpacks 项目(github.com/GoogleCloudPlatform/buildpacks)提供了用于工具如 Skaffold 的构建器镜像。您可以选择跳过此步骤,但在成功构建后,Skaffold 将把镜像推送到远程注册表。

提示

从 Spring Boot 2.3 版本开始,Spring Boot 直接支持 Maven 和 Gradle 项目的 Buildpacks。使用mvn spring-boot:build-image命令,您可以创建一个加载到本地运行的 Docker 守护程序的应用程序镜像。虽然您不需要维护 Dockerfile,但 Buildpacks 依赖于 Docker 守护程序进程。如果您在本地没有运行 Docker 守护程序,执行 Maven 命令时将会收到以下错误:

“无法执行目标 org.springframework.boot:spring-boot-maven-plugin:2.4.2:build-image (default-cli) on project imagebuilder: Execution default-cli of goal org.springframework.boot:spring-boot-maven-plugin:2.4.2:build-image failed: Connection to the Docker daemon at 'localhost' failed with error "[61] Connection refused"; ensure the Docker daemon is running and accessible”

为了使用 Buildpacks 构建我们的应用程序,我们添加了一个名为pack的新配置文件,并将其用于向skaffold.yaml配置文件的build部分添加一个新的部分。在builder字段中,我们指示 Skaffold 使用gcr.io/buildpacks/builder:v1构建器映像。以下是配置文件的相关部分:

profiles:
  - name: pack
    build:
      artifacts:
        - image: reactive-web-app
          buildpacks:
            builder: gcr.io/buildpacks/builder:v1
            env:
              - GOOGLE_RUNTIME_VERSION=16

我们可以使用skaffold dev –profile=pack命令运行构建。输出应该类似于我们在图 6.2中看到的。

提示

Spring Boot Buildpacks 集成可用于将映像推送到远程容器注册表。我们需要在pom.xml中进行以下更改:

<plugin>
    <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <image>
           <name>docker.example.com/library/$
            {project.artifactId}</name>
           <publish>true</publish>
        </image>
        <docker>
          <publishRegistry>
            <username>user</username>
            <password>secret</password>
            <url>https://docker.example.com/v1/</url>
            <email>user@example.com</email>
            </publishRegistry>
        </docker>
    </configuration>
</plugin>

自定义脚本

如果没有支持的容器映像构建器适用于您的用例,您可以使用自定义脚本选项。通过此选项,您可以编写自定义脚本或选择您喜欢的构建工具。您可以通过在skaffold.yaml文件的构建部分的每个相应构件中添加一个自定义字段来配置自定义脚本。

在下面的示例skaffold.yaml文件中,我们创建了一个名为custom的新配置文件。在buildCommand字段中,我们使用build.sh脚本来将我们的 Spring Boot 应用程序容器化:

 profiles:
  - name: custom
    build:
      artifacts:
        - image: reactive-web-app
          custom:
            buildCommand: sh build.sh

build.sh脚本文件包含以下内容。它使用docker build命令来创建我们应用程序的映像。Skaffold 将提供$IMAGE(即完全限定的映像名称环境变量)给自定义构建脚本:

#!/bin/sh
set -e
docker build -t "$IMAGE" .

接下来我们转向 kaniko。

kaniko

kaniko 是一个开源工具,用于在容器或 Kubernetes 集群内部从 Dockerfile 构建容器映像。kaniko 不需要特权根访问权限来构建容器映像。

kaniko 不依赖于 Docker 守护程序,并在用户空间完全执行 Dockerfile 中的每个命令。使用 kaniko,您可以在无法安全运行 Docker 守护程序的环境中开始构建容器映像,例如标准 Kubernetes 集群。那么,kaniko 是如何工作的呢?嗯,kaniko 使用一个名为gcr.io/kaniko-project/executor的执行器映像,该映像在容器内运行。不建议在另一个映像中运行 kaniko 执行器二进制文件,因为它可能无法正常工作。

让我们看看这是如何完成的:

  1. 我们将使用以下 Dockerfile 与 kaniko 构建应用程序的容器映像:
FROM maven:3-adoptopenjdk-16 as build
RUN mkdir /app
COPY . /app
WORKDIR /app
RUN mvn clean verify -DskipTests
FROM adoptopenjdk:16-jre
RUN mkdir /project
COPY --from=build /app/target/*.jar /project/app.jar
WORKDIR /project
ENTRYPOINT ["java","-jar","app.jar"]
  1. 以下是skaffold.yaml的相关部分:
profiles:
  - name: kaniko
    build:
      cluster:
        pullSecretPath: /Users/ashish/Downloads/kaniko-secret.json
      artifacts:
        - image: reactive-web-app
          kaniko: {}

在这里,我们添加了一个名为kaniko的新配置文件,以在 Google Kubernetes 集群中构建我们的容器映像。您将在第八章中了解更多关于 GKE 的信息,使用 Skaffold 将 Spring Boot 应用部署到 Google Kubernetes Engine

skaffold.yaml文件中需要强调的一个重要点是,我们需要从活动的 Kubernetes 集群获取凭据,以便在集群内构建我们的映像。为此,需要一个 GCP 服务帐户。此帐户具有存储管理员角色,以便可以拉取和推送映像。我们可以使用以下命令构建并将应用程序部署到 GKE:

skaffold run --profile=kaniko --default-repo=gcr.io/basic-curve-316617

我们将在 GCP 上托管的远程 Kubernetes 集群上进行演示。让我们开始吧:

  1. 首先,我们需要为 kaniko 创建一个服务帐户,该帐户具有从gcr.io拉取和推送映像的权限。然后,我们需要下载 JSON 服务帐户文件并将文件重命名为kaniko-secret。还要确保不要在文件名后添加.json;使用以下命令创建 Kubernetes 密钥。您需要确保 Kubernetes 上下文设置为远程 Kubernetes 集群:
kubectl create secret generic kaniko-secret --from-file=kaniko-secret
  1. 由于我们将把映像推送到Google 容器注册表GCR),我们已经提到了--default-repo标志,以便它始终指向 GCR。以下是日志:
Generating tags...
 - reactive-web-app -> gcr.io/basic-curve-316617/reactive-web-app:fcda757-dirty
Checking cache...
 - reactive-web-app: Not found. Building
Starting build...
Checking for kaniko secret [default/kaniko-secret]...
Creating kaniko secret [default/kaniko-secret]...
Building [reactive-web-app]...
INFO[0000] GET KEYCHAIN                                 
INFO[0000] running on kubernetes ....

在以下日志中,您可以看到 kaniko 开始在容器内构建映像,下载不同构建阶段的基础映像。kaniko 开始打包和下载我们的 Spring Boot 应用程序的依赖项:

INFO[0001] Retrieving image manifest adoptopenjdk:16-jre 
INFO[0001] Retrieving image adoptopenjdk:16-jre from registry index.docker.io 
INFO[0001] GET KEYCHAIN
INFO[0001] Built cross stage deps: map[0:[/app/target/*.jar]] 
INFO[0001] Retrieving image manifest maven:3-adoptopenjdk-16 
...............
INFO[0035] RUN mvn clean verify -DskipTests             
INFO[0035] cmd: /bin/sh                                 
INFO[0035] args: [-c mvn clean verify -DskipTests]      
INFO[0035] Running: [/bin/sh -c mvn clean verify -DskipTests] 
[INFO] Scanning for projects...
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/2.5.2/spring-boot-starter-parent-2.5.2.pom
  1. 在以下日志中,您可以看到构建成功,并且 kaniko 能够将映像推送到 GCR。然后,我们使用kubectl将映像部署到 Google Kubernetes 集群:
[INFO] BUILD SUCCESS
INFO[0109] Taking snapshot of full filesystem...        
INFO[0114] Saving file app/target/reactive-web-app-0.0.1-SNAPSHOT.jar for later use 
....        
INFO[0130] COPY --from=build /app/target/*.jar /project/app.jar    
....        
INFO[0131] ENTRYPOINT ["java","-jar","app.jar"]
INFO[0131] GET KEYCHAIN                                 
INFO[0131] Pushing image to gcr.io/basic-curve-316617/reactive-web-app:fcda757-dirty 
INFO[0133] Pushed image to 1 destinations               
Starting test...
Tags used in deployment:
 - reactive-web-app -> gcr.io/basic-curve-316617/reactive-web-app:fcda757-dirty@9797e8467bd25fa4a237 e21656cd574c0c46501e5b3233a1f27639cb5b66132e
Starting deploy...
 - service/reactive-web-app created
 - deployment.apps/reactive-web-app created
Waiting for deployments to stabilize...
 - deployment/reactive-web-app: creating container reactive-web-app
    - pod/reactive-web-app-6b885dcf95-q8dr5: creating container reactive-web-app
 - deployment/reactive-web-app is ready.
Deployments stabilized in 12.854 seconds

在以下截图中,我们可以看到部署后,一个 pod 正在运行,并且暴露的服务是Load balancer类型:

图 6.7 – Pod 运行和服务暴露给外部访问

图 6.7 – Pod 运行和服务暴露给外部访问

访问我们的 Spring Boot 应用程序的/employee REST 端点后,使用 GKE 公开的端点的输出如下:

图 6.8 – REST 应用程序响应

图 6.8 – REST 应用程序响应

Google Cloud Build

Cloud Build 是一个使用 GCP 基础设施运行构建的服务。Cloud Build 通过从各种存储库或 Google Cloud Storage 空间导入源代码,执行构建,并生成容器镜像等工件来工作。

我们在skaffold.yaml中创建了一个名为gcb的新配置文件,以使用 Google Cloud Build 触发我们应用程序的远程构建。以下是skaffold.yaml配置文件部分的相关部分:

profiles:
  - name: gcb
    build:
      artifacts:
        - image: reactive-web-app
          docker:
            cacheFrom:
              - reactive-web-app
      googleCloudBuild: {}

我们可以运行以下命令来开始使用 Google Cloud Build 远程构建我们的应用程序:

skaffold run --profile=gcb --default-repo=gcr.io/basic-curve-316617

如果这是您第一次这样做,请确保已经从Cloud Console仪表板或通过 gcloud CLI 启用了 Cloud Build API。否则,您可能会收到以下错误:

Generating tags...
 - reactive-web-app -> gcr.io/basic-curve-316617/reactive-web-app:fcda757-dirty
Checking cache...
 - reactive-web-app: Not found. Building
Starting build...
Building [reactive-web-app]...
Pushing code to gs://basic-curve-316617_cloudbuild/source/basic-curve-316617-046b951c-5062-4824-963b-a204302a77e1.tar.gz
could not create build: googleapi: Error 403: Cloud Build API has not been used in project 205787228205 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudbuild.googleapis.com/overview?project=205787228205 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
.....

您可以通过访问错误日志中提到的 URL 并单击ENABLE按钮来通过Cloud Console仪表板启用 Cloud Build API,如下截图所示:

图 6.9 – 启用 Cloud Build API

图 6.9 – 启用 Cloud Build API

在运行实际命令启动构建和部署过程之前,您需要确保在您的kubeconfig文件中,GKE 远程集群是此部署的活动集群。以下是skaffold run命令的输出。在以下日志中,您可以看到我们的整个源代码被打包为tar.gz文件并发送到 Google Cloud Storage 位置。然后,Cloud Build 会获取它并开始构建我们的镜像:

skaffold run --profile=gcb --default-repo=gcr.io/basic-curve-316617
Generating tags...
 - reactive-web-app -> gcr.io/basic-curve-316617/reactive-web-app:fcda757-dirty
Checking cache...
 - reactive-web-app: Not found. Building
Starting build...
Building [reactive-web-app]...
Pushing code to gs://basic-curve-316617_cloudbuild/source/basic-curve-316617-aac889cf-d854-4e7f-a3bc-b26ea06bf854.tar.gz
Logs are available at 
https://console.cloud.google.com/m/cloudstorage/b/basic-curve-316617_cloudbuild/o/log-43705458-0f75-4cfd-8532-7f7db103818e.txt
starting build "43705458-0f75-4cfd-8532-7f7db103818e"
FETCHSOURCE
Fetching storage object: gs://basic-curve-316617_cloudbuild/source/basic-curve-316617-aac889cf-d854-4e7f-a3bc-b26ea06bf854.tar.gz#1626576177672677
Copying gs://basic-curve-316617_cloudbuild/source/basic-curve-316617-aac889cf-d854-4e7f-a3bc-b26ea06bf854.tar.gz#1626576177672677...
- [1 files][ 42.2 MiB/ 42.2 MiB]                                                
Operation completed over 1 objects/42.2 MiB.                                     
BUILD
Starting Step #0
Step #0: Already have image (with digest): gcr.io/cloud-builders/docker
…

在以下日志中,您可以看到镜像已经构建、标记并推送到 GCR。然后,使用kubectl,应用程序被部署到 GKE,如下所示:

Step #1: Successfully built 1a2c04528dad
Step #1: Successfully tagged gcr.io/basic-curve-316617/reactive-web-app:fcda757-dirty
Finished Step #1
PUSH
Pushing gcr.io/basic-curve-316617/reactive-web-app:fcda757-dirty
The push refers to repository [gcr.io/basic-curve-316617/reactive-web-app]
7a831de44071: Preparing
574a11c0c1c8: Preparing
783bfc5acd81: Preparing
2da4fab53cd6: Preparing
a70daca533d0: Preparing
783bfc5acd81: Layer already exists
2da4fab53cd6: Layer already exists
a70daca533d0: Layer already exists
574a11c0c1c8: Pushed
7a831de44071: Pushed
fcda757-dirty: digest: sha256:22b2de72d3e9551f2531f2b9dcdf5e4b2eabaabc9d1c7a5930bcf226e6b9c04b size: 1372
DONE
Starting test...
Tags used in deployment:
 - reactive-web-app -> gcr.io/basic-curve-316617/reactive-web-app:fcda757-dirty@sha256:22b2de72d3e9551f2531f2b9dcdf5e4b2 eabaabc9d1c7a5930bcf226e6b9c04b
Starting deploy...
 - service/reactive-web-app configured
 - deployment.apps/reactive-web-app created
Waiting for deployments to stabilize...
 - deployment/reactive-web-app: creating container reactive-web-app
    - pod/reactive-web-app-789f775d4-z998t: creating container reactive-web-app
 - deployment/reactive-web-app is ready.
Deployments stabilized in 1 minute 51.872 seconds

在 GKE 的Workload部分,您可以看到reactive-web-app已经部署,并且其状态为 OK,如下所示:

图 6.10 – 应用程序成功部署在 GKE 上

图 6.10 - 应用程序成功部署在 GKE 上

在本节中,我们学习了如何以不同的方式将我们的 Reactive Spring Boot CRUD 应用程序容器化。

在下一节中,我们将探讨使用 Skaffold 将应用程序部署到 Kubernetes 的不同方法。

探索 Skaffold 容器镜像部署程序

在本节中,我们将看看 Skaffold 支持的容器镜像部署方法。使用 Skaffold,您可以使用以下三种工具将应用程序部署到 Kubernetes:

  • Helm

  • kubectl

  • Kustomize

让我们详细讨论一下。

Helm

Helm是软件包管理器,charts是您的 Kubernetes 应用程序的软件包。它允许您轻松定义、安装和更新您的 Kubernetes 应用程序。您可以为您的应用程序编写图表,或者从稳定的图表存储库中使用用于流行软件(如 MySQL 和 MongoDB)的生产就绪的预打包图表。

直到 Helm 2,Helm 遵循客户端-服务器架构。然而,由于 Helm 3 对架构进行了重大更改,它是一个仅客户端的架构。因此,在您的 Kubernetes 集群上不需要安装Tiller等服务器端组件。

现在,让我们更多地了解 Helm:

  1. Skaffold 不会为我们安装 Helm,因此我们必须使用 macOS 的 Homebrew 软件包管理器进行安装:
$ brew install helm
$ helm version
version.BuildInfo{Version:"v3.6.3", GitCommit:"d506314abfb5d21419df8c7e7e68012379db2354", GitTreeState:"dirty", GoVersion:"go1.16.5"}

对于 Windows,您可以使用 chocolatey 进行下载:

choco install kubernetes-helm

您还可以使用安装程序脚本下载 Helm,该脚本将下载最新版本:

$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh
  1. 接下来,我们将使用以下命令创建 Helm 图表骨架:
$ helm create reactive-web-app-helm             
Creating charts
  1. 我们将创建一个名为jibWithHelm的新 Skaffold 配置文件,以使用 Jib 构建图像,然后使用 Helm 部署它:
profiles:
    - name: jibWithHelm
    build:
      artifacts:
        - image: gcr.io/basic-curve-316617/reactive-
            web-app-helm
          jib:
            args:
              - -DskipTests
    deploy:
      helm:
        releases:
          - name: reactive-web-app-helm
            chartPath: reactive-web-app-helm
            artifactOverrides:
              imageKey: gcr.io/basic-curve-
                316617/reactive-web-app-helm
            valuesFiles:
              - reactive-web-app-helm/values.yaml
            imageStrategy:
              helm: { }

build部分下的图像名称应与skaffold.yaml文件的artifactOverrides部分下给定的图像名称匹配。否则,将会出现错误。

我们还在skaffold.yaml文件的valuesFiles部分提供了指向values.yaml文件的路径。

使用 Helm 定义图像引用的典型约定是通过values.yaml文件。以下是将由 Helm 引用的values.yaml文件的内容:

replicaCount: 1
imageKey:
  repository: gcr.io/basic-curve-316617
  pullPolicy: IfNotPresent
  tag: latest
service:
  type: LoadBalancer
  port: 8080
  targetPort: 8080

values.yaml文件中的值将在模板化资源文件中被引用,如下面的代码片段所示。此模板文件位于reactive-web-app-helm/templates/**.yaml中:

    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: {{ .Values.imageKey.repository }}:{{ 
            .Values.imageKey.tag }}
          imagePullPolicy: {{ .Values.imageKey.pullPolicy }}

运行skaffold run --profile=jibWithHelm后,Skaffold 将使用 Jib 构建图像,并使用 Helm 图表将其部署到 GKE。这将导致以下输出:

skaffold run --profile=jibWithHelm
Generating tags...
 - gcr.io/basic-curve-316617/reactive-web-app-helm -> gcr.io/basic-curve-316617/reactive-web-app-helm:3ab62c6-dirty
Checking cache...
 - gcr.io/basic-curve-316617/reactive-web-app-helm: Found Remotely
Starting test...
Tags used in deployment:
 - gcr.io/basic-curve-316617/reactive-web-app-helm -> gcr.io/basic-curve-316617/reactive-web-app-helm:3ab62c6-dirty@sha256:2d9539eb23bd9db578feae7e4956c30d9320786217a7307e0366d9cc5ce359bc
Starting deploy...
Helm release reactive-web-app-helm not installed. Installing...
NAME: reactive-web-app-helm
LAST DEPLOYED: Thu Aug 26 11:34:39 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
Waiting for deployments to stabilize...
 - deployment/reactive-web-app-helm is ready.
Deployments stabilized in 3.535 seconds

我们可以通过转到 GKE 的工作负载部分来验证 pod 是否正在运行。在下面的截图中,我们可以看到我们有一个正在运行的 pod:

图 6.11 - Helm 图表在 GKE 上成功部署

](image/Figure_6.11_B17385.jpg)

图 6.11 - Helm 图表在 GKE 上成功部署

同样,在服务和入口部分下,我们可以看到已经为外部访问暴露了一个外部负载均衡器类型的服务:

图 6.12 - 在 GKE 上暴露的 LoadBalancer 服务类型

图 6.12 - 在 GKE 上暴露的 LoadBalancer 服务类型

Endpoints列中使用 URL 访问应用程序后,输出应该类似于我们在图 6.2中看到的。

kubectl

kubectl 是一个命令行工具,用于在 Kubernetes 集群上运行命令。它与 Kubernetes API 服务器交互以运行这些命令。您可以使用它来完成各种任务,例如查看 pod 的日志,创建 Kubernetes

例如部署资源,了解集群的状态和 pod 等。在下面的代码片段中,您可以看到我们正在使用 kubectl 进行部署。Kubernetes 清单位于k8s目录下:

deploy:
  kubectl:
    manifests:
    - k8s/manifest.yaml

Kustomize

Kustomize,顾名思义,是一种无模板的声明性方法,用于 Kubernetes 配置、管理和自定义选项。使用 Kustomize,我们提供一个基本的框架和补丁。在这种方法中,与 Helm 相比,我们提供一个基本的部署,然后描述不同环境的差异。例如,我们可以在生产环境和暂存环境中有不同数量的副本和健康检查。Kustomize 可以单独安装,自 kubectl 的 1.14 版本以来,我们可以使用-k命令。请按照kubectl.docs.kubernetes.io/installation/kustomize/中提到的说明在支持的操作系统上安装它。

在下面的例子中,我们有一个名为kustomizeProd的配置文件,并且正在使用 Kustomize 作为我们应用的部署策略:

 profiles:  
  - name: kustomizeProd
    build:
      artifacts:
        - image: reactive-web-app
          jib:
            args:
              - -DskipTests
    deploy:
      kustomize:
        paths:
          - kustomization/overlays/prod

为了使 Kustomize 正常工作,我们必须具有以下目录结构。在下面的目录中,您可以看到在kustomization/base目录下,我们有描述我们想要在 GKE 集群中部署的资源的原始 YAML 文件。我们永远不会触及这些文件;相反,我们只会在它们之上应用定制来创建新的资源定义:

├── kustomization
│   ├── base
│   │   ├── deployment.yaml
│   │   ├── kustomization.yaml
│   │   └── service.yaml
│   └── overlays
│       ├── dev
│       │   ├── environment.yaml
│       │   └── kustomization.yaml
│       └── prod
│           ├── increase_replica.yaml
│           ├── kustomization.yaml
│           └── resources_constraint.yaml

我们在base文件夹中有一个名为kustomization.yaml的文件。它描述了您使用的资源。这些资源是相对于当前文件的 Kubernetes 清单文件的路径:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml 

接下来,我们有kustomization/overlays/prod文件夹,其中包含一个kustomization.yaml文件。它包含以下内容:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
patchesStrategicMerge:
  - increase_replica.yaml
  - resources_constraint.yaml

如果您能看到,在base中,我们没有定义任何环境变量、副本数或资源约束。但是对于生产场景,我们必须在我们的基础之上添加这些内容。为此,我们只需创建我们想要应用在我们的基础之上的 YAML 块,并在kustomization.yaml文件中引用它。我们已经将这个 YAML 添加到kustomization.yaml文件中的patchesStrategicMerge列表中。

increase_replica.yaml文件包含两个副本,内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: reactive-web-app
spec:
  replicas: 2

resources_constraint.yaml文件包含资源请求和限制,内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: reactive-web-app
spec:
  template:
    spec:
      containers:
        - name: reactive-web-app
          resources:
            requests:
              memory: 512Mi
              cpu: 256m
            limits:
              memory: 1Gi
              cpu: 512m

现在,我们可以运行skaffold run --profile=kustomizeProd --default-repo=gcr.io/basic-curve-316617命令。这将使用 Kustomize 将应用程序部署到 GKE。我们得到的输出应该与我们之前在图 6.2中看到的类似。

在本节中,我们看了一下我们可以使用 Skaffold 来将应用程序部署到 Kubernetes 集群的工具。

总结

在本章中,我们首先介绍了响应式编程,并构建了一个 Spring Boot CRUD 应用程序。我们还介绍了 Skaffold 支持的容器镜像构建工具,包括 Docker、kaniko、Jib 和 Buildpacks。我们通过实际实现来了解了它们。我们还讨论了使用诸如 kubectl、Helm 和 Kustomize 等工具将镜像部署到 Kubernetes 集群的不同方式。

在本章中,我们对诸如 Jib、kaniko、Helm 和 Kustomize 等工具有了扎实的了解。您可以运用这些工具的知识来构建和部署您的容器。

在下一章中,我们将使用 Google 的 Cloud Code 扩展构建和部署一个 Spring Boot 应用程序到 Kubernetes。

进一步阅读

要了解更多关于 Skaffold 的信息,请查看 Skaffold 文档:skaffold.dev/docs/

第三部分:使用 Skaffold 构建和部署云原生 Spring Boot 应用程序

本节将主要关注使用 Skaffold 构建和部署 Spring Boot 应用程序到本地(minikube 等)和远程集群(GKE)的过程。我们将探讨如何在舒适的 IDE 环境中使用 Google 开发的 Cloud Code 构建和部署云原生应用程序。然后,我们将使用 Skaffold 构建和部署 Spring Boot 应用程序到像 GKE 这样的托管 Kubernetes 平台。我们还将学习如何使用 Skaffold 和 GitHub Actions 创建一个生产就绪的 CI/CD 流水线。我们将通过结合 Skaffold 和 Argo CD 来实现 GitOps 风格的 CD 工作流进行一些实验。最后,我们将探讨一些 Skaffold 的替代方案,并了解我们在工作流中应该利用的 Skaffold 最佳实践。此外,我们将探讨使用 Skaffold 开发应用程序时最常见的陷阱和限制。最后,我们将总结本书学到的内容。

在本节中,我们有以下章节:

  • [第七章],使用 Cloud Code 插件构建和部署 Spring Boot 应用程序

  • [第八章],使用 Skaffold 将 Spring Boot 应用程序部署到 Google Kubernetes Engine

  • [第九章],使用 Skaffold 创建生产就绪的 CI/CD 流水线

  • [第十章],探索 Skaffold 的替代方案、最佳实践和陷阱

第七章:使用 Cloud Code 插件构建和部署 Spring Boot 应用程序

在上一章中,我们了解了 Skaffold 支持的容器镜像构建器和部署器。在本章中,我们将向您介绍 Google 的 Cloud Code 插件,该插件可在 IntelliJ 等 IDE 中使用。我们将创建一个 Spring Boot 应用程序,并使用 Cloud Code 插件将其部署到本地 Kubernetes 集群。

在本章中,我们将涵盖以下主要主题:

  • 介绍 Google 的 Cloud Code 插件

  • 安装并使用 IntelliJ Cloud Code 插件

  • 创建一个 Spring Boot 应用程序

  • 使用 Cloud Code 对 Spring Boot 应用进行容器化和部署

通过本章结束时,您将对 Cloud Code 插件有一个扎实的理解,并了解如何使用它来加速使用 IDE 开发 Kubernetes 应用程序的开发生命周期。

技术要求

对于本章,您将需要以下内容:

本书的 GitHub 存储库中的代码可以在github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-Using-Skaffold/tree/main/Chapter07找到。

介绍 Google 的 Cloud Code 插件

如果您正在开发或维护当今的云原生应用程序,那么一个不成文的事实是您需要一套工具或工具来简化您的开发过程。作为开发人员,我们通常在内部开发循环中执行以下任务:

  • 下载特定的依赖项,如 Skaffold、minikubekubectl,以设置本地开发环境。

  • 进行大量的上下文切换以查看日志、文档并浏览云供应商提供的控制台。

虽然 Skaffold 是解决这个问题的一个很好的解决方案,但是将所有东西都整合到您的 IDE 中不是很好吗?例如,我们可以添加一个插件来执行所有这些任务并专注于编码部分。为此,我们可以使用 Google Cloud Code 扩展,因为它简化了使用您喜爱的 IDE(如 IntelliJ、Visual Studio Code 等)开发基于云的应用程序。

让我们了解一些 Cloud Code 提供的功能:

  • 更快地编写、调试和部署 Kubernetes 应用程序。

  • 支持多个 IDE,包括 JetBrains IntelliJ、Visual Studio Code 和 Cloud Shell Editor。

  • 多个不同语言的启动模板,以最佳实践快速开始开发。

  • 您可以通过单击 Google Kubernetes 引擎或 Cloud Run 来部署您的应用程序。

  • 高效地与其他谷歌云平台服务一起使用,包括谷歌 Kubernetes 引擎、谷歌容器注册表和云存储。

  • 通过代码片段和内联文档等功能改进 YAML 文件编辑过程。

  • 内置对 Skaffold 的支持,加快内部开发循环。

  • 轻松远程和本地调试在 Kubernetes 上运行的应用程序。

  • 内置日志查看器,实时查看 Kubernetes 应用程序的应用程序日志。

现在我们已经了解了 Cloud Code 是什么以及它的特性,让我们尝试安装和使用其启动模板,快速部署 Java 应用程序到本地 Kubernetes 集群。

安装并使用 IntelliJ Cloud Code 插件

要开始使用 Cloud Code 插件,首先我们需要下载它。您可以访问 IntelliJ 插件市场进行下载。让我们学习如何做到这一点:

  1. 对于 Windows 或 Linux,导航到File | Settings | Plugins,在搜索区域输入Cloud Code,然后单击Install

  2. 对于 macOS,导航到IntelliJ IDEA | Preferences | Plugins,在搜索区域输入Cloud Code,然后单击Install,如下截图所示:图 7.1 - 从 IntelliJ 市场安装 Cloud Code

图 7.1 - 从 IntelliJ 市场安装 Cloud Code

  1. 下载完成后,将弹出一个欢迎屏幕。在这里,单击创建一个 Kubernetes 示例应用程序,如下截图所示:图 7.2 - Cloud Code 欢迎页面

图 7.2 - Cloud Code 欢迎页面

  1. 在下一个屏幕上,将打开一个新项目窗口。我们需要选择Java: Guestbook项目,如下截图所示,然后单击Next图 7.3 - 选择预构建的 Java Guestbook 应用程序

图 7.3 - 选择预构建的 Java Guestbook 应用程序

  1. 在下一个屏幕上,您将被要求指定您的容器镜像存储库。如果您使用 DockerHub、GCR 或任何其他镜像注册表,则添加这些详细信息,然后单击下一步。例如,如果您使用 GCR,则输入类似gcr.io/gcp-project-id的内容。由于我们使用启动模板并且镜像名称已在 Kubernetes 清单中定义,因此我们可以留下这部分。

  2. 在下一个屏幕上,输入项目名称,然后单击完成。示例 Java 项目将下载到默认项目位置。

  3. 现在我们有一个可用的项目,单击运行/调试配置下拉菜单,然后选择编辑配置

  4. 运行/调试配置对话框中,选择在 Kubernetes 上开发配置。然后,在运行 | 部署下,选择部署到当前上下文(minikube),如下面的屏幕截图所示:图 7.4 – 将 Kubernetes 的当前上下文设置为 Minikube

图 7.4 – 将 Kubernetes 的当前上下文设置为 Minikube

  1. 单击应用确定以保存更改。

  2. 最后,要在本地 Minikube 集群上运行应用程序,请单击绿色运行图标:图 7.5 – 运行应用程序

图 7.5 – 运行应用程序

如前所述,Cloud Code 使用 Skaffold。一旦应用程序成功部署到本地 Minikube 集群,您应该会看到以下输出:

图 7.6 – 部署日志

图 7.6 – 部署日志

  1. 您将在 IntelliJ 的事件日志部分收到通知。单击查看以访问已部署的 Kubernetes 服务的本地 URL:图 7.7 – 事件日志通知

图 7.7 – 事件日志通知

  1. 您可以单击java-guestbook-frontend URL 来访问应用程序:

图 7.8 – 可用服务

图 7.8 – 可用服务

在访问http://localhost:4503 URL 后,您应该会看到以下屏幕:

图 7.9 – 我的留言板应用程序登录页面

图 7.9 – 我的留言板应用程序登录页面

在这一部分,我们安装了 Cloud Code 插件,并使用提供的启动模板快速启动了这个插件。通过我们非常简单的设置,我们构建并部署了一个 Java 应用到本地的 Kubernetes 集群。接下来的部分将创建一个 Spring Boot 应用程序,用于显示实时空气质量数据。

创建一个 Spring Boot 应用程序

根据世界卫生组织(https://www.who.int/health-topics/air-pollution)的数据,空气污染每年导致全球约 700 万人死亡。这不仅是发达国家的问题,也是发展中国家的问题。我们应该尽一切努力阻止这种情况发生,采取有力措施。作为技术人员,我们可以创造解决方案,让人们了解他们所在地区的空气质量。有了这个,人们可以采取预防措施,比如在外出时戴口罩,如果室外空气有毒,让老年人和孩子呆在家里。

在这一部分,我们将创建一个 Spring Boot 应用程序,用于显示您当前位置的实时空气质量数据。我们将使用 Openaq(https://openaq.org/)提供的 API,这是一个名为空气质量数据维基百科的非营利组织。它公开了许多实时空气质量数据的端点,但我们将使用api.openaq.org/v1/latest?country=IN URL 来为我们的 Spring Boot 应用程序。让我们开始吧。

和往常一样,我们将通过浏览start.spring.io/来下载一个 Spring Boot 应用程序的工作桩。我们还将为我们的项目添加以下依赖项:

图 7.10 – Spring Boot 项目 Maven 依赖

图 7.10 – Spring Boot 项目 Maven 依赖

除了我们已经讨论过的依赖项,我们还将添加以下 Dekorate Spring Boot starter 依赖项:

<dependency>
    <groupId>io.dekorate</groupId>
    <artifactId>kubernetes-spring-starter</artifactId>
    <version>2.1.4</version>
</dependency>

Dekorate(https://github.com/dekorateio/dekorate)是一个工具,可以自动生成 Kubernetes 清单文件。它可以检测应用程序是否具有 Spring Boot web 依赖项,并在编译期间自动生成 Kubernetes 清单文件,并默认配置服务、部署和探针。另外,在你的主类中,你可以添加@KubernetesApplication注解来进行一些自定义。例如,你可以提供副本数量、服务类型、入口等等:

@KubernetesApplication(serviceType = ServiceType.LoadBalancer, replicas = 2,expose = true)

Dekorate 在target/classes/META-INF/dekorate目录中以.json.yml格式生成 Kubernetes 清单。

以下是 Kubernetes 服务清单的代码:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: scanner
    app.kubernetes.io/version: 0.0.1-SNAPSHOT
  name: scanner
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  selector:
    app.kubernetes.io/name: scanner
    app.kubernetes.io/version: 0.0.1-SNAPSHOT
  type: LoadBalancer

以下是部署 Kubernetes 清单的相关部分。正如您所看到的,Dekorate 已生成了存活和就绪探针:

spec:
  containers:
    - env:
        - name: KUBERNETES_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
      image: breathe
      imagePullPolicy: IfNotPresent
      livenessProbe:
        failureThreshold: 3
        httpGet:
          path: /actuator/health/liveness
          port: 8080
          scheme: HTTP
        initialDelaySeconds: 0
        periodSeconds: 30
        successThreshold: 1
        timeoutSeconds: 10
      name: scanner
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP
      readinessProbe:
        failureThreshold: 3
        httpGet:
          path: /actuator/health/readiness
          port: 8080
          scheme: HTTP
        initialDelaySeconds: 0
        periodSeconds: 30
        successThreshold: 1
        timeoutSeconds: 10

这是AirQualityController类,它已经用@Controller注解进行了注释。所有传入的 HTTP 请求到/index都由index()方法处理,该方法以国家代码、限制、页面和城市名称作为输入。这些参数的默认值分别为IN51Delhi

根据以下代码片段,我们有一个名为getAqiForCountry()的方法,每当我们请求/index时都会调用该方法。该方法还使用RestTemplate从端点获取实时空气质量数据,如COUNTRY_AQI_END_POINT变量中所述,并返回一个AqiCountryResponse对象。请参考以下代码:

图 7.11 - 实时空气质量数据的代码

图 7.11 - 实时空气质量数据的代码

提示

自 5.0 版本以来,RestTemplate类已进入维护模式。这意味着只允许进行轻微的错误修复,并且它将在未来被移除,以支持org.springframework.web.reactive.client.WebClient类,该类支持同步和异步操作。要使用WebClient,您将需要添加另一个依赖,比如spring-boot-starter-webflux。如果您想避免只有一个依赖,您也可以使用 Java 11 中新增的新 HTTP 客户端 API。使用这个新 API,我们可以发送同步或异步请求。在下面的同步阻塞示例中,我们使用了send(HttpRequest, HttpResponse.BodyHandler)方法。该方法会阻塞,直到请求被发送并收到响应:

HttpClient httpClient = HttpClient.newBuilder().build();

HttpRequest httpRequest = HttpRequest.newBuilder()

      .uri(URI.create("URL"))

.GET()

      .build();

HttpResponse<String> syncHttpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());

同样,对于异步非阻塞,我们可以使用sendAsync(HttpRequest, HttpResponse.BodyHandler)方法。它返回一个CompletableFuture<HttpResponse>,可以与不同的异步任务结合使用。

AqiCountryResponse对象包含以下数据元素:

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
  public class AqiCountryResponse {
    public List<Location> results;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
  public class Location {
    public String location;
    public String city;
    public List<Measurement> measurements;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
  public class Measurement {
    public String parameter;
    public String value;
    public String unit;
}

最后,我们必须对数据进行排序,并将数据返回到index.html页面上,以便在 UI 上呈现。对于 UI 部分,我们使用了 Spring Boot Thymeleaf 依赖。使用以下逻辑,我们可以在/index.html页面上显示实时空气质量数据:

<div th:if="${ not#lists.isEmpty(response)}">
    <table class="table table-bordered table-striped" 
      id="example" style="width: -moz-max-content">
        <tr>
            <th>Location</th>
            <th>City</th>
            <th colspan="30">Measurements</th>
        </tr>
        <tr th:each="response : ${response}">
            <td th:text="${response.location}"></td>
            <td th:text="${response.city}"></td>
            <th:block th:each="p ${response.measurements}">
                <td th:text="${p.parameter}"></td>
                <td th:text="${p.value}+''+${p.unit}"></td>
            </th:block>
        </tr>
        <table>
</div>

我们还创建了一个静态 HTML 表,指定了空气污染级别,并在同一页内为它们分配了颜色。这些颜色使人们可以轻松地确定他们所在地区的污染是否已经达到警戒级别:

<table class="table table-bordered" id="example1"
  style="width: max-content">
    <tr>
        <th>AQI</th>
        <th>Air Pollution Level</th>
        <th>Health Implications</th>
        <th>Cautionary Statement (for PM2.5)</th>
    </tr>
    <tr bgcolor="green">
        <td>0-50</td>
        <td>Good</td>
        <td>Air quality is considered satisfactory,
            and air pollution poses little or no risk</td>
        <td>None</td>
    </tr>
    <tr bgcolor="yellow">
        <td>51-100</td>
        <td>Moderate</td>
        <td>Air quality is acceptable; however, 
            for some pollutants there may be a moderate
            health concern for a very small number of
            people who are unusually sensitive to air
            pollution.
        </td>
        <td>Active children and adults, and people with
            respiratory disease, such as asthma,
            should limit prolonged outdoor exertion.
        </td>
    </tr>
<table>

此时,应用程序已经准备就绪。我们可以通过使用mvn sprinboot:run命令来运行它。让我们这样做,看看我们是否得到了预期的输出。在下面的截图中,您可以看到我们已将默认城市更改为孟买,并且我们可以查看孟买的实时空气质量数据:

图 7.12 - 呼吸 - 孟买的实时空气质量数据

图 7.12 - 呼吸 - 孟买的实时空气质量数据

在同一页上,我们可以看到一个包含不同 AQI 范围及其严重程度相关信息的表格:

图 7.13 - 空气质量指数

图 7.13 - 空气质量指数

在这一部分,我们创建了一个 Spring Boot 应用程序,用于显示您国家一个城市的实时空气质量数据。

在下一节中,我们将使用 Cloud Code 插件将我们的应用程序容器化并部署到本地的 Kubernetes 集群。

使用 Cloud Code 对 Spring Boot 应用程序进行容器化和部署

让我们尝试将我们在上一节中创建的 Spring Boot 应用程序进行容器化和部署。为了容器化我们的 Spring Boot 应用程序,我们将使用jib-maven-plugin。我们在之前的章节中多次使用过这个插件,所以我会在这里跳过设置。我们将使用kubectl将其部署到本地的 Minikube 集群。让我们学习如何做到这一点:

  1. 首先,我们需要在项目的根目录中有一个skaffold.yaml文件。

  2. 您可以创建一个名为skaffold.yaml的空文件,并使用 Cloud Code 的自动补全功能,如下截图所示,生成一个可用的skaffold.yaml文件:图 7.14 - 使用 Cloud Code 创建 skaffold.yaml 文件

图 7.14 - 使用 Cloud Code 创建 skaffold.yaml 文件

  1. 有时,可能会有新的模式版本可用。Cloud Code 足够智能,可以检测到这些更改,并建议您升级模式,如下面的屏幕截图所示:图 7.15 – 使用 Cloud Code 更新模式版本

图 7.15 – 使用 Cloud Code 更新模式版本

  1. 以下是我们的skaffold.yaml配置文件的最终版本。在这里,您可以看到我们使用jib来将我们的应用程序容器化。我们使用kubectl进行部署,我们使用的路径与我们为 Kubernetes 清单生成使用 Dekorate 时使用的路径相同:
apiVersion: skaffold/v2beta20
kind: Config
metadata:
  name: scanner
build:
  artifacts:
  - image: breathe
    jib:
      project: com.air.quality:scanner
deploy:
  kubectl:
    manifests:
      - target/classes/META-INF/dekorate/kubernetes.yml

在创建skaffold.yaml配置文件后不久,Cloud Code 检测到更改,并建议我们创建 Cloud Code Kubernetes 运行配置,如下所示:

图 7.16 – 使用 Cloud Code 创建运行配置

图 7.16 – 使用 Cloud Code 创建运行配置

  1. 单击此选项后,在 IntelliJ 的运行/调试配置下,将创建两个名为在 Kubernetes 上开发在 Kubernetes 上运行的新配置文件:图 7.17 – Cloud Code 配置文件

图 7.17 – Cloud Code 配置文件

  1. 要在持续开发模式下运行我们的应用程序,请从下拉菜单中选择在 Kubernetes 上开发。Cloud Code 在此模式下内部使用skaffold dev命令。它将为您执行以下操作:
  • 它将开始监视源代码的更改。

  • 它将使用 Jib 对我们的 Spring Boot 应用程序进行容器化。由于我们使用的是本地 Kubernetes 集群,Skaffold 足够智能,不会将图像推送到远程注册表以实现快速内部开发循环。相反,它将图像加载到本地 Docker 守护程序中。

  • 它将部署图像到 Minikube 集群,端口转发到端口8080,并在您的 IDE 中开始流式传输日志。您的 IDE 中的事件日志将显示服务 URL,您可以使用该 URL 访问您的应用程序。输出将类似于我们在上一节中看到的内容。

在 Kubernetes 上运行选项类似于skaffold run命令。您可以使用此选项在需要时部署,而不是在每次代码更改时都这样做。

即使我们还没有这样做,您甚至可以使用 Cloud Code 部署到远程 Kubernetes 集群。如果您的 Kubernetes 上下文指向像 GKE 这样的远程集群,那么 Cloud Code 也可以在那里进行部署。如果您没有远程集群,Cloud Code 也可以帮助您创建。

Cloud Code 具有良好的集成,可以运行无服务器工作负载,以及使用谷歌的 Cloud Run。

在本节中,您学习了如何使用 Cloud Code 将 Spring Boot 应用程序容器化并部署到本地 Kubernetes 集群。现在,让我们总结一下本章。

总结

在本章中,您学习了如何使用谷歌开发的 Cloud Code 插件,从您的 IDE 中进行单击部署 Kubernetes 应用程序。我们从解释 Cloud Code 的各种功能开始了本章。在示例中,我们解释了如何使用 Cloud Code 提供的启动模板从您的 IDE 中编写、构建和部署 Java 应用程序。然后,我们创建了一个使用 Dekorate 在编译时生成 Kubernetes 清单的 Spring Boot 应用程序。最后,我们将 Spring Boot 应用程序容器化并部署到本地 Minikube 集群。

通过这样做,您已经发现了如何使用 Cloud Code 在开发云原生应用程序时提高生产力。

下一章将讨论如何将 Spring Boot 应用程序部署到 Google Kubernetes Engine。

第八章:使用 Skaffold 将 Spring Boot 应用部署到 Google Kubernetes Engine

在上一章中,您学习了如何使用 Google 的 IntelliJ 的 Cloud Code 插件将 Spring Boot 应用部署到本地 Kubernetes 集群。本章重点介绍了如何将相同的 Spring Boot 应用部署到远程 Google Kubernetes Engine(GKE),这是 Google Cloud Platform(GCP)提供的托管 Kubernetes 服务。我们还将向您介绍 Google 最近推出的无服务器 Kubernetes 服务 GKE Autopilot。您还将了解 Google Cloud SDK 和 Cloud Shell,并使用它们来连接和管理远程 Kubernetes 集群。

在本章中,我们将涵盖以下主要主题:

  • 开始使用 Google Cloud Platform

  • 使用 Google Cloud SDK 和 Cloud Shell

  • 设置 Google Kubernetes Engine

  • 介绍 GKE Autopilot 集群

  • 将 Spring Boot 应用部署到 GKE

到本章结束时,您将对 GCP 提供的基本服务有深入的了解,以便将 Spring Boot 应用部署到 Kubernetes。

技术要求

您需要在系统上安装以下内容才能按照本章的示例进行操作:

本章中的代码示例也可以在 GitHub 上找到:github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-Using-Skaffold

开始使用 Google Cloud Platform

今天,许多组织利用不同云提供商提供的服务,如亚马逊网络服务(AWS)、谷歌的 GCP、微软 Azure、IBM 云或甲骨文云。使用这些云供应商的优势在于您无需自行管理基础架构,通常按小时支付这些服务器的使用费用。此外,大多数情况下,如果组织不了解或未能解决其应用程序所需的计算能力,可能会导致计算资源的过度配置。

如果您自行管理基础架构,就必须雇佣一大批人员来负责维护活动,如打补丁操作系统、升级软件和升级硬件。这些云供应商通过为我们提供这些服务来帮助我们解决业务问题。此外,这些云供应商支持的产品都具有内置的维护功能,无论是数据库还是 Kubernetes 等托管服务。如果您已经使用过这些云供应商中的任何一个,您可能会发现所有这些供应商提供类似的服务或产品,但实施和工作方式是不同的。

例如,您可以在链接cloud.google.com/free/docs/aws-azure-gcp-service-comparison中查看 GCP 提供的服务及其 AWS 和 Azure 等价物。

现在我们知道使用这些云供应商有不同用例的优势,让我们谈谈一个这样的云供应商——谷歌云平台。

谷歌云平台(通常缩写为 GCP)为您提供一系列服务,如按需虚拟机(通过谷歌计算引擎)、用于存储文件的对象存储(通过谷歌云存储)和托管的 Kubernetes(通过谷歌 Kubernetes 引擎)等。

在开始使用谷歌的云服务之前,您首先需要注册一个帐户。如果您已经拥有谷歌帐户,如 Gmail 帐户,那么您可以使用该帐户登录,但您仍需要单独注册云帐户。如果您已经在谷歌云平台上注册,可以跳过此步骤。

首先,转到cloud.google.com。接下来,您将被要求进行典型的 Google 登录流程。如果您还没有 Google 帐户,请按照注册流程创建一个。以下屏幕截图是 Google Cloud 登录页面:

图 8.1 - 开始使用 Google Cloud

图 8.1 - 开始使用 Google Cloud

如果您仔细查看屏幕截图,会发现它说新客户可获得价值 300 美元的 Google Cloud 免费信用额度。所有客户都可以免费使用 20 多种产品。这意味着您可以免费使用免费套餐产品,而无需支付任何费用,并且您还将获得价值 300 美元的信用额度,可供您在 90 天内探索或评估 GCP 提供的不同服务。例如,您可以在指定的月度使用限制内免费使用 Compute Engine、Cloud Storage 和BigQuery

您可以单击免费开始登录。如果您是第一次注册,必须提供您的计费信息,这将重定向您到您的云控制台。此外,系统会自动为您创建一个新项目。项目是您的工作空间。单个项目中的所有资源都与其他项目中的资源隔离。您可以控制对该项目的访问,并仅授予特定个人或服务帐户访问权限。以下屏幕截图是您的 Google Cloud 控制台仪表板视图:

图 8.2 - Google Cloud 控制台仪表板

图 8.2 - Google Cloud 控制台仪表板

在控制台页面的左侧,您可以查看 GCP 提供的不同服务:

图 8.3 - Google Cloud 服务视图

图 8.3 - Google Cloud 服务视图

在本章中,重点将放在 GCP 提供的 GKE 服务 API 上。但在讨论这些服务之前,我们需要安装一些工具来使用这些服务。让我们在下一节讨论这些工具。

使用 Google Cloud SDK 和 Cloud Shell

您现在可以访问 GCP 控制台,并且可以使用控制台几乎可以做任何事情。但是,开发人员的更好方法是使用 Cloud SDK,这是一组工具,允许通过使用仿真器或kubectlSkaffoldminikube等工具进行更快的本地开发。不仅如此,您还可以管理资源,对远程 Kubernetes 集群进行身份验证,并从本地工作站启用或禁用 GCP 服务。另一个选项是从浏览器使用 Cloud Shell,我们将在本章中探索这两个选项。Cloud SDK 为您提供了与其产品和服务进行交互的工具和库。在使用 Cloud SDK 时,您可以根据需要安装和删除组件。

让我们从 Cloud SDK 开始。您可以转到cloud.google.com/sdk/并单击开始按钮。这将重定向您到安装指南。Cloud SDK 的最低先决条件是具有 Python。支持的版本包括 Python 3(首选 3.5 到 3.8)和 Python 2(2.7.9 或更高版本)。例如,现代版本的 macOS 包括 Cloud SDK 所需的适当版本的 Python。但是,如果您想要安装带有 Cloud SDK 的 Python 3,可以选择带有捆绑 Python 安装的 macOS 64 位版本。

在 Linux 上下载 Cloud SDK

Cloud SDK 需要安装 Python,因此首先使用以下命令验证 Python 版本:

python --version

要从命令行下载 Linux 64 位存档文件,请运行以下命令:

curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-336.0.0-linux-x86_64.tar.gz

对于 32 位存档文件,请运行以下命令:

curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-336.0.0-linux-x86.tar.gz

在 macOS 上下载 Cloud SDK

要在 macOS 上下载 Cloud SDK,您可以选择以下选项:

图 8.4 - macOS 的下载选项

图 8.4 - macOS 的下载选项

如果您不确定您的机器硬件,那么运行uname -m命令。根据您的机器,您将获得以下输出:

$uname -m
x86_64

现在选择适当的软件包,并从cloud.google.com/sdk/docs/install#mac中的表中的软件包列中给出的 URL 进行下载。

设置 Cloud SDK

下载软件包后,您需要将存档提取到文件系统上您选择的位置。以下是提取的google-cloud-sdk存档的内容:

tree -L 1 google-cloud-sdk
google-cloud-sdk
├── LICENSE
├── README
├── RELEASE_NOTES
├── VERSION
├── bin
├── completion.bash.inc
├── completion.zsh.inc
├── data
├── deb
├── install.bat
├── install.sh
├── lib
├── path.bash.inc
├── path.fish.inc
├── path.zsh.inc
├── platform
├── properties
└── rpm

解压缩存档后,您可以通过运行存档根目录中的install.sh脚本来继续安装。您可能会看到以下输出:

$ ./google-cloud-sdk/install.sh 
Welcome to the Google Cloud SDK!
To help improve the quality of this product, we collect anonymized usage data
and anonymized stacktraces when crashes are encountered; additional information
is available at <https://cloud.google.com/sdk/usage-statistics>. This data is
handled in accordance with our privacy policy
<https://cloud.google.com/terms/cloud-privacy-notice>. You may choose to opt in this
collection now (by choosing 'Y' at the below prompt), or at any time in the
future by running the following command:
    gcloud config set disable_usage_reporting false
Do you want to help improve the Google Cloud SDK (y/N)?  N
Your current Cloud SDK version is: 336.0.0
The latest available version is: 336.0.0

在以下屏幕中,您可以看到已安装和未安装的组件列表:

图 8.5 – Google Cloud SDK 组件列表

图 8.5 – Google Cloud SDK 组件列表

您可以使用以下 Cloud SDK 命令来安装或移除组件:

To install or remove components at your current SDK version [336.0.0], run:
  $ gcloud components install COMPONENT_ID
  $ gcloud components remove COMPONENT_ID
Enter a path to an rc file to update, or leave blank to use 
[/Users/ashish/.zshrc]:  
No changes necessary for [/Users/ashish/.zshrc].
For more information on how to get started, please visit:
  https://cloud.google.com/sdk/docs/quickstarts

确保在此之后使用source .zshrc命令来源化您的 bash 配置文件。从安装中,您可以看到默认只安装了三个组件,即. bqcoregsutil

下一步是运行gcloud init命令来初始化 SDK,使用以下命令:

$/google-cloud-sdk/bin/gcloud init  
Welcome! This command will take you through the configuration of gcloud.
Your current configuration has been set to: [default]
You can skip diagnostics next time by using the following flag:
  gcloud init --skip-diagnostics
Network diagnostic detects and fixes local network connection issues.
Checking network connection...done
Reachability Check passed.
Network diagnostic passed (1/1 checks passed).
You must log in to continue. Would you like to log in (Y/n)?  Y
Your browser has been opened to visit:
    https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=32555940559.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2F&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Flocalhost%3A8085%2F&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fappengine.admin+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcompute+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Faccounts.reauth&state=CU1Yhij0NWZB8kZvNx6aAslkkXdlYf&access_type=offline&code_challenge=sJ0_hf6-zNKLjVSw9fZlxjLodFA-EsunnBWiRB5snmw&code_challenge_method=S256

此时,您将被重定向到浏览器窗口,并被要求登录您的 Google 账户进行身份验证,并授予 Cloud SDK 对您的云资源的访问权限。

点击允许按钮,确保下次可以作为自己与 GCP API 进行交互。授予访问权限后,您将看到以下屏幕以确认身份验证:

图 8.6 – Google Cloud SDK 身份验证完成

图 8.6 – Google Cloud SDK 身份验证完成

现在您已经完成了身份验证,并准备好使用 Cloud SDK 进行工作。完成身份验证后,您可能会在命令行上看到以下内容:

Updates are available for some Cloud SDK components.  To install them,
please run:
  $ gcloud components update
You are logged in as: [XXXXXXX@gmail.com].
Pick cloud project to use: 
 [1] xxxx-xxx-307204
 [2] Create a new project
Please enter numeric choice or text value (must exactly match list 
item):  1
Your current project has been set to: [your-project-id].
Do you want to configure a default Compute Region and Zone? (Y/n)?  Y
Which Google Compute Engine zone would you like to use as project 
default?
If you do not specify a zone via a command line flag while working 
with Compute Engine resources, the default is assumed.
 [1] us-east1-b
 [2] us-east1-c
 [3] us-east1-d
.................
Please enter a value between 1 and 77, or a value present in the list:  1
Your project default Compute Engine zone has been set to [us-east1-b].
You can change it by running [gcloud config set compute/zone NAME].
Your project default Compute Engine region has been set to [us-east1].
You can change it by running [gcloud config set compute/region NAME].
Your Google Cloud SDK is configured and ready to use!
......

从命令行输出可以清楚地看出,我们已经选择了项目并确认了计算引擎区域。现在,我们已经成功安装了 Cloud SDK。在下一节中,我们将学习有关 Cloud Shell 的内容。

使用 Cloud Shell

Cloud Shell 是一个基于浏览器的终端/CLI 和编辑器。它预装了诸如 Skaffold、minikube 和 Docker 等工具。您可以通过单击 Cloud 控制台浏览器窗口右上角的以下图标来激活它:

图 8.7 – 激活 Cloud Shell

图 8.7 – 激活 Cloud Shell

激活后,您将被重定向到以下屏幕:

图 8.8 – Cloud Shell 编辑器

图 8.8 – Cloud Shell 编辑器

您可以使用gcloud config set project projectid命令设置您的项目 ID,或者只是开始使用gcloud命令进行操作。以下是 Cloud Shell 提供的一些突出功能:

  • Cloud Shell 完全基于浏览器,并且您可以从任何地方访问它。唯一的要求是互联网连接。

  • Cloud Shell 为您的$HOME目录挂载了 5GB 的持久存储。

  • Cloud Shell 带有在线代码编辑器。您可以使用它来构建、测试和调试您的应用程序。

  • Cloud Shell 还带有安装的 Git 客户端,因此您可以从代码编辑器或命令行克隆和推送更改到您的存储库。

  • Cloud Shell 带有 Web 预览,您可以在 Web 应用中查看本地更改。

我们已经为我们的使用安装和配置了 Google Cloud SDK。我们还看了 Cloud Shell 及其提供的功能。现在让我们创建一个 Kubernetes 集群,我们可以在其中部署我们的 Spring Boot 应用程序。

设置 Google Kubernetes Engine 集群

我们需要在 GCP 上设置一个 Kubernetes 集群,以部署我们的容器化 Spring Boot 应用程序。GCP 可以提供托管和管理的 Kubernetes 部署。我们可以使用以下两种方法在 GCP 上创建 Kubernetes 集群:

  • 使用 Google Cloud SDK 创建 Kubernetes 集群

  • 使用 Google 控制台创建 Kubernetes 集群

让我们详细讨论每个。

使用 Google Cloud SDK 创建 Kubernetes 集群

我们可以使用以下 gcloud SDK 命令创建用于运行容器的 Kubernetes 集群。这将使用默认设置创建一个 Kubernetes 集群:

图 8.9 - GKE 集群已启动

我们已成功使用 Cloud SDK 创建了一个 Kubernetes 集群。接下来,我们将尝试使用 Google 控制台创建集群。

使用 Google 控制台创建 Kubernetes 集群

要使用控制台创建 Kubernetes 集群,您应首先使用左侧导航栏并选择Kubernetes Engine。在呈现的选项中,选择Clusters

图 8.10 - 开始使用 Google Kubernetes Engine 创建集群

图 8.10 - 开始使用 Google Kubernetes Engine 创建集群

之后,您将在下一页上看到以下屏幕:

图 8.11 - 创建 Google Kubernetes Engine 集群

图 8.11 - 创建 Google Kubernetes Engine 集群

您可以通过单击弹出窗口上的CREATE按钮或单击页面顶部的+CREATE来选择创建集群。两者都会为您提供以下选项可供选择,如图 8.12中所述:

图 8.12 – Google Kubernetes Engine 集群模式

图 8.12 – Google Kubernetes Engine 集群模式

您可以选择创建标准Kubernetes 集群,或者选择完全无需操作的Autopilot模式。在本节中,我们将讨论标准集群。我们将在下一节单独讨论 Autopilot。

在标准集群模式下,您可以灵活选择集群节点的数量,并根据需要调整配置或设置。以下是创建 Kubernetes 集群的步骤。由于我们使用默认配置,您必须单击下一步接受默认选项。

图 8.13 – Google Kubernetes Engine 集群创建

图 8.13 – Google Kubernetes Engine 集群创建

最后,单击页面底部的创建按钮,您的 Kubernetes 集群将在几分钟内运行起来!

以下是您集群的默认配置:

图 8.14 – Google Kubernetes Engine 集群配置视图

图 8.14 – Google Kubernetes Engine 集群配置视图

您的 Kubernetes 集群现在已经运行起来。在下面的截图中,我们可以看到我们有一个三节点集群,具有六个 vCPU 和 12GB 的总内存:

图 8.15 – Google Kubernetes Engine 集群已经运行

图 8.15 – Google Kubernetes Engine 集群已经运行

您可以通过单击集群名称cluster-1查看有关集群节点、存储和日志的更多详细信息。以下是我们刚刚创建的集群节点的详细信息:

图 8.16 – Google Kubernetes Engine 集群视图

图 8.16 – Google Kubernetes Engine 集群视图

您可以看到整体集群状态和节点健康状况都是正常的。集群节点是使用 Compute Engine GCP 创建的,并提供机器类型为e2-medium。您可以通过查看左侧导航栏上的 Compute Engine 资源来验证这一点。我们在这里显示了相同的三个节点,GKE 集群使用了我们刚刚创建的这些节点。

图 8.17 – Google Kubernetes Engine 集群 VM 实例

图 8.17 – Google Kubernetes Engine 集群 VM 实例

我们已经学会了如何使用 Google 控制台创建一个 Kubernetes 标准集群。在接下来的部分,我们将学习关于 Autopilot 集群。

介绍 Google Kubernetes Engine Autopilot 集群

2021 年 2 月 24 日,Google 宣布了他们完全托管的 Kubernetes 服务 GKE Autopilot 的一般可用性。这是一个完全托管和无服务器的 Kubernetes 即服务提供。目前没有其他云提供商在管理云上的 Kubernetes 集群时提供这种级别的自动化。大多数云提供商会让你自己管理一些集群管理工作,无论是管理控制平面(API 服务器、etcd、调度器等)、工作节点,还是根据你的需求从头开始创建一切。

正如其名字所示,GKE Autopilot 是一个完全无需干预的体验,在大多数情况下,你只需要指定一个集群名称和区域,如果需要的话设置网络,就这样。你可以专注于部署你的工作负载,让 Google 完全管理你的 Kubernetes 集群。Google 为 Autopilot pods 在多个区域提供 99.9%的正常运行时间。即使你自己管理这些,也无法达到 Google 提供的数字。此外,GKE Autopilot 是具有成本效益的,因为你不需要支付虚拟机(VMs)的费用,你只需要按资源的秒数计费(例如,被你的 pods 消耗的 vCPU、内存和磁盘空间)。

那么,我们在上一节中创建的 GKE 标准集群和 GKE Autopilot 集群有什么区别呢?答案如下:对于标准集群,你只管理节点,因为 GKE 管理控制平面;而对于 GKE Autopilot,你什么都不用管理(甚至不用管理你的工作节点)。

这引发了一个问题:我无法控制我的节点是好事还是坏事?现在,这是值得讨论的,但是今天大多数组织并不像 amazon.com、google.com 或 netflix.com 那样处理流量或负载。这可能是一个过于简化的说法,但老实说,即使您认为自己有特定的需求或需要一个专门的集群,往往最终会浪费大量时间和资源来保护和管理您的集群。如果您有一支 SRE 团队,他们可以匹敌 Google SRE 的经验或知识水平,您可以随心所欲地处理您的集群。但是今天大多数组织并没有这样的专业知识,也不知道他们在做什么。这就是为什么最好依赖完全托管的 Kubernetes 服务,比如 GKE Autopilot – 它经过了实战测试,并且根据从 Google SRE 学到的最佳实践进行了加固。

我们已经讨论了足够多关于 GKE 自动驾驶的特性以及它提供的完全抽象,然而,要记住这些抽象,也有一些限制。例如,在自动驾驶模式下,您不能为容器运行特权模式。有关限制的完整列表,请阅读官方文档:cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview#limits

到目前为止,我们已经对 GKE 自动驾驶有了足够的了解,现在是时候创建我们的集群了。让我们开始吧!

创建自动驾驶集群

在单击配置按钮之后,如图 8.13中所述,您将被重定向到以下屏幕:

图 8.18 – 创建 GKE 自动驾驶集群

图 8.18 – 创建 GKE 自动驾驶集群

自动驾驶集群具有节点管理、网络、安全和遥测等功能,这些功能已经内置了 Google 推荐的最佳实践。GKE 自动驾驶确保您的集群经过了优化,并且已经准备好投入生产。

正如您所见,您在这里可以更改的选项非常少。您可以更改集群的名称,选择另一个区域,或选择网络(即公共或私有)。在网络选项下,您可以更改诸如网络、子网络、Pod IP 地址范围和集群服务 IP 地址范围等内容,如下面的屏幕截图所示:

图 8.19 – GKE 自动驾驶集群配置

图 8.19 – GKE Autopilot 集群配置

高级选项下,您可以启用维护窗口,并允许特定时间范围内的维护排除(如图 8.19所示)。在此窗口中,您的 GKE 集群将进行自动维护窗口,并且不会对您可用。您应根据自己的需求选择维护窗口。

图 8.20 – 配置 GKE Autopilot 集群维护窗口

图 8.20 – 配置 GKE Autopilot 集群维护窗口

现在,我们将使用默认值并单击页面底部的创建按钮来创建集群。创建集群可能需要几分钟时间。在下面的屏幕截图中,您可以看到 Autopilot 集群已经运行:

图 8.21 – GKE Autopilot 集群已经运行

图 8.21 – GKE Autopilot 集群已经运行

在这里,您可以看到节点的数量没有被提及,因为它是由 GKE 管理的。

接下来,我们可以尝试连接到这个集群。要这样做,请单击屏幕右上角的三个点,然后单击连接

图 8.22 – 连接到 GKE Autopilot 集群

图 8.22 – 连接到 GKE Autopilot 集群

单击连接后,应该会出现以下弹出窗口。您可以将此处提到的命令复制到您的 CLI 或 Cloud Shell 中:

图 8.23 – 连接到 GKE Autopilot 集群的命令

图 8.23 – 连接到 GKE Autopilot 集群的命令

然后,您可以使用以下kubectl get nodes命令验证集群详细信息:

图 8.24 – kubectl 命令输出

我们还可以使用以下命令在自动驾驶模式下创建 GKE 集群:

图 8.25 – 自动驾驶模式下的 GKE 集群

我们也可以在 Google Cloud 控制台上进一步验证。您可以看到我们现在有两个集群。第一个是使用 Cloud 控制台创建的,第二个是使用 gcloud 命令行创建的。

图 8.26 – GKE Autopilot 和标准模式集群

图 8.26 – GKE Autopilot 集群

我们已经了解了在 GCP 上创建 Kubernetes 集群的不同方式。现在,让我们使用 Skaffold 将一个可工作的 Spring Boot 应用程序部署到 GKE。

将 Spring Boot 应用程序部署到 GKE

我们将在本节中使用的 Spring Boot 应用程序与上一章中相同(我们命名为Breathe – View Real-Time Air Quality Data的应用程序)。我们已经熟悉这个应用程序,所以我们将直接跳转到部署到 GKE。我们将使用在上一节中创建的gke-autopilot-cluster1来进行部署。我们将使用 Skaffold 使用以下两种方法进行部署:

  • 使用 Skaffold 从本地部署到远程 GKE 集群

  • 使用 Skaffold 从 Cloud Shell 部署到 GKE 集群

使用 Skaffold 从本地部署到远程 GKE 集群

在本节中,您将学习如何使用 Skaffold 将 Spring Boot 应用程序部署到远程 Kubernetes 集群。让我们开始吧:

  1. 在上一章中,我们使用Dockerfile将我们的 Spring Boot 应用程序容器化。然而,在本章中,我们将使用Jib-Maven插件来容器化应用程序。我们已经知道如何在以前的章节中使用 jib-maven 插件,所以我们将跳过在这里再次解释这个。

  2. 唯一的变化是我们将使用Google 容器注册表GCR)来存储 Jib 推送的图像。GCR 是您图像的安全私有注册表。在那之前,我们需要确保 GCR 访问已对您的帐户启用。您可以使用以下gcloud命令来允许访问:

gcloud services enable containerregistry.googleapis.com

或者您可以转到cloud.google.com/container-registry/docs/quickstart,并通过单击启用 API按钮来启用容器注册表 API。

图 8.27 – 启用 Google 容器注册表 API

图 8.27 – 启用 Google 容器注册表 API

接下来,您将被要求选择一个项目,然后单击继续。就是这样!

图 8.28 – 为容器注册表 API 注册您的应用程序

图 8.28 – 为容器注册表 API 注册您的应用程序

  1. 您还可以使容器注册表中的图像对公共访问可用。如果它们是公共的,您的图像用户可以在不进行任何身份验证的情况下拉取图像。在下面的屏幕截图中,您可以看到一个选项,启用漏洞扫描,用于推送到您的容器注册表的图像。如果您愿意,您可以允许它扫描您的容器图像以查找漏洞。图 8.29 – GCR 设置

图 8.29 – GCR 设置

  1. 下一个谜题的部分是创建 Kubernetes 清单,如DeploymentService。在上一章中,我们使用了Dekorate工具(github.com/dekorateio/dekorate)创建了它们。在这里,我们将继续使用相同的 Kubernetes 清单生成过程。生成的 Kubernetes 清单位于target/classes/META-INF/dekorate/kubernetes.yml路径下。

  2. 接下来,我们将运行skaffold init --XXenableJibInit命令,这将为我们创建一个skaffold.yaml配置文件。您可以看到 Skaffold 在生成的skaffold.yaml文件的deploy部分中添加了 Kubernetes 清单的路径,并将使用jib进行镜像构建:

apiVersion: skaffold/v2beta20
kind: Config
metadata:
  name: scanner
build:
artifacts:
  - image: breathe
    jib:
      project: com.air.quality:scanner
deploy:
  kubectl:
    manifests:
    - target/classes/META-INF/dekorate/kubernetes.yml
  1. 我们有与上一章中解释的相同的主类,该主类使用了 Dekorate 工具提供的@KubernetesApplication (serviceType = ServiceType.LoadBalancer)注解,将服务类型声明为LoadBalancer
@KubernetesApplication(serviceType = ServiceType.LoadBalancer)
@SpringBootApplication
public class AirQualityScannerApplication {
   public static void main(String[] args) {
      SpringApplication.run(AirQualityScannerApplication.        class, args);
   }
}

在编译时,Dekorate 将生成以下 Kubernetes 清单。我还将它们保存在源代码的 k8s 目录中,因为有时我们必须手动添加或删除 Kubernetes 清单中的内容。部署和服务 Kubernetes 清单也可以在 GitHub 上找到:github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-Using-Skaffold/blob/main/Chapter07/k8s/kubernetes.yml

接下来,我们需要确保您已经通过gcloud auth list命令进行了身份验证,以便使用 Google Cloud 服务。您将看到以下输出:

Credentialed AccountsACTIVE  ACCOUNT*       <my_account>@<my_domain.com>To set the active account, run:    $ gcloud config set account 'ACCOUNT'

如果您尚未进行身份验证,也可以使用gcloud auth login命令。

  1. 如果尚未设置,请使用gcloud config set project <PROJECT_ID>命令设置您的 GCP 项目。

  2. 确保 Kubernetes 上下文设置为远程 Google Kubernetes 集群。使用以下命令进行验证:

$ kubectl config current-context    
gke_project_id_us-east1_gke-autopilot-cluster1
  1. 现在我们已经准备好部署。让我们运行skaffold run --default-repo=gcr.io/<PROJECT_ID>命令。这将构建应用程序的容器镜像,并将其推送到远程 GCR。图 8.30 – 镜像推送到 Google 容器注册表

图 8.30 – 镜像推送到 Google 容器注册表

推送的镜像详细信息可以在以下截图中看到:

图 8.31 – Google 容器注册表图像视图

图 8.31 – Google 容器注册表图像视图

  1. 最后,将其部署到远程 Google Kubernetes 集群。第一次运行时,部署需要一些时间来稳定,但后续运行会快得多。图 8.32 – Skaffold 运行输出

图 8.32 – Skaffold 运行输出

  1. 我们还可以在 Google Cloud 控制台上查看部署状态。转到Kubernetes Engine,然后单击左侧导航栏上的工作负载选项卡以查看部署状态。部署状态为OK图 8.33 – 部署状态

图 8.33 – 部署状态

您可以通过单击应用程序名称查看更多部署详情。

图 8.34 – 部署详情

图 8.34 – 部署详情

  1. 到目前为止一切看起来都很好。现在我们只需要服务的 IP 地址,这样我们就可以访问我们的应用程序了。在同一部署详情页面的底部,我们有关于我们的服务的详细信息。图 8.35 – 暴露的服务

图 8.35 – 暴露的服务

  1. 让我们访问 URL 并验证是否获得了期望的输出。我们可以查看德里的实时空气质量数据:图 8.36 – Spring Boot 应用响应

图 8.36 – Spring Boot 应用响应

  1. 我们可以使用执行器/health/liveness/health/readiness端点来验证应用程序的健康状况。我们已经将这些端点用作部署到 Kubernetes 集群的 Pod 的活跃性和就绪性探针。

图 8.37 – Spring Boot 应用执行器探针

图 8.37 – Spring Boot 应用执行器探针

通过这些步骤,我们已经完成了使用 Skaffold 从本地工作站将我们的 Spring Boot 应用部署到远程 Google Kubernetes 集群。在下一节中,我们将学习如何从基于浏览器的 Cloud Shell 环境部署应用到 GKE。

使用 Skaffold 从 Cloud Shell 部署到 GKE 集群

在本节中,重点将放在使用基于浏览器的 Cloud Shell 工具将 Spring Boot 应用部署到 GKE 上。让我们开始吧!

  1. 第一步是激活 Cloud Shell 环境。这可以通过在 Google Cloud 控制台右上角点击激活 Cloud Shell图标来完成。图 8.38 – Cloud Shell 编辑器

图 8.38 – Cloud Shell 编辑器

  1. 如前一个截图所示,您被要求使用gcloud config set project [PROJECT_ID]命令设置您的 Cloud PROJECT_ID。如果您知道您的PROJECT_ID,您可以使用这个命令,或者使用gcloud projects list等命令。之后,Cloud Shell 将请求授权您的请求,通过调用 GCP API。之后,您无需为每个请求提供凭据。图 8.39 – 授权 Cloud Shell

图 8.39 – 授权 Cloud Shell

我们需要在 Cloud Shell 环境中的应用程序源代码。Cloud Shell 带有安装了 Git 客户端,因此我们可以运行git clone github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-using-Skaffold.git命令并克隆我们的 GitHub 存储库。

图 8.40 – 克隆 GitHub 存储库

图 8.40 – 克隆 GitHub 存储库

  1. 接下来,您需要编译项目,以便生成 Kubernetes 清单。运行./mvnw clean compile命令来构建您的项目。您的构建将失败,并且您将收到错误:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project scanner: Fatal error compiling: error: invalid target release: 16 -> [Help 1] . 

失败的原因是在 Cloud Shell 环境中将JAVA_HOME设置为 Java 11:

$ java -version
openjdk version "11.0.11" 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-post-Debian-1deb10u1)
OpenJDK 64-Bit Server VM (build 11.0.11+9-post-Debian-1deb10u1, mixed mode, sharing)

我们在pomx.ml中指定要使用 Java 16。这个问题可以通过下载 Java 16 并设置JAVA_HOME环境变量来解决。

注意

我们有解决这个问题的正确工具,SDKMAN,可以从 sdkman.io/ 访问。它允许您并行使用多个版本的 Java JDK。查看支持的 JDK(sdkman.io/jdks)和 SDK(sdkman.io/sdks)。随着新的六个月发布周期,我们每六个月就会获得一个新的 JDK。作为开发人员,我们喜欢尝试和探索这些功能,通过手动下载并更改 JAVA_HOME 来切换到不同的 JDK。整个过程都是手动的,而使用 SDKMAN,我们只需运行一个命令来下载您选择的 JDK,下载完成后,它甚至会将 JAVA_HOME 更新为最新下载的 JDK。很酷,不是吗?

  1. 让我们尝试使用 SDKMAN 安装 JDK16。请注意,您不必在云 Shell 预配的 VM 实例中安装 SDKMAN,因为它已经预装。现在在 CLI 中输入 sdk,它将显示支持的命令:图 8.41 – SDKMAN 命令帮助

图 8.41 – SDKMAN 命令帮助

要了解不同支持的 JDK,请运行 sdk list java 命令。在下面的截图中,您将无法看到所有支持的 JDK 供应商,但您可以了解到大致情况:

图 8.42 – SDKMAN 支持的 JDK

图 8.42 – SDKMAN 支持的 JDK

要下载特定供应商的 JDK,请运行 sdk install java Identifier 命令。在我们的情况下,实际命令将是 sdk install java 16-open,因为我们决定使用 Java 16 的 OpenJDK 构建。

图 8.43 – 安装 JDK16

图 8.43 – 安装 JDK16

您可能还想要运行以下命令来更改活动 shell 会话中的 JDK:

$ sdk use java 16-open
Using java version 16-open in this shell.
  1. 让我们再次通过运行 ./mvnw clean compile 命令来编译项目。在下面的输出中,您可以看到构建成功:图 8.44 – Maven 构建成功

图 8.44 – Maven 构建成功

  1. 我们准备好从 Cloud Shell 运行命令,将 Spring Boot 应用部署到远程 GKE 集群。在此之前,请确保您的 Kubernetes 上下文已设置为远程集群。如果不确定,请通过运行kubectl config current-context命令进行验证。如果未设置,则使用gcloud container clusters get-credentials gke-autopilot-cluster1 --region us-east1命令进行设置,这将在kubeconfig文件中添加条目。

  2. 在最后一步,我们只需运行skaffold run --default-repo=gcr.io/<PROJECT_ID>命令。部署已稳定,最终输出将与上一节中步骤 13中看到的相同。

图 8.45 - Skaffold 运行输出

图 8.45 - Skaffold 运行输出

使用基于浏览器的 Cloud Shell 环境完成了将 Spring Boot 应用部署到远程 GKE 集群的过程。我们学会了如何利用基于浏览器的预配置 Cloud Shell 环境进行开发。如果您想尝试和尝试一些东西,那么这是 Google 提供的一个很好的功能。但是,我不确定您是否应该将其用于生产用例。使用 Cloud Shell 提供的 Google Compute Engine VM 实例是基于每个用户、每个会话的基础提供的。如果您的会话处于活动状态,您的 VM 实例将持续存在;否则,它们将被丢弃。有关 Cloud Shell 工作原理的信息,请阅读官方文档:

cloud.google.com/shell/docs/how-cloud-shell-works

总结

在本章中,我们首先讨论了使用云供应商的功能和优势。然后,我们向您介绍了 GCP。首先,我们详细介绍了如何加入 Cloud 平台。接下来,我们介绍了 Google Cloud SDK,它允许您执行各种任务,如安装组件、创建 Kubernetes 集群以及启用不同的服务,如 Google 容器注册表等。

我们还讨论了基于浏览器的 Cloud Shell 编辑器,它由 Google Compute Engine VM 实例提供支持。您可以将其用作临时沙盒环境,以测试 GCP 支持的各种服务。然后,我们介绍了使用 Cloud SDK 和 Cloud Console 创建 Kubernetes 集群的两种不同方式。之后,我们向您介绍了无服务器 Kubernetes 提供的 GKE Autopilot,并介绍了其特点和优势,以及与标准 Kubernetes 集群相比的优势。最后,我们使用 Skaffold 从本地成功将 Spring Boot 应用程序部署到 GKE Autopilot 集群,然后在最后一节中使用 Google Cloud Shell。

在本章中,您已经获得了有关 GCP 托管的 Kubernetes 服务以及 Cloud SDK 和 Cloud Shell 等工具的实际知识。您还学会了如何使用 Skaffold 将 Spring Boot 应用程序部署到远程 Kubernetes 集群。

在下一章中,我们将学习如何使用 GitHub actions 和 Skaffold 创建 CI/CD 流水线。

进一步阅读

第九章:使用 Skaffold 创建一个生产就绪的 CI/CD 流水线

在上一章中,我们学习了如何使用 Skaffold 将 Spring Boot 应用部署到 Google Cloud Platform。在本章中,重点将介绍 GitHub Actions 及其相关概念。我们还将演示如何使用 Skaffold 和 GitHub Actions 创建一个生产就绪的 Spring Boot 应用的持续集成(CI)持续部署(CD)流水线。在最后一节中,我们将熟悉 GitOps 概念,并学习如何使用 Argo CD 和 Skaffold 为 Kubernetes 应用创建持续交付流水线。

本章将涵盖以下主要主题:

  • 使用 GitHub Actions 入门

  • 创建 GitHub Actions 工作流

  • 使用 GitHub Actions 和 Skaffold 创建 CI/CD 流水线

  • 使用 Argo CD 和 Skaffold 实现 GitOps 工作流

通过本章的学习,您将对如何使用 GitHub Actions 和 Skaffold 创建有效的 CI/CD 流水线有扎实的理解。

技术要求

GitHub 存储库中的代码可以在github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-using-Skaffold/tree/main/Chapter07找到。

使用 GitHub Actions 入门

GitHub Actions 允许您从 GitHub 存储库构建、测试和部署工作负载。GitHub Actions 是事件驱动的;例如,当有人创建拉取请求、打开问题、进行部署等时。具体的操作是基于事件触发的。您甚至可以创建自己的 GitHub Actions 来根据您的用例定制工作流。还有一个很棒的市场可用,网址为 https://github.com/marketplace,您可以从中将现有的 GitHub Actions 集成到您的工作流中。

GitHub Actions 使用 YAML 语法文件来定义事件、作业、操作和命令。在下图中,您可以看到 GitHub Actions 组件的完整列表:

图 9.1 – GitHub Actions 组件

图 9.1 – GitHub Actions 组件

让我们详细讨论 GitHub 组件:

  • 工作流:这用于在 GitHub 上构建、测试、打包、发布或部署项目。工作流由作业组成,并由事件触发。工作流在您的 GitHub 存储库中的.github/workflows目录中以 YAML 语法文件定义。

  • 事件:这代表触发工作流的活动;例如,将更改推送到分支或创建拉取请求。

  • 作业:这由在 runner 上执行的步骤组成。它使用步骤来控制操作执行的顺序。您可以为工作流运行多个作业。它们可以并行或顺序运行。

  • 步骤:这些代表一个动作,即检出源代码或 shell 命令。

  • 操作:这些代表您想要运行的命令,比如检出您的源代码或下载 JDK。

  • 跑步者:这是一个托管在 GitHub 上的服务器,安装了 runner 应用程序。您可以托管自己的 runner,也可以使用 GitHub 提供的 runner。在工作流中定义的作业在 runner 机器上执行。它将结果、进度和日志发送回 GitHub 存储库。GitHub 托管的 runner 支持 Ubuntu Linux、Microsoft Windows 和 macOS。

现在我们已经了解了 GitHub Action 组件的详细信息。在下一节中,我们将为 Spring Boot 应用程序创建一个 GitHub Action 工作流。

创建 GitHub Actions 工作流

在本节中,我们将创建一个使用 GitHub Actions 构建 Spring Boot Java 应用程序的工作流。这个工作流将使用mvn clean install Maven 构建工具命令来构建一个 Spring Boot 应用程序。以下是使用 Maven 构建 Java 项目的工作流文件示例:

name: Build Java project with Maven
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Install and Setup Java 16  
      uses: AdoptOpenJDK/install-jdk@v1
      with:
        version: '16'
        architecture: x64
    - name: Build with Maven
      run: mvn clean install

以下是工作流 YAML 文件的解释:

  1. 在工作流 YAML 文件中,我们已经订阅了pushpull请求事件。因此,每当为主分支提出pull请求或推送更改时,此工作流将触发。

  2. 然后在jobs部分,我们首先指定该作业将在 GitHub 托管的ubuntu Linux 操作系统 runner 上运行。

  3. steps中,我们已经定义了需要执行此工作流的操作。

  4. 首先,我们使用actions/checkout@v2在 runner 上检出源代码。

  5. 然后我们安装诸如 Java 之类的依赖项。我们使用AdoptOpenJDK/install-jdk@v1操作来做到这一点。

  6. 在最后一步中,我们正在使用mvn clean install命令构建一个 Java 项目。

让我们看看这个工作流是如何运作的。接下来,我们将在我们的 GitHub 存储库中创建这个工作流,并通过将更改推送到主分支来触发工作流。

我们将使用我们在第七章中创建的 Spring Boot 应用程序,使用 Cloud Code 插件构建和部署 Spring Boot 应用程序,进行演示。我已经详细解释了应用程序,所以在这里不会再次解释:

  1. 第一步是在您的 GitHub 存储库中创建工作流 YAML 文件。这可以通过导航到 GitHub 存储库的Actions选项卡,并点击自己设置工作流链接来完成,如下截图所示:图 9.2 – 使用 GitHub Actions 入门

图 9.2 – 使用 GitHub Actions 入门

在下一个屏幕上,粘贴我们之前讨论过的工作流 YAML 文件的内容。请参考以下截图:

图 9.3 – 创建工作流

图 9.3 – 创建工作流

点击开始提交按钮后,将打开一个新的提交消息窗口,您可以在其中输入提交消息。

  1. 然后点击提交新文件将工作流文件添加到 GitHub 存储库中:图 9.4 – 提交工作流文件

图 9.4 – 提交工作流文件

  1. 在存储库中,现在您可以看到有一个.github/workflows目录,在该目录中,我们有main.yml工作流文件:

图 9.5 – 将 GitHub 工作流文件添加到您的存储库

图 9.5 – 将 GitHub 工作流文件添加到您的存储库

这也会创建一个提交并将更改推送到存储库,从而触发工作流。在下面的截图中,您可以看到工作流已触发并正在进行中:

图 9.6 – 执行 GitHub 工作流

图 9.6 – 执行 GitHub 工作流

在下面的截图中,您可以看到流水线是绿色的,触发的工作流已成功完成:

图 9.7 – GitHub 工作流成功完成

图 9.7 – GitHub 工作流成功完成

我们已经成功使用 GitHub Actions 构建了一个 Spring Boot 应用程序。下一部分将使用 Skaffold 和 GitHub Actions 为 GitHub 仓库中的 Spring Boot 应用程序创建一个 CI/CD 流水线。

使用 GitHub Actions 和 Skaffold 创建 CI/CD 流水线

CI 和 CD 是 DevOps 生命周期的主要支柱之一。顾名思义,持续集成CI)是一种软件开发实践,开发人员每天多次将代码提交到版本控制系统。在持续部署CD)中,软件功能通过自动部署频繁交付,这个过程中没有手动干预或批准。只有测试失败才会阻止部署到生产环境。经常与持续部署混淆的是持续交付,但它们在现实中是不同的。在持续交付中,主要关注的是发布和发布策略,并通过批准实际部署到生产环境。它经常被称为单击部署

到目前为止,您将对 GitHub Actions 如何基于事件驱动并可以自动化软件开发任务有一定的了解。您还将了解到您可以根据 GitHub 仓库中的某些事件(如 Git 推送或在特定分支上创建拉取请求)触发整个 CI/CD 流水线。

本节将重点介绍使用 Skaffold 和 GitHub Actions 将 Spring Boot 应用程序部署到 Google Kubernetes 引擎。工作流程将紧密模仿我们通常如何使用 CI/CD 流水线在生产环境中进行部署。

在继续进行此任务之前,我们应该了解一些先决条件。以下是一些重点先决条件。

先决条件

请注意以下先决条件:

  • 您需要创建一个新的 Google Cloud 项目(或选择现有项目)。这部分在第八章中已经完成,使用 Skaffold 将 Spring Boot 微服务部署到 Google Cloud 平台,我们将使用相同的项目。

  • 请确保您启用了容器注册表Kubernetes 引擎API。

  • 您还需要创建一个新的Google Kubernetes 引擎GKE)集群或选择现有的 GKE 集群。

  • 如果尚未完成,您还需要为服务帐户创建 JSON 服务帐户密钥,并添加 Kubernetes 引擎开发人员和存储管理员角色。服务帐户密钥是从外部访问您的云资源的安全方式。为了建立服务帐户的身份,使用公钥/私钥对。公钥存储在 Google Cloud 中,私钥可供您使用。

  • 要创建服务帐户密钥,请在 Google Cloud 控制台的左侧导航栏上单击IAM 和管理。单击服务帐户,然后您将看到以下屏幕:

图 9.8 - 您的 GCP 项目的服务帐户

图 9.8 - 您的 GCP 项目的服务帐户

  • 现在单击服务帐户的电子邮件地址,并从右侧选项卡中选择密钥。单击添加密钥,然后选择创建新密钥,如下面的屏幕截图所示:

图 9.9 - 为您的服务帐户添加密钥

图 9.9 - 为您的服务帐户添加密钥

选择JSON作为密钥类型,然后单击创建。它将下载密钥到您的系统,如下所示:

图 9.10 - 为您的服务帐户选择密钥类型

](image/Figure_9.10_B17385.jpg)

图 9.10 - 为您的服务帐户选择密钥类型

  • 您需要向您的服务帐户添加以下 Cloud IAM 角色:

a. Kubernetes 引擎开发人员:此角色将允许您部署到 GKE。

b. 存储管理员:此角色将允许您将容器映像发布到 Google 容器注册表:

图 9.11 - 为您的服务帐户添加角色

](image/Figure_9.11_B17385.jpg)

图 9.11 - 为您的服务帐户添加角色

  • 将以下机密添加到您的 GitHub 存储库的机密中。您可以通过导航到设置选项卡,然后单击左侧导航栏上的机密来添加 GitHub 存储库机密。在那里,单击新存储库机密,然后添加以下机密:

a. PROJECT_ID:Google Cloud 项目 ID

b. SERVICE_ACCOUNT_KEY:服务帐户 JSON 文件的内容

请参阅以下屏幕截图:

图 9.12 - 为您的 GitHub 存储库添加密钥

](image/Figure_9.12_B17385.jpg)

图 9.12 - 为您的 GitHub 存储库添加密钥

有了这个,我们已经完成了所有的先决条件。在下一节中,我们将使用 GitHub Actions 和 Skaffold 创建 CI/CD 流水线。

使用 GitHub Actions 和 Skaffold 实现 CI/CD 工作流程

在本节中,我们将使用 Skaffold 和 GitHub Actions 创建一个生产就绪的 CI/CD 流水线。

以下图示演示了使用 Skaffold 和 GitHub Actions 的 CI/CD 工作流程:

图 9.13 – 使用 Skaffold 的 CI/CD 工作流程

图 9.13 – 使用 Skaffold 的 CI/CD 工作流程

我们将使用以下工作流程 YAML 文件。在这里,我已经在每个步骤的注释中解释了工作流程 YAML 文件:

  1. 指定工作流程的名称和事件:
name: Deploy to GKE
on:
  push:
    branches:
      - main
  1. 然后我们将 GitHub 的秘密作为环境变量传递:
env:
  PROJECT_ID: ${{ secrets.PROJECT_ID }}
  GKE_CLUSTER: autopilot-cluster-1
  GKE_ZONE: us-central1
  SKAFFOLD_DEFAULT_REPO: gcr.io/${{ secrets.PROJECT_ID
  }}/breathe
  1. 接下来,我们定义在 GitHub 托管的 Ubuntu Linux 运行器上运行的作业:
jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    env:
      ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
  1. 在定义步骤时,第一步是检出源代码,然后安装 Java 16:
    steps:
      - name: Check out repository on main branch
        uses: actions/checkout@v1
        with:
          ref: main
      - name: Install Java 16  
        uses: AdoptOpenJDK/install-jdk@v1
        with:
          version: '16'
          architecture: x64
  1. 然后我们设置gcloud CLI:
      - name: Install gcloud
        uses: google-github-actions/setup-
              gcloud@master
        with:
          version: "309.0.0"
          service_account_key: ${{
            secrets.SERVICE_ACCOUNT_KEY }}
          project_id: ${{ secrets.PROJECT_ID }}
          export_default_credentials: true
  1. 接下来,下载kubectl进行部署后验证和skaffold进行持续交付:
      - name: Install kubectl and skaffold
        uses: daisaru11/setup-cd-tools@v1
        with:
          kubectl: "1.19.2"
          skaffold: "1.29.0"
  1. 接下来,缓存诸如依赖项之类的工件,以提高工作流程执行时间:
      - name: Cache skaffold image builds & config
        uses: actions/cache@v2
        with:
          path: ~/.skaffold/
          key: fixed-${{ github.sha }}
  1. 配置 docker 以使用 gcloud 命令行工具作为身份验证的凭据助手:
      - name: Configure docker
        run: |
          gcloud --quiet auth configure-docker

获取 GKE 凭据并使用skaffold run命令部署到集群,如下所示:

      - name: Connect to cluster
        run: |
          gcloud container clusters get-credentials
            "$GKE_CLUSTER" --zone "$GKE_ZONE"
  1. 最后,使用skaffold run构建和部署到 GKE,并在部署后使用kubectl get all进行验证:
      - name: Build and then deploy to GKE cluster
              with Skaffold
        run: |
          skaffold run
      - name: Verify deployment
        run: |
          kubectl get all

您可以在项目中使用此工作流程 YAML 文件,并用您的值替换秘密。如果您已将skaffold.yaml文件放在存储库的根目录中,那就没问题,否则您可以在skaffold run命令中传递--filename标志,指向 Skaffold 配置文件。

如果工作流程成功执行,则应该看到以下输出:

图 9.14 – 使用 Skaffold 成功构建和部署到 GKE

图 9.14 – 使用 Skaffold 成功构建和部署到 GKE

在本节中,我们已经成功地使用 Skaffold 和 GitHub Actions 从 GitHub 存储库构建和部署了一个 Spring Boot 应用程序到远程集群,使用了定制的 CI/CD 流水线。

接下来,让我们看看在理解它们是什么的同时,如何使用 Argo CD 和 Skaffold 实现工作流程。

使用 Argo CD 和 Skaffold 实现 GitOps 工作流程

第四章中,了解 Skaffold 的功能和架构,在解释 Skaffold 功能时,我们简要讨论了如何使用skaffold renderskaffold apply命令使用 Skaffold 创建 GitOps 风格的持续交付工作流程。在本节中,我们将使用 Skaffold 和 Argo CD 实现 GitOps 工作流程。但首先,让我们了解 GitOps 及其好处。

什么是 GitOps,以及它的好处是什么?

GitOps一词是由一家名为 Weaveworks 的公司创造的。GitOps 背后的理念是将 Git 视为应用程序和声明性基础设施的单一真相来源。使用 Git 管理声明性基础设施使开发人员更容易,因为他们每天都与 Git 进行交互。一旦您在 Git 中添加配置,您就会获得版本控制的好处,例如使用拉取请求审查更改,审计和合规性。

通过 GitOps,我们创建自动化流水线,以在有人向 Git 存储库推送更改时向您的基础设施推出更改。然后,我们使用 GitOps 工具将您的应用程序的实际生产状态与您在源代码控制下定义的状态进行比较。然后,当您的集群与您在生产环境中拥有的不匹配时,它还会告诉您,并自动或手动将其与期望状态进行协调。这就是真正的持续交付。

您可以通过简单的git revert从 Kubernetes 中回滚更改。在灾难情景中,或者如果有人意外地摧毁了整个 Kubernetes 集群,我们可以快速地从 Git 中重新生成整个集群基础设施。

现在,让我们了解一下 GitOps 的一些好处:

  • 使用 GitOps,团队每天向生产环境发布 30-100 个更改。当然,您需要使用部署策略,如蓝绿部署和金丝雀发布,在向所有用户提供更改之前验证您的更改。总体好处是增加开发人员的生产力。

  • 通过 GitOps,开发人员推送代码而不是容器,从而获得更好的开发人员体验。此外,他们使用熟悉的工具,如 Git,并且不需要了解 Kubernetes 的内部(即kubectl命令)。

  • 通过将声明性基础设施作为代码放入 Git 存储库中,您自动获得诸如集群审计跟踪(例如谁在什么时候做了什么)的好处。它进一步确保了 Kubernetes 集群的合规性和稳定性。

  • 在灾难发生时,您也可以更快地恢复集群,从几个小时到几分钟,因为您的整个系统都在 Git 中描述。

  • 您的应用程序代码已经在 Git 上,并且通过 GitOps,您的操作任务是同一端到端工作流程的一部分。您在整个组织中都有一致的 Git 工作流程。

公平地说,我们也应该涵盖一些关于 Argo CD 的细节,这样在后面实现使用 Skaffold 和 Argo CD 的 GitOps 工作流程时会更容易理解。

什么是 Argo CD?

根据Argo CD的官方文档,argo-cd.readthedocs.io/en/stable/,它是一个声明性的、GitOps 的 Kubernetes 持续交付工具。在前一节中,我们使用了术语GitOps 工具,它可以比较和同步应用程序状态,如果与 Git 存储库中定义的不一致,那么可以说 Argo CD 是处理这种自动化的工具。Kubernetes 通过控制循环的概念介绍了我们,通过这个概念,Kubernetes 检查运行的副本数量是否与期望的副本数量匹配。Argo CD 利用了相同的KubernetesK8s)功能,它的核心组件是argocd-application-controller,基本上是一个 Kubernetes 控制器。它监视您的应用程序状态,并相应地调整集群。

现在是时候通过在 Google Kubernetes Engine 上使用 Skaffold 和 Argo CD 来实现 GitOps 了。让我们开始吧。

在 GKE 上使用 Argo CD 和 Skaffold 进行持续交付

在开始之前,我们需要确保已满足以下先决条件。

  • 我们首先需要安装kubectl

  • 当前的 Kubernetes 上下文设置为远程 GKE 集群。您可以使用kubectl config current-context命令验证当前上下文。

我们可以在本地 Kubernetes 集群上运行这个演示,但是理想情况下,您应该在像 GKE 这样的托管 Kubernetes 服务上运行它。让我们开始吧:

  1. 首先,我们将使用以下命令在 GKE 上安装 Argo CD:
kubectl create namespace argocd 
kubectl apply -n argocd -f 
https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

我们创建了一个单独的命名空间argocd,所有与 Argo CD 相关的组件都将成为其中的一部分。我们可以通过转到 GKE 下的工作负载部分来验证安装。

在下面的截图中,您可以看到 Argo CD 的有状态集组件,即argocd-application-controller,以及部署组件,如argocd-server正在在 GKE 上运行:

图 9.15 - Argo CD Kubernetes 资源部署到 GKE

图 9.15 - Argo CD Kubernetes 资源部署到 GKE

接下来,我们可以安装 Argo CD CLI。这是一个可选步骤,因为我们将使用 Argo CD UI。

  1. 接下来,我们需要公开 Argo CD API 服务器,因为默认情况下它不会对外部访问公开。我们可以运行以下命令将服务类型更改为LoadBalancer
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'

在下面的屏幕截图中,您可以看到服务类型已更改为“外部负载均衡器”,我们将使用该 IP 地址访问 Argo CD GUI:

图 9.16 - Argo CD API 服务器公开为 LoadBalancer

图 9.16 - Argo CD API 服务器公开为 LoadBalancer

您甚至可以使用 ingress 或kubectl端口转发来访问 Argo CD API 服务器,而无需暴露服务。

  1. 我们现在可以使用默认的管理员用户名访问 Argo CD GUI,并使用以下命令获取密码:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

您应该看到以下登录界面:

图 9.17 - Argo CD 登录界面

图 9.17 - Argo CD 登录界面

登录后,点击+新应用按钮,如下面的屏幕截图所示:

图 9.18 - 创建应用程序

图 9.18 - 创建应用程序

在下一个屏幕上,输入您的应用程序名称,选择默认项目,并将同步策略设置为自动

图 9.19 - Argo CD 应用程序入职

图 9.19 - Argo CD 应用程序入职

  1. 输入源 Git 存储库 URL。提供 Git 存储库中 Kubernetes 清单的路径。 Argo CD 每 3 分钟轮询一次您的 Git 存储库,以将更新的清单应用到您的 Kubernetes 集群。您可以通过设置 webhook 事件来避免此延迟:

图 9.20 - 向 Argo CD 提供应用程序 Git 存储库详细信息

图 9.20 - 向 Argo CD 提供应用程序 Git 存储库详细信息

将目的地设置为集群内部,并将命名空间设置为默认值,如下面的屏幕截图所示:

图 9.21 - 向 Argo CD 提供目的地集群详细信息

图 9.21 - 向 Argo CD 提供目的地集群详细信息

填写所需信息后,点击 UI 顶部的CREATE来创建应用程序。点击CREATE按钮后,将从 Git 存储库的路径Chapter09/gitops中检索到的 Kubernetes 清单,并且 Argo CD 对这些清单执行kubectl apply

图 9.22 - 创建应用程序

创建应用程序后,您应该看到以下屏幕。状态进行中

图 9.23 - 应用程序已创建并同步

图 9.23 - 应用程序已创建并同步

点击应用程序,您将看到以下屏幕:

图 9.24 - 应用程序部署并处于健康状态

图 9.24 - 应用程序部署并处于健康状态

您可以在此处看到部署、svc和 pod 的列表。应用程序同步状态已同步应用健康健康。Argo CD 对不同的 Kubernetes 资源类型(如 Deployment 和 ReplicaSets)内置了健康检查。

我们已经为我们的应用程序设置了持续交付工作流程,并且应用程序已成功同步。现在我们将尝试通过以下步骤进行一些本地更改来测试工作流程:

  1. 使用skaffold config set default-repo gcr.io/project-id命令将默认容器注册表设置为 GCR。

  2. 我们将使用skaffold build命令构建、标记和推送容器映像。

  3. 然后我们将运行skaffold render命令。该命令将生成水合物(即,带有新生成的图像标签)的 Kubernetes 清单文件,我们将稍后提交并推送到 Git 存储库。使用 Argo CD 的 GitOps 流水线将挑选并同步这些更改到目标 Kubernetes 集群。

让我们从这个过程开始。

我们将进行一些代码的外观更改,将副本数从 1 增加到 2,并运行skaffold render命令。根据skaffold.yaml文件,Kubernetes 清单在 k8s 目录中定义。在运行skaffold render命令时,我们还将传递--output=gtipos/manifest.yaml标志,以便稍后将其推送到 Git 存储库。以下是输出:

skaffold build && skaffold render --output=gtipos/manifest.yaml 
Generating tags...
- breathe -> gcr.io/basic-curve-316617/breathe:99b8380-dirty
Checking cache...
- breathe: Not found. Building
Starting build...
Building [breathe]...

我只想强调一下,skaffold render不会生成新的,而是将使用现有的 Kubernetes 清单并使用新标签更新图像。

最后,我们使用以下命令提交更改并将其推送到 GitHub 存储库:

git commit -m  "changing number of replicas" && git push

推送后不久,Argo CD 将同步更改到 GKE 集群,如下截图所示:

图 9.25 – 增加副本数

图 9.25 – 增加副本数

在截图中,您可以看到现在我们有两个正在运行的 pod,因为我们增加了副本数。

以下截图说明了使用 Skaffold 和 Argo CD 的典型 GitOps 工作流程。到目前为止,我们已经基本上涵盖了相同的步骤。让我们尝试总结到目前为止我们学到了什么。

图 9.26 – 使用 Skaffold 和 Argo CD 的 GitOps 工作流程

我们可以从截图中得出以下结论:

  • 开发人员提交并推送代码更改到 Git 存储库。

  • 连续集成流水线启动,并使用 skaffold 构建,我们将构建、标记并推送图像到容器注册表。

  • 我们还将使用 skaffold render 生成水合物清单,并将其提交到相同或不同的存储库。

  • 使用 CI webhook 触发任一同步操作,或者在 Kubernetes 集群内运行的 Argo CD 控制器定期轮询间隔后拉取更改。

  • 此外,Argo CD 控制器将比较实时状态与期望的目标状态(根据配置存储库上的 git 提交)。

  • 如果 Argo CD 检测到应用程序处于 OutOfSync 状态,它将应用最新更改到 Kubernetes 集群。

在本节中,我们学习了如何结合两个强大工具 Skaffold 和 Argo CD 创建 GitOps 流水线。我们本可以使用skaffold apply命令代替 Argo CD,但skaffold apply命令总是使用kubetcl将资源部署到目标集群。如果您的应用程序打包为 Helm 图表,则它将无法工作。此外,使用 Argo CD,您可以结合 Argo Rollouts 进行蓝绿部署和金丝雀部署,因为它们在 Skaffold 中原生不受支持。

摘要

在本章中,您已经学会了如何使用 GitHub Actions 自动化您的开发工作流程。我们通过解释 GitHub Actions 及其组件来开始本章。我们通过示例解释了 GitHub Actions 和相关概念。在示例中,我们解释了如何从 GitHub 存储库构建、测试和部署 Java 应用程序。然后我们描述了如何使用 Skaffold 和 GitHub Actions 为您的 Kubernetes 应用程序创建 CI/CD 流水线。

您已经了解了如何利用 GitHub Actions 并将其与 Skaffold 结合,创建 CI/CD 流水线。然后在最后一节中,我们深入探讨了如何使用 Skaffold 和 Argo CD 建立 GitOps 风格的持续交付工作流。我们已经了解到,在 GitOps 中,我们将 Git 存储库视为与基础架构相关的任何更改的唯一真相来源。我们还演示了如何使用 Argo CD 和 Skaffold 实现 GitOps 流水线。

在下一章中,我们将讨论 Skaffold 的替代方案,还将涵盖其最佳实践和常见陷阱。

进一步阅读

第十章:探索 Skaffold 替代方案、最佳实践和陷阱

在上一章中,您了解了如何使用 GitHub Actions 和 Skaffold 为 Spring Boot 应用程序创建 CI/CD 流水线。在本章中,我们将首先看看市场上提供类似功能的其他工具。我们将了解开发人员在使用 Skaffold 开发云原生 Kubernetes 应用程序时可以遵循的技巧和诀窍。最后,我们将了解开发人员通常可以避免的 Skaffold 陷阱。

在本章中,我们将涵盖以下主要主题:

  • 与其他替代方案比较 Skaffold

  • 应用 Skaffold 最佳实践

  • 避免 Skaffold 陷阱

  • 未来路线图

  • 最终思考

通过本章结束时,您将对除 Skaffold 之外的工具有扎实的了解,以改善 Kubernetes 的开发者体验。您还将了解可以用于开发工作流程的 Skaffold 最佳实践以及在开发生命周期中可以避免的一些常见陷阱。

与其他替代方案比较 Skaffold

在本节中,我们将比较 Skaffold 与其他解决类似问题的替代工具,即改善 Kubernetes 开发者体验的工具。然而,可能存在 Skaffold 不适用的情况,因此在下一节中,我们将研究除 Skaffold 之外的工具,以解决复杂的使用情况。我们还将比较这些 Kubernetes 开发工具与 Skaffold 提供的功能。让我们开始吧!

Telepresence

Telepresence (www.telepresence.io/) 是由 Ambassador Labs 开发的工具。它是一个 Cloud Native Computing Foundation 的沙盒项目。其目标是改善 Kubernetes 的开发者体验。Telepresence 允许您在本地运行单个服务,同时将该服务连接到远程 Kubernetes 集群。您可以在这里阅读更多信息:www.telepresence.io/about/

使用 Telepresence,您可以作为集群的一部分在本地开发和调试服务。您不必不断在 Kubernetes 集群中发布和部署新的构件。与 Skaffold 相比,Telepresence 不需要本地 Kubernetes 集群。它在远程集群中运行一个 pod 作为您的应用程序的占位符,并将传入的流量路由到在本地工作站上运行的容器。当开发人员更改应用程序代码时,它将在远程集群中反映出来,而无需部署新的容器。

另一个优点是,您只需要计算资源,即 CPU 和内存,来在本地运行您的服务,因为您直接与远程集群进行交互。此外,您不必设置和运行本地 Kubernetes 集群,比如 minikube 或 Docker Desktop。在您运行五到六个微服务并且您的应用程序必须与它们交互的情况下,这是有帮助的。相比之下,Skaffold 更像是一个打包成一个解决方案,满足您的本地开发需求和 CI/CD 工作流。但假设您的应用程序需要与许多微服务进行交互。在这种情况下,情况就会变得棘手,因为由于资源限制,难以在本地运行所有实例,并且您可能最终会模拟一些可能无法复制实际生产行为的服务。这就是 Telepresence 可以帮助您的地方,它具有从您的笔记本电脑进行远程开发并且资源使用最小化的能力。

它也有一些缺点,比如在 Windows 操作系统上有一些已知的卷挂载问题,并且需要高速网络连接。

Tilt.dev

Tilt (tilt.dev/) 是一个用于改善 Kubernetes 开发体验的开源工具。

在 Skaffold 中,我们使用skaffold.yaml配置文件来定义、构建和部署;同样,在 Tilt 中,我们使用 Tiltfile 进行配置。Tiltfiles 是用一种称为Starlark的 Python 方言编写的。在这里查看 API 参考:docs.tilt.dev/api.html。以下是一个 Tiltfile 示例:

docker_build('example-html-image', '.')
k8s_yaml('kubernetes.yaml')
k8s_resource('example-html', port_forwards=8000)

接下来,您可以运行tilt run命令。与 Skaffold 相比,Tilt 不仅是一个 CLI 工具。Tilt 还提供了一个整洁的 UI,您可以在其中查看每个服务的健康状态、它们的构建和运行时日志。虽然 Skaffold 是一个没有供应商支持的开源项目,但 Tilt 的企业版提供了供应商支持。

Tilt 的缺点是你必须熟悉其 Starlark Python 语法语言,这是编写 Tiltfile 所必需的,而 Skaffold 使用 skaffold.yaml 配置文件作为 YAML 语法文件。但如果你使用 Kubernetes 清单,那么这并不是什么新鲜事,大多数开发人员已经熟悉了。

OpenShift Do

使用 Kubernetes 或 Platform-as-a-Service(PaaS)提供的 OpenShift 等开发应用程序是很困难的,如果你在开发过程中没有使用正确的工具。在 OpenShift 的情况下,它已经有一个 CLI 工具 oc,但不幸的是,它更多地专注于帮助运维人员,对开发人员不太友好。oc CLI 要求你了解与 OpenShift 相关的概念,比如部署和构建配置等,作为开发人员,你可能对这些并不感兴趣。Red Hat 团队意识到了这个问题,并开发了一个新的 CLI 工具叫做 OpenShift Do(odo),它更加针对开发人员。它还有助于改善开发云原生应用程序部署到 Kubernetes 或 OpenShift 时的开发体验。

让我们来看一下它的一些特点,如下所示:

  • 更快地开发并加速内部开发循环。

  • 使用 odo watch 命令进行实时反馈。如果你曾经使用过 Skaffold,那么它与 skaffold dev 命令非常相似。odo CLI 使用开发人员关注的概念,如项目、应用程序和组件。

  • 它是一个完全基于 CLI 客户端的工具。

OpenShift odo 非常特定于 OpenShift 本身;即使文档说它可以与原始的 Kubernetes 发行版一起使用。缺乏文档和实际示例来使用 odo 与 minikube 或其他工具。

Oketo

Oketo(https://okteto.com/)是一个 CLI 工具,其方法与 Skaffold 完全不同。Oketo 不是在本地工作站自动化你的内部开发循环,而是将内部开发循环移到集群本身。你可以在一个 YAML 清单文件 oketo.yaml 中定义你的开发环境,然后使用 oketo init 和 oketo up 来启动你的开发环境。

以下是 Oketo 一些突出的特点:

  • 本地和远程 Kubernetes 集群之间的文件同步。

  • 一个二进制文件可以在不同的操作系统上运行。

  • 您可以在容器开发环境中获得远程终端。

  • 热重载您的代码。

  • 与本地和远程 Kubernetes 集群、Helm 和无服务器函数一起使用。

  • 双向端口转发。

  • 在开发过程中,不需要构建、推送或部署,因为您直接在集群上开发。

  • 无需在工作站上安装 Docker 或 Kubernetes。

  • 甚至运行时(JRE、npm、Python)也不需要,因为一切都在 Docker 镜像中。

Garden

Garden (garden.io/)遵循与 Oketo 相同的哲学,即部署到远程集群而不是在本地系统上进行设置。Garden 是一个开源工具,用于在远程集群中运行 Kubernetes 应用程序,用于开发、自动化测试、手动测试和审查。

使用 Garden,您可以通过诸如garden create project的 CLI 辅助命令开始。您将通过一个 YAML 配置文件管理 Garden 配置。

以下是 Garden YAML 配置文件的关键元素:

  • 模块:在模块中,您指定如何构建您的容器。

  • 服务:在服务中,您指定如何在 Kubernetes 上运行您的容器。

  • 测试:在测试中,您指定单元测试和集成测试。

以下是 Garden 提供的一些功能:

  • 当源代码更改时,它会自动将应用程序重新部署到远程集群。

  • 它支持多模块和多服务操作(依赖关系树)。

  • 它为依赖项提供了一个图形化的仪表板。

  • 运行任务的能力(例如,在构建流程中作为数据库迁移的一部分)。

  • 它支持 Helm 和 OpenFass 部署。

  • 它支持热重载功能,其中源代码直接发送到正在运行的容器。

  • 将容器日志流式传输到您的终端的能力。

  • 它支持文件监视和远程集群代码的热重载。

Garden 的设置比 Skaffold 更复杂,需要一段时间才能熟悉其概念,因此涉及陡峭的学习曲线。使用 Skaffold,您可以使用熟悉的构建和部署工具,并且很容易开始使用。对于小型应用程序来说,使用 Garden 可能也是过度复杂的。Garden 是商业开源的,因此与完全开源的 Skaffold 相比,它的一些功能是付费的。

docker-compose

docker-compose 是一个主要用于本地容器开发的工具。它允许您在本地运行多个容器,并模拟应用程序在部署到 Kubernetes 时的外观。要开始使用它,需要在工作站上安装 Docker。虽然 docker-compose 可能会让一些开发人员错误地认为他们的应用程序在 Kubernetes 环境(如 minikube)中运行,但实际上,它与在 Kubernetes 集群中运行完全不同。这也意味着,因为您的应用程序在 docker-compose 上运行正常,当部署到生产环境的 Kubernetes 集群时,它将无法正常工作或表现类似。虽然我们知道容器解决了“在我的机器上运行”的问题,但是使用 docker-compose,我们引入了一个新问题,即“在我的 docker-compose 设置上运行”。

也许会诱人将 docker-compose 用作云应用程序内部开发循环的替代品,但正如前面所解释的,您的本地和生产环境将不相同。由于这种差异,很难调试任何环境,而使用 Skaffold,您可以在本地和远程构建和部署时使用完全相同的堆栈。如果由于某种原因您被困在 docker-compose 设置中,甚至可以将其与 Skaffold 配对使用。Skaffold 内部使用 Kompose (kompose.io/) 将 docker-compose.yaml 转换为 Kubernetes 清单。

您可以使用以下命令将现有的 docker-compose.yaml 文件与 Skaffold 一起使用:

skaffold init --compose-file docker-compose.yml 

在本节中,我们已经了解了除 Skaffold 之外的 Kubernetes 开发工具,帮助开发人员在内部开发循环中更快地开发并获得快速反馈。

在接下来的部分中,我们将学习如何将一些最佳实践应用到我们现有或新的 Skaffold 工作流中。

应用 Skaffold 最佳实践

在本节中,我们将了解 Skaffold 的最佳实践,作为开发人员,您可以利用这些最佳实践,加快内部或外部开发循环中的部署速度,或者在使用 Skaffold 时使用一些标志来简化事情。让我们开始:

  • 在部署到 Kubernetes 的多个微服务应用程序中工作时,有时很难为每个应用程序创建单个的skaffold.yaml配置文件。在这些常见情况下,您可以为每个应用程序创建范围为skaffold.yaml,然后独立运行skaffold devrun命令。您甚至可以在单个 Skaffold 会话中同时迭代这两个应用程序。假设我们有一个前端应用和一个后端应用,对于它们两个,您的单个skaffold.yaml文件应如下所示:
apiVersion: skaffold/v2beta18
kind: Config
requires:
- path: ./front-end-app
- path: ./backend-app

当您使用 Skaffold 引导项目并且没有 Kubernetes 清单时,您可以使用skaffold init命令传递--generate-manifests标志来为项目生成基本的 Kubernetes 清单。

  • 最好始终与 Skaffold 一起使用default-repo功能。如果您使用default-repo,则无需手动编辑 YAML 文件,因为 Skaffold 可以使用您指定的容器镜像注册表前缀图像名称。因此,您可以在skaffold.yaml配置文件中输入图像名称,而不是编写gcr.io/myproject/imagename。另一个优点是,您可以轻松地与其他团队共享您的skaffold.yaml文件,因为如果他们使用不同的容器镜像注册表,则无需手动编辑 YAML 文件。因此,基本上,您可以使用default-repo功能,而无需在skaffold.yaml配置文件中硬编码容器镜像注册表名称。

您可以以以下三种方式利用default-repo功能:

a. 通过传递--default-repo标志:

skaffold dev --default-repo gcr.io/myproject/imagename 

b. 通过传递SKAFFOLD_DEFAULT_REPO环境变量:

SKAFFOLD_DEFAULT_REPO= gcr.io/myproject/imagename skaffold dev

c. 通过设置 Skaffold 的全局配置:

skaffold config set default-repo gcr.io/myproject
/imagename
  • 当您遇到 Skaffold 命令的问题时,了解实际问题变得棘手。在某些情况下,您可能需要比 Skaffold 通常显示的流式日志更多的信息。对于这种情况,您可以使用-v-verbosity标志使用特定的日志级别。例如,您可以使用skaffold dev -v info来查看信息级别的日志。

Skaffold 支持以下日志级别,默认为warn

  • info

  • warn

  • error

  • fatal

  • debug

  • trace

  • 为了更快地构建,您可以通过将并发标志设置为0来利用并发标志。默认值为0,表示没有限制的并行构建,因此所有构建都是并行进行的。只有在本地构建并发性方面,该值才会默认为1,这意味着构建将按顺序进行,以避免任何副作用。

  • 如果您正在使用 Jib 来构建和推送容器镜像,那么您可以使用自动配置来使用特殊的同步支持。您可以通过使用sync:选项来启用它,如下面的skaffold.yaml文件中所述:

apiVersion: skaffold/v2beta16
kind: Config
build: 
  artifacts: 
    - 
      image: file-sync
      jib: {}
      sync: 
        auto: true
deploy: 
  kubectl: 
    manifests: 
      - k8s-*

使用此选项,Jib 可以将您的类文件、资源文件和 Jib 的额外目录文件同步到本地或远程运行的容器中。您不必为内部开发循环中的每次更改重新构建、重新部署或重新启动 pod。但是,要使其与您的 Spring Boot 应用程序配合使用,您需要在 Maven 项目的pom.xml文件中具有spring-boot-devtools依赖项。

  • Skaffold 还支持 Cloud Native Buildpacks 来构建您的容器镜像,并且与 Jib 类似,它还支持sync选项,以在对某种类型的文件进行更改时自动重新构建和重新启动应用程序。它支持以下类型的源文件:

  • Go: *.go

  • Java: *.java, *.kt, *.scala, *.groovy, *.clj

  • Node.js: *.js, *.mjs, *.coffee, *.litcoffee, *.json

在本节中,我们已经学习了一些最佳实践,我们可以应用这些最佳实践来高效开发,并且甚至可以更快地加速 Skaffold 的开发循环。

在下一节中,我们将看一些我们作为开发人员应该注意的常见 Skaffold 陷阱。

避免 Skaffold 陷阱

在本书中,我们已经使用了 Skaffold 提供的各种功能。现在让我们讨论一些常见的 Skaffold 陷阱,作为开发人员,我们应该了解并尽量避免:

  • Skaffold 要求您具有一些本地或远程 Kubernetes 设置,因此与我们在上一节中讨论的其他工具相比,Skaffold 并不会减少设置开发环境所需的时间。

  • 在大多数情况下,使用 Skaffold 时,您会使用一些本地 Kubernetes,如 minikube 或 Docker Desktop,并且由于它们的限制,您无法复制整个类似生产环境的设置。这留下了集成问题的空间,这些问题在本地系统上可能看不到,但可能会在更高的环境中出现。

  • 有时,使用 Skaffold 会在您的机器上浪费更多的硬件资源。例如,如果您需要运行,比如说,10 个微服务,那么这将变得具有挑战性,因为您的笔记本电脑资源有限。

  • Skaffold 内置支持通过(beta)skaffold debug命令与调试器集成。使用这个调试选项,Skaffold 会自动配置应用程序运行时进行远程调试。这是一个很棒的功能,但在微服务环境中使用调试器最好是棘手的。在远程集群中使用会更加困难。要谨慎使用。

  • Skaffold 没有 Web UI。虽然在上一节中我们讨论了许多提供 UI 以获得更好体验的工具,但我不会为此而哭泣。这更多是个人偏好,因为有些人倾向于喜欢 UI,而有些人则喜欢 CLI。如果你更喜欢 UI,那么你可能无法与 Skaffold 相处。

  • Skaffold 非常适合在本地开发和测试中使用。尽管它被宣传为一些用例的完整 CI/CD 解决方案,但它可能不是最适合的工具。例如,如果我们想要扩展到生产或预生产用例,最好使用专门的工具,比如Spinnaker pipelines (spinnaker.io/) 或 Argo Rollouts (argoproj.github.io/argo-rollouts/)。这些工具 Spinnaker/Agro Rollouts 相对于 Skaffold 提供了一些优势。让我们来看看它们:

  1. 在 Spinnaker/Agro Rollouts 的情况下,两者都支持复杂的部署策略。您可以定义诸如金丝雀部署和蓝绿部署之类的部署策略。

  2. Spinnaker 允许多集群部署。此外,您可以配置基于易用 UI 的部署到多个集群。

  3. Spinnaker 具有出色的可视化效果。它提供了一个丰富的 UI,显示跨集群、区域、命名空间和云提供商的任何部署或 Pod 状态。

在这一部分,我们已经涵盖了 Skaffold 的一些陷阱,在决定是否继续在 Kubernetes 工作负载中使用之前,你应该注意这些。

未来路线图

由于 Skaffold 是一个开源工具,社区主要推动了 Skaffold 的路线图,而来自 Google 的工程师团队做出最终决定。Google 开发人员还提出了一些令人兴奋的新功能,这些功能将增强 Skaffold 的用户体验,除了社区提出的变更。

然而,路线图不应被视为无论如何都会实现的承诺清单。这是 Skaffold 工程团队认为值得投入时间的愿望清单。路线图背后的主要动机是从社区中获取他们希望在 Skaffold 中看到的功能的反馈。

您可以通过访问github.com/GoogleContainerTools/skaffold/blob/master/ROADMAP.md#2021-roadmap URL 来查看 2021 年的 Skaffold 路线图。

最后的思考

近年来,围绕 Kubernetes 开发工具的工具化显著改善。这背后的主要动机是 Kubernetes 在行业中的日益普及。现代开发人员希望有一个能够提高他们在为云开发应用程序时的生产力的工具。Skaffold 极大地提高了开发人员构建和部署 Kubernetes 应用程序的生产力。

许多工具内部使用 Skaffold,例如 Jenkins X 和 Cloud Code,以改善 Kubernetes 的整体开发体验。与 Jenkins X 不同,后者在流水线中使用 Skaffold 构建和推送镜像,Cloud Code 完全围绕 Skaffold 及其支持的工具构建,以为 Kubernetes 应用程序提供无缝的入门体验。

最后,我想总结一下,Skaffold 简化了 Kubernetes 开发,在我看来,它做得很好。它提供了灵活性和可扩展性,可以选择与之一起使用的集成类型。其可扩展和可插拔的架构允许开发人员为构建和部署应用程序的每个步骤选择适当的工具。

总结

在本章中,我们首先将 Skaffold 与其他工具(如 Tilt、Telepresence、Garden、Oketo、docker-compose和 OpenShift odo)进行了比较。这些工具原则上试图提供与 Skaffold 解决的类似问题的解决方案。然后,我们比较了这些工具相对于 Skaffold 提供的功能。我们还研究了一些可以与 Skaffold 一起使用以获得更好开发体验的最佳实践。最后,我们通过解释与 Skaffold 相关的一些潜在问题来总结,如果您的用例更高级,您应该注意这些问题。

你已经发现了如何通过遵循我们试图解释的一些最佳实践来利用 Skaffold。现在你更有能力决定 Skaffold 是否满足你的使用情况,或者你是否需要考虑本章中涵盖的其他选项。

通过这一点,我们已经到达了旅程的尽头,我希望你会被鼓励尝试并探索大量的 Skaffold!

posted @ 2024-05-20 12:03  绝不原创的飞龙  阅读(77)  评论(0编辑  收藏  举报