Sviluppo con Cloud Workstations e Cloud Code

1. Panoramica

Questo lab illustra caratteristiche e funzionalità progettate per semplificare il flusso di lavoro di sviluppo per i tecnici del software incaricati di sviluppare applicazioni Java in un ambiente containerizzato. Lo sviluppo tipico dei container richiede che l'utente comprenda i dettagli dei container e del processo di compilazione dei container. Inoltre, gli sviluppatori in genere devono interrompere il flusso, uscendo dal loro IDE per testare ed eseguire il debug delle loro applicazioni in ambienti remoti. Con gli strumenti e le tecnologie citati in questo tutorial, gli sviluppatori possono lavorare in modo efficace con le applicazioni containerizzate senza uscire dall'IDE.

Cosa imparerai a fare

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

  • Sviluppo di InnerLoop con Cloud Workstations
  • Creazione di una nuova applicazione iniziale Java
  • Panoramica del processo di sviluppo
  • Sviluppo di un servizio REST CRUD semplice
  • Debug dell'applicazione su cluster GKE
  • Connessione dell'applicazione al database Cloud SQL

58a4cdd3ed7a123a.png

2. Configurazione e requisiti

Configurazione dell'ambiente autogestito

  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 progetto è il nome visualizzato dei partecipanti del progetto. Si tratta di una stringa di caratteri non utilizzata dalle API di Google. Puoi aggiornarla in qualsiasi momento.
  • L'ID progetto è univoco in tutti i progetti Google Cloud ed è immutabile (non può essere modificato dopo essere stato impostato). La console Cloud genera automaticamente una stringa univoca; di solito non ti importa cosa sia. Nella maggior parte dei codelab, dovrai fare riferimento all'ID progetto (in genere è identificato come PROJECT_ID). Se l'ID generato non ti soddisfa, puoi generarne un altro casuale. In alternativa, puoi provarne una personalizzata per verificare se è disponibile. Non può essere modificato dopo questo passaggio e rimarrà per tutta la durata del progetto.
  • Per informazione, c'è un terzo valore, un numero di progetto, utilizzato da alcune API. Scopri di più su tutti e tre questi valori nella documentazione.
  1. Successivamente, dovrai abilitare la fatturazione nella console Cloud per utilizzare risorse/API Cloud. Eseguire questo codelab non dovrebbe costare molto. Per arrestare le risorse in modo da non incorrere in fatturazione oltre questo tutorial, puoi eliminare le risorse che hai creato o eliminare l'intero progetto. I nuovi utenti di Google Cloud sono idonei al programma prova senza costi di 300$.

Avvia editor Cloudshell

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 di 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 apre 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. Salvale 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 nel container-developer-workshop in GoogleCloudPlatform su GitHub. Clonalo con il comando seguente, 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 su GKE e accedi ai dati archiviati in un database Cloud SQL. Lo script di configurazione riportato di seguito prepara l'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 nella console Cloud. Attendi che il cluster si trovi nello stato READY.

305e1a3d63ac7ff6.png

Crea configurazione workstation

Se la sessione di Cloud Shell è stata disconnessa, fai clic su "Riconnetti" quindi esegui il comando gcloud cli per impostare l'ID progetto. Sostituisci l'ID progetto di esempio riportato di seguito con il tuo ID progetto qwiklabs prima di eseguire il comando.

gcloud config set project qwiklabs-gcp-project-id

Esegui lo script seguente 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. Sono necessari 2 minuti per passare allo stato PRONTO.

7a6af5aa2807a5f2.png

Apri Cloud Workstations nella console e crea una nuova istanza.

a53adeeac81a78c8.png

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

f21c216997746097.png

Verifica i risultati nella sezione Workstations.

66a9fc8b20543e32.png

Avvia workstation

Avvia e 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 "Sito non funzionante?".

36a84c0e2e3b85b.png

Fai clic su "Accetta i cookie".

2259694328628fba.png

Una volta avviata la workstation, verrà visualizzato l'IDE Code OSS. Fai clic su "Segna come completato". nella pagina Getting Started (Per iniziare) l'IDE

94874fba9b74cc22.png

3. Creazione di una nuova applicazione iniziale Java

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

c31d48f2e4938c38.png

clona l'applicazione di esempio

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

Fai clic sul pulsante Consenti se viene visualizzato questo messaggio per copiare e incollare i dati nella workstation.

58149777e5cc350a.png

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

Aggiungi Spring-boot-devtools e Fiocco

Per abilitare i DevTools Spring Boot, individua e apri il file pom.xml dall'Explorer nel tuo editor. Incolla il seguente codice dopo la riga descrittiva <description>Demo project for Spring Boot</description>

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

