Отладка при неудачном деплое в Kubernetes c Helm Hook в GitLab CI

Используемое ПО: Gitlab CI, Helm, Docker, Kubernetes Цель: в случае неудачного деплоя, получить информацию о проблеме в CI, а не ходить в k8s или Kibana руками и не смотреть логи подов Вводная: для деплоя в кластер Kubernetes используется Helm версии 3 и Gitlab CI, отдельный сервер для сборки (не DinD), с которого доступен Kubernetes API. Данный вариант основан на ключе Helm --atomic, который осуществляет автоматический откат неудавшегося релиза. Идея в том, чтобы не запускать отдельно helm rollback, а использовать Hook с аннотацией pre-delete, которая перед удалением релиза запустит под и соберет логи с NOT READY подов, а далее с помощью after_script выведет данные логи в CI. К реализации... Helm 3 использует реквизиты подключения kubectl, соответственно Helm hook должен будет иметь следующие права Service Account в своем namespace:
kubectl -n NS get role ROLE -o yaml
rules:
- apiGroups:
  - "" 
  - extensions
  - apps
  - batch
  - certmanager.k8s.io
  resources:
  - '*'
  verbs:
  - '*'
Dockerfile: https://github.com/sfnagg/k8s-helm-logs/blob/master/Dockerfile Скрипты kubelogin.sh и hooklog.sh включены в образ: https://github.com/sfnagg/k8s-helm-logs/tree/master/scripts
  • kubelogin.sh - выполняем логин в кластер с токеном Service Account
  • hooklog.sh - запускаем выборку всех подов в состоянии NOT READY, берем первый под и выводим его логи
  • Собранный образ, готовый к работе: https://hub.docker.com/r/sfnagg/k8s-helm-logs Helm hook создается в каталоге templates, для запуска ничего более не требуется, так как при ключе --atomic происходит именно удаление релиза, а не rollback, нужно использовать аннотацию "helm.sh/hook": pre-delete Документация по хукам Helm: https://helm.sh/docs/topics/charts_hooks/
    .helm/templates/job_helm_logs.yaml
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: "helm-release-logs-{{ .Chart.Name }}"
      annotations:
        "helm.sh/hook": pre-delete
      labels:
        app: {{ .Chart.Name }}
        chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
    spec:
      activeDeadlineSeconds: 60
      template:
        metadata:
          name: "helm-release-logs-{{ .Chart.Name }}"
          labels:
            app: {{ .Chart.Name }}
            type: helmlogs
        spec:
          restartPolicy: Never
          containers:
          - name: helmlogs
            image: sfnagg/k8s-helm-logs:v1.1.7
            imagePullPolicy: Always
            command:
            - /usr/local/bin/kubelogin.sh
            args:
            - /usr/local/bin/hooklog.sh
            - {{ .Release.Namespace }}
            - {{ .Release.Name }}
            env:
            - name: K8S_API_URL
              value: {{ .Values.k8s_config.api_url }}
            - name: K8S_CI_TOKEN
              value: {{ .Values.k8s_config.ci_token }}
    
    Пример деплоя Gitlab CI Комментарии: в примере приведен деплой с gitlab-runner executor shell, правильнее использовать docker в этом варианте, но я решил показать более сложный вариант, возможно пригодится в других ситуациях. Переменная --timeout в продакшене обычно устанавливается от 100 до 300 секунд. K8S_API_URL - Kubernetes API (kubectl get ep kubernetes) K8S_CI_TOKEN - это токен Service Account, который хранится в переменных Gitlab
    .gitlab-ci.yml
    variables:
      K8S_API_URL: https://10.222.0.172:6443
    
    stages:
      - build
      - push
      - deploy
    
    build:
      stage: build
      script:
        - export DOCKER_BUILDKIT=1
        - docker build -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID --cache-from $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID --build-arg BUILDKIT_INLINE_CACHE=1 .
    
    push:
      stage: push
      before_script:
        - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
      script:
        - docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
      only:
        - master
    
    deploy:
      after_script:
        - docker run
          --rm
          -e "K8S_API_URL=$K8S_API_URL"
          -e "K8S_CI_TOKEN=$K8S_CI_TOKEN"
          -e "CI_PROJECT_PATH_SLUG=$CI_PROJECT_PATH_SLUG"
          -e "CI_ENVIRONMENT_NAME=$CI_ENVIRONMENT_NAME"
          sfnagg/k8s-helm:v3.2.1
          /bin/bash -c
          'kubectl config set-cluster k8s --insecure-skip-tls-verify=true --server="$K8S_API_URL" &&
          kubectl config set-credentials ci --token="$K8S_CI_TOKEN" &&
          kubectl config set-context ci --cluster=k8s --user=ci &&
          kubectl config use-context ci &&
          kubectl -n $CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_NAME logs -ltype=helmlogs --tail=-1'
      stage: deploy
      environment:
        name: production
      variables:
        GIT_STRATEGY: none
      script:
        - docker run
          --rm
          -v $PWD/.helm:/.helm
          -e "K8S_API_URL=$K8S_API_URL"
          -e "K8S_CI_TOKEN=$K8S_CI_TOKEN"
          -e "CI_PROJECT_PATH_SLUG=$CI_PROJECT_PATH_SLUG"
          -e "CI_ENVIRONMENT_NAME=$CI_ENVIRONMENT_NAME"
          -e "CI_REGISTRY=$CI_REGISTRY"
          -e "CI_PROJECT_NAMESPACE=$CI_PROJECT_NAMESPACE"
          -e "CI_PROJECT_NAME=$CI_PROJECT_NAME"
          -e "CI_COMMIT_REF_SLUG=$CI_COMMIT_REF_SLUG"
          -e "CI_PIPELINE_ID=$CI_PIPELINE_ID"
          sfnagg/k8s-helm:v3.2.1
          /bin/bash -c
          'kubectl config set-cluster k8s --insecure-skip-tls-verify=true --server="$K8S_API_URL" &&
          kubectl config set-credentials ci --token="$K8S_CI_TOKEN" &&
          kubectl config set-context ci --cluster=k8s --user=ci &&
          kubectl config use-context ci &&
          helm upgrade --install "$CI_PROJECT_PATH_SLUG" .helm
            --set image=$CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME
            --set imageTag=$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
            --set k8s_config.api_url=$K8S_API_URL
            --set k8s_config.ci_token=$K8S_CI_TOKEN
            --wait
            --timeout 40s
            --atomic
            --debug
            --namespace $CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_NAME'
      only:
        - master
    
    Вывод отладки в CI: