Développement InnerLoop avec Java – Spring Boot

1. Présentation

Cet atelier présente des fonctionnalités conçues pour simplifier le workflow de développement des ingénieurs logiciel chargés de développer des applications Java dans un environnement conteneurisé. En règle générale, le développement de conteneurs nécessite que l'utilisateur comprenne les détails des conteneurs et le processus de création de conteneurs. De plus, les développeurs doivent généralement interrompre leur flux et sortir de leur IDE pour tester et déboguer leurs applications dans des environnements distants. Grâce aux outils et technologies mentionnés dans ce tutoriel, les développeurs peuvent travailler efficacement avec des applications conteneurisées sans quitter leur IDE.

Objectifs de l'atelier

Dans cet atelier, vous allez découvrir des méthodes de développement avec des conteneurs dans GCP, y compris:

  • Préparation
  • Créer une application de démarrage Java
  • Parcourir le processus de développement
  • Développer un service REST CRUD simple
  • Nettoyage

2. Préparation

Configuration de l'environnement d'auto-formation

  1. Connectez-vous à la console Google Cloud, puis créez un projet ou réutilisez un projet existant. (Si vous ne possédez pas encore de compte Gmail ou Google Workspace, vous devez en créer un.)

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Le nom du projet est le nom à afficher pour les participants au projet. Il s'agit d'une chaîne de caractères qui n'est pas utilisée par les API Google, et que vous pouvez modifier à tout moment.
  • L'ID du projet doit être unique sur l'ensemble des projets Google Cloud et doit être immuable (vous ne pouvez pas le modifier une fois que vous l'avez défini). Cloud Console génère automatiquement une chaîne unique dont la composition importe peu, en général. Dans la plupart des ateliers de programmation, vous devrez référencer l'ID du projet (généralement identifié comme PROJECT_ID), donc s'il ne vous convient pas, générez-en un autre au hasard ou définissez le vôtre, puis vérifiez s'il est disponible. Il est ensuite "gelé" une fois le projet créé.
  • La troisième valeur est le numéro de projet, utilisé par certaines API. Pour en savoir plus sur ces trois valeurs, consultez la documentation.
  1. Vous devez ensuite activer la facturation dans Cloud Console afin d'utiliser les ressources/API Cloud. L'exécution de cet atelier de programmation est très peu coûteuse, voire sans frais. Pour arrêter les ressources afin d'éviter qu'elles ne vous soient facturées après ce tutoriel, suivez les instructions de nettoyage indiquées à la fin de l'atelier. Les nouveaux utilisateurs de Google Cloud peuvent participer au programme d'essai gratuit pour bénéficier d'un crédit de 300 $.

Démarrer l'éditeur Cloudshell

Cet atelier a été conçu et testé pour être utilisé avec l'éditeur Google Cloud Shell. Pour accéder à l'éditeur,

  1. Accédez à votre projet Google à l'adresse https://console.cloud.google.com.
  2. En haut à droite, cliquez sur l'icône de l'éditeur Cloud Shell.

8560cc8d45e8c112.png

  1. Un nouveau volet s'ouvre au bas de la fenêtre.
  2. Cliquez sur le bouton "Ouvrir l'éditeur"

9e504cb98a6a8005.png

  1. L'éditeur s'ouvre avec un explorateur à droite et un éditeur dans la zone centrale
  2. Un volet de terminal devrait également être disponible au bas de l'écran.
  3. Si le terminal n'est PAS ouvert, utilisez la combinaison de touches Ctrl+ pour ouvrir une nouvelle fenêtre de terminal.

Configurer gcloud

Dans Cloud Shell, définissez l'ID de votre projet et la région dans laquelle vous souhaitez déployer votre application. Enregistrez-les en tant que variables PROJECT_ID et REGION.

export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

Obtenir le code source

Le code source de cet atelier se trouve dans l'atelier "container-developer-workshop" de GoogleCloudPlatform sur GitHub. Clonez-le à l'aide de la commande ci-dessous, puis accédez au répertoire.

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

Provisionner l'infrastructure utilisée dans cet atelier

Dans cet atelier, vous allez déployer du code sur GKE et accéder aux données stockées dans une base de données Cloud SQL. Le script de configuration ci-dessous prépare cette infrastructure pour vous. Le processus de provisionnement prend plus de 10 minutes. Vous pouvez passer aux étapes suivantes pendant le traitement de la configuration.

./setup.sh

3. Créer une application de démarrage Java

Dans cette section, vous allez créer entièrement une application Java Spring Boot à l'aide d'un exemple d'application fourni par print.io.

Cloner l'exemple d'application

  1. Créer une application de démarrage
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. Décompresser l'application
unzip sample-app.zip -d sample-app
  1. Accédez au répertoire "sample-app" et ouvrez le dossier dans l'espace de travail IDE Cloud Shell
cd sample-app && cloudshell workspace .

Ajoutez des outils de développement Jab

Pour activer les outils de développement Spring Boot, recherchez et ouvrez le fichier pom.xml depuis l'explorateur de votre éditeur. Collez ensuite le code suivant après la ligne de description <description>Demo project for Spring Boot</description>.

  1. Ajoutez "Spring-boot-devtools" dans le fichier pom.xml.

Ouvrez le fichier pom.xml à la racine du projet. Ajoutez la configuration suivante après l'entrée 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. Activer jib-maven-plugin dans pom.xml

Jib est un outil de conteneurisation Java Open Source de Google, qui permet aux développeurs Java de créer des conteneurs à l'aide des outils Java qu'ils connaissent. Jib est un générateur d'images de conteneurs rapide et simple qui gère toutes les étapes du packaging de votre application dans une image de conteneur. Vous n'avez pas besoin d'écrire de fichier Dockerfile ni d'installer Docker. De plus, ce fichier est directement intégré à Maven et Gradle.

Faites défiler le fichier pom.xml vers le bas et mettez à jour la section Build pour inclure le plug-in Jib. Une fois l'opération terminée, la section de compilation doit correspondre à ce qui suit.

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>

Sélectionnez Always si vous êtes invité à modifier le fichier de compilation.

447a90338f51931f.png

Générer des fichiers manifestes

Skaffold fournit des outils intégrés pour simplifier le développement de conteneurs. Au cours de cette étape, vous allez initialiser Skaffold, qui créera automatiquement des fichiers YAML Kubernetes de base. Le processus tente d'identifier les répertoires avec des définitions d'image de conteneur, comme un Dockerfile, puis crée un fichier manifeste de déploiement et de service pour chacun d'eux.

Exécutez la commande ci-dessous pour commencer le processus.

  1. Exécutez la commande suivante dans le terminal.
skaffold init --generate-manifests
  1. Lorsque vous y êtes invité :
  • Utilisez les flèches pour déplacer le curseur sur Jib Maven Plugin.
  • Appuyez sur la barre d'espace pour sélectionner l'option.
  • Appuyez sur Entrée pour continuer
  1. Saisissez 8080 comme port
  2. Saisissez y pour enregistrer la configuration

Deux fichiers sont ajoutés à la visualisation de l'espace de travail, skaffold.yaml et deployment.yaml.

Mettre à jour le nom de l'application

Actuellement, les valeurs par défaut incluses dans la configuration ne correspondent pas au nom de votre application. Mettez à jour les fichiers pour référencer le nom de votre application plutôt que les valeurs par défaut.

  1. Modifier les entrées dans la configuration Skaffold
  • Ouvrir skaffold.yaml
  • Sélectionnez le nom de l'image actuellement défini sur pom-xml-image
  • Effectuez un clic droit et sélectionnez "Modifier toutes les occurrences"
  • Saisissez le nouveau nom demo-app.
  1. Modifier les entrées dans la configuration Kubernetes
  • Ouvrir le fichier deployment.yaml
  • Sélectionnez le nom de l'image actuellement défini sur pom-xml-image
  • Effectuez un clic droit et sélectionnez "Modifier toutes les occurrences"
  • Saisissez le nouveau nom demo-app.

Activer la synchronisation à chaud

Pour optimiser l'expérience de hot reload, vous utiliserez la fonctionnalité de synchronisation fournie par Jib. Au cours de cette étape, vous allez configurer Skaffold pour utiliser cette fonctionnalité dans le processus de compilation.

Notez que l'option "sync" que vous configurez dans la configuration Skaffold utilise la synchronisation Spring Profil que vous avez configuré à l'étape précédente, où vous avez activé la prise en charge de Spring-dev-tools.

  1. Mettre à jour la configuration Skaffold

Dans le fichier skaffold.yaml, remplacez l'intégralité de la section de compilation du fichier par la spécification suivante. Ne modifiez pas les autres sections du fichier.

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

Ajouter une route par défaut

Créez un fichier appelé HelloController.java à l'emplacement /src/main/java/com/example/springboot/.

Collez le contenu suivant dans le fichier pour créer une route HTTP par défaut

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. Parcourir le processus de développement

Dans cette section, vous allez apprendre à utiliser le plug-in Cloud Code pour connaître les processus de base et valider la configuration de votre application de démarrage.

Cloud Code s'intègre à Skaffold pour simplifier votre processus de développement. Lors des étapes suivantes, Cloud Code et Skaffold créent automatiquement votre image de conteneur, la transfèrent vers Container Registry, puis déploient votre application sur GKE. Cela se produit en arrière-plan afin d'éliminer les détails du flux de développement. Cloud Code améliore également votre processus de développement en fournissant des fonctionnalités traditionnelles de débogage et de synchronisation à chaud avec le développement basé sur des conteneurs.

Déployer sur Kubernetes

  1. Dans le volet situé en bas de l'éditeur Cloud Shell, sélectionnez Cloud Code .

fdc797a769040839.png

  1. Dans le panneau qui s'affiche en haut, sélectionnez "Debug on Kubernetes" (Déboguer sur Kubernetes). Si vous y êtes invité, sélectionnez "Oui" pour utiliser le contexte Kubernetes actuel.

cfce0d11ef307087.png

  1. La première fois que vous exécutez la commande, une invite s'affiche en haut de l'écran pour vous demander si vous voulez le contexte Kubernetes actuel. Sélectionnez "Oui" accepter et utiliser le contexte actuel.

817ee33b5b412ff8.png

  1. Une invite s'affiche ensuite pour vous demander quel registre de conteneurs utiliser. Appuyez sur Entrée pour accepter la valeur par défaut fournie

eb4469aed97a25f6.png

  1. Sélectionnez l'onglet "Output" (Sortie) dans le volet inférieur pour afficher la progression et les notifications.

f95b620569ba96c5.png

  1. Sélectionnez "Kubernetes: Run/Debug - Détaillé". dans le menu déroulant du canal à droite pour afficher des informations supplémentaires et les journaux diffusés en direct depuis les conteneurs.

94acdcdda6d2108.png

  1. Revenez à la vue simplifiée en sélectionnant "Kubernetes: Run/Debug" (Kubernetes : Exécuter/Déboguer) dans le menu déroulant
  2. Une fois la compilation et les tests terminés, l'onglet "Output" (Sortie) indique Resource deployment/demo-app status completed successfully, et une URL s'affiche : "Forwarded URL from service demo-app: http://localhost:8080"
  3. Dans le terminal Cloud Code, pointez sur l'URL affichée dans le résultat (http://localhost:8080), puis sélectionnez "Ouvrir l'aperçu sur le Web" dans l'info-bulle qui s'affiche.

La réponse sera:

Hello from your local environment!

Utiliser des points d'arrêt

  1. Ouvrez l'application HelloController.java située à l'emplacement /src/main/java/com/example/springboot/HelloController.java.
  2. Recherchez l'instruction de retour pour le chemin d'accès racine qui indique return String.format("Hello from your %s environment!", target);
  3. Ajoutez un point d'arrêt à cette ligne en cliquant sur l'espace vide à gauche du numéro de ligne. Un indicateur rouge s'affiche pour indiquer que le point d'arrêt est défini.
  4. Actualisez votre navigateur et notez que le débogueur arrête le processus au point d'arrêt et vous permet d'examiner la variable d'état (sable) de l'application qui s'exécute à distance dans GKE.
  5. Cliquez sur la section "Variables" vers le bas jusqu'à la colonne "Cible". .
  6. Observer la valeur actuelle en tant que "local"
  7. Double-cliquez sur le nom de variable "target". puis, dans la fenêtre pop-up, remplacez la valeur par "Cloud"
  8. Cliquez sur le bouton "Continuer" dans le panneau de configuration du débogage.
  9. Examinez la réponse dans votre navigateur qui affiche à présent la nouvelle valeur que vous venez de saisir.

Actualisation à chaud

  1. Modifiez l'instruction pour renvoyer une valeur différente, telle que "Hello from %s Code" (Bonjour de %s Code).
  2. Le fichier est automatiquement enregistré et synchronisé dans les conteneurs distants dans GKE
  3. Actualisez votre navigateur pour voir les résultats mis à jour.
  4. Arrêtez la session de débogage en cliquant sur le carré rouge dans la barre d'outils de débogage a13d42d726213e6c.png.

5. Développer un service REST CRUD simple

À ce stade, votre application est entièrement configurée pour le développement conteneurisé, et vous avez suivi le workflow de développement de base avec Cloud Code. Dans les sections suivantes, vous allez mettre en pratique ce que vous avez appris en ajoutant des points de terminaison du service REST se connectant à une base de données gérée dans Google Cloud.

Configurer des dépendances

Le code de l'application utilise une base de données pour conserver les données du service REST. Assurez-vous que les dépendances sont disponibles en ajoutant ce qui suit dans le fichier pom.xl.

  1. Ouvrez le fichier pom.xml et ajoutez le code suivant dans la section des dépendances de la configuration

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>

Coder le service REST

Quote.java

Créez un fichier nommé Citation.java dans /src/main/java/com/example/springboot/ et copiez-y le code ci-dessous. Ce champ définit le modèle d'entité de l'objet Citation utilisé dans l'application.

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

Créez un fichier nommé CitationRepository.java dans src/main/java/com/example/springboot et copiez-y le code suivant.

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

Ce code utilise JPA pour la persistance des données. La classe étend l'interface Spring JPARepository et permet de créer du code personnalisé. Dans le code, vous avez ajouté une méthode personnalisée findRandomQuote.

QuoteController.java

Pour exposer le point de terminaison au service, une classe QuoteController fournira cette fonctionnalité.

Créez un fichier nommé CitationController.java dans src/main/java/com/example/springboot et copiez-le dans le contenu suivant.

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

Ajouter des configurations de base de données

application.yaml

Ajoutez une configuration pour la base de données backend à laquelle le service a accès. Modifiez (ou créez-le s'il n'existe pas) le fichier application.yaml sous src/main/resources et ajoutez une configuration Spring paramétrée pour le 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

Ajouter une migration de base de données

Créez un dossier à l'emplacement src/main/resources/db/migration/.

Créer un fichier SQL: V1__create_quotes_table.sql

Collez le contenu suivant dans le fichier

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

Configuration de Kubernetes

Les ajouts suivants au fichier deployment.yaml permettent à l'application de se connecter aux instances Cloud SQL.

  • TARGET : configure la variable pour indiquer l'environnement dans lequel l'application est exécutée.
  • SPRING_PROFILES_ACTIVE : affiche le profil Spring actif, qui sera configuré sur cloud-dev.
  • DB_HOST : adresse IP privée de la base de données, qui a été notée lors de la création de l'instance de base de données ou en cliquant sur SQL dans le menu de navigation de la console Google Cloud. Veuillez modifier la valeur.
  • DB_USER et DB_PASS, tels que définis dans la configuration de l'instance Cloud SQL, stockés en tant que Secret dans GCP

Mettez à jour votre fichier deployment.yaml avec le contenu ci-dessous.

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 

Remplacez la valeur DB_HOST par l'adresse de votre base de données.

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

Déployer et valider l'application

  1. Dans le volet situé en bas de l'éditeur Cloud Shell, sélectionnez Cloud Code, puis "Déboguer sur Kubernetes" en haut de l'écran.
  2. Une fois la compilation et les tests terminés, l'onglet "Output" (Sortie) indique Resource deployment/demo-app status completed successfully, et une URL s'affiche : "Forwarded URL from service demo-app: http://localhost:8080"
  3. Afficher les citations aléatoires

Depuis le terminal cloudshell, exécutez plusieurs fois la commande ci-dessous sur le point de terminaison "random-citation". Observer un appel répété renvoyant des guillemets différents

curl -v 127.0.0.1:8080/random-quote
  1. Ajouter un devis

Créez un autre devis avec id=6 à l'aide de la commande ci-dessous et observez l'écho de la requête.

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. Supprimer une citation

Maintenant, supprimez la citation que vous venez d'ajouter avec la méthode delete et observez un code de réponse HTTP/1.1 204.

curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. Erreur du serveur

Vous rencontrerez un état d'erreur en exécutant à nouveau la dernière requête après la suppression de l'entrée.

curl -v -X DELETE 127.0.0.1:8080/quotes/6

Notez que la réponse renvoie un HTTP:500 Internal Server Error.

Déboguer l'application

Dans la section précédente, vous avez trouvé un état d'erreur dans l'application lorsque vous avez essayé de supprimer une entrée qui ne figurait pas dans la base de données. Dans cette section, vous allez définir un point d'arrêt pour localiser le problème. L'erreur s'est produite lors de l'opération DELETE. Vous allez donc utiliser la classe CitationController.

  1. Ouvrez src.main.java.com.example.springboot.CitationController.java
  2. Rechercher la méthode deleteQuote()
  3. Recherchez la ligne permettant de supprimer un élément de la base de données: quoteRepository.deleteById(id);
  4. Définissez un point d'arrêt sur cette ligne en cliquant sur l'espace vide à gauche du numéro de ligne.
  5. Un indicateur rouge s'affiche pour indiquer que le point d'arrêt est défini.
  6. Exécutez à nouveau la commande delete.
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. Pour revenir à la vue de débogage, cliquez sur l'icône dans la colonne de gauche.
  2. Comme vous pouvez le constater, la ligne de débogage s'est arrêtée dans la classe CitationController.
  3. Dans le débogueur, cliquez sur l'icône step over b814d39b2e5f3d9e.png et observez qu'une exception est générée.
  4. Notez qu'un RuntimeException was caught. très générique renvoie au client une erreur de serveur interne HTTP 500, ce qui n'est pas idéal.
   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

Mettre à jour le code

Le code est incorrect et le bloc d'exceptions doit être refactorisé pour intercepter l'exception EmptyResultDataAccessException et renvoyer un code d'état HTTP 404 (introuvable).

Corrigez l'erreur.

  1. La session de débogage étant toujours en cours d'exécution, terminez la requête en cliquant sur le bouton "Continuer". dans le panneau de configuration du débogage.
  2. Ajoutez ensuite le bloc suivant au code:
       } catch (EmptyResultDataAccessException e){
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        }

La méthode doit se présenter comme suit :

    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. Réexécuter la commande de suppression
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. Parcourez le débogueur et observez que EmptyResultDataAccessException est intercepté et qu'un message HTTP 404 Not Found est renvoyé à l'appelant.
   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. Arrêtez la session de débogage en cliquant sur le carré rouge dans la barre d'outils de débogage a13d42d726213e6c.png.

6. Nettoyage

Félicitations ! Dans cet atelier, vous avez entièrement créé une application Java et l'avez configurée pour fonctionner efficacement avec des conteneurs. Vous avez ensuite déployé et débogué votre application sur un cluster GKE distant en suivant le même parcours de développement que dans les piles d'applications traditionnelles.

Pour effectuer un nettoyage une fois l'atelier terminé:

  1. Supprimer les fichiers utilisés dans l'atelier
cd ~ && rm -rf container-developer-workshop
  1. Supprimer le projet pour retirer toute l'infrastructure et toutes les ressources associées