Разработка с использованием облачных рабочих станций и облачного кода

1. Обзор

В этой лабораторной работе демонстрируются функции и возможности, разработанные для оптимизации рабочего процесса разработки для инженеров-программистов, занимающихся разработкой Java-приложений в контейнеризированной среде. Типичная разработка в контейнерах требует от пользователя понимания деталей контейнеров и процесса сборки контейнеров. Кроме того, разработчикам обычно приходится прерывать свой рабочий процесс, выходя из IDE для тестирования и отладки своих приложений в удаленных средах. С помощью инструментов и технологий, упомянутых в этом руководстве, разработчики могут эффективно работать с контейнеризированными приложениями, не покидая свою IDE.

Что вы узнаете

В этой лабораторной работе вы изучите методы разработки с использованием контейнеров в GCP, в том числе:

  • Разработка с использованием технологии InnerLoop на облачных рабочих станциях.
  • Создание нового стартового Java-приложения
  • Обзор процесса разработки
  • Разработка простого REST-сервиса с операциями CRUD.
  • Отладка приложения в кластере GKE.
  • Подключение приложения к базе данных CloudSQL

58a4cdd3ed7a123a.png

2. Настройка и требования

Настройка среды для самостоятельного обучения

  1. Войдите в консоль Google Cloud и создайте новый проект или используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Название проекта — это отображаемое имя участников данного проекта. Это строка символов, не используемая API Google. Вы можете изменить её в любое время.
  • Идентификатор проекта уникален для всех проектов Google Cloud и является неизменяемым (его нельзя изменить после установки). Консоль Cloud автоматически генерирует уникальную строку; обычно вам неважно, какая она. В большинстве практических заданий вам потребуется указать идентификатор проекта (обычно он обозначается как PROJECT_ID ). Если сгенерированный идентификатор вас не устраивает, вы можете сгенерировать другой случайный идентификатор. В качестве альтернативы вы можете попробовать свой собственный и посмотреть, доступен ли он. После этого шага его нельзя изменить, и он останется неизменным на протяжении всего проекта.
  • К вашему сведению, существует третье значение — номер проекта , который используется некоторыми API. Подробнее обо всех трех значениях можно узнать в документации .
  1. Далее вам потребуется включить оплату в консоли Cloud для использования ресурсов/API Cloud. Выполнение этого практического задания не должно стоить дорого, если вообще что-либо. Чтобы отключить ресурсы и избежать дополнительных расходов после завершения этого урока, вы можете удалить созданные ресурсы или удалить весь проект. Новые пользователи Google Cloud имеют право на бесплатную пробную версию стоимостью 300 долларов США .

Запустить редактор Cloudshell

Данная лабораторная работа разработана и протестирована для использования с редактором Google Cloud Shell. Для доступа к редактору,

  1. Получите доступ к своему проекту Google по адресу https://console.cloud.google.com .
  2. В правом верхнем углу нажмите на значок редактора облачной оболочки.

8560cc8d45e8c112.png

  1. В нижней части вашего окна откроется новое окно.
  2. Нажмите кнопку «Открыть редактор».

9e504cb98a6a8005.png

  1. В начале редактора справа будет изображен исследователь, а в центре — сам редактор.
  2. В нижней части экрана также должна быть доступна панель терминала.
  3. Если терминал НЕ открыт, используйте комбинацию клавиш `Ctrl+`, чтобы открыть новое окно терминала.

Настройте gcloud

В Cloud Shell укажите идентификатор проекта и регион, в который вы хотите развернуть приложение. Сохраните их как переменные PROJECT_ID и REGION .

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

Клонируйте исходный код

Исходный код для этой лабораторной работы находится в репозитории container-developer-workshop на GitHub в GoogleCloudPlatform. Клонируйте его с помощью приведенной ниже команды, а затем перейдите в нужную директорию.

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

Обеспечьте наличие инфраструктуры, используемой в этой лаборатории.

В этой лабораторной работе вы развернете код в GKE и получите доступ к данным, хранящимся в базе данных CloudSQL. Приведенный ниже скрипт настройки подготовит для вас эту инфраструктуру. Процесс развертывания займет более 25 минут. Дождитесь завершения работы скрипта, прежде чем переходить к следующему разделу.

./setup_with_cw.sh &

Кластер облачных рабочих станций

Откройте Cloud Workstations в Cloud Console. Дождитесь, пока кластер перейдет в состояние READY .

305e1a3d63ac7ff6.png

Создание конфигурации рабочих станций

Если ваша сессия Cloud Shell была разорвана, нажмите «Переподключиться», а затем выполните команду cli gcloud, чтобы установить идентификатор проекта. Перед выполнением команды замените указанный ниже пример идентификатора проекта на идентификатор вашего проекта qwiklabs.

gcloud config set project qwiklabs-gcp-project-id

Запустите приведенный ниже скрипт в терминале, чтобы создать конфигурацию облачных рабочих станций.

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

Проверьте результаты в разделе «Конфигурации». Переход в состояние «ГОТОВО» займет 2 минуты.

7a6af5aa2807a5f2.png

Откройте раздел «Облачные рабочие станции» в консоли и создайте новый экземпляр.

a53adeeac81a78c8.png

Измените имя на my-workstation и выберите существующую конфигурацию: codeoss-java .

f21c216997746097.png

Проверьте результаты в разделе «Рабочие станции».

66a9fc8b20543e32.png

Запуск рабочей станции

Запустите рабочую станцию.

c91bb69b61ec8635.png

Разрешите использование сторонних файлов cookie, нажав на значок в адресной строке. 1b8923e2943f9bc4.png

fcf9405b6957b7d7.png

Нажмите «Сайт не работает?».

36a84c0e2e3b85b.png

Нажмите «Разрешить файлы cookie».

2259694328628fba.png

После запуска рабочей станции вы увидите запущенную среду разработки Code OSS IDE. На странице «Начало работы» в среде разработки рабочей станции нажмите кнопку «Готово».

94874fba9b74cc22.png

3. Создание нового стартового Java-приложения

В этом разделе вы создадите новое Java-приложение Spring Boot с нуля, используя пример приложения, предоставленный spring.io. Откройте новый терминал.

c31d48f2e4938c38.png

Клонируйте демонстрационное приложение

  1. Создайте стартовое приложение
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

Если вы видите это сообщение, нажмите кнопку «Разрешить», чтобы иметь возможность скопировать и вставить текст на рабочую станцию.

58149777e5cc350a.png

  1. Распакуйте приложение.
unzip sample-app.zip -d sample-app
  1. Откройте папку "sample-app".
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

Добавьте spring-boot-devtools и Jib.

Чтобы включить инструменты разработчика Spring Boot, найдите и откройте файл pom.xml в обозревателе вашего редактора. Затем вставьте следующий код после строки описания, которая выглядит так <description>Demo project for Spring Boot</description>

  1. Добавьте spring-boot-devtools в pom.xml

Откройте файл pom.xml в корне проекта. Добавьте следующую конфигурацию после записи 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. Включите jib-maven-plugin в pom.xml

Jib — это инструмент для контейнеризации Java-приложений от Google с открытым исходным кодом, который позволяет Java-разработчикам создавать контейнеры, используя знакомые им инструменты Java. Jib — это быстрый и простой конструктор образов контейнеров, который обрабатывает все этапы упаковки вашего приложения в образ контейнера. Он не требует написания Dockerfile или установки Docker и напрямую интегрирован с Maven и Gradle.

Прокрутите вниз файл pom.xml и обновите раздел Build , добавив плагин Jib. После завершения раздел Build должен соответствовать следующему содержимому.

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>

Создать манифесты

Skaffold предоставляет интегрированные инструменты для упрощения разработки контейнеров. На этом этапе вы инициализируете Skaffold, который автоматически создаст базовые YAML-файлы Kubernetes. Процесс пытается идентифицировать каталоги с определениями образов контейнеров, подобно Dockerfile, а затем создает манифест развертывания и службы для каждого из них.