Apri pom.xml nella directory principale 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. Attiva jib-maven-plugin in pom.xml

Jib è uno strumento di containerizzazione Java open source di Google che consente agli sviluppatori Java di creare container utilizzando gli strumenti Java che conoscono. Jib è uno strumento per la creazione di immagini container semplice e veloce che gestisce tutti i passaggi per pacchettizzare l'applicazione in un'immagine container. Non richiede la scrittura di un Dockerfile o l'installazione di un Dockerfile 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 relativa alla build dovrebbe corrispondere alla seguente.

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 inizializzarai Skaffold, che creerà automaticamente i file YAML Kubernetes di base. Il processo tenta di identificare le directory con definizioni di immagini container, come un Dockerfile, quindi crea un manifest di deployment e di servizio per ciascuna.

Esegui il comando seguente nel terminale per iniziare il processo.

d869e0cd38e983d7.png

  1. Esegui questo comando nel terminale
skaffold init --generate-manifests
  1. Quando richiesto:
  • Utilizza 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 Skaffold:

b33cc1e0c2077ab8.png

Aggiorna il nome dell'app

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

  1. Modifica le voci nella configurazione Skaffold
  • Apri skaffold.yaml
  • Seleziona il nome dell'immagine attualmente impostato su 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. Modifica le voci nella configurazione di Kubernetes
  • Apri file deployment.yaml
  • Seleziona il nome dell'immagine attualmente impostato su pom-xml-image
  • Fai clic con il tasto destro del mouse e scegli Modifica tutte le occorrenze
  • Digita il nuovo nome come demo-app.

Attiva modalità di sincronizzazione automatica

Per facilitare un'esperienza di ricarica rapida ottimizzata, utilizzerai la funzionalità di sincronizzazione fornita da Jib. In questo passaggio configurerai Skaffold in modo che utilizzi questa funzionalità nel processo di creazione.

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

  1. Aggiorna configurazione Skaffold

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

Aggiungi una route predefinita

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

a624f5dd0c477c09.png

Incolla i contenuti seguenti 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. Panoramica del processo di sviluppo

In questa sezione analizzerai alcuni passaggi utilizzando il plug-in Cloud Code per apprendere i processi di base e convalidare la configurazione della tua applicazione iniziale.

Cloud Code si integra con Skaffold per semplificare il processo di sviluppo. Quando esegui il deployment su GKE nei passaggi seguenti, Cloud Code e Skaffold creano automaticamente l'immagine container, ne eseguono il push a un Container Registry e quindi il deployment dell'applicazione su GKE. Ciò avviene dietro le quinte, astraendo i dettagli dal flusso degli sviluppatori. Cloud Code migliora inoltre il processo di sviluppo fornendo funzionalità tradizionali di debug e sincronizzazione a caldo 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 per accedere".

923bb1c8f63160f9.png

Controlla l'output nel terminale e apri il link:

517fdd579c34aa21.png

Accedi con le tue credenziali Qwiklabs per studenti.

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. Aggiungi un cluster

62a3b97bdbb427e5.png

  1. Seleziona Google Kubernetes Engine:

9577de423568bbaa.png

  1. Seleziona progetto.

c5202fcbeebcd41c.png

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

366cfd8bc27cd3ed.png

9d68532c9bc4a89b.png

Imposta l'ID progetto corrente utilizzando l'interfaccia a riga di comando gcloud

Copia l'ID progetto per questo lab dalla pagina 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

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

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: Esegui/Debug - Dettagliato" nel menu a discesa del canale a destra per visualizzare ulteriori dettagli e i log trasmessi in live streaming dai container

86b44c59db58f8f3.png

Attendi che venga eseguito il deployment dell'applicazione.

9f37706a752829fe.png

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

