Como desenvolver com o Cloud Workstations e o Cloud Code

1. Visão geral

Este laboratório mostra as funcionalidades projetadas para simplificar o fluxo de trabalho de engenheiros de software responsáveis pelo desenvolvimento de aplicativos Java em um ambiente conteinerizado. O desenvolvimento de contêineres típico exige que o usuário entenda os detalhes dos contêineres e do processo de criação do contêiner. Além disso, os desenvolvedores geralmente precisam interromper o fluxo, 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 efetivamente com aplicativos conteinerizados sem sair do ambiente de desenvolvimento integrado.

O que você vai aprender

Neste laboratório, você aprenderá métodos para desenvolver com contêineres no GCP, incluindo:

  • Desenvolvimento do InnerLoop com o Cloud Workstations
  • Como criar um novo aplicativo inicial Java
  • Orientações sobre o processo de desenvolvimento
  • Como desenvolver um serviço CRUD simples
  • Aplicativo de depuração no cluster do GKE
  • Como conectar o aplicativo ao banco de dados do CloudSQL

58a4cdd3ed7a123a.png

2. Configuração e requisitos

Configuração de ambiente personalizada

  1. 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.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • 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 Você pode atualizar 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. normalmente você não se importa com o que seja. Na maioria dos codelabs, é necessário fazer referência ao ID do projeto, que normalmente é identificado como PROJECT_ID. Se você não gostar do ID gerado, pode gerar outro ID aleatório. Como alternativa, você pode tentar o seu próprio e ver se ele está disponível. Ela não pode ser alterada após essa etapa e permanecerá durante a duração do projeto.
  • Para sua informação, há um terceiro valor, um Número de projeto, que algumas APIs usam. Saiba mais sobre esses três valores na documentação.
  1. 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 encerrar os recursos e não gerar faturamento além deste tutorial, exclua os recursos criados ou exclua o projeto inteiro. Novos usuários do Google Cloud estão qualificados para o programa de US$ 300 de avaliação sem custos.

Inicie o editor do Cloud Shell

Este laboratório foi elaborado e testado para uso com o Editor do Google Cloud Shell. Para acessar o editor,

  1. acesse seu projeto do Google em https://console.cloud.google.com.
  2. No canto superior direito, clique no ícone do editor do Cloud Shell

8560cc8d45e8c112.png

  1. Um novo painel será aberto na parte inferior da janela.
  2. Clique no botão "Abrir editor"

9e504cb98a6a8005.png

  1. O editor será aberto com um explorador à direita e o editor na área central.
  2. Um painel do terminal também deve estar disponível na parte inferior da tela
  3. Se o terminal NÃO estiver aberto, use a combinação de teclas "ctrl+" para abrir uma nova janela de 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 em container-developer-workshop em GoogleCloudPlatform no GitHub. Clone-o 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 um código no GKE e acessar os dados armazenados em um banco de dados do CloudSQL. O script de configuração abaixo prepara essa infraestrutura para você. O processo de provisionamento levará mais de 25 minutos. Aguarde a conclusão do script antes de prosseguir para a próxima seção.

./setup_with_cw.sh &

Cluster do Cloud Workstations

Abra o Cloud Workstations no Console do Cloud. Aguarde o cluster estar no status READY.

305e1a3d63ac7ff6.png

Criar configuração de estações de trabalho

Se a sessão do Cloud Shell tiver sido desconectada, clique em "Reconectar". e execute o comando gcloud cli para definir o ID do projeto. Substitua o ID do projeto de exemplo abaixo pelo ID do 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 levará dois minutos.

7a6af5aa2807a5f2.png

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

a53adeeac81a78c8.png

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

f21c216997746097.png

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

66a9fc8b20543e32.png

Iniciar estação de trabalho

Inicie e inicie a estação de trabalho.

c91bb69b61ec8635.png

Clique no ícone na barra de endereço para permitir cookies de terceiros. 1b8923e2943f9bc4.png

fcf9405b6957b7d7.png

Clique em "O site não está funcionando?".

36a84c0e2e3b85b.png

Clique em "Permitir cookies".

2259694328628fba.png

Depois que a estação de trabalho for iniciada, o ambiente de desenvolvimento integrado Code OSS vai aparecer. Clique em "Marcar como concluído" na página "Primeiros passos" um, o ambiente de desenvolvimento integrado da estação de trabalho

