Programowanie z użyciem Cloud Workstations i Cloud Code

1. Omówienie

W tym module przedstawiamy funkcje i możliwości zaprojektowane w celu usprawnienia procesu programowania dla inżynierów, których zadaniem jest tworzenie aplikacji w języku Java w skonteneryzowanym środowisku. Typowe tworzenie kontenerów wymaga, aby użytkownik znał szczegóły kontenerów i proces ich tworzenia. Poza tym deweloperzy zwykle muszą przerwać przepływ pracy, wychodząc z IDE, aby przetestować i debugować aplikacje w środowiskach zdalnych. Dzięki narzędziom i technologiom wspomnianym w tym samouczku deweloperzy mogą wydajnie pracować z aplikacjami skonteneryzowanymi bez opuszczania IDE.

Czego się nauczysz

W tym module nauczysz się, jak tworzyć aplikacje z wykorzystaniem kontenerów w GCP, takich jak:

  • Programowanie InnerLoop z użyciem Cloud Workstations
  • Tworzenie nowej aplikacji startowej Java
  • Omówienie procesu programowania
  • Opracowanie prostej usługi CRUD Rest Service
  • Debugowanie aplikacji w klastrze GKE
  • Łączę aplikację z bazą danych Cloud SQL

58a4cdd3ed7a123a.png

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

  1. Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub wykorzystaj już istniejący. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Nazwa projektu jest wyświetlaną nazwą uczestników tego projektu. To ciąg znaków, który nie jest używany przez interfejsy API Google. W każdej chwili możesz ją zmienić.
  • Identyfikator projektu jest unikalny we wszystkich projektach Google Cloud i nie można go zmienić (po jego ustawieniu nie można go zmienić). Cloud Console automatycznie wygeneruje unikalny ciąg znaków. zwykle nieważne, co ona jest. W większości ćwiczeń z programowania konieczne jest odwołanie się do identyfikatora projektu (zwykle nazywa się on PROJECT_ID). Jeśli nie podoba Ci się wygenerowany identyfikator, możesz wygenerować kolejny losowy. Możesz też spróbować własnych sił i sprawdzić, czy jest dostępna. Potem nie będzie można go zmienić. Pozostanie ono przez czas trwania projektu.
  • Dostępna jest trzecia wartość, numer projektu, z którego korzystają niektóre interfejsy API. Więcej informacji o wszystkich 3 wartościach znajdziesz w dokumentacji.
  1. Następnie musisz włączyć płatności w Cloud Console, aby korzystać z zasobów Cloud/interfejsów API. Ukończenie tego ćwiczenia z programowania nie powinno kosztować zbyt wiele. Aby wyłączyć zasoby, aby nie naliczać opłat po zakończeniu tego samouczka, możesz usunąć utworzone zasoby lub cały projekt. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego o wartości 300 USD.

Uruchom edytor Cloud Shell

Ten moduł został opracowany i przetestowany pod kątem użycia z edytorem Google Cloud Shell. Aby uzyskać dostęp do edytora:

  1. wejdź na stronę swojego projektu Google na https://console.cloud.google.com.
  2. W prawym górnym rogu kliknij ikonę edytora Cloud Shell.

8560cc8d45e8c112.png

  1. Na dole okna otworzy się nowy panel
  2. Kliknij przycisk Otwórz edytor

9E504cb98a6a8005.png

  1. Edytor otworzy się z eksploratorem po prawej stronie i edytorem w obszarze środkowym.
  2. Okienko terminala powinno być też dostępne u dołu ekranu
  3. Jeśli terminal NIE jest otwarty, użyj kombinacji klawiszy „Ctrl+”, aby otworzyć nowe okno terminala

Konfigurowanie gcloud

W Cloud Shell ustaw identyfikator projektu i region, w którym chcesz wdrożyć aplikację. Zapisz je jako zmienne PROJECT_ID i REGION.

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

Klonowanie kodu źródłowego

Kod źródłowy tego modułu znajduje się w module container-developer-workshop w GoogleCloudPlatform na GitHubie. Skopiuj go za pomocą poniższego polecenia, a następnie przejdź do katalogu.

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

Udostępnij infrastrukturę używaną w tym module

W tym module wdrożysz kod w GKE i uzyskasz dostęp do danych przechowywanych w bazie danych Cloud SQL. Poniższy skrypt konfiguracji przygotowuje dla Ciebie tę infrastrukturę. Proces obsługi administracyjnej potrwa ponad 25 minut. Zanim przejdziesz do następnej sekcji, poczekaj, aż skrypt się zakończy.

