Sviluppo con Cloud Workstations e Cloud Code

1. Panoramica

Questo lab mostra funzionalità e capacità progettate per semplificare il flusso di lavoro di sviluppo per gli ingegneri software incaricati di sviluppare applicazioni Java in un ambiente containerizzato. Il tipico sviluppo di container richiede all'utente di comprendere i dettagli dei container e il processo di compilazione dei container. Inoltre, gli sviluppatori in genere devono interrompere il flusso di lavoro, uscire dall'IDE per testare ed eseguire il debug delle applicazioni in ambienti remoti. Con gli strumenti e le tecnologie menzionati in questo tutorial, gli sviluppatori possono lavorare in modo efficace con le applicazioni in contenitori senza uscire dall'IDE.

Cosa imparerai a fare

In questo lab imparerai i metodi per sviluppare con i container in Google Cloud, tra cui:

  • Sviluppo InnerLoop con Cloud Workstations
  • Creazione di una nuova applicazione Java iniziale
  • Esaminare il processo di sviluppo
  • Sviluppo di un semplice servizio REST CRUD
  • Eseguire il debug dell'applicazione sul cluster GKE
  • Connessione dell'applicazione al database Cloud SQL

58a4cdd3ed7a123a.png

2. Configurazione e requisiti

Configurazione dell'ambiente autonomo

  1. Accedi alla console Google Cloud e crea un nuovo progetto o riutilizzane uno esistente. Se non hai ancora un account Gmail o Google Workspace, devi crearne uno.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Il nome del progetto è il nome visualizzato per i partecipanti a questo progetto. È una stringa di caratteri non utilizzata dalle API di Google. Puoi aggiornarlo in qualsiasi momento.
  • L'ID progetto è univoco in tutti i progetti Google Cloud ed è immutabile (non può essere modificato dopo l'impostazione). La console Cloud genera automaticamente una stringa univoca, di solito non ti interessa di cosa si tratta. Nella maggior parte dei codelab, devi fare riferimento all'ID progetto (in genere è identificato come PROJECT_ID). Se non ti piace l'ID generato, puoi generarne un altro casuale. In alternativa, puoi provare a crearne uno e vedere se è disponibile. Non può essere modificato dopo questo passaggio e rimarrà per tutta la durata del progetto.
  • Per tua informazione, esiste un terzo valore, un numero di progetto, utilizzato da alcune API. Scopri di più su tutti e tre questi valori nella documentazione.
  1. Successivamente, devi abilitare la fatturazione in Cloud Console per utilizzare le risorse/API Cloud. L'esecuzione di questo codelab non dovrebbe costare molto, se non nulla. Per arrestare le risorse in modo da non incorrere in costi di fatturazione al termine di questo tutorial, puoi eliminare le risorse che hai creato o l'intero progetto. I nuovi utenti di Google Cloud possono beneficiare del programma prova senza costi di 300$.

Avvia l'editor di Cloud Shell

Questo lab è stato progettato e testato per l'utilizzo con l'editor di Google Cloud Shell. Per accedere all'editor:

  1. accedi al tuo progetto Google all'indirizzo https://console.cloud.google.com.
  2. Nell'angolo in alto a destra, fai clic sull'icona dell'editor Cloud Shell.

8560cc8d45e8c112.png

  1. Si aprirà un nuovo riquadro nella parte inferiore della finestra.
  2. Fai clic sul pulsante Apri editor.

9e504cb98a6a8005.png

  1. L'editor si aprirà con un explorer a destra e l'editor nell'area centrale
  2. Nella parte inferiore dello schermo dovrebbe essere disponibile anche un riquadro del terminale
  3. Se il terminale NON è aperto, utilizza la combinazione di tasti "Ctrl + `" per aprire una nuova finestra del terminale.

Configura gcloud

In Cloud Shell, imposta l'ID progetto e la regione in cui vuoi eseguire il deployment dell'applicazione. Salvali come variabili 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)')

Clona il codice sorgente

Il codice sorgente di questo lab si trova in container-developer-workshop in GoogleCloudPlatform su GitHub. Clonalo con il comando riportato di seguito, quindi passa alla directory.

