DEV Community

Cover image for Como Executar sua Aplicação Spring Boot no Kubernetes com Banco MySQL
Wagner Negrão 👨‍🔧
Wagner Negrão 👨‍🔧

Posted on • Edited on

Como Executar sua Aplicação Spring Boot no Kubernetes com Banco MySQL

Neste post, vamos aprender passo a passo como subir uma aplicação Spring Boot conectada a um banco de dados MySQL dentro do Kubernetes.

A ideia é ser didático e acessível, então, se você está começando agora, siga com calma cada etapa. Algumas instruções podem variar de acordo com o sistema operacional. Estou usando macOS, mas com pequenas adaptações, tudo funcionará em outros sistemas.

Pré-requisitos

Antes de começar, certifique-se de ter:

  • Docker instalado

  • Kubernetes instalado (Minikube, Docker Desktop ou outro)

  • Maven instalado (opcional, mas recomendado para build manual)

  • Postman ou cURL para testar a API

Se não tiver esses itens configurados ainda, recomendo procurar tutoriais específicos de instalação.

Etapa 1: Criando a Aplicação Spring Boot

Nossa aplicação será uma API REST simples para gerenciar pedidos (Order). Ela terá as operações básicas de criar e buscar pedidos. Vamos focar no essencial para que o projeto seja leve e fácil de entender.

Estrutura do Projeto

Vamos começar definindo a estrutura de dados para o nosso pedido.

@Getter
@Setter
@Entity
@Table(name = "orders")
public class Order {
   @Id
   @GeneratedValue
   private long id;
   private String productName;
   private int quantity;

   public Order(long id, String productName, int quantity) {
       this.id = id;
       this.productName = productName;
       this.quantity = quantity;
   }
}

Enter fullscreen mode Exit fullscreen mode

O próximo passo é criar o repositório. Usaremos o JpaRepository, uma interface do Spring Data que já vem com operações básicas de CRUD.

public interface OrderRespository extends JpaRepository<Order, Long> {
}

Enter fullscreen mode Exit fullscreen mode

Por fim, vamos criar o controller, que será responsável por expor os endpoints da API e interagir diretamente com o banco de dados por meio do repositório.

@RestController
@RequestMapping("/orders")
public class OrderController {

   private OrderRespository orderRespository;

   public OrderController(final OrderRespository orderRespository) {
       this.orderRespository = orderRespository;
   }


   @PostMapping
   public ResponseEntity<Order> create(@RequestBody Order order) {
       Order response = orderRespository.save(order);
       return ResponseEntity.ok(response);
   }


   @GetMapping("/{id}")
   public ResponseEntity<Order> getOrderById(@PathVariable Long id) {
       return orderRespository.findById(id)
               .map(ResponseEntity::ok)
               .orElse(ResponseEntity.notFound().build());
   }
}

Enter fullscreen mode Exit fullscreen mode

Agora, vamos ver as configurações definidas no arquivo application.yaml, responsável por centralizar as propriedades da aplicação.


spring:
 datasource:
   url: jdbc:mysql://${DB_HOST}/${DB_NAME}?useSSL=false
   username: ${DB_USERNAME}
   password: ${DB_PASSWORD}
   driver-class-name: com.mysql.cj.jdbc.Driver
   hikari:
     initialization-fail-timeout: 0  # Disable initialization timeout


 jpa:
   database-platform: org.hibernate.dialect.MySQLDialect
   generate-ddl: true
   show-sql: true
   hibernate:
     ddl-auto: update
   properties:
     hibernate:
       format_sql: true


Enter fullscreen mode Exit fullscreen mode

Algumas das informações acima estão parametrizadas com variáveis de ambiente. Vamos configurá-las mais adiante, mas você já pode deixá-las prontas no seu projeto.

Agora, vou compartilhar o conteúdo do pom.xml, que define as dependências e configurações do projeto. Assim, garantimos que todos estejam utilizando o mesmo ambiente e consigam reproduzir os resultados.

POM

Etapa 2 – Criando a imagem Docker

