Mit Cloud Workstations und Cloud Code entwickeln

1. Übersicht

In diesem Lab werden Funktionen und Möglichkeiten vorgestellt, die den Entwicklungsablauf für Softwareentwickler optimieren sollen, die Java-Anwendungen in einer Containerumgebung entwickeln. Für die typische Containerentwicklung muss der Nutzer Details zu Containern und zum Container-Build-Prozess kennen. Außerdem müssen Entwickler ihren Workflow in der Regel unterbrechen und ihre IDE verlassen, um ihre Anwendungen in Remote-Umgebungen zu testen und zu debuggen. Mit den in diesem Tutorial erwähnten Tools und Technologien können Entwickler effektiv mit containerisierten Anwendungen arbeiten, ohne ihre IDE zu verlassen.

Lerninhalte

In diesem Lab lernen Sie Methoden für die Entwicklung mit Containern in der GCP kennen, darunter:

  • Inner-Loop-Entwicklung mit Cloud Workstations
  • Neue Java-Starteranwendung erstellen
  • Entwicklungsprozess durchlaufen
  • Einfachen CRUD-REST-Dienst entwickeln
  • Anwendungsfehler in GKE-Clustern beheben
  • Anwendung mit Cloud SQL-Datenbank verbinden

58a4cdd3ed7a123a.png

2. Einrichtung und Anforderungen

Umgebung zum selbstbestimmten Lernen einrichten

  1. Melden Sie sich in der Google Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes. Wenn Sie noch kein Gmail- oder Google Workspace-Konto haben, müssen Sie eines erstellen.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Der Projektname ist der Anzeigename für die Teilnehmer dieses Projekts. Es handelt sich um einen String, der nicht von Google APIs verwendet wird. Sie können ihn jederzeit aktualisieren.
  • Die Projekt-ID ist für alle Google Cloud-Projekte eindeutig und unveränderlich (kann nach dem Festlegen nicht mehr geändert werden). In der Cloud Console wird automatisch ein eindeutiger String generiert. Normalerweise ist es nicht wichtig, wie dieser String aussieht. In den meisten Codelabs müssen Sie auf die Projekt-ID verweisen (sie wird in der Regel als PROJECT_ID angegeben). Wenn Ihnen die generierte ID nicht gefällt, können Sie eine andere zufällige ID generieren. Alternativ können Sie es mit einem eigenen versuchen und sehen, ob es verfügbar ist. Sie kann nach diesem Schritt nicht mehr geändert werden und bleibt für die Dauer des Projekts bestehen.
  • Zur Information: Es gibt einen dritten Wert, die Projektnummer, die von einigen APIs verwendet wird. Weitere Informationen zu diesen drei Werten
  1. Als Nächstes müssen Sie die Abrechnung in der Cloud Console aktivieren, um Cloud-Ressourcen/-APIs zu verwenden. Die Durchführung dieses Codelabs sollte keine oder nur geringe Kosten verursachen. Wenn Sie Ressourcen herunterfahren möchten, damit Ihnen nach Abschluss dieser Anleitung keine Kosten mehr in Rechnung gestellt werden, können Sie die von Ihnen erstellten Ressourcen oder das gesamte Projekt löschen. Neue Nutzer von Google Cloud kommen für das Programm für kostenlose Testversionen mit einem Guthaben von 300$ infrage.

Cloud Shell-Editor starten

Dieses Lab wurde für die Verwendung mit Google Cloud Shell Editor entwickelt und getestet. So greifen Sie auf den Editor zu:

  1. Rufen Sie Ihr Google-Projekt unter https://console.cloud.google.com auf.
  2. Klicken Sie oben rechts auf das Cloud Shell Editor-Symbol.

8560cc8d45e8c112.png

  1. Unten im Fenster wird ein neuer Bereich geöffnet.
  2. Klicken Sie auf die Schaltfläche „Editor öffnen“.

9e504cb98a6a8005.png

  1. Der Editor wird mit einem Explorer auf der rechten Seite und dem Editor im mittleren Bereich geöffnet.
  2. Unten auf dem Bildschirm sollte auch ein Terminalbereich verfügbar sein.
  3. Wenn das Terminal NICHT geöffnet ist, verwenden Sie die Tastenkombination „Strg+``“, um ein neues Terminalfenster zu öffnen.

