1 - Melhores Práticas de Configuração

Esse documento destaca e consolida as melhores práticas de configuração apresentadas em todo o guia de usuário, na documentação de introdução e nos exemplos.

Este é um documento vivo. Se você pensar em algo que não está nesta lista, mas pode ser útil para outras pessoas, não hesite em criar uma issue ou submeter um PR.

Dicas Gerais de Configuração

  • Ao definir configurações, especifique a versão mais recente estável da API.

  • Os arquivos de configuração devem ser armazenados em um sistema de controle antes de serem enviados ao cluster. Isso permite que você reverta rapidamente uma alteração de configuração, caso necessário. Isso também auxilia na recriação e restauração do cluster.

  • Escreva seus arquivos de configuração usando YAML ao invés de JSON. Embora esses formatos possam ser usados alternadamente em quase todos os cenários, YAML tende a ser mais amigável.

  • Agrupe objetos relacionados em um único arquivo sempre que fizer sentido. Geralmente, um arquivo é mais fácil de gerenciar do que vários. Veja o guestbook-all-in-one.yaml como exemplo dessa sintaxe.

  • Observe também que vários comandos kubectl podem ser chamados em um diretório. Por exemplo, você pode chamar kubectl apply em um diretório de arquivos de configuração.

  • Não especifique valores padrões desnecessariamente: configurações simples e mínimas diminuem a possibilidade de erros.

  • Coloque descrições de objetos nas anotações para permitir uma melhor análise.

"Naked" Pods comparados a ReplicaSets, Deployments, e Jobs

  • Se você puder evitar, não use "naked" Pods (ou seja, se você puder evitar, pods não vinculados a um ReplicaSet ou Deployment). Os "naked" pods não serão reconfigurados em caso de falha de um nó.

    Criar um Deployment, que cria um ReplicaSet para garantir que o número desejado de Pods esteja disponível e especifica uma estratégia para substituir os Pods (como RollingUpdate), é quase sempre preferível do que criar Pods diretamente, exceto para alguns cenários explícitos de restartPolicy:Never. Um Job também pode ser apropriado.

Services

  • Crie o Service antes de suas cargas de trabalho de backend correspondentes (Deployments ou ReplicaSets) e antes de quaisquer cargas de trabalho que precisem acessá-lo. Quando o Kubernetes inicia um contêiner, ele fornece variáveis de ambiente apontando para todos os Services que estavam em execução quando o contêiner foi iniciado. Por exemplo, se um Service chamado foo existe, todos os contêineres vão receber as seguintes variáveis em seu ambiente inicial:

    FOO_SERVICE_HOST=<o host em que o Service está executando>
    FOO_SERVICE_PORT=<a porta em que o Service está executando>
    

Isso implica em um requisito de pedido - qualquer Service que um Pod quer acessar precisa ser criado antes do Pod em si, ou então as variáveis de ambiente não serão populadas. O DNS não possui essa restrição.

  • Um cluster add-on opcional (embora fortemente recomendado) é um servidor DNS. O servidor DNS monitora a API do Kubernetes buscando novos Services e cria um conjunto de DNS para cada um. Se o DNS foi habilitado em todo o cluster, então todos os Pods devem ser capazes de fazer a resolução de Services automaticamente.

  • Não especifique um hostPort para um Pod a menos que isso seja absolutamente necessário. Quando você vincula um Pod a um hostPort, isso limita o número de lugares em que o Pod pode ser agendado, porque cada combinação de <hostIP, hostPort, protocol> deve ser única. Se você não especificar o hostIP e protocol explicitamente, o Kubernetes vai usar 0.0.0.0 como o hostIP padrão e TCP como protocol padrão.

    Se você precisa de acesso a porta apenas para fins de depuração, pode usar o apiserver proxy ou o kubectl port-forward.

    Se você precisa expor explicitamente a porta de um Pod no nó, considere usar um Service do tipo NodePort antes de recorrer a hostPort.

  • Evite usar hostNetwork pelos mesmos motivos do hostPort.

  • Use headless Services (que tem um ClusterIP ou None) para descoberta de serviço quando você não precisar de um balanceador de carga kube-proxy.

