Nesse artigo fizemos um Hello World de Docker com Java e abordamos os conceitos de imagens e containers. Entendemos que aplicações na forma de containers possuem caracteristicas peculiares que possibilitam sua replicação e distribuição entre os diferentes ambientes com muito mais agilidade. Porém elas também apresentam novos desafios.
Com o comando docker run podemos criar e iniciar um novo container e nossa aplicação está no ar. Porém temos apenas uma única instancia da aplicação em um único docker host. O que acontece se:
- o número de usuários crescer acima da capacidade suportada pela aplicação?
- precisarmos monitorar o estado de dezenas ou centenas de containers?
- um ou vários containers param por qualquer motivo?
- o host no qual rodam os containers pára?
Aplicação que iremos conteinerizar já está pronta. É a que usamos nesse artigo que apresenta uma introdução à autenticação JWT em Java. O que a aplicação faz não é revelevante por agora. O que importa é que ela já possui uma suite testes de integração automatizados apontando para localhost.
Após conteinerizá-la e replicar os containers, vamos rodar os testes apontando para o load-balancer do cluster kubernetes, o qual irá distribuir a carga entre as réplicas criadas.
Para realizar esse teste precisamos instalar o minikube (utilitário que simula um cluster na sua máquina) com o seguinte comando:
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
Agora instalamos o utilitário kubectl que permite gerencial o cluster kubernetes por linha de comando:
$ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
$ sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
$ kubectl version --client
O primeiro passo para conteinerizar a aplicação é criar seu Dockerfile, no qual definimos todo o script para seu build e deploy.
Nosso Dockerfile começa declarando que o build começa em uma imagem que possui o JDK 11 e o maven instalados:
- copiamos a pasta /src e o arquivo pom.xml para uma imagem que já contenha JDK 11 e Maven configurados (maven:3.6.3-openjdk-11-slim)
- buildamos a aplicação dentro da imagem com mvn clean package (esse passo gera o .war)
- copiamos o artefato gerado (.war) no passo anterior para uma outra imagem jboss/wildfly e descartamos a imagem do Maven
- expomos a porta 8080 do container jboss/wildfly
FROM maven:3.6.3-openjdk-11-slim AS Builder
LABEL key="https://finalexception.blogspot.com"
COPY ./pom.xml /app/pom.xml
COPY ./src /app/src
WORKDIR /app
RUN mvn clean package
FROM jboss/wildfly
EXPOSE 8080
COPY --from=Builder /app/target/hello-authentication.war /opt/jboss/wildfly/standalone/deployments/
Com o Dockerfile pronto, criamos a imagem:
$ docker build -t rafaelnasc1mento/hello-authentication:latest .
Em seguida publico a imagem em alguma conta docker-hub com o padrão <prefixo-conta>/hello-authentication:latest:
$ docker login
$ docker push rafaelnasc1mento/hello-authentication:latest
Agora que temos a imagem devidamente publicada no hub, é hora do kubernetes entrar em ação.
Este arquivo contem a descrição de como nosso cluster irá operar e qual é o estado desejado do mesmo:
- Nosso serviço se chama hello-authentication-service.
- Queremos um Load-Balancer para distribuir a carga entre os containers
- Queremos 3 réplicas do container
---
kind: Service
apiVersion: v1
metadata:
name: hello-authentication-service
spec:
selector:
app: hello-authentication
ports:
- protocol: "TCP"
# port accessible inside cluster
port: 8081
# port to forward to inside pod
targetPort: 8080
# port accessible outside the cluster
# whenever I hit this port, it will forward the request to the 'targetPort', which is the port to forward to the pod specified in 'selector.app'
nodePort: 30001
type: LoadBalancer
---
apiVersion: apps/v1
# a deployment defines the desired state of our application
kind: Deployment
metadata:
name: hello-authentication-deployment
spec:
replicas: 3
selector:
matchLabels:
# name that's refereed by the load-balancer or other components inside the cluster
app: hello-authentication
tier: backend
template:
metadata:
labels:
app: hello-authentication
tier: backend
spec:
containers:
# container name
- name: hello-authentication
# docker image at docker-hub
image: rafaelnasc1mento/hello-authentication:1.0
ports:
- containerPort: 8080
Isso é tudo! Para subirmos o cluster basta iniciar o minikube e criar o deployment:
$ minikube start
$ kubectl create -f deployment.yml
Use apply ao invez de create se o cluster já existe e você quer apenas atualizar alguma diretriz, editando o deployment.yaml.
Nesse ponto nosso cluster minimamente resiliente com 3 réplicas do container Hello-Authentication e um load balancer está totalmente operacional. Para confirmar, execute:
$ kubectl get pods
PODs são a unidade básica de deploy em um cluster. Neste caso podemos ver 3 instâncias de cada um dos containers.
Conforme dito, o Load Balancer é a porta de entrada dos serviços, umas vez que os containers não ficam expostos diretamente ao mundo exterior. Para descobrir o IP do Load Balancer execute:
$ minikube ip
Pelo output do comando, podemos ver que o IP do seriço Load Balancer, através do qual acessamos o cluster é 192.168.49.2.
Testes Automatizados
No pacote de testes do projeto Hello Atuhtentication, temos a classe Constants.java e nela o campo BASE_URI. Vamos colocar o IP do minikube como valor desse campo:
Agora, ao rodar os testes, as requisições serão direcionadas para o IP do cluster 192.168.49.2 na porta 30001. Depois da porta, colocamos a url dos serviços conforme definido na aplicação java.
Clique com o botão direito na classe ServiceApiTest.java para rodar os testes:
Podemos ver que as replicas recebem as requisições normalmente.
Outro recurso interessante do minikube é o dashborad. Execute:
$ minikube addons enable metrics-server
$ minikube dashboard