Na Movile, temos diversas aplicações rodando em nossa infraestrutura distribuídas em datacenters e provedores de cloud, como AWS, Google e Azure. Com isso, sempre nos preocupamos em disponibilizar o deploy dessas aplicações, da melhor maneira, possibilitando: fácil gerenciamento, rastreabilidade e atualização. Atualmente construímos pacotes RPM dessas aplicações a cada release e nossa automação com o Chef se encarrega de fazer o deploy nos servidores. Assim, o objetivo deste artigo é demonstrar com um exemplo bem simples como estamos fazendo o deploy de aplicações “conteinerizadas”, orquestrado pelo Kubernetes.

Kubernetes

Atualmente estamos migrando algumas aplicações para contêineres e escolhemos o Kubernetes para orquestração devido às principais características:

  • Self-healing

Se ocorrer alguma falha, como um crash na aplicação, o contêiner é recriado de maneira instantânea ao invés de ficar parado, com problema, esperando uma ação manual.

  • Auto-scaling

É possível realizar o auto-scaling de maneira extremamente simples definindo o número de réplicas de seu pod.

  • Gerenciamento de DNS

Para cada pod é criado sua entrada de DNS e, se desenvolvido seu devido manifesto, também cria um nome que pode responder para vários pods (DNS round-robin).

  • Load Balancer

É possível criar load balancers externos e internos para publicar os Front-ends.

  • Rolling Update e Rollback

Atualização de suas aplicações controladas e graduais assim como a facilidade de reversão caso algo dê errado!

Exemplo de aplicação em Kubernetes

Para facilitar o entendimento, irei demonstrar uma configuração extremamente simples de deploy em Kubernetes:

$ cat exemplo-deploy.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: exemplo-deploy
  namespace: default
  labels:
app: exemplo-deploy
spec:
  template:
metadata:
   labels:
     app: exemplo-deploy
spec:
   containers:
   - name: exemplo-deploy
     image: nginx:latest
     ports:
     - containerPort: 80

$ cat exemplo-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: exemplo-service
  namespace: default
  labels:
app: exemplo
spec:
  ports:
  - protocol: TCP
port: 80
targetPort: 80
  selector:
app: exemplo-deploy
  type: LoadBalancer
$ kubectl apply -f exemplo-service.yaml
service "exemplo-service" created
$ kubectl apply -f exemplo-deploy.yaml
deployment "exemplo-deploy" created
$ kubectl get svc
NAME                             TYPE  CLUSTER-IP  EXTERNAL-IP   PORT(S)                               AGE
exemplo-service                  LoadBalancer   10.55.246.1 104.154.94.121   80:30650/TCP                     57s
kubernetes                       ClusterIP  10.55.240.1 <none>        443/TCP                               55d

$ kubectl get pods

NAME                               READY  STATUS    RESTARTS AGE
exemplo-deploy-7f56cfb95f-jn55s    1/1       Running 0          1m

$ curl -I http://104.154.94.121
HTTP/1.1 200 OK
Server: nginx/1.15.0
Date: Wed, 13 Jun 2018 21:51:33 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 05 Jun 2018 12:00:18 GMT
Connection: keep-alive
ETag: "5b167b52-264"
Accept-Ranges: bytes
$

Como visto no exemplo acima, fizemos o deploy do nginx pelo Kubernetes expondo sua porta publicamente pelo load balancer.

Quando temos uma aplicação somente, é fácil de controlar seus arquivos YAML, mas e quando temos diversas aplicações onde a grande maioria é semelhante, muitas vezes, só mudando de nome e porta? E se eu quiser alterar a versão ou alterar o valor de um campo? Por causa dessas e outras perguntas,tive a necessidade de encontrar algo que facilitasse o templating desses arquivos YAML e encontrei o Helm.

Helm

O Helm é um gerenciador de aplicações Kubernetes que te ajuda a criar, versionar, compartilhar e publicar seus artefatos. Você consegue desenvolver templates dos seus arquivos YAML e durante a instalação de cada aplicação personalizar por parâmetros as variáveis com facilidade. O Helm é mantido pela comunidade e gigantes como Google e Microsoft.

Componentes

O Helm tem dois principais componentes:

  •     Helm Client

É o CLI onde gerenciamos repositórios, desenvolvemos localmente os charts e interagimos com o Tiller server. Escrito em Go, utiliza o protocolo gRPC para interagir com o servidor Tiller.

  •       Tiller Server

É o servidor que interage com a API do Kubernetes através dos comandos executados pelo Helm Client. Também é escrito em Go.

Instalação

A instalação é bem simples mas exige atenção na pós-instalação, pois em alguns casos é necessário criar uma role para garantir o acesso do Tiller no cluster Kubernetes:

  •       Instalação do Client:
$ curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh
$ chmod 700 get_helm.sh
$ ./get_helm.sh
  •    Criar o ServiceAccount e ClusterRoleBinding:
$ cat rbac-config.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
  metadata:
