About this codelab
1. Overview
This lab demonstrates features and capabilities designed to streamline the development workflow for software engineers tasked with developing Java applications in a containerized environment. Typical container development requires the user to understand details of containers and the container build process. Additionally, developers typically have to break their flow, moving out of their IDE to test and debug their applications in remote environments. With the tools and technologies mentioned in this tutorial, developers can work effectively with containerized applications without leaving their IDE.
What you will learn
In this lab you will learn methods for developing with containers in GCP including:
- InnerLoop development with Cloud Workstations
- Creating a new Java starter application
- Walking through the development process
- Developing a simple CRUD Rest Service
- Debugging application on GKE cluster
- Connecting application to CloudSQL database
2. Setup and Requirements
Self-paced environment setup
- Sign-in to the Google Cloud Console and create a new project or reuse an existing one. If you don't already have a Gmail or Google Workspace account, you must create one.
- The Project name is the display name for this project's participants. It is a character string not used by Google APIs. You can update it at any time.
- The Project ID is unique across all Google Cloud projects and is immutable (cannot be changed after it has been set). The Cloud Console auto-generates a unique string; usually you don't care what it is. In most codelabs, you'll need to reference the Project ID (it is typically identified as
PROJECT_ID
). If you don't like the generated ID, you may generate another random one. Alternatively, you can try your own and see if it's available. It cannot be changed after this step and will remain for the duration of the project. - For your information, there is a third value, a Project Number which some APIs use. Learn more about all three of these values in the documentation.
- Next, you'll need to enable billing in the Cloud Console to use Cloud resources/APIs. Running through this codelab shouldn't cost much, if anything at all. To shut down resources so you don't incur billing beyond this tutorial, you can delete the resources you created or delete the whole project. New users of Google Cloud are eligible for the $300 USD Free Trial program.
Start Cloudshell Editor
This lab was designed and tested for use with Google Cloud Shell Editor. To access the editor,
- access your google project at https://console.cloud.google.com.
- In the top right corner click on the cloud shell editor icon
- A new pane will open in the bottom of your window
- Click on the Open Editor button
- The editor will open with an explorer on the right and editor in the central area
- A terminal pane should also be available in the bottom of the screen
- If the terminal is NOT open use the key combination of `ctrl+`` to open a new terminal window
Set up gcloud
In Cloud Shell, set your project ID and the region you want to deploy your application to. Save them as PROJECT_ID
and REGION
variables.
export REGION=us-central1
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
Clone the source code
The source code for this lab is located in the container-developer-workshop in GoogleCloudPlatform on GitHub. Clone it with the command below then change into the directory.
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot
Provision the infrastructure used in this lab
In this lab you will deploy code to GKE and access data stored in a CloudSQL database. The setup script below prepares this infrastructure for you. The provisioning process will take over 25 minutes. Wait for the script to complete before moving to the next section.
./setup_with_cw.sh &
Cloud Workstations Cluster
Open Cloud Workstations in the Cloud Console. Wait for the cluster to be in READY
status.
Create Workstations Configuration
If your Cloud Shell session was disconnected, click "Reconnect" and then run the gcloud cli command to set the project ID. Replace sample project id below with your qwiklabs project ID before running the command.
gcloud config set project qwiklabs-gcp-project-id
Run the script below in the terminal to create Cloud Workstations configuration.
cd ~/container-developer-workshop/labs/spring-boot
./workstation_config_setup.sh
Verify results under the Configurations section. It will take 2 minutes to transition to READY status.
Open Cloud Workstations in the Console and create new instance.
Change name to my-workstation
and select existing configuration: codeoss-java
.
Verify results under the Workstations section.
Launch Workstation
Start and launch the workstation.
Allow 3rd party cookies by clicking on the icon in the address bar.
Click "Site not working?".
Click "Allow cookies".
Once the workstation launches you will see Code OSS IDE come up. Click on "Mark Done" on the Getting Started page one the workstation IDE
3. Creating a new Java starter application
In this section you'll create a new Java Spring Boot application from scratch utilizing a sample application provided by spring.io. Open a new Terminal.
Clone the Sample Application
- Create a starter application
curl https://start.spring.io/starter.zip -d dependencies=web -d type=maven-project -d javaVersion=17 -d packageName=com.example.springboot -o sample-app.zip
Click on the Allow button if you see this message, so that you can copy paste into the workstation.
- Unzip the application
unzip sample-app.zip -d sample-app
- Open the "sample-app" folder
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"
Add spring-boot-devtools & Jib
To enable the Spring Boot DevTools find and open the pom.xml from the explorer in your editor. Next paste the following code after the description line which reads <description>Demo project for Spring Boot</description>
- Add spring-boot-devtools in pom.xml
Open the pom.xml
in the root of the project. Add the following configuration after the Description
entry.
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>
- Enable jib-maven-plugin in pom.xml
Jib is an open-source Java containerizing tool from Google that lets Java developers build containers using the Java tools they know. Jib is a fast and simple container image builder that handles all the steps of packaging your application into a container image. It does not require you to write a Dockerfile or have docker installed, and it is directly integrated into Maven and Gradle.
Scroll down in the pom.xml
file and update the Build
section to include the Jib plugin. The build section should match the following when completed.
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>
Generate Manifests
Skaffold provides integrated tools to simplify container development. In this step you will initialize Skaffold which will automatically create base Kubernetes YAML files. The process tries to identify directories with container image definitions, like a Dockerfile, and then creates a deployment and service manifest for each.
Execute the command below in the Terminal to begin the process.
- Execute the following command in the terminal
skaffold init --generate-manifests
- When prompted:
- Use the arrows to move your cursor to
Jib Maven Plugin
- Press the spacebar to select the option.
- Press enter to continue
- Enter 8080 for the port
- Enter y to save the configuration
Two files are added to the workspace skaffold.yaml
and deployment.yaml
Skaffold output:
Update app name
The default values included in the configuration don't currently match the name of your application. Update the files to reference your application name rather than the default values.
- Change entries in Skaffold config
- Open
skaffold.yaml
- Select the image name currently set as
pom-xml-image
- Right click and choose Change All Occurrences
- Type in the new name as
demo-app
- Change entries in Kubernetes config
- Open
deployment.yaml
file - Select the image name currently set as
pom-xml-image
- Right click and choose Change All Occurrences
- Type in the new name as
demo-app
Enable Auto sync mode
To facilitate an optimized hot reload experience you'll utilize the Sync feature provided by Jib. In this step you will configure Skaffold to utilize that feature in the build process.
Note that the "sync" profile you are configuring in the Skaffold configuration leverages the Spring "sync" Profile you have configured in the previous step, where you have enabled support for spring-dev-tools.
- Update Skaffold config
In the skaffold.yaml
file replace the entire build section of the file with the following specification. Do not alter other sections of the file.
skaffold.yaml
build:
artifacts:
- image: demo-app
jib:
project: com.example:demo
type: maven
args:
- --no-transfer-progress
- -Psync
fromImage: gcr.io/distroless/java17-debian11:debug
sync:
auto: true
Add a default route
Create a file called HelloController.java
in /src/main/java/com/example/springboot/
folder.
Paste the following contents in the file to create a default http route.
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. Walking through the development process
In this section you'll walk through a few steps using the Cloud Code plugin to learn the basic processes and to validate the configuration and setup of your starter application.
Cloud Code integrates with Skaffold to streamline your development process. When you deploy to GKE in the following steps, Cloud Code and Skaffold will automatically build your container image, push it to a Container Registry, and then deploy your application to GKE. This happens behind the scenes abstracting the details away from the developer flow. Cloud Code also enhances your development process by providing traditional debug and hotsync capabilities to container based development.
Sign in to Google Cloud
Click on Cloud Code icon and Select "Sign in to Google Cloud":
Click "Proceed to sign in".
Check the output in the Terminal and open the link:
Login with your Qwiklabs students credentials.
Select "Allow":
Copy verification code and return to the Workstation tab.
Paste the verification code and hit Enter.
Add Kubernetes Cluster
- Add a Cluster
- Select Google Kubernetes Engine:
- Select project.
- Select "quote-cluster" that was created in the initial setup.
Set current project id using gcloud cli
Copy project ID for this lab from qwiklabs page.
Run the gcloud cli command to set the project ID. Replace sample project id before running the command.
gcloud config set project qwiklabs-gcp-project-id
Sample output:
Debug on Kubernetes
- In the left pane at the bottom select Cloud Code.
- In the panel that appears under DEVELOPMENT SESSIONS, select Debug on Kubernetes.
Scroll down if the option is not visible.
- Select "Yes" to use current context.
- Select "quote-cluster" that was created during initial setup.
- Select Container Repository.
- Select the Output tab in the lower pane to view progress and notifications
- Select "Kubernetes: Run/Debug - Detailed" in the channel drop down to the right to view additional details and logs streaming live from the containers
Wait for the application to be deployed.
- Review deployed application on GKE in Cloud Console.
- Return to the simplified view by selecting "Kubernetes: Run/Debug" from the dropdown on the OUTPUT tab.
- When the build and tests are done, the Output tab says:
Resource deployment/demo-app status completed successfully
, and a url is listed: "Forwarded URL from service demo-app: http://localhost:8080" - In the Cloud Code terminal, hover over the URL in the output (http://localhost:8080), and then in the tool tip that appears select Follow link.
New tab will be opened and you will see output below:
Utilize Breakpoints
- Open the
HelloController.java
application located at/src/main/java/com/example/springboot/HelloController.java
- Locate the return statement for the root path which reads
return String.format("Hello from your %s environment!", target);
- Add a breakpoint to that line by clicking the blank space to the left of the line number. A red indicator will show to note the breakpoint is set
- Reload your browser and note the debugger stops the process at the breakpoint and allows you to investigate the variables and state of the application which is running remotely in GKE
- Click down into the variables section until you find the "Target" variable.
- Observe the current value as "local"
- Double click on the variable name "target" and in the popup,
change the value to "Cloud Workstations"
- Click the Continue button in the debug control panel
- Review the response in your browser which now shows the updated value you just entered.
- Remove the breakpoint by clicking the red indicator to the left of the line number. This will prevent your code from stopping execution at this line as you move forward in this lab.
Hot Reload
- Change the statement to return a different value such as "Hello from %s Code"
- The file is automatically saved and synced into the remote containers in GKE
- Refresh your browser to see the updated results.
- Stop the debugging session by clicking on the red square in the debug toolbar
Select "Yes clean up after each run".
5. Developing a simple CRUD Rest Service
At this point your application is fully configured for containerized development and you've walked through the basic development workflow with Cloud Code. In the following sections you practice what you've learned by adding REST service endpoints connecting to a managed database in Google Cloud.
Configure Dependencies
The application code uses a database to persist the rest service data. Ensure the dependencies are available by adding the following in the pom.xl
- Open the
pom.xml
file and add the following into the dependencies section of the config
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>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>
Code REST service
Quote.java
Create a file called Quote.java
in /src/main/java/com/example/springboot/
and copy in the code below. This defines the Entity model for the Quote object used in the application.
package com.example.springboot;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.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
Create a file called QuoteRepository.java
at src/main/java/com/example/springboot
and copy in the following code
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();
}
This code uses JPA for persisting the data. The class extends the Spring JPARepository
interface and allows the creation of custom code. In the code you've added a findRandomQuote
custom method.
QuoteController.java
To expose the endpoint for the service, a QuoteController
class will provide this functionality.
Create a file called QuoteController.java
at src/main/java/com/example/springboot
and copy in the following contents
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) {
Optional<Quote> quote = quoteRepository.findById(id);
if (quote.isPresent()) {
quoteRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
Add Database Configurations
application.yaml
Add configuration for the backend database accessed by the service. Edit (or create if not present) the file called application.yaml
file under src/main/resources
and add a parameterized Spring configuration for the backend.
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
Add Database Migration
Create folders db/migration
under src/main/resources
Create a SQL file: V1__create_quotes_table.sql
Paste the following contents into the file
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 Config
The following additions to the deployment.yaml
file allow the application to connect to the CloudSQL instances.
- TARGET - configures the variable to indicate the environment where the app is executed
- SPRING_PROFILES_ACTIVE - shows the active Spring profile, which will be configured to
cloud-dev
- DB_HOST - the private IP for the database, which has been noted when the database instance has been created or by clicking
SQL
in the Navigation Menu of the Google Cloud Console - please change the value ! - DB_USER and DB_PASS - as set in the CloudSQL instance configuration, stored as a Secret in GCP
Update your deployment.yaml with the contents below.
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
Replace the DB_HOST value with the address of your Database by running the commands below in the terminal:
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
Open deployment.yaml and verify that DB_HOST value was updated with Instance IP.
Deploy and Validate Application
- In the pane at the bottom of Cloud Shell Editor, select Cloud Code then select Debug on Kubernetes at the top of the screen.
- When the build and tests are done, the Output tab says:
Resource deployment/demo-app status completed successfully
, and a url is listed: "Forwarded URL from service demo-app: http://localhost:8080". Note that sometimes the port may be different like 8081. If so set the appropriate value. Set the value of URL in the terminal
export URL=localhost:8080
- View Random Quotes
From Terminal, run the command below multiple times against the random-quote endpoint. Observe repeated call returning different quotes
curl $URL/random-quote | jq
- Add a Quote
Create a new quote, with id=6 using the command listed below and observe the request being echoed back
curl -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 $URL/quotes
- Delete a quote
Now delete the quote you just added with the delete method and observe an HTTP/1.1 204
response code.
curl -v -X DELETE $URL/quotes/6
- Server Error
Experience an error state by running the last request again after the entry has already been deleted
curl -v -X DELETE $URL/quotes/6
Notice the response returns an HTTP:500 Internal Server Error
.
Debug the application
In the previous section you found an error state in the application when you tried to delete an entry that was not in the database. In this section you'll set a breakpoint to locate the issue. The error occurred in the DELETE operation, so you'll work with the QuoteController class.
- Open
src/main/java/com/example/springboot/QuoteController.java
- Find the
deleteQuote()
method - Find the the line:
Optional<Quote> quote = quoteRepository.findById(id);
- Set a breakpoint on that line by clicking the blank space to the left of the line number.
- A red indicator will appear indicating the breakpoint is set
- Run the
delete
command again
curl -v -X DELETE $URL/quotes/6
- Switch back to the debug view by clicking the icon in the left column
- Observe the debug line stopped in the QuoteController class.
- In the debugger, click the
step over
icon - Observe that a code returns an Internal Server Error HTTP 500 to the client which is not ideal.
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
Update the code
The code is incorrect and the else
block should be refactored to send back a HTTP 404 not found status code.
Correct the error.
- With the Debug session still running, complete the request by pressing the "continue" button in the debug control panel.
- Next change the
else
block to the code:
else {
return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
}
The method should look like the following
@DeleteMapping("/quotes/{id}") public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) { Optional<Quote> quote = quoteRepository.findById(id); if (quote.isPresent()) { quoteRepository.deleteById(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } else { return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND); } }
- Rerun the delete command
curl -v -X DELETE $URL/quotes/6
- Step through the debugger and observe the HTTP 404 Not Found returned to the caller.
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
- Stop the debugging session by clicking on the red square in the debug toolbar
6. Congratulations
Congratulations! In this lab you've created a new Java application from scratch and configured it to work effectively with containers. You then deployed and debugged your application to a remote GKE cluster following the same developer flow found in traditional application stacks.
What you have learned
- InnerLoop development with Cloud Workstations
- Creating a new Java starter application
- Walking through the development process
- Developing a simple CRUD REST Service
- Debugging application on GKE cluster
- Connecting application to CloudSQL database