Migrar Objetos do Kubernetes Usando a Migração de Versão de Armazenamento

ESTADO DA FUNCIONALIDADE: Kubernetes v1.35 [beta](desabilitado por padrão)

O Kubernetes depende da reescrita ativa dos dados da API para suportar algumas atividades de manutenção relacionadas ao armazenamento em repouso. Dois exemplos notáveis são o esquema versionado dos recursos armazenados (ou seja, o esquema de armazenamento preferido mudando de v1 para v2 para um determinado recurso) e a criptografia em repouso (ou seja, reescrever dados obsoletos com base em uma mudança na forma como os dados devem ser criptografados).

Executar migrações de versão de armazenamento garante que todos os objetos de um recurso foram migrados de uma versão de armazenamento obsoleta. O requisito para executar uma migração de armazenamento é garantir que o recurso possua uma versão de recurso inteira. Todos os recursos do Kubernetes e CRDs possuem essa propriedade, mas a migração falhará se esse não for o caso, por exemplo, com APIs agregadas.

Antes de você começar

Instale o kubectl.

Você precisa ter um cluster do Kubernetes e a ferramenta de linha de comando kubectl deve estar configurada para se comunicar com seu cluster. É recomendado executar esse tutorial em um cluster com pelo menos dois nós que não estejam atuando como hosts de camada de gerenciamento. Se você ainda não possui um cluster, pode criar um usando o minikube ou pode usar um dos seguintes ambientes:

O seu servidor Kubernetes deve estar numa versão igual ou superior a v1.30.

Para verificar a versão, digite kubectl version.

Certifique-se de que seu cluster tenha o feature gate StorageVersionMigrator habilitado. Você precisará de acesso de administrador à camada de gerenciamento para fazer essa alteração.

Habilite a API REST de migração de versão de armazenamento definindo a configuração de agente de execução storagemigration.k8s.io/v1beta1 como true para o servidor de API. Para mais informações sobre como fazer isso, leia habilitar ou desabilitar uma API do Kubernetes.

Criptografar novamente os secrets do Kubernetes usando migração de versão de armazenamento

  • Para começar, configure o provedor KMS para criptografar dados em repouso no etcd usando a seguinte configuração de criptografia.

    kind: EncryptionConfiguration
    apiVersion: apiserver.config.k8s.io/v1
    resources:
    - resources:
      - secrets
      providers:
      - aescbc:
          keys:
          - name: key1
            secret: c2VjcmV0IGlzIHNlY3VyZQ==
    

    Certifique-se de habilitar o recarregamento automático do arquivo de configuração de criptografia definindo --encryption-provider-config-automatic-reload como true.

  • Crie um Secret usando kubectl.

    kubectl create secret generic my-secret --from-literal=key1=supersecret
    
  • Verifique se os dados serializados desse objeto Secret possuem o prefixo k8s:enc:aescbc:v1:key1.

  • Atualize o arquivo de configuração de criptografia conforme a seguir para rotacionar a chave de criptografia.

    kind: EncryptionConfiguration
    apiVersion: apiserver.config.k8s.io/v1
    resources:
    - resources:
      - secrets
      providers:
      - aescbc:
          keys:
          - name: key2
            secret: c2VjcmV0IGlzIHNlY3VyZSwgaXMgaXQ/
      - aescbc:
          keys:
          - name: key1
            secret: c2VjcmV0IGlzIHNlY3VyZQ==
    
  • Para garantir que o secret my-secret criado anteriormente seja criptografado novamente com a nova chave key2, você utilizará a Migração de Versão de Armazenamento.

  • Crie um manifesto StorageVersionMigration chamado migrate-secret.yaml conforme a seguir:

    kind: StorageVersionMigration
    apiVersion: storagemigration.k8s.io/v1beta1
    metadata:
      name: secrets-migration
    spec:
      resource:
        group: ""
        resource: secrets
    

    Crie o objeto usando kubectl conforme a seguir:

    kubectl apply -f migrate-secret.yaml
    
  • Monitore a migração dos Secrets verificando o .status do StorageVersionMigration. Uma migração bem-sucedida deve ter sua condição Succeeded definida como true. Obtenha o objeto StorageVersionMigration conforme a seguir:

    kubectl wait --for=condition=Succeeded storageversionmigration.storagemigration.k8s.io/secrets-migration
    

    A saída é semelhante a:

    kind: StorageVersionMigration
    apiVersion: storagemigration.k8s.io/v1beta1
    metadata:
      name: secrets-migration
      uid: 628f6922-a9cb-4514-b076-12d3c178967c
      resourceVersion: "90"
      creationTimestamp: "2024-03-12T20:29:45Z"
    spec:
      resource:
        group: ""
        resource: secrets
    status:
      conditions:
      - type: Running
        status: "False"
        lastUpdateTime: "2024-03-12T20:29:46Z"
        reason: StorageVersionMigrationInProgress
      - type: Succeeded
        status: "True"
        lastUpdateTime: "2024-03-12T20:29:46Z"
        reason: StorageVersionMigrationSucceeded
      resourceVersion: "84"
    
  • Verifique se o secret armazenado agora possui o prefixo k8s:enc:aescbc:v1:key2.

