1. 概览
本实验演示了一些特性和功能,这些特性和功能旨在简化在容器化环境中开发 Java 应用的软件工程师的开发工作流。典型的容器开发要求用户了解容器和容器构建流程的详细信息。此外,开发者通常需要中断他们的流程,离开 IDE,以在远程环境中测试和调试其应用。借助本教程中提到的工具和技术,开发者无需离开 IDE 即可高效使用容器化应用。
学习内容
在本实验中,您将学习在 GCP 中使用容器进行开发的方法,包括:
- 设置和要求
- 创建新的 Java 起始应用
- 开发过程介绍
- 开发简单的 CRUD 静态服务
- 清理
2. 设置和要求
自定进度的环境设置
- 登录 Google Cloud 控制台,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个。
- 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串,您可以随时对其进行更新。
- 项目 ID 在所有 Google Cloud 项目中必须是唯一的,并且不可变(一经设置便无法更改)。Cloud Console 会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(它通常标识为
PROJECT_ID
),因此如果您不喜欢某个 ID,请再生成一个随机 ID,还可以尝试自己创建一个,并确认是否可用。然后,项目创建后,ID 会处于“冻结”状态。 - 第三个值是一些 API 使用的项目编号。如需详细了解所有这三个值,请参阅文档。
- 接下来,您需要在 Cloud Console 中启用结算功能,才能使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有费用的话)。要关闭资源以避免产生超出本教程范围的费用,请按照此 Codelab 末尾提供的任何“清理”说明操作。Google Cloud 的新用户符合参与 $300 USD 免费试用计划的条件。
启动 Cloudshell 编辑器
本实验旨在与 Google Cloud Shell Editor 搭配使用,并经过测试。要访问该编辑器,请按以下步骤操作:
- 通过 https://console.cloud.google.com 访问您的 Google 项目。
- 点击右上角的 Cloud Shell 编辑器图标
- 窗口底部会打开一个新窗格
- 点击“打开编辑器”按钮
- 编辑器将打开,右侧为探索器,中心区域为编辑器
- 屏幕底部还应提供一个终端窗格
- 如果终端未打开,请使用 `ctrl+` 的组合键打开新的终端窗口
设置 gcloud
在 Cloud Shell 中,设置项目 ID 以及要将应用部署到的区域。将它们保存为 PROJECT_ID
和 REGION
变量。
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
获取源代码
本实验的源代码位于 GitHub 上 GoogleCloudPlatform 的 container-developer-workshop 中。使用以下命令克隆该文件,然后切换到该目录。
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot
预配本实验中使用的基础架构
在本实验中,您会将代码部署到 GKE,并访问存储在 CloudSql 数据库中的数据。下面的设置脚本会为您准备此基础架构。预配过程将需要 10 分钟以上。在设置处理期间,您可以继续执行接下来的几个步骤。
./setup.sh
3. 创建新的 Java 起始应用
在本部分中,您将使用 Spring.io 提供的示例应用从头开始创建一个新的 Java Spring Boot 应用。
克隆示例应用
- 创建起始应用
curl https://start.spring.io/starter.zip -d dependencies=web -d type=maven-project -d javaVersion=11 -d packageName=com.example.springboot -o sample-app.zip
- 解压缩应用
unzip sample-app.zip -d sample-app
- 切换到 sample-app 目录,然后在 Cloud Shell IDE 工作区中打开该文件夹
cd sample-app && cloudshell workspace .
添加 Spring-boot-devtools 和吉布
如需启用 Spring Boot 开发者工具,请在编辑器中从资源管理器中找到并打开 pom.xml。接下来,将以下代码粘贴到显示 <description>Spring Boot Demo 项目</description>的说明行之后。
- 在 pom.xml 中添加 Spring-boot-devtools
打开项目根目录中的 pom.xml。在 Description
条目后添加以下配置。
pom.xml
<!-- Spring profiles-->
<profiles>
<profile>
<id>sync</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
- 在 pom.xml 中启用 jib-maven-plugin
Jib 是 Google 推出的一款开源 Java 容器化工具,可让 Java 开发者使用他们熟悉的 Java 工具构建容器。Jib 是一款快速简单的容器映像构建器,可处理将应用打包到容器映像中的所有步骤。它不需要您编写 Dockerfile 或安装 docker,它直接集成到 Maven 和 Gradle 中。
在 pom.xml 文件中向下滚动,然后更新 Build
部分以包含 Jib 插件。构建部分在完成后应与以下内容一致。
pom.xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- Jib Plugin-->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<!-- Maven Resources Plugin-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
如果系统提示更改 build 文件,请选择 Always
。
生成清单
Skaffold 提供集成工具以简化容器开发。在此步骤中,您将初始化 skaffold,它会自动创建基础 Kubernetes YAML 文件。该过程会尝试识别包含容器映像定义的目录(例如 Dockerfile),然后为每个目录创建部署和服务清单。
执行以下命令以开始该过程。
- 在终端中执行以下命令
skaffold init --generate-manifests
- 当系统提示时:
- 使用箭头将光标移至
Jib Maven Plugin
- 按空格键即可选择相应选项。
- 按 Enter 键即可继续
- 输入 8080 作为端口
- 输入 y 以保存配置
向工作区可视化图表添加了 skaffold.yaml
和 deployment.yaml
两个文件
更新应用名称
配置中包含的默认值目前与应用的名称不匹配。更新文件以引用您的应用名称,而不是默认值。
- 更改 Skaffold 配置中的条目
- 打开
skaffold.yaml
- 选择当前设为“
pom-xml-image
”的映像名称 - 右键点击并选择“更改所有出现次数”
- 输入新名称
demo-app
- 更改 Kubernetes 配置中的条目
- 打开
deployment.yaml
文件 - 选择当前设为“
pom-xml-image
”的映像名称 - 右键点击并选择“更改所有出现次数”
- 输入新名称
demo-app
启用热同步
为了优化热重载体验,您需要使用 Jib 提供的同步功能。在此步骤中,您将配置 Skaffold 以在构建流程中使用该功能。
请注意,“同步”命令您在 Skaffold 配置中配置的配置文件利用了 Spring“sync”您在上一步中配置的配置文件,其中启用了对 spring-dev-tools 的支持。
- 更新 Skaffold 配置
在 skaffold.yaml 文件中,将该文件的整个构建部分替换为以下规范。请勿更改文件的其他部分。
skaffold.yaml
build:
artifacts:
- image: demo-app
jib:
project: com.example:demo
type: maven
args:
- --no-transfer-progress
- -Psync
fromImage: gcr.io/distroless/java:debug
sync:
auto: true
添加默认路由
在 /src/main/java/com/example/springboot/ 处创建一个名为 HelloController.java 的文件
将以下内容粘贴到此文件中,以创建默认的 http 路由
HelloController.java
package com.example.springboot;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;
@RestController
public class HelloController {
@Value("${target:local}")
String target;
@GetMapping("/")
public String hello()
{
return String.format("Hello from your %s environment!", target);
}
}
4. 开发过程介绍
在本部分中,您将逐步完成使用 Cloud Code 插件的几个步骤,了解基本流程并验证起始应用的配置和设置。
Cloud Code 与 Skaffold 集成,可简化您的开发流程。当您按以下步骤部署到 GKE 时,Cloud Code 和 Skaffold 会自动构建容器映像,将其推送到 Container Registry,然后将应用部署到 GKE。这是在后台将细节从开发者流程中提取出来的。Cloud Code 还可以为基于容器的开发提供传统的调试和热同步功能,从而增强您的开发流程。
将容器部署到 Kubernetes
- 在 Cloud Shell Editor 底部的窗格中,选择 Cloud Code 
- 在顶部显示的面板中,选择“在 Kubernetes 上调试”。如果出现提示,请选择“Yes”以使用当前的 Kubernetes 上下文。
- 首次运行此命令时,屏幕顶部会显示一条提示,询问您是否需要当前的 Kubernetes 上下文,请选择“是”接受并使用当前上下文。
- 接下来,系统会显示一条提示,询问要使用哪个容器注册表。按 Enter 键可接受提供的默认值
- 选择下部窗格中的“Output”(输出)标签页可查看进度和通知
- 选择“Kubernetes:运行/调试 - 详细”查看右侧渠道下拉菜单中的 其他详细信息和实时从容器流式传输的日志
- 选择“Kubernetes: Run/Debug”返回简化的视图下拉菜单
- 构建和测试完成后,“Output”(输出)标签页会显示
Resource deployment/demo-app status completed successfully
,并列出了一个网址:“Forwarded 网址 from service demo-app: http://localhost:8080” - 在 Cloud Code 终端中,将鼠标悬停在输出中的网址 (http://localhost:8080) 上,然后在显示的工具提示中选择“打开网页预览”。
响应将是:
Hello from your local environment!
利用断点
- 打开位于 /src/main/java/com/example/springboot/HelloController.java 的 HelloController.java 应用
- 找到显示
return String.format("Hello from your %s environment!", target);
的根路径的 return 语句 - 点击行号左侧的空白处,为该行添加断点。系统会显示一个红色指示器,指明断点已设置
- 重新加载浏览器,并注意调试程序会在断点处停止进程,并允许您调查在 GKE 中远程运行的应用的可变沙状态
- 点击“变量”部分,直到找到“目标”变量。
- 观察到当前值为“local”
- 双击变量名称“target”在弹出式窗口中,将值更改为其他值,例如“Cloud”
- 点击调试控制台中的“继续”按钮
- 在浏览器中查看响应,浏览器现在会显示您刚刚输入的更新值。
热重载
- 更改语句以返回不同的值,例如“Hello from %s Code”
- 文件会自动保存并同步到 GKE 中的远程容器中
- 刷新浏览器以查看更新后的结果。
- 点击调试工具栏 中的红色方块,停止调试会话
5. 开发简单的 CRUD 静态服务
至此,您的应用已完全针对容器化开发进行了配置,并且您已完成 Cloud Code 的基本开发工作流。在以下部分中,您将通过添加连接到 Google Cloud 中的代管式数据库的 REST 服务端点,练习所学知识。
配置依赖项
应用代码使用数据库来保留其余服务数据。通过在 pom.xl 中添加以下代码,确保依赖项可用。
- 打开
pom.xml
文件,并将以下代码添加到配置的依赖项部分
pom.xml
<!-- Database dependencies-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
对其余服务进行编码
Quote.java
在 /src/main/java/com/example/springboot/ 中创建一个名为 Quote.java 的文件,并复制以下代码。这定义了应用中使用的 Quote 对象的 Entity 模型。
package com.example.springboot;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Objects;
@Entity
@Table(name = "quotes")
public class Quote
{
@Id
@Column(name = "id")
private Integer id;
@Column(name="quote")
private String quote;
@Column(name="author")
private String author;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getQuote() {
return quote;
}
public void setQuote(String quote) {
this.quote = quote;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Quote quote1 = (Quote) o;
return Objects.equals(id, quote1.id) &&
Objects.equals(quote, quote1.quote) &&
Objects.equals(author, quote1.author);
}
@Override
public int hashCode() {
return Objects.hash(id, quote, author);
}
}
QuoteRepository.java
在 src/main/java/com/example/springboot 处创建一个名为 QuoteRepository.java 的文件,并复制以下代码
package com.example.springboot;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
public interface QuoteRepository extends JpaRepository<Quote,Integer> {
@Query( nativeQuery = true, value =
"SELECT id,quote,author FROM quotes ORDER BY RANDOM() LIMIT 1")
Quote findRandomQuote();
}
此代码使用 JPA 保留数据。该类扩展了 Spring JPARepository
接口,并允许创建自定义代码。您在代码中添加了 findRandomQuote
自定义方法。
QuoteController.java
为了公开该服务的端点,QuoteController
类将提供此功能。
在 src/main/java/com/example/springboot 处创建一个名为 QuoteController.java 的文件,并复制以下内容
package com.example.springboot;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class QuoteController {
private final QuoteRepository quoteRepository;
public QuoteController(QuoteRepository quoteRepository) {
this.quoteRepository = quoteRepository;
}
@GetMapping("/random-quote")
public Quote randomQuote()
{
return quoteRepository.findRandomQuote();
}
@GetMapping("/quotes")
public ResponseEntity<List<Quote>> allQuotes()
{
try {
List<Quote> quotes = new ArrayList<Quote>();
quoteRepository.findAll().forEach(quotes::add);
if (quotes.size()==0 || quotes.isEmpty())
return new ResponseEntity<List<Quote>>(HttpStatus.NO_CONTENT);
return new ResponseEntity<List<Quote>>(quotes, HttpStatus.OK);
} catch (Exception e) {
System.out.println(e.getMessage());
return new ResponseEntity<List<Quote>>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PostMapping("/quotes")
public ResponseEntity<Quote> createQuote(@RequestBody Quote quote) {
try {
Quote saved = quoteRepository.save(quote);
return new ResponseEntity<Quote>(saved, HttpStatus.CREATED);
} catch (Exception e) {
System.out.println(e.getMessage());
return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PutMapping("/quotes/{id}")
public ResponseEntity<Quote> updateQuote(@PathVariable("id") Integer id, @RequestBody Quote quote) {
try {
Optional<Quote> existingQuote = quoteRepository.findById(id);
if(existingQuote.isPresent()){
Quote updatedQuote = existingQuote.get();
updatedQuote.setAuthor(quote.getAuthor());
updatedQuote.setQuote(quote.getQuote());
return new ResponseEntity<Quote>(updatedQuote, HttpStatus.OK);
} else {
return new ResponseEntity<Quote>(HttpStatus.NOT_FOUND);
}
} catch (Exception e) {
System.out.println(e.getMessage());
return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@DeleteMapping("/quotes/{id}")
public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
try {
quoteRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (RuntimeException e) {
System.out.println(e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
添加数据库配置
application.yaml
为服务访问的后端数据库添加配置。在 src/main/resources
下修改(如果不存在)名为 application.yaml
的文件,并为后端添加参数化 Spring 配置。
target: local spring: config: activate: on-profile: cloud-dev datasource: url: 'jdbc:postgresql://${DB_HOST:127.0.0.1}/${DB_NAME:quote_db}' username: '${DB_USER:user}' password: '${DB_PASS:password}' jpa: properties: hibernate: jdbc: lob: non_contextual_creation: true dialect: org.hibernate.dialect.PostgreSQLDialect hibernate: ddl-auto: update
添加数据库迁移
在 src/main/resources/db/migration/ 处创建一个文件夹
创建 SQL 文件:V1__create_quotes_table.sql
将以下内容粘贴到文件中
V1__create_quotes_table.sql
CREATE TABLE quotes(
id INTEGER PRIMARY KEY,
quote VARCHAR(1024),
author VARCHAR(256)
);
INSERT INTO quotes (id,quote,author) VALUES (1,'Never, never, never give up','Winston Churchill');
INSERT INTO quotes (id,quote,author) VALUES (2,'While there''s life, there''s hope','Marcus Tullius Cicero');
INSERT INTO quotes (id,quote,author) VALUES (3,'Failure is success in progress','Anonymous');
INSERT INTO quotes (id,quote,author) VALUES (4,'Success demands singleness of purpose','Vincent Lombardi');
INSERT INTO quotes (id,quote,author) VALUES (5,'The shortest answer is doing','Lord Herbert');
Kubernetes 配置
向 deployment.yaml
文件中添加以下内容可让应用连接到 CloudSQL 实例。
- TARGET - 配置此变量以指示执行应用的环境
- SPRING_PROFILES_ACTIVE - 显示有效的 Spring 配置文件,该配置文件将配置为
cloud-dev
- DB_HOST - 数据库的专用 IP。在创建数据库实例时或通过点击 Google Cloud 控制台导航菜单中的
SQL
时,系统已记下该 IP - 请更改值! - DB_USER 和 DB_PASS - 如 CloudSQL 实例配置中所设置,以 Secret 形式存储在 GCP 中
使用以下内容更新 deployment.yaml。
deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: demo-app
labels:
app: demo-app
spec:
ports:
- port: 8080
protocol: TCP
clusterIP: None
selector:
app: demo-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
labels:
app: demo-app
spec:
replicas: 1
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app
image: demo-app
env:
- name: PORT
value: "8080"
- name: TARGET
value: "Local Dev - CloudSQL Database - K8s Cluster"
- name: SPRING_PROFILES_ACTIVE
value: cloud-dev
- name: DB_HOST
value: ${DB_INSTANCE_IP}
- name: DB_PORT
value: "5432"
- name: DB_USER
valueFrom:
secretKeyRef:
name: gke-cloud-sql-secrets
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: gke-cloud-sql-secrets
key: password
- name: DB_NAME
valueFrom:
secretKeyRef:
name: gke-cloud-sql-secrets
key: database
将 DB_HOST 值替换为您的数据库地址
export DB_INSTANCE_IP=$(gcloud sql instances describe quote-db-instance \
--format=json | jq \
--raw-output ".ipAddresses[].ipAddress")
envsubst < deployment.yaml > deployment.new && mv deployment.new deployment.yaml
部署并验证应用
- 在 Cloud Shell 编辑器底部的窗格中,选择“Cloud Code”,然后选择屏幕顶部的“在 Kubernetes 上调试”。
- 构建和测试完成后,“Output”(输出)标签页会显示
Resource deployment/demo-app status completed successfully
,并列出了一个网址:“Forwarded 网址 from service demo-app: http://localhost:8080” - 查看随机报价
在 cloudshell 终端中,针对 random-quote 端点多次运行以下命令。观察重复调用返回不同报价
curl -v 127.0.0.1:8080/random-quote
- 添加报价
使用下列命令创建一个新报价(id=6),并观察回显请求
curl -v -H 'Content-Type: application/json' -d '{"id":"6","author":"Henry David Thoreau","quote":"Go confidently in the direction of your dreams! Live the life you have imagined"}' -X POST 127.0.0.1:8080/quotes
- 删除报价
现在,删除您刚刚使用 delete 方法添加的引用,并观察 HTTP/1.1 204
响应代码。
curl -v -X DELETE 127.0.0.1:8080/quotes/6
- 服务器错误
在条目被删除后再次运行最后一个请求,从而遇到错误状态
curl -v -X DELETE 127.0.0.1:8080/quotes/6
请注意,响应会返回 HTTP:500 Internal Server Error
。
调试应用
在上一部分中,当您尝试删除数据库中不存在的条目时,发现应用中存在错误状态。在本部分中,您将设置一个断点来查找问题。DELETE 操作中发生该错误,因此您将使用 QuoteController 类。
- 打开 src.main.java.com.example.springboot.QuoteController.java
- 查找
deleteQuote()
方法 - 找到从数据库中删除商品所在的行:
quoteRepository.deleteById(id);
- 点击行号左侧的空白处,在该行上设置断点。
- 系统会显示一个红色指示器,指示已设置断点
- 再次运行
delete
命令
curl -v -X DELETE 127.0.0.1:8080/quotes/6
- 点击左侧列中的图标切换回调试视图
- 您会发现,在 QuoteController 类中停止的调试行。
- 在调试程序中,点击
step over
图标 并观察系统会抛出异常 - 您会发现一个非常普通的
RuntimeException was caught.
。这会向客户端返回一个内部服务器错误 HTTP 500,这并不理想。
Trying 127.0.0.1:8080... * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > DELETE /quotes/6 HTTP/1.1 > Host: 127.0.0.1:8080 > User-Agent: curl/7.74.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 500 < Content-Length: 0 < Date: < * Connection #0 to host 127.0.0.1 left intact
更新代码
代码不正确,应重构异常块,以捕获 EmptyResultDataAccessException
异常并发回 HTTP 404 找不到状态代码。
更正错误。
- 在调试会话仍在运行的情况下,按“continue”(继续)完成请求按钮。
- 接下来,将以下代码块添加到代码中:
} catch (EmptyResultDataAccessException e){
return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
}
该方法应如下所示:
public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) { try { quoteRepository.deleteById(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } catch(EmptyResultDataAccessException e){ return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND); } catch (RuntimeException e) { System.out.println(e.getMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } }
- 重新运行删除命令
curl -v -X DELETE 127.0.0.1:8080/quotes/6
- 逐步执行调试程序,并观察已捕获
EmptyResultDataAccessException
,并向调用方返回“HTTP 404 Not Found”。
Trying 127.0.0.1:8080... * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > DELETE /quotes/6 HTTP/1.1 > Host: 127.0.0.1:8080 > User-Agent: curl/7.74.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 404 < Content-Length: 0 < Date: < * Connection #0 to host 127.0.0.1 left intact
- 点击调试工具栏 中的红色方块,停止调试会话
6. 清理
恭喜!在本实验中,您从头开始创建了一个新的 Java 应用,并将其配置为与容器高效运行。然后,您按照传统应用堆栈中的相同开发者流程,将应用部署并调试到远程 GKE 集群。
在完成实验后进行清理:
- 删除实验中使用的文件
cd ~ && rm -rf container-developer-workshop
- 删除项目以移除所有相关基础架构和资源