git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot

Esegui il provisioning dell'infrastruttura utilizzata in questo lab

In questo lab eseguirai il deployment del codice in GKE e accederai ai dati archiviati in un database CloudSQL. Lo script di configurazione riportato di seguito prepara questa infrastruttura per te. Il processo di provisioning richiederà più di 25 minuti. Attendi il completamento dello script prima di passare alla sezione successiva.

./setup_with_cw.sh &

Cluster Cloud Workstations

Apri Cloud Workstations in Cloud Console. Attendi che il cluster sia nello stato READY.

305e1a3d63ac7ff6.png

Crea configurazione workstation

Se la sessione di Cloud Shell è stata disconnessa, fai clic su "Riconnetti" e poi esegui il comando gcloud CLI per impostare l'ID progetto. Prima di eseguire il comando, sostituisci l'ID progetto di esempio riportato di seguito con il tuo ID progetto Qwiklabs.

gcloud config set project qwiklabs-gcp-project-id

Esegui lo script riportato di seguito nel terminale per creare la configurazione di Cloud Workstations.

cd ~/container-developer-workshop/labs/spring-boot
./workstation_config_setup.sh

Verifica i risultati nella sezione Configurazioni. Il passaggio allo stato PRONTO richiederà 2 minuti.

7a6af5aa2807a5f2.png

Apri Cloud Workstations nella console e crea una nuova istanza.

a53adeeac81a78c8.png

Modifica il nome in my-workstation e seleziona la configurazione esistente: codeoss-java.

f21c216997746097.png

Verifica i risultati nella sezione Workstation.

66a9fc8b20543e32.png

Avvia workstation

Avvia la workstation.

c91bb69b61ec8635.png

Consenti i cookie di terze parti facendo clic sull'icona nella barra degli indirizzi. 1b8923e2943f9bc4.png

fcf9405b6957b7d7.png

Fai clic su "Il sito non funziona?".

36a84c0e2e3b85b.png

Fai clic su "Consenti cookie".

2259694328628fba.png

Una volta avviata la workstation, viene visualizzato l'IDE Code OSS. Fai clic su "Mark Done" (Segna come completato) nella pagina Getting Started (Guida introduttiva) dell'IDE della workstation.

94874fba9b74cc22.png

3. Creazione di una nuova applicazione Java iniziale

In questa sezione creerai una nuova applicazione Java Spring Boot da zero utilizzando un'applicazione di esempio fornita da spring.io. Apri un nuovo terminale.

c31d48f2e4938c38.png

Clona l'applicazione di esempio

  1. Creare un'applicazione iniziale
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 visualizzi questo messaggio, fai clic sul pulsante Consenti per poter copiare e incollare nella workstation.

58149777e5cc350a.png

  1. Decomprimi l'applicazione
unzip sample-app.zip -d sample-app
  1. Apri la cartella "sample-app".
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

Aggiungi spring-boot-devtools e Jib

Per attivare Spring Boot DevTools, trova e apri il file pom.xml dall'explorer nell'editor. Poi incolla il seguente codice dopo la riga descrittiva che riporta <description>Demo project for Spring Boot</description>

  1. Aggiungi spring-boot-devtools in pom.xml

Apri il file pom.xml nella directory root del progetto. Aggiungi la seguente configurazione dopo la voce 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. Abilita jib-maven-plugin in pom.xml

Jib è uno strumento open source di containerizzazione Java di Google che consente agli sviluppatori Java di creare container utilizzando gli strumenti Java che conoscono. Jib è un builder di immagini container veloce e semplice che gestisce tutti i passaggi di pacchettizzazione dell'applicazione in un'immagine container. Non richiede di scrivere un Dockerfile o di installare Docker ed è integrato direttamente in Maven e Gradle.

Scorri verso il basso nel file pom.xml e aggiorna la sezione Build per includere il plug-in Jib. Una volta completata, la sezione di creazione dovrebbe corrispondere a quella riportata di seguito.

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>

Genera manifest