Для начала процесса выполните следующую команду в терминале.

d869e0cd38e983d7.png

  1. Выполните следующую команду в терминале.
skaffold init --generate-manifests
  1. При появлении запроса:
  • Используйте стрелки, чтобы переместить курсор к Jib Maven Plugin
  • Нажмите пробел, чтобы выбрать нужный вариант.
  • Нажмите Enter, чтобы продолжить.
  1. Введите 8080 для порта.
  2. Введите y для сохранения конфигурации.

В рабочую область добавлены два файла skaffold.yaml и deployment.yaml

Вывод Skaffold:

b33cc1e0c2077ab8.png

Обновить название приложения

Значения по умолчанию, указанные в конфигурации, в настоящее время не соответствуют названию вашего приложения. Обновите файлы, чтобы они ссылались на название вашего приложения, а не на значения по умолчанию.

  1. Измените записи в конфигурации Skaffold.
  • Откройте skaffold.yaml
  • Выберите имя изображения, которое в данный момент задано как pom-xml-image
  • Щелкните правой кнопкой мыши и выберите «Изменить все вхождения».
  • Введите новое имя как demo-app
  1. Измените записи в конфигурации Kubernetes.
  • Откройте файл deployment.yaml
  • Выберите имя изображения, которое в данный момент задано как pom-xml-image
  • Щелкните правой кнопкой мыши и выберите «Изменить все вхождения».
  • Введите новое имя как demo-app

Включить режим автоматической синхронизации

Для оптимизации процесса горячей перезагрузки вы будете использовать функцию синхронизации, предоставляемую Jib. На этом шаге вы настроите Skaffold для использования этой функции в процессе сборки.

Обратите внимание, что профиль "sync", который вы настраиваете в конфигурации Skaffold, использует профиль "sync" Spring, который вы настроили на предыдущем шаге, где вы включили поддержку spring-dev-tools.

  1. Обновить конфигурацию Skaffold

В файле skaffold.yaml замените весь раздел build следующим описанием. Не изменяйте другие разделы файла.

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

Добавить маршрут по умолчанию

Создайте файл с именем HelloController.java в папке /src/main/java/com/example/springboot/ .

a624f5dd0c477c09.png

Вставьте следующее содержимое в файл, чтобы создать 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. Обзор процесса разработки.

В этом разделе вы шаг за шагом изучите основные процессы с помощью плагина Cloud Code, а также проверите конфигурацию и настройку вашего стартового приложения.

Cloud Code интегрируется со Skaffold для оптимизации процесса разработки. При развертывании в GKE на следующих этапах Cloud Code и Skaffold автоматически создадут образ контейнера, загрузят его в реестр контейнеров, а затем развернут ваше приложение в GKE. Это происходит в фоновом режиме, абстрагируя детали от процесса разработки. Cloud Code также улучшает процесс разработки, предоставляя традиционные возможности отладки и синхронизации для разработки на основе контейнеров.

Войдите в Google Cloud

Нажмите на значок Cloud Code и выберите «Войти в Google Cloud»:

1769afd39be372ff.png

Нажмите «Перейти к входу».

923bb1c8f63160f9.png

Проверьте вывод в терминале и откройте ссылку:

517fdd579c34aa21.png

Войдите в систему, используя свои учетные данные студента Qwiklabs.

db99b345f7a8e72c.png

Выберите «Разрешить»:

a5376553c430ac84.png

Скопируйте проверочный код и вернитесь на вкладку «Рабочая станция».

6719421277b92eac.png

Вставьте проверочный код и нажмите Enter.

e9847cfe3fa8a2ce.png

Добавить кластер Kubernetes

  1. Добавить кластер

62a3b97bdbb427e5.png

  1. Выберите Google Kubernetes Engine:

9577de423568bbaa.png

  1. Выберите проект.

c5202fcbeebcd41c.png

  1. Выберите "quote-cluster", созданный при первоначальной настройке.

