BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Tutorial sobre Spring Boot: Construindo Microservices Implantados no Google Cloud

Tutorial sobre Spring Boot: Construindo Microservices Implantados no Google Cloud

Pontos Principais

  • Utilizar o Google Kubernetes Engine (GKE) junto com o Spring Boot permite que configuremos os microservices de maneira rápida e fácil.

  • O Jib é uma ótima maneira de colocar a nossa aplicação Java em um container. Ele permite criar imagens otimizadas sem a utilização do Docker, usando o Maven ou o Gradle.

  • A implementação do Google’s Spring Cloud GCP permite que os desenvolvedores aproveitem os serviços da Google Cloud Platform (GCP) usando pouca configuração e alguns dos padrões do Spring.

  • A configuração do Skaffold com o Cloud Code permite que os desenvolvedores tenham um bom ciclo de desenvolvimento. Isso é especialmente útil quando se inicia o protótipo de um novo serviço.

Introdução

Com a crescente popularidade dos microservices no setor, houve um boom de tecnologias e plataformas a fim de escolhermos para construir nossas aplicações. Às vezes, é difícil escolher algo para começar. Neste artigo, iremos mostrar como criar uma aplicação baseada em Spring Boot aproveitando alguns dos serviços oferecidos pelo Google Cloud. Essa é a abordagem que estamos usando em nossa equipe no Google há algum tempo. Espero que ache útil.

O Básico

Vamos começar definindo o que construiremos. Começaremos com uma aplicação muito básica, baseado no Spring Boot, escrito em Java. O Spring é um framework maduro que nos permite criar aplicações muito poderosas e ricas em recursos, de maneira bem rápida.

We'll then make a few changes to containerize the application using Jib (builds optimized Docker and OCI images for your Java applications without a Docker) and a distroless version of Java 11. Jib works both with Maven and Gradle. We'll use Maven for this example.

Em seguida, faremos algumas alterações para colocar a aplicação em um container usando o Jib (que cria imagens otimizadas do Docker e OCI para as aplicações Java sem o uso de um Docker) e uma versão Java 11. O Jib funciona tanto com o Maven quanto com o Gradle. Usaremos o Maven neste exemplo.

Posteriormente, criaremos um projeto no Google Cloud Platform (GCP) e usaremos o Spring Cloud GCP para aproveitar o Cloud Firestore. O Spring Cloud GCP permite que aplicações baseadas em Spring consumam facilmente serviços do Google, como bancos de dados (Cloud Firestore, Cloud Spanner ou mesmo Cloud SQL), Google Cloud Pub/Sub, Stackdriver para registro (log), rastreamento (tracing), etc.

Depois, faremos alterações na aplicação para implantá-la no Google Kubernetes Engine (GKE). O GKE é um ambiente gerenciável e pronto para produção, para implantar aplicações em container baseados em Kubernetes.

Por fim, usaremos o Skaffold e o Cloud Code para facilitar o desenvolvimento. O Skaffold lida com o fluxo de trabalho para criar, fazer push e implantar a aplicação. O Cloud Code é um plugin para o VS Code e o IntelliJ que funciona com o Skaffold e sua IDE, para que possamos fazer várias coisas como implantar no GKE com um clique de um botão. Neste artigo, usaremos o IntelliJ com Cloud Code.

Configurando Nossas Ferramentas

Antes de escrevermos qualquer código, precisamos verificar se temos um projeto do Google Cloud e todas as ferramentas instaladas.

Criando um Projeto no Google Cloud

Configurar uma instância do GCP é fácil. Podemos fazer isso seguindo as instruções deste link. Esse novo projeto nos permitirá implantar a aplicação no GKE, obter acesso a um banco de dados (Cloud Firestore) e também ter um local onde podemos enviar as imagens do Docker quando colocarmos nos containers a aplicação.

Instalar Cloud Code

Em seguida, instalaremos o Cloud Code. Podemos seguir estas instruções para instalar o Cloud Code no IntelliJ. O Cloud Code gerencia a instalação do Skaffold e do Google SDK que usaremos mais adiante neste artigo. O Cloud Code também nos permite inspecionar as implantações e serviços GKE. Além disso, ele também possui um modo de desenvolvimento inteligente do GKE que ouve continuamente as alterações no código, quando detecta uma alteração na criação da aplicação, ele cria a imagem e a envia para o seu registro, faz o deploy da aplicação no cluster GKE, inicia a transmissão dos logs e abre um túnel localhost para que possamos testar o serviço localmente. É como mágica!

