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 - Secrets

Um Secret é um objeto que contém uma pequena quantidade de informação sensível, como senhas, tokens ou chaves. Este tipo de informação poderia, em outras circunstâncias, ser colocada diretamente em uma configuração de Pod ou em uma imagem de contêiner. O uso de Secrets evita que você tenha de incluir dados confidenciais no seu código.

Secrets podem ser criados de forma independente dos Pods que os consomem. Isto reduz o risco de que o Secret e seus dados sejam expostos durante o processo de criação, visualização e edição ou atualização de Pods. O Kubernetes e as aplicações que rodam no seu cluster podem também tomar outras precauções com Secrets, como por exemplo evitar a escrita de dados confidenciais em local de armazenamento persistente (não-volátil).

Secrets são semelhantes a ConfigMaps, mas foram especificamente projetados para conter dados confidenciais.

Visão Geral de Secrets

Para utilizar um Secret, um Pod precisa referenciar o Secret. Um Secret pode ser utilizado em um Pod de três maneiras diferentes:

A camada de gerenciamento do Kubernetes também utiliza Secrets. Por exemplo, os Secrets de tokens de autoinicialização são um mecanismo que auxilia a automação do registro de nós.

O nome de um Secret deve ser um subdomínio DNS válido. Você pode especificar o campo data e/ou o campo stringData na criação de um arquivo de configuração de um Secret. Ambos os campos data e stringData são opcionais. Os valores das chaves no campo data devem ser strings codificadas no formato base64. Se a conversão para base64 não for desejável, você pode optar por informar os dados no campo stringData, que aceita strings arbitrárias como valores.

As chaves dos campos data e stringData devem consistir de caracteres alfanuméricos, -, _, ou .. Todos os pares chave-valor no campo stringData são internamente combinados com os dados do campo data. Se uma chave aparece em ambos os campos, o valor informado no campo stringData toma a precedência.

Tipos de Secrets

Ao criar um Secret, você pode especificar o seu tipo utilizando o campo type do objeto Secret, ou algumas opções de linha de comando equivalentes no comando kubectl, quando disponíveis. O campo type de um Secret é utilizado para facilitar a manipulação programática de diferentes tipos de dados confidenciais.

O Kubernetes oferece vários tipos embutidos de Secret para casos de uso comuns. Estes tipos variam em termos de validações efetuadas e limitações que o Kubernetes impõe neles.

Tipo embutido Caso de uso
Opaque dados arbitrários definidos pelo usuário
kubernetes.io/service-account-token token de service account (conta de serviço)
kubernetes.io/dockercfg arquivo ~/.dockercfg serializado
kubernetes.io/dockerconfigjson arquivo ~/.docker/config.json serializado
kubernetes.io/basic-auth credenciais para autenticação básica (basic auth)
kubernetes.io/ssh-auth credenciais para autenticação SSH
kubernetes.io/tls dados para um cliente ou servidor TLS
bootstrap.kubernetes.io/token dados de token de autoinicialização

Você pode definir e utilizar seu próprio tipo de Secret definindo o valor do campo type como uma string não-nula em um objeto Secret. Uma string em branco é tratada como o tipo Opaque. O Kubernetes não restringe nomes de tipos. No entanto, quando tipos embutidos são utilizados, você precisa atender a todos os requisitos daquele tipo.

Secrets tipo Opaque

Opaque é o tipo predefinido de Secret quando o campo type não é informado em um arquivo de configuração. Quando um Secret é criado usando o comando kubectl, você deve usar o subcomando generic para indicar que um Secret é do tipo Opaque. Por exemplo, o comando a seguir cria um Secret vazio do tipo Opaque:

kubectl create secret generic empty-secret
kubectl get secret empty-secret

O resultado será semelhante ao abaixo:

NAME           TYPE     DATA   AGE
empty-secret   Opaque   0      2m6s

A coluna DATA demonstra a quantidade de dados armazenados no Secret. Neste caso, 0 significa que este objeto Secret está vazio.

Secrets de token de service account (conta de serviço)

Secrets do tipo kubernetes.io/service-account-token são utilizados para armazenar um token que identifica uma service account (conta de serviço). Ao utilizar este tipo de Secret, você deve garantir que a anotação kubernetes.io/service-account.name contém um nome de uma service account existente. Um controlador do Kubernetes preenche outros campos, como por exemplo a anotação kubernetes.io/service-account.uid e a chave token no campo data com o conteúdo do token.

O exemplo de configuração abaixo declara um Secret de token de service account:

apiVersion: v1
kind: Secret
metadata:
  name: secret-sa-sample
  annotations:
    kubernetes.io/service-account-name: "sa-name"
type: kubernetes.io/service-account-token
data:
  # Você pode incluir pares chave-valor adicionais, da mesma forma que faria com
  # Secrets do tipo Opaque
  extra: YmFyCg==

Ao criar um Pod, o Kubernetes automaticamente cria um Secret de service account e automaticamente atualiza o seu Pod para utilizar este Secret. O Secret de token de service account contém credenciais para acessar a API.

A criação automática e o uso de credenciais de API podem ser desativados se desejado. Porém, se tudo que você necessita é poder acessar o servidor da API de forma segura, este é o processo recomendado.

Veja a documentação de ServiceAccount para mais informações sobre o funcionamento de service accounts. Você pode verificar também os campos automountServiceAccountToken e serviceAccountName do Pod para mais informações sobre como referenciar service accounts em Pods.

Secrets de configuração do Docker

Você pode utilizar um dos tipos abaixo para criar um Secret que armazena credenciais para accesso a um registro de contêineres compatível com Docker para busca de imagens:

  • kubernetes.io/dockercfg
  • kubernetes.io/dockerconfigjson

O tipo kubernetes.io/dockercfg é reservado para armazenamento de um arquivo ~/.dockercfg serializado. Este arquivo é o formato legado para configuração do utilitário de linha de comando do Docker. Ao utilizar este tipo de Secret, é preciso garantir que o campo data contém uma chave .dockercfg cujo valor é o conteúdo do arquivo ~/.dockercfg codificado no formato base64.

O tipo kubernetes.io/dockerconfigjson foi projetado para armazenamento de um conteúdo JSON serializado que obedece às mesmas regras de formato que o arquivo ~/.docker/config.json. Este arquivo é um formato mais moderno para o conteúdo do arquivo ~/.dockercfg. Ao utilizar este tipo de Secret, o conteúdo do campo data deve conter uma chave .dockerconfigjson em que o conteúdo do arquivo ~/.docker/config.json é fornecido codificado no formato base64.

Um exemplo de um Secret do tipo kubernetes.io/dockercfg:

apiVersion: v1
kind: Secret
metadata:
  name: secret-dockercfg
type: kubernetes.io/dockercfg
data:
  .dockercfg: |
        "<base64 encoded ~/.dockercfg file>"

Ao criar estes tipos de Secret utilizando um manifesto (arquivo YAML), o servidor da API verifica se a chave esperada existe no campo data e se o valor fornecido pode ser interpretado como um conteúdo JSON válido. O servidor da API não verifica se o conteúdo informado é realmente um arquivo de configuração do Docker.

Quando você não tem um arquivo de configuração do Docker, ou quer utilizar o comando kubectl para criar um Secret de registro de contêineres compatível com o Docker, você pode executar:

kubectl create secret docker-registry secret-tiger-docker \
  --docker-username=tiger \
  --docker-password=pass113 \
  --docker-email=tiger@acme.com \
  --docker-server=my-registry.example:5000

Esse comando cria um secret do tipo kubernetes.io/dockerconfigjson, cujo conteúdo é semelhante ao exemplo abaixo:

{
    "apiVersion": "v1",
    "data": {
        ".dockerconfigjson": "eyJhdXRocyI6eyJteS1yZWdpc3RyeTo1MDAwIjp7InVzZXJuYW1lIjoidGlnZXIiLCJwYXNzd29yZCI6InBhc3MxMTMiLCJlbWFpbCI6InRpZ2VyQGFjbWUuY29tIiwiYXV0aCI6ImRHbG5aWEk2Y0dGemN6RXhNdz09In19fQ=="
    },
    "kind": "Secret",
    "metadata": {
        "creationTimestamp": "2021-07-01T07:30:59Z",
        "name": "secret-tiger-docker",
        "namespace": "default",
        "resourceVersion": "566718",
        "uid": "e15c1d7b-9071-4100-8681-f3a7a2ce89ca"
    },
    "type": "kubernetes.io/dockerconfigjson"
}