366cfd8bc27cd3ed.png

9d68532c9bc4a89b.png

Установите идентификатор текущего проекта с помощью gcloud cli.

Скопируйте идентификатор проекта для этой лабораторной работы со страницы qwiklabs.

fcff2d10007ec5bc.png

Выполните команду gcloud cli, чтобы установить идентификатор проекта. Перед выполнением команды замените sample project id.

gcloud config set project qwiklabs-gcp-project-id

Пример выходных данных:

f1c03d01b7ac112c.png

Отладка в Kubernetes

  1. В левой панели внизу выберите Cloud Code.

60b8e4e95868b561.png

  1. В панели, которая отображается в разделе «СЕАНСЫ РАЗРАБОТКИ», выберите «Отладка в Kubernetes».

Если эта опция не видна, прокрутите вниз.

7d30833d96632ca0.png

  1. Выберите «Да», чтобы использовать текущий контекст.

a024a69b64de7e9e.png

  1. Выберите "quote-cluster", созданный во время первоначальной настройки.

faebabf372e3caf0.png

  1. Выберите репозиторий контейнеров.

fabc6dce48bae1b4.png

  1. Чтобы просмотреть ход выполнения и уведомления, выберите вкладку «Вывод» в нижней панели.
  2. Выберите «Kubernetes: Запуск/Отладка — Подробная информация» в раскрывающемся списке каналов справа, чтобы просмотреть дополнительные сведения и журналы, транслируемые в режиме реального времени из контейнеров.

86b44c59db58f8f3.png

Дождитесь развертывания приложения.

9f37706a752829fe.png

  1. Проверьте развернутое приложение в GKE в консоли Cloud Console .

6ad220e5d1980756.png

  1. Чтобы вернуться к упрощенному представлению, выберите "Kubernetes: Run/Debug" из выпадающего списка на вкладке OUTPUT.
  2. После завершения сборки и тестирования на вкладке «Вывод» отображается сообщение: Resource deployment/demo-app status completed successfully , а также указан URL-адрес: «Перенаправленный URL-адрес из службы демонстрационного приложения: http://localhost:8080».
  3. В терминале Cloud Code наведите курсор на URL-адрес в выводе (http://localhost:8080), а затем во всплывающей подсказке выберите «Перейти по ссылке».

28c5539880194a8e.png

Откроется новая вкладка, и вы увидите результат, показанный ниже:

d67253ca16238f49.png

Используйте точки останова

  1. Откройте приложение HelloController.java , расположенное по адресу /src/main/java/com/example/springboot/HelloController.java
  2. Найдите оператор return для корневого пути, который выглядит следующим образом: return String.format("Hello from your %s environment!", target);
  3. Установите точку останова на этой строке, щелкнув по пустому месту слева от номера строки. Красный индикатор покажет, что точка останова установлена.

5027dc6da2618a39.png

  1. Перезагрузите браузер и обратите внимание, что отладчик останавливает процесс в точке останова и позволяет вам исследовать переменные и состояние приложения, работающего удаленно в GKE.

71acfb426623cec2.png

  1. Прокрутите вниз в раздел переменных, пока не найдете переменную "Цель".
  2. Текущее значение следует рассматривать как "локальное".

a1160d2ed2bb5c82.png

  1. Дважды щелкните по имени переменной "target", и во всплывающем окне...

Измените значение на "Облачные рабочие станции".

e597a556a5c53f32.png

  1. Нажмите кнопку «Продолжить» на панели управления отладкой.

ec17086191770d0d.png

  1. Проверьте ответ в браузере, где теперь отображается обновленное значение, которое вы только что ввели.

6698a9db9e729925.png

  1. Снимите точку останова, щелкнув красный индикатор слева от номера строки. Это предотвратит остановку выполнения вашего кода на этой строке по мере прохождения данной лабораторной работы.

Горячая перезарядка

  1. Измените оператор так, чтобы он возвращал другое значение, например, "Привет от %s Code".
  2. Файл автоматически сохраняется и синхронизируется с удалёнными контейнерами в GKE.
  3. Обновите страницу в браузере, чтобы увидеть обновленные результаты.
  4. Остановите сеанс отладки, нажав на красный квадрат на панели инструментов отладки.

a541f928ec8f430e.pngc2752bb28d82af86.png

Выберите «Да, очищать после каждого запуска».

984eb2fa34867d70.png

5. Разработка простого REST-сервиса с операциями CRUD.

На этом этапе ваше приложение полностью настроено для контейнерной разработки, и вы прошли базовый рабочий процесс разработки с помощью Cloud Code. В следующих разделах вы попрактикуетесь в применении полученных знаний, добавив конечные точки REST-сервисов, подключающиеся к управляемой базе данных в Google Cloud.

Настройка зависимостей

Код приложения использует базу данных для сохранения данных REST-сервиса. Убедитесь в наличии зависимостей, добавив следующее в файл pom.xl.

  1. Откройте файл pom.xml и добавьте следующее в раздел dependencies файла config.xml.

pom.xml

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

Код REST-сервиса

Quote.java

Создайте файл с именем Quote.java в /src/main/java/com/example/springboot/ и скопируйте в него приведенный ниже код. Он определяет модель Entity для объекта Quote, используемого в приложении.

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

Создайте файл с именем QuoteRepository.java в src/main/java/com/example/springboot и скопируйте в него следующий код.

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

Этот код использует JPA для сохранения данных. Класс расширяет интерфейс Spring JPARepository и позволяет создавать собственный код. В коде вы добавили пользовательский метод findRandomQuote .

QuoteController.java

Для предоставления доступа к конечной точке сервиса этот функционал будет реализован в классе QuoteController .

Создайте файл с именем QuoteController.java в src/main/java/com/example/springboot и скопируйте в него следующее содержимое.

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

}