Skaffold fornisce strumenti integrati per semplificare lo sviluppo dei container. In questo passaggio inizializzerai Skaffold, che creerà automaticamente i file YAML di base di Kubernetes. Il processo tenta di identificare le directory con definizioni di immagini container, come un Dockerfile, e poi crea un manifest di deployment e servizio per ciascuna.

Esegui il comando riportato di seguito nel terminale per iniziare la procedura.

d869e0cd38e983d7.png

  1. Esegui questo comando nel terminale
skaffold init --generate-manifests
  1. Quando richiesto:
  • Usa le frecce per spostare il cursore su Jib Maven Plugin.
  • Premi la barra spaziatrice per selezionare l'opzione.
  • Premi Invio per continuare
  1. Inserisci 8080 per la porta.
  2. Inserisci y per salvare la configurazione.

Due file vengono aggiunti allo spazio di lavoro skaffold.yaml e deployment.yaml

Output di Skaffold:

b33cc1e0c2077ab8.png

Aggiorna il nome dell'app

I valori predefiniti inclusi nella configurazione non corrispondono attualmente al nome dell'applicazione. Aggiorna i file in modo che facciano riferimento al nome dell'applicazione anziché ai valori predefiniti.

  1. Modificare le voci nella configurazione di Skaffold
  • Apri skaffold.yaml
  • Seleziona il nome dell'immagine attualmente impostata come pom-xml-image
  • Fai clic con il tasto destro del mouse e scegli Modifica tutte le occorrenze.
  • Digita il nuovo nome come demo-app
  1. Modificare le voci nella configurazione di Kubernetes
  • Apri il file deployment.yaml
  • Seleziona il nome dell'immagine attualmente impostata come pom-xml-image
  • Fai clic con il tasto destro del mouse e scegli Modifica tutte le occorrenze.
  • Digita il nuovo nome come demo-app

Attivare la modalità Sincronizzazione automatica

Per facilitare un'esperienza di ricarica rapida ottimizzata, utilizzerai la funzionalità di sincronizzazione fornita da Jib. In questo passaggio configurerai Skaffold per utilizzare questa funzionalità nel processo di compilazione.

Tieni presente che il profilo "sync" che stai configurando nella configurazione di Skaffold utilizza il profilo "sync" di Spring che hai configurato nel passaggio precedente, in cui hai attivato il supporto per spring-dev-tools.

  1. Aggiorna la configurazione di Skaffold

Nel file skaffold.yaml, sostituisci l'intera sezione di compilazione del file con la seguente specifica. Non modificare altre sezioni del 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

Aggiungere una route predefinita

Crea un file denominato HelloController.java nella cartella /src/main/java/com/example/springboot/.

a624f5dd0c477c09.png

Incolla i seguenti contenuti nel file per creare una route HTTP predefinita.

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. Esaminare il processo di sviluppo

In questa sezione, seguirai alcuni passaggi utilizzando il plug-in Cloud Code per apprendere le procedure di base e convalidare la configurazione e la configurazione dell'applicazione iniziale.

Cloud Code si integra con Skaffold per semplificare il processo di sviluppo. Quando esegui il deployment in GKE nei passaggi successivi, Cloud Code e Skaffold creeranno automaticamente l'immagine container, eseguiranno il push in Container Registry e poi eseguiranno il deployment dell'applicazione in GKE. Questa operazione avviene dietro le quinte, astraendo i dettagli dal flusso dello sviluppatore. Cloud Code migliora anche il processo di sviluppo fornendo funzionalità di debug e sincronizzazione rapida tradizionali allo sviluppo basato su container.

Accedi a Google Cloud

Fai clic sull'icona di Cloud Code e seleziona "Accedi a Google Cloud":

1769afd39be372ff.png

Fai clic su "Procedi all'accesso".

923bb1c8f63160f9.png

Controlla l'output nel terminale e apri il link:

517fdd579c34aa21.png

Accedi con le credenziali degli studenti Qwiklabs.

db99b345f7a8e72c.png

Seleziona "Consenti":

a5376553c430ac84.png

Copia il codice di verifica e torna alla scheda Workstation.

6719421277b92eac.png

Incolla il codice di verifica e premi Invio.

e9847cfe3fa8a2ce.png

Aggiungi cluster Kubernetes

  1. Aggiungere un cluster

62a3b97bdbb427e5.png

  1. Seleziona Google Kubernetes Engine:

9577de423568bbaa.png

  1. Seleziona il progetto.

c5202fcbeebcd41c.png

  1. Seleziona "quote-cluster" creato nella configurazione iniziale.

366cfd8bc27cd3ed.png

9d68532c9bc4a89b.png

Imposta l'ID progetto corrente utilizzando gcloud CLI

Copia l'ID progetto per questo lab dalla pagina di Qwiklabs.

fcff2d10007ec5bc.png

Esegui il comando gcloud CLI per impostare l'ID progetto. Sostituisci l'ID progetto di esempio prima di eseguire il comando.

gcloud config set project qwiklabs-gcp-project-id

Esempio di output:

f1c03d01b7ac112c.png

Esegui il debug su Kubernetes

  1. Nel riquadro a sinistra in basso, seleziona Cloud Code.

60b8e4e95868b561.png

  1. Nel riquadro visualizzato in SESSIONI DI SVILUPPO, seleziona Debug su Kubernetes.

Scorri verso il basso se l'opzione non è visibile.

7d30833d96632ca0.png

  1. Seleziona "Sì" per utilizzare il contesto attuale.

a024a69b64de7e9e.png

  1. Seleziona "quote-cluster" creato durante la configurazione iniziale.

faebabf372e3caf0.png

  1. Seleziona Container Repository.

fabc6dce48bae1b4.png

  1. Seleziona la scheda Output nel riquadro inferiore per visualizzare l'avanzamento e le notifiche.
  2. Seleziona "Kubernetes: Run/Debug - Detailed" (Kubernetes: esecuzione/debug - dettagliato) nel menu a discesa del canale a destra per visualizzare ulteriori dettagli e i log in streaming live dai container.

86b44c59db58f8f3.png

Attendi il deployment dell'applicazione.

9f37706a752829fe.png

  1. Esamina l'applicazione di cui è stato eseguito il deployment su GKE in Cloud Console.

