Helm Package para além do básico

Criar e implantar manifestos de kubernetes pode até ser simples, mas continuamente manter-los garantindo que as alterações façam sentido entre si: isso pode ser um grande desafio! Outro desafio é a necessidade eventual de reverter um conjunto de alterações no k8s ao mesmo tempo, e não apenas o deployment. Enfim, para lidar com problemas como esse é que soluções de customização do kubernetes surgiram (Para conhecer mais sobre os diferentes manifestos do kubernetes conheça o projeto Awesome-Kubernetes.) Entenda o Helm Package para além do básico.

Mas, além disso, considerando a real necessidade de empacotar e customizar conjuntos de manifestos do kubernetes, soluções como Kubes, Helm ou Kustomize ganham cada vez mais espaço. Assim, nos especializaremos no Helm, que é o mais sofisticado da lista e além de ser bastante utilizado. Mas caso queira ver algumas utilizações do helm veja 10 Ferramentas mais utilizadas no Kubernetes.

Por fim, esse artigo te dará meios para entender o Helm Package em mais detalhes. Em síntese você entenderá melhor como trabalhar com os built-in objects, comandos de controle de fluxo, variáveis, modelos reaproveitáveis, files entre outros. Esse artigo não entrará em detalhes na linguagem de programação Go Lang mas é uma outra possibilidade interessante através do Go SDK.

Os diferentes sistemas de customização

Primeiramente o Kustomize é a solução mais simples de todas e que pode ser útil em alguns cenários em particular. Com ele você pode indicar que um conjunto de manifestos kubernetes seja instalado numa certa ordem. Mas o mais interessante é a utilização de patchs, facilitando o uso para configurações específica, como por exemplo, separação de stages de desenvolvimento, qa e produção.

Por outro lado o Kubes é um pouco mais sofisticado: com ele é possível criar alguns templates que poderão ser alterados dinamicamente. Logo isso pode facilitar consideravelmente o nível de customização dos seus deployments.

Mas, além deles há o Helm: solução mais completa e complexa de todas. Ao mesmo tempo ela oferece uma estrutura de repositórios particular, ela suporta variáveis, customização em Go Lang, upgrade de pacotes, sub-pacotes (chamados também de sub-charts), entre outros.

Uma introdução ao Helm

Primeiramente, como já comentado, o Helm é bastante robusto e oferece uma grande gama de possibilidades. Para instala-lo basta executar as linhas de comando abaixo, com base na documentação exposta através do link https://helm.sh/docs/intro/quickstart.

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

Logo após a instalação do Helm pode-se utilizar o comando helm repo. Assim é possível adicionar, remover, atualizar e listar todos os repositórios de pacotes que necessitar, bem como gerenciadores conhecidos como apt-get, yum, npm, etc.

Os pacotes do Helm também são chamados de Charts, mas quando instalados são chamados de Releases: por conta disso, para facilitar nossa comunicação, chamarei apenas de helm packages.

Além disso, quando uma instalação é feita no Helm, uma ordem é estabelecida automaticamente pela ferramenta para garantir que os manifestos sejam aplicados do melhor modo. Por exemplo, um namespace é aplicado antes de um deployment. Mas, por outro lado, a remoção segue a ordem inversa.

Namespace
NetworkPolicy
ResourceQuota
LimitRange
PodSecurityPolicy
PodDisruptionBudget
ServiceAccount
Secret
SecretList
ConfigMap
StorageClass
PersistentVolume
PersistentVolumeClaim
CustomResourceDefinition
ClusterRole
ClusterRoleList
ClusterRoleBinding
ClusterRoleBindingList
Role
RoleList
RoleBinding
RoleBindingList
Service
DaemonSet
Pod
ReplicationController
ReplicaSet
Deployment
HorizontalPodAutoscaler
StatefulSet
Job
CronJob
Ingress
APIService

Ao mesmo tempo, para criar um novo pacote Helm há o comando helm package que cria toda uma árvore de diretórios e arquivos (serão melhor explicados ao longo do artigo). Além disso o helm package cria um arquivo compactoado tar.gz que represeta o estado final do pacote. Por fim a instalação de um pacote helm é feita através de comandos como helm install e helm upgrade.

O Portal ArtifactHub

Bem como github é referência para códigos fonte, o Artifacthub.io é referência para encontrar Helm Packages, afinal ele é um site que facilita o acesso a diversos repositórios diferentes. Ele não efetivamente guarda os repositórios mas sim ele é um apontador para os repositórios reais. E Por fim, a maioria dos repositórios, na prática, fica no próprio Github e é exposto através do github pages: isso é Helm Package para além do básico.

Tela do artifacthub
Tela do Artifacthub

A estrutura de um Helm Chart

Logo após executar o comando helm create uma estrutura de arquivos e diretórios específica é materializada para manipulação. Desses arquivos e pastas eu destaco 3: o Charts.yaml, o values.yaml e o diretório templates.

O Charts.yaml é um documento de manifesto que explica como é esse helm package: o nome, a versão, etc. O values.yaml tem uma grande lista de valores de referência que definem o pacote com coisas como: nome da imagem, quantidade de réplicas, se o deploy é para aks ou eks, e mais o que imaginar. Por fim, da lista que selecionei há o diretório templates que possui todos os manifestos yaml do kubernetes com palavras reservadas que são substituídas pelo arquivo values.yaml ao final da instalação do helm package.

# Comando para criação da estrutura de pastas do Helm
helm create <folder-name>

## ------------------------------------------------------------------------
Chart-folder/
  Chart.yaml          		# Informações gerais do Chart
  LICENSE            		# Opcional: Informações de licenciamento
  README.md           		# OPTIONAL: Arquivo readme 
  values.yaml         		# Arquivo com todas as configurações
  values.schema.json  		# Opcional: Definição do esquema do values.yaml
  charts/             		# Diretório com subcharts
  crds/               		# Diretório com Custom Resource Definitions
  templates/          		# Diretório com todos os manifestos yaml 
  templates/NOTES.txt 	        # Opcional: Aparecerá após a instalação do pacote 

Os Built-in objects

Prosseguindo, o Helm possui também uma grande lista de objetos pré-definidos que podem ser utilizados na manipulação do seu helm package. Alguns exemplos muito comuns são Release.Name, Files.Get. A lista a seguir tem todos os built-in objects da versão atual do produto. Mas destaco o Values.* que é o ponto para relacionar os itens indicados no arquivo values.yaml.

# General
{{ .Release.Name }}         # Nome da release
{{ .Release.Namespace }}    # Nome do namespace
{{ .Release.IsUpgrade }}    # é upgrade?
{{ .Release.IsInstall }}    # é instalação?
{{ .Release.Revision }}     # Qual é o revisão
{{ .Release.Service }}      # Qual é o serviço?
{{ .Values.* }}             # Algum valor do values.yaml, exemplo: .Values.Name  
{{ .Chart.* }}              # Algum valro do charts.yaml, exemplo: .Chart.Name

# Files
{{ .Files.Get }}            # Obtém um arquivo
{{ .Files.GetBytes }}       # Obtém o tamanho em bytes de um arquivo
{{ .Files.Glob }}           # Lista de arquivos que batem com um determinado padrão
{{ .Files.Lines }}          # Quantidade de linhas de um arquivo
{{ .Files.AsSecrets }}      # Retorna o conteúdo de um arquivo em base64
{{ .Files.AsConfig }}       # Retorna o conteúdo de um arquivo num mapa YAML

# Capabilities
{{ .Capabilities.APIVersions }}         # Lista com as versões de api envolvidas
{{ .Capabilities.APIVersions.Has $ version }} 	# a versão está disponível?
{{ .Capabilities.KubeVersion.Version }}       	# Versão do kubernetes
{{ .Capabilities.KubeVersion.Major }}         	# O k8s major version
{{ .Capabilities.KubeVersion.Minor }}         	# O k8s minor version
{{ .Capabilities.HelmVersion }}               	# Versão do helm
{{ .Capabilities.HelmVersion.Version }}       	# Versão do helm (igual ao anterior)
{{ .Capabilities.HelmVersion.GitCommit }}     	# SHA1 do helm git (para gitOps)
{{ .Capabilities.HelmVersion.GitTreeState }}  	# Status do helm git tree
{{ .Capabilities.HelmVersion.GoVersion }}     	# Versão do go compilada para uso

# Template
# Nome completo do arquivo do template (e.g. mychart/templates/mytemplate.yaml)
{{ .Template.Name }}             		 

# Caminho completo para a pasta do template (e.g. mychart/templates).
{{ .Template.BasePath }}

Programando o chart

Por outro lado, os manifestos yaml feitos para interagir com o kubernetes podem ter áreas que serão substituídas por valores específicos – manifestos que ficam na pasta templates. Na maior parte do tempo às estruturas dos yamls são substituídas por itens do values.charts mas isso não é obrigatório.

### arquivo values.yaml
minhabebida: coffee

### arquivo /templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
"metadata":
  name: meu-configmap
"data":
  meu-valor: "Helm Package para além do básico"
  bebida: {{ .Values.minhabebida }}

Além disso há a possibilidade de utilizar funções construídas em go que modificam as entradas que são manipuladas de maneira declarativa. Assim há dois tipos básicos de comandos: template function e pipeline function. Isso é Helm Package para além do básico.

  • Os templates functions aparecem antes dos parametros, se assemelhante às linguagens de alto nível de programação;
  • já as pipeline functions se assemelham a shells e prompt de comando, utilizados através de um pipe. Veja a seguir exemplos:
### arquivo /templates/configmap.yaml (exemplo com Template Function)
apiVersion: v1
kind: ConfigMap
"metadata":
  name: meu-configmap
"data":
  meu-valor: "Hello World"
  bebida: {{ quote .Values.favorita.bebida }}
  comida: {{ quote .Values.favorita.comida}}

### arquivo /templates/configmap.yaml (exemplo com Pipeline Function)
apiVersion: v1
kind: ConfigMap
"metadata":
  name: meu-configmap
"data":
  meu-valor: "Helm Package para além do básico"
  bebida: {{ .Values.favorita.bebida | quote }}
  comida: {{ .Values.favorita.comida | quote }}

Trabalhando com control flow (if, else, with e range)

Para aprender Helm Package para além do básico é fundamental entender bem como controlar fluxo. O helm tem poucas opções de control flow, se comparado a linguagens de alto nível, mas atende bem às demandas mais comuns. Basicamente ele possui if-else para a comparação mais simples, o with para definição de um contexto de variável (mas que também faz uma comparação) e o range que funciona como um iterador (como for, foreach ou repeat, em outras linguagens).

apiVersion: v1
kind: ConfigMap
"metadata":
  name: {{ .Release.Name }}-configmap
"data":
  myvalue: "Helm Package para além do básico"
  drink: {{ .Values.favorite.drink | default "tea" | quote }}
  food: {{ .Values.favorite.food | upper | quote }}
  {{ if eq .Values.favorite.drink "coffee" }}mug: "true"{{ end }}
apiVersion: v1
kind: ConfigMap
"metadata":
  name: {{ .Release.Name }}-configmap
"data":
  myvalue: "Helm Package para além do básico"
  {{ with .Values.favorite }}
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  {{ end }}
apiVersion: v1
kind: ConfigMap
"metadata":
  name: {{ .Release.Name }}-configmap
"data":
  myvalue: "Helm Package para além do básico"
  {{ with .Values.favorite }}
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  {{ end }}
  toppings: |-
    {{ range .Values.pizzaToppings }}
    - {{ . | title | quote }}
    {{ end }}

Variáveis no Helm

O uso direto de variáveis no Helm não é algo muito comum mas é uma possibilidade. A estrutura de values muitas vezes já é suficiente para lidar com as necessidades. De todo modo, veja um exemplo:

apiVersion: v1
kind: ConfigMap
"metadata":
  name: {{ .Release.Name }}-configmap
"data":
  myvalue: "Helm Package para além do básico"
  {{ $relname := .Release.Name }}
  {{ with .Values.favorite }}
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  release: {{ $relname }}
  {{ end }}
toppings: |-
    {{ range $index, $topping := .Values.pizzaToppings }}
      {{ $index }}: {{ $topping }}
    {{ end }}
apiVersion: v1
kind: ConfigMap
"metadata":
  name: {{ .Release.Name }}-configmap
"data":
  myvalue: "Helm Package para além do básico"
  {{ range $key, $val := .Values.favorite }}
       {{ $key }}: {{ $val | quote }}
  {{ end }}

Named Templates

Com o intuito de aumentar o reuso, no Helm é possível indicar blocos de códigos para serem reaproveitados: são os chamados de named templates ou templates nomeados. Eles são criados através da palavra reservada define e utilizados com template ou include. O template considera que há apenas 1 replace a fazer, já o include substitui todas as incidências. Na prática é mais interessante utilizar sempre o include.

