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

1. Обзор

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

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

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

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

58a4cdd3ed7a123a.png

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

Самостоятельная настройка среды

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

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Имя проекта — это отображаемое имя для участников этого проекта. Это строка символов, не используемая API Google. Вы можете обновить его в любое время.
  • Идентификатор проекта уникален для всех проектов Google Cloud и является неизменяемым (нельзя изменить после его установки). Cloud Console автоматически генерирует уникальную строку; обычно тебя не волнует, что это такое. В большинстве лабораторий кода вам потребуется указать идентификатор проекта (обычно он обозначается как PROJECT_ID ). Если вам не нравится сгенерированный идентификатор, вы можете создать другой случайный идентификатор. Кроме того, вы можете попробовать свой собственный и посмотреть, доступен ли он. Его нельзя изменить после этого шага, и он останется в силе на протяжении всего проекта.
  • К вашему сведению, есть третье значение — номер проекта , который используют некоторые API. Подробнее обо всех трех этих значениях читайте в документации .
  1. Затем вам необходимо включить выставление счетов в Cloud Console, чтобы использовать облачные ресурсы/API. Прохождение этой лаборатории кода не должно стоить много, если вообще стоит. Чтобы отключить ресурсы и избежать выставления счетов за пределами этого руководства, вы можете удалить созданные вами ресурсы или удалить весь проект. Новые пользователи 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)')

Клонировать исходный код

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

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

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

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

./setup_with_cw.sh &

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

Откройте облачные рабочие станции в облачной консоли. Подождите, пока кластер перейдет в состояние READY .

305e1a3d63ac7ff6.png

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

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

gcloud config set project qwiklabs-gcp-project-id

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

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

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

7a6af5aa2807a5f2.png

Откройте Cloud Workstations в консоли и создайте новый экземпляр.

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. Нажмите «Отметить готово» на странице «Начало работы» в среде 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. Откройте папку «пример приложения».
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

Добавьте Spring-Boot-devtools и Jib

Чтобы включить Spring Boot DevTools, найдите и откройте 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 в pom.xml

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

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

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

Выход каркаса:

b33cc1e0c2077ab8.png

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

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

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

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

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

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

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

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

скаффолд.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-маршрут по умолчанию.

Приветконтроллер.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

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

а5376553c430ac84.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, чтобы установить идентификатор проекта. Замените идентификатор примера проекта перед запуском команды.

gcloud config set project qwiklabs-gcp-project-id

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

f1c03d01b7ac112c.png

Отладка в Kubernetes

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

60b8e4e95868b561.png

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

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

7d30833d96632ca0.png

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

а024a69b64de7e9e.png

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

faebabf372e3caf0.png

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

fabc6dce48bae1b4.png

  1. Выберите вкладку «Вывод» на нижней панели, чтобы просмотреть ход выполнения и уведомления.
  2. Выберите «Kubernetes: Run/Debug — Detailed» в раскрывающемся списке каналов справа, чтобы просмотреть дополнительные сведения и журналы, транслируемые в реальном времени из контейнеров.

86b44c59db58f8f3.png

Подождите, пока приложение будет развернуто.

9f37706a752829fe.png

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

6ad220e5d1980756.png

  1. Вернитесь к упрощенному представлению, выбрав «Kubernetes: Run/Debug» в раскрывающемся списке на вкладке «Вывод».
  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 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».
  2. Файл автоматически сохраняется и синхронизируется с удаленными контейнерами в GKE.
  3. Обновите браузер, чтобы увидеть обновленные результаты.
  4. Остановите сеанс отладки, щелкнув красный квадрат на панели инструментов отладки.

a541f928ec8f430e.pngc2752bb28d82af86.png

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

984eb2fa34867d70.png

5. Разработка простой службы отдыха CRUD

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

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

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

  1. Откройте файл pom.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-сервиса

Цитата.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 .

ЦитатаController.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');

Конфигурация Кубернетеса

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

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

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

развертывание.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, затем выберите «Отладка в Kubernetes» вверху экрана.

33a5cf41aae91adb.png

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

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

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

Создайте новую цитату с идентификатором = 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
  • Прогулка по процессу разработки
  • Разработка простой службы CRUD REST
  • Отладка приложения в кластере GKE
  • Подключение приложения к базе данных CloudSQL