gcloud einrichten

Legen Sie in Cloud Shell Ihre Projekt-ID und die Region fest, in der Sie Ihre Anwendung bereitstellen möchten. Speichern Sie diese als die Variablen PROJECT_ID und REGION ab.

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

Quellcode klonen

Der Quellcode für dieses Lab befindet sich im Repository „container-developer-workshop“ in der Organisation „GoogleCloudPlatform“ auf GitHub. Klonen Sie das Repository mit dem folgenden Befehl und wechseln Sie dann in das Verzeichnis.

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

Die in diesem Lab verwendete Infrastruktur bereitstellen

In diesem Lab stellen Sie Code in GKE bereit und greifen auf Daten zu, die in einer Cloud SQL-Datenbank gespeichert sind. Das folgende Setupscript bereitet diese Infrastruktur für Sie vor. Der Bereitstellungsprozess dauert mehr als 25 Minuten. Warten Sie, bis das Skript beendet ist, bevor Sie mit dem nächsten Abschnitt fortfahren.

./setup_with_cw.sh &

Cloud Workstations-Cluster

Öffnen Sie Cloud Workstations in der Cloud Console. Warten Sie, bis der Cluster den Status READY hat.

305e1a3d63ac7ff6.png

Workstationkonfiguration erstellen

Wenn Ihre Cloud Shell-Sitzung getrennt wurde, klicken Sie auf „Reconnect“ (Wiederverbinden) und führen Sie dann den gcloud-CLI-Befehl aus, um die Projekt-ID festzulegen. Ersetzen Sie die Beispielprojekt-ID unten durch Ihre Qwiklabs-Projekt-ID, bevor Sie den Befehl ausführen.

gcloud config set project qwiklabs-gcp-project-id

Führen Sie das folgende Skript im Terminal aus, um die Cloud Workstations-Konfiguration zu erstellen.

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

Überprüfen Sie die Ergebnisse im Bereich „Konfigurationen“. Es dauert 2 Minuten, bis der Status zu „BEREIT“ wechselt.

7a6af5aa2807a5f2.png

Öffnen Sie Cloud Workstations in der Console und erstellen Sie eine neue Instanz.

a53adeeac81a78c8.png

Ändern Sie den Namen in my-workstation und wählen Sie die vorhandene Konfiguration codeoss-java aus.

f21c216997746097.png

Prüfen Sie die Ergebnisse im Bereich „Arbeitsstationen“.

66a9fc8b20543e32.png

Workstation starten

Starten Sie die Workstation.

c91bb69b61ec8635.png

Erlauben Sie Drittanbieter-Cookies, indem Sie in der Adressleiste auf das Symbol klicken. 1b8923e2943f9bc4.png

fcf9405b6957b7d7.png

Klicken Sie auf „Website funktioniert nicht?“.

36a84c0e2e3b85b.png

Klicken Sie auf „Cookies zulassen“.

2259694328628fba.png

Nachdem die Workstation gestartet wurde, wird die Code OSS-IDE angezeigt. Klicken Sie auf der Seite „Erste Schritte“ in der Workstation-IDE auf „Als erledigt markieren“.

94874fba9b74cc22.png

3. Neue Java-Starteranwendung erstellen

In diesem Abschnitt erstellen Sie eine neue Java Spring Boot-Anwendung von Grund auf. Dazu verwenden Sie eine Beispielanwendung von spring.io. Öffnen Sie ein neues Terminal.

c31d48f2e4938c38.png

Beispielanwendung klonen

  1. Startanwendung erstellen
curl  https://start.spring.io/starter.zip -d dependencies=web -d type=maven-project -d javaVersion=17 -d packageName=com.example.springboot -o sample-app.zip

Klicken Sie auf die Schaltfläche „Zulassen“, wenn diese Meldung angezeigt wird, damit Sie Inhalte in die Workstation kopieren und einfügen können.

58149777e5cc350a.png

  1. Anwendung entzippen
unzip sample-app.zip -d sample-app
  1. Öffnen Sie den Ordner „sample-app“.
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

spring-boot-devtools und Jib hinzufügen