{{ define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{ end }}

apiVersion: v1
kind: ConfigMap
"metadata":
  name: {{ .Release.Name }}-configmap
  {{ template "mychart.labels" }}
"data":
  myvalue: "Hello World"
  {{ range $key, $val := .Values.favorite }}
      {{ $key }}: {{ $val | quote }}
  {{ end }}
{{ define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{ end }}

apiVersion: v1
kind: ConfigMap
"metadata":
  name: {{ .Release.Name }}-configmap
  labels:
{{ include "mychart.app" . | indent 4 }}
"data":
  myvalue: "Helm Package para além do básico"
  {{- range $key, $val := .Values.favorite }}
     {{ $key }}: {{ $val | quote }}
  {{- end }}
{{ include "mychart.app" . | indent 2 }}

Files

Outra opção interessante é fazer o seu Helm obter dados de um arquivo qualquer, como um .env ou qualquer outra coisa. O exemplo a seguir mostra como pode ser a utilização de 3 arquivos config*.toml através dessa função.

### Considere os 3 arquivos a seguir
# config1.toml
message = Hello from config 1
# config2.toml
message = Hello from config 2
# config3.toml
message = Hello from config 3

### /templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
"metadata":
  name: {{ .Release.Name }}-configmap
"data":
  {{ $files := .Files }}
  {{ range tuple "config1.toml" "config2.toml" "config3.toml" }}
  {{ . }}: |-
        {{ $files.Get . }}
  {{ end }}


### Versão final do arquivo
apiVersion: v1
kind: ConfigMap
"metadata":
  name: quieting-giraf-configmap
"data":
  config1.toml: |-
        message = Hello from config 1

  config2.toml: |-
        message = This is config 2

  config3.toml: |-
        message = Goodbye from config 3

Sub-charts

Sobretudo no Helm, é possível estruturar um chart para depender de outro chart, num mesmo helm package: A Pasta /Charts carrega todos os sub-charts envolvidos. Entretanto há alguns pontos a considerar:

  1. Um subchart é considerado “autônomo”, o que significa que um subchart nunca pode depender explicitamente de seu chart pai.
  2. Um subchart não pode acessar os valores de seu pai.
  3. Um chart pai pode substituir valores para subcharts.
  4. O Helm possui um conceito de valores globais que podem ser acessados por todos os charts.

Outras estruturas de um helm package

Há algumas outras estruturas que possuem valor e devem ser consideradas para realmente entender Helm Package para além do básico.

Hooks

É possível indicar que após ou antes de um manifesto ser instalado no k8s algo aconteça. Os hooks servem para lidar com eventos como esse.

HookDescrição
pre-installExecuta após os templates serem renderizados, mas antes de quaisquer recursos serem criados no Kubernetes
post-installExecuta depois que todos os recursos são carregados no Kubernetes
pre-deleteExecuta em uma solicitação de exclusão antes que qualquer recurso seja excluído do Kubernetes
post-deleteExecuta em uma solicitação de exclusão após a exclusão de todos os recursos da versão
pre-upgradeExecuta em uma solicitação de atualização após a renderização dos modelos, mas antes da atualização de quaisquer recursos
post-upgradeExecuta em uma solicitação de atualização após a atualização de todos os recursos
pre-rollbackExecuta em uma solicitação de reversão após os modelos serem renderizados, mas antes de quaisquer recursos serem revertidos
post-rollbackExecuta em uma solicitação de reversão após todos os recursos terem sido modificados
testExecuta quando o subcomando de teste do Helm é invocado

Test

Além de tudo o que vimos, é possível validar o helm package através de um teste, incluído em um pod específico. Para fazer essa açpão é necessário adicionar a annotation: ‘helm.sh/hook: test’. O comando ‘helm test demo’ cria o pod e executa os testes

Libraries

Há dois tipos essenciais de charts: Template ou Libraries. As Libraries apenas expõe métodos e não podem ser executados diretamente.

Plugins

Essa é uma técnica para estender a CLI do helm para suportar novos comandos. Plugins no github https://github.com/search?q=topic%3Ahelm-plugin&type=Repositories. Plugins no artifacthub https://artifacthub.io/packages/search?kind=6&sort=relevance&page=1

Dependencies

Um chart pode depender de outro
No arquivo Chart.yaml é necessário indicar a dependência entre eles.

Integrity

Essa é a estrutura para assinatura de um package que é muito útil para GitOps com o Argo-cd, por exemplo.

Outros comandos de referência

helm install –verify
helm package --sign --key 'John Smith' --keyring path/to/keyring.secret mychart
helm verify mychart-0.1.0.tgz

Debugando um Template

Quando ocorre algum problema na geração do helm package há alguns comandos de referência para entender o problema e planejar um contorno. São os comandos:

  • helm lint – Verifica se o pacote está bem escrito
  • helm template –debug – Testa a renderização local do pacote helm
  • helm install –dry-run –debug – Exibe o arquivo de manifesto final
  • helm get manifest – Obtém o manifesto de uma release instalada

Helm Package para além do básico: Dicas e truques

Para definitivamente aprender Helm Package para além do básico agrupei várias dicas num pequeno pacote para facilitar sua vida e ter em seu conjunto de ferramentas

  • Conheça os Template Functions
  • Programe em Go SDK e estenda o Helm
  • Use aspas em strings e não em inteiros
  • Utilize parcials com Includes (e não templates)
  • Utilize a função required
  • Utilize a função Default
  • Utilize o tpl
  • Crie secrets para as imagens baixadas
  • Estruturar seus upgrades
    • Atualizar configmaps não afeta automaticamente os workloads
    • Coloque uma annotation com o checksum nos deployments, exigindo sua atualização
  • Pode ser necessário não instalar algo
    • “helm.sh/resource-policy”: keep
  • Cuidado com valores randômicos
    • Isso pode inviabilizar ou deturpar upgrades
  • Fique atento às variáveis de ambiente do Helm
  • Crie um bom arquivo Notes.txt
  • Fique atento aos detalhes do .helmignore

Thiago Anselme
Thiago Anselme - Gerente de TI - Arquiteto de Soluções

Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.

Deixe um comentário