Sviluppo InnerLoop con Java - SpringBoot

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:

  • Configurazione e requisiti
  • Creazione di una nuova applicazione Java iniziale
  • Esaminare il processo di sviluppo
  • Sviluppo di un semplice servizio REST CRUD
  • Esegui la pulizia

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 e puoi aggiornarla in qualsiasi momento.
  • L'ID progetto deve essere univoco in tutti i progetti Google Cloud ed è immutabile (non può essere modificato dopo essere stato impostato). Cloud Console 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 (che in genere è identificato come PROJECT_ID), quindi, se non ti piace, generane un altro casuale oppure puoi provare il tuo e vedere se è disponibile. Viene "congelato" dopo la creazione del progetto.
  • 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, segui le istruzioni di "pulizia" riportate alla fine del codelab. 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 PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

Recupera 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 Cloud SQL. Lo script di configurazione riportato di seguito prepara questa infrastruttura per te. La procedura di provisioning richiederà più di 10 minuti. Puoi continuare con i passaggi successivi mentre la configurazione è in corso.

./setup.sh

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

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=11 -d packageName=com.example.springboot -o sample-app.zip
  1. Decomprimi l'applicazione
unzip sample-app.zip -d sample-app
  1. Passa alla directory sample-app e apri la cartella nello spazio di lavoro dell'IDE Cloud Shell
cd sample-app && cloudshell workspace .

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 radice 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 Google per la creazione di container Java 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>

Scegli Always se ti viene chiesto di modificare il file di build.

447a90338f51931f.png

Genera manifest

Skaffold fornisce strumenti integrati per semplificare lo sviluppo dei container. In questo passaggio inizializzerai 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, e poi crea un manifest di deployment e servizio per ciascuna.

Esegui il comando riportato di seguito per iniziare la procedura.

  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.

Allo spazio di lavoro vengono aggiunti due file: skaffold.yaml e deployment.yaml

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 sincronizzazione rapida

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 configurazione Skaffold

Nel file skaffold.yaml, sostituisci l'intera sezione di build 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/java:debug
    sync:
      auto: true

Aggiungere una route predefinita

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

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.

Eseguire il deployment in Kubernetes

  1. Nel riquadro in fondo all'editor di Cloud Shell, seleziona Cloud Code. 

fdc797a769040839.png

  1. Nel riquadro visualizzato in alto, seleziona Debug su Kubernetes. Se richiesto, seleziona Sì per utilizzare il contesto Kubernetes attuale.

cfce0d11ef307087.png

  1. La prima volta che esegui il comando, nella parte superiore dello schermo viene visualizzato un prompt che ti chiede se vuoi utilizzare il contesto Kubernetes corrente. Seleziona "Sì" per accettare e utilizzare il contesto corrente.

817ee33b5b412ff8.png

  1. Successivamente, verrà visualizzato un prompt che chiede quale registro dei container utilizzare. Premi Invio per accettare il valore predefinito fornito.

eb4469aed97a25f6.png

  1. Seleziona la scheda Output nel riquadro inferiore per visualizzare l'avanzamento e le notifiche.

f95b620569ba96c5.png

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

94acdcdda6d2108.png

  1. Torna alla visualizzazione semplificata selezionando "Kubernetes: Run/Debug" (Kubernetes: esegui/debug) dal menu a discesa.
  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 Cloud Code, passa il mouse sopra l'URL nell'output (http://localhost:8080) e poi seleziona Apri anteprima web nel suggerimento visualizzato.

La risposta sarà:

Hello from your local environment!

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
  4. Ricarica il browser e nota che il debugger interrompe il processo nel punto di interruzione e ti consente di esaminare lo stato delle variabili e dell'applicazione in esecuzione da remoto in GKE
  5. Fai clic nella sezione delle variabili fino a trovare la variabile "Target".
  6. Osserva il valore corrente "local"
  7. Fai doppio clic sul nome della variabile "target" e, nel popup, modifica il valore in qualcosa di diverso, ad esempio "Cloud".
  8. Fai clic sul pulsante Continua nel pannello di controllo del debug
  9. Controlla la risposta nel browser, che ora mostra il valore aggiornato appena inserito.

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 a13d42d726213e6c.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 del 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>

Codifica il servizio REST

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 javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.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 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) {
        try {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
            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 una cartella in src/main/resources/db/migration/

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.

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

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.
  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. Visualizzare citazioni casuali

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

curl -v 127.0.0.1:8080/random-quote
  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 -v -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 127.0.0.1:8080/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 127.0.0.1:8080/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 127.0.0.1:8080/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 in cui eliminare un elemento dal database: quoteRepository.deleteById(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 127.0.0.1:8080/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 e osserva che viene generata un'eccezione.
  4. Tieni presente che viene restituito un errore interno del server HTTP 500 al client, il che non è l'ideale.RuntimeException was caught.
   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 di eccezione deve essere sottoposto a refactoring per rilevare l'eccezione EmptyResultDataAccessException e 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 aggiungi il seguente blocco al codice:
       } catch (EmptyResultDataAccessException e){
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        }

Il metodo dovrebbe avere il seguente aspetto

    public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        try {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch(EmptyResultDataAccessException e){
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
  1. Esegui di nuovo il comando di eliminazione
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. Esegui il debug e osserva l'eccezione EmptyResultDataAccessException intercettata e la risposta HTTP 404 Not Found restituita 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 a13d42d726213e6c.png

6. Esegui la pulizia

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.

Per liberare spazio dopo aver completato il lab:

  1. Eliminare i file utilizzati nel lab
cd ~ && rm -rf container-developer-workshop
  1. Elimina il progetto per rimuovere tutte le infrastrutture e le risorse correlate