name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
name: tiller
namespace: kube-system
$ kubectl create -f rbac-config.yaml
  •       Inicializar o Tiller no cluster e atualizar o repositório:
$ helm init --service-account tiller
$ helm repo update

Pronto! Agora você pode procurar e instalar pacotes do repositório público e criar os seus próprios!

Charts

O Helm utiliza um formato de empacotamento chamado “charts“. Um chart é uma coleção de arquivos que descrevem os recursos que serão aplicados no Kubernetes (os YAMLs).

  •    Estrutura do Chart
exemplo/

Chart.yaml       # A YAML file containing information about the chart
LICENSE          # OPTIONAL: A plain text file containing the license for the chart
README.md        # OPTIONAL: A human-readable README file
requirements.yaml   # OPTIONAL: A YAML file listing dependencies for the chart
values.yaml      # The default configuration values for this chart
charts/          # A directory containing any charts upon which this chart depends.
templates/          # A directory of templates that, when combined with values,
                    # will generate valid Kubernetes manifest files.
templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes
  •       Criando nosso chart

Agora vamos ao que interessa! Vamos transformar nossa aplicação “exemplo” em um chart! Primeiro vamos iniciá-lo:

$ helm create exemplo
Creating exemplo
$

Será criado um diretório chamado “exemplo” já com alguns arquivos de apoio, recomendo fortemente verificar esses arquivos para entender melhor sua estrutura.

No nosso caso, vamos apagar tudo da pasta templates e começar do zero!

