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://medium.com/@abhaydiwan/kubernetes-introduction-and-twelve-key-features-cdfe8a1f2d21