Programowanie z użyciem Cloud Workstations i Cloud Code

1. Przegląd

W tym module poznasz funkcje i możliwości, które usprawniają proces tworzenia aplikacji w języku Java w środowisku skonteneryzowanym. Typowe tworzenie kontenerów wymaga od użytkownika znajomości szczegółów dotyczących kontenerów i procesu kompilacji kontenerów. Deweloperzy muszą też zwykle przerywać pracę i opuszczać IDE, aby testować i debugować aplikacje w środowiskach zdalnych. Dzięki narzędziom i technologiom wymienionym w tym samouczku deweloperzy mogą efektywnie pracować z aplikacjami w kontenerach bez opuszczania środowiska IDE.

Czego się nauczysz

W tym module poznasz metody tworzenia aplikacji w kontenerach w GCP, w tym:

  • Programowanie w trybie inner loop z użyciem Cloud Workstations
  • Tworzenie nowej aplikacji startowej w Javie
  • Omówienie procesu tworzenia
  • Tworzenie prostej usługi REST CRUD
  • Debugowanie aplikacji w klastrze GKE
  • Łączenie aplikacji 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 użyj istniejącego. Jeśli nie masz jeszcze konta Gmail lub Google Workspace, musisz je utworzyć.

b35bf95b8bf3d5d8.pnga99b7ace416376c4.pngbd84a6d3004737c5.png

  • Nazwa projektu to wyświetlana nazwa dla uczestników tego projektu. Jest to ciąg znaków, który nie jest używany przez interfejsy API Google. Możesz ją zaktualizować w dowolnym momencie.
  • Identyfikator projektu jest unikalny we wszystkich projektach Google Cloud i nie można go zmienić po ustawieniu. Konsola Cloud automatycznie generuje unikalny ciąg znaków. Zwykle nie musisz się nim przejmować. W większości przewodników programowania musisz odwoływać się do identyfikatora projektu (zwykle jest on oznaczony jako PROJECT_ID). Jeśli wygenerowany identyfikator Ci się nie podoba, możesz wygenerować inny losowy identyfikator. Możesz też spróbować utworzyć własny adres i sprawdzić, czy jest dostępny. Po tym kroku nie można go zmienić i będzie obowiązywać przez cały czas trwania projektu.
  • Warto wiedzieć, że istnieje trzecia wartość, numer projektu, której używają niektóre interfejsy API. Więcej informacji o tych 3 wartościach znajdziesz w dokumentacji.
  1. Następnie musisz włączyć płatności w konsoli Cloud, aby korzystać z zasobów i interfejsów API Google Cloud. Ukończenie tego laboratorium nie powinno wiązać się z dużymi kosztami, a nawet z żadnymi. Aby wyłączyć zasoby i uniknąć naliczania 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, w którym mają do dyspozycji środki w wysokości 300 USD.

Uruchamianie edytora Cloud Shell

Ten moduł został zaprojektowany i przetestowany pod kątem używania w edytorze Google Cloud Shell. Aby uzyskać dostęp do edytora:

  1. uzyskać dostęp do projektu Google na stronie https://console.cloud.google.com;
  2. W prawym górnym rogu kliknij ikonę edytora Cloud Shell.

8560cc8d45e8c112.png

  1. U dołu 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 środkowej części.
  2. U dołu ekranu powinien być też dostępny panel terminala.
  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 repozytorium container-developer-workshop w GoogleCloudPlatform na GitHubie. Sklonuj go za pomocą polecenia poniżej, a potem przejdź do katalogu.

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

wdrożyć 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 przygotuje tę infrastrukturę. Proces obsługi administracyjnej potrwa ponad 25 minut. Zanim przejdziesz do następnej sekcji, poczekaj na zakończenie działania skryptu.

./setup_with_cw.sh &

Klaster Cloud Workstations

Otwórz Cloud Workstations w Cloud Console. Poczekaj, aż klaster osiągnie stan 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 gcloud cli, aby ustawić identyfikator projektu. Przed uruchomieniem polecenia 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 w terminalu poniższy skrypt.

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

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

