1. Descripción general
En este lab, se muestran las funciones y capacidades diseñadas para optimizar el flujo de trabajo de desarrollo de los ingenieros de software encargados de desarrollar aplicaciones de Java en un entorno alojado en contenedores. El desarrollo típico de contenedores requiere que el usuario comprenda sus detalles y el proceso de compilación de estos. Además, por lo general, los desarrolladores tienen que interrumpir el flujo y salir del IDE para probar y depurar sus aplicaciones en entornos remotos. Con las herramientas y tecnologías mencionadas en este instructivo, los desarrolladores pueden trabajar de manera eficaz con aplicaciones alojadas en contenedores sin salir de su IDE.
Qué aprenderás
En este lab, aprenderás métodos para desarrollar con contenedores en GCP, incluidos los siguientes:
- Desarrollo de InnerLoop con Cloud Workstations
- Crea una nueva aplicación de inicio de Java
- Explicación del proceso de desarrollo
- Desarrollar un servicio de REST de CRUD simple
- Aplicación de depuración en el clúster de GKE
- Conectando la aplicación a la base de datos de Cloud SQL
2. Configuración y requisitos
Cómo configurar el entorno a tu propio ritmo
- Accede a Google Cloud Console y crea un proyecto nuevo o reutiliza uno existente. Si aún no tienes una cuenta de Gmail o de Google Workspace, debes crear una.
- El Nombre del proyecto es el nombre visible de los participantes de este proyecto. Es una cadena de caracteres que no se utiliza en las APIs de Google. Puedes actualizarla en cualquier momento.
- El ID del proyecto es único en todos los proyectos de Google Cloud y es inmutable (no se puede cambiar después de configurarlo). La consola de Cloud genera automáticamente una cadena única. por lo general, no te importa qué es. En la mayoría de los codelabs, deberás hacer referencia al ID del proyecto (por lo general, se identifica como
PROJECT_ID
). Si no te gusta el ID generado, puedes generar otro aleatorio. También puedes probar el tuyo propio y ver si está disponible. No se puede cambiar después de este paso y se mantendrá mientras dure el proyecto. - Para tu información, hay un tercer valor, un número de proyecto que usan algunas APIs. Obtén más información sobre estos tres valores en la documentación.
- A continuación, deberás habilitar la facturación en la consola de Cloud para usar las APIs o los recursos de Cloud. Ejecutar este codelab no debería costar mucho, tal vez nada. Para cerrar recursos y evitar que se te facture más allá de este instructivo, puedes borrar los recursos que creaste o borrar todo el proyecto. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de USD 300.
Inicia el editor de Cloud Shell
Este lab se diseñó y probó para usarse con el editor de Google Cloud Shell. Para acceder al editor,
- accede a tu proyecto de Google en https://console.cloud.google.com.
- En la esquina superior derecha, haz clic en el ícono del editor de Cloud Shell.
- Se abrirá un panel nuevo en la parte inferior de la ventana.
- Haz clic en el botón Abrir editor.
- El editor se abrirá con un explorador a la derecha y un editor en el área central.
- También debería haber un panel de terminal disponible en la parte inferior de la pantalla.
- Si la terminal NO está abierta, usa la combinación de teclas “ctrl+” para abrir una nueva ventana de terminal.
Configura gcloud
En Cloud Shell, configura el ID del proyecto y la región en la que quieres implementar la aplicación. Guárdalos como variables PROJECT_ID
y REGION
.
export REGION=us-central1
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
Clona el código fuente
El código fuente de este lab se encuentra en container-developer-workshop en GoogleCloudPlatform, en GitHub. Clónalo con el comando que aparece a continuación y, luego, cambia al directorio.
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot
Aprovisiona la infraestructura que se usa en este lab
En este lab, implementarás código en GKE y accederás a datos almacenados en una base de datos de Cloud SQL. La siguiente secuencia de comandos de configuración prepara esta infraestructura por ti. El proceso de aprovisionamiento tardará más de 25 minutos. Espera a que se complete la secuencia de comandos antes de pasar a la siguiente sección.
./setup_with_cw.sh &
Clúster de Cloud Workstations
Abre Cloud Workstations en la consola de Cloud. Espera a que el clúster tenga el estado READY
.
Crear configuración de estaciones de trabajo
Si se desconectó tu sesión de Cloud Shell, haz clic en “Volver a conectar” y, luego, ejecuta el comando gcloud cli para configurar el ID del proyecto. Reemplaza el ID del proyecto de muestra que aparece a continuación por el ID de tu proyecto de Qwiklabs antes de ejecutar el comando.
gcloud config set project qwiklabs-gcp-project-id
Ejecuta la siguiente secuencia de comandos en la terminal para crear la configuración de Cloud Workstations.
cd ~/container-developer-workshop/labs/spring-boot
./workstation_config_setup.sh
Verifica los resultados en la sección Configuración. La transición al estado READY tardará 2 minutos.
Abre Cloud Workstations en la consola y crea una instancia nueva.
Cambia el nombre a my-workstation
y selecciona la configuración existente: codeoss-java
.
Verifica los resultados en la sección Estaciones de trabajo.
Iniciar estación de trabajo
Inicia y, luego, inicia la estación de trabajo.
Permite las cookies de terceros haciendo clic en el ícono de la barra de direcciones.
Haz clic en "¿El sitio no funciona?".
Haz clic en "Permitir cookies".
Una vez que se inicie la estación de trabajo, verás que aparece Code OSS IDE. Haz clic en "Marcar como hecho". En la página Primeros pasos, el IDE de la estación de trabajo
3. Crea una nueva aplicación de inicio de Java
En esta sección, crearás una aplicación nueva de Java Spring Boot desde cero utilizando una aplicación de ejemplo proporcionada por spring.io. Abre una terminal nueva.
Clona la aplicación de muestra
- Crea una aplicación inicial
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
Si ves este mensaje, haz clic en el botón Permitir para copiar y pegar en la estación de trabajo.
- Descomprime la aplicación
unzip sample-app.zip -d sample-app
- Abre la “app-de-muestra”. carpeta
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"
Agrega las herramientas para desarrolladores de Springboot y Jib
Para habilitar las Herramientas para desarrolladores de Spring Boot, busca y abre el archivo pom.xml desde el explorador de tu editor. Luego, pega el siguiente código después de la línea descriptiva que dice <description>Demo project for Spring Boot</description>
.
- Agrega spring-boot-devtools en pom.xml
Abre el archivo pom.xml
en la raíz del proyecto. Agrega la siguiente configuración después de la entrada 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>
- Habilitar jib-maven-plugin en pom.xml
Jib es una herramienta de código abierto para la creación de contenedores de Java de Google que permite a los desarrolladores de Java compilar contenedores con las herramientas de Java que ya conocen. Jib es un compilador de imágenes de contenedor rápido y simple que controla todos los pasos para empaquetar tu aplicación en una imagen de contenedor. No requiere que escribas un Dockerfile o que tengas instalado Docker, y está integrado directamente en Maven y Gradle.
Desplázate hacia abajo en el archivo pom.xml
y actualiza la sección Build
para incluir el complemento Jib. La sección de compilación debería coincidir con lo siguiente cuando se complete.
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>
Generar manifiestos
Skaffold proporciona herramientas integradas para simplificar el desarrollo de contenedores. En este paso, inicializarás Skaffold, que creará automáticamente archivos YAML base de Kubernetes. El proceso intenta identificar directorios con definiciones de imágenes de contenedor, como un Dockerfile, y, luego, crea un manifiesto de implementación y de servicio para cada uno.
Ejecuta el siguiente comando en la terminal para comenzar el proceso.
- Ejecuta el siguiente comando en la terminal
skaffold init --generate-manifests
- Realice las siguientes acciones cuando se le solicite:
- Usa las flechas para mover el cursor hacia
Jib Maven Plugin
- Presiona la barra espaciadora para seleccionar la opción.
- Presiona Intro para continuar
- Ingresa 8080 para el puerto
- Ingresa y para guardar la configuración.
Se agregan dos archivos al espacio de trabajo skaffold.yaml
y deployment.yaml
Resultado de Skaffold:
Actualiza el nombre de la app
Los valores predeterminados incluidos en la configuración actualmente no coinciden con el nombre de tu aplicación. Actualiza los archivos para hacer referencia al nombre de tu aplicación en lugar de los valores predeterminados.
- Cambia las entradas en la configuración de Skaffold
- Abrir
skaffold.yaml
- Selecciona el nombre de la imagen establecido actualmente como
pom-xml-image
- Haz clic con el botón derecho y elige Cambiar todos los casos.
- Escribe el nombre nuevo como
demo-app
.
- Cambia las entradas en la configuración de Kubernetes
- Abrir archivo
deployment.yaml
- Selecciona el nombre de la imagen establecido actualmente como
pom-xml-image
- Haz clic con el botón derecho y elige Cambiar todos los casos.
- Escribe el nombre nuevo como
demo-app
.
Habilitar el modo de sincronización automática
Para facilitar una experiencia optimizada de recarga en caliente, usarás la función de sincronización que proporciona Jib. En este paso, configurarás Skaffold para usar esa función en el proceso de compilación.
Ten en cuenta que la "sincronización" que estás configurando en Skaffold, que aprovecha la función de “sincronización” de Spring Perfil que configuraste en el paso anterior, en el que habilitaste la compatibilidad con spring-dev-tools.
- Actualiza la configuración de Skaffold
En el archivo skaffold.yaml
, reemplaza toda la sección de compilación del archivo por la siguiente especificación. No modifiques otras secciones del archivo.
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
Agrega una ruta predeterminada
Crea un archivo llamado HelloController.java
en la carpeta /src/main/java/com/example/springboot/
.
Pega el siguiente contenido en el archivo para crear una ruta HTTP predeterminada.
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. Explicación del proceso de desarrollo
En esta sección, seguirás algunos pasos con el complemento de Cloud Code para aprender los procesos básicos y validar la configuración de tu aplicación inicial.
Cloud Code se integra en Skaffold para optimizar tu proceso de desarrollo. Cuando sigas estos pasos para implementar en GKE, Cloud Code y Skaffold compilarán automáticamente tu imagen de contenedor, la enviarán a Container Registry y, luego, implementarán tu aplicación en GKE. Esto sucede detrás de escena, abstrae los detalles del flujo del desarrollador. Cloud Code también mejora tu proceso de desarrollo, ya que proporciona capacidades tradicionales de depuración y hotsync al desarrollo basado en contenedores.
Accede a Google Cloud
Haz clic en el ícono de Cloud Code y selecciona “Acceder a Google Cloud”:
Haz clic en "Continuar para acceder".
Verifica el resultado en la terminal y abre el vínculo:
Accede con tus credenciales de estudiantes de Qwiklabs.
Selecciona "Permitir":
Copia el código de verificación y regresa a la pestaña Workstation.
Pega el código de verificación y presiona Intro.
Agregar clúster de Kubernetes
- Agrega un clúster
- Selecciona Google Kubernetes Engine:
- Selecciona un proyecto.
- Selecciona “quote-cluster” que se creó en la configuración inicial.
Establece el ID del proyecto actual con gcloud cli
Copia el ID del proyecto de este lab desde la página de Qwiklabs.
Ejecuta el comando gcloud cli para establecer el ID del proyecto. Reemplaza el ID del proyecto de muestra antes de ejecutar el comando.
gcloud config set project qwiklabs-gcp-project-id
Resultado de muestra:
Cómo depurar en Kubernetes
- En el panel izquierdo, en la parte inferior, selecciona Cloud Code.
- En el panel que aparece en DEVELOPMENT SESSIONS, selecciona Debug on Kubernetes.
Desplázate hacia abajo si la opción no está visible.
- Selecciona "Sí". para usar el contexto actual.
- Selecciona “quote-cluster” creada durante la configuración inicial.
- Selecciona Container Repository.
- Selecciona la pestaña Salida en el panel inferior para ver el progreso y las notificaciones.
- Selecciona “Kubernetes: Run/Debug - Detail”. En el menú desplegable de canales que se encuentra a la derecha para ver detalles adicionales y registros que se transmiten en vivo desde los contenedores
Espera a que se implemente la aplicación.
- Revisa la aplicación implementada en GKE en la consola de Cloud.
- Para volver a la vista simplificada, selecciona "Kubernetes: Run/Debug" en el menú desplegable de la pestaña SALIDA.
- Cuando finalicen la compilación y las pruebas, la pestaña Salida indicará
Resource deployment/demo-app status completed successfully
y aparecerá una URL: “URL reenviada de service demo-app: http://localhost:8080”. - En la terminal de Cloud Code, coloca el cursor sobre la URL en el resultado (http://localhost:8080) y, luego, en la sugerencia de herramientas que aparece, selecciona Seguir vínculo.
Se abrirá una nueva pestaña y verás el siguiente resultado:
Utiliza puntos de interrupción
- Abre la aplicación
HelloController.java
ubicada en/src/main/java/com/example/springboot/HelloController.java
- Ubica la sentencia return para la ruta raíz que dice
return String.format("Hello from your %s environment!", target);
. - Para agregar un punto de interrupción a esa línea, haz clic en el espacio en blanco a la izquierda del número de línea. Se mostrará un indicador rojo para indicar que se estableció el punto de interrupción
- Vuelve a cargar el navegador y observa que el depurador detiene el proceso en el punto de interrupción y te permite investigar las variables y el estado de la aplicación que se ejecuta de forma remota en GKE.
- Haz clic en la sección de variables hasta encontrar el "Objetivo". de salida.
- Observa el valor actual como "local"
- Haz doble clic en el nombre de variable "target" y, en la ventana emergente,
cambia el valor a “Cloud Workstations”
- Haz clic en el botón Continuar en el panel de control de depuración.
- Revisa la respuesta en tu navegador, que ahora muestra el valor actualizado que acabas de ingresar.
- Para quitar el punto de interrupción, haz clic en el indicador rojo a la izquierda del número de línea. Esto evitará que tu código detenga la ejecución en esta línea a medida que avances en este lab.
Recarga en caliente
- Cambia la sentencia para que devuelva un valor diferente, como "Hello from %s Code"
- El archivo se guarda automáticamente y se sincroniza en los contenedores remotos en GKE.
- Actualiza el navegador para ver los resultados actualizados.
- Para detener la sesión de depuración, haz clic en el cuadrado rojo de la barra de herramientas de depuración.
Selecciona “Sí, limpiar después de cada ejecución”.
5. Desarrollar un servicio de REST de CRUD simple
En este punto, tu aplicación está completamente configurada para el desarrollo en contenedores, y ya recorriste el flujo de trabajo básico de desarrollo con Cloud Code. En las siguientes secciones, pondrás en práctica lo que aprendiste agregando extremos del servicio de REST que se conectan a una base de datos administrada en Google Cloud.
Configura dependencias
El código de la aplicación usa una base de datos para conservar los datos del servicio de REST. Asegúrate de que las dependencias estén disponibles agregando lo siguiente al archivo pom.xl
- Abre el archivo
pom.xml
y agrega lo siguiente en la sección de dependencias de la configuración
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>
Servicio de REST de código
Quote.java
Crea un archivo llamado Quote.java
en /src/main/java/com/example/springboot/
y cópialo en el siguiente código. Esto define el modelo de entidad para el objeto Quote que se usa en la aplicación.
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
Crea un archivo llamado QuoteRepository.java
en src/main/java/com/example/springboot
y cópialo en el siguiente código
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();
}
Este código usa JPA para conservar los datos. La clase extiende la interfaz Spring JPARepository
y permite la creación de código personalizado. En el código, agregaste un método personalizado findRandomQuote
.
QuoteController.java
Para exponer el extremo del servicio, una clase QuoteController
proporcionará esta funcionalidad.
Crea un archivo llamado QuoteController.java
en src/main/java/com/example/springboot
y cópialo en el siguiente contenido.
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);
}
}
}
Agregar parámetros de configuración de la base de datos
application.yaml
Agrega la configuración para la base de datos de backend a la que accede el servicio. Edita (o crea si no está presente) el archivo llamado application.yaml
en src/main/resources
y agrega una configuración de Spring parametrizada para el backend.
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
Agregar migración de bases de datos
Crear carpetas db/migration
en src/main/resources
Crea un archivo SQL: V1__create_quotes_table.sql
Pega el siguiente contenido en el archivo
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');
Configuración de Kubernetes
Las siguientes incorporaciones al archivo deployment.yaml
permiten que la aplicación se conecte a las instancias de Cloud SQL.
- TARGET: configura la variable para indicar el entorno en el que se ejecuta la app.
- SPRING_PROFILES_ACTIVE: Muestra el perfil activo de Spring, que se configurará como
cloud-dev
. - DB_HOST: Es la IP privada de la base de datos, que se anotó cuando se creó la instancia de base de datos o haciendo clic en
SQL
en el menú de navegación de la consola de Google Cloud. Cambia el valor. - DB_USER y DB_PASS, como se establece en la configuración de la instancia de Cloud SQL, almacenado como Secret en GCP
Actualiza el archivo deployment.yaml con el siguiente contenido.
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
Reemplaza el valor DB_HOST por la dirección de tu base de datos. Para ello, ejecuta los siguientes comandos en la terminal:
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
Abre deployment.yaml y verifica que el valor de DB_HOST se haya actualizado con la IP de la instancia.
Implementa y valida la aplicación
- En el panel ubicado en la parte inferior del editor de Cloud Shell, selecciona Cloud Code. Luego, selecciona Debug on Kubernetes en la parte superior de la pantalla.
- Cuando la compilación y las pruebas estén listas, la pestaña Salida indicará
Resource deployment/demo-app status completed successfully
y aparecerá una URL: “URL reenviada de servicio demo-app: http://localhost:8080”. Ten en cuenta que, a veces, el puerto puede ser diferente, como el 8081. Si es así, configura el valor adecuado. Configura el valor de URL en la terminal
export URL=localhost:8080
- Ver citas al azar
En la terminal, ejecuta el siguiente comando varias veces en el extremo de comillas aleatorias. Observar llamadas repetidas que muestran comillas diferentes
curl $URL/random-quote | jq
- Agregar una cotización
Crea una nueva cotización con id=6 mediante el comando que se indica a continuación y observa cómo se repite la solicitud
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
- Cómo borrar una cotización
Ahora, borra la cita que acabas de agregar con el método delete y observa un código de respuesta HTTP/1.1 204
.
curl -v -X DELETE $URL/quotes/6
- Error del servidor
Experimenta un estado de error cuando vuelves a ejecutar la última solicitud después de que ya se haya borrado la entrada.
curl -v -X DELETE $URL/quotes/6
Observa que la respuesta muestra un HTTP:500 Internal Server Error
.
Depura la aplicación
En la sección anterior, encontraste un estado de error en la aplicación cuando intentaste borrar una entrada que no estaba en la base de datos. En esta sección, establecerás un punto de interrupción para localizar el problema. El error ocurrió en la operación DELETE, por lo que trabajarás con la clase QuoteController.
- Abrir
src/main/java/com/example/springboot/QuoteController.java
- Cómo buscar el método
deleteQuote()
- Busca la línea
Optional<Quote> quote = quoteRepository.findById(id);
. - Establece un punto de interrupción en esa línea haciendo clic en el espacio en blanco a la izquierda del número de línea.
- Aparecerá un indicador rojo que indica que se estableció el punto de interrupción
- Vuelve a ejecutar el comando
delete
curl -v -X DELETE $URL/quotes/6
- Para volver a la vista de depuración, haz clic en el ícono de la columna izquierda.
- Observa la línea de depuración detenida en la clase QuoteController.
- En el depurador, haz clic en el ícono
step over
.
- Observa que un código devuelve un error HTTP 500 con el error interno del servidor al cliente, lo cual no es ideal.
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
Actualiza el código
El código es incorrecto y el bloque else
debe refactorizarse para devolver un código de estado HTTP 404 no encontrado.
Corrige el error.
- Con la sesión de depuración en ejecución, completa la solicitud presionando "Continue". del panel de control de depuración.
- A continuación, cambia el bloque
else
al código:
else {
return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
}
El método debería verse de la siguiente manera:
@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); } }
- Vuelve a ejecutar el comando delete
curl -v -X DELETE $URL/quotes/6
- Revisa el depurador y observa el error HTTP 404 Not Found que se muestra al emisor.
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
- Para detener la sesión de depuración, haz clic en el cuadrado rojo de la barra de herramientas de depuración.
6. Felicitaciones
¡Felicitaciones! En este lab, creaste una nueva aplicación de Java desde cero y la configuraste para que funcione de manera eficaz con contenedores. Luego, implementaste y depuraste tu aplicación en un clúster de GKE remoto con el mismo flujo de desarrollador que se encuentra en las pilas de aplicaciones tradicionales.
Qué aprendiste
- Desarrollo de InnerLoop con Cloud Workstations
- Crea una nueva aplicación de inicio de Java
- Explicación del proceso de desarrollo
- Cómo desarrollar un servicio de REST de CRUD simple
- Aplicación de depuración en el clúster de GKE
- Conectando la aplicación a la base de datos de Cloud SQL