Para usar o Cloud Code e prosseguir com a aplicação, faça login usando o plugin do Cloud Code clicando no ícone que deve aparecer no canto superior direito da janela do IntelliJ:

Além disso, iremos executar alguns comandos para garantir que a aplicação esteja sendo executada na nossa máquina e possamos nos comunicar com os serviços em execução no nosso projeto no Google Cloud. Precisamos nos certificar de que estamos apontando para o projeto correto e estamos autenticando do seguinte modo:

gcloud config set project <YOUR PROJECT ID>
gcloud auth login

Em seguida, iremos garantir que a máquina possua as credenciais da aplicação para executá-la localmente:

gcloud auth application-default login

Habilitando as APIs

Agora que temos tudo configurado, precisamos ativar as APIs que usaremos na aplicação:

  • Google Container Registry API - Isso nos permitirá ter um registro no qual podemos enviar as imagens de maneira privada.
     
  • Cloud Firestore no Datastore mode - Isso nos permitirá armazenar entidades em um banco de dados NoSQL. Vamos nos certificar de selecionar o modo Armazenamento de dados (Datastore) para que possamos usar o suporte do Spring Cloud GCP.

Podemos gerenciar as APIs ativas no nosso projeto, visitando o API Dashboard do projeto.

Criando o serviço Dog

Vamos começar do início! Vamos fazer uma aplicação simples para que possamos executar localmente. Criaremos algo importante como um microservice "Dog". Como estou usando o IntelliJ Ultimate, irei em `File -> New -> Project…` e selecione "Spring Initializr". Selecionarei Maven, Jar, Java 11 e alterarei o nome para algo importante como "dog", como podemos ver abaixo:

Clique em Avançar e adicione: Lombok, Spring Web e GCP Support:

Se tudo correu bem, agora devemos ter uma aplicação que possamos executar. Se não quisermos usar o IntelliJ para isso, podemos usar o equivalente na nossa IDE ou usar o Spring's Initilizr.

Em seguida, nós adicionaremos um POJO ao serviço "Dog" e alguns endpoints REST para testar a aplicação. Nosso objeto "Dog" terá um name e uma age de atributo e usaremos a notação @Data da Lombok para nos salvar de criar os getters, setters, etc. Também usaremos a notação @AllArgsConstructor para criar um construtor. Usaremos isso mais tarde quando estivermos criando os "Dogs".

@Data
@AllArgsConstructor
public class Dog {
 private String name;
 private int age;
}

Vamos criar uma classe controladora (Controller) para a classe "Dog" e os endpoints REST:

@RestController
@Slf4j
public class DogController {

  @GetMapping("/api/v1/dogs")
  public List<Dog> getAllDogs() {
    log.debug("->getAllDogs");
    return ImmutableList.of(new Dog("Fluffy", 5),
        new Dog("Bob", 6),
        new Dog("Cupcake", 11));
  }

  @PostMapping("/api/v1/dogs")
  public Dog saveDog(@RequestBody Dog dog) {
    log.debug("->saveDog {}", dog);
    return dog;
  }
}

Os endpoints retornam uma lista de "Dogs" predefinidos e o endpoint saveDog realmente não faz muito, mas isso é suficiente para começarmos.

Usando o Cloud Firestore

Agora que temos um esqueleto da aplicação, vamos tentar usar alguns dos serviços no GCP. O Spring Cloud GCP adiciona suporte ao Spring Data para o Google Cloud Firestore no modo Datastore. Usaremos isso para armazenar os registros de "Dogs" em vez de usar uma lista simples. Agora, os usuários também poderão salvar um "Dog" no banco de dados.

Para começar, adicionaremos a dependência do Spring Cloud GCP Data Datastore ao POM:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-gcp-data-datastore</artifactId>
</dependency>

Agora, podemos modificar a classe "Dog" para que ela possa ser armazenada. Adicionaremos uma anotação @Entity e uma anotação @Id a um valor do tipo Long para atuar como um identificador da entidade:

@Entity
@Data
@AllArgsConstructor
public class Dog {
  @Id private Long id;
  private String name;
  private int age;
}

Assim podemos criar uma classe regular do Spring Repository da seguinte maneira:

@Repository
public interface DogRepository extends DatastoreRepository<Dog, Long> {}

Como de costume nos repositórios do Spring, não há necessidade de escrever implementações para essa interface, pois usaremos métodos muito básicos.