7a6af5aa2807a5f2.png

Otwórz Cloud Workstations w konsoli 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 stację roboczą.

c91bb69b61ec8635.png

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

fcf9405b6957b7d7.png

Kliknij „Strona nie działa?”.

36a84c0e2e3b85b.png

Kliknij „Zezwalaj na pliki cookie”.

2259694328628fba.png

Po uruchomieniu stacji roboczej pojawi się środowisko IDE Code OSS. Na stronie Wprowadzenie w środowisku IDE stacji roboczej kliknij „Oznacz jako gotowe”.

94874fba9b74cc22.png

3. Tworzenie nowej aplikacji startowej w Javie

W tej sekcji utworzysz od zera 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 początkowej
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 zobaczysz ten komunikat, kliknij przycisk Zezwól, aby móc kopiować i wklejać na stacji roboczej.

58149777e5cc350a.png

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

Dodawanie spring-boot-devtools i Jib

Aby włączyć narzędzia Spring Boot DevTools, znajdź i otwórz plik pom.xml w eksploratorze w edytorze. Następnie wklej ten kod po wierszu opisu <description>Demo project for Spring Boot</description>

  1. Dodawanie spring-boot-devtools w pliku pom.xml

Otwórz plik pom.xml w katalogu głównym projektu. Dodaj tę 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 wtyczkę jib-maven-plugin w pliku pom.xml

Jib to narzędzie open source do konteneryzacji w języku Java od Google, które umożliwia programistom Java tworzenie kontenerów za pomocą znanych im narzędzi Java. Jib to szybki i prosty kreator obrazów kontenerów, który wykonuje wszystkie czynności związane z pakowaniem aplikacji w obraz kontenera. Nie wymaga pisania pliku Dockerfile ani instalowania Dockera i jest bezpośrednio zintegrowany z Mavenem i Gradle.

Przewiń w dół plik pom.xml i zaktualizuj sekcję Build, aby uwzględnić wtyczkę Jib. Po zakończeniu sekcja kompilacji powinna wyglądać tak:

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>

Generowanie plików 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 próbuje zidentyfikować katalogi z definicjami obrazów kontenerów, takie jak Dockerfile, a następnie tworzy dla każdego z nich manifest wdrożenia i usługi.

Aby rozpocząć proces, wykonaj w terminalu to polecenie:

d869e0cd38e983d7.png

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

Do obszaru roboczego dodawane są 2 pliki: skaffold.yaml i deployment.yaml.

Dane wyjściowe Skaffold:

b33cc1e0c2077ab8.png

Aktualizowanie nazwy aplikacji

Wartości domyślne uwzględnione w konfiguracji nie pasują obecnie do nazwy Twojej aplikacji. Zaktualizuj pliki, aby odwoływały się do nazwy aplikacji, a nie do wartości domyślnych.

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

Włącz tryb automatycznej synchronizacji

Aby ułatwić zoptymalizowane gorące przeładowanie, użyj funkcji synchronizacji udostępnianej przez Jib. W tym kroku skonfigurujesz Skaffold tak, aby korzystał z tej funkcji w procesie kompilacji.

Pamiętaj, że profil „sync” konfigurowany w konfiguracji Skaffold korzysta z profilu „sync” Springa skonfigurowanego w poprzednim kroku, w którym włączono obsługę spring-dev-tools.

  1. Aktualizowanie konfiguracji Skaffold

W pliku skaffold.yaml zastąp całą sekcję kompilacji tymi specyfikacjami. 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

Dodawanie trasy domyślnej

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

a624f5dd0c477c09.png

Wklej do pliku tę zawartość, 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 tworzenia

W tej sekcji wykonasz kilka czynności za pomocą wtyczki Cloud Code, aby poznać podstawowe procesy i sprawdzić konfigurację aplikacji startowej.

