Deploy de Aplicações
Conceitos Gerais
Nos projetos da PDPJ, os ambientes de deployment estão instalados em clusters Kubernetes, gerenciados por meio da ferramenta open source de orquestração de containeres chamada Rancher.
De toda sorte, o desenvolvedor não necessita maiores conhecimentos a respeito desses ambientes, bastando que sua aplicação possua os arquivos yamls sugeridos para que o deploy ocorra de forma automática por meio de pipelines de CI/CD (Continuous Delivery e Continuous Integration) executados pelo GitLab do CNJ (https://git.cnj.jus.br/).
Além disso, como a aplicação será executada em ambiente "contineirizado", ela deve possuir um arquivo Dockerfile, especificando o modo pelo qual ela deve ser empacotada.
Por fim, o projeto deve possuir um arquivo .gitlab-ci.yml, o qual especifica a pipeline propriamente dita.
Em suma, para que ocorra o deploy da aplicação de forma automática, é necessário:
- Presença de Dockerfile definindo o modo de empacotamento da aplicação
- Presença do arquivo .gitlab-ci.yml definindo a pipeline de deploy automático
- Presença da pasta kubernetes, com os arquivos yamls necessários para os diversos ambientes de destino
Quando ocorrerá o deploy automático? Depende da configuração feita no arquivo .gitlab-ci.yaml, mas o exemplo trazido mais abaixo gera um deploy automático para o ambiente de homologação quando houver um push para a branch master do repositório git, e um deploy automático para o ambiente de produção quando uma tag é lançada no projeto.
Acesso aos Logs da Aplicação
Caso seguida as recomendações da seção anterior, a equipe de desenvolvimento terá acesso aos logs gerados pelos pods da aplicação diretamente no GitLab.
Para tanto, basta clicar no menu Operações, submenu Logs, na página do projeto:
Logs diretamente pelo GitLab
Estrutura do Projeto
A estrutura do projeto, independentemente da linguagem de programação em que for escrita, deve possuir, portanto, a seguinte estrutura mínima:
projeto
├── Dockerfile [1]
├── .gitlab-ci.yml [2]
└── kubernetes/ [3]
├── base/
│ ├── base.yml
│ └── kustomization.yml
└── overlays/
├── eks-hml-01/ [4]
│ ├── configmaps.yml [5]
│ ├── ingress.yml [6]
│ └── kustomization.yml [7]
└── eks-prd-01/ [8]
├── configmaps.yml
├── ingress.yml
└── kustomization.yml
- Dockerfile que define o empacotamento/build da aplicação (varia para cada linguagem de programação/caso concreto)
- Arquivo yaml que define os passos da pipeline de build e deploy automáticos
- Pasta que define as configurações necessárias para o ambiente kubernetes
- Pasta com configurações para o ambiente de homologação da PDPJ
- Arquivo de configuração/propriedades da aplicação para o ambiente em questão
- Arquivo de configuração do ingress da aplicação. Necessário apenas para aquelas aplicações que necessitem de URL própria exposta na Internet. Se a aplicação for um serviço que será acessado exclusivamente via Gateway API, pode ser omitido
- Arquivo da ferramenta Kustomize, que realiza a personalização das configurações conforme o ambiente
- Pasta com configurações para o ambiente de produção da PDPJ
Se o projeto for uma aplicação Java, sugere-se a existência também de um arquivo .m2/settings.xml
, que é o arquivo de configuração do maven apontando para o repositório de artefatos do CNJ, utilizado no exemplo abaixo para o build de aplicações Java.
Exemplos de Arquivos
Dockerfile para uma aplicação em Java
# Dockerfile
FROM openjdk:11-jre-slim
COPY target/myapp.jar /opt/myapp/myapp.jar
EXPOSE 8080
ENTRYPOINT [ "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/opt/myapp/myapp.jar" ]
.gitlab-ci.yml
(independe da lingaguem de programação, exceto a fase de build, que varia conforme a linguagem adotada. No exemplo abaixo, presume Java com maven)
stages:
- package
- build
- release
- deploy
# build de aplicações java
maven-package:
stage: package
interruptible: true
image:
name: maven:3-jdk-11
entrypoint: [ "" ]
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
DOCKER_DRIVER: overlay2
MAVEN_CLI_OPTS: "--settings .m2/settings.xml --batch-mode "
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
services:
- docker:dind
cache:
key: "${CI_PROJECT_ID}-${CI_JOB_NAME}"
paths: [ ".m2/repository" ]
artifacts:
paths: [ "target/myapp.jar" ]
script:
- mvn $MAVEN_CLI_OPTS package
# Ambiente de homologação
.env-eks-hml-01:
environment:
name: eks-hml-01
deployment_tier: staging
kubernetes:
namespace: negociais
# Ambiente de produção
.env-eks-prd-01:
environment:
name: eks-prd-01
deployment_tier: production
kubernetes:
namespace: negociais
.prepare-job-template:
stage: package
image:
name: registry.cnj.jus.br/segsa/k8s-utils:latest
entrypoint: [ "" ]
dependencies: []
artifacts:
paths: [ "kubernetes/" ]
environment:
action: prepare
script:
- cd "$CI_PROJECT_DIR/kubernetes/overlays/$CI_ENVIRONMENT_NAME"
- kustomize edit set image "registry.cnj.jus.br/pdpj/myapp:$CI_COMMIT_REF_NAME"
- kustomize edit add annotation --force "app.gitlab.com/app:${CI_PROJECT_PATH_SLUG}"
"app.gitlab.com/env:${CI_ENVIRONMENT_SLUG}" "app.gitlab.com/commit-short-sha:${CI_COMMIT_SHORT_SHA}"
- kubectl apply --dry-run=client -k .
prepare-eks-hml-01:
extends:
- .prepare-job-template
- .env-eks-hml-01
prepare-eks-prd-01:
extends:
- .prepare-job-template
- .env-eks-prd-01
# Criação da imagem Docker da aplicação e seu envio para o servidor do CNJ
docker-image:
stage: build
image: docker:20.10
services:
- docker:20.10-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- docker login -u "$NEXUS_USERNAME" -p "$NEXUS_PASSWORD" registry.cnj.jus.br
script:
- docker build --pull -t "registry.cnj.jus.br/pdpj/myapp:$CI_COMMIT_REF_NAME"
-t "registry.cnj.jus.br/pdpj/myapp:latest" -f Dockerfile "$CI_PROJECT_DIR"
- docker push "registry.cnj.jus.br/pdpj/myapp:$CI_COMMIT_REF_NAME"
- docker push "registry.cnj.jus.br/pdpj/myapp:latest"
release-job:
stage: release
image:
name: registry.gitlab.com/gitlab-org/release-cli:latest
entrypoint: [ "" ]
dependencies: []
rules:
- if: $CI_COMMIT_TAG
variables:
GIT_STRATEGY: none
script:
- release-cli create --tag-name "${CI_COMMIT_TAG}" --description "${CI_COMMIT_MESSAGE}"
.deploy-job-template:
stage: deploy
image:
name: registry.cnj.jus.br/segsa/k8s-utils:latest
entrypoint: [ "" ]
variables:
GIT_STRATEGY: none
retry:
max: 2
when: stuck_or_timeout_failure
script:
- cd "$CI_PROJECT_DIR/kubernetes/overlays/$CI_ENVIRONMENT_NAME"
- kubectl apply -k . || (kubectl delete --ignore-not-found --wait -k . && kubectl apply -k .)
deploy-eks-hml-01:
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
extends:
- .deploy-job-template
- .env-eks-hml-01
dependencies: [ "prepare-eks-hml-01" ]
deploy-eks-prd-01:
rules:
- if: $CI_COMMIT_TAG
extends:
- .deploy-job-template
- .env-eks-prd-01
dependencies: [ "prepare-eks-prd-01" ]
Importante destacar que a imagem docker deve ser sempre salva no repositório de artefatos do CNJ, uma vez que o Rancher busca de lá as imagens para o deploy da aplicação. O usuário não precisa se preocupar com as credenciais do repositório, uma vez que eles são fornecidas de forma transparente pelo ambiente do GitLab.
Os arquivos dentro do diretório kubernetes/base
contêm a aplicação e tudo que é comum a todos os ambientes:
kubernetes/base/base.yml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: negociais # namespace para módulos negociais da PDPJ
labels:
app: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: myapp
topologyKey: kubernetes.io/hostname
containers:
- name: myapp
image: registry.cnj.jus.br/pdpj/myapp:latest
imagePullPolicy: Always
env:
- name: TZ
value: America/Sao_Paulo
- name: EUREKA_INSTANCE_PREFERIPADDRESS
value: "true"
envFrom:
- secretRef:
name: db-secret # secret no Kubernetes (senhas e outras informações confidenciais devem ser armazenadas em secrets)
ports:
- containerPort: 8080
name: http
livenessProbe:
httpGet:
path: /
port: http
failureThreshold: 6
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 10
readinessProbe:
httpGet:
path: /
port: http
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 10
startupProbe:
httpGet:
path: /
port: http
failureThreshold: 60
periodSeconds: 30
timeoutSeconds: 10
resources:
requests:
memory: "512Mi"
cpu: "50m"
limits:
memory: "1Gi"
cpu: "1000m"
volumeMounts:
- name: myapp-config
mountPath: /opt/myapp/myapp.properties
subPath: myapp.properties
readOnly: true
volumes:
- name: myapp-config
configMap:
name: myapp-config
...
---
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: negociais # namespace para módulos negociais da PDPJ
labels:
app: myapp
spec:
type: NodePort
ports:
- name: http
port: 8080
selector:
app: myapp
...
kubernetes/base/kustomization.yml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- base.yml
...
Os arquivos dentro do diretório kubernetes/overlays/eks-hml-01
contêm somente as configurações que devem ser aplicadas ao ambiente de homologação da aplicação.
kubernetes/overlays/eks-hml-01/configmaps.yml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
namespace: myapp-namespace
labels:
app: myapp
data:
myapp.properties: |
ambiente=homologação
propriedade.X=valorX
...
---
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-env
namespace: myapp-namespace
labels:
app: myapp
data:
SPRING_PROFILES_ACTIVE: "staging"
...
Um configmap nada mais é do que uma ferramenta de substituição de arquivos e seus conteúdos fornecido pelo ambiente Kubernetes, a fim de facilitar a configuração da aplicação conforme o ambiente em que ela esteja a executar (homologação, sustentação, produção, treinamento, etc).
kubernetes/overlays/eks-hml-01/ingress.yml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
namespace: myapp-namespace
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
alb.ingress.kubernetes.io/ssl-redirect: '443'
labels:
app: myapp
spec:
rules:
- host: myapp.stg.pdpj.jus.br
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
name: http
...
Um ingress, por sua vez, é a configuração do modo pelo qual a aplicação será acessada externamente pela Internet. Essa configuração só faz sentido se a aplicação possuir uma URL/frontend própria. Caso ela vá servir apenas como fachada REST acessível via o Gateway API, a configuração de ingress é desnecessária.
kubernetes/overlays/eks-hml-01/kustomization.yml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- configmaps.yml
- ingress.yml
...
O arquivo kustomization.yaml possui a configuração da ferramenta Kustomize, utilizada pelo CNJ para viabilizar o deploy automático a depender do ambiente de destino.
Por sua vez, os arquivos dentro do diretório kubernetes/overlays/eks-prd-01
contêm somente as configurações que devem ser aplicadas ao ambiente de produção da aplicação:
kubernetes/overlays/eks-prd-01/configmaps.yml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
namespace: myapp-namespace
labels:
app: myapp
data:
myapp.properties: |
ambiente=produção
propriedade.X=valorY
...
---
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-env
namespace: myapp-namespace
labels:
app: myapp
data:
SPRING_PROFILES_ACTIVE: "production"
...
kubernetes/overlays/eks-prd-01/ingress.yml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
namespace: myapp-namespace
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
alb.ingress.kubernetes.io/ssl-redirect: '443'
labels:
app: myapp
spec:
rules:
- host: myapp.pdpj.jus.br
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
name: http
...
kubernetes/overlays/eks-prd-01/kustomization.yml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- configmaps.yml
- ingress.yml
# Na produção utilizamos 2 ou mais réplicas, para alta disponibilidade da aplicação
replicas:
- name: myapp
count: 2
...
Por fim, se aplicação for escrita na linguagem Java e fizer uso da ferramenta maven, o seguinte arquivo deve existir no projeto, na pasta .m2
(obs: as variáveis de ambiente NEXUS_USERNAME e NEXUS_PASSWORD são preenchidas em tempo de execução pelo próprio GitLab, ou seja, pode-se deixar o conteúdo arquivo abaixo ipsis litteris no projeto):
m2/settings.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>nexus</id>
<username>${env.NEXUS_USERNAME}</username>
<password>${env.NEXUS_PASSWORD}</password>
</server>
</servers>
<mirrors>
<mirror>
<id>nexus</id>
<name>nexus.cnj.jus.br</name>
<url>https://nexus.cnj.jus.br/repository/maven-public</url>
<mirrorOf>external:*</mirrorOf>
</mirror>
</mirrors>
</settings>
Dúvidas e necessidades oriundas do desenvolvimento ou da infraestrutura devem ser dirigidas ao Mentor Técnico do projeto, indicado pelo CNJ e apresentado na reunião de Kick off.