Добавить настройки базы данных

application.yaml

Добавьте конфигурацию для базы данных бэкэнда, к которой обращается сервис. Отредактируйте (или создайте, если она отсутствует) файл application.yaml в папке src/main/resources и добавьте параметризованную конфигурацию Spring для бэкэнда.

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

Добавить миграцию базы данных

Создайте папку db/migration в каталоге src/main/resources

Создайте SQL-файл: V1__create_quotes_table.sql

Вставьте следующее содержимое в файл.

V1__create_quotes_table.sql

CREATE TABLE quotes(
   id INTEGER PRIMARY KEY,
   quote VARCHAR(1024),
   author VARCHAR(256)
);

INSERT INTO quotes (id,quote,author) VALUES (1,'Never, never, never give up','Winston Churchill');
INSERT INTO quotes (id,quote,author) VALUES (2,'While there''s life, there''s hope','Marcus Tullius Cicero');
INSERT INTO quotes (id,quote,author) VALUES (3,'Failure is success in progress','Anonymous');
INSERT INTO quotes (id,quote,author) VALUES (4,'Success demands singleness of purpose','Vincent Lombardi');
INSERT INTO quotes (id,quote,author) VALUES (5,'The shortest answer is doing','Lord Herbert');

Конфигурация Kubernetes

Следующие дополнения к файлу deployment.yaml позволяют приложению подключаться к экземплярам CloudSQL.

  • TARGET — задает переменную, указывающую среду, в которой выполняется приложение.
  • SPRING_PROFILES_ACTIVE — показывает активный профиль Spring, который будет настроен для cloud-dev
  • DB_HOST — частный IP-адрес базы данных, который был указан при создании экземпляра базы данных или при нажатии на SQL в навигационном меню консоли Google Cloud — пожалуйста, измените это значение!
  • DB_USER и DB_PASS — параметры, заданные в конфигурации экземпляра CloudSQL и хранящиеся в качестве секрета в GCP.

Обновите файл deployment.yaml, добавив в него содержимое, указанное ниже.

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

Замените значение DB_HOST адресом вашей базы данных, выполнив в терминале следующие команды:

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