./setup_with_cw.sh &

Klaster Cloud Workstations

Otwórz Cloud Workstations w konsoli Cloud. Poczekaj, aż klaster zmieni stan na READY.

305e1a3d63ac7ff6.png

Tworzenie konfiguracji stacji roboczych

Jeśli sesja Cloud Shell została odłączona, kliknij „Połącz ponownie” a następnie uruchom polecenie wiersza poleceń gcloud, aby ustawić identyfikator projektu. Zanim uruchomisz polecenie, zastąp przykładowy identyfikator projektu poniżej identyfikatorem projektu qwiklabs.

gcloud config set project qwiklabs-gcp-project-id

Aby utworzyć konfigurację Cloud Workstations, uruchom poniższy skrypt w terminalu.

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

Sprawdź wyniki w sekcji Konfiguracje. Przejście w stan GOTOWY zajmie 2 minuty.

7a6af5aa2807a5f2.png

Otwórz w konsoli Cloud Workstations i utwórz nową instancję.

a53adeeac81a78c8.png

Zmień nazwę na my-workstation i wybierz istniejącą konfigurację: codeoss-java.

f21c216997746097.png

Sprawdź wyniki w sekcji Stacje robocze.

66a9fc8b20543e32.png

Uruchom stację roboczą

Uruchom i uruchom stację roboczą.

c91bb69b61ec8635.png

Zezwalaj na pliki cookie innych firm, klikając ikonę na pasku adresu. 1b8923e2943f9bc4.png

fcf9405b6957b7d7.png

Kliknij „Witryna nie działa?”.

36a84c0e2e3b85b.png

Kliknij przycisk „Zezwalaj na pliki cookie”.

2259694328628fba.png

Po uruchomieniu stacji roboczej pojawi się okno Code OSS IDE. Kliknij „Oznacz jako gotowe”. na pierwszej stronie wprowadzającej do IDE stacji roboczej

94874fba9b74cc22.png

3. Tworzenie nowej aplikacji startowej Java

W tej sekcji utworzysz od podstaw nową aplikację Java Spring Boot, korzystając z przykładowej aplikacji udostępnionej przez spring.io. Otwórz nowy terminal.

c31d48f2e4938c38.png

Klonowanie przykładowej aplikacji

  1. Tworzenie aplikacji startowej
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

Jeśli widzisz ten komunikat, kliknij przycisk Zezwalaj, aby skopiować i wkleić do stacji roboczej.

58149777e5cc350a.png

  1. Rozpakuj aplikację
unzip sample-app.zip -d sample-app
  1. Otwórz „sample-app”. folder
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

Dodaj narzędzia deweloperskie i Jib

Aby włączyć Spring Boot DevTools, znajdź i otwórz plik pom.xml w eksploratorze w edytorze. Następnie wklej następujący kod po linii tekstu, która brzmi <description>Demo project for Spring Boot</description>

  1. Dodaj narzędzie spring-boot-devtools w pliku pom.xml

Otwórz pom.xml w katalogu głównym projektu. Dodaj poniższą konfigurację po wpisie 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. Włącz jib-maven-plugin w pom.xml

Jib to oferowane przez Google narzędzie do konteneryzacji języka Java, które umożliwia programistom Java tworzenie kontenerów przy użyciu znanych im narzędzi Java. Jib to szybkie i proste narzędzie do tworzenia obrazów kontenerów, które wykonuje wszystkie etapy pakowania aplikacji w obraz kontenera. Nie wymaga napisania pliku Dockerfile ani zainstalowania Dockera. Usługa jest też bezpośrednio zintegrowana z Maven i Gradle.

Przewiń plik pom.xml w dół i zaktualizuj sekcję Build, aby uwzględnić wtyczkę Jib. Po zakończeniu sekcja kompilacji powinna być zgodna z podanymi niżej informacjami.

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>

Generuj pliki manifestu

Skaffold udostępnia zintegrowane narzędzia, które upraszczają tworzenie kontenerów. W tym kroku zainicjujesz Skaffold, który automatycznie utworzy podstawowe pliki YAML Kubernetes. Proces ten próbuje zidentyfikować katalogi z definicjami obrazów kontenerów, na przykład plik Dockerfile, a następnie tworzy dla każdego z nich plik manifestu wdrożenia i usługi.