Se você extrair o conteúdo da chave .dockerconfigjson, presente no campo data, e decodificá-lo do formato base64, você irá obter o objeto JSON abaixo, que é uma configuração válida do Docker criada automaticamente:

{
  "auths":{
    "my-registry:5000":{
      "username":"tiger",
      "password":"pass113",
      "email":"tiger@acme.com",
      "auth":"dGlnZXI6cGFzczExMw=="
    }
  }
}

Secret de autenticação básica

O tipo kubernetes.io/basic-auth é fornecido para armazenar credenciais necessárias para autenticação básica. Ao utilizar este tipo de Secret, o campo data do Secret deve conter as duas chaves abaixo:

  • username: o usuário utilizado para autenticação;
  • password: a senha ou token para autenticação.

Ambos os valores para estas duas chaves são textos codificados em formato base64. Você pode fornecer os valores como texto simples utilizando o campo stringData na criação do Secret.

O arquivo YAML abaixo é um exemplo de configuração para um Secret de autenticação básica:

apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: admin
  password: t0p-Secret

O tipo de autenticação básica é fornecido unicamente por conveniência. Você pode criar um Secret do tipo Opaque utilizado para autenticação básica. No entanto, utilizar o tipo embutido de Secret auxilia a unificação dos formatos das suas credenciais. O tipo embutido também fornece verificação de presença das chaves requeridas pelo servidor da API.

Secret de autenticação SSH

O tipo embutido kubernetes.io/ssh-auth é fornecido para armazenamento de dados utilizados em autenticação SSH. Ao utilizar este tipo de Secret, você deve especificar um par de chave-valor ssh-privatekey no campo data ou no campo stringData com a credencial SSH a ser utilizada.

O YAML abaixo é um exemplo de configuração para um Secret de autenticação SSH:

apiVersion: v1
kind: Secret
metadata:
  name: secret-ssh-auth
type: kubernetes.io/ssh-auth
data:
  # os dados estão abreviados neste exemplo
  ssh-privatekey: |
          MIIEpQIBAAKCAQEAulqb/Y ...

O Secret de autenticação SSH é fornecido apenas para a conveniência do usuário. Você pode criar um Secret do tipo Opaque para credentials utilizadas para autenticação SSH. No entanto, a utilização do tipo embutido auxilia na unificação dos formatos das suas credenciais e o servidor da API fornece verificação dos campos requeridos em uma configuração de Secret.

Secrets TLS

O Kubernetes fornece o tipo embutido de Secret kubernetes.io/tls para armazenamento de um certificado e sua chave associada que são tipicamente utilizados para TLS. Estes dados são utilizados primariamente para a finalização TLS do recurso Ingress, mas podem ser utilizados com outros recursos ou diretamente por uma carga de trabalho. Ao utilizar este tipo de Secret, as chaves tls.key e tls.crt devem ser informadas no campo data (ou stringData) da configuração do Secret, embora o servidor da API não valide o conteúdo de cada uma destas chaves.

O YAML a seguir tem um exemplo de configuração para um Secret TLS:

apiVersion: v1
kind: Secret
metadata:
  name: secret-tls
type: kubernetes.io/tls
data:
  # os dados estão abreviados neste exemplo
  tls.crt: |
        MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
  tls.key: |
        MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...

O tipo TLS é fornecido para a conveniência do usuário. Você pode criar um Secret do tipo Opaque para credenciais utilizadas para o servidor e/ou cliente TLS. No entanto, a utilização do tipo embutido auxilia a manter a consistência dos formatos de Secret no seu projeto; o servidor da API valida se os campos requeridos estão presentes na configuração do Secret.

Ao criar um Secret TLS utilizando a ferramenta de linha de comando kubectl, você pode utilizar o subcomando tls conforme demonstrado no exemplo abaixo:

kubectl create secret tls my-tls-secret \
  --cert=path/to/cert/file  \
  --key=path/to/key/file

O par de chaves pública/privada deve ser criado separadamente. O certificado de chave pública a ser utilizado no argumento --cert deve ser codificado em formato .PEM (formato DER codificado em texto base64) e deve corresponder à chave privada fornecida no argumento --key. A chave privada deve estar no formato de chave privada PEM não-encriptado. Em ambos os casos, as linhas inicial e final do formato PEM (por exemplo, --------BEGIN CERTIFICATE----- e -------END CERTIFICATE---- para um certificado) não são incluídas.

Secret de token de autoinicialização

Um Secret de token de autoinicialização pode ser criado especificando o tipo de um Secret explicitamente com o valor bootstrap.kubernetes.io/token. Este tipo de Secret é projetado para tokens utilizados durante o processo de inicialização de nós. Este tipo de Secret armazena tokens utilizados para assinar ConfigMaps conhecidos.

Um Secret de token de autoinicialização é normalmente criado no namespace kube-system e nomeado na forma bootstrap-token-<id-do-token>, onde <id-do-token> é um texto com 6 caracteres contendo a identificação do token.

No formato de manifesto do Kubernetes, um Secret de token de autoinicialização se assemelha ao exemplo abaixo:

apiVersion: v1
kind: Secret
metadata:
  name: bootstrap-token-5emitj
  namespace: kube-system
type: bootstrap.kubernetes.io/token
data:
  auth-extra-groups: c3lzdGVtOmJvb3RzdHJhcHBlcnM6a3ViZWFkbTpkZWZhdWx0LW5vZGUtdG9rZW4=
  expiration: MjAyMC0wOS0xM1QwNDozOToxMFo=
  token-id: NWVtaXRq
  token-secret: a3E0Z2lodnN6emduMXAwcg==
  usage-bootstrap-authentication: dHJ1ZQ==
  usage-bootstrap-signing: dHJ1ZQ==

Um Secret do tipo token de autoinicialização possui as seguintes chaves no campo data:

  • token-id: Uma string com 6 caracteres aleatórios como identificador do token. Requerido.
  • token-secret: Uma string de 16 caracteres aleatórios como o conteúdo do token. Requerido.
  • description: Uma string contendo uma descrição do propósito para o qual este token é utilizado. Opcional.
  • expiration: Um horário absoluto UTC no formato RFC3339 especificando quando o token deve expirar. Opcional.
  • usage-bootstrap-<usage>: Um conjunto de flags booleanas indicando outros usos para este token de autoinicialização.
  • auth-extra-groups: Uma lista separada por vírgulas de nomes de grupos que serão autenticados adicionalmente, além do grupo system:bootstrappers.

O YAML acima pode parecer confuso, já que os valores estão todos codificados em formato base64. Você pode criar o mesmo Secret utilizando este YAML:

apiVersion: v1
kind: Secret
metadata:
  # Observe como o Secret é nomeado
  name: bootstrap-token-5emitj
  # Um Secret de token de inicialização geralmente fica armazenado no namespace
  # kube-system
  namespace: kube-system
type: bootstrap.kubernetes.io/token
stringData:
  auth-extra-groups: "system:bootstrappers:kubeadm:default-node-token"
  expiration: "2020-09-13T04:39:10Z"
  # Esta identificação de token é utilizada no nome
  token-id: "5emitj"
  token-secret: "kq4gihvszzgn1p0r"
  # Este token pode ser utilizado para autenticação.
  usage-bootstrap-authentication: "true"
  # e pode ser utilizado para assinaturas
  usage-bootstrap-signing: "true"

Criando um Secret

Há várias formas diferentes de criar um Secret:

Editando um Secret

Um Secret existente no cluster pode ser editado com o seguinte comando:

kubectl edit secrets mysecret

Este comando abrirá o editor padrão configurado e permitirá a modificação dos valores codificados em base64 no campo data:

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: { ... }
  creationTimestamp: 2016-01-22T18:41:56Z
  name: mysecret
  namespace: default
  resourceVersion: "164619"
  uid: cfee02d6-c137-11e5-8d73-42010af00002
type: Opaque

Utilizando Secrets

Secrets podem ser montados como volumes de dados ou expostos como variáveis de ambiente para serem utilizados num container de um Pod. Secrets também podem ser utilizados por outras partes do sistema, sem serem diretamente expostos ao Pod. Por exemplo, Secrets podem conter credenciais que outras partes do sistema devem utilizar para interagir com sistemas externos no lugar do usuário.

Utilizando Secrets como arquivos em um Pod