Então podemos modificar a classe do controlador. Nós injetaremos o "DogRepository" no "DogController" e vamos modificar a classe para usar o repositório da seguinte maneira:

@RestController
@Slf4j
@RequiredArgsConstructor
public class DogController {

  private final DogRepository dogRepository;

  @GetMapping("/api/v1/dogs")
  public Iterable<Dog> getAllDogs() {
    log.debug("->getAllDogs");
    return dogRepository.findAll();
  }

  @PostMapping("/api/v1/dogs")
  public Dog saveDog(@RequestBody Dog dog) {
    log.debug("->saveDog {}", dog);
    return dogRepository.save(dog);
  }
}

Observe que estamos usando o @RequiredArgsConstructor do Lombok para criar um construtor para injetar o "DogRepository". Quando executarmos a aplicação, os endpoints irão chamar o serviço "Dog" que tentará usar o Cloud Firestore para recuperar ou armazenar os "Dogs".

DICA: Para testar, podemos criar uma solicitação HTTP no IntelliJ com o seguinte comando:

POST http://localhost:8080/api/v1/dogs
Content-Type: application/json

{
  "name": "bob",
  "age": 5
}

Em apenas algumas etapas, temos a aplicação em funcionamento e consumindo serviços do GCP. Impressionante! Agora, vamos transformar isso em um container e implantar!

Adicionando o serviço Dog em um container

Começar a escrever um Dockerfile para colocar a aplicação que criamos em um container. Vamos usar o Jib. Uma das coisas que mais gosto no Jib é que ele separa a aplicação em várias camadas, dividindo as dependências das classes. Isso nos permite criar versões mais rápidas para que não precisemos esperar pelo Docker para reconstruir toda a nossa aplicação Java, basta implantar as camadas que foram alteradas. Além disso, o Jib possui um plugin do Maven que facilita a configuração, modificando apenas o arquivo POM na aplicação.

Para começar a usar o plugin, iremos precisar modificar o arquivo POM para adicionar as seguintes linhas de código:

<plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>1.8.0</version>
        <configuration>
          <from>
            <image>gcr.io/distroless/java:11</image>
          </from>
          <to>
            <image>gcr.io/<YOUR_GCP_REGISTRY>/${project.artifactId}</image>
          </to>
        </configuration>
</plugin>

Observe que estamos usando a imagem do Google's distroless para o Java 11. As imagens "distroless" contêm apenas a aplicação e as dependências de runtime. Eles não possuem gerenciadores de pacotes, shells ou quaisquer outros programas que esperamos encontrar em uma distribuição padrão do Linux.

Restringir o conteúdo do container em runtime é necessário para a nossa aplicação, pois é uma prática recomendada, empregada pelo Google e por outros gigantes da tecnologia que usam containers em produção há muitos anos. Ele melhora o sinal de ruído dos scanners (por exemplo, CVE) e reduz o ônus de estabelecer os recursos exatamente do que precisamos.

Precisamos nos certificar de substituir o registro do GCP no código acima para corresponder ao nome do nosso projeto.

Depois de fazermos isso, podemos tentar criar e enviar a imagem da aplicação executando um comando como:

$ ./mvnw install jib:build

Isso fará o build e testará a aplicação. Vamos criar a imagem e, finalmente, enviá-la ao nosso registro.

NOTA: Geralmente, é uma prática comum muito boa, usar uma distribuição com um resumo específico ao invés de usar a "mais recente". Deixarei ao leitor decidir qual imagem base e de resumo irá usar, dependendo da versão do Java que estivermos usando.

Implantando o serviço Dog

Estamos quase prontos para implantar a nossa aplicação. Para fazermos isso, primeiro vamos criar um cluster GKE no qual iremos implementar a aplicação.

Criando um Cluster GKE

Para criar um cluster GKE, vamos seguir essas instruções. Basicamente, iremos visitar a página do GKE, aguardar a ativação da API e clicar no botão para criar um cluster. Podemos usar a configuração padrão, mas é importante certificar de clicar no botão "Mais Opções" para permitir acesso total a todas as APIs do Cloud APIs:

Isso permite que os nós do GKE tenham permissões para acessar o restante dos serviços do Google Cloud. Após alguns instantes, o cluster estará criado:

Aplicações que vivem dentro do Kubernetes