6ad220e5d1980756.png

  1. Torna alla visualizzazione semplificata selezionando "Kubernetes: Run/Debug" (Kubernetes: esegui/debug) dal menu a discesa nella scheda OUTPUT.
  2. Al termine della build e dei test, nella scheda Output viene visualizzato il messaggio Resource deployment/demo-app status completed successfully e viene elencato un URL: "Forwarded URL from service demo-app: http://localhost:8080"
  3. Nel terminale di Cloud Code, passa il mouse sopra l'URL nell'output (http://localhost:8080), poi seleziona Segui link nel suggerimento visualizzato.

28c5539880194a8e.png

Si aprirà una nuova scheda e vedrai l'output riportato di seguito:

d67253ca16238f49.png

Utilizzare i punti di interruzione

  1. Apri l'applicazione HelloController.java che si trova in /src/main/java/com/example/springboot/HelloController.java
  2. Individua la dichiarazione return per il percorso principale che indica return String.format("Hello from your %s environment!", target);
  3. Aggiungi un punto di interruzione a quella riga facendo clic sullo spazio vuoto a sinistra del numero di riga. Viene visualizzato un indicatore rosso per indicare che il punto di interruzione è impostato

5027dc6da2618a39.png

  1. Ricarica il browser e nota che il debugger interrompe il processo nel punto di interruzione e ti consente di esaminare le variabili e lo stato dell'applicazione in esecuzione in remoto in GKE

71acfb426623cec2.png

  1. Fai clic nella sezione delle variabili fino a trovare la variabile "Target".
  2. Osserva il valore corrente "local"

a1160d2ed2bb5c82.png

  1. Fai doppio clic sul nome della variabile "target" e nel popup,

modifica il valore in "Cloud Workstations"

e597a556a5c53f32.png

  1. Fai clic sul pulsante Continua nel pannello di controllo del debug

ec17086191770d0d.png

  1. Controlla la risposta nel browser, che ora mostra il valore aggiornato appena inserito.

6698a9db9e729925.png

  1. Rimuovi il punto di interruzione facendo clic sull'indicatore rosso a sinistra del numero di riga. In questo modo, il codice non interromperà l'esecuzione a questa riga man mano che avanzi in questo lab.

Ricaricamento rapido

  1. Modifica l'istruzione in modo che restituisca un valore diverso, ad esempio "Hello from %s Code"
  2. Il file viene salvato e sincronizzato automaticamente nei container remoti in GKE
  3. Aggiorna il browser per visualizzare i risultati aggiornati.
  4. Interrompi la sessione di debug facendo clic sul quadrato rosso nella barra degli strumenti di debug.

a541f928ec8f430e.png c2752bb28d82af86.png

Seleziona "Sì, libera spazio dopo ogni esecuzione".

984eb2fa34867d70.png

5. Sviluppo di un semplice servizio REST CRUD

A questo punto, l'applicazione è completamente configurata per lo sviluppo containerizzato e hai esaminato il flusso di lavoro di sviluppo di base con Cloud Code. Nelle sezioni seguenti metterai in pratica ciò che hai imparato aggiungendo endpoint di servizio REST che si connettono a un database gestito in Google Cloud.

Configura le dipendenze

Il codice dell'applicazione utilizza un database per archiviare i dati del servizio REST. Assicurati che le dipendenze siano disponibili aggiungendo quanto segue in pom.xl

  1. Apri il file pom.xml e aggiungi quanto segue alla sezione delle dipendenze della configurazione

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>

Servizio REST di codice

Quote.java

Crea un file denominato Quote.java in /src/main/java/com/example/springboot/ e copia il codice riportato di seguito. Definisce il modello di entità per l'oggetto Quote utilizzato nell'applicazione.

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

Crea un file chiamato QuoteRepository.java in src/main/java/com/example/springboot e copia il seguente codice

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();
}

Questo codice utilizza JPA per rendere persistenti i dati. La classe estende l'interfaccia Spring JPARepository e consente la creazione di codice personalizzato. Nel codice che hai aggiunto, è presente un metodo personalizzato findRandomQuote.

QuoteController.java

Per esporre l'endpoint per il servizio, una classe QuoteController fornirà questa funzionalità.

Crea un file denominato QuoteController.java in src/main/java/com/example/springboot e copia i seguenti contenuti

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);
        }
    }

}

Aggiungere configurazioni del database

application.yaml

Aggiungi la configurazione per il database di backend a cui accede il servizio. Modifica (o crea se non presente) il file application.yaml in src/main/resources e aggiungi una configurazione Spring parametrizzata per il 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

Aggiungi migrazione del database

Crea cartelle db/migration in src/main/resources

Crea un file SQL: V1__create_quotes_table.sql

Incolla i seguenti contenuti nel 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

Le seguenti aggiunte al file deployment.yaml consentono all'applicazione di connettersi alle istanze Cloud SQL.

  • TARGET: configura la variabile per indicare l'ambiente in cui viene eseguita l'app
  • SPRING_PROFILES_ACTIVE: mostra il profilo Spring attivo, che verrà configurato su cloud-dev
  • DB_HOST: l'IP privato per il database, annotato durante la creazione dell'istanza del database o facendo clic su SQL nel menu di navigazione di console Google Cloud. Modifica il valore.
  • DB_USER e DB_PASS, come impostati nella configurazione dell'istanza Cloud SQL, memorizzati come secret in GCP

Aggiorna il file deployment.yaml con i contenuti riportati di seguito.

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

Sostituisci il valore DB_HOST con l'indirizzo del tuo database eseguendo i seguenti comandi nel terminale:

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

Apri deployment.yaml e verifica che il valore DB_HOST sia stato aggiornato con l'IP dell'istanza.

fd63c0aede14beba.png

Esegui il deployment e convalida l'applicazione

  1. Nel riquadro in basso dell'editor di Cloud Shell, seleziona Cloud Code, quindi seleziona Debug su Kubernetes nella parte superiore dello schermo.