Usando Labels

  • Defina e use labels que identifiquem atributos semânticos da sua aplicação ou Deployment, como { app: myapp, tier: frontend, phase: test, deployment: v3 }. Você pode usar essas labels para selecionar os Pods apropriados para outros recursos; por exemplo, um Service que seleciona todos os Pods tier: frontend, ou todos os componentes de app: myapp. Veja o app guestbook para exemplos dessa abordagem.

Um Service pode ser feito para abranger vários Deployments, omitindo labels específicas de lançamento de seu seletor. Quando você precisar atualizar um serviço em execução sem downtime, use um Deployment.

Um estado desejado de um objeto é descrito por um Deployment, e se as alterações nesse spec forem aplicadas o controlador do Deployment altera o estado real para o estado desejado em uma taxa controlada.

  • Use as labels comuns do Kubernetes para casos de uso comuns. Essas labels padronizadas enriquecem os metadados de uma forma que permite que ferramentas, incluindo kubectl e a dashboard, funcionem de uma forma interoperável.

  • Você pode manipular labels para depuração. Como os controladores do Kubernetes (como ReplicaSet) e Services se relacionam com os Pods usando seletor de labels, remover as labels relevantes de um Pod impedirá que ele seja considerado por um controlador ou que seja atendido pelo tráfego de um Service. Se você remover as labels de um Pod existente, seu controlador criará um novo Pod para substituí-lo. Essa é uma maneira útil de depurar um Pod anteriormente "ativo" em um ambiente de "quarentena". Para remover ou alterar labels interativamente, use kubectl label.

Imagens de Contêiner

A imagePullPolicy e tag da imagem afetam quando o kubelet tenta puxar a imagem especificada.

  • imagePullPolicy: IfNotPresent: a imagem é puxada apenas se ainda não estiver presente localmente.

  • imagePullPolicy: Always: sempre que o kubelet inicia um contêiner, ele consulta o registry da imagem do contêiner para verificar o resumo de assinatura da imagem. Se o kubelet tiver uma imagem do contêiner com o mesmo resumo de assinatura armazenado em cache localmente, o kubelet usará a imagem em cache, caso contrário, o kubelet baixa(pulls) a imagem com o resumo de assinatura resolvido, e usa essa imagem para iniciar o contêiner.

  • imagePullPolicy é omitido se a tag da imagem é :latest ou se imagePullPolicy é omitido é automaticamente definido como Always. Observe que não será utilizado para ifNotPresentse o valor da tag mudar.

  • imagePullPolicy é omitido se uma tag da imagem existe mas não :latest: imagePullPolicy é automaticamente definido como ifNotPresent. Observe que isto não será atualizado para Always se a tag for removida ou alterada para :latest.

  • imagePullPolicy: Never: presume-se que a imagem exista localmente. Não é feita nenhuma tentativa de puxar a imagem.

Usando kubectl

  • Use kubectl apply -f <directory>. Isso procura por configurações do Kubernetes em todos os arquivos .yaml, .yml em <directory> e passa isso para apply.

  • Use labels selectors para operações get e delete em vez de nomes de objetos específicos. Consulte as seções sobre label selectors e usando Labels efetivamente.

  • Use kubectl create deployment e kubectl expose para criar rapidamente Deployments e Services de um único contêiner. Consulte Use um Service para acessar uma aplicação em um cluster para obter um exemplo.

2 - ConfigMaps

Um ConfigMap é um objeto da API usado para armazenar dados não-confidenciais em pares chave-valor. Pods podem consumir ConfigMaps como variáveis de ambiente, argumentos de linha de comando ou como arquivos de configuração em um volume.

Um ConfigMap ajuda a desacoplar configurações vinculadas ao ambiente das imagens de contêiner, de modo a tornar aplicações mais facilmente portáveis.

Motivação

Utilize um ConfigMap para manter a configuração separada do código da aplicação.

