Removendo uma ramificação de recurso desatualizada em um cluster Kubernetes

Removendo uma ramificação de recurso desatualizada em um cluster Kubernetes

Oi! Ramo de recursos (também conhecido como pré-visualização de implantação, aplicativo de revisão) - ocorre quando não apenas o branch master é implantado, mas também cada solicitação pull para um URL exclusivo. Você pode verificar se o código funciona em um ambiente de produção; o recurso pode ser mostrado a outros programadores ou especialistas de produto. Enquanto você está trabalhando em uma solicitação pull, cada nova implantação atual de commit para o código antigo é excluída e a nova implantação para o novo código é lançada. Podem surgir dúvidas quando você mescla uma solicitação pull no branch master. Você não precisa mais da ramificação de recursos, mas os recursos do Kubernetes ainda estão no cluster.

Mais sobre ramificações de recursos

Uma abordagem para criar ramificações de recursos no Kubernetes é usar namespaces. Resumindo, a configuração de produção fica assim:

kind: Namespace
apiVersion: v1
metadata:
  name: habr-back-end
...

kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: habr-back-end
spec:
  replicas: 3
...

Para uma feature branch, um namespace é criado com seu identificador (por exemplo, o número do pull request) e algum tipo de prefixo/postfix (por exemplo, -pr-):

kind: Namespace
apiVersion: v1
metadata:
  name: habr-back-end-pr-17
...

kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: habr-back-end-pr-17
spec:
  replicas: 1
...

Em geral, eu escrevi Operador Kubernetes (um aplicativo que tem acesso aos recursos do cluster), link para o projeto no Github. Ele remove namespaces que pertencem a ramificações de recursos antigas. No Kubernetes, se você excluir um namespace, outros recursos nesse namespace também serão excluídos automaticamente.

$ kubectl get pods --all-namespaces | grep -e "-pr-"
NAMESPACE            ... AGE
habr-back-end-pr-264 ... 4d8h
habr-back-end-pr-265 ... 5d7h

Você pode ler sobre como implementar ramificações de recursos em um cluster aqui и aqui.

Motivação

Vejamos um ciclo de vida típico de pull request com integração contínua (continuous integration):

  1. Enviamos um novo commit para o branch.
  2. Na construção, linters e/ou testes são executados.
  3. As configurações de pull request do Kubernetes são geradas dinamicamente (por exemplo, seu número é inserido no modelo finalizado).
  4. Usando kubectl apply, as configurações são adicionadas ao cluster (implantação).
  5. A solicitação pull é mesclada no branch master.

Enquanto você está trabalhando em uma solicitação pull, cada nova implantação atual de commit para o código antigo é excluída e a nova implantação para o novo código é lançada. Mas quando uma solicitação pull é mesclada no branch master, apenas o branch master será construído. Como resultado, já esquecemos a solicitação pull e seus recursos do Kubernetes ainda estão no cluster.

Como utilizar

Instale o projeto com o comando abaixo:

$ kubectl apply -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/configs/production.yml

Crie um arquivo com o seguinte conteúdo e instale via kubectl apply -f:

apiVersion: feature-branch.dmytrostriletskyi.com/v1
kind: StaleFeatureBranch
metadata:
  name: stale-feature-branch
spec:
  namespaceSubstring: -pr-
  afterDaysWithoutDeploy: 3

Parâmetro namespaceSubstring necessário para filtrar namespaces para solicitações pull de outros namespaces. Por exemplo, se o cluster tiver os seguintes namespaces: habr-back-end, habr-front-end, habr-back-end-pr-17, habr-back-end-pr-33, então os candidatos para exclusão serão habr-back-end-pr-17, habr-back-end-pr-33.

Parâmetro afterDaysWithoutDeploy necessário para excluir namespaces antigos. Por exemplo, se o namespace for criado 3 дня 1 час de volta, e o parâmetro indica 3 дня, esse namespace será excluído. Também funciona na direção oposta se o namespace for criado 2 дня 23 часа de volta, e o parâmetro indica 3 дня, esse namespace não será excluído.

Há mais um parâmetro, ele é responsável pela frequência de verificação de todos os namespaces e verificação de dias sem implantação - verificar a cada minuto. Por padrão é igual 30 минутам.

Как это работает

Na prática, você precisará de:

  1. Estivador para trabalhar em um ambiente isolado.
  2. Minikubo criará um cluster Kubernetes localmente.
  3. kubectl — interface de linha de comando para gerenciamento de cluster.

Criamos um cluster Kubernetes localmente:

$ minikube start --vm-driver=docker
minikube v1.11.0 on Darwin 10.15.5
Using the docker driver based on existing profile.
Starting control plane node minikube in cluster minikube.

Especificar kubectl use cluster local por padrão:

$ kubectl config use-context minikube
Switched to context "minikube".

Baixe as configurações para o ambiente de produção:

$ curl https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/configs/production.yml > stale-feature-branch-production-configs.yml

Como as configurações de produção são configuradas para verificar namespaces antigos e nosso cluster recém-criado não os possui, substituiremos a variável de ambiente IS_DEBUG em true. Com este valor o parâmetro afterDaysWithoutDeploy não é levado em consideração e os namespaces não são verificados por dias sem implantação, apenas para a ocorrência da substring (-pr-).

Se você está em Linux:

$ sed -i 's|false|true|g' stale-feature-branch-production-configs.yml

Se você está em macOS:

$ sed -i "" 's|false|true|g' stale-feature-branch-production-configs.yml

Instalando o projeto:

$ kubectl apply -f stale-feature-branch-production-configs.yml

Verificando se um recurso apareceu no cluster StaleFeatureBranch:

$ kubectl api-resources | grep stalefeaturebranches
NAME                 ... APIGROUP                             ... KIND
stalefeaturebranches ... feature-branch.dmytrostriletskyi.com ... StaleFeatureBranch

Verificamos se um operador apareceu no cluster:

$ kubectl get pods --namespace stale-feature-branch-operator
NAME                                           ... STATUS  ... AGE
stale-feature-branch-operator-6bfbfd4df8-m7sch ... Running ... 38s

Se você olhar seus logs, ele está pronto para processar recursos StaleFeatureBranch:

$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator
... "msg":"Operator Version: 0.0.1"}
...
... "msg":"Starting EventSource", ... , "source":"kind source: /, Kind="}
... "msg":"Starting Controller", ...}
... "msg":"Starting workers", ..., "worker count":1}

Instalamos pronto fixtures (configurações prontas para modelar recursos de cluster) para um recurso StaleFeatureBranch:

$ kubectl apply -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/fixtures/stale-feature-branch.yml

As configurações indicam a busca por namespaces com uma substring -pr- uma vez em 1 минуту.:

apiVersion: feature-branch.dmytrostriletskyi.com/v1
kind: StaleFeatureBranch
metadata:
  name: stale-feature-branch
spec:
  namespaceSubstring: -pr-
  afterDaysWithoutDeploy: 1 
  checkEveryMinutes: 1

O operador respondeu e está pronto para verificar os namespaces:

$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator
... "msg":"Stale feature branch is being processing.","namespaceSubstring":"-pr-","afterDaysWithoutDeploy":1,"checkEveryMinutes":1,"isDebug":"true"}

Conjunto fixtures, contendo dois namespaces (project-pr-1, project-pr-2) e eles deployments, services, ingress, e assim por diante:

$ kubectl apply -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/fixtures/first-feature-branch.yml -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/fixtures/second-feature-branch.yml
...
namespace/project-pr-1 created
deployment.apps/project-pr-1 created
service/project-pr-1 created
horizontalpodautoscaler.autoscaling/project-pr-1 created
secret/project-pr-1 created
configmap/project-pr-1 created
ingress.extensions/project-pr-1 created
namespace/project-pr-2 created
deployment.apps/project-pr-2 created
service/project-pr-2 created
horizontalpodautoscaler.autoscaling/project-pr-2 created
secret/project-pr-2 created
configmap/project-pr-2 created
ingress.extensions/project-pr-2 created

Verificamos se todos os recursos acima foram criados com sucesso:

$ kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-1 && kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-2
...
NAME                              ... READY ... STATUS  ... AGE
pod/project-pr-1-848d5fdff6-rpmzw ... 1/1   ... Running ... 67s

NAME                         ... READY ... AVAILABLE ... AGE
deployment.apps/project-pr-1 ... 1/1   ... 1         ... 67s
...

Desde que incluímos debug, espaços para nome project-pr-1 и project-pr-2, portanto todos os outros recursos deverão ser excluídos imediatamente sem levar em conta o parâmetro afterDaysWithoutDeploy. Isso pode ser visto nos logs do operador:

$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator
... "msg":"Namespace should be deleted due to debug mode is enabled.","namespaceName":"project-pr-1"}
... "msg":"Namespace is being processing.","namespaceName":"project-pr-1","namespaceCreationTimestamp":"2020-06-16 18:43:58 +0300 EEST"}
... "msg":"Namespace has been deleted.","namespaceName":"project-pr-1"}
... "msg":"Namespace should be deleted due to debug mode is enabled.","namespaceName":"project-pr-2"}
... "msg":"Namespace is being processing.","namespaceName":"project-pr-2","namespaceCreationTimestamp":"2020-06-16 18:43:58 +0300 EEST"}
... "msg":"Namespace has been deleted.","namespaceName":"project-pr-2"}

Se você verificar a disponibilidade de recursos, eles estarão no status Terminating (processo de exclusão) ou já excluído (a saída do comando está vazia).

$ kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-1 && kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-2
...

Você pode repetir o processo de criação fixtures várias vezes e certifique-se de que sejam removidos dentro de um minuto.

Alternativas

O que pode ser feito em vez de um operador que trabalha em um cluster? Existem várias abordagens, todas elas são imperfeitas (e suas deficiências são subjetivas), e cada um decide por si o que é melhor para um determinado projeto:

  1. Exclua a ramificação do recurso durante a construção de integração contínua da ramificação mestre.

    • Para fazer isso, você precisa saber qual pull request está relacionado ao commit que está sendo construído. Como o namespace da ramificação do recurso contém o identificador da solicitação pull - seu número ou o nome da ramificação, o identificador sempre deverá ser especificado no commit.
    • As compilações do branch master estão falhando. Por exemplo, você tem as seguintes etapas: baixar o projeto, executar testes, construir o projeto, fazer um release, enviar notificações, limpar o branch de recurso da última solicitação pull. Se a construção falhar ao enviar uma notificação, você terá que excluir todos os recursos do cluster manualmente.
    • Sem o contexto adequado, a exclusão de ramificações de recursos na compilação mestre não é óbvia.

  2. Usando webhooks (exemplo).

    • Esta pode não ser a sua abordagem. Por exemplo, em Jenkins, apenas um tipo de pipeline oferece suporte à capacidade de salvar suas configurações no código-fonte. Ao usar webhooks, você precisa escrever seu próprio script para processá-los. Este script deverá ser colocado na interface Jenkins, que é difícil de manter.

  3. Escreva cronjob e adicione um cluster Kubernetes.

    • Gastar tempo escrevendo e apoiando.
    • O operador já funciona de forma semelhante, está documentado e tem suporte.

Obrigado pela sua atenção para o artigo. Link para o projeto no Github.

Fonte: habr.com

Adicionar um comentário