> For the complete documentation index, see [llms.txt](https://aurimrv.gitbook.io/pratica-devops-com-docker/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://aurimrv.gitbook.io/pratica-devops-com-docker/id-7-deploy-na-nuvem/7.3-conhecendo-os-recursos-e-conceitos-do-kubernetes.md).

# 7.3 Conhecendo os recursos e conceitos do Kubernetes

O Kubernetes permite a criação de um *cluster* de máquinas, cujo objetivo é a execução de aplicações com a utilização de contêineres. Além de criar o *cluster*, o Kubernetes permite a definição de recursos que juntos tornam possível a execução das aplicações. Nesse capítulo vamos conhecer as funções de 3 diferentes tipos de recursos: *deployment*, *service* e *ingress*.

Cada um deles oferece funcionalidades interessantes, e juntos vão nos ajudar a realizar o *deploy* de nossa aplicação em um *cluster* Kubernetes. De modo bem simples, o *deployment* permite configurar os contêineres necessários para executar a aplicação, o *service* expõe a aplicação como um serviço, e o *ingress* habilita o acesso externo ao serviço por meio dos protocolos HTTP e HTTPS.

Vejamos a seguir em mais detalhes as funções de cada um, e como são definidos.

## Deployment

O *deployment*, como o nome sugere, é o recurso que permite a definição de como será realizada a implantação de uma aplicação em um *cluster*. Ele pode ser entendido como uma declaração de um estado desejado para uma aplicação, sendo possível por exemplo informar o número de instâncias que serão executadas, e as imagens utilizadas para a criação dos contêineres.

Em um único *deployment*, podem ser definidos contêineres com diferentes imagens, que podem por exemplo serem executados em paralelo. Para cada instância informada, o Kubernetes cria um *pod*, que é de fato, a menor unidade implantável que você pode definir e gerenciar com o Kubernetes. Portanto, o *pod* é um agrupamento de 1 ou mais contêineres, os quais são implantados em um mesmo nó do *cluster*. Além de estarem em um mesmo nó, eles conseguem fazer uso de recursos de armazenamento compartilhados e de rede.

Os recursos no Kubernetes, incluindo os *deployments*, podem ser definidos utilizando arquivos YAML. Esse tipo de arquivo é composto de chaves e valores, tal como o exemplo de *deployment* a seguir:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}
```

Nesse exemplo, temos a chave `kind`, com o valor `Deployment`. Também podemos ter chaves aninhadas, como é o caso a seguir, em que `replicas` é uma chave aninhada na chave `spec`:

```yaml
spec:
  replicas: 2
```

Nesse caso, é importante destacar que para cada replica do *deployment*, será criado um *pod*, que conterá os containers defindos no *deployment*.

Observe que o *deployment* (e grande parte dos recursos do Kubernetes) possui as seguintes seções (chaves) principais: **`apiVersion`**, **`kind`**, **`metadata`**, **`spec`** e **`status`**. As duas primeiras servem para definir qual a API e o recurso utilizado do Kubernetes.

A chave **metadata** serve para informar metadados do recurso, como por exemplo seu nome (`name`), data de criação (`creationTimestamp`) e `labels`, as quais são utilizadas para classificar o recurso e permitir sua busca e referência.

Já na chave `spec`, que vem da palavra em ingês *specification* (especificação), podemos definir de fato as configurações do recurso, como por exemplo número de réplicas (`replicas`) e os contêineres que serão executados (`containers`).

Por fim, temos a chave `status`, que mostra o estado atual do *deployment*.

## Service

O service é um recurso do Kubernetes que permite expor de modo abstrato e como um serviço de rede uma aplicação executada em um conjunto de *pods*.

Desse modo, é possivel acessar a aplicação por um único nome DNS. O Kubernetes realiza o trabalho de atribuir um endereço de IP para cada *pod*, e realizar o balanceamento de carga entre os containers.

Para ficar mais claro, vamos analisar um caso no qual a aplicação A tenha um *deployment* definindo um único *container,* com 3 réplicas. Nessa situação serão criados 3 *pods,* cada um com um único *container,* o qual segue as especificações do *deployment*. Cada um desses *pods* (e consequentemente seu respectivo *container*) pode estar em uma máquina diferente. Cada container, recebe do Kubernetes um endereço único de IP. Sendo assim, sem a utilização de um *service* do Kubernetes, uma aplicação B que precise se comunicar com a aplicação A necessitaria tentar acessar um dos 3 endereços disponíveis.

No entanto, como os contêineres podem ser removidos e recriados dinamicanente pelo Kubernetes, os IPs dos contêineres rodando a aplicação A poderiam mudar, trazendo dificuldades para a aplicação B manter o contato com a aplicação A.

O *service* resolve justamente esse problema. Ele permite a definição de um nome para o serviço, por exemplo aplicacao-a. Quando algum contêiner é adicionado, o *service* incorpora o novo IP na lista de contêineres que fornecem o serviço, podendo enviar requisições para ele. De modo análogo, se um contêiner é removido, o service retira o IP do contêiner removido da sua lista, e não envia mais requisições para esse IP. Desse modo, para a aplicação B fica transparente quantos contêineres estão rodando a aplicação A, e seus respectivos IPs. Ela sempre se comunicará com o nome do serviço, ou seja aplicacao-a.

Além dessa facilidade, o service também realiza o balanceamento de carga entre os containers, ou seja, ele utiliza uma política de distribuição das requisições do serviço entre os containers, diminuindo a chance de que um determinado contêiner fique sobrecarregado, enquanto os demais fiquem ociosos.

A seguir temos um exemplo de service, definido no formato YAML:

```yaml
apiVersion: v1
kind: Service
metadata:
  name: aplicacao-a
  labels:
    app: nginx
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      name: http
  selector:
    app: nginx