Por exemplo, imagine que você esteja desenvolvendo uma aplicação que pode ser executada no seu computador local (para desenvolvimento) e na nuvem (para manipular tráfego real). Você escreve código para ler a variável de ambiente chamada DATABASE_HOST. No seu ambiente local, você configura essa variável com o valor localhost. Na nuvem, você configura essa variável para referenciar um serviço do Kubernetes que expõe o componente do banco de dados ao seu cluster. Isto permite que você baixe uma imagem de contêiner que roda na nuvem e depure exatamente o mesmo código localmente se necessário.

Um ConfigMap não foi planejado para conter grandes quantidades de dados. Os dados armazenados em um ConfigMap não podem exceder 1 MiB. Se você precisa armazenar configurações que são maiores que este limite, considere montar um volume ou utilizar um serviço separado de banco de dados ou de arquivamento de dados.

Objeto ConfigMap

Um ConfigMap é um objeto da API que permite o armazenamento de configurações para consumo por outros objetos. Diferentemente de outros objetos do Kubernetes que contém um campo spec, o ConfigMap contém os campos data e binaryData. Estes campos aceitam pares chave-valor como valores. Ambos os campos data e binaryData são opcionais. O campo data foi pensado para conter sequências de bytes UTF-8, enquanto o campo binaryData foi planejado para conter dados binários em forma de strings codificadas em base64.

É obrigatório que o nome de um ConfigMap seja um subdomínio DNS válido.

Cada chave sob as seções data ou binaryData pode conter quaisquer caracteres alfanuméricos, -, _ e .. As chaves armazenadas na seção data não podem colidir com as chaves armazenadas na seção binaryData.

A partir da versão v1.19 do Kubernetes, é possível adicionar o campo immutable a uma definição de ConfigMap para criar um ConfigMap imutável.

ConfigMaps e Pods

Você pode escrever uma spec para um Pod que se refere a um ConfigMap e configurar o(s) contêiner(es) neste Pod baseados em dados do ConfigMap. O Pod e o ConfigMap devem estar no mesmo namespace.

Exemplo de um ConfigMap que contém algumas chaves com valores avulsos e outras chaves com valores semelhantes a fragmentos de arquivos de configuração:

apiVersion: v1
kind: ConfigMap
metadata:
  name: game-demo
data:
  # chaves com valores de propriedades; cada chave mapeia para um valor avulso
  player_initial_lives: "3"
  ui_properties_file_name: "user-interface.properties"

  # chaves semelhantes a fragmentos de arquivos
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5    
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true    

Existem quatro formas diferentes para consumo de um ConfigMap na configuração de um contêiner dentro de um Pod:

  1. Dentro de um comando de contêiner e seus argumentos.
  2. Variáveis de ambiente para um contêiner.
  3. Criando um arquivo em um volume somente leitura, para consumo pela aplicação.
  4. Escrevendo código para execução dentro do Pod que utilize a API do Kubernetes para ler um ConfigMap.

Os diferentes métodos de consumo oferecem diferentes formas de modelar os dados sendo consumidos. Para os três primeiros métodos, o kubelet utiliza os dados de um ConfigMap quando o(s) contêiner(es) do Pod são inicializados.

O quarto método envolve escrita de código para leitura do ConfigMap e dos seus dados. No entanto, como a API do Kubernetes está sendo utilizada diretamente, a aplicação pode solicitar atualizações sempre que o ConfigMap for alterado e reagir quando isso ocorre. Acessar a API do Kubernetes diretamente também permite ler ConfigMaps em outros namespaces.

Exemplo de um Pod que utiliza valores do ConfigMap game-demo para configurar um Pod:

apiVersion: v1
kind: Pod
metadata:
  name: configmap-demo-pod