Para consumir um Secret em um volume em um Pod:

  1. Crie um Secret ou utilize um previamente existente. Múltiplos Pods podem referenciar o mesmo secret.
  2. Modifique sua definição de Pod para adicionar um volume na lista .spec.volumes[]. Escolha um nome qualquer para o seu volume e adicione um campo .spec.volumes[].secret.secretName com o mesmo valor do seu objeto Secret.
  3. Adicione um ponto de montagem de volume à lista .spec.containers[].volumeMounts[] de cada contêiner que requer o Secret. Especifique .spec.containers[].volumeMounts[].readOnly = true e especifique o valor do campo .spec.containers[].volumeMounts[].mountPath com o nome de um diretório não utilizado onde você deseja que os Secrets apareçam.
  4. Modifique sua imagem ou linha de comando de modo que o programa procure por arquivos naquele diretório. Cada chave no campo data se torna um nome de arquivo no diretório especificado em mountPath.

Este é um exemplo de Pod que monta um Secret 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
    secret:
      secretName: mysecret

Cada Secret que você deseja utilizar deve ser referenciado na lista .spec.volumes.

Se existirem múltiplos contêineres em um Pod, cada um dos contêineres necessitará seu próprio bloco volumeMounts, mas somente um volume na lista .spec.volumes é necessário por Secret.

Você pode armazenar vários arquivos em um Secret ou utilizar vários Secrets distintos, o que for mais conveniente.

Projeção de chaves de Secrets a caminhos específicos

Você pode também controlar os caminhos dentro do volume onde as chaves do Secret são projetadas. Você pode utilizar o campo .spec.volumes[].secret.items para mudar o caminho de destino de cada chave:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username

Neste caso:

  • O valor da chave username é armazenado no arquivo /etc/foo/my-group/my-username ao invés de /etc/foo/username.
  • O valor da chave password não é projetado no sistema de arquivos.

Se .spec.volumes[].secret.items for utilizado, somente chaves especificadas na lista items são projetadas. Para consumir todas as chaves do Secret, deve haver um item para cada chave no campo items. Todas as chaves listadas precisam existir no Secret correspondente. Caso contrário, o volume não é criado.

Permissões de arquivos de Secret

Você pode trocar os bits de permissão de uma chave avulsa de Secret. Se nenhuma permissão for especificada, 0644 é utilizado por padrão. Você pode também especificar uma permissão padrão para o volume inteiro de Secret e sobrescrever esta permissão por chave, se necessário.

Por exemplo, você pode especificar uma permissão padrão da seguinte maneira:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      defaultMode: 0400

Dessa forma, o Secret será montado em /etc/foo e todos os arquivos criados no volume terão a permissão 0400.

Note que a especificação JSON não suporta notação octal. Neste caso, utilize o valor 256 para permissões equivalentes a 0400. Se você utilizar YAML ao invés de JSON para o Pod, você pode utilizar notação octal para especificar permissões de uma forma mais natural.

Perceba que se você acessar o Pod com kubectl exec, você precisará seguir o vínculo simbólico para encontrar a permissão esperada. Por exemplo,

Verifique as permissões do arquivo de Secret no pod.

kubectl exec mypod -it sh

cd /etc/foo
ls -l

O resultado é semelhante ao abaixo:

total 0
lrwxrwxrwx 1 root root 15 May 18 00:18 password -> ..data/password
lrwxrwxrwx 1 root root 15 May 18 00:18 username -> ..data/username

Siga o vínculo simbólico para encontrar a permissão correta do arquivo.

cd /etc/foo/..data
ls -l

O resultado é semelhante ao abaixo:

total 8
-r-------- 1 root root 12 May 18 00:18 password
-r-------- 1 root root  5 May 18 00:18 username

Você pode também utilizar mapeamento, como no exemplo anterior, e especificar permissões diferentes para arquivos diferentes conforme abaixo:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username
        mode: 0777

Neste caso, o arquivo resultante em /etc/foo/my-group/my-username terá as permissões 0777. Se você utilizar JSON, devido às limitações do formato, você precisará informar as permissões em base decimal, ou o valor 511 neste exemplo.

Note que os valores de permissões podem ser exibidos em formato decimal se você ler essa informação posteriormente.

Consumindo valores de Secrets em volumes

Dentro do contêiner que monta um volume de Secret, as chaves deste Secret aparecem como arquivos e os valores dos Secrets são decodificados do formato base64 e armazenados dentro destes arquivos. Ao executar comandos dentro do contêiner do exemplo anterior, obteremos os seguintes resultados:

ls /etc/foo

O resultado é semelhante a:

username
password
cat /etc/foo/username

O resultado é semelhante a:

admin
cat /etc/foo/password

O resultado é semelhante a:

1f2d1e2e67df

A aplicação rodando dentro do contêiner é responsável pela leitura dos Secrets dentro dos arquivos.

Secrets montados são atualizados automaticamente

Quando um Secret que está sendo consumido a partir de um volume é atualizado, as chaves projetadas são atualizadas após algum tempo também. O kubelet verifica se o Secret montado está atualizado a cada sincronização periódica. No entanto, o kubelet utiliza seu cache local para buscar o valor corrente de um Secret. O tipo do cache é configurável utilizando o campo ConfigMapAndSecretChangeDetectionStrategy na estrutura KubeletConfiguration. Um Secret pode ser propagado através de 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 Secret foi atualizado até o momento em que 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.

Utilizando Secrets como variáveis de ambiente

Para utilizar um secret em uma variável de ambiente em um Pod:

  1. Crie um Secret ou utilize um já existente. Múltiplos Pods podem referenciar o mesmo Secret.
  2. Modifique a definição de cada contêiner do Pod em que desejar consumir o Secret, adicionando uma variável de ambiente para cada uma das chaves que deseja consumir. A variável de ambiente que consumir o valor da chave em questão deverá popular o nome do Secret e a sua chave correspondente no campo env[].valueFrom.secretKeyRef.
  3. Modifique sua imagem de contêiner ou linha de comando de forma que o programa busque os valores nas variáveis de ambiente especificadas.

Este é um exemplo de um Pod que utiliza Secrets em variáveis de ambiente:

apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
    - name: SECRET_USERNAME
      valueFrom:
        secretKeyRef:
          name: mysecret
          key: username
    - name: SECRET_PASSWORD
      valueFrom:
        secretKeyRef:
          name: mysecret
          key: password
  restartPolicy: Never

Consumindo valores de Secret em variáveis de ambiente

Dentro de um contêiner que consome um Secret em variáveis de ambiente, a chave do Secret aparece como uma variável de ambiente comum, contendo os dados do Secret decodificados do formato base64. Ao executar comandos no contêiner do exemplo anterior, obteremos os resultados abaixo:

echo $SECRET_USERNAME

O resultado é semelhante a:

admin
echo $SECRET_PASSWORD

O resultado é semelhante a:

1f2d1e2e67df

Variáveis de ambiente não são atualizadas após uma atualização no Secret

Se um contêiner já consome um Secret em uma variável de ambiente, uma atualização dos valores do Secret não será refletida no contêiner a menos que o contêiner seja reiniciado. Existem ferramentas de terceiros que oferecem reinicializações automáticas quando Secrets são atualizados.

Secrets imutáveis

FEATURE STATE: Kubernetes v1.21 [stable]

A funcionalidade do Kubernetes Secrets e ConfigMaps imutáveis fornece uma opção para marcar Secrets e ConfigMaps individuais como imutáveis. Em clusters que fazem uso extensivo de Secrets (pelo menos dezenas de milhares de montagens únicas de Secrets em Pods), prevenir alterações aos dados dos Secrets traz as seguintes vantagens:

  • protege você de alterações acidentais ou indesejadas que poderiam provocar disrupções na execução de aplicações;
  • melhora a performance do seu cluster através da redução significativa de carga no kube-apiserver, devido ao fechamento de watches de Secrets marcados como imutáveis.

Esta funcionalidade é controlada pelo feature gate ImmutableEphemeralVolumes, que está habilitado por padrão desde a versão v1.19. Você pode criar um Secret imutável adicionando o campo immutable com o valor true. Por exemplo:

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

Usando imagePullSecrets

O campo imagePullSecrets é uma lista de referências para Secrets no mesmo namespace. Você pode utilizar a lista imagePullSecrets para enviar Secrets que contém uma senha para acesso a um registro de contêineres do Docker (ou outros registros de contêineres) ao kubelet. O kubelet utiliza essa informação para baixar uma imagem privada no lugar do seu Pod. Veja a API PodSpec para maiores detalhes sobre o campo imagePullSecrets.

Especificando imagePullSecrets manualmente

Você pode ler sobre como especificar imagePullSecrets em um Pod na documentação de imagens de contêiner.

Configurando imagePullSecrets para serem vinculados automaticamente