Atualizar o esquema de armazenamento preferido de um CRD

Considere um cenário onde um CustomResourceDefinition (CRD) é criado para servir recursos personalizados (CRs) e é definido como o esquema de armazenamento preferido. Quando chega o momento de introduzir a v2 do CRD, ela pode ser adicionada apenas para servir com um webhook de conversão. Isso permite uma transição mais suave, onde os usuários podem criar CRs usando o esquema v1 ou v2, com o webhook implementado para realizar a conversão de esquema necessária entre eles. Antes de definir a v2 como a versão preferida do esquema de armazenamento, é importante garantir que todos os CRs existentes armazenados como v1 sejam migrados para v2. Essa migração pode ser realizada por meio da Migração de Versão de Armazenamento para migrar todos os CRs de v1 para v2.

  • Crie um manifesto para o CRD, chamado test-crd.yaml, conforme a seguir:

    apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    metadata:
      name: selfierequests.example.com
    spec:
      group: example.com
      names:
        plural: selfierequests
        singular: selfierequest
        kind: SelfieRequest
        listKind: SelfieRequestList
      scope: Namespaced
      versions:
      - name: v1
        served: true
        storage: true
        schema:
          openAPIV3Schema:
            type: object
            properties:
              hostPort:
                type: string
      conversion:
        strategy: Webhook
        webhook:
          clientConfig:
            url: "https://127.0.0.1:9443/crdconvert"
            caBundle: <CABundle info>
        conversionReviewVersions:
        - v1
        - v2
    

    A versão armazenada neste ponto deve ser v1, confirme isso executando:

    kubectl get crd selfierequests.example.com -o jsonpath='{.spec.versions[?(@.storage==true)].name}'
    

    Crie o CRD usando kubectl:

    kubectl apply -f test-crd.yaml
    
  • Crie um manifesto para um testcrd de exemplo. Nomeie o manifesto como cr1.yaml e use o seguinte conteúdo:

    apiVersion: example.com/v1
    kind: SelfieRequest
    metadata:
      name: cr1
      namespace: default
    

    Crie o CR usando kubectl:

    kubectl apply -f cr1.yaml
    
  • Verifique se o CR foi escrito e armazenado como v1 obtendo o objeto do etcd.

    ETCDCTL_API=3 etcdctl get /kubernetes.io/example.com/testcrds/default/cr1 [...] | hexdump -C
    

    onde [...] contém os argumentos adicionais para conexão com o servidor etcd.

  • Atualize o CRD test-crd.yaml para incluir a versão v2 para servir e armazenamento e v1 apenas para servir, conforme a seguir:

    apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    metadata:
    name: selfierequests.example.com
    spec:
      group: example.com
      names:
        plural: selfierequests
        singular: selfierequest
        kind: SelfieRequest
        listKind: SelfieRequestList
      scope: Namespaced
      versions:
        - name: v2
          served: true
          storage: true
          schema:
            openAPIV3Schema:
              type: object
              properties:
                host:
                  type: string
                port:
                  type: string
        - name: v1
          served: true
          storage: false
          schema:
            openAPIV3Schema:
              type: object
              properties:
                hostPort:
                  type: string
      conversion:
        strategy: Webhook
        webhook:
          clientConfig:
            url: "https://127.0.0.1:9443/crdconvert"
            caBundle: <CABundle info>
          conversionReviewVersions:
            - v1
            - v2
    

    A versão armazenada agora deve ser v2, confirme isso:

    kubectl get crd selfierequests.example.com -o jsonpath='{.spec.versions[?(@.storage==true)].name}'
    

    Atualize o CRD usando kubectl:

    kubectl apply -f test-crd.yaml
    
  • Crie o arquivo de recurso CR com o nome cr2.yaml conforme a seguir:

    apiVersion: example.com/v2
    kind: SelfieRequest
    metadata:
      name: cr2
      namespace: default
    
  • Crie o CR usando kubectl:

    kubectl apply -f cr2.yaml
    
  • Verifique se o CR foi escrito e armazenado como v2 obtendo o objeto do etcd.

    ETCDCTL_API=3 etcdctl get /kubernetes.io/example.com/testcrds/default/cr2 [...] | hexdump -C
    

    onde [...] contém os argumentos adicionais para conexão com o servidor etcd.

  • Crie um manifesto StorageVersionMigration chamado migrate-crd.yaml, com o conteúdo conforme a seguir:

    kind: StorageVersionMigration
    apiVersion: storagemigration.k8s.io/v1beta1
    metadata:
      name: crdsvm
    spec:
      resource:
        group: example.com
        resource: SelfieRequest
    

    Crie o objeto usando kubectl conforme a seguir:

    kubectl apply -f migrate-crd.yaml
    
  • Monitore a migração dos secrets usando o status. Uma migração bem-sucedida deve ter a condição Succeeded definida como "True" no campo de status. Obtenha o recurso de migração conforme a seguir:

    kubectl get storageversionmigration.storagemigration.k8s.io/crdsvm -o yaml
    

    A saída é semelhante a:

    kind: StorageVersionMigration
    apiVersion: storagemigration.k8s.io/v1beta1
    metadata:
      name: crdsvm
      uid: 13062fe4-32d7-47cc-9528-5067fa0c6ac8
      resourceVersion: "111"
      creationTimestamp: "2024-03-12T22:40:01Z"
    spec:
      resource:
        group: example.com
        resource: testcrds
    status:
      conditions:
        - type: Running
          status: "False"
          lastUpdateTime: "2024-03-12T22:40:03Z"
          reason: StorageVersionMigrationInProgress
        - type: Succeeded
          status: "True"
          lastUpdateTime: "2024-03-12T22:40:03Z"
          reason: StorageVersionMigrationSucceeded
      resourceVersion: "106"
    
  • Verifique se o cr1 criado anteriormente agora foi escrito e armazenado como v2 obtendo o objeto do etcd.

    ETCDCTL_API=3 etcdctl get /kubernetes.io/example.com/testcrds/default/cr1 [...] | hexdump -C
    

    onde [...] contém os argumentos adicionais para conexão com o servidor etcd.

  • Verifique também se o status da versão armazenada do CRD agora é apenas v2:

    kubectl get crd testcrds.example.com -o yaml
    

    A saída é semelhante a:

    kind: CustomResourceDefinition
    apiVersion: apiextensions.k8s.io/v1
    metadata:
      name: testcrds.example.com
    spec:
      group: example.com
      names:
        kind: TestCRD
        plural: testcrds
      scope: Namespaced
      versions:
        - name: v1
          served: true
          storage: false
        - name: v2
          served: true
          storage: true
    status:
      acceptedNames:
        kind: TestCRD
        plural: testcrds
      conditions:
        - type: Established
          status: "True"
      storedVersions:
        - v2