Откройте файл deployment.yaml и убедитесь, что значение DB_HOST было обновлено IP-адресом экземпляра.

fd63c0aede14beba.png

Развертывание и проверка приложения

  1. В нижней части окна редактора Cloud Shell выберите Cloud Code, а затем в верхней части экрана выберите Debug on Kubernetes.

33a5cf41aae91adb.png

  1. После завершения сборки и тестирования на вкладке «Вывод» отображается сообщение: Resource deployment/demo-app status completed successfully , а также указан URL-адрес: «Перенаправленный URL-адрес из сервиса demo-app: http://localhost:8080 ». Обратите внимание, что иногда порт может отличаться, например, 8081. В этом случае установите соответствующее значение. Установите значение URL-адреса в терминале.
export URL=localhost:8080
  1. Просмотреть случайные цитаты

В терминале несколько раз выполните указанную ниже команду для конечной точки random-quote. Обратите внимание, что при повторном вызове возвращаются разные котировки.

curl $URL/random-quote | jq
  1. Добавить цитату

Создайте новое коммерческое предложение с идентификатором id=6, используя указанную ниже команду, и понаблюдайте за тем, как запрос будет возвращен.

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. Удалить цитату

Теперь удалите только что добавленную цитату с помощью метода delete и посмотрите на код ответа HTTP/1.1 204 .

curl -v -X DELETE $URL/quotes/6
  1. Ошибка сервера

Повторный запуск последнего запроса после удаления записи может привести к ошибке.

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

Обратите внимание, что в ответе возвращается HTTP:500 Internal Server Error .

Отладка приложения

В предыдущем разделе вы обнаружили ошибку в приложении при попытке удалить запись, которой не было в базе данных. В этом разделе вы установите точку останова, чтобы найти проблему. Ошибка произошла в операции DELETE, поэтому вы будете работать с классом QuoteController.

  1. Откройте src/main/java/com/example/springboot/QuoteController.java
  2. Найдите метод deleteQuote()
  3. Найдите строку: Optional<Quote> quote = quoteRepository.findById(id);
  4. Установите точку останова на этой строке, щелкнув по пустому месту слева от номера строки.
  5. Появится красный индикатор, указывающий на то, что точка останова установлена.
  6. Повторите команду delete .
curl -v -X DELETE $URL/quotes/6
  1. Чтобы вернуться в режим отладки, нажмите на значок в левой колонке.
  2. Обратите внимание на строку отладки, которая останавливается в классе QuoteController.
  3. В отладчике нажмите значок " step over ". b814d39b2e5f3d9e.png
  4. Обратите внимание, что код возвращает клиенту ошибку внутреннего сервера HTTP 500, что нежелательно.
   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

Обновите код

Код некорректен, и блок else следует переписать таким образом, чтобы он отправлял код состояния HTTP 404 (страница не найдена).

Исправьте ошибку.

  1. Не отключая сеанс отладки, завершите запрос, нажав кнопку «Продолжить» на панели управления отладкой.
  2. Далее замените блок else следующим кодом:
       else {
                return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
            }

Метод должен выглядеть следующим образом.

@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. Повторно выполните команду удаления.
curl -v -X DELETE $URL/quotes/6
  1. Пошагово отладчик отслеживает 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. Остановите сеанс отладки, нажав на красный квадрат на панели инструментов отладки.

12bc3c82f63dcd8a.png

6f19c0f855832407.png

6. Поздравляем!

Поздравляем! В этой лабораторной работе вы создали с нуля новое Java-приложение и настроили его для эффективной работы с контейнерами. Затем вы развернули и отладили свое приложение в удаленном кластере GKE, следуя тому же процессу разработки, что и в традиционных стеках приложений.

Чему вы научились

  • Разработка с использованием технологии InnerLoop на облачных рабочих станциях.
  • Создание нового стартового Java-приложения
  • Обзор процесса разработки
  • Разработка простого REST-сервиса с операциями CRUD.
  • Отладка приложения в кластере GKE.
  • Подключение приложения к базе данных CloudSQL