1. Visão geral
Este laboratório demonstra recursos e funcionalidades projetados para simplificar o fluxo de trabalho de desenvolvimento de engenheiros de software responsáveis por desenvolver aplicativos Java em um ambiente conteinerizado. O desenvolvimento típico de contêineres exige que o usuário entenda os detalhes dos contêineres e do processo de build deles. Além disso, os desenvolvedores geralmente precisam interromper o fluxo de trabalho, saindo do ambiente de desenvolvimento integrado para testar e depurar os aplicativos em ambientes remotos. Com as ferramentas e tecnologias mencionadas neste tutorial, os desenvolvedores podem trabalhar de forma eficaz com aplicativos contêinerizados sem sair do ambiente de desenvolvimento integrado.
O que você vai aprender
Neste laboratório, você vai aprender métodos para desenvolver com contêineres no GCP, incluindo:
- Desenvolvimento de ciclo interno com o Cloud Workstations
- Como criar um novo aplicativo inicial em Java
- Como percorrer o processo de desenvolvimento
- Como desenvolver um serviço REST CRUD simples
- Depuração de aplicativos no cluster do GKE
- Como conectar o aplicativo ao banco de dados do Cloud SQL

2. Configuração e requisitos
Configuração de ambiente personalizada
- Faça login no Console do Google Cloud e crie um novo projeto ou reutilize um existente. Crie uma conta do Gmail ou do Google Workspace, se ainda não tiver uma.



- O Nome do projeto é o nome de exibição para os participantes do projeto. É uma string de caracteres não usada pelas APIs do Google É possível atualizar o local a qualquer momento.
- O ID do projeto precisa ser exclusivo em todos os projetos do Google Cloud e não pode ser mudado após a definição. O console do Cloud gera automaticamente uma string exclusiva. Em geral, não importa o que seja. Na maioria dos codelabs, é necessário fazer referência ao ID do projeto, normalmente identificado como
PROJECT_ID. Se você não gostar do ID gerado, crie outro aleatório. Se preferir, teste o seu e confira se ele está disponível. Ele não pode ser mudado após essa etapa e permanece durante o projeto. - Para sua informação, há um terceiro valor, um Número do projeto, que algumas APIs usam. Saiba mais sobre esses três valores na documentação.
- Em seguida, ative o faturamento no console do Cloud para usar os recursos/APIs do Cloud. A execução deste codelab não será muito cara, se tiver algum custo. Para desligar os recursos e evitar cobranças além deste tutorial, exclua os recursos criados ou o projeto inteiro. Novos usuários do Google Cloud estão qualificados para o programa de US$ 300 de avaliação sem custos.
Iniciar o editor do Cloud Shell
Este laboratório foi projetado e testado para uso com o editor do Google Cloud Shell. Para acessar o editor,
- Acesse seu projeto do Google em https://console.cloud.google.com.
- No canto superior direito, clique no ícone do editor do Cloud Shell.

- Um novo painel será aberto na parte de baixo da janela
- Clique no botão "Abrir editor".

- O editor será aberto com um explorador à direita e o editor na área central.
- Um painel de terminal também vai estar disponível na parte de baixo da tela.
- Se o terminal NÃO estiver aberto, use a combinação de teclas "ctrl+`" para abrir uma nova janela do terminal.
Configurar a gcloud
No Cloud Shell, defina o ID do projeto e a região em que você quer implantar o aplicativo. Salve-as como variáveis PROJECT_ID e REGION.
export REGION=us-central1
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
Clonar o código-fonte
O código-fonte deste laboratório está localizado no container-developer-workshop em GoogleCloudPlatform no GitHub. Clone com o comando abaixo e mude para o diretório.
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot
Provisionar a infraestrutura usada neste laboratório
Neste laboratório, você vai implantar código no GKE e acessar dados armazenados em um banco de dados do Cloud SQL. O script de configuração abaixo prepara essa infraestrutura para você. O processo de provisionamento vai levar mais de 25 minutos. Aguarde a conclusão do script antes de passar para a próxima seção.
./setup_with_cw.sh &
Cluster do Cloud Workstations
Abra o Cloud Workstations no Console do Cloud. Aguarde até que o cluster esteja no status READY.
Criar configuração de estações de trabalho
Se a sessão do Cloud Shell foi desconectada, clique em "Reconectar" e execute o comando da CLI gcloud para definir o ID do projeto. Substitua o ID do projeto de exemplo abaixo pelo ID do seu projeto do Qwiklabs antes de executar o comando.
gcloud config set project qwiklabs-gcp-project-id
Execute o script abaixo no terminal para criar a configuração do Cloud Workstations.
cd ~/container-developer-workshop/labs/spring-boot
./workstation_config_setup.sh
Verifique os resultados na seção "Configurações". A transição para o status READY leva 2 minutos.

