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 logiciels chargés de développer des applications Java dans un environnement conteneurisé. Le développement de conteneurs typique exige de l'utilisateur qu'il comprenne les détails des conteneurs et du processus de création de conteneurs. De plus, les développeurs doivent généralement interrompre leur flux de travail et quitter 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 sans frais 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 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 en 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 doit également être disponible en 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 votre ID de 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 container-developer-workshop dans 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 prendra 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 une application Java Spring Boot à partir de zéro en utilisant un exemple d'application fourni par spring.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 de l'IDE Cloud Shell.
cd sample-app && cloudshell workspace .

Ajouter spring-boot-devtools et Jib

Pour activer Spring Boot DevTools, recherchez et ouvrez le fichier pom.xml à partir de l'explorateur de votre éditeur. Collez ensuite le code suivant après la ligne de description <description>Projet de démonstration pour Spring Boot</description>.

  1. Ajoutez spring-boot-devtools dans 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 outil de création d'images de conteneurs rapide et simple qui gère toutes les étapes de l'empaquetage de votre application dans une image de conteneur. Il ne nécessite pas d'écrire un fichier Dockerfile ni d'installer Docker, et il 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 la section "Build" terminée, elle 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>

Choisissez 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. Dans cette étape, vous allez initialiser Skaffold, ce qui créera automatiquement des fichiers YAML Kubernetes de base. Le processus tente d'identifier les répertoires contenant des définitions d'images de conteneurs, comme un fichier 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 pour le 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.

Modifier le nom de l'application

Les valeurs par défaut incluses dans la configuration ne correspondent pas au nom de votre application. Mettez à jour les fichiers pour qu'ils fassent référence au nom de votre application plutôt qu'aux valeurs par défaut.

  1. Modifier des 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, par exemple demo-app.
  1. Modifier les entrées de 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, par exemple demo-app.

Activer la synchronisation à chaud

Pour faciliter une expérience de rechargement à chaud optimisée, 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 le profil "sync" que vous configurez dans la configuration Skaffold utilise le profil Spring "sync" que vous avez configuré à l'étape précédente, où vous avez activé la compatibilité avec spring-dev-tools.

  1. Mettre à jour la configuration Skaffold

Dans le fichier skaffold.yaml, remplacez l'intégralité de la section de compilation 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 dans /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 suivre quelques étapes à l'aide du plug-in Cloud Code pour découvrir 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. Lorsque vous déployez sur GKE 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, en masquant les détails du flux de développement. Cloud Code améliore également votre processus de développement en fournissant des fonctionnalités de débogage et de synchronisation à chaud traditionnelles pour le développement basé sur des conteneurs.

Déployer sur Kubernetes

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

fdc797a769040839.png

  1. Dans le panneau qui s'affiche en haut, sélectionnez "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 souhaitez utiliser le contexte Kubernetes actuel. Sélectionnez "Oui" pour 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 "Sortie" dans le volet inférieur pour afficher la progression et les notifications.

f95b620569ba96c5.png

  1. Sélectionnez "Kubernetes: Run/Debug - Detailed" (Kubernetes : Exécuter/Déboguer – Détails) dans le menu déroulant à droite pour afficher des informations supplémentaires et les journaux diffusés en direct depuis les conteneurs.

94acdcdda6d2108.png

  1. Pour revenir à la vue simplifiée, sélectionnez "Kubernetes : Exécuter/Déboguer" dans le menu déroulant.
  2. Une fois la compilation et les tests terminés, l'onglet "Résultat" indique Resource deployment/demo-app status completed successfully et une URL est listée : "URL transférée depuis l'application de démonstration du service : http://localhost:8080".
  3. Dans le terminal Cloud Code, pointez sur l'URL dans la sortie (http://localhost:8080), puis dans l'info-bulle qui s'affiche, sélectionnez "Ouvrir l'aperçu sur le Web".

La réponse sera la suivante :

Hello from your local environment!