$ rm -rf exemplo/templates/*

Agora vamos mover nosso exemplo-deploy.yaml e exemplo-service.yaml para a pasta exemplo/templates e alterar onde queremos colocar as diretivas de template:

$ cat exemplo/templates/exemplo-deploy.yaml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-deploy
  namespace: {{ .Release.Namespace }}
  labels:
    app: {{ .Release.Name }}-deploy
spec:
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-deploy
    spec:
      containers:
      - name: exemplo-deploy
        image: nginx:{{ .Values.deployment.version }}
        ports:
        - containerPort: 80

$ cat exemplo/templates/exemplo-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}-service
  namespace: {{ .Release.Namespace }}
  labels:
    app: {{ .Release.Name }}
spec:
  ports:
  - protocol: TCP
    port: {{ .Values.service.port }}
    targetPort: 80
  selector:
    app: {{ .Release.Name }}-deploy
  type: LoadBalancer

E o exemplo/values.yaml irá conter alguns valores padrão que poderão ser alterados pelo usuário na instalação do chart:

$ cat exemplo/values.yaml
deployment:
  version: latest

service:
  port: 80
$

Vamos testar o chart no modo dry-run e debug para verificar se tudo está conforme esperamos:

$ helm install --dry-run --debug exemplo/
[debug] Created tunnel using local port: '38007'

[debug] SERVER: "127.0.0.1:38007"

[debug] Original chart version: ""
[debug] CHART PATH: /home/ip_fix/exemplo

NAME:   fair-salamander
REVISION: 1
RELEASED: Sun Jun 17 20:25:10 2018
CHART: exemplo-0.1.0

USER-SUPPLIED VALUES:
{}

COMPUTED VALUES:
deployment:
  version: latest
service:
  port: 80

HOOKS:
MANIFEST:

---

# Source: exemplo/templates/exemplo-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: fair-salamander-service
  namespace: default
  labels:
    app: fair-salamander
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  selector:
    app: fair-salamander-deploy
    type: LoadBalancer
---
# Source: exemplo/templates/exemplo-deploy.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: fair-salamander-deploy
  namespace: default
  labels:
    app: fair-salamander-deploy
spec:
  template:
    metadata:
      labels:
        app: fair-salamander-deploy
    spec:
      containers:
      - name: exemplo-deploy
        image: nginx:latest
        ports:
        - containerPort: 80
$

Como podem ver, simulei a instalação para ver o template renderizado sem ser instalado de fato, sem passar parâmetros adicionais para ver os valores padrão. Ele gerou automaticamente o Release Name (fair-salamander).

  •       Instalando e testando o Chart

Agora vamos ver o resultado final da instalação do que seriam duas aplicações iguais, mas com parâmetros diferentes na instalação, sem a necessidade de retrabalho.

  

$ helm install exemplo/ --name exemplo01 --namespace exemplo01 --set deployment.version=1.15.0,service.port=8080
NAME:   exemplo01
LAST DEPLOYED: Sun Jun 17 20:40:50 2018
NAMESPACE: exemplo01
STATUS: DEPLOYED

RESOURCES:
==> v1/Service
NAME            TYPE       CLUSTER-IP EXTERNAL-IP  PORT(S)  AGE
exemplo01-service  LoadBalancer 10.55.250.66  <pending> 8080:30097/TCP  2s

==> v1beta1/Deployment
NAME           DESIRED  CURRENT UP-TO-DATE  AVAILABLE AGE
exemplo01-deploy  1 1  1        0      2s

==> v1/Pod(related)
NAME                            READY  STATUS         RESTARTS  AGE
exemplo01-deploy-55b74c5c58-dl9qj  0/1 ContainerCreating 0  2s

$ helm install exemplo/ --name exemplo02 --namespace exemplo02 --set deployment.version=1.14.0,service.port=8081
NAME:   exemplo02
LAST DEPLOYED: Sun Jun 17 20:41:25 2018
NAMESPACE: exemplo02
STATUS: DEPLOYED

RESOURCES:
==> v1/Service
NAME            TYPE       CLUSTER-IP   EXTERNAL-IP PORT(S)      AGE
exemplo02-service  LoadBalancer 10.55.255.9  <pending> 8081:31590/TCP  0s

==> v1beta1/Deployment
NAME           DESIRED  CURRENT UP-TO-DATE  AVAILABLE AGE
exemplo02-deploy  1 1  1        0       0s

==> v1/Pod(related)
NAME                               READY STATUS  RESTARTS  AGE
exemplo02-deploy-768c96479b-hn7zn  0/1 ContainerCreating 0  0s

$ helm list
NAME   REVISION UPDATED                 STATUS  CHART                      NAMESPACE
exemplo01  1     Sun Jun 17 20:40:50 2018   DEPLOYED exemplo-0.1.0           exemplo01
exemplo02  1     Sun Jun 17 20:41:25 2018   DEPLOYED exemplo-0.1.0           exemplo02

Ambos pacotes instalados com sucesso! Vamos conferir se realmente funcionou?

$ kubectl get pods -n exemplo01
NAME                             READY  STATUS    RESTARTS AGE
exemplo01-deploy-55b74c5c58-wtzpr   1/1 Running 0  21s

$ kubectl get svc -n exemplo01
NAME             TYPE        CLUSTER-IP   EXTERNAL-IP  PORT(S)       AGE
exemplo01-service   LoadBalancer 10.55.250.197   35.225.126.33 8080:31764/TCP  33s

$ curl -I http://35.225.126.33:8080
HTTP/1.1 200 OK
Server: nginx/1.15.0
Date: Sun, 17 Jun 2018 23:55:46 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 05 Jun 2018 12:00:18 GMT
Connection: keep-alive
ETag: "5b167b52-264"
Accept-Ranges: bytes

$ kubectl get pods -n exemplo02
NAME                             READY  STATUS    RESTARTS AGE
exemplo02-deploy-768c96479b-dll24   1/1 Running 0  1m

$ kubectl get svc -n exemplo02
NAME             TYPE        CLUSTER-IP EXTERNAL-IP   PORT(S)       AGE
exemplo02-service   LoadBalancer 10.55.255.7   35.193.109.201 8081:31513/TCP   1m

$ curl -I http://35.193.109.201:8081
HTTP/1.1 200 OK
Server: nginx/1.14.0
Date: Sun, 17 Jun 2018 23:56:29 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 17 Apr 2018 13:46:53 GMT
Connection: keep-alive
ETag: "5ad5facd-264"
Accept-Ranges: bytes
$

Conclusão

O Helm é uma ferramenta muito flexível na construção de nossos pacotes para Kubernetes. Há muito o que ser explorado como assinatura de charts, criação e sincronização dos pacotes para um repositório privado e até testes para validação! Espero que este simples artigo desperte o seu interesse e traga novas ideias para serem aplicadas com o Helm.

* https://kubernetes.io/

* https://helm.sh/

* https://medium.com/@abhaydiwan/kubernetes-introduction-and-twelve-key-features-cdfe8a1f2d21

 

Everson “IP_FIX” Tavares tem mais de 14 anos de experiência na área de TI, principalmente em ambientes Linux e Cloud Computing, também já tendo atuado com redes e segurança da informação. Atualmente focado em cloud computing e automação, participo diretamente de projetos de aplicações que necessitam de integração contínua, agilidade, disponibilidade, qualidade, estabilidade com escalabilidade e elasticidade utilizando a metodologia devops.

Posted by:Everson Tavares

Everson "IP_FIX" Tavares tem mais de 14 anos de experiência na área de TI, principalmente em ambientes Linux e Cloud Computing, também já tendo atuado com redes e segurança da informação. Atualmente focado em cloud computing e automação, participo diretamente de projetos de aplicações que necessitam de integração contínua, agilidade, disponibilidade, qualidade, estabilidade com escalabilidade e elasticidade utilizando a metodologia devops.

One thought on “Empacotando aplicações Kubernetes com Helm

  1. Bom artigo Everson, parabéns!
    Tenho uma dúvida, estou na luta de fazer o helm funcionar usando o rolling update sendo que minha imagem docker sempre usa a versão latest.
    Coloquei como imagepullpolicy: always, tentei deixar o force no upgrade mas, ele não recria os pods, só consegui forçando com a flag –recreate-pods,. mas ai ele recria todosao mesmo tempo, consegue me ajudar?

Deixe seu comentário