Você pode criar manualmente imagePullSecrets e referenciá-los em uma ServiceAccount. Quaisquer Pods criados com esta ServiceAccount, especificada explicitamente ou por padrão, têm o campo imagePullSecrets populado com os mesmos valores existentes na service account. Veja adicionando imagePullSecrets a uma service account para uma explicação detalhada do processo.

Detalhes

Restrições

Referências a Secrets em volumes são validadas para garantir que o objeto especificado realmente existe e é um objeto do tipo Secret. Portanto, um Secret precisa ser criado antes de quaisquer Pods que dependam deste.

Objetos Secret residem em um namespace. Secrets podem ser referenciados somente por Pods no mesmo namespace.

Secrets individuais são limitados ao tamanho de 1MiB. Esta limitação ter por objetivo desencorajar a criação de Secrets muito grandes que poderiam exaurir a memória do servidor da API e do kubelet. No entanto, a criação de muitos Secrets pequenos também pode exaurir a memória. Limites mais completos de uso de memória em função de Secrets é uma funcionalidade prevista para o futuro.

O kubelet suporta apenas o uso de Secrets em Pods onde os Secrets são obtidos do servidor da API. Isso inclui quaisquer Pods criados usando o comando kubectl, ou indiretamente através de um controlador de replicação, mas não inclui Pods criados como resultado das flags --manifest-url e --config do kubelet, ou a sua API REST (estas são formas incomuns de criar um Pod). A spec de um Pod estático não pode se referir a um Secret ou a qualquer outro objeto da API.

Secrets precisam ser criados antes de serem consumidos em Pods como variáveis de ambiente, exceto quando são marcados como opcionais. Referências a Secrets que não existem provocam falhas na inicialização do Pod.

Referências (campo secretKeyRef) a chaves que não existem em um Secret nomeado provocam falhas na inicialização do Pod.

Secrets utilizados para popular variáveis de ambiente através do campo envFrom que contém chaves inválidas para utilização como nome de uma variável de ambiente terão tais chaves ignoradas. O Pod inicializará normalmente. Porém, um evento será gerado com a razão InvalidVariableNames e a mensagem gerada conterá a lista de chaves inválidas que foram ignoradas. O exemplo abaixo demonstra um Pod que se refere ao Secret default/mysecret, contendo duas chaves inválidas: 1badkey e 2alsobad.

kubectl get events

O resultado é semelhante a:

LASTSEEN   FIRSTSEEN   COUNT     NAME            KIND      SUBOBJECT                         TYPE      REASON
0s         0s          1         dapi-test-pod   Pod                                         Warning   InvalidEnvironmentVariableNames   kubelet, 127.0.0.1      Keys [1badkey, 2alsobad] from the EnvFrom secret default/mysecret were skipped since they are considered invalid environment variable names.

Interações do ciclo de vida entre Secrets e Pods

Quando um Pod é criado através de chamadas à API do Kubernetes, não há validação da existência de um Secret referenciado. Uma vez que um Pod seja agendado, o kubelet tentará buscar o valor do Secret. Se o Secret não puder ser encontrado porque não existe ou porque houve uma falha de comunicação temporária entre o kubelet e o servidor da API, o kubelet fará novas tentativas periodicamente. O kubelet irá gerar um evento sobre o Pod, explicando a razão pela qual o Pod ainda não foi inicializado. Uma vez que o Secret tenha sido encontrado, o kubelet irá criar e montar um volume contendo este Secret. Nenhum dos contêineres do Pod irá iniciar até que todos os volumes estejam montados.

Casos de uso

Caso de uso: Como variáveis de ambiente em um contêiner

Crie um manifesto de Secret

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  USER_NAME: YWRtaW4=
  PASSWORD: MWYyZDFlMmU2N2Rm

Crie o Secret no seu cluster:

kubectl apply -f mysecret.yaml

Utilize envFrom para definir todos os dados do Secret como variáveis de ambiente do contêiner. Cada chave do Secret se torna o nome de uma variável de ambiente no Pod.

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      envFrom:
      - secretRef:
          name: mysecret
  restartPolicy: Never

Caso de uso: Pod com chaves SSH

Crie um Secret contendo chaves SSH:

kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub

O resultado é semelhante a:

secret "ssh-key-secret" created

Você também pode criar um manifesto kustomization.yaml com um campo secretGenerator contendo chaves SSH.

Agora você pode criar um Pod que referencia o Secret com a chave SSH e consome-o em um volume:

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
  labels:
    name: secret-test
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: ssh-key-secret
  containers:
  - name: ssh-test-container
    image: mySshImage
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"

Ao rodar o comando do contêiner, as partes da chave estarão disponíveis em:

/etc/secret-volume/ssh-publickey
/etc/secret-volume/ssh-privatekey

O contêiner então pode utilizar os dados do secret para estabelecer uma conexão SSH.

Caso de uso: Pods com credenciais de ambientes de produção ou testes

Este exemplo ilustra um Pod que consome um Secret contendo credenciais de um ambiente de produção e outro Pod que consome um Secret contendo credenciais de um ambiente de testes.

Você pode criar um manifesto kustomization.yaml com um secretGenerator ou rodar kubectl create secret.

kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11

O resultado é semelhante a:

secret "prod-db-secret" created

Você pode também criar um Secret com credenciais para o ambiente de testes.

kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtests

O resultado é semelhante a:

secret "test-db-secret" created

Agora, crie os Pods:

cat <<EOF > pod.yaml
apiVersion: v1
kind: List
items:
- kind: Pod
  apiVersion: v1
  metadata:
    name: prod-db-client-pod
    labels:
      name: prod-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: prod-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
- kind: Pod
  apiVersion: v1
  metadata:
    name: test-db-client-pod
    labels:
      name: test-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: test-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
EOF

Adicione os Pods a um manifesto kustomization.yaml:

cat <<EOF >> kustomization.yaml
resources:
- pod.yaml
EOF

Crie todos estes objetos no servidor da API rodando o comando:

kubectl apply -k .

Ambos os contêineres terão os seguintes arquivos presentes nos seus sistemas de arquivos, com valores para cada um dos ambientes dos contêineres:

/etc/secret-volume/username
/etc/secret-volume/password

Observe como as specs para cada um dos Pods diverge somente em um campo. Isso facilita a criação de Pods com capacidades diferentes a partir de um template mais genérico.

Você pode simplificar ainda mais a definição básica do Pod através da utilização de duas service accounts diferentes:

  1. prod-user com o Secret prod-db-secret
  2. test-user com o Secret test-db-secret

A especificação do Pod é reduzida para:

apiVersion: v1
kind: Pod
metadata:
  name: prod-db-client-pod
  labels:
    name: prod-db-client
spec:
  serviceAccount: prod-db-client
  containers:
  - name: db-client-container
    image: myClientImage

Caso de uso: dotfiles em um volume de Secret

Você pode fazer com que seus dados fiquem "ocultos" definindo uma chave que se inicia com um ponto (.). Este tipo de chave representa um dotfile, ou arquivo "oculto". Por exemplo, quando o Secret abaixo é montado em um volume, secret-volume:

apiVersion: v1
kind: Secret
metadata:
  name: dotfile-secret
data:
  .secret-file: dmFsdWUtMg0KDQo=
---
apiVersion: v1
kind: Pod
metadata:
  name: secret-dotfiles-pod
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: dotfile-secret
  containers:
  - name: dotfile-test-container
    image: k8s.gcr.io/busybox
    command:
    - ls
    - "-l"
    - "/etc/secret-volume"
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"

Este volume irá conter um único arquivo, chamado .secret-file, e o contêiner dotfile-test-container terá este arquivo presente no caminho /etc/secret-volume/.secret-file.

Caso de uso: Secret visível somente em um dos contêineres de um pod

Suponha que um programa necessita manipular requisições HTTP, executar regras de negócio complexas e então assinar mensagens com HMAC. Devido à natureza complexa da aplicação, pode haver um exploit despercebido que lê arquivos remotos no servidor e que poderia expor a chave privada para um invasor.

Esta aplicação poderia ser dividida em dois processos, separados em dois contêineres distintos: um contêiner de front-end, que manipula as interações com o usuário e a lógica de negócio, mas não consegue ver a chave privada; e um contêiner assinador, que vê a chave privada e responde a requisições simples de assinatura do front-end (por exemplo, através de rede local).

Com essa abordagem particionada, um invasor agora precisa forçar o servidor de aplicação a rodar comandos arbitrários, o que é mais difícil de ser feito do que apenas ler um arquivo presente no disco.

Melhores práticas