spec:
  containers:
    - name: demo
      image: alpine
      command: ["sleep", "3600"]
      env:
        # Define as variáveis de ambiente
        - name: PLAYER_INITIAL_LIVES # Note que aqui a variável está definida em caixa alta,
                                     # diferente da chave no ConfigMap.
          valueFrom:
            configMapKeyRef:
              name: game-demo           # O ConfigMap de onde esse valor vem.
              key: player_initial_lives # A chave que deve ser buscada.
        - name: UI_PROPERTIES_FILE_NAME
          valueFrom:
            configMapKeyRef:
              name: game-demo
              key: ui_properties_file_name
      volumeMounts:
      - name: config
        mountPath: "/config"
        readOnly: true
  volumes:
    # Volumes são definidos no escopo do Pod, e os pontos de montagem são definidos
    # nos contêineres dentro dos pods.
    - name: config
      configMap:
        # Informe o nome do ConfigMap que deseja montar.
        name: game-demo
        # Uma lista de chaves do ConfigMap para serem criadas como arquivos.
        items:
        - key: "game.properties"
          path: "game.properties"
        - key: "user-interface.properties"
          path: "user-interface.properties"

ConfigMaps não diferenciam entre propriedades com valores simples ou valores complexos, que ocupam várias linhas. O importante é a forma que Pods e outros objetos consomem tais valores.

Neste exemplo, definir um volume e montar ele dentro do contêiner demo no caminho /config cria dois arquivos: /config/game.properties e /config/user-interface.properties, embora existam quatro chaves distintas no ConfigMap. Isso se deve ao fato de que a definição do Pod contém uma lista items na seção volumes. Se a lista items for omitida, cada chave do ConfigMap torna-se um arquivo cujo nome é a sua chave correspondente, e quatro arquivos serão criados.

Usando ConfigMaps

ConfigMaps podem ser montados como volumes de dados. ConfigMaps também podem ser utilizados por outras partes do sistema sem serem diretamente expostos ao Pod. Por exemplo, ConfigMaps podem conter dados que outras partes do sistema devem usar para configuração.

A forma mais comum de utilização de ConfigMaps é a configuração de contêineres executando em Pods no mesmo namespace. Você também pode utilizar um ConfigMap separadamente.

Por exemplo, existem complementos ou operadores que adaptam seus comportamentos de acordo com dados de um ConfigMap.

Utilizando ConfigMaps como arquivos em um Pod

Para consumir um ConfigMap em um volume em um Pod:

  1. Crie um ConfigMap ou utilize um ConfigMap existente. Múltiplos Pods podem referenciar o mesmo ConfigMap.
  2. Modifique sua definição de Pod para adicionar um volume em .spec.volumes[]. Escolha um nome qualquer para o seu volume, e referencie o seu objeto ConfigMap no campo .spec.volumes[].configMap.name.
  3. Adicione um campo .spec.containers[].volumeMounts[] a cada um dos contêineres que precisam do ConfigMap. Especifique .spec.containers[].volumeMounts[].readOnly = true e informe no campo .spec.containers[].volumeMounts[].mountPath um caminho de um diretório não utilizado onde você deseja que este ConfigMap apareça.
  4. Modifique sua imagem ou linha de comando de modo que o programa procure por arquivos no diretório especificado no passo anterior. Cada chave no campo data do ConfigMap será transformado em um nome de arquivo no diretório especificado por mountPath.

Exemplo de um Pod que monta um ConfigMap em um volume:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    configMap:
      name: myconfigmap

Cada ConfigMap que você deseja utilizar precisa ser referenciado em .spec.volumes.

Se houver múltiplos contêineres no Pod, cada contêiner deve ter seu próprio bloco volumeMounts, mas somente uma instância de .spec.volumes é necessária por ConfigMap.

ConfigMaps montados são atualizados automaticamente

Quando um ConfigMap que está sendo consumido em um volume é atualizado, as chaves projetadas são eventualmente atualizadas também. O Kubelet checa se o ConfigMap montado está atualizado em cada sincronização periódica. No entanto, o kubelet utiliza o cache local para buscar o valor atual do ConfigMap. O tipo de cache é configurável utilizando o campo ConfigMapAndSecretChangeDetectionStrategy na configuração do Kubelet (KubeletConfiguration). Um ConfigMap pode ter sua propagação baseada em um watch (comportamento padrão), que é o sistema de propagação de mudanças incrementais em objetos do Kubernetes; baseado em TTL (time to live, ou tempo de expiração); ou redirecionando todas as requisições diretamente para o servidor da API. Como resultado, o tempo decorrido total entre o momento em que o ConfigMap foi atualizado até o momento quando as novas chaves são projetadas nos Pods pode ser tão longo quanto o tempo de sincronização do kubelet somado ao tempo de propagação do cache, onde o tempo de propagação do cache depende do tipo de cache escolhido: o tempo de propagação pode ser igual ao tempo de propagação do watch, TTL do cache, ou zero, de acordo com cada um dos tipos de cache.