Cloud Code jest zintegrowany ze Skaffold, aby usprawnić proces programowania. Gdy w kolejnych krokach wdrożysz aplikację w GKE, Cloud Code i Skaffold automatycznie skompilują obraz kontenera, wypchną go do Container Registry, a następnie wdrożą aplikację w GKE. Dzieje się to za kulisami, co pozwala deweloperowi skupić się na innych aspektach. Cloud Code usprawnia też proces programowania, zapewniając tradycyjne funkcje debugowania i szybkiej synchronizacji w przypadku programowania opartego na kontenerach.

Logowanie się do Google Cloud

Kliknij ikonę Cloud Code i wybierz „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ę, używając danych logowania ucznia Qwiklabs.

db99b345f7a8e72c.png

Wybierz „Zezwól”:

a5376553c430ac84.png

Skopiuj kod weryfikacyjny i wróć na kartę stacji roboczej.

6719421277b92eac.png

Wklej kod weryfikacyjny i naciśnij Enter.

e9847cfe3fa8a2ce.png

Dodawanie klastra Kubernetes

  1. Dodawanie klastra

62a3b97bdbb427e5.png

  1. Wybierz Google Kubernetes Engine:

9577de423568bbaa.png

  1. Wybierz projekt.

c5202fcbeebcd41c.png

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

366cfd8bc27cd3ed.png9d68532c9bc4a89b.png

Ustawianie bieżącego identyfikatora projektu za pomocą gcloud CLI

Skopiuj identyfikator projektu na potrzeby tego modułu ze strony Qwiklabs.

fcff2d10007ec5bc.png

Uruchom polecenie gcloud CLI, aby ustawić identyfikator projektu. Przed uruchomieniem polecenia zastąp przykładowy identyfikator projektu.

gcloud config set project qwiklabs-gcp-project-id

Przykładowe dane wyjściowe:

f1c03d01b7ac112c.png

Debugowanie w Kubernetes

  1. W lewym panelu u dołu wybierz Cloud Code.

60b8e4e95868b561.png

  1. W panelu, który pojawi się w sekcji SESJE DEWELOPERSKIE, kliknij Debuguj w Kubernetes.

Jeśli nie widzisz tej opcji, przewiń w dół.

7d30833d96632ca0.png

  1. Aby użyć bieżącego kontekstu, wybierz „Tak”.

a024a69b64de7e9e.png

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

faebabf372e3caf0.png

  1. Wybierz repozytorium kontenerów.

fabc6dce48bae1b4.png

  1. Aby wyświetlić postęp i powiadomienia, w dolnym okienku wybierz kartę Wyjście.
  2. Aby wyświetlić dodatkowe szczegóły i dzienniki przesyłane strumieniowo na żywo z kontenerów, w menu po prawej stronie wybierz „Kubernetes: Run/Debug - Detailed” (Kubernetes: uruchamianie/debugowanie – szczegółowe).

86b44c59db58f8f3.png

Poczekaj na wdrożenie aplikacji.

9f37706a752829fe.png

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

6ad220e5d1980756.png

  1. Aby wrócić do widoku uproszczonego, w menu na karcie WYJŚCIE wybierz „Kubernetes: Run/Debug” (Kubernetes: uruchamianie/debugowanie).
  2. Po zakończeniu kompilacji i testów na karcie Wyniki pojawi się komunikat Resource deployment/demo-app status completed successfully i adres URL: „Forwarded URL from service demo-app: http://localhost:8080” (Przekierowany adres URL z usługi demo-app: http://localhost:8080).
  3. W terminalu Cloud Code najedź kursorem na adres URL w wyniku (http://localhost:8080), a następnie w wyświetlonej etykiecie narzędzia kliknij Follow link (Otwórz link).

28c5539880194a8e.png

Otworzy się nowa karta, na której zobaczysz te dane wyjściowe:

d67253ca16238f49.png

Wykorzystywanie punktów przerwania

  1. Otwórz aplikację HelloController.java znajdującą się w lokalizacji /src/main/java/com/example/springboot/HelloController.java
  2. Znajdź instrukcję powrotu dla ścieżki głównej, która brzmi return String.format("Hello from your %s environment!", target);
  3. Dodaj punkt przerwania do tego wiersza, klikając puste miejsce po lewej stronie numeru wiersza. Pojawi się czerwony wskaźnik, który oznacza, że punkt przerwania został ustawiony.

5027dc6da2618a39.png

  1. Odśwież przeglądarkę i zwróć uwagę, że debuger zatrzymuje proces w punkcie przerwania i umożliwia zbadanie zmiennych oraz stanu aplikacji, która działa zdalnie w GKE.

71acfb426623cec2.png

  1. Klikaj w sekcji zmiennych, aż znajdziesz zmienną „Cel”.
  2. Sprawdź, czy bieżąca wartość to „local”.

a1160d2ed2bb5c82.png

  1. Kliknij dwukrotnie nazwę zmiennej „target” i w wyskakującym okienku

zmień wartość na „Cloud Workstations”;

e597a556a5c53f32.png

  1. Na panelu sterowania debugowaniem kliknij przycisk Dalej.

ec17086191770d0d.png

  1. Sprawdź odpowiedź w przeglądarce, w której powinna się teraz wyświetlać wpisana przez Ciebie zaktualizowana wartość.

6698a9db9e729925.png

  1. Usuń punkt przerwania, klikając czerwony wskaźnik po lewej stronie numeru wiersza. Dzięki temu kod nie zatrzyma się w tym wierszu podczas wykonywania kolejnych kroków tego modułu.

Gorące przeładowanie

  1. Zmień instrukcję, aby zwracała inną wartość, np. „Hello from %s Code”.
  2. Plik jest automatycznie zapisywany i synchronizowany w kontenerach zdalnych w GKE.
  3. Aby zobaczyć zaktualizowane wyniki, odśwież przeglądarkę.
  4. Zakończ sesję debugowania, klikając czerwony kwadrat na pasku narzędzi debugowania.

a541f928ec8f430e.png c2752bb28d82af86.png

Wybierz „Tak, zwalniaj miejsce po każdym uruchomieniu”.

984eb2fa34867d70.png

5. Tworzenie prostej usługi REST CRUD

Na tym etapie aplikacja jest w pełni skonfigurowana pod kątem tworzenia skonteneryzowanych aplikacji, a Ty znasz już podstawowy przepływ pracy programisty w Cloud Code. W kolejnych sekcjach przećwiczysz zdobytą wiedzę, dodając punkty końcowe usługi REST łączące się z zarządzaną bazą danych w Google Cloud.

Konfigurowanie zależności

Kod aplikacji używa bazy danych do przechowywania danych usługi REST. Upewnij się, że zależności są dostępne, dodając poniższy kod do pliku pom.xml.

  1. Otwórz plik pom.xml i dodaj ten kod do sekcji zależności w 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 Code

Quote.java

Utwórz w katalogu /src/main/java/com/example/springboot/ plik o nazwie Quote.java i skopiuj do niego poniższy kod. Określa model jednostki dla obiektu Quote 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 w katalogu src/main/java/com/example/springboot i skopiuj do niego 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 używa interfejsu JPA do utrwalania danych. Klasa rozszerza interfejs Spring JPARepository i umożliwia tworzenie niestandardowego kodu. W dodawanym kodzie znajduje się metoda niestandardowa findRandomQuote.

QuoteController.java

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

Utwórz plik o nazwie QuoteController.java w katalogu src/main/java/com/example/springboot i skopiuj do niego 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);
        }
    }

}

Dodawanie konfiguracji 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 folderze src/main/resources i dodaj sparametryzowaną konfigurację Springa 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

Dodawanie usługi migracji bazy danych

Utwórz foldery db/migration w folderze 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 wskazywała środowisko, w którym jest wykonywana aplikacja.
  • SPRING_PROFILES_ACTIVE – pokazuje aktywny profil Spring, który będzie skonfigurowany na cloud-dev.
  • DB_HOST – prywatny adres IP bazy danych, który został zapisany podczas tworzenia instancji bazy danych lub po kliknięciu SQL w menu nawigacyjnym konsoli Google Cloud – zmień wartość.
  • DB_USER i DB_PASS – zgodnie z ustawieniami w konfiguracji instancji Cloud SQL, przechowywane jako Secret w GCP.