Clientes que utilizam a API de Secrets

Ao instalar aplicações que interajam com a API de Secrets, você deve limitar o acesso utilizando políticas de autorização como RBAC.

Secrets frequentemente contém valores com um espectro de importância, muitos dos quais podem causar escalações dentro do Kubernetes (por exemplo, tokens de service account) e de sistemas externos. Mesmo que um aplicativo individual possa avaliar o poder do Secret com o qual espera interagir, outras aplicações dentro do mesmo namespace podem tornar estas suposições inválidas.

Por estas razões, as requisições watch (observar) e list (listar) de Secrets dentro de um namespace são permissões extremamente poderosas e devem ser evitadas, pois a listagem de Secrets permite a clientes inspecionar os valores de todos os Secrets presentes naquele namespace. A habilidade de listar e observar todos os Secrets em um cluster deve ser reservada somente para os componentes mais privilegiados, que fazem parte do nível de aplicações de sistema.

Aplicações que necessitam acessar a API de Secret devem realizar uma requisição get nos Secrets que precisam. Isto permite que administradores restrinjam o acesso a todos os Secrets, enquanto utilizam uma lista de autorização a instâncias individuais que a aplicação precise.

Para melhor desempenho em uma requisição get repetitiva, clientes podem criar objetos que referenciam o Secret e então utilizar a requisição watch neste novo objeto, requisitando o Secret novamente quando a referência mudar. Além disso, uma API de "observação em lotes" para permitir a clientes observar recursos individuais também foi proposta e provavelmente estará disponível em versões futuras do Kubernetes.

Propriedades de segurança

Proteções

Como Secrets podem ser criados de forma independente de Pods que os utilizam, há menos risco de um Secret ser exposto durante o fluxo de trabalho de criação, visualização, e edição de Pods. O sistema pode também tomar precauções adicionais com Secrets, como por exemplo evitar que sejam escritos em disco quando possível.

Um Secret só é enviado para um nó se um Pod naquele nó requerê-lo. O kubelet armazena o Secret num sistema de arquivos tmpfs, de forma a evitar que o Secret seja escrito em armazenamento persistente. Uma vez que o Pod que depende do Secret é removido, o kubelet apaga sua cópia local do Secret também.

Secrets de vários Pods diferentes podem existir no mesmo nó. No entanto, somente os Secrets que um Pod requerer estão potencialmente visíveis em seus contêineres. Portanto, um Pod não tem acesso aos Secrets de outro Pod.

Um Pod pode conter vários contêineres. Porém, cada contêiner em um Pod precisa requerer o volume de Secret nos seus volumeMounts para que este fique visível dentro do contêiner. Esta característica pode ser utilizada para construir partições de segurança ao nível do Pod.

Na maioria das distribuições do Kubernetes, a comunicação entre usuários e o servidor da API e entre servidor da API e os kubelets é protegida por SSL/TLS. Secrets são protegidos quando transmitidos através destes canais.

FEATURE STATE: Kubernetes v1.13 [beta]

Você pode habilitar encriptação em disco em dados de Secret para evitar que estes sejam armazenados em texto plano no etcd.

Riscos

  • No servidor da API, os dados de Secret são armazenados no etcd; portanto:
    • Administradores devem habilitar encriptação em disco para dados do cluster (requer Kubernetes v1.13 ou posterior).
    • Administradores devem limitar o acesso ao etcd somente para usuários administradores.
    • Administradores podem desejar apagar definitivamente ou destruir discos previamente utilizados pelo etcd que não estiverem mais em uso.
    • Ao executar o etcd em um cluster, administradores devem garantir o uso de SSL/TLS para conexões ponto-a-ponto do etcd.
  • Se você configurar um Secret utilizando um arquivo de manifesto (JSON ou YAML) que contém os dados do Secret codificados como base64, compartilhar este arquivo ou salvá-lo num sistema de controle de versão de código-fonte compromete este Secret. Codificação base64 não é um método de encriptação e deve ser considerada idêntica a texto plano.
  • Aplicações ainda precisam proteger o valor do Secret após lê-lo de um volume, como por exemplo não escrever seu valor em logs ou enviá-lo para um sistema não-confiável.
  • Um usuário que consegue criar um Pod que utiliza um Secret também consegue ler o valor daquele Secret. Mesmo que o servidor da API possua políticas para impedir que aquele usuário leia o valor do Secret, o usuário poderia criar um Pod que expõe o Secret.

Próximos passos

4 - Gerenciamento de recursos em Pods e contêineres

Ao criar a especificação de um Pod, você pode opcionalmente especificar quanto de cada recurso um contêiner precisa. Os recursos mais comuns a serem especificados são CPU e memória (RAM); há outros recursos que podem ser especificados.

Quando você especifica o requerimento de recursos em um Pod, o kube-scheduler utiliza esta informação para decidir a qual nó o Pod será atribuído. Quando você especifica um limite de recurso para um contêiner, o kubelet garante o cumprimento de tais limites, de modo que o contêiner em execução não consiga utilizar uma quantidade de tal recurso além do limite especificado. O kubelet também reserva pelo menos o requerimento daquele recurso de sistema especificamente para que este contêiner utilize.

Requerimentos e limites

Se o nó em que um Pod está rodando tem o suficiente de um recurso específico disponível, é possível (e permitido) a um contêiner utilizar mais do que o seu request para aquele recurso especifica. No entanto, não é permitido a um contêiner consumir mais do que o seu limit para um recurso.

Por exemplo, se você especificar um requerimento de memory de 256 MiB para um contêiner, e aquele contêiner está em um Pod atribuído a um nó com 8GiB de memória, sem outros Pods, então este contêiner pode tentar consumir mais memória RAM.

Se você especificar um limite de memory de 4GiB para aquele contêiner, o kubelet (e o agente de execução de contêiner) vão garantir o cumprimento do limite. O agente de execução impede que o contêiner utilize mais de um recurso do que seu limite configurado. Por exemplo, quando um processo no contêiner tenta consumir mais que o limite permitido de memória, o núcleo do sistema encerra o processo que tentou efetuar a alocação de memória com um erro de memória esgotada (out of memory (OOM) error).

Limites podem ser implementados de forma reativa (o sistema intervém quando uma violação ocorre) ou por garantia (o sistema previne o contêiner de exceder o limite). Diferentes agentes de execução implementam as mesmas restrições de maneiras diferentes.

Tipos de recursos

CPU e memória são tipos de recursos. Um tipo de recurso possui uma unidade básica. CPU representa processamento computacional e é especificada em unidades de CPU do Kubernetes. Memória é especificada em bytes. Em cargas de trabalho Linux, você pode especificar o recurso huge pages. Huge pages são uma funcionalidade específica do Linux que permite ao núcleo do sistema operacional alocar blocos de memória muito maiores que o tamanho de página de memória padrão.

Por exemplo, em um sistema onde o tamanho da página de memória padrão é de 4 KiB, você pode especificar um limite hugepages-2Mi: 80Mi. Se o contêiner tentar alocar mais de 40 huge pages de 2 MiB cada, ou um total de 80 MiB, essa alocação irá falhar.

CPU e memória são chamados coletivamente de recursos computacionais, ou apenas recursos. Recursos computacionais são quantidades mensuráveis que podem ser requisitadas, alocadas, e consumidas. Estes recursos diferem dos recursos de API. Recursos de API, como Pods e Services são objetos que podem ser lidos e modificados através do servidor da API do Kubernetes.

Requerimentos de recursos e limites de Pod e contêiner

Para cada contêiner, você pode especificar limites e requerimentos de recursos, incluindo os seguintes recursos:

  • spec.containers[].resources.limits.cpu
  • spec.containers[].resources.limits.memory
  • spec.containers[].resources.limits.hugepages-<size>
  • spec.containers[].resources.requests.cpu
  • spec.containers[].resources.requests.memory
  • spec.containers[].resources.requests.hugepages-<size>

Embora você possa especificar apenas requerimentos e limites para contêineres individuais, é útil também pensar sobre os requerimentos e limites gerais de um Pod. Para um recurso em particular, um requerimento ou limite de recurso de um Pod é a soma de todos os valores dos requerimentos ou limites de um recurso daquele tipo, especificados em cada um dos contêineres daquele Pod.

Unidades de recursos no Kubernetes

Unidades de recurso de CPU

Limites e requerimentos de recursos de CPU são mensurados em unidades de cpu. No Kubernetes, uma unidade de CPU é equivalente a um núcleo físico de CPU, ou um núcleo virtual, dependendo se o nó é uma máquina física ou uma máquina virtual rodando em uma máquina física.