Aby rozpocząć proces, wykonaj poniższe polecenie w terminalu.

d869e0cd38e983d7.png

  1. Wykonaj w terminalu to polecenie
skaffold init --generate-manifests
  1. Gdy pojawi się komunikat:
  • Użyj strzałek, by przenieść kursor do: Jib Maven Plugin
  • Naciśnij spację, aby wybrać opcję.
  • Aby kontynuować, naciśnij Enter
  1. Wpisz 8080 jako numer portu
  2. Wpisz y, aby zapisać konfigurację.

Do obszarów roboczych skaffold.yaml i deployment.yaml zostaną dodane 2 pliki

Dane wyjściowe Skaffold:

b33cc1e0c2077ab8.png

Zaktualizuj nazwę aplikacji

Domyślne wartości uwzględnione w konfiguracji nie są obecnie zgodne z nazwą Twojej aplikacji. Zaktualizuj pliki, aby odwoływały się do nazwy aplikacji zamiast do wartości domyślnych.

  1. Zmień wpisy w konfiguracji Skaffold
  • Otwórz: skaffold.yaml
  • Wybierz nazwę obrazu, który jest obecnie ustawiony jako pom-xml-image
  • Kliknij prawym przyciskiem myszy i wybierz Zmień wszystkie wystąpienia
  • Wpisz nową nazwę jako demo-app
  1. Zmień wpisy w konfiguracji Kubernetes
  • Otwórz plik deployment.yaml
  • Wybierz nazwę obrazu, który jest obecnie ustawiony jako pom-xml-image
  • Kliknij prawym przyciskiem myszy i wybierz Zmień wszystkie wystąpienia
  • Wpisz nową nazwę jako demo-app

Włącz tryb automatycznej synchronizacji

Aby zoptymalizować proces ponownego ładowania z pamięci, użyjesz funkcji synchronizacji dostarczanej przez Jib. W tym kroku skonfigurujesz Skaffold tak, aby używał tej funkcji w procesie kompilacji.

Pamiętaj, że funkcja synchronizacji profil konfigurowany w konfiguracji Skaffold korzysta ze springowej synchronizacji Skonfigurowany w poprzednim kroku profil z włączoną obsługą narzędzi wiosennych dla programistów.

  1. Aktualizowanie konfiguracji Skaffold

W pliku skaffold.yaml zastąp całą sekcję kompilacji pliku podaną poniżej. Nie zmieniaj innych sekcji pliku.

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

Dodaj trasę domyślną

Utwórz plik o nazwie HelloController.java w folderze /src/main/java/com/example/springboot/.

a624f5dd0c477c09.png

Wklej poniższą zawartość w pliku, aby utworzyć domyślną trasę HTTP.

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. Omówienie procesu programowania

W tej sekcji zapoznasz się z kilkoma krokami korzystania z wtyczki Cloud Code, które pozwolą Ci poznać podstawowe procesy i sprawdzić konfigurację oraz konfigurację aplikacji startowej.

Cloud Code integruje się ze Skaffold, aby usprawnić proces programowania. Gdy wdrożysz obraz kontenera w GKE w poniższych krokach, Cloud Code i Skaffold automatycznie skompilują obraz kontenera, wypchnie go do Container Registry, a następnie wdroży aplikację w GKE. Dzieje się to za kulisami, odbierając szczegóły od procesu deweloperskiego. Cloud Code usprawnia też proces programowania, udostępniając tradycyjne funkcje debugowania i synchronizacji przy użyciu kontenerów podczas programowania.

Logowanie się do Google Cloud

Kliknij ikonę Cloud Code i wybierz opcję „Sign in to Google Cloud” (Zaloguj się w Google Cloud):

1769afd39be372ff.png

Kliknij „Przejdź do logowania”.

923bb1c8f63160f9.png

Sprawdź dane wyjściowe w terminalu i otwórz link:

517fdd579c34aa21.png

Zaloguj się danymi logowania uczniów Qwiklabs.

db99b345f7a8e72c.png

Wybierz „Zezwól”:

a5376553c430ac84.png

Skopiuj kod weryfikacyjny i wróć na kartę Workstations.

6719421277b92eac.png

Wklej kod weryfikacyjny i naciśnij Enter.

e9847cfe3fa8a2ce.png