Utiliser les points d'arrêt

  1. Ouvrez l'application HelloController.java située dans /src/main/java/com/example/springboot/HelloController.java.
  2. Recherchez l'instruction de retour pour le chemin racine, qui se lit 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. Rechargez votre navigateur et notez que le débogueur arrête le processus au point d'arrêt et vous permet d'examiner l'état des variables et de l'application qui s'exécute à distance dans GKE.
  5. Cliquez sur la section des variables jusqu'à ce que vous trouviez la variable "Cible".
  6. Notez que la valeur actuelle est "local".
  7. Double-cliquez sur le nom de la variable "target" et, dans le pop-up, remplacez la valeur par une autre, par exemple "Cloud".
  8. Cliquez sur le bouton "Continuer" dans le panneau de configuration du débogueur.
  9. Examinez la réponse dans votre navigateur. Elle affiche désormais la valeur modifiée que vous venez de saisir.

Hot reload

  1. Modifiez l'instruction pour renvoyer une autre valeur, par exemple "Bonjour depuis %s Code".
  2. Le fichier est automatiquement enregistré et synchronisé dans les conteneurs distants de GKE.
  3. Actualisez votre navigateur pour afficher 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 parcouru 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 de service REST qui se connectent à une base de données gérée dans Google Cloud.

Configurer les 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 les éléments suivants dans le fichier pom.xl.

  1. Ouvrez le fichier pom.xml et ajoutez les éléments suivants à 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é Quote.java dans /src/main/java/com/example/springboot/ et copiez-y le code ci-dessous. Cela définit le modèle d'entité pour l'objet "Quote" (Devis) 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é QuoteRepository.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 conserver les 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 du service, une classe QuoteController fournira cette fonctionnalité.

Créez un fichier QuoteController.java dans src/main/java/com/example/springboot et copiez-y 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 la configuration de la base de données de backend à laquelle le service accède. Modifiez (ou créez, le cas échéant) 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 la migration de bases de données

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

Créez 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 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 qu'ils sont 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é au 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 "Résultat" indique Resource deployment/demo-app status completed successfully et une URL est listée : "URL transférée depuis l'application de démonstration du service : http://localhost:8080".
  3. Afficher des citations aléatoires

Dans le terminal Cloud Shell, exécutez plusieurs fois la commande ci-dessous sur le point de terminaison random-quote. Observer des appels répétés renvoyant des citations différentes

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

Créez un devis avec l'ID 6 à l'aide de la commande ci-dessous et observez la demande renvoyée.

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 un devis

Supprimez maintenant la citation que vous venez d'ajouter avec la méthode de suppression 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

Découvrez 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 rencontré 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 identifier le problème. L'erreur s'est produite lors de l'opération DELETE. Vous allez donc travailler avec la classe QuoteController.

  1. Ouvrez src.main.java.com.example.springboot.QuoteController.java
  2. Recherchez la méthode deleteQuote().
  3. Recherchez la ligne où un élément est supprimé 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. Revenez à la vue Débogage en cliquant sur l'icône dans la colonne de gauche.
  2. Observez la ligne de débogage arrêtée dans la classe QuoteController.
  3. Dans le débogueur, cliquez sur l'icône step over b814d39b2e5f3d9e.png et constatez qu'une exception est générée.
  4. Notez qu'un RuntimeException was caught. très générique renvoie une erreur HTTP 500 (Internal Server Error) au client, 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'exception doit être refactorisé pour intercepter l'exception EmptyResultDataAccessException et renvoyer un code d'état HTTP 404 (page introuvable).

Corrigez l'erreur.

  1. La session de débogage étant toujours en cours d'exécution, terminez la requête en appuyant 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 devrait 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 l'exception EmptyResultDataAccessException qui est interceptée et le code HTTP 404 (Introuvable) 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 créé une application Java à partir de zéro et l'avez configurée pour qu'elle fonctionne efficacement avec les conteneurs. Vous avez ensuite déployé et débogué votre application sur un cluster GKE distant en suivant le même flux de développement que celui utilisé dans les piles d'applications traditionnelles.

Pour effectuer le 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 supprimer toute l'infrastructure et les ressources associées