Requerimentos fracionários são permitidos. Quando você define um contêiner cujo valor do campo spec.containers[].resources.requests.cpu é 0.5, você está solicitando metade da quantidade de CPU que teria sido solicitada caso o valor fosse 1.0. No caso de unidades de recurso de CPU, a expressão de quantidade 0.1 é equivalente à expressão 100m, que pode ser lida como "cem milicpus", ou "cem milinúcleos". "Milicpu" ou "milinúcleo" equivalem à milésima parte de um núcleo ou CPU, de modo que "100m" equivalem a 10% do tempo computacional de um processador.

Recursos de CPU são sempre especificados como uma quantidade absoluta de recurso, nunca como uma quantidade relativa. Por exemplo, 500m de CPU representam grosseiramente a mesma quantidade de poder computacional, independentemente do contêiner rodar em uma máquina com processador de núcleo único, de dois núcleos ou de 48 núcleos.

Unidades de recurso de memória

Limites e requerimentos de memory são medidos em bytes. Você pode expressar memória como um número inteiro ou como um número de ponto fixo, utilizando um destes sufixos de quantidade: E, P, T, G, M, k. Você também pode utilizar os equivalentes de potência de dois: Ei, Pi, Ti, Gi, Mi, Ki. Por exemplo, as quantidades abaixo representam, a grosso modo, o mesmo valor:

128974848, 129e6, 129M, 128974848000m, 123Mi

Tome cuidado com os sufixos. Se você solicitar 400m de memória, esta quantidade estará de fato requerendo o equivalente a 0,4 byte de memória. A intenção da pessoa que fez esta requisição provavelmente era solictar 400 mebibytes (400Mi) ou 400 megabytes (400M).

Exemplo de recursos de contêiner

O Pod seguinte tem dois contêineres. Ambos os contêineres têm um requerimento de 0,25 CPU e 64 MiB (ou 226 bytes) de memória. Cada contêiner tem um limite de 0,5 CPU e 128 MiB de memória. Você pode dizer que o Pod tem um requerimento de 0,5 CPU e 128 MiB de memória, e um limite de 1 CPU e 256 MiB de memória.

---
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Como Pods com requerimentos de recursos são agendados

Quando você cria um Pod, o escalonador do Kubernetes seleciona um nó para que o Pod rode. Cada nó possui uma capacidade máxima para cada um dos tipos de recurso: a quantidade de CPU e memória que o nó pode fornecer aos Pods. O escalonador garante que, para cada tipo de recurso, a soma dos requerimentos de recursos dos contêineres agendados seja menor que a capacidade do nó. Note que, embora o consumo de memória ou CPU real nos nós seja muito baixo, o escalonador ainda irá se recusar a agendar um Pod em um nó se a verificação de capacidade falhar. Isso protege contra a falta de um recurso em um nó quando o consumo de recursos aumenta com o passar do tempo, como por exemplo durante o pico diário de requisições a um serviço.

Como o Kubernetes aplica requisições e limites de recursos

Quando o kubelet inicia um contêiner como parte de um Pod, o kubelet envia as requisições e limites de memória e de CPU ao agente de execução de contêiner.

No Linux, o agente de execução de contêiner normalmente configura os cgroups que aplicam e garantem os limites que você definiu.

  • O limite de CPU determina um teto de quanto tempo de CPU o contêiner pode utilizar. A cada intervalo de agendamento, o núcleo do sistema operacional do Linux verifica se este limite foi excedido; se este for o caso, o núcleo aguarda antes de permitir que aquele cgroup continue sua execução.
  • O requerimento de CPU normalmente define um método de balanceamento. Se vários contêineres diferentes (cgroups) querem rodar em um sistema disputado, cargas de trabalho com requerimentos maiores de CPU têm mais tempo de CPU alocado para si do que cargas de trabalho com pequenos requerimentos.
  • O requerimento de memória é usado principalmente durante o agendamento de um Pod. Em um nó que utiliza cgroups v2, o agente de execução de contêiner pode utilizar o requerimento de memória como uma dica para definir valores para memory.min e memory.low.
  • O limite de memória define um limite de memória para aquele cgroup. Se o contêiner tenta alocar mais memória que aquele limite, o subsistema out-of-memory do núcleo do sistema operacional Linux é ativado e, normalmente, intervém encerrando um dos processos do contêiner que tentou alocar mais memória. Se o processo em questão for o PID 1 do contêiner, e o contêiner estiver marcado como reinicializável, então o Kubernetes irá reiniciar o contêiner.
  • O limite de memória para um Pod ou contêiner é também aplicado a páginas em volumes armazenados em memória, como um emptyDir. O kubelet considera sistemas de arquivos tmpfs em volumes do tipo emptyDir como uso de memória em um contêiner, ao invés de armazenamento efêmero local.

Se um contêiner exceder seu requerimento de memória e o nó em que esse contêiner está rodando ficar com pouca memória no total, é provável que o Pod a que este contêiner pertence seja removido.

A um contêiner pode ou não ser permitido exceder seu limite de CPU por períodos de tempo estendidos. No entanto, agentes de execução de contêiner não encerram Pods por uso excessivo de CPU.

A fim de determinar se um contêiner não pode ser agendado ou está sendo encerrado devido a limites de recursos, consulte a seção de solução de problemas.

Monitorando utilização de recursos computacionais e de memória

O kubelet relata a utilização de recursos de um Pod como parte do status do Pod.

Se ferramentas opcionais para monitoramento de recursos estiverem disponíveis em seu cluster, a utilização de recursos de um Pod pode ser verificada diretamente através de API de métricas ou através das suas ferramentas de monitoramento

Armazenamento efêmero local

FEATURE STATE: Kubernetes v1.10 [beta]

Nós possuem armazenamento efêmero local, através de dispositivos de escrita conectados localmente ou através de RAM. "Efêmero" significa que não há garantia de longo termo com relação a durabilidade.

Pods utilizam armazenamento local efêmero para dados temporários, cache e logs. O kubelet pode fornecer armazenamento temporário a Pods que utilizam armazenamento local efêmero para montar volumes do tipo emptyDir em contêineres.

O kubelet também utiliza este tipo de armazenamento para logs de contêineres a nível de nó, imagens de contêiner e camadas graváveis de contêineres em execução.

Com esta funcionalidade em fase beta, o Kubernetes permite que você rastreie, reserve e limite quanto armazenamento local efêmero um Pod pode consumir.

Configurações para armazenamento local efêmero

O Kubernetes suporta duas formas de configuração para o armazenamento local efêmero em um nó:

Nesta configuração, você armazena todos os tipos diferentes de dados locais efêmeros (volumes do tipo emptyDir, camadas graváveis, imagens de contêiner, logs) em um sistema de arquivos único. A forma mais efetiva de configurar o kubelet é dedicar este sistema de arquivos aos dados do Kubernetes (kubelet).

O kubelet também escreve logs de contêiner a nível de nó e trata estes logs de maneira semelhante ao armazenamento efêmero local.

O kubelet escreve logs em arquivos dentro do seu diretório de log configurado (/var/log por padrão) e possui um diretório base para outros dados armazenados localmente (/var/lib/kubelet por padrão).

Normalmente, ambos os diretórios /var/lib/kubelet e /var/log encontram-se no sistema de arquivos raiz, e o kubelet é projetado com este desenho em mente.

Seu nó pode ter tantos outros sistemas de arquivos não utilizados pelo Kubernetes quantos você desejar.

Você tem um sistema de arquivos no nó que você utiliza para dados efêmeros que vêm de Pods em execução: logs e volumes do tipo emptyDir. Você pode utilizar este sistema de arquivos para outros dados (por exemplo, logs de sistema não relacionados ao Kubernetes); este sistema de arquivos pode até mesmo ser o sistema de arquivos raiz.

O kubelet também escreve logs de contêiner a nível de nó no primeiro sistema de arquivos e os trata de forma semelhante ao armazenamento local efêmero.

Você também tem um segundo sistema de arquivos, separado, conectado a um dispositivo lógico de armazenamento distinto. Nesta configuração, o diretório que você configurou o kubelet para armazenar as camadas de imagens de contêiner e as camadas graváveis de contêineres em execução estará neste segundo sistema de arquivos.

O primeiro sistema de arquivos não armazena nenhuma camada de imagens de contêiner ou camada gravável.

Seu nó pode ter tantos outros sistemas de arquivos não utilizados pelo Kubernetes quantos você desejar.