Dodaj klaster Kubernetes

  1. Dodaj klaster

62a3b97bdbb427e5.png

  1. Wybierz Google Kubernetes Engine:

9577de423568bbaa.png

  1. Wybierz projekt.

c5202fcbeebcd41c.png

  1. Wybierz „quote-cluster” utworzony podczas początkowej konfiguracji.

366cfd8bc27cd3ed.png

9d68532c9bc4a89b.png

Ustaw identyfikator bieżącego projektu za pomocą wiersza poleceń gcloud

Skopiuj identyfikator projektu powiązany z tym modułem ze strony qwiklabs.

fcff2d10007ec5bc.png

Aby ustawić identyfikator projektu, uruchom polecenie interfejsu wiersza poleceń gcloud. Zastąp przykładowy identyfikator projektu przed uruchomieniem polecenia.

gcloud config set project qwiklabs-gcp-project-id

Przykładowe dane wyjściowe:

f1c03d01b7ac112c.png

Debuguj w Kubernetes

  1. W panelu po lewej stronie u dołu kliknij Cloud Code.

60b8e4e95868b561.png

  1. W panelu, który pojawi się w sekcji DEVELOPMENT SESSIONS, wybierz Debug on Kubernetes.

Przewiń w dół, jeśli ta opcja nie jest widoczna.

7D30833d96632ca0.png

  1. Wybierz „Tak”. aby użyć bieżącego kontekstu.

a024a69b64de7e9e.png

  1. Wybierz „quote-cluster” utworzony podczas początkowej konfiguracji.

faebabf372e3caf0.png

  1. Wybierz Repozytorium kontenerów.

fabc6dce48bae1b4.png

  1. Wybierz kartę Wyniki w dolnym panelu, aby zobaczyć postęp i powiadomienia.
  2. Wybierz „Kubernetes: Run/Debug - detail”. w menu kanału po prawej stronie, aby wyświetlić dodatkowe szczegóły i logi, które są przesyłane na żywo z kontenerów.

86b44c59db58f8f3.png

Poczekaj na wdrożenie aplikacji.

9f37706a752829fe.png

  1. Sprawdź wdrożoną aplikację w GKE w konsoli Cloud.