94874fba9b74cc22.png

3. Como criar um novo aplicativo inicial Java

Nesta seção, você vai criar um novo aplicativo Java Spring Boot do zero utilizando um aplicativo de exemplo fornecido pela spring.io. Abra um novo Terminal.

c31d48f2e4938c38.png

Clone o aplicativo de amostra

  1. 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

Se essa mensagem aparecer para você, clique no botão "Permitir" para copiar e colar na estação de trabalho.

58149777e5cc350a.png

  1. Descompactar o aplicativo
unzip sample-app.zip -d sample-app
  1. Abra o "sample-app". pasta
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

Adicionar spring-boot- DevTools e Jib

Para ativar as DevTools do Spring Boot, encontre e abra o pom.xml no explorador no seu editor. Em seguida, cole o seguinte código após a linha de descrição, que diz <description>Demo project for Spring Boot</description>

  1. Adicionar 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>
  1. Ativar jib-maven-plugin em pom.xml

Jib é uma ferramenta de conteinerização Java de código aberto do Google que permite que desenvolvedores Java criem contêineres usando as ferramentas Java que eles conhecem. O Jib é um criador de imagens de contêiner rápido e simples que processa todas as etapas de empacotamento do aplicativo em uma imagem de contêiner. Ele não exige que você escreva um Dockerfile ou tenha o Docker instalado, e está diretamente integrado ao Maven e ao Gradle.

Role para baixo no arquivo pom.xml e atualize a seção Build para incluir o plug-in Jib. Quando concluída, a seção de criação vai corresponder ao seguinte:

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 fornece ferramentas integradas para simplificar o desenvolvimento de contêineres. Nesta etapa, você inicializará o Skaffold, que criará automaticamente arquivos YAML de base do Kubernetes. O processo tenta identificar diretórios com definições de imagem de contêiner, como um Dockerfile, e depois cria um manifesto de implantação e serviço para cada um.

Execute o comando abaixo no terminal para iniciar o processo.

d869e0cd38e983d7.png

  1. Execute o seguinte comando no terminal
skaffold init --generate-manifests
  1. 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
  1. Digite 8080 na porta
  2. Digite y para salvar a configuração

Dois arquivos foram adicionados aos espaços de trabalho skaffold.yaml e deployment.yaml

Saída do Skaffold:

b33cc1e0c2077ab8.png

Atualizar o nome do app

Os valores padrão incluídos na configuração atualmente 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.

  1. Alterar entradas na configuração do Skaffold
  • Abrir skaffold.yaml
  • Selecione o nome da imagem definido atualmente como pom-xml-image
  • Clique com o botão direito do mouse e escolha "Alterar todas as ocorrências"
  • Digite o novo nome como demo-app
  1. Alterar entradas na configuração do Kubernetes
  • Abrir arquivo deployment.yaml
  • Selecione o nome da imagem definido atualmente como pom-xml-image
  • Clique com o botão direito do mouse e escolha "Alterar todas as ocorrências"
  • Digite o novo nome como demo-app

Ativar o modo de sincronização automática

Para facilitar uma experiência otimizada de recarga automática, use o recurso de sincronização fornecido pelo Jib. Nesta etapa, você vai configurar o Skaffold para utilizar esse recurso no processo de build.

Observe que a opção de "sincronizar" que você está configurando no Skaffold usa a "sincronização" do Spring Perfil que você configurou na etapa anterior, em que ativou o suporte a spring-dev-tools.

  1. Atualizar configuração do Skaffold

No arquivo skaffold.yaml, substitua toda a seção de build do arquivo pela especificação abaixo. 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 com o nome HelloController.java na pasta /src/main/java/com/example/springboot/.

a624f5dd0c477c09.png

Cole o seguinte conteúdo 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. Orientações sobre o processo de desenvolvimento

Nesta seção, você verá algumas etapas usando o plug-in do Cloud Code para aprender os processos básicos e validar a configuração do seu aplicativo inicial.

O Cloud Code se integra ao Skaffold para simplificar o processo de desenvolvimento. Quando você 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 fornecendo recursos tradicionais de depuração e hotsync para o desenvolvimento baseado em contêiner.

Faça login no Google Cloud

Clique no ícone do Cloud Code e selecione "Fazer login no Google Cloud":

1769afd39be372ff.png

Clique em "Continuar para fazer login".