Agora, vou compartilhar o conteúdo do meu Dockerfile. É nele que definimos como a imagem Docker da aplicação será criada e executada.
Embora fosse possível usar uma versão mais leve, como a slim-buster, optei por manter a imagem padrão para facilitar o entendimento.


# configurando o maven
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /kubernetes
COPY . .
RUN mvn clean package -DskipTests


# configurando o java
FROM openjdk:17
EXPOSE 8080
WORKDIR /kubernetes
COPY --from=build /kubernetes/target/*.jar kubernetes-0.0.1-SNAPSHOT.jar
ENTRYPOINT ["java", "-jar", "/kubernetes/kubernetes-0.0.1-SNAPSHOT.jar"]

Enter fullscreen mode Exit fullscreen mode

A imagem Docker foi dividida em duas partes: uma para compilar o código com o Maven e outra para executar a aplicação com Java. Essa separação é útil caso, no futuro, você queira automatizar o processo de build e deploy com ferramentas de CI/CD e Kubernetes.

Antes de continuar, verifique se sua estrutura de pastas está assim:

project

Etapa 3 – Criando os arquivos do Kubernetes

Agora vamos começar a criar os arquivos de configuração do Kubernetes. O primeiro será o Deployment, responsável por criar e gerenciar o Pod onde nossa API será executada.

apiVersion: apps/v1
kind: Deployment
metadata:
 name: crud-deployment
spec:
 selector:
   matchLabels:
     app: crud-deployment
 replicas: 1
 template:
   metadata:
     labels:
       app: crud-deployment
   spec:
     containers:
     - name: crud-deployment
       image: springboot-k8s:1.0.8
       imagePullPolicy: IfNotPresent
       ports:
         - containerPort: 8080
       env:
         - name: DB_HOST
           valueFrom:
             configMapKeyRef:
               name: mysql-config
               key: dbHost
         - name: DB_NAME
           valueFrom:
             configMapKeyRef:
               name: mysql-config
               key: dbName
         - name: DB_USERNAME
           valueFrom:
             secretKeyRef:
               name: mysql-secret
               key: username
         - name: DB_PASSWORD
           valueFrom:
             secretKeyRef:
               name: mysql-secret
               key: password

Enter fullscreen mode Exit fullscreen mode

Podemos observar que essa configuração define os seguintes parâmetros:


- name: crud-deployment
  image: springboot-k8s:1.0.8
  imagePullPolicy: IfNotPresent

Enter fullscreen mode Exit fullscreen mode

No campo image, colocamos o nome da nossa imagem Docker, que será usada para criar o container no Pod. O imagePullPolicy: IfNotPresent garante que o Kubernetes só irá buscar a imagem no Docker Hub se ela ainda não estiver disponível localmente. Logo abaixo, temos as env, que são variáveis de ambiente. Elas se conectam com os dados do nosso application.yaml, mas ainda precisam ser configuradas.

A seguir, vamos criar o recurso Service, responsável por expor nossa aplicação na porta 8080.

apiVersion: v1
kind: Service
metadata:
 name: crud-svc
spec:
 selector:
   app: crud-deployment
 type: NodePort # or LoadBalancer validar o segundo caso
 ports:
   - port: 8080 # porta do serviço
     targetPort: 8080 # porta do container que sera exposta

Enter fullscreen mode Exit fullscreen mode

Finalizamos a parte da API, então agora vamos configurar o banco de dados.
O primeiro passo é criar o Deployment, que define como o Kubernetes deve iniciar e manter o container do MySQL rodando corretamente.

apiVersion: apps/v1
kind: Deployment
metadata:
 name: mysql-deployment
 labels:
   app: mysql
   tier: database
spec:
   selector:
       matchLabels:
         app: mysql
         tier: database
   replicas: 1
   template:
       metadata:
         labels: # Deve ser igual ao do servico e deployment
           app: mysql
           tier: database
       spec:
         containers:
           - image: mysql:5.7
             imagePullPolicy: IfNotPresent
             name: mysql
             args:
               - "--ignore-db-dir=lost+found"  # Ignore lost+found directory
             env:
               - name: MYSQL_ROOT_PASSWORD
                 valueFrom:
                   secretKeyRef:
                     name: mysql-secret
                     key: password
               - name: MYSQL_DATABASE
                 valueFrom:
                   configMapKeyRef:
                     name: mysql-config
                     key: dbName
             ports:
               - containerPort: 3306
                 name: mysql
             volumeMounts:
               - name: mysql-storage
                 mountPath: /var/lib/mysql
         volumes:
           - name: mysql-storage
             persistentVolumeClaim:
               claimName: mysql-pvc
Enter fullscreen mode Exit fullscreen mode

Nesse arquivo, definimos as portas e os volumes necessários para o funcionamento do MySQL, além das variáveis de ambiente que representam as credenciais de acesso, que ainda serão configuradas.

Agora, vamos criar o Service, que irá expor o banco de dados na porta padrão 3306.

apiVersion: v1
kind: Service
metadata:
 name: mysql-svc
 labels:
   app: mysql
   tier: database
spec:
 ports:
   - port: 3306
     targetPort: 3306
     protocol: TCP
 selector:
   app: mysql
   tier: database
 clusterIP: None  # Use None for headless service if needed
Enter fullscreen mode Exit fullscreen mode

Agora vamos configurar o PersistentVolumeClaim (PVC), que será responsável por garantir o armazenamento persistente dos dados do nosso banco de dados.


apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: mysql-pvc
 labels:
   app: mysql
   tier: database
spec:
 accessModes:
   - ReadWriteOnce
 resources:
   requests:
     storage: 1Gi
Enter fullscreen mode Exit fullscreen mode

Com a configuração do banco de dados finalizada, agora vamos criar os recursos que irão fornecer os valores para os parâmetros utilizados anteriormente.

Começaremos pelo ConfigMap, que será responsável por armazenar as configurações que não são sensíveis, como o nome do banco e o host.

apiVersion: v1
kind: ConfigMap
metadata:
 name: mysql-config
data:
 dbHost: mysql-svc
 dbName: javatest
Enter fullscreen mode Exit fullscreen mode

Agora vamos configurar a Secret, que será usada para armazenar informações sensíveis, como usuário e senha do banco de dados.

No exemplo abaixo, os dados estão comentados, mas em um ambiente de produção, o ideal seria utilizar um serviço de gerenciamento de segredos, como o AWS Secrets Manager, HashiCorp Vault ou o próprio Sealed Secrets do Kubernetes.

apiVersion: v1
kind: Secret
metadata:
   name: mysql-secret
data:
   username: cm9vdA==  # encoded 'root'
   password: cm9vdA==  # encoded 'root'
Enter fullscreen mode Exit fullscreen mode

Etapa 4 – Executando tudo

Vamos recapitular rapidamente o que foi feito até aqui:

  • Criamos os arquivos Deployment e Service da nossa API, que serão responsáveis por controlar a criação e exposição dos Pods da aplicação.

  • Em seguida, configuramos os arquivos de Deployment, Service e PersistentVolumeClaim para o banco de dados, garantindo seu funcionamento e armazenamento persistente.

  • Por fim, criamos o ConfigMap e a Secret, que armazenam respectivamente as variáveis de ambiente e informações sensíveis (como usuário e senha do banco).

Com tudo configurado, podemos agora executar os arquivos.

O primeiro passo é gerar a imagem da aplicação com base no Dockerfile.
Caso esteja usando o Minikube localmente, execute o seguinte comando no diretório onde está seu Dockerfile:

eval $(minikube docker-env)
Enter fullscreen mode Exit fullscreen mode

Esse comando faz com que o terminal passe a usar o Docker interno do Minikube, tornando sua imagem visível para o cluster. Assim, ao executar os manifests do Kubernetes, o Minikube conseguirá encontrar e rodar sua imagem sem precisar baixá-la de um repositório externo.

Agora vamos executar o comando responsável por gerar a imagem Docker da nossa aplicação, utilizando o Dockerfile que criamos anteriormente. No terminal, dentro do diretório onde está localizado o Dockerfile, execute o seguinte comando:

docker build -t springboot-k8s:1.0.8 .
Enter fullscreen mode Exit fullscreen mode

Neste exemplo, estou utilizando a versão 1.0.8 da imagem, mas você pode começar com a versão 1.0.0 e, futuramente, atualizar a tag no seu arquivo de Deployment do Kubernetes conforme necessário.

Para verificar se a imagem foi criada com sucesso, execute o seguinte comando no terminal:

docker images
Enter fullscreen mode Exit fullscreen mode

Você deverá ver a imagem que acabou de ser gerada listada entre os resultados.

Agora, com tudo pronto, vamos aplicar os arquivos de configuração do Kubernetes para subir nossa aplicação.

 kubectl apply -f infra/
Enter fullscreen mode Exit fullscreen mode

Caso o comando para aplicar todos os arquivos de uma vez não funcione corretamente — seja por erro de formatação em algum arquivo ou problema pontual —, você pode aplicar cada um manualmente com o seguinte comando:

kubectl apply -f nome-do-arquivo.yaml
Enter fullscreen mode Exit fullscreen mode

Basta repetir esse comando para cada arquivo de configuração (como deployment.yaml, service.yaml, configmap.yaml, etc.), garantindo que todos os recursos sejam criados corretamente no cluster.

Se tudo ocorreu conforme o esperado, você poderá verificar os pods em execução com o seguinte comando:

kubectl get pods
Enter fullscreen mode Exit fullscreen mode

Esse comando lista todos os pods ativos no cluster e seus respectivos status. Se tudo estiver certo, o status dos pods deve aparecer como Running.

kubernetes

Etapa 5 – Acessando a aplicação

Com tudo em execução, agora podemos fazer uma requisição para testar nossa API.

No entanto, se você estiver utilizando o Minikube, talvez precise criar um túnel para expor o serviço externamente, dependendo de como o ambiente foi configurado.

Para isso, execute o comando abaixo:

minikube service <nome-do-serviço>
Enter fullscreen mode Exit fullscreen mode

Esse comando abrirá um endpoint acessível via navegador ou ferramentas como o Postman.

Se tiver dúvidas sobre qual nome usar, você pode listar todos os serviços disponíveis com:

minikube service list
Enter fullscreen mode Exit fullscreen mode

A saída será parecida com isso:

list service

Ao executar o comando:

minikube service <nome-do-serviço>
Enter fullscreen mode Exit fullscreen mode

o Minikube criará automaticamente um túnel de conexão, abrindo uma URL no navegador (ou exibindo no terminal) que permite o acesso direto à sua API rodando dentro do cluster Kubernetes.

Esse túnel expõe a porta configurada no Service, facilitando testes locais sem a necessidade de configurar um Ingress ou LoadBalancer manualmente.

tunel

Etapa 6 – Testando via Postman

Agora que a aplicação está rodando e exposta corretamente, podemos fazer nossas requisições de teste usando o Postman (ou qualquer cliente HTTP de sua preferência).

post

get

Se você quiser remover os recursos criados (como Pods, Services, Deployments, etc.), basta executar o comando abaixo no diretório onde estão os seus arquivos de configuração:

kubectl delete -f infra/
Enter fullscreen mode Exit fullscreen mode

Esse comando irá deletar todos os recursos definidos na pasta infra/, limpando o ambiente do cluster.

Espero que este post tenha sido claro e que você tenha conseguido executar todos os passos com sucesso.
Caso encontre algum problema ou tenha dúvidas durante o processo, deixe um comentário — ficarei feliz em tentar ajudar!

🚀 Próximo passo

No próximo post, vamos avançar mais um pouco, vamos configurar um Load Balancer e adicionar o Ingress à nossa estrutura Kubernetes, permitindo que diferentes aplicações se comuniquem entre si de forma controlada e organizada.

Top comments (0)