O kubelet consegue medir quanto armazenamento local está sendo utilizado. O kubelet faz isso desde que:

  • o feature gate LocalStorageCapacityIsolation esteja habilitado (a funcionalidade está ligada por padrão), e
  • você tenha configurado o nó utilizando uma das configurações suportadas para o armazenamento local efêmero.

Se você tiver uma configuração diferente, o kubelet não irá aplicar limites de recursos para o armazenamento local efêmero.

Configurando requerimentos e limites para armazenamento local efêmero

Você pode especificar o recurso ephemeral-storage para gerenciar o armazenamento local efêmero. Cada contêiner de um Pod pode especificar um dos valores abaixo, ou ambos:

  • spec.containers[].resources.limits.ephemeral-storage
  • spec.containers[].resources.requests.ephemeral-storage

Limites e requerimentos de ephemeral-storage são medidos em quantidades de bytes. Você pode expressar armazenamento como um inteiro ou como um valor de ponto fixo utilizando um dos seguintes sufixos: E, P, T, G, M, k. Você pode também utilizar os equivalentes de potência de dois: Ei, Pi, Ti, Gi, Mi, Ki. Por exemplo, as quantidades abaixo representam grosseiramente o mesmo valor:

  • 128974848
  • 129e6
  • 129M
  • 123Mi

No exemplo a seguir, o Pod tem dois contêineres. Cada contêiner tem um requerimento de 2GiB de armazenamento efêmero local. Cada contêiner tem um limite de 4GiB de armazenamento efêmero local. Portanto, o Pod tem um requerimento de 4GiB e um limite de 8GiB de armazenamento efêmero local.

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        ephemeral-storage: "2Gi"
      limits:
        ephemeral-storage: "4Gi"
    volumeMounts:
    - name: ephemeral
      mountPath: "/tmp"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        ephemeral-storage: "2Gi"
      limits:
        ephemeral-storage: "4Gi"
    volumeMounts:
    - name: ephemeral
      mountPath: "/tmp"
  volumes:
    - name: ephemeral
      emptyDir: {}

Como Pods com requerimentos de ephemeral-storage são agendados

Quando você cria um Pod, o Kubernetes seleciona um nó para o Pod rodar. Cada nó tem uma quantidade máxima de armazenamento efêmero local que pode ser fornecida aos Pods. Para mais informações, consulte Node Allocatable.

O escalonador garante que a soma dos requerimentos de recursos dos contêineres agendados é menor que a capacidade do nó.

Gerenciamento do consumo do armazenamento efêmero

Se o kubelet estiver gerenciando armazenamento local efêmero como um recurso, o kubelet irá medir o consumo de armazenamento em:

  • volumes emptyDir, com exceção dos volumes do tipo tmpfs
  • diretórios que armazenem logs a nível de nó
  • camadas de contêiner graváveis

Se um Pod estiver utilizando mais armazenamento efêmero do que o permitido, o kubelet irá gerar um sinal de remoção para aquele Pod.

Para isolamento a nível de contêiner, se o consumo de armazenamento de um contêiner em camadas graváveis e logs exceder seu limite de armazenamento, o kubelet irá marcar o Pod para remoção.

Para isolamento a nível de Pod, o kubelet calcula um limite de armazenamento total para um Pod somando os limites de cada contêiner naquele Pod. Neste caso, se a soma do consumo de armazenamento efêmero local de todas os contêineres e também dos volumes emptyDir de um Pod exceder o limite de armazenamento total do Pod, então o kubelet marca o Pod para remoção.

O kubelet suporta formas diferentes de medir o uso de armazenamento dos Pods:

O kubelet executa verificações agendadas, em intervalos regulares, que varrem cada volume do tipo emptyDir, diretório de log de contêiner, e camada gravável de contêiner.

A varredura mede quanto espaço está sendo utilizado.

Quotas de projeto são uma funcionalidade a nível de sistema operacional para gerenciamento de uso do armazenamento em sistemas de arquivos. Com o Kubernetes, você pode habilitar quotas de projeto para o monitoramento de armazenamento em uso. Tenha certeza que o sistema de arquivos do nó que esteja sendo utilizado em volumes do tipo emptyDir possui suporte a quotas de projeto. Por exemplo, os sistemas de arquivos XFS e ext4fs oferecem suporte a quotas de projeto.

O Kubernetes utiliza IDs de projeto iniciando em 1048576. Os IDs em uso estão registrados nos diretórios /etc/projects e /etc/projid. Se os IDs de projeto nestes intervalos forem utilizados para outros propósitos no sistema, estes IDs de projeto deverão estar registrados nos diretórios especificados acima para que o Kubernetes não os tente utilizar.

Quotas fornecem melhor desempenho e mais precisão do que varredura de diretórios. Quando um diretório é atribuído a um projeto, todos os arquivos criados no diretório são também criados no projeto, e o núcleo do sistema pode simplesmente manter controle de quantos blocos estão em uso por arquivos daquele projeto. Se um arquivo é criado e apagado, mas possui um descritor de arquivo aberto, ele continua a consumir espaço. O rastreio de quotas registra este espaço de forma precisa, enquanto varreduras de diretório ignoram o uso de espaço de armazenamento por arquivos apagados.

Se você deseja utilizar quotas de projeto, você deve:

  • Habilitar o feature gate LocalStorageCapacityIsolationFSQuotaMonitoring=true utilizando o campo featureGates na configuração do kubelet ou a opção de linha de comando --feature-gates.

  • Garantir que o sistema de arquivos raiz (ou o sistema de arquivos opcional de tempo de execução) tem quotas de projeto habilitadas. Todos os sistemas de arquivos XFS suportam quotas de projeto. Em sistemas de arquivos ext4, você precisa habilitar a funcionalidade de rastreio de quotas de projeto enquanto o sistema de arquivos ainda não está montado.

    # Para sistema de arquivos ext4, com o volume /dev/block-device não montado
    sudo tune2fs -O project -Q prjquota /dev/block-device
    
  • Garanta que o sistema de arquivos raiz (ou sistema de arquivos opcional de tempo de execução) esteja montado com quotas de projeto habilitadas. Em ambos os sistemas XFS e ext4fs, a opção de montagem é chamada prjquota.

Recursos estendidos

Recursos estendidos são nomes de recursos absolutos fora do domínio kubernetes.io. Estes recursos permitem a operadores de cluster anunciar e a usuários consumir recursos que não são embutidos pelo Kubernetes.

Dois passos são necessários para a utilização de recursos estendidos. Primeiramente, o operador do cluster deve anunciar um recurso estendido. Em segundo lugar, os usuários devem solicitar o recurso estendido em Pods.

Gerenciando recursos estendidos

Recursos estendidos a nível de nó

Recursos estendidos a nível de nó são recursos ligados ao nó.

Recursos gerenciados por dispositivos conectados

Veja Device Plugin para mais informações sobre como anunciar recursos gerenciados por dispositivos conectados em cada nó.

Outros recursos

A fim de anunciar um novo recurso estendido a nível de nó, o operador do cluster pode enviar uma requisição HTTP com o método PATCH para o servidor da API do Kubernetes para especificar a quantidade disponível em um nó no cluster, através do campo status.capacity. Após a realização desta operação, o campo status.capacity do nó irá conter um novo recurso. O campo status.allocatable é atualizado automaticamente pelo kubelet, de forma assíncrona, com o novo recurso.

Como o escalonador utiliza o valor do campo status.allocatable do nó ao verificar a saúde do Pod, o escalonador somente considerará o novo valor do campo após esta atualização assíncrona. Pode haver um pequeno atraso entre a atualização da capacidade do nó com um novo recurso e o momento em que o primeiro Pod que requer o recurso poderá ser agendado naquele nó.

Exemplo:

Este exemplo demonstra como utilizar a ferramenta curl para criar uma requisição HTTP que anuncia cinco recursos "example.com/foo" no nó k8s-node-1, cujo nó da camada de gerenciamento é k8s-master.

curl --header "Content-Type: application/json-patch+json" \
  --request PATCH \
  --data '[{"op": "add", "path": "/status/capacity/example.com~1foo", "value": "5"}]' \
  http://k8s-master:8080/api/v1/nodes/k8s-node-1/status

Recursos estendidos a nível de cluster

Recursos estendidos a nível de cluster não são vinculados aos nós. Estes recursos são normalmente gerenciados por extensões do escalonador, que manipulam o consumo e as quotas de recursos.

Você pode especificar os recursos estendidos que são manipulados por extensões do escalonador nas configurações do kube-scheduler.

Exemplo:

A configuração abaixo para uma política do escalonador indica que o recurso estendido a nível de cluster "example.com/foo" é manipulado pelas extensões do escalonador.

  • O escalonador envia um Pod para a extensão do escalonador somente se o Pod solicitar "example.com/foo".
  • O campo ignoredByScheduler especifica que o escalonador não verifica o recurso "example.com/foo" em seu predicado PodFitsResources.
{
  "kind": "Policy",
  "apiVersion": "v1",
  "extenders": [
    {
      "urlPrefix":"<extender-endpoint>",
      "bindVerb": "bind",
      "managedResources": [
        {
          "name": "example.com/foo",
          "ignoredByScheduler": true
        }
      ]
    }
  ]
}

Consumindo recursos estendidos

Usuários podem consumir recursos estendidos em especificações de Pods como CPU e memória. O escalonador controla a contagem de recursos de modo que a quantidade alocada simultaneamente a Pods não seja maior que a quantidade disponível.

O servidor da API limita as quantidades de recursos estendidos a números inteiros. Exemplos de quantidades válidas são 3, 3000m e 3Ki. Exemplos de quantidades inválidas são 0.5 e 1500m.

Para consumir um recurso estendido em um Pod, inclua o nome do recurso como uma chave no mapa spec.containers[].resources.limits na especificação do contêiner.

Um Pod só é agendado se todos os seus requerimentos de recursos forem satisfeitos, incluindo CPU, memória e quaisquer recursos estendidos. O Pod permanece no estado PENDING enquanto seus requerimentos de recursos não puderem ser satisfeitos.

Exemplo:

O Pod abaixo requisita duas CPUs e um "example.com/foo" (um recurso estendido).

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: my-container
    image: myimage
    resources:
      requests:
        cpu: 2
        example.com/foo: 1
      limits:
        example.com/foo: 1

Limitação de PID

Limites de ID de processo (PID) permitem à configuração de um kubelet limitar o número de PIDs que um dado Pod pode consumir. Consulte PID Limiting para mais informações.

Solução de problemas

Meus pods estão pendentes com um evento FailedScheduling

Se o escalonador não conseguir encontrar nenhum nó que atenda aos requisitos de recursos do Pod, este Pod permanecerá não-agendado até que um local destino possa ser encontrado. Um Evento é produzido cada vez que o escalonador falhar em encontrar um local para agendar o Pod. Você pode utilizar o utilitário kubectl para ver os eventos de um Pod. Por exemplo:

kubectl describe pod frontend | grep -A 9999999999 Events
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  23s   default-scheduler  0/42 nodes available: insufficient cpu

No exemplo acima, o Pod de nome "frontend" não pôde ser agendado devido à nenhum nó possuir CPU suficiente para suprir seu requerimento de CPU. Mensagens de erro semelhantes a essa podem sugerir falha devido a falta de memória (PodExceedsFreeMemory). De maneira geral, se um Pod estiver pendente com uma mensagem deste tipo, há diversas possibilidades de solução a serem tentadas:

  • Adicione mais nós ao cluster.
  • Encerre Pods desnecessários para liberar espaço para Pods pendentes.
  • Verifique se o Pod não é maior que todos os nós. Por exemplo, se todos os nós têm uma capacidade de cpu: 1, um Pod que requisita cpu: 1.1 nunca será agendado.
  • Verifique se os nós não possuem taints. Se a maioria dos seus nós possuem taints, e o novo Pod não tolera tal taint, o escalonador somente considera agendar o Pod nos nós que não possuem aquele taint.

Você pode verificar capacidades de nós e quantidades alocadas com o comando kubectl describe nodes. Por exemplo:

kubectl describe nodes e2e-test-node-pool-4lw4
Name:            e2e-test-node-pool-4lw4
[ ... linhas abreviadas para simplificação ...]
Capacity:
 cpu:                               2
 memory:                            7679792Ki
 pods:                              110
Allocatable:
 cpu:                               1800m
 memory:                            7474992Ki
 pods:                              110
[ ... linhas abreviadas para simplificação ...]
Non-terminated Pods:        (5 in total)
  Namespace    Name                                  CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------    ----                                  ------------  ----------  ---------------  -------------
  kube-system  fluentd-gcp-v1.38-28bv1               100m (5%)     0 (0%)      200Mi (2%)       200Mi (2%)
  kube-system  kube-dns-3297075139-61lj3             260m (13%)    0 (0%)      100Mi (1%)       170Mi (2%)
  kube-system  kube-proxy-e2e-test-...               100m (5%)     0 (0%)      0 (0%)           0 (0%)
  kube-system  monitoring-influxdb-grafana-v4-z1m12  200m (10%)    200m (10%)  600Mi (8%)       600Mi (8%)
  kube-system  node-problem-detector-v0.1-fj7m3      20m (1%)      200m (10%)  20Mi (0%)        100Mi (1%)
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  CPU Requests    CPU Limits    Memory Requests    Memory Limits
  ------------    ----------    ---------------    -------------
  680m (34%)      400m (20%)    920Mi (11%)        1070Mi (13%)

No exemplo anterior, você pode verificar que se um Pod requisitar mais que 1,120 CPUs ou mais que 6,23Gi de memória, tal Pod não caberá neste nó.

Ao verificar a seção "Pods", você pode observar quais Pods estão consumindo espaço neste nó.

A quantidade de recursos disponível aos Pods é menor que a capacidade do nó, pois daemons do sistema utilizam uma parcela dos recursos disponíveis. Dentro da API do Kubernetes, cada nó tem um campo .status.allocatable (consulte NodeStatus para mais detalhes).

O campo .status.allocatable descreve a quantidade de recursos que está disponível a Pods naquele nó (por exemplo: 15 CPUs virtuais e 7538 MiB de memória). Para mais informações sobre recursos alocáveis do nó no Kubernetes, veja Reserve Compute Resources for System Daemons.

Você pode configurar quotas de recursos para limitar a quantidade total de recursos que um namespace pode consumir. O Kubernetes garante quotas para objetos em um namespace específico quando há uma ResourceQuota naquele namespace. Por exemplo, se você atribuir namespaces específicos a times diferentes, você pode adicionar ResourceQuotas nestes namespaces. Criar quotas de recursos ajuda a evitar que um time utilize tanto de um recurso que chegue a afetar outros times utilizando o mesmo cluster.

Você deve também considerar o nível de acesso fornecido aos usuários de qualquer namespace: acesso completo para escrita permite a alguém com este acesso remover qualquer recurso, incluindo uma configuração de ResourceQuota.

Meu contêiner foi terminado

Seu contêiner pode ser terminado se faltar recursos para que este rode. Para verificar se um contêiner está sendo terminado por chegar no limite de algum recurso, utilize o comando kubectl describe pod no Pod em questão:

kubectl describe pod simmemleak-hra99

A saída será semelhante a:

Name:                           simmemleak-hra99
Namespace:                      default
Image(s):                       saadali/simmemleak
Node:                           kubernetes-node-tf0f/10.240.216.66
Labels:                         name=simmemleak
Status:                         Running
Reason:
Message:
IP:                             10.244.2.75
Containers:
  simmemleak:
    Image:  saadali/simmemleak:latest
    Limits:
      cpu:          100m
      memory:       50Mi
    State:          Running
      Started:      Tue, 07 Jul 2019 12:54:41 -0700
    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
      Started:      Fri, 07 Jul 2019 12:54:30 -0700
      Finished:     Fri, 07 Jul 2019 12:54:33 -0700
    Ready:          False
    Restart Count:  5
Conditions:
  Type      Status
  Ready     False
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  42s   default-scheduler  Successfully assigned simmemleak-hra99 to kubernetes-node-tf0f
  Normal  Pulled     41s   kubelet            Container image "saadali/simmemleak:latest" already present on machine
  Normal  Created    41s   kubelet            Created container simmemleak
  Normal  Started    40s   kubelet            Started container simmemleak
  Normal  Killing    32s   kubelet            Killing container with id ead3fb35-5cf5-44ed-9ae1-488115be66c6: Need to kill Pod

No exemplo acima, o campo Restart Count: 5 indica que o contêiner simmemleak deste Pod foi terminado e reiniciado cinco vezes até o momento. A razão OOMKilled demonstra que o contêiner tentou consumir mais memória do que o seu limite.

O próximo passo neste cenário seria vasculhar e depurar o código da aplicação, procurando por vazamentos de memória. Se você determinar que a aplicação está se comportando conforme o esperado, considere aumentar o limite (e possivelmente o requerimento) de memória para aquele contêiner.

Próximos passos

5 - 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