923bb1c8f63160f9.png

Confira a resposta no terminal e abra o link:

517fdd579c34aa21.png

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

db99b345f7a8e72c.png

Selecione "Permitir":

a5376553c430ac84.png

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

6719421277b92eac.png

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

e9847cfe3fa8a2ce.png

Adicionar cluster do Kubernetes

  1. Adicionar um cluster

62a3b97bdbb427e5.png

  1. Selecione o Google Kubernetes Engine:

9577de423568bbaa.png

  1. Selecione o projeto.

c5202fcbeebcd41c.png

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

366cfd8bc27cd3ed.png

9d68532c9bc4a89b.png

Definir o ID do projeto atual usando a gcloud cli

Copie o ID do projeto deste laboratório na página do qwiklabs.

fcff2d10007ec5bc.png

Execute o comando gcloud cli 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:

f1c03d01b7ac112c.png

Depurar no Kubernetes

  1. No painel esquerdo na parte de baixo, selecione Cloud Code.

60b8e4e95868b561.png

  1. No painel que aparece em "SESSÕES DE DESENVOLVIMENTO", selecione "Depurar no Kubernetes".

Role para baixo se a opção não estiver visível.

7d30833d96632ca0.png

  1. Selecione "Sim" para usar o contexto atual.

a024a69b64de7e9e.png

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

faebabf372e3caf0.png

  1. Selecione Container Repository.

fabc6dce48bae1b4.png

  1. Selecione a guia Output no painel inferior para visualizar o progresso e as notificações
  2. Selecione "Kubernetes: Run/Debug - Detalhado". no menu suspenso do canal à direita, é possível ver mais detalhes e os registros das transmissões ao vivo dos contêineres.

86b44c59db58f8f3.png

Aguarde a implantação do aplicativo.

9f37706a752829fe.png

  1. Analise o aplicativo implantado no GKE no Console do Cloud.

6ad220e5d1980756.png

  1. Retorne à visualização simplificada selecionando "Kubernetes: Run/Debug". no menu suspenso da guia OUTPUT.
  2. Quando o build e os testes forem concluídos, a guia "Output" vai mostrar: Resource deployment/demo-app status completed successfully e um URL vai estar listado: "Forwarded URL from service demo-app: http://localhost:8080"
  3. No terminal do Cloud Code, passe o cursor sobre o URL na saída (http://localhost:8080) e, na dica de ferramenta exibida, selecione "Seguir link".

28c5539880194a8e.png

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

d67253ca16238f49.png

Usar pontos de interrupção

  1. Abra o aplicativo HelloController.java localizado em /src/main/java/com/example/springboot/HelloController.java
  2. Localize a instrução de retorno do caminho raiz que diz return String.format("Hello from your %s environment!", target);.
  3. 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 indicar que o ponto de interrupção está definido

5027dc6da2618a39.png

  1. Atualize 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

71acfb426623cec2.png

  1. Clique na seção de variáveis até encontrar "Target" variável.
  2. Observe o valor atual como "local".

a1160d2ed2bb5c82.png

  1. Clique duas vezes no nome da variável "target". e, no pop-up,

mude o valor para "Cloud Workstations".

e597a556a5c53f32.png

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

ec17086191770d0d.png

  1. Revise a resposta no navegador, que agora mostra o valor atualizado que você inseriu.

6698a9db9e729925.png

  1. Remova o ponto de interrupção clicando no indicador vermelho à esquerda do número da linha. Isso vai impedir que o código interrompa a execução nessa linha conforme você avançar no laboratório.

Fazer recarga automática

  1. Altere a instrução para retornar um valor diferente, como "Hello from %s Code"
  2. O arquivo é salvo e sincronizado automaticamente nos contêineres remotos no GKE
  3. Atualize seu navegador para conferir os resultados atualizados.
  4. Pare a sessão de depuração clicando no quadrado vermelho na barra de ferramentas de depuração

a541f928ec8f430e.png c2752bb28d82af86.png

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

984eb2fa34867d70.png

5. Como desenvolver um serviço CRUD simples

Neste ponto, o aplicativo está totalmente configurado para o desenvolvimento conteinerizado, e você já conheceu o fluxo de trabalho básico de desenvolvimento com o Cloud Code. Nas seções a seguir, você vai praticar o que aprendeu adicionando endpoints do 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. Verifique se as dependências estão disponíveis adicionando o seguinte no arquivo pom.xl

  1. Abra o arquivo pom.xml e adicione o seguinte comando à 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>

Serviço REST de código

Quote.java

Crie um arquivo com o nome Quote.java em /src/main/java/com/example/springboot/ e copie o código abaixo. Isso define o modelo Entity 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 código a seguir

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 manter os dados. A classe estende a interface JPARepository do Spring e permite a criação de um 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 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 parametrizada do Spring 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 o Database Migration

Criar pastas db/migration em src/main/resources

Crie um arquivo SQL: V1__create_quotes_table.sql

Cole o seguinte conteúdo 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 ao 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 aplicativo é 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 clicando em SQL no menu de navegação do console do Google Cloud. Altere o valor!
  • DB_USER e DB_PASS: conforme definido na configuração da instância do CloudSQL, armazenados como um secret no GCP

Atualize o 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.

fd63c0aede14beba.png

Implante e valide o aplicativo

  1. No painel da parte de baixo do editor do Cloud Shell, selecione Cloud Code e, depois, Depuração no Kubernetes na parte de cima da tela.

33a5cf41aae91adb.png

  1. Quando o build e os testes forem concluídos, a guia "Output" vai mostrar: Resource deployment/demo-app status completed successfully e um URL vai estar listado: "Forwarded URL from service demo-app: http://localhost:8080". Às vezes, a porta pode ser diferente, como 8081. Nesse caso, defina o valor apropriado. Defina o valor de URL no terminal
export URL=localhost:8080
  1. Ver cotações aleatórias

No Terminal, execute o comando abaixo várias vezes no endpoint de aspas aleatórias. Observar chamadas repetidas retornando aspas diferentes

curl $URL/random-quote | jq
  1. Adicionar cotação

Crie uma nova citação, com id=6 usando o comando listado abaixo, e observe a solicitação ser ecoada de volta.

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
  1. Excluir uma cotação

Agora, exclua a citação que você adicionou 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
  1. Erro de servidor

Ocorre um estado de erro ao executar a última solicitação novamente após a entrada ter sido excluída

curl -v -X DELETE $URL/quotes/6

Observe que 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ê trabalhará com a classe quoteController.

  1. Abrir src/main/java/com/example/springboot/QuoteController.java
  2. Encontre o método deleteQuote().
  3. Encontre a linha: Optional<Quote> quote = quoteRepository.findById(id);
  4. Defina um ponto de interrupção nessa linha clicando no espaço em branco à esquerda do número da linha.
  5. Um indicador vermelho será exibido, indicando que o ponto de interrupção está definido
  6. Execute o comando delete novamente.
curl -v -X DELETE $URL/quotes/6
  1. Volte à visualização de depuração clicando no ícone na coluna à esquerda.
  2. Observe que a linha de depuração parou na classe quoteController.
  3. No depurador, clique no ícone step over b814d39b2e5f3d9e.png
  4. Observe que um código retorna ao cliente um Erro interno do servidor HTTP 500, 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 retornar um código de status HTTP 404 não encontrado.

Corrija o erro.

  1. Com a sessão de depuração ainda em execução, conclua a solicitação pressionando o botão "continue" no painel de controle de depuração.
  2. Em seguida, mude o bloco else para 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);
        }
    }
  1. Executar novamente o comando de exclusão
curl -v -X DELETE $URL/quotes/6
  1. Percorra o depurador e observe o erro HTTP 404 Not Found 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
  1. Pare a sessão de depuração clicando no quadrado vermelho na barra de ferramentas de depuração

12bc3c82f63dcd8a.png

6f19c0f855832407.png

6. Parabéns

Parabéns! Neste laboratório, você criou um novo aplicativo Java do zero e o configurou para funcionar efetivamente com contêineres. Em seguida, você implantou e depurou seu aplicativo em um cluster remoto do GKE seguindo o mesmo fluxo de desenvolvedor encontrado nas pilhas de aplicativos tradicionais.

O que você aprendeu

  • Desenvolvimento do InnerLoop com o Cloud Workstations
  • Como criar um novo aplicativo inicial Java
  • Orientações sobre o processo de desenvolvimento
  • Como desenvolver um serviço REST CRUD simples
  • Aplicativo de depuração no cluster do GKE
  • Como conectar o aplicativo ao banco de dados do CloudSQL