Abra o Cloud Workstations no console e crie uma nova instância.

Mude o nome para my-workstation e selecione a configuração atual: codeoss-java.

Verifique os resultados na seção "Estações de trabalho".

Iniciar estação de trabalho
Inicie e abra a estação de trabalho.

Para permitir cookies de terceiros, clique no ícone na barra de endereço. 

Clique em "Site indisponível?".

Clique em "Permitir cookies".

Quando a estação de trabalho for iniciada, o ambiente de desenvolvimento integrado do Code OSS vai aparecer. Clique em "Marcar como concluído" na página "Como começar" do ambiente de desenvolvimento integrado da estação de trabalho.

3. Como criar um novo aplicativo inicial em Java
Nesta seção, você vai criar um novo aplicativo Java Spring Boot do zero usando um aplicativo de amostra fornecido por spring.io. Abra um novo terminal.

Clonar o aplicativo de exemplo
- Criar um aplicativo inicial
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
Clique no botão "Permitir" se essa mensagem aparecer para poder copiar e colar na estação de trabalho.

- Descompacte o aplicativo
unzip sample-app.zip -d sample-app
- Abra a pasta "sample-app".
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"
Adicionar spring-boot-devtools e Jib
Para ativar o Spring Boot DevTools, encontre e abra o pom.xml no explorador do editor. Em seguida, cole o código a seguir depois da linha de descrição, que diz <description>Demo project for Spring Boot</description>
- Adicione spring-boot-devtools em pom.xml
Abra o pom.xml na raiz do projeto. Adicione a seguinte configuração após a entrada 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>
- Ativar o jib-maven-plugin em pom.xml
O Jib é uma ferramenta de contêineres Java de código aberto do Google que permite que desenvolvedores Java criem contêineres usando as ferramentas Java que já conhecem. O Jib é um criador de imagens de contêiner rápido e simples que lida com todas as etapas de empacotamento do aplicativo em uma imagem de contêiner. Não é necessário escrever um Dockerfile nem ter o Docker instalado, e ele é integrado diretamente ao Maven e ao Gradle.
Role para baixo no arquivo pom.xml e atualize a seção Build para incluir o plug-in Jib. A seção de build precisa corresponder ao seguinte quando concluída.
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>
Gerar manifestos
O Skaffold oferece ferramentas integradas para simplificar o desenvolvimento de contêineres. Nesta etapa, você vai inicializar o Skaffold, que vai criar automaticamente arquivos YAML básicos do Kubernetes. O processo tenta identificar diretórios com definições de imagens de contêiner, como um Dockerfile, e cria um manifesto de implantação e serviço para cada um.
Execute o comando abaixo no terminal para iniciar o processo.

- Execute o comando a seguir no terminal:
skaffold init --generate-manifests
- Quando solicitado:
- Use as setas para mover o cursor até
Jib Maven Plugin - Pressione a barra de espaço para selecionar a opção.
- Pressione Enter para continuar
- Insira 8080 para a porta
- Digite y para salvar a configuração.
Dois arquivos são adicionados ao espaço de trabalho skaffold.yaml e deployment.yaml
Saída do Skaffold:

Atualizar nome do app
Os valores padrão incluídos na configuração não correspondem ao nome do seu aplicativo. Atualize os arquivos para fazer referência ao nome do aplicativo em vez dos valores padrão.
- Mudar entradas na configuração do Skaffold
- Abrir
skaffold.yaml - Selecione o nome da imagem definido como
pom-xml-image. - Clique com o botão direito do mouse e escolha "Mudar todas as ocorrências".
- Digite o novo nome como
demo-app
- Mudar entradas na configuração do Kubernetes
- Abrir arquivo
deployment.yaml - Selecione o nome da imagem definido como
pom-xml-image. - Clique com o botão direito do mouse e escolha "Mudar todas as ocorrências".
- Digite o novo nome como
demo-app
Ativar o modo de sincronização automática
Para facilitar uma experiência de recarga automática otimizada, use o recurso de sincronização fornecido pelo Jib. Nesta etapa, você vai configurar o Skaffold para usar esse recurso no processo de build.
Observe que o perfil "sync" que você está configurando na configuração do Skaffold usa o perfil "sync" do Spring configurado na etapa anterior, em que você ativou o suporte para spring-dev-tools.
- Atualizar a configuração do Skaffold
No arquivo skaffold.yaml, substitua toda a seção de build pela seguinte especificação. Não altere outras seções do arquivo.
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
Adicionar uma rota padrão
Crie um arquivo chamado HelloController.java na pasta /src/main/java/com/example/springboot/.

Cole o conteúdo a seguir no arquivo para criar uma rota HTTP padrão.
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. Como percorrer o processo de desenvolvimento
Nesta seção, você vai seguir algumas etapas usando o plug-in Cloud Code para aprender os processos básicos e validar a configuração e a instalação do aplicativo inicial.
O Cloud Code se integra ao Skaffold para simplificar seu processo de desenvolvimento. Ao implantar no GKE nas etapas a seguir, o Cloud Code e o Skaffold vão criar automaticamente a imagem do contêiner, enviá-la para um Container Registry e implantar o aplicativo no GKE. Isso acontece nos bastidores, abstraindo os detalhes do fluxo do desenvolvedor. O Cloud Code também melhora seu processo de desenvolvimento ao oferecer recursos tradicionais de depuração e hotsync para desenvolvimento baseado em contêineres.
Faça login no Google Cloud
Clique no ícone do Cloud Code e selecione "Fazer login no Google Cloud":

Clique em "Prosseguir com login".

Confira a saída no terminal e abra o link:

Faça login com as credenciais de estudante do Qwiklabs.

Selecione "Permitir":

Copie o código de verificação e volte à guia "Estação de trabalho".

Cole o código de verificação e pressione "Enter".

Adicionar cluster do Kubernetes
- Adicionar um cluster

- Selecione Google Kubernetes Engine:

- Selecione o projeto.

- Selecione "quote-cluster", que foi criado na configuração inicial.


Definir o ID do projeto atual usando a CLI gcloud
Copie o ID do projeto deste laboratório na página do Qwiklabs.

Execute o comando da CLI gcloud para definir o ID do projeto. Substitua o ID do projeto de exemplo antes de executar o comando.
gcloud config set project qwiklabs-gcp-project-id
Exemplo de resposta:

Depurar no Kubernetes
- No painel esquerdo na parte de baixo, selecione "Cloud Code".

- No painel que aparece em SESSÕES DE DESENVOLVIMENTO, selecione "Depurar no Kubernetes".
Role a tela para baixo se a opção não estiver visível.

- Selecione "Sim" para usar o contexto atual.

- Selecione "quote-cluster", que foi criado durante a configuração inicial.

- Selecione "Repositório de contêineres".

- Selecione a guia "Saída" no painel de baixo para conferir o progresso e as notificações.
- Selecione "Kubernetes: Run/Debug - Detailed" no menu suspenso do canal à direita para conferir mais detalhes e registros transmitidos ao vivo dos contêineres.

Aguarde a implantação do aplicativo.

- Revise o aplicativo implantado no GKE no Console do Cloud.