6ad220e5d1980756.png

  1. Wróć do widoku uproszczonego, wybierając „Kubernetes: Run/Debug”. z menu na karcie OUTPUT (WYJŚCIE).
  2. Po zakończeniu kompilacji i testów na karcie Dane wyjściowe będzie widoczny komunikat Resource deployment/demo-app status completed successfully oraz adres URL: „Przekierowany adres URL z aplikacji demonstracyjnej usługi: http://localhost:8080”.
  3. W terminalu Cloud Code najedź kursorem na adres URL w danych wyjściowych (http://localhost:8080), a następnie we wskazówce, która się wyświetli, kliknij link Obserwuj.

28c5539880194a8e.png

Nowa karta zostanie otwarta, a dane wyjściowe zobaczysz poniżej:

d67253ca16238f49.png

Wykorzystuj punkty przerwania

  1. Otwórz aplikację HelloController.java pod adresem /src/main/java/com/example/springboot/HelloController.java.
  2. Znajdź instrukcję zwrotną dla ścieżki głównej, która brzmi: return String.format("Hello from your %s environment!", target);
  3. Dodaj do tego wiersza punkt przerwania, klikając puste miejsce po lewej stronie numeru wiersza. Pojawi się czerwony wskaźnik informujący o ustawieniu punktu przerwania

5027dc6da2618a39.png

  1. Załaduj ponownie przeglądarkę. Pamiętaj, że debuger zatrzymuje proces w punkcie przerwania i umożliwia zbadanie zmiennych oraz stanu aplikacji uruchomionej zdalnie w GKE.

71acfb426623cec2.png

  1. Przejdź do sekcji zmiennych, aż znajdziesz sekcję „Wartość docelowa” .
  2. Obserwuj obecną wartość jako „local”

a1160d2ed2bb5c82.png

  1. Kliknij dwukrotnie zmienną o nazwie „target”. a w wyskakującym okienku

zmień wartość na „Cloud Workstations”

e597a556a5c53f32.png

  1. Kliknij przycisk Dalej w panelu sterowania debugowania.

ec17086191770d0d.png

  1. Sprawdź odpowiedź w przeglądarce, w której wyświetla się wprowadzona przed chwilą zaktualizowana wartość.

6698a9db9e729925.png

  1. Usuń punkt przerwania, klikając czerwony wskaźnik po lewej stronie numeru wiersza. Dzięki temu kod nie zatrzyma wykonywania w tym wierszu w dalszej części tego modułu.

Ponowne załadowanie „na gorąco”

  1. Zmień instrukcję tak, aby zwracała inną wartość, na przykład „Cześć od %s Code”
  2. Plik jest automatycznie zapisywany i synchronizowany z kontenerami zdalnymi w GKE
  3. Aby zobaczyć zaktualizowane wyniki, odśwież przeglądarkę.
  4. Zatrzymaj sesję debugowania, klikając czerwony kwadrat na pasku narzędzi debugowania.

a541f928ec8f430e.png c2752bb28d82af86.png

Wybierz „Tak wyczyść po każdym uruchomieniu”.

984eb2fa34867d70.png

5. Opracowanie prostej usługi CRUD Rest Service

Na tym etapie Twoja aplikacja jest w pełni skonfigurowana do programowania skonteneryzowanego i masz już za sobą podstawowy przepływ pracy programistyczny w Cloud Code. W kolejnych sekcjach przećwiczysz zdobyte informacje, dodając punkty końcowe usługi REST łączące się z zarządzaną bazą danych w Google Cloud.

Skonfiguruj zależności

Kod aplikacji używa bazy danych do utrwalania danych usługi reszty. Sprawdź, czy zależności są dostępne, dodając do pliku pom.xl ten kod

  1. Otwórz plik pom.xml i dodaj poniższy kod w sekcji zależności konfiguracji

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>

Usługa REST kodu

Quote.java

Utwórz w usłudze /src/main/java/com/example/springboot/ plik o nazwie Quote.java i skopiuj poniższy kod. Definiuje model encji dla obiektu Offer używanego w aplikacji.

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

Utwórz plik o nazwie QuoteRepository.java na stronie src/main/java/com/example/springboot i skopiuj ten kod

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

Ten kod wykorzystuje JPA do utrwalania danych. Klasa rozszerza interfejs Spring JPARepository i umożliwia tworzenie kodu niestandardowego. W kodzie dodano niestandardową metodę findRandomQuote.

QuoteController.java

Aby udostępnić punkt końcowy usługi, klasa QuoteController udostępnia tę funkcję.

Utwórz plik o nazwie QuoteController.java o src/main/java/com/example/springboot i skopiuj tę treść

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

}

Dodaj konfiguracje bazy danych

application.yaml

Dodaj konfigurację bazy danych backendu, do której usługa ma dostęp. Edytuj (lub utwórz, jeśli nie istnieje) plik o nazwie application.yaml w katalogu src/main/resources i dodaj z parametrami konfigurację Spring dla backendu.

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

Dodaj usługę migracji bazy danych

Tworzenie folderów db/migration pod adresem src/main/resources

Utwórz plik SQL: V1__create_quotes_table.sql

Wklej do pliku tę zawartość

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

Konfiguracja Kubernetes

Poniższe dodatki do pliku deployment.yaml umożliwiają aplikacji łączenie się z instancjami Cloud SQL.

  • TARGET – konfiguruje zmienną, aby wskazać środowisko, w którym aplikacja jest wykonywana
  • SPRING_PROFILES_ACTIVE – pokazuje aktywny profil Spring, który zostanie skonfigurowany jako cloud-dev
  • DB_HOST – prywatny adres IP bazy danych, który został zanotowany podczas tworzenia instancji bazy danych lub po kliknięciu SQL w menu nawigacyjnym konsoli Google Cloud – zmień wartość.
  • DB_USER i DB_PASS – wartość ustawiona w konfiguracji instancji Cloud SQL, zapisana jako obiekt tajny w GCP

Zaktualizuj plik Deployment.yaml poniżej, podając poniższą zawartość.

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

Zastąp wartość DB_HOST adresem bazy danych, uruchamiając w terminalu te polecenia:

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

Otwórz plik Deployment.yaml i sprawdź, czy wartość DB_HOST została zaktualizowana przy użyciu adresu IP instancji.

fd63c0aede14beba.png

Wdróż i zweryfikuj aplikację

  1. W panelu u dołu edytora Cloud Shell kliknij Cloud Code, a potem u góry ekranu wybierz Debug on Kubernetes (Debuguj w Kubernetes).