33a5cf41aae91adb.png

  1. Al termine della build e dei test, nella scheda Output viene visualizzato il messaggio Resource deployment/demo-app status completed successfully e un URL: "Forwarded URL from service demo-app: http://localhost:8080". Tieni presente che a volte la porta potrebbe essere diversa, ad esempio 8081. In caso affermativo, imposta il valore appropriato. Imposta il valore dell'URL nel terminale
export URL=localhost:8080
  1. Visualizzare citazioni casuali

Dal terminale, esegui più volte il comando riportato di seguito sull'endpoint random-quote. Osserva le chiamate ripetute che restituiscono preventivi diversi

curl $URL/random-quote | jq
  1. Aggiungere una citazione

Crea una nuova citazione con id=6 utilizzando il comando elencato di seguito e osserva la richiesta che viene restituita

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. Eliminare un preventivo

Ora elimina la citazione che hai appena aggiunto con il metodo di eliminazione e osserva un codice di risposta HTTP/1.1 204.

curl -v -X DELETE $URL/quotes/6
  1. Errore del server

Visualizzare uno stato di errore eseguendo di nuovo l'ultima richiesta dopo che la voce è già stata eliminata

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

Nota che la risposta restituisce un HTTP:500 Internal Server Error.

Esegui il debug dell'applicazione

Nella sezione precedente hai riscontrato uno stato di errore nell'applicazione quando hai tentato di eliminare una voce che non era presente nel database. In questa sezione imposterai un punto di interruzione per individuare il problema. L'errore si è verificato nell'operazione DELETE, quindi lavorerai con la classe QuoteController.

  1. Apri src/main/java/com/example/springboot/QuoteController.java
  2. Trova il metodo deleteQuote()
  3. Trova la riga: Optional<Quote> quote = quoteRepository.findById(id);
  4. Imposta un punto di interruzione su quella riga facendo clic sullo spazio vuoto a sinistra del numero di riga.
  5. Verrà visualizzato un indicatore rosso che indica che il punto di interruzione è impostato.
  6. Esegui di nuovo il comando delete.
curl -v -X DELETE $URL/quotes/6
  1. Torna alla visualizzazione di debug facendo clic sull'icona nella colonna a sinistra.
  2. Osserva la riga di debug interrotta nella classe QuoteController.
  3. Nel debugger, fai clic sull'icona step over b814d39b2e5f3d9e.png.
  4. Tieni presente che un codice restituisce un errore interno del server HTTP 500 al client, il che non è l'ideale.
   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

Aggiorna il codice

Il codice non è corretto e il blocco else deve essere sottoposto a refactoring per restituire un codice di stato HTTP 404 pagina non trovata.

Correggi l'errore.

  1. Con la sessione di debug ancora in esecuzione, completa la richiesta premendo il pulsante "Continua" nel pannello di controllo del debug.
  2. Poi modifica il blocco else con il codice:
       else {
                return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
            }

Il metodo dovrebbe avere il seguente aspetto

@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. Esegui di nuovo il comando di eliminazione
curl -v -X DELETE $URL/quotes/6
  1. Esegui il debug e osserva l'errore HTTP 404 Not Found restituito al chiamante.
   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. Interrompi la sessione di debug facendo clic sul quadrato rosso nella barra degli strumenti di debug.

12bc3c82f63dcd8a.png

6f19c0f855832407.png

6. Complimenti

Complimenti! In questo lab hai creato una nuova applicazione Java da zero e l'hai configurata per funzionare in modo efficace con i container. Successivamente, hai eseguito il deployment e il debug dell'applicazione in un cluster GKE remoto seguendo lo stesso flusso di sviluppo presente negli stack di applicazioni tradizionali.

Cosa hai imparato

  • Sviluppo InnerLoop con Cloud Workstations
  • Creazione di una nuova applicazione Java iniziale
  • Esaminare il processo di sviluppo
  • Sviluppare un semplice servizio REST CRUD
  • Eseguire il debug dell'applicazione sul cluster GKE
  • Connessione dell'applicazione al database Cloud SQL