- Para voltar à visualização simplificada, selecione "Kubernetes: Run/Debug" no menu suspenso da guia OUTPUT.
- Quando o build e os testes forem concluídos, a guia "Saída" vai mostrar:
Resource deployment/demo-app status completed successfully, e um URL será listado: "URL encaminhado do app de demonstração do serviço: http://localhost:8080" - No terminal do Cloud Code, passe o cursor sobre o URL na saída (http://localhost:8080) e, na dica de ferramenta que aparece, selecione "Seguir link".

Uma nova guia será aberta, e você verá a saída abaixo:

Usar pontos de interrupção
- Abra o aplicativo
HelloController.javaem/src/main/java/com/example/springboot/HelloController.java - Localize a instrução de retorno do caminho raiz, que diz
return String.format("Hello from your %s environment!", target);. - Adicione um ponto de interrupção a essa linha clicando no espaço em branco à esquerda do número da linha. Um indicador vermelho vai aparecer para mostrar que o ponto de interrupção foi definido.

- Recarregue o navegador e observe que o depurador interrompe o processo no ponto de interrupção e permite investigar as variáveis e o estado do aplicativo que está sendo executado remotamente no GKE.

- Clique na seção de variáveis até encontrar a variável "Destino".
- Observe o valor atual como "local".

- Clique duas vezes no nome da variável "target" e, no pop-up,
mude o valor para "Cloud Workstations"

- Clique no botão "Continuar" no painel de controle de depuração.

- Revise a resposta no navegador, que agora mostra o valor atualizado que você acabou de inserir.

- Remova o ponto de interrupção clicando no indicador vermelho à esquerda do número da linha. Isso vai impedir que o código pare de ser executado nessa linha à medida que você avança no laboratório.
Recarga automática
- Mude a instrução para retornar um valor diferente, como "Olá do código %s".
- O arquivo é salvo e sincronizado automaticamente nos contêineres remotos no GKE.
- Atualize o navegador para ver os resultados atualizados.
- Clique no quadrado vermelho na barra de ferramentas de depuração para interromper a sessão.

Selecione "Sim, liberar espaço após cada execução".

5. Como desenvolver um serviço REST CRUD simples
Neste ponto, seu aplicativo está totalmente configurado para desenvolvimento em contêineres, e você já passou pelo fluxo de trabalho de desenvolvimento básico com o Cloud Code. Nas seções a seguir, você vai praticar o que aprendeu adicionando endpoints de serviço REST que se conectam a um banco de dados gerenciado no Google Cloud.
Configurar dependências
O código do aplicativo usa um banco de dados para manter os dados do serviço REST. Adicione o seguinte ao pom.xl para garantir que as dependências estejam disponíveis:
- Abra o arquivo
pom.xmle adicione o seguinte à seção de dependências da configuração:
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>
Codificar o serviço REST
Quote.java
Crie um arquivo chamado Quote.java em /src/main/java/com/example/springboot/ e copie o código abaixo. Isso define o modelo de entidade para o objeto "Quote" usado no aplicativo.
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
Crie um arquivo chamado QuoteRepository.java em src/main/java/com/example/springboot e copie o seguinte código:
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();
}
Esse código usa JPA para persistir os dados. A classe estende a interface JPARepository do Spring e permite a criação de código personalizado. No código, você adicionou um método personalizado findRandomQuote.
QuoteController.java
Para expor o endpoint do serviço, uma classe QuoteController vai fornecer essa funcionalidade.
Crie um arquivo chamado QuoteController.java em src/main/java/com/example/springboot e copie o seguinte conteúdo:
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);
}
}
}
Adicionar configurações de banco de dados
application.yaml
Adicione a configuração do banco de dados de back-end acessado pelo serviço. Edite (ou crie, se não estiver presente) o arquivo chamado application.yaml em src/main/resources e adicione uma configuração Spring parametrizada para o back-end.
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
Adicionar migração de banco de dados
Crie pastas db/migration em src/main/resources
Criar um arquivo SQL: V1__create_quotes_table.sql
Cole o conteúdo a seguir no arquivo:
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');
Configuração do Kubernetes
As adições a seguir no arquivo deployment.yaml permitem que o aplicativo se conecte às instâncias do Cloud SQL.
- TARGET: configura a variável para indicar o ambiente em que o app é executado.
- SPRING_PROFILES_ACTIVE: mostra o perfil ativo do Spring, que será configurado como
cloud-dev. - DB_HOST: o IP privado do banco de dados, que foi anotado quando a instância do banco de dados foi criada ou ao clicar em
SQLno menu de navegação do console do Google Cloud. Mude o valor. - DB_USER e DB_PASS: conforme definido na configuração da instância do Cloud SQL, armazenados como um secret no GCP.
Atualize o arquivo deployment.yaml com o conteúdo abaixo.
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
Substitua o valor DB_HOST pelo endereço do seu banco de dados executando os comandos abaixo no 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
Abra deployment.yaml e verifique se o valor DB_HOST foi atualizado com o IP da instância.

