Перейти к содержанию

cert-manager и ACME: диагностика и типовые проблемы

Общий документ для алертов группы cert-manager-alerts. Страницы отдельных алертов ссылаются на разделы этого документа.

Устройство

Кластер Контекст kubectl Версия cert-manager Deployment контроллера Grafana
prod yc-sarex-prod v1.20.0 cert-manager grafana-prod-dashboard.sarex.io
prod-multiaz yc-sarex-prod-multiaz 1.16.1 cert-manager-controller grafana-prod-dashboard.sarex.io, кластер prod-multiaz
stage yc-sarex-stage 1.16.1 cert-manager-controller grafana.dev.stage.sarex.io

Дашборд: Kubernetes / Cert-Manager (uid cert-manager). Метрики multiaz пишутся в хранилище prod, поэтому prod-multiaz выбирается на prod-дашборде переменной «Кластер».

Сертификаты шлюза находятся в namespace istio-system и выпускаются через ClusterIssuer letsencrypt-issuer-istio (Let's Encrypt, HTTP-01, ingress.class: istio). Объекты Certificate для prod и stage описаны в репозитории istio-config (certManager.certificates в .helm/values.yaml) и принадлежат helm-релизу istio-config.

Перевыпуск начинается за renewBefore до истечения. У большинства сертификатов prod задано 360h (15 дней). Если renewBefore не задан, cert-manager начинает перевыпуск за треть срока действия: для сертификата Let's Encrypt на 90 дней это 30 дней.

Цепочка выпуска

Certificate → CertificateRequest → Order → Challenge → solver: Pod + Service + Ingress (namespace сертификата)
Let's Encrypt → GET http://<domain>/.well-known/acme-challenge/<token> → istio-ingressgateway → Service solver → Pod solver

Solver-под cm-acme-http-solver-* создаётся в namespace сертификата, а не в cert-manager. imagePullSecrets контроллера на него не распространяются. Образ solver находится в приватном cr.yandex/crp3ccidau046kdj8g9q.

Здоровый HTTP-01 Challenge проходит за секунды или минуты, после чего Challenge и solver-под удаляются. Solver-под, который существует дольше нескольких минут, означает зависший выпуск.

Команды диагностики

Все команды выполняются с явным --context. Ниже <ctx> — контекст кластера, <ns> — namespace сертификата (обычно istio-system), <cert> — имя Certificate.

Состояние всей цепочки:

kubectl --context <ctx> -n <ns> get certificate,certificaterequest,order,challenge | grep <cert>
kubectl --context <ctx> -n <ns> describe certificate <cert>
kubectl --context <ctx> -n <ns> describe challenge <challenge>

Поле Status.Reason у Challenge содержит текст ошибки cert-manager или Let's Encrypt.

Solver-поды и их состояние:

kubectl --context <ctx> -n <ns> get pods,svc,ingress | grep cm-acme-http-solver
kubectl --context <ctx> -n <ns> describe pod <solver-pod>

Логи контроллера по сертификату:

kubectl --context <ctx> -n cert-manager logs deploy/<deployment> --since=1h | grep <cert>

Проверка challenge-адреса снаружи (любой токен; solver отвечает на свой токен, на чужой ожидается 404 от solver, а не 503 от шлюза):

curl -sv http://<domain>/.well-known/acme-challenge/test 2>&1 | grep -E '^< HTTP|^< server'

Принудительный запуск выпуска:

cmctl renew <cert> -n <ns> --context <ctx>

После неудачной попытки cert-manager повторяет выпуск с растущей паузой, вплоть до 32 часов. cmctl renew запускает попытку сразу. Причину неудачи необходимо устранить до запуска, иначе попытка повторит ту же ошибку и увеличит счётчик неудачных валидаций в Let's Encrypt.

Типовые проблемы

1. Solver-под не скачал образ

Признаки: solver-под в ImagePullBackOff или ErrImagePull, в событиях пода 403 Forbidden от cr.yandex. У Service solver нет endpoints, шлюз на challenge-адресе отвечает 503 (UH в логе istio-ingressgateway). Challenge находится в pending.

Причина: у namespace сертификата нет доступа к приватному реестру. Именно так истёк idp-prod-multiaz-tls в сентябре 2026 года.

Состояние по кластерам на 2026-09-30:

Кластер regcred в istio-system imagePullSecrets в ClusterIssuer
prod-multiaz есть задан
prod нет не задан (образ скачивается, предположительно через сервисный аккаунт нод)
stage нет не задан (не проверено на практике)

Исправление:

  1. Скопировать regcred в namespace сертификата.
  2. Задать imagePullSecrets в podTemplate HTTP-01 solver у ClusterIssuer:
solvers:
  - http01:
      ingress:
        class: istio
        podTemplate:
          spec:
            imagePullSecrets:
              - name: regcred
  1. Удалить зависший Order (Challenge и solver-под удалятся вместе с ним) либо выполнить cmctl renew.

2. Шлюз не доводит запрос до solver

Признаки: solver-под в Running, в Status.Reason у Challenge текст вида did not get expected response when querying endpoint или wrong status code '503' / '404'.

Проверить:

  1. Ответ шлюза на challenge-адрес (команда curl выше). Ответ server: istio-envoy с кодом 503 означает, что маршрута до solver нет или у Service нет endpoints.
  2. Endpoints Service solver: kubectl --context <ctx> -n <ns> get endpoints | grep cm-acme-http-solver.
  3. Наличие HTTP-сервера на порту 80 в Gateway для этого хоста. Шаблон Gateway в istio-config создаёт сервер на порту 80 для каждого хоста. Редирект HTTP→HTTPS на порту 80 ломает HTTP-01.
  4. VirtualService с prefix: / на тот же хост, привязанный к тому же Gateway, может перехватить запрос раньше Ingress solver.

3. Домен не указывает на шлюз или недоступен

Признаки: в Status.Reason у Challenge текст failed to perform self check GET request ... context deadline exceeded или ошибка DNS.

Проверить:

dig +short <domain>
kubectl --context <ctx> -n istio-system get svc istio-ingressgateway

Адрес из DNS должен совпадать с внешним адресом istio-ingressgateway этого кластера. Если домен больше не используется, Certificate следует удалить (см. раздел 7).

4. Ограничения Let's Encrypt

Признаки: 429 urn:ietf:params:acme:error:rateLimited в Status.Reason у Order или Challenge, рост ответов 429 на панели «Запросы к ACME API по статусу ответа».

Основные лимиты: 5 неудачных валидаций на домен в час, 5 одинаковых сертификатов в неделю, 50 сертификатов на зарегистрированный домен в неделю. Действие: устранить причину неудач и дождаться окна, указанного в тексте ошибки. Повторные ручные запуски до истечения окна продлевают блокировку.

5. Challenge от старой попытки

Признаки: Challenge в expired, invalid или errored (часто с текстом Expired authorization), при этом Certificate в Ready и срок действия в норме.

Причина: объект остался от неудачной попытки, следующая попытка прошла через другой Order. На текущий сертификат не влияет.

Проверить, что Order этой попытки не в работе, и удалить Challenge:

kubectl --context <ctx> -n <ns> get order | grep <cert>
kubectl --context <ctx> -n <ns> delete challenge <challenge>

6. Два Certificate на один Secret

Признаки: у одного из Certificate Ready=False с причиной IncorrectCertificate и сообщением Secret was issued for "<другой certificate>".

Причина: два объекта Certificate указывают на один secretName. Они перезаписывают Secret друг друга, перевыпуск становится непредсказуемым.

Действие: оставить один Certificate, второй удалить из values istio-config и из кластера.

7. Сертификат ни разу не выпускался

Признаки: Ready=False с причиной DoesNotExist, Secret отсутствует, в метриках срок действия равен 0. Challenge может висеть в pending месяцами.

Если домен не используется, удалить Certificate. Связанные CertificateRequest, Order и Challenge удалятся каскадно. Если Certificate создан helm-релизом (meta.helm.sh/release-name в аннотациях), сначала удалить его из values соответствующего репозитория, иначе следующий деплой создаст его заново.

kubectl --context <ctx> -n <ns> get certificate <cert> -o jsonpath='{.metadata.annotations}'
kubectl --context <ctx> -n <ns> delete certificate <cert>

Ограничения мониторинга

Метрика certmanager_certificate_challenge_status существует только в cert-manager v1.20.0 (prod). В prod-multiaz и stage (1.16.1) её нет, поэтому алерты AcmeChallengeStuck, AcmeChallengeStuckLong и AcmeChallengeFailed там не срабатывают. Зависший выпуск в этих кластерах проявится через CertificateNotReady и алерты по сроку действия, а также в таблице «Solver-поды, которые не запустились» на дашборде.

Два давно не работающих сертификата prod (landing-service-ingress-cert-name, preprod-sarex-ingress-cert-name) исключены из алертов по имени.