```

Alguns pontos de atenção na definição dos services são o seu tipo (*spec.type*), a definição de portas (`spec.ports`) e os seletores utilizados (`spec.selector`).

Um service pode ser de 4 tipos diferentes: ClusterIP, NodePort, LoadBalancer e External Name.

Um serviço do tipo ClusterIP recebe um IP interno ao *cluster*, ou seja, só pode ser acessado de dentro do próprio *cluster*.

Já o tipo NodePort expõe o serviço em uma porta estática do nó do Kubernetes. Nesse caso, automaticamente também é criado um serviço do tipo ClusterIP, para o qual são roteadas as requisições que chegam a porta do nó. O serviço nesse caso pode ser acessado por meio do endereço `NODE_IP:NODE_PORT`.

O tipo LoadBalancer expõe o serviço externamente, por meio de um *load balancer* do provedor de nuvem. O Kubernetes automaticamente cria dois outros services, um do tipo NodePort e outro do tipo ClusterIP, para os quais são roteadas as requisições que chegam no *load balancer* externo.

Por último, temos o tipo ExternalName, o qual é responsável por mapear o serviço para um nome externo, por exemplo (meuservico.ufscar.br). Ele retorna um registro CNAME com esse nome e nenhum tipo de proxy é configurado nessa situação.

É​ interessante perceber que com exeção do caso do ExternalName, o tipo ClusterIP sempre é criado, e utilizado pelos demais. O tipo NodePort também é utilizado pelo tipo LoadBalancer, mostrando que o Kubernetes faz reúso de seus tipos mais básicos.

Já na definição de portas, temos a porta do próprio serviço (*port*), pela qual será acessado, e a porta do container para o qual será mapeada a requisição (*targetPort*), além do protocolo utilizado (protocol). É possível acessar mais de uma porta, e poderíamos, por exemplo, ter a porta HTTP (80) e HTTPS (443) do serviço expostas.

Por último temos os seletores. Eles definem as *labels* que os *containers* devem ter para que sejam reconhecidos pelo *service* como *containers* para os quais devem ser encaminhadas as requisições que chegam ao serviço. No exemplo vemos que o *service* seleciona os *containers* que apresentem a *label* `app: nginx`, como o container definido no exemplo anterior.

## Ingress

Para completar os recursos que serão utilizados, temos o *ingress*. Ele é utilizado para gerenciar o acesso externo aos serviços presentes no *cluster*, em geral por meio dos protocolos HTTP/HTTPS.

O ingress pode permitir a utilização de SSL, além de prover balanceamento de carga e a definição de URL para acesso aos serviços de maneira externa. A seguir um exemplo de *ingress*:

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: "meuservico.ufscar.br"
    http:
      paths:
      - path: /app-a
        pathType: Prefix
        backend:
          service:
            name: aplicacao-a
            port:
              number: 80

```

Observe que o ingress possui um conjunto de regras (`rules`) que devem ser atendidas para que ele realize o roteamento da requisição para um determinado *service*. No exemplo, temos uma única regra, que determina que requisições que cheguem tentando acessar o host `meuservico.ufscar.br`, no caminho `/app-a` sejam enviadas para o serviço `aplicacao-a` (definido anteriormente), na porta `80`.

## Arquivos estáticos

É importante mencionar que como vimos nos exemplos anteriores, os arquivos YAML utilizados para definir os recursos são estáticos, ou seja, os valores das configurações do recurso estão fixados nos próprios arquivos.

Para testarmos localmente, não há problemas nessa abordagem, no entanto, caso nossa aplicação possa ser implantada em diversos ambientes, com configurações diferentes, é interessante permitir que sejam passados valores diferentes para preencher os arquivos dos recursos, de acordo com as ​necessidades de cada ambiente.

Nesse sentido, podemos utilizar *charts*, que são pacotes de distribuição de aplicações no Kubernetes e que permitem essa flexibilidade. No próximo capítulo, vamos entender o que é um *chart*, sua estrutura básica e ver como ele facilita a implantação de aplicações em diferentes ambientes.
