# 7.4 Entendendo o Chart

## Helm Charts

O *deploy* da aplicação no Kubernetes pode ser realizado por meio do [Helm](https://helm.sh/), o qual, como dito anteriormente, é um gerenciador de pacotes para o Kubernetes. Nesse caso podemos entender pacote como uma ou mais aplicações. Ou seja, com o Helm podemos instalar e gerenciar aplicações em um *cluster* Kubernetes, que é exatamente o que ocorre no Auto DevOps.

### Conceitos importantes

Para entender como funciona o Helm, precisamos entender alguns conceitos importantes referentes a como o Helm trabalha. Basicamente são 3 conceitos principais:

* *Chart*
* *Config*
* *Release*

#### Chart

O *chart* é conjunto de informações necessárias, reunidas numa coleção de arquivos, para definir uma estrutura lógica para realizar o *deploy* uma aplicação no Kubernetes.

Vamos simplificar nossa aplicação e imaginar que ela necessite apenas de um servidor Tomcat e de um banco MySQL para ser executada. O *chart* nesse caso descreverá os elementos (recursos) da estrutura necessária para executar essa aplicação no Kubernetes, por exemplo, *deployments*, *services*, *load balancers* e *volumes*.

#### Config

A configuração (*config*) consiste na definição de valores que serão utilizados para a realização do *deploy*, adaptando-o conforme necessidade. Por exemplo, podemos ter uma configuração específica para o ambiente de produção e outra para um ambiente de teste. Ou seja, pode ser entendida como uma personalização ou adequação do *chart* para um propósito específico.

#### Release

A *release* consiste na implantação efetiva da aplicação, usando como base o *chart* e as configurações pré-determinadas para o ambiente em questão. Ou seja, ela consiste em tornar real o plano que é dado pelo *chart* e pela *config*.

Entendidos esses conceitos, vamos analisar em detalhes a estrutura de um *chart*.

### Estrutura de um chart

Os arquivos do *chart* são alocados uma estrutura de diretórios bem definida:

```
chart/
  Chart.yaml          # Arquivo YAML com informações sobre o chart
  LICENSE             # Opcional: Arquivo de texto plano com a licença para o chart
  README.md           # Opcional: Um README para explicar o funcionamento do chart
  values.yaml         # Os valores-padrão de configuraçãodo chart
  values.schema.json  # Opcional: Arquivo json para impor uma estrutura arbitrária dos valores no arquivo values.yaml
  charts/             # Opcional: Diretório onde devem estar os charts dos quais esse chart depende.
  crds/               # Opcional: Custom Resource Definitions (https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/)
  templates/          # Diretório de templates para definição dos recursos do Kubernetes. Nesses templates podem
                      # existir variáveis que serão substituídas com os valores definidos no values.yaml,
  templates/NOTES.txt # Opcional: Arquivo contendo explicações de como usar o chart
```

Veremos essa estrutura em detalhes a seguir.

#### Diretório chart

O diretório `chart` pode ter qualquer nome, de preferência o nome da aplicação. No caso do SAGUI, o nome desse diretório poderia ser `sagui`. Esse é o diretório raiz do *chart*, e é nele (e nos seus subdiretórios) que estão contidos todos os arquivos do *chart*, inclusive as configurações.

#### Arquivo LICENSE

Nesse arquivo de texto, pode estar contida a licença pela qual o *chart* é disponibilizado. Ele permite aos usuários do *chart* entenderem os modos pelos quais podem fazer uso do *chart*, se podem redistribuí-lo, etc.

#### Arquivo README.md

Esse arquivo é utizado para explicar o funcionamento do *chart*. Nele podem/devem estar os valores padrão das variáveis utilizadas no *chart*, bem como o significado delas.

É a principal documentação do *chart*, pode ser gerado automaticamente usando ferramentas como o [Helm Docs](https://github.com/norwoodj/helm-docs).

#### Arquivo `Chart.yaml`

O arquivo `Chart.yaml` é obrigatório em todo *chart*. Nesse arquivo são definidas diversas informações sobre o *chart*, por meio de campos pré-definidos, sendo alguns obrigatórios e outros opcionais. Os campos obrigatórios são:

```
apiVersion: Versão da API utilizada no chart
name: O nome do chart
version: A versão do chart, seguindo a versão 2 da SemVer (https://semver.org/lang/pt-BR/)
```

Caso tenha interesse, confira a [lista dos demais campos](https://semver.org/lang/pt-BR/).

Como exemplo, temos o `Chart.yaml` usado no projeto:

```yaml
apiVersion: v2
name: loja-virtual
description: Chart para implantação da loja virtual no Kubernetes

type: application
version: 0.1.0
appVersion: "1.16.0"
```

No campo `apiVersion`, deve ser informado o valor `v2` para projetos que exigem o Helm 3, e `v1` para projetos que suportam versões prévias dele.

Um fato interessante, é que existe o campo `version`, para indicar a versão do *chart* e o campo `appVersion`, que define a versão da aplicação que será instalad pelo *chart*, sendo assim, esses campos podem ter valores diferentes, como no caso da aplicação que estamos utilizando.

#### Diretório charts

Esse diretório é bem interessante, pois pode ser utilizado para armazenar os *charts* dos quais depende o *chart* atual. Considerando nossa aplicação, que depende do MySQL e do Nagios, nesse diretório, poderiam estar os *charts* de cada uma dessas aplicações, o que simplifica o entendimento do *chart* atual.

#### Diretório templates

O diretório templates é sem dúvida o diretório mais importante de um *chart*. É nele que são definidos os *templates* dos recursos Kubernetes utilizados para realizar a implantação da aplicação. Esses templates quando combinados com valores devem gerar arquivos Kubernetes válidos.

Vamos ver um exemplo de template bem simples:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
{{- include "app.labels" . | nindent 4 }}
  name: {{ .Values.app.name }
spec:
  replicas: 1
  selector:
    matchLabels:
{{- include "app.labels" . | nindent 4 }}
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
{{- include "app.labels" . | nindent 4 }}
    spec:
      containers:
      - image: {{ .Values.app.image.repository }}
        name: {{ .Values.app.container.name }}
        resources: {}

```

Todos os trechos de código que estão entre `{{ }}` são trechos que são passíveis de alteração por um determinado valor. Por exemplo, na linha `name: {{ .Values.app.name }}` o valor do campo `name` será dado pelo valor definido para a chave `app.name`. As chaves e seus respectivos valores geralmente estão definidas no arquivo `values.yaml`.

Já a construção `{{- include "app.labels" . | nindent 4 }}` terá seu valor definido pela execução da [função include](https://helm.sh/docs/chart_template_guide/named_templates/#the-include-function), que permite a utilização de uma construção chamada [named template](https://helm.sh/docs/chart_template_guide/named_templates/).

Para entender como podemos determinar esses valores, vamos olhar o arquivo `values.yaml`

#### Arquivo values.yaml

Como todo arquivo YAML, o arquivo `values.yaml` espera que sejam definidas chaves e seus repectivos valores. O modo mais simples é assim:

```yaml
chave: valor
```

Mas também é possível ter chaves aninhadas:

```yaml
chave1: 
    subchave1_1: valor_subchave1
    subchave1_2:
      subchave1_2_1: valor_subchave1_2_1
```

Ou de modo mais verboso:

```yaml
chave1.subchave1_1: valor_subchave1
chave1.subchave1_2.subchave1_2_1: valor_subchave1_2_1
```

Para maiores informações, consulte a [documentação oficial](https://helm.sh/docs/chart_template_guide/values_files/).

Esse arquivo faz parte da estrutura do *chart*, mas determina as configurações (*config*) de uma *release*. É importante salientar, que também é possível utilizar arquivos extra de configuração, além de ser possível passar configurações por linha de comando, como veremos posteriormente.

**Exemplos**

Vamos olhar um trecho do arquivo `values.yaml` da aplicação:

```yaml
# Default values for chart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

app:
  image:
    repository: nginx
    pullPolicy: IfNotPresent
    # Overrides the image tag whose default is the chart appVersion.
    tag: ""
imagePullPolicy: IfNotPresent
imagePullSecrets: 
  - name: gitlab-registry
nameOverride: ""
fullnameOverride: ""
```

Observe que podemos usar comentários, pré-fixando as linhas com o caracter `#`. Além disso, podemos utilizar esse arquivo para personalizar nosso *deployment*, configurando por a imagem utilizada:

```yaml
app:
  image:
    repository: nginx
```

Como vimos, esse valor pode ser utilizado no arquivo de deployment do seguinte modo:

```
    spec:
      containers:
      - image: {{ .Values.app.image.repository }}
```

### Valores de variáveis diferentes por ambiente

Como mencionado anteriormente, o arquivo `values.yaml` é utilizado para definir valores padrão que são utilizados nos *templates* do Helm. No entanto, há casos em que é necessários ter configurações diferentes de acordo com o ambiente no qual será feita a implantação da aplicação. Felizmente, o Helm permite a utilização de outro arquivo com definição de valores para sobreescrever valores padrão definidos no `values.yaml`.

Podemos por exemplo, definir um arquivo com um nome arbitrário, por exemplo `prod-values.yaml`. Ele usa a mesma sintaxe do arquivo `values.yaml`, sobre o qual terá precedência.

Imaginemos então a seguinte situação. Criamos o arquivo `values.yaml` do seguinte modo:

```yaml
chave1: valor1
chave2: valor2
```

E o arquivo `prod-values.yaml` assim:

```yaml
chave1: valor1a
chave3: valor3
```

Esses dois arquivos são então combinados (mesclados), e devido a precedência sobre o arquivo `values.yaml`, a variável `chave1` assume o valor `valor1a` definido no `rod-values.yaml`, já a variável `chave3`, definida somente no arquivo `rod-values.yaml` recebe o valor `valor3`, e a variável `chave2`, por não estar definida nesse arquivo, herda o valor do `values.yaml` (`valor2`). Sendo assim, os valores passados para o Helm seriam:

```yaml
chave1: valor1a
chave2: valor2
chave3: valor3
```

Ou seja, é feita uma mescla entre os dois arquivos, e o resultado final leva em consideração a precedência do arquivo `prod-values.yaml`. Podemos ter um arquivo de values para cada ambiente, desse modo, é possível utilizar valores diferentes para cada ambiente, tornando bem flexível a utilização do Helm.

É importante salientar que os nomes (e o diretório) desses arquivos são arbitrários e escolhidos pelo usuário, não sendo necessário seguir uma convenção nesses casos.

### Testando *templates*

Ao criar ou modificar um *template*, ou então modificar os valores utilizados nele, é sempre uma boa prática verificar se o arquivo Kubernetes gerado a partir do template é realmente válido. Para fazer isso, você deve ter o Helm instalado localmente.

### Validação dos arquivos Kubernetes

Uma vez instalado o Helm localmente, e assumindo a convenção do GitLab para os arquivos `prod-values.yaml` e `values.yaml` podemos gerar os arquivos do Kubernetes com o seguinte comando:

```
helm template chart/ -f prod-values.yaml -f chart/values.yaml --debug -v 5 --output-dir ./build/templates/
```

Nesse comando informamos que desejamos utilizar um *template* (`helm template`) para gerar arquivos Kubernetes no diretório `.build/templates/` com a opção `--output-dir`, sendo que nosso chart está localizado em `chart`, e nossos arquivos de valores serão `prod-values.yaml` e `chart/values.yaml`, dado o parâmetro `-f`. Além disso queremos executar em modo de *debug* (`--debug`), com verbosidade nível 5 (`-v 5`).

Desse modo, se houver algum problema no *template*, como por exemplo sintaxe incorreta, ou falta de definição de algum valor, saberemos rapidamente, sem ter que tentar implantar a aplicação no Kubernetes.

Os contêineres que são exectados no Kubernetes utilizam imagens construídas previamente e armazenadas em *registries*, que são repositórios de imagens. Na próxima seção, vamos conhecer o Jib, uma ferramenta que auxilia a criação de imagens de aplicações Java sem a necessidade de conhecer e ter instalado o Docker, além de outras vantagens.