ConfigMaps que são consumidos como variáveis de ambiente não atualizam automaticamente e requerem uma reinicialização do pod.

ConfigMaps imutáveis

FEATURE STATE: Kubernetes v1.21 [stable]

A funcionalidade Secrets e ConfigMaps imutáveis do Kubernetes fornece uma opção para marcar Secrets e ConfigMaps individuais como imutáveis. Para clusters que utilizam ConfigMaps extensivamente (ao menos centenas de milhares de mapeamentos únicos de ConfigMaps para Pods), prevenir alterações dos seus dados traz as seguintes vantagens:

  • protege de atualizações acidentais ou indesejadas que podem causar disrupção na execução de aplicações
  • melhora o desempenho do cluster através do fechamento de watches de ConfigMaps marcados como imutáveis, diminuindo significativamente a carga no kube-apiserver

Essa funcionalidade é controlada pelo feature gate ImmutableEphemeralVolumes. É possível criar um ConfigMap imutável adicionando o campo immutable e marcando seu valor com true. Por exemplo:

apiVersion: v1
kind: ConfigMap
metadata:
  ...
data:
  ...
immutable: true

Após um ConfigMap ser marcado como imutável, não é possível reverter a alteração, nem alterar o conteúdo dos campos data ou binaryData. É possível apenas apagar e recriar o ConfigMap. Como Pods existentes que consomem o ConfigMap em questão mantém um ponto de montagem que continuará referenciando este objeto após a remoção, é recomendado recriar estes pods.

Próximos passos

3 - Organizando o acesso ao cluster usando arquivos kubeconfig

Utilize arquivos kubeconfig para organizar informações sobre clusters, usuários, namespaces e mecanismos de autenticação. A ferramenta de linha de comando kubectl faz uso dos arquivos kubeconfig para encontrar as informações necessárias para escolher e se comunicar com o serviço de API de um cluster.

Por padrão, o kubectl procura por um arquivo de nome config no diretório $HOME/.kube

Você pode especificar outros arquivos kubeconfig através da variável de ambiente KUBECONFIG ou adicionando a opção --kubeconfig.

Para maiores detalhes na criação e especificação de um kubeconfig, veja o passo a passo em Configurar Acesso para Múltiplos Clusters.

Suportando múltiplos clusters, usuários e mecanismos de autenticação

Imagine que você possua inúmeros clusters, e seus usuários e componentes se autenticam de várias formas. Por exemplo:

  • Um kubelet ativo pode se autenticar utilizando certificados
  • Um usuário pode se autenticar através de tokens
  • Administradores podem possuir conjuntos de certificados os quais provém acesso aos usuários de forma individual.

Através de arquivos kubeconfig, você pode organizar os seus clusters, usuários, e namespaces. Você também pode definir contextos para uma fácil troca entre clusters e namespaces.

Contexto

Um elemento de contexto em um kubeconfig é utilizado para agrupar parâmetros de acesso em um nome conveniente. Cada contexto possui três parâmetros: cluster, namespace, e usuário.

Por padrão, a ferramenta de linha de comando kubectl utiliza os parâmetros do contexto atual para se comunicar com o cluster.

Para escolher o contexto atual:

kubectl config use-context

A variável de ambiente KUBECONFIG

A variável de ambiente KUBECONFIG possui uma lista dos arquivos kubeconfig. Para Linux e Mac, esta lista é delimitada por vírgula. No Windows, a lista é delimitada por ponto e vírgula. A variável de ambiente KUBECONFIG não é um requisito obrigatório - caso ela não exista o kubectl utilizará o arquivo kubeconfig padrão localizado no caminho $HOME/.kube/config.