Implantar e validar o aplicativo
- No painel na parte de baixo do editor do Cloud Shell, selecione "Cloud Code" e depois "Depurar no Kubernetes" na parte de cima da tela.

- Quando o build e os testes forem concluídos, a guia "Saída" vai mostrar:
Resource deployment/demo-app status completed successfully, e um URL será listado: "URL encaminhado do serviço demo-app: http://localhost:8080". Observe que, às vezes, a porta pode ser diferente, como 8081. Se sim, defina o valor adequado. Defina o valor do URL no terminal
export URL=localhost:8080
- Ver citações aleatórias
No terminal, execute o comando abaixo várias vezes no endpoint random-quote. Observe a chamada repetida retornando cotações diferentes
curl $URL/random-quote | jq
- Adicionar uma citação
Crie uma nova cotação com id=6 usando o comando listado abaixo e observe a solicitação sendo repetida.
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
- Excluir uma cotação
Agora exclua a citação que você acabou de adicionar com o método de exclusão e observe um código de resposta HTTP/1.1 204.
curl -v -X DELETE $URL/quotes/6
- Erro no servidor
Apresentar um estado de erro executando a última solicitação novamente depois que a entrada já foi excluída
curl -v -X DELETE $URL/quotes/6
A resposta retorna um HTTP:500 Internal Server Error.
Depurar o aplicativo
Na seção anterior, você encontrou um estado de erro no aplicativo ao tentar excluir uma entrada que não estava no banco de dados. Nesta seção, você vai definir um ponto de interrupção para localizar o problema. O erro ocorreu na operação DELETE, então você vai trabalhar com a classe QuoteController.
- Abrir
src/main/java/com/example/springboot/QuoteController.java - Encontre o método
deleteQuote() - Encontre a linha:
Optional<Quote> quote = quoteRepository.findById(id); - Defina um ponto de interrupção nessa linha clicando no espaço em branco à esquerda do número da linha.
- Um indicador vermelho vai aparecer para mostrar que o ponto de interrupção foi definido.
- Execute o comando
deletenovamente.
curl -v -X DELETE $URL/quotes/6
- Clique no ícone na coluna à esquerda para voltar à visualização de depuração.
- Observe a linha de depuração interrompida na classe QuoteController.
- No depurador, clique no ícone
step over
- Observe que um código retorna um erro interno do servidor HTTP 500 ao cliente, o que não é o 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
Atualizar o código
O código está incorreto, e o bloco else precisa ser refatorado para enviar um código de status HTTP 404 "Não encontrado".
Corrija o erro.
- Com a sessão de depuração ainda em execução, conclua a solicitação pressionando o botão "Continuar" no painel de controle de depuração.
- Em seguida, mude o bloco
elsepara o código:
else {
return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
}
O método vai ficar assim:
@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);
}
}
- Execute o comando de exclusão novamente.
curl -v -X DELETE $URL/quotes/6
- Percorra o depurador e observe o HTTP 404 Não encontrado retornado ao autor da chamada.
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
- Clique no quadrado vermelho na barra de ferramentas de depuração para interromper a sessão.


6. Parabéns
Parabéns! Neste laboratório, você criou um novo aplicativo Java do zero e o configurou para funcionar de maneira eficaz com contêineres. Em seguida, você implantou e depurou o aplicativo em um cluster do GKE remoto seguindo o mesmo fluxo de desenvolvedor encontrado em stacks de aplicativos tradicionais.
O que você aprendeu
- Desenvolvimento de ciclo interno com o Cloud Workstations
- Como criar um novo aplicativo inicial em Java
- Como percorrer o processo de desenvolvimento
- Como desenvolver um serviço REST CRUD simples
- Depuração de aplicativos no cluster do GKE
- Como conectar o aplicativo ao banco de dados do Cloud SQL