33a5cf41aae91adb.png

  1. Po zakończeniu kompilacji i testów na karcie Dane wyjściowe będzie widoczny komunikat Resource deployment/demo-app status completed successfully oraz adres URL: „Przekierowany adres URL z aplikacji demonstracyjnej usługi: http://localhost:8080”. Pamiętaj, że czasami numer portu może być inny, np. 8081. W takim przypadku ustaw odpowiednią wartość. Ustaw wartość adresu URL w terminalu
export URL=localhost:8080
  1. Wyświetl losowe cytaty

W terminalu uruchom poniższe polecenie kilka razy, kierując się punktem końcowym w postaci losowej cudzysłowu. Zwróć uwagę na powtarzające się wywołanie zwracające różne cudzysłowy

curl $URL/random-quote | jq
  1. Dodaj wycenę

Utwórz nową wycenę o wartości id=6 za pomocą polecenia wymienionego poniżej i sprawdź, czy żądanie jest powtarzane

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. Usuwanie wyceny

Teraz usuń dodany przed chwilą cytat za pomocą metody usuwania i obserwuj kod odpowiedzi HTTP/1.1 204.

curl -v -X DELETE $URL/quotes/6
  1. Błąd serwera

Wystąpił błąd podczas ponownego uruchamiania ostatniego żądania po usunięciu wpisu

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

Zauważ, że odpowiedź zwraca wartość HTTP:500 Internal Server Error.

Debugowanie aplikacji

W poprzedniej sekcji podczas próby usunięcia wpisu, którego nie było w bazie danych, wystąpił w aplikacji stan błędu. W tej sekcji ustawisz punkt przerwania, który pozwoli zlokalizować problem. Błąd wystąpił w operacji DELETE, więc będziesz pracować z klasą citeController.

  1. Otwórz: src/main/java/com/example/springboot/QuoteController.java
  2. Znajdź metodę deleteQuote()
  3. Znajdź linię: Optional<Quote> quote = quoteRepository.findById(id);
  4. Aby ustawić punkt przerwania w tym wierszu, kliknij puste miejsce po lewej stronie numeru wiersza.
  5. Pojawi się czerwony wskaźnik wskazujący, że punkt przerwania został ustawiony.
  6. Ponownie uruchom polecenie delete
curl -v -X DELETE $URL/quotes/6
  1. Aby wrócić do widoku debugowania, kliknij ikonę w lewej kolumnie
  2. Zwróć uwagę na wiersz debugowania zatrzymany w klasie citeController.
  3. W debugerze kliknij ikonę step over b814d39b2e5f3d9e.png.
  4. Zwróć uwagę, że kod zwraca do klienta wewnętrzny błąd serwera HTTP 500, co nie jest idealnym rozwiązaniem.
   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

Zaktualizuj kod

Kod jest nieprawidłowy i należy zrefaktoryzować blok else, aby odesłać kod stanu 404 (nie znaleziono).

Popraw błąd.

  1. Gdy sesja debugowania nadal działa, dokończ żądanie, naciskając „Dalej”. w panelu sterowania debugowania.
  2. Następnie zmień blok else na kod:
       else {
                return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
            }

Metoda powinna wyglądać mniej więcej tak:

@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. Ponownie uruchom polecenie usuwania
curl -v -X DELETE $URL/quotes/6
  1. Przejdź przez debuger i sprawdź, czy zwracany do elementu wywołującego wywołanie błędu HTTP 404 „Not Found”.
   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. Zatrzymaj sesję debugowania, klikając czerwony kwadrat na pasku narzędzi debugowania.

12bc3c82f63dcd8a.png

6f19c0f855832407.png

6. Gratulacje

Gratulacje! W tym module udało Ci się utworzyć od zera nową aplikację w języku Java i skonfigurować ją tak, aby wydajnie współpracowała z kontenerami. Następnie wdrożono i debugowałeś(-aś) aplikację w zdalnym klastrze GKE, postępując zgodnie z procedurą programistyczną obowiązującą w tradycyjnych stosach aplikacji.

Zdobyte informacje

  • Programowanie InnerLoop z użyciem Cloud Workstations
  • Tworzenie nowej aplikacji startowej Java
  • Omówienie procesu programowania
  • Programowanie prostej usługi CRUD REST
  • Debugowanie aplikacji w klastrze GKE
  • Łączę aplikację z bazą danych Cloud SQL