Um die Spring Boot DevTools zu aktivieren, suchen Sie im Explorer Ihres Editors nach der Datei „pom.xml“ und öffnen Sie sie. Fügen Sie dann den folgenden Code nach der Beschreibungszeile <description>Demo project for Spring Boot</description> ein.

  1. „spring-boot-devtools“ in „pom.xml“ hinzufügen

Öffnen Sie die Datei pom.xml im Stammverzeichnis des Projekts. Fügen Sie die folgende Konfiguration nach dem Eintrag Description hinzu.

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. jib-maven-plugin in pom.xml aktivieren

Jib ist ein Open-Source-Tool von Google zum Containerisieren von Java-Anwendungen, mit dem Java-Entwickler Container mit den Java-Tools erstellen können, die sie kennen. Jib ist ein schneller und einfacher Container-Image-Builder, der alle Schritte beim Packen Ihrer Anwendung in ein Container-Image übernimmt. Sie müssen kein Dockerfile schreiben und Docker muss nicht installiert sein. Außerdem ist das Tool direkt in Maven und Gradle integriert.

Scrollen Sie in der Datei pom.xml nach unten und aktualisieren Sie den Abschnitt Build, damit er das Jib-Plug-in enthält. Der Build-Abschnitt sollte nach Abschluss so aussehen.

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>

Manifeste generieren

Skaffold bietet integrierte Tools, um die Containerentwicklung zu vereinfachen. In diesem Schritt initialisieren Sie Skaffold, wodurch automatisch Kubernetes-YAML-Basisdateien erstellt werden. Dabei wird versucht, Verzeichnisse mit Container-Image-Definitionen wie Dockerfile zu identifizieren. Anschließend wird für jedes Verzeichnis ein Bereitstellungs- und Dienstmanifest erstellt.

Führen Sie im Terminal den folgenden Befehl aus, um den Vorgang zu starten.

d869e0cd38e983d7.png

  1. Führen Sie im Terminal den folgenden Befehl aus.
skaffold init --generate-manifests
  1. Tun Sie Folgendes, wenn Sie dazu aufgefordert werden:
  • Verwende die Pfeile, um den Cursor zu Jib Maven Plugin zu bewegen.
  • Drücken Sie die Leertaste, um die Option auszuwählen.
  • Zum Fortfahren die Eingabetaste drücken
  1. Geben Sie 8080 für den Port ein.
  2. Geben Sie y ein, um die Konfiguration zu speichern.

Dem Arbeitsbereich werden zwei Dateien hinzugefügt: skaffold.yaml und deployment.yaml.

Skaffold-Ausgabe:

b33cc1e0c2077ab8.png

App-Name aktualisieren

Die in der Konfiguration enthaltenen Standardwerte stimmen derzeit nicht mit dem Namen Ihrer Anwendung überein. Aktualisieren Sie die Dateien so, dass sie auf Ihren Anwendungsnamen und nicht auf die Standardwerte verweisen.

  1. Einträge in der Skaffold-Konfiguration ändern
  • skaffold.yaml öffnen
  • Wählen Sie den Bildnamen aus, der derzeit als pom-xml-image festgelegt ist.
  • Klicken Sie mit der rechten Maustaste und wählen Sie „Alle Vorkommen ändern“ aus.
  • Geben Sie den neuen Namen als demo-app ein.
  1. Einträge in der Kubernetes-Konfiguration ändern
  • deployment.yaml-Datei öffnen
  • Wählen Sie den Bildnamen aus, der derzeit als pom-xml-image festgelegt ist.
  • Klicken Sie mit der rechten Maustaste und wählen Sie „Alle Vorkommen ändern“ aus.
  • Geben Sie den neuen Namen als demo-app ein.

Modus für automatische Synchronisierung aktivieren

Um ein optimiertes Hot-Reload-Erlebnis zu ermöglichen, nutzen Sie die von Jib bereitgestellte Synchronisierungsfunktion. In diesem Schritt konfigurieren Sie Skaffold so, dass diese Funktion im Build-Prozess verwendet wird.