6ad220e5d1980756.png

  1. Torna alla visualizzazione semplificata selezionando "Kubernetes: Run/Debug" dal menu a discesa della scheda OUTPUT.
  2. Una volta completati la build e i test, la scheda Output riporta il seguente messaggio: Resource deployment/demo-app status completed successfully e un URL: "Forwarded URL from service demo-app: http://localhost:8080"
  3. Nel terminale Cloud Code, passa il mouse sopra l'URL nell'output (http://localhost:8080), quindi nella descrizione comando visualizzata seleziona Segui link.

28c5539880194a8e.png

La nuova scheda verrà aperta e vedrai l'output di seguito:

d67253ca16238f49.png

Utilizza i punti di interruzione

  1. Apri l'applicazione HelloController.java all'indirizzo /src/main/java/com/example/springboot/HelloController.java
  2. Individua l'istruzione di ritorno per il percorso principale che indica return String.format("Hello from your %s environment!", target);.
  3. Aggiungi un punto di interruzione alla 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 in corrispondenza del punto di interruzione e ti consente di esaminare le variabili e lo stato dell'applicazione in esecuzione da remoto in GKE

71acfb426623cec2.png

  1. Fai clic nella sezione Variabili fino a trovare la colonna "Target" .
  2. Osserva il valore attuale come "local"

a1160d2ed2bb5c82.png

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

Cambia il valore in "Cloud Workstations".

e597a556a5c53f32.png

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

ec17086191770d0d.png

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

6698a9db9e729925.png

  1. Per rimuovere il punto di interruzione, fai clic sull'indicatore rosso a sinistra del numero di riga. In questo modo, l'esecuzione del codice in questa riga non verrà interrotta man mano che procedi nel lab.

Ricarica a caldo

  1. Modifica l'istruzione in modo che restituisca un valore diverso, ad esempio "Ciao da codice %s"
  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ì, esegui la pulizia dopo ogni esecuzione".

984eb2fa34867d70.png

5. Sviluppo di un servizio REST CRUD semplice

A questo punto la tua applicazione è completamente configurata per lo sviluppo containerizzato e hai seguito 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 rendere persistenti i dati del servizio rimanenti. Assicurati che le dipendenze siano disponibili aggiungendo quanto segue nel file pom.xl

  1. Apri il file pom.xml e aggiungi quanto segue nella 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 del 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 denominato QuoteRepository.java all'indirizzo src/main/java/com/example/springboot e copialo nel 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 hai aggiunto 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 all'indirizzo src/main/java/com/example/springboot e copialo nel seguente contenuto

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

}

Aggiungi configurazioni di 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 con parametri 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 di database

Crea le cartelle db/migration in src/main/resources

Crea un file SQL: V1__create_quotes_table.sql

Incolla i contenuti seguenti 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');

Configurazione di Kubernetes

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 al momento della creazione dell'istanza del database o facendo clic su SQL nel menu di navigazione della console Google Cloud. Modifica il valore.
  • DB_USER e DB_PASS: come impostato nella configurazione dell'istanza Cloud SQL, archiviati come secret in Google Cloud

Aggiorna il file deployment.yaml con il contenuto seguente.

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 comandi seguenti 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 nella parte inferiore dell'editor di Cloud Shell, seleziona Cloud Code, quindi Debug su Kubernetes nella parte superiore dello schermo.

33a5cf41aae91adb.png

  1. Una volta completati la build e i test, nella scheda Output appare il seguente messaggio: Resource deployment/demo-app status completed successfully, mentre viene visualizzato un URL con il seguente URL: "Forwarded URL from service demo-app: http://localhost:8080". Tieni presente che a volte la porta potrebbe essere diversa, ad esempio 8081. In questo caso, imposta il valore appropriato. Imposta il valore dell'URL nel terminale
export URL=localhost:8080
  1. Visualizza citazioni casuali

Dal terminale, esegui più volte il comando riportato di seguito sull'endpoint con virgolette casuali. Osservare una chiamata ripetuta con citazioni diverse

curl $URL/random-quote | jq
  1. Aggiungi un preventivo

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

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 delete e osserva un codice di risposta HTTP/1.1 204.

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

Si verifica 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 rilevato 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 utilizzerai la classe QuoteController.

  1. Apri src/main/java/com/example/springboot/QuoteController.java
  2. Trova il metodo deleteQuote()
  3. Trova la linea: Optional<Quote> quote = quoteRepository.findById(id);
  4. Imposta un punto di interruzione sulla riga in questione facendo clic sullo spazio vuoto a sinistra del numero di riga.
  5. Viene 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. Un codice restituisce al client un errore interno del server HTTP 500, 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 inviare un codice di stato HTTP 404 Non trovato.

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 essere simile al seguente

@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 debugger e osserva il messaggio HTTP 404 Non trovato 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 da zero una nuova applicazione Java e l'hai configurata per funzionare in modo efficace con i container. Hai quindi eseguito il deployment dell'applicazione ed eseguito il debug in un cluster GKE remoto seguendo lo stesso flusso di sviluppatori che si trova negli stack di applicazioni tradizionali.

Che cosa hai imparato

  • Sviluppo di InnerLoop con Cloud Workstations
  • Creazione di una nuova applicazione iniziale Java
  • Panoramica del processo di sviluppo
  • Sviluppo di un servizio REST CRUD semplice
  • Debug dell'applicazione su cluster GKE
  • Connessione dell'applicazione al database Cloud SQL