Se a variável de ambiente KUBECONFIG existir, o kubectl utilizará uma configuração que é o resultado da combinação dos arquivos listados na variável de ambiente KUBECONFIG.

Combinando arquivos kubeconfig

Para inspecionar a sua configuração atual, execute o seguinte comando:

kubectl config view

Como descrito anteriormente, a saída poderá ser resultado de um único arquivo kubeconfig, ou poderá ser o resultado da junção de vários arquivos kubeconfig.

Aqui estão as regras que o kubectl utiliza quando realiza a combinação de arquivos kubeconfig:

  1. Se o argumento --kubeconfig está definido, apenas o arquivo especificado será utilizado. Apenas uma instância desta flag é permitida.

    Caso contrário, se a variável de ambiente KUBECONFIG estiver definida, esta deverá ser utilizada como uma lista de arquivos a serem combinados, seguindo o fluxo a seguir:

    • Ignorar arquivos vazios.
    • Produzir erros para aquivos cujo conteúdo não for possível desserializar.
    • O primeiro arquivo que definir um valor ou mapear uma chave determinada, será o escolhido.
    • Nunca modificar um valor ou mapear uma chave. Exemplo: Preservar o contexto do primeiro arquivo que definir current-context. Exemplo: Se dois arquivos especificarem um red-user, use apenas os valores do primeiro red-user. Mesmo se um segundo arquivo possuir entradas não conflitantes sobre a mesma entrada red-user, estas deverão ser descartadas.

    Para um exemplo de definição da variável de ambiente KUBECONFIG veja Definido a variável de ambiente KUBECONFIG.

    Caso contrário, utilize o arquivo kubeconfig padrão encontrado no diretório $HOME/.kube/config, sem qualquer tipo de combinação.

  2. Determine o contexto a ser utilizado baseado no primeiro padrão encontrado, nesta ordem:

    1. Usar o conteúdo da flag --context caso ela existir.
    2. Usar o current-context a partir da combinação dos arquivos kubeconfig.

    Um contexto vazio é permitido neste momento.

  3. Determinar o cluster e o usuário. Neste ponto, poderá ou não existir um contexto. Determinar o cluster e o usuário no primeiro padrão encontrado de acordo com a ordem à seguir. Este procedimento deverá executado duas vezes: uma para definir o usuário a outra para definir o cluster.

    1. Utilizar a flag caso ela existir: --user ou --cluster.
    2. Se o contexto não estiver vazio, utilizar o cluster ou usuário deste contexto.

    O usuário e o cluster poderão estar vazios neste ponto.

  4. Determinar as informações do cluster atual a serem utilizadas. Neste ponto, poderá ou não existir informações de um cluster.

    Construir cada peça de informação do cluster baseado nas opções à seguir; a primeira ocorrência encontrada será a opção vencedora:

    1. Usar as flags de linha de comando caso existirem: --server, --certificate-authority, --insecure-skip-tls-verify.
    2. Se algum atributo do cluster existir a partir da combinação de kubeconfigs, estes deverão ser utilizados.
    3. Se não existir informação de localização do servidor falhar.
  5. Determinar a informação atual de usuário a ser utilizada. Construir a informação de usuário utilizando as mesmas regras utilizadas para o caso de informações de cluster, exceto para a regra de técnica de autenticação que deverá ser única por usuário:

    1. Usar as flags, caso existirem: --client-certificate, --client-key, --username, --password, --token.
    2. Usar os campos user resultado da combinação de arquivos kubeconfig.
    3. Se existirem duas técnicas conflitantes, falhar.
  6. Para qualquer informação que ainda estiver ausente, utilizar os valores padrão e potencialmente solicitar informações de autenticação a partir do prompt de comando.

Referências de arquivos

Arquivos e caminhos referenciados em um arquivo kubeconfig são relativos à localização do arquivo kubeconfig.

Referências de arquivos na linha de comando são relativas ao diretório de trabalho vigente.

No arquivo $HOME/.kube/config, caminhos relativos são armazenados de forma relativa, e caminhos absolutos são armazenados de forma absoluta.

Próximos passos