Programowanie w InnerLoop za pomocą Javy – SpringBoot

1. Przegląd

W tym laboratorium dowiesz się o funkcjach i możliwościach, które usprawniają przepływ pracy programistów zajmujących się tworzeniem aplikacji w Javie 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 zwykle muszą też 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:

  • Konfiguracja i wymagania
  • Tworzenie nowej aplikacji startowej w Javie
  • Omówienie procesu tworzenia
  • Tworzenie prostej usługi REST CRUD
  • Czyszczenie

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 ani Google Workspace, musisz je utworzyć.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Nazwa projektu to wyświetlana nazwa uczestników tego projektu. Jest to ciąg znaków, który nie jest używany przez interfejsy API Google. Możesz go w dowolnym momencie zaktualizować.
  • Identyfikator projektu musi być unikalny we wszystkich projektach Google Cloud i jest niezmienny (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 modułów z kodem musisz odwoływać się do identyfikatora projektu (zwykle oznaczanego jako PROJECT_ID). Jeśli Ci się nie podoba, wygeneruj inny losowy identyfikator lub spróbuj użyć własnego i sprawdź, czy jest dostępny. Po utworzeniu projektu jest on „zamrażany”.
  • Istnieje też trzecia wartość, czyli numer projektu, którego 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ąć naliczenia opłat po zakończeniu tego samouczka, postępuj zgodnie z instrukcjami „czyszczenia” na końcu ćwiczenia. Nowi użytkownicy Google Cloud mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.

Uruchamianie edytora Cloud Shell

To laboratorium zostało zaprojektowane i przetestowane 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_IDREGION.

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

Pobieranie kodu źródłowego

Kod źródłowy tego laboratorium znajduje się w repozytorium container-developer-workshop w GoogleCloudPlatform na GitHubie. Sklonuj 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

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 10 minut. Podczas przetwarzania konfiguracji możesz przejść do kolejnych kroków.

./setup.sh

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.

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=11 -d packageName=com.example.springboot -o sample-app.zip
  1. Rozpakuj aplikację
unzip sample-app.zip -d sample-app
  1. Przejdź do katalogu sample-app i otwórz folder w obszarze roboczym IDE Cloud Shell.
cd sample-app && cloudshell workspace .

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 poniższy kod po linii tekstu opisu, który brzmi <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 tworzenia kontenerów 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>

Jeśli pojawi się pytanie o zmianę pliku kompilacji, kliknij Always.

447a90338f51931f.png

Generowanie manifestów

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 polecenie poniżej.

  1. W terminalu wykonaj 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 port.
  2. Wpisz y, aby zapisać konfigurację.

Do obszaru roboczego zostaną dodane 2 pliki: skaffold.yamldeployment.yaml.

Aktualizowanie nazwy aplikacji

Wartości domyślne uwzględnione w konfiguracji nie pasują obecnie do nazwy 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. Zmiana 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łączanie synchronizacji na gorąco

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/java:debug
    sync:
      auto: true

Dodawanie trasy domyślnej

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

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 z narzędziem 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 w sposób niewidoczny, 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.

Wdrażanie w Kubernetes

  1. W panelu u dołu edytora Cloud Shell wybierz Cloud Code .

fdc797a769040839.png

  1. W panelu, który pojawi się u góry, kliknij Debuguj w Kubernetes. Jeśli pojawi się taka prośba, wybierz Yes (Tak), aby użyć bieżącego kontekstu Kubernetes.

cfce0d11ef307087.png

  1. Gdy po raz pierwszy uruchomisz to polecenie, u góry ekranu pojawi się pytanie, czy chcesz użyć bieżącego kontekstu Kubernetes. Aby zaakceptować i użyć bieżącego kontekstu, kliknij „Yes” (Tak).

817ee33b5b412ff8.png

  1. Następnie pojawi się pytanie o to, którego repozytorium kontenerów chcesz użyć. Naciśnij Enter, aby zaakceptować podaną wartość domyślną.

eb4469aed97a25f6.png

  1. Wybierz kartę Wyjście w dolnym panelu, aby wyświetlić postęp i powiadomienia.

f95b620569ba96c5.png

  1. Aby wyświetlić dodatkowe szczegóły i logi przesyłane strumieniowo na żywo z kontenerów, w menu po prawej stronie wybierz „Kubernetes: Run/Debug - Detailed” (Kubernetes: uruchamianie/debugowanie – szczegółowe).

94acdcdda6d2108.png

  1. Aby wrócić do widoku uproszczonego, wybierz „Kubernetes: Uruchom/Debuguj” z menu rozwijanego.
  2. Po zakończeniu kompilacji i testów na karcie Wyniki pojawi się komunikat Resource deployment/demo-app status completed successfully oraz adres URL: „Forwarded URL from service demo-app: http://localhost:8080”.
  3. W terminalu Cloud Code najedź kursorem na adres URL w danych wyjściowych (http://localhost:8080), a następnie w wyświetlonej etykiecie narzędzia wybierz Otwórz podgląd w przeglądarce.

Odpowiedź będzie następująca:

Hello from your local environment!

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.
  4. Odśwież przeglądarkę i zwróć uwagę, że debuger zatrzymuje proces w punkcie przerwania i umożliwia zbadanie zmiennych i stanu aplikacji, która jest uruchomiona zdalnie w GKE.
  5. Klikaj w sekcji zmiennych, aż znajdziesz zmienną „Cel”.
  6. Sprawdź, czy bieżąca wartość to „local”.
  7. Kliknij dwukrotnie nazwę zmiennej „target” i w wyskakującym okienku zmień wartość na inną, np. „Cloud”.
  8. Kliknij przycisk Dalej w panelu sterowania debugowaniem.
  9. Sprawdź odpowiedź w przeglądarce, w której powinna się teraz wyświetlać wpisana przez Ciebie zaktualizowana wartość.

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 a13d42d726213e6c.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, które łączą 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. Sprawdź, czy 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>

Kodowanie usługi REST

Quote.java

Utwórz plik o nazwie Quote.java w katalogu /src/main/java/com/example/springboot/ i skopiuj do niego poniższy kod. Określa model jednostki dla obiektu Quote używanego w aplikacji.

package com.example.springboot;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Objects;

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

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

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

    public Integer getId() {
        return id;
    }

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

    public String getQuote() {
        return quote;
    }

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

    public String getAuthor() {
        return author;
    }

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

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

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

QuoteRepository.java

Utwórz plik o nazwie QuoteRepository.java w katalogu src/main/java/com/example/springboot i wklej 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ę zawartość:

package com.example.springboot;

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

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

@RestController
public class QuoteController {

    private final QuoteRepository quoteRepository;

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

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

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

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

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

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

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

    @DeleteMapping("/quotes/{id}")
    public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        try {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }    
}

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

Dodaj usługę migracji bazy danych

Utwórz folder w katalogu src/main/resources/db/migration/.

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 zostanie 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ń tę wartość.
  • DB_USER i DB_PASS – zgodnie z ustawieniami 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.

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

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).
  2. Po zakończeniu kompilacji i testów na karcie Wyniki pojawi się komunikat Resource deployment/demo-app status completed successfully oraz adres URL: „Forwarded URL from service demo-app: http://localhost:8080”.
  3. Wyświetlanie losowych cytatów

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

curl -v 127.0.0.1:8080/random-quote
  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 -v -H 'Content-Type: application/json' -d '{"id":"6","author":"Henry David Thoreau","quote":"Go confidently in the direction of your dreams! Live the life you have imagined"}' -X POST 127.0.0.1:8080/quotes
  1. Usuwanie wyceny

Teraz usuń dodany cytat za pomocą metody usuwania i sprawdź kod odpowiedzi HTTP/1.1 204.

curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. Błąd serwera

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

curl -v -X DELETE 127.0.0.1:8080/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 plik src.main.java.com.example.springboot.QuoteController.java
  2. Znajdź metodę deleteQuote().
  3. Znajdź wiersz, w którym usuwasz element z bazy danych: quoteRepository.deleteById(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 127.0.0.1:8080/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 i sprawdź, czy został zgłoszony wyjątek.
  4. Zauważ, że bardzo ogólny komunikat RuntimeException was caught. 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 wyjątku należy zmodyfikować, aby przechwytywał wyjątek EmptyResultDataAccessException i odsyłał 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 dodaj do kodu ten blok:
       } catch (EmptyResultDataAccessException e){
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        }

Metoda powinna wyglądać tak:

    public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        try {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch(EmptyResultDataAccessException e){
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
  1. Ponowne uruchomienie polecenia usuwania
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. Przejdź przez debuger i zobacz, jak przechwytywany jest wyjątek EmptyResultDataAccessException, a do elementu wywołującego zwracany jest błąd HTTP 404 (nie znaleziono).
   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 a13d42d726213e6c.png.

6. Czyszczenie

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

Aby zwolnić miejsce po ukończeniu modułu:

  1. Usuwanie plików użytych w laboratorium
cd ~ && rm -rf container-developer-workshop
  1. Usuwanie projektu w celu usunięcia całej powiązanej infrastruktury i zasobów