O Kubernetes gosta de monitorar a aplicação para garantir que ela esteja funcionando. Em caso de falha, o Kubernetes sabe que a aplicação está inoperante e que precisa gerar uma nova instância. Para fazer isso, precisamos garantir que a aplicação possa responder quando o Kubernetes analisá-lo. Vamos adicionar o actuator e o Spring Cloud Kubernetes.

Adicione as seguintes dependências ao arquivo POM:

<dependency>
    <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Se a aplicação tiver um arquivo application.properties dentro do diretório src/main/resources, precisamos removê-lo e criar um arquivo application.yaml com o seguinte conteúdo:

spring:
  application:
    name: dog-service

management:
  endpoint:
    health:
      enabled: true

Isso adiciona um nome a aplicação e expõe o endpoint íntegro mencionado acima. Para verificar se está funcionando, podemos visitar a aplicação em localhost:8080/actuator/health e devemos ver algo como:

{
    "status": "UP"
}

Configurando para executar no Kubernetes

Para podermos implantar nossa aplicação no novo cluster GKE, precisamos escrever alguns YAML's adicionais. Precisamos criar uma implantação e um serviço. Use a implantação a seguir. Precisamos lembrar de substituir o nome GCR pelo nome do nosso projeto:

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dog-service
spec:
  selector:
    matchLabels:
      app: dog-service
  replicas: 1
  template:
    metadata:
      labels:
        app: dog-service
    spec:
      containers:
        - name: dog-service
          image: gcr.io/<YOUR GCR REGISTRY NAME>/dog
          ports:
            - containerPort: 8080
          livenessProbe:
            initialDelaySeconds: 20
            httpGet:
              port: 8080
              path: /actuator/health
          readinessProbe:
            initialDelaySeconds: 30
            httpGet:
              port: 8080
              path: /actuator/health

Adicione um arquivo service.yaml com o seguinte:

apiVersion: v1
kind: Service
metadata:
  name: dog-service
spec:
  type: NodePort
  selector:
    app: dog-service
  ports:
    - port: 8080
      targetPort: 8080

A implantação contém algumas alterações na análise de prontidão e disponíveis. Isso é feito para que o Kubernetes use os endpoints para analisar a aplicação e verificar se ela está viva. O serviço expõe a implantação para que outros serviços possam consumi-la.

Depois de fazer isso, podemos começar a usar o plugin do Cloud Code que instalamos no início deste artigo. No menu Tools, selecione: 'Cloud Code -> Kubernetes -> Add Kubernetes Support'. Isso adicionará automaticamente um Skaffold YAML a nossa aplicação e irá configurar algumas coisas para a gente, para que possamos implantar no cluster clicando em um botão. Para confirmar que tudo funcionou, podemos inspecionar a configuração na seção 'Run/Debug Configurations' no IntelliJ. Se clicarmos em 'Develop on Kubernetes run', devemos ter escolhido automaticamente o cluster GKE e os arquivos de configuração do Kubernetes e deve mostrar o seguinte:

Clique em Ok e clique no botão verde "Play" no canto superior direito:

Depois disso, o Cloud Code criará a aplicação e a imagem, implantará a aplicação no cluster GKE e transmitirá os logs do Stackdriver para a máquina local. Além disso, abrirá um túnel para que possamos consumir nosso serviço via localhost:8080. Também podemos dar uma analisada na página de cargas de trabalho no Google Cloud Console:

Conclusões

Parabéns se chegou até aqui! A aplicação que construímos neste artigo mostra algumas das principais tecnologias que a maioria das aplicações baseadas em microservices usaria: Um rápido banco de dados NoSQL, totalmente gerenciado, sem servidor e nativo na nuvem (Cloud Firestore), GKE um ambiente gerenciado e pronto para produção para implantar aplicações em container baseados em Kubernetes, e finalmente, um microservice simples e nativo na nuvem com o Spring Boot. Ao longo do caminho, também aprendemos como usar algumas ferramentas como o Cloud Code para otimizar o fluxo de trabalho de desenvolvimento e o Jib para criar aplicações em containers usando padrões Java comuns.

Espero que tenha achado o artigo útil e que tente essas tecnologias. Se achou isso interessante, dê uma olhada em vários codelabs que o Google oferece, onde podemos aprender sobre os produtos Spring e Google Cloud.

Sobre o Autor

Sergio Feilx é engenheiro de software no Google Cloud, onde trabalha na Cloud Engineering Productivity, uma organização do Google Cloud que se concentra em tornar o desenvolvimento sem atrito e melhorar a excelência em produtos.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT