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 | нет | не задан (не проверено на практике) |
Исправление:
- Скопировать
regcredв namespace сертификата. - Задать
imagePullSecretsвpodTemplateHTTP-01 solver у ClusterIssuer:
solvers:
- http01:
ingress:
class: istio
podTemplate:
spec:
imagePullSecrets:
- name: regcred
- Удалить зависший 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'.
Проверить:
- Ответ шлюза на challenge-адрес (команда
curlвыше). Ответserver: istio-envoyс кодом503означает, что маршрута до solver нет или у Service нет endpoints. - Endpoints Service solver:
kubectl --context <ctx> -n <ns> get endpoints | grep cm-acme-http-solver. - Наличие HTTP-сервера на порту 80 в Gateway для этого хоста. Шаблон Gateway в
istio-configсоздаёт сервер на порту 80 для каждого хоста. Редирект HTTP→HTTPS на порту 80 ломает HTTP-01. - 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) исключены из алертов по имени.