Zaktualizuj plik deployment.yaml, wstawiając do niego 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 o adres IP instancji.

fd63c0aede14beba.png

Wdrażanie i weryfikowanie aplikacji

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

33a5cf41aae91adb.png

  1. Po zakończeniu kompilacji i testów na karcie Wyniki pojawi się komunikat Resource deployment/demo-app status completed successfully i adres URL: „Forwarded URL from service demo-app: http://localhost:8080” (Przekierowany adres URL z usługi demo-app: http://localhost:8080). Pamiętaj, że czasami port może być inny, np. 8081. Jeśli tak, ustaw odpowiednią wartość. Ustawianie wartości adresu URL na terminalu
export URL=localhost:8080
  1. Wyświetlanie losowych cytatów

W terminalu uruchom poniższe polecenie kilka razy w odniesieniu do punktu końcowego random-quote. Obserwowanie powtarzających się wywołań zwracających różne wyceny

curl $URL/random-quote | jq
  1. Dodawanie wyceny

Utwórz nową ofertę z identyfikatorem 6 za pomocą polecenia podanego poniżej i sprawdź, czy żądanie zostało zwrócone.

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 cytat za pomocą metody delete i sprawdź kod odpowiedzi HTTP/1.1 204.

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

Wywołaj stan błędu, ponownie uruchamiając ostatnie żądanie po usunięciu wpisu.

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

Zwróć uwagę, że odpowiedź zwraca wartość HTTP:500 Internal Server Error.

Debugowanie aplikacji

W poprzedniej sekcji wystąpił błąd w aplikacji, gdy próbowano usunąć wpis, którego nie było w bazie danych. W tej sekcji ustawisz punkt przerwania, aby zlokalizować problem. Błąd wystąpił w operacji DELETE, więc będziesz pracować z klasą QuoteController.

  1. Otwórz: src/main/java/com/example/springboot/QuoteController.java
  2. Znajdź metodę deleteQuote().
  3. Znajdź wiersz: Optional<Quote> quote = quoteRepository.findById(id);
  4. Ustaw punkt przerwania w tym wierszu, klikając puste miejsce po lewej stronie numeru wiersza.
  5. Pojawi się czerwony wskaźnik, który oznacza, ż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 QuoteController.
  3. W debuggerze kliknij ikonę step over b814d39b2e5f3d9e.png.
  4. Zwróć uwagę, że kod zwraca do klienta błąd wewnętrzny serwera HTTP 500, co nie jest idealne.
   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

Aktualizowanie kodu

Kod jest nieprawidłowy i blok else należy zmodyfikować, aby zwracał kod stanu HTTP 404 (nie znaleziono).

Popraw błąd.

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

Metoda powinna wyglądać 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. Ponowne uruchomienie polecenia usuwania
curl -v -X DELETE $URL/quotes/6
  1. Przejdź przez debuger i obserwuj błąd HTTP 404 Not Found zwracany do wywołującego.
   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. Zakończ sesję debugowania, klikając czerwony kwadrat na pasku narzędzi debugowania.

12bc3c82f63dcd8a.png6f19c0f855832407.png

6. Gratulacje

Gratulacje! W tym module udało Ci się utworzyć od podstaw nową aplikację w języku Java i skonfigurować ją tak, aby skutecznie działała z kontenerami. Następnie wdrożono i debugowano aplikację w zdalnym klastrze GKE, korzystając z tego samego przepływu pracy dewelopera, który jest stosowany w tradycyjnych stosach aplikacji.

Czego się dowiedziałeś(-aś)

  • Programowanie w trybie inner loop z użyciem Cloud Workstations
  • Tworzenie nowej aplikacji startowej w Javie
  • Omówienie procesu tworzenia
  • Tworzenie prostej usługi REST CRUD
  • Debugowanie aplikacji w klastrze GKE
  • Łączenie aplikacji z bazą danych Cloud SQL