Das Profil „sync“, das Sie in der Skaffold-Konfiguration konfigurieren, nutzt das Spring-Profil „sync“, das Sie im vorherigen Schritt konfiguriert haben. Dort haben Sie die Unterstützung für Spring-Devtools aktiviert.

  1. Skaffold-Konfiguration aktualisieren

Ersetzen Sie in der Datei skaffold.yaml den gesamten Build-Abschnitt der Datei durch die folgende Spezifikation. Ändern Sie keine anderen Abschnitte der Datei.

skaffold.yaml

build:
  artifacts:
  - image: demo-app
    jib:
      project: com.example:demo
      type: maven
      args: 
      - --no-transfer-progress
      - -Psync
      fromImage: gcr.io/distroless/java17-debian11:debug
    sync:
      auto: true

Standardroute hinzufügen

Erstellen Sie im Ordner /src/main/java/com/example/springboot/ eine Datei mit dem Namen HelloController.java.

a624f5dd0c477c09.png

Fügen Sie den folgenden Inhalt in die Datei ein, um eine Standard-HTTP-Route zu erstellen.

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. Entwicklungsprozess durchlaufen

In diesem Abschnitt führen Sie einige Schritte mit dem Cloud Code-Plug-in aus, um die grundlegenden Prozesse kennenzulernen und die Konfiguration und Einrichtung Ihrer Starteranwendung zu validieren.

Cloud Code ist in Skaffold eingebunden, um den Entwicklungsprozess zu optimieren. Wenn Sie Ihre Anwendung in den folgenden Schritten in GKE bereitstellen, erstellen Cloud Code und Skaffold automatisch Ihr Container-Image, übertragen es per Push in eine Container Registry und stellen Ihre Anwendung dann in GKE bereit. Dies geschieht im Hintergrund und die Details werden vom Entwicklerfluss abstrahiert. Cloud Code verbessert Ihren Entwicklungsprozess auch durch die Bereitstellung herkömmlicher Debugging- und Hotsync-Funktionen für die containerbasierte Entwicklung.

In Google Cloud anmelden

Klicken Sie auf das Cloud Code-Symbol und wählen Sie „In Google Cloud anmelden“ aus:

1769afd39be372ff.png

Klicken Sie auf „Weiter zur Anmeldung“.

923bb1c8f63160f9.png

Prüfen Sie die Ausgabe im Terminal und öffnen Sie den Link:

517fdd579c34aa21.png

Melden Sie sich mit den Anmeldedaten Ihres Qwiklabs-Schülerkontos an.

db99b345f7a8e72c.png

Wählen Sie „Zulassen“ aus:

a5376553c430ac84.png

Kopieren Sie den Bestätigungscode und kehren Sie zum Tab „Workstation“ zurück.

6719421277b92eac.png

Fügen Sie den Bestätigungscode ein und drücken Sie die Eingabetaste.

e9847cfe3fa8a2ce.png

Kubernetes-Cluster hinzufügen

  1. Cluster hinzufügen

62a3b97bdbb427e5.png

  1. Wählen Sie Google Kubernetes Engine aus:

9577de423568bbaa.png

  1. Wählen Sie ein Projekt aus.

c5202fcbeebcd41c.png

  1. Wählen Sie „quote-cluster“ aus, das bei der Ersteinrichtung erstellt wurde.

366cfd8bc27cd3ed.png

9d68532c9bc4a89b.png

Aktuelle Projekt-ID mit der gcloud CLI festlegen

Kopieren Sie die Projekt-ID für dieses Lab von der Qwiklabs-Seite.

fcff2d10007ec5bc.png

Führen Sie den gcloud CLI-Befehl aus, um die Projekt-ID festzulegen. Ersetzen Sie die Beispielprojekt-ID, bevor Sie den Befehl ausführen.

gcloud config set project qwiklabs-gcp-project-id

Beispielausgabe:

f1c03d01b7ac112c.png

In Kubernetes debuggen

  1. Wählen Sie im linken Bereich unten „Cloud Code“ aus.

60b8e4e95868b561.png

  1. Wählen Sie im Bereich, der unter ENTWICKLUNGSSESSIONS angezeigt wird, „In Kubernetes debuggen“ aus.

Scrollen Sie nach unten, wenn die Option nicht sichtbar ist.

7d30833d96632ca0.png

  1. Wählen Sie „Ja“ aus, um den aktuellen Kontext zu verwenden.

a024a69b64de7e9e.png

  1. Wählen Sie „quote-cluster“ aus, das bei der Ersteinrichtung erstellt wurde.

faebabf372e3caf0.png

  1. Wählen Sie „Container Repository“ aus.

fabc6dce48bae1b4.png

  1. Wählen Sie im unteren Bereich den Tab „Ausgabe“ aus, um den Fortschritt und Benachrichtigungen zu sehen.
  2. Wählen Sie im Drop-down-Menü rechts „Kubernetes: Run/Debug – Detailed“ (Kubernetes: Ausführen/Debuggen – Detailliert) aus, um zusätzliche Details und Logs zu sehen, die live aus den Containern gestreamt werden.

86b44c59db58f8f3.png

Warten Sie, bis die Anwendung bereitgestellt wurde.

9f37706a752829fe.png

  1. Sehen Sie sich die bereitgestellte Anwendung in GKE in der Cloud Console an.

6ad220e5d1980756.png

  1. Sie können zur vereinfachten Ansicht zurückkehren, indem Sie auf dem Tab „OUTPUT“ im Drop-down-Menü die Option „Kubernetes: Run/Debug“ auswählen.
  2. Wenn der Build und die Tests abgeschlossen sind, wird auf dem Tab „Ausgabe“ Resource deployment/demo-app status completed successfully angezeigt und es wird eine URL aufgeführt: „Weitergeleitete URL vom Dienst demo-app: http://localhost:8080“.
  3. Bewegen Sie den Mauszeiger im Cloud Code-Terminal auf die URL in der Ausgabe (http://localhost:8080) und wählen Sie dann im angezeigten Infofeld die Option „Link öffnen“ aus.

28c5539880194a8e.png

Ein neuer Tab wird geöffnet und die folgende Ausgabe wird angezeigt:

d67253ca16238f49.png

Haltepunkte verwenden

  1. Öffnen Sie die HelloController.java-Anwendung unter /src/main/java/com/example/springboot/HelloController.java.
  2. Suchen Sie die Return-Anweisung für den Root-Pfad, die return String.format("Hello from your %s environment!", target); lautet.
  3. Fügen Sie der Zeile einen Haltepunkt hinzu, indem Sie auf den leeren Bereich links neben der Zeilennummer klicken. Eine rote Markierung zeigt an, dass der Haltepunkt festgelegt ist.

5027dc6da2618a39.png

  1. Laden Sie den Browser neu. Der Debugger hält den Prozess am Haltepunkt an und ermöglicht es Ihnen, die Variablen und den Status der Anwendung zu untersuchen, die remote in GKE ausgeführt wird.

71acfb426623cec2.png

  1. Klicken Sie im Bereich „Variablen“ nach unten, bis Sie die Variable „Ziel“ finden.
  2. Der aktuelle Wert ist „local“.

a1160d2ed2bb5c82.png

  1. Doppelklicken Sie auf den Variablennamen „target“ und

Ändern Sie den Wert in „Cloud Workstations“.

e597a556a5c53f32.png

  1. Klicken Sie im Debugging-Steuerfeld auf die Schaltfläche „Weiter“.

ec17086191770d0d.png

  1. Sehen Sie sich die Antwort in Ihrem Browser an. Dort wird jetzt der aktualisierte Wert angezeigt, den Sie gerade eingegeben haben.

6698a9db9e729925.png

  1. Entfernen Sie den Haltepunkt, indem Sie links neben der Zeilennummer auf die rote Markierung klicken. Dadurch wird verhindert, dass die Ausführung Ihres Codes in dieser Zeile gestoppt wird, wenn Sie mit diesem Lab fortfahren.

Hot Reload

  1. Ändern Sie die Anweisung so, dass ein anderer Wert zurückgegeben wird, z. B. „Hallo von %s Code“.
  2. Die Datei wird automatisch gespeichert und mit den Remote-Containern in GKE synchronisiert.
  3. Aktualisieren Sie Ihren Browser, um die aktualisierten Ergebnisse zu sehen.
  4. Beenden Sie die Debugging-Sitzung, indem Sie in der Debug-Symbolleiste auf das rote Quadrat klicken.

a541f928ec8f430e.png c2752bb28d82af86.png

Wählen Sie „Ja, nach jedem Lauf reinigen“ aus.

984eb2fa34867d70.png

5. Einfachen CRUD-REST-Dienst entwickeln

Ihre Anwendung ist jetzt vollständig für die containerisierte Entwicklung konfiguriert und Sie haben den grundlegenden Entwicklungs-Workflow mit Cloud Code durchlaufen. In den folgenden Abschnitten wenden Sie das Gelernte an, indem Sie REST-Dienstendpunkte hinzufügen, die eine Verbindung zu einer verwalteten Datenbank in Google Cloud herstellen.

Abhängigkeiten konfigurieren

Im Anwendungscode wird eine Datenbank verwendet, um die Daten des REST-Dienstes zu speichern. Achten Sie darauf, dass die Abhängigkeiten verfügbar sind, indem Sie Folgendes in die Datei „pom.xml“ einfügen:

  1. Öffnen Sie die Datei pom.xml und fügen Sie Folgendes in den Abschnitt „dependencies“ der Konfiguration ein:

pom.xml

    <!--  Database dependencies-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.flywaydb</groupId>
      <artifactId>flyway-core</artifactId>
    </dependency>
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>javax.persistence-api</artifactId>
      <version>2.2</version>
    </dependency>

REST-Dienst programmieren

Quote.java

Erstellen Sie im Ordner /src/main/java/com/example/springboot/ eine Datei mit dem Namen Quote.java und kopieren Sie den folgenden Code hinein. Hiermit wird das Entitätsmodell für das in der Anwendung verwendete Angebotsobjekt definiert.

package com.example.springboot;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import java.util.Objects;

@Entity
@Table(name = "quotes")
public class Quote
{
    @Id
    @Column(name = "id")
    private Integer id;

    @Column(name="quote")
    private String quote;

    @Column(name="author")
    private String author;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getQuote() {
        return quote;
    }

    public void setQuote(String quote) {
        this.quote = quote;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
        Quote quote1 = (Quote) o;
        return Objects.equals(id, quote1.id) &&
                Objects.equals(quote, quote1.quote) &&
                Objects.equals(author, quote1.author);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, quote, author);
    }
}

QuoteRepository.java

Erstellen Sie eine Datei mit dem Namen QuoteRepository.java unter src/main/java/com/example/springboot und kopieren Sie den folgenden Code hinein.

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

In diesem Code wird JPA zum Speichern der Daten verwendet. Die Klasse erweitert die Spring-Schnittstelle JPARepository und ermöglicht die Erstellung von benutzerdefiniertem Code. Im Code haben Sie eine benutzerdefinierte findRandomQuote-Methode hinzugefügt.

QuoteController.java

Um den Endpunkt für den Dienst verfügbar zu machen, stellt eine QuoteController-Klasse diese Funktionalität bereit.

Erstellen Sie eine Datei mit dem Namen QuoteController.java unter src/main/java/com/example/springboot und kopieren Sie den folgenden Inhalt hinein.

package com.example.springboot;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class QuoteController {

    private final QuoteRepository quoteRepository;

    public QuoteController(QuoteRepository quoteRepository) {
        this.quoteRepository = quoteRepository;
    }

    @GetMapping("/random-quote") 
    public Quote randomQuote()
    {
        return quoteRepository.findRandomQuote();  
    }

    @GetMapping("/quotes") 
    public ResponseEntity<List<Quote>> allQuotes()
    {
        try {
            List<Quote> quotes = new ArrayList<Quote>();
            
            quoteRepository.findAll().forEach(quotes::add);

            if (quotes.size()==0 || quotes.isEmpty()) 
                return new ResponseEntity<List<Quote>>(HttpStatus.NO_CONTENT);
                
            return new ResponseEntity<List<Quote>>(quotes, HttpStatus.OK);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<List<Quote>>(HttpStatus.INTERNAL_SERVER_ERROR);
        }        
    }

    @PostMapping("/quotes")
    public ResponseEntity<Quote> createQuote(@RequestBody Quote quote) {
        try {
            Quote saved = quoteRepository.save(quote);
            return new ResponseEntity<Quote>(saved, HttpStatus.CREATED);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }     

    @PutMapping("/quotes/{id}")
    public ResponseEntity<Quote> updateQuote(@PathVariable("id") Integer id, @RequestBody Quote quote) {
        try {
            Optional<Quote> existingQuote = quoteRepository.findById(id);
            
            if(existingQuote.isPresent()){
                Quote updatedQuote = existingQuote.get();
                updatedQuote.setAuthor(quote.getAuthor());
                updatedQuote.setQuote(quote.getQuote());

                return new ResponseEntity<Quote>(updatedQuote, HttpStatus.OK);
            } else {
                return new ResponseEntity<Quote>(HttpStatus.NOT_FOUND);
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }     

    @DeleteMapping("/quotes/{id}")
    public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        Optional<Quote> quote = quoteRepository.findById(id);
        if (quote.isPresent()) {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } else {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

}

Datenbankkonfigurationen hinzufügen

application.yaml

Fügen Sie die Konfiguration für die Backend-Datenbank hinzu, auf die der Dienst zugreift. Bearbeiten Sie die Datei application.yaml unter src/main/resources (oder erstellen Sie sie, falls sie nicht vorhanden ist) und fügen Sie eine parametrisierte Spring-Konfiguration für das Backend hinzu.

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

Datenbankmigration hinzufügen

Erstellen Sie Ordner db/migration unter src/main/resources.

SQL-Datei erstellen: V1__create_quotes_table.sql

Fügen Sie den folgenden Inhalt in die Datei ein.

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-Konfiguration

Durch die folgenden Ergänzungen der Datei deployment.yaml kann die Anwendung eine Verbindung zu den Cloud SQL-Instanzen herstellen.

  • TARGET: Konfiguriert die Variable, um die Umgebung anzugeben, in der die App ausgeführt wird.
  • SPRING_PROFILES_ACTIVE: Zeigt das aktive Spring-Profil an, das für cloud-dev konfiguriert wird.
  • DB_HOST: Die private IP-Adresse für die Datenbank, die beim Erstellen der Datenbankinstanz notiert wurde oder durch Klicken auf SQL im Navigationsmenü der Google Cloud Console angezeigt wird. Bitte ändern Sie den Wert.
  • DB_USER und DB_PASS – wie in der CloudSQL-Instanzkonfiguration festgelegt, als Secret in GCP gespeichert

Aktualisieren Sie Ihre Datei „deployment.yaml“ mit dem folgenden Inhalt.

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

Ersetzen Sie den Wert DB_HOST durch die Adresse Ihrer Datenbank, indem Sie die folgenden Befehle im Terminal ausführen:

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

Öffnen Sie „deployment.yaml“ und prüfen Sie, ob der DB_HOST-Wert mit der Instanz-IP aktualisiert wurde.

fd63c0aede14beba.png

Anwendung bereitstellen und validieren

  1. Wählen Sie im Bereich unten im Cloud Shell-Editor „Cloud Code“ und dann oben auf dem Bildschirm „Debug on Kubernetes“ aus.

33a5cf41aae91adb.png

  1. Wenn der Build und die Tests abgeschlossen sind, wird auf dem Tab „Ausgabe“ Resource deployment/demo-app status completed successfully angezeigt und eine URL wird aufgeführt: „Weitergeleitete URL vom Dienst demo-app: http://localhost:8080“. Manchmal kann der Port auch ein anderer sein, z. B. 8081. Legen Sie in diesem Fall den entsprechenden Wert fest. URL-Wert im Terminal festlegen
export URL=localhost:8080
  1. Zufällige Zitate ansehen

Führen Sie den folgenden Befehl im Terminal mehrmals für den Endpunkt „random-quote“ aus. Wiederholte Aufrufe geben unterschiedliche Angebote zurück

curl $URL/random-quote | jq
  1. Zitat hinzufügen

Erstellen Sie mit dem unten aufgeführten Befehl ein neues Angebot mit der ID 6 und beobachten Sie, wie die Anfrage zurückgegeben wird.

curl -H 'Content-Type: application/json' -d '{"id":"6","author":"Henry David Thoreau","quote":"Go confidently in the direction of your dreams! Live the life you have imagined"}' -X POST $URL/quotes
  1. Angebot löschen

Löschen Sie nun das gerade hinzugefügte Zitat mit der Methode „delete“ und beobachten Sie den Antwortcode HTTP/1.1 204.

curl -v -X DELETE $URL/quotes/6
  1. Serverfehler

Fehlerzustand durch erneutes Ausführen der letzten Anfrage, nachdem der Eintrag bereits gelöscht wurde

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

Beachten Sie, dass die Antwort eine HTTP:500 Internal Server Error zurückgibt.

Anwendung debuggen

Im vorherigen Abschnitt haben Sie einen Fehlerzustand in der Anwendung gefunden, als Sie versucht haben, einen Eintrag zu löschen, der nicht in der Datenbank vorhanden war. In diesem Abschnitt setzen Sie einen Haltepunkt, um das Problem zu finden. Der Fehler ist beim DELETE-Vorgang aufgetreten. Sie arbeiten also mit der QuoteController-Klasse.

  1. src/main/java/com/example/springboot/QuoteController.java öffnen
  2. deleteQuote()-Methode finden
  3. Suchen Sie die Zeile: Optional<Quote> quote = quoteRepository.findById(id);
  4. Setzen Sie einen Haltepunkt in dieser Zeile, indem Sie auf den leeren Bereich links neben der Zeilennummer klicken.
  5. Ein roter Indikator wird angezeigt, der darauf hinweist, dass der Haltepunkt festgelegt ist.
  6. Führen Sie den Befehl delete noch einmal aus.
curl -v -X DELETE $URL/quotes/6
  1. Klicken Sie auf das Symbol in der linken Spalte, um zur Debug-Ansicht zurückzukehren.
  2. Beobachten Sie, dass die Debugzeile in der Klasse „QuoteController“ angehalten wurde.
  3. Klicken Sie im Debugger auf das Symbol step over b814d39b2e5f3d9e.png.
  4. Beachten Sie, dass ein Code den HTTP-Fehler 500 (Internal Server Error) an den Client zurückgibt, was nicht ideal ist.
   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

Code aktualisieren

Der Code ist falsch und der else-Block sollte so umgestaltet werden, dass der HTTP-Statuscode „404 Not Found“ zurückgegeben wird.

Korrigieren Sie den Fehler.

  1. Während die Debugging-Sitzung noch läuft, schließen Sie die Anfrage ab, indem Sie in der Debugging-Steuerung auf die Schaltfläche „Weiter“ klicken.
  2. Ändern Sie als Nächstes den else-Block in den folgenden Code:
       else {
                return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
            }

Die Methode sollte so aussehen:

@DeleteMapping("/quotes/{id}")
public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        Optional<Quote> quote = quoteRepository.findById(id);
        if (quote.isPresent()) {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } else {
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        }
    }
  1. Löschbefehl noch einmal ausführen
curl -v -X DELETE $URL/quotes/6
  1. Gehen Sie den Debugger durch und beobachten Sie, wie der HTTP-Fehler 404 „Not Found“ an den Aufrufer zurückgegeben wird.
   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. Beenden Sie die Debugging-Sitzung, indem Sie in der Debug-Symbolleiste auf das rote Quadrat klicken.

12bc3c82f63dcd8a.png

6f19c0f855832407.png

6. Glückwunsch

Glückwunsch! In diesem Lab haben Sie eine neue Java-Anwendung von Grund auf erstellt und so konfiguriert, dass sie effektiv mit Containern zusammenarbeitet. Anschließend haben Sie Ihre Anwendung in einem Remote-GKE-Cluster bereitgestellt und dort Fehler behoben. Dabei haben Sie denselben Entwickler-Workflow verwendet, der auch bei herkömmlichen Anwendungsstacks zum Einsatz kommt.

Das haben Sie gelernt

  • Inner-Loop-Entwicklung mit Cloud Workstations
  • Neue Java-Starteranwendung erstellen
  • Entwicklungsprozess durchlaufen
  • Einfachen CRUD-REST-Dienst entwickeln
  • Anwendungsfehler in GKE-Clustern beheben
  • Anwendung mit Cloud SQL-Datenbank verbinden