doc_memo/kubernetes/k3s-lets-encrypt-cert-manager.md

13 KiB
Raw Blame History

Подключение менеджера сертификатов (cert-manager) Let's Encrypt к k3s

№ Установка cert-manager

Установим cert-manager из официального манифеста GitHub. Это автоматически добавит все необходимые компоненты, включая CRD (Custom Resource Definitions) и контроллеры:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.yaml

Это установит cert-manager версии 1.15.3 (предварительно надо проверь актуальную версию на GitHub cert-manager). Компоненты развернутся в namespace cert-manager.

Проверим установку:

kubectl get pods --namespace cert-manager

Увидим, что все поды в статусе Running:

NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-778cb8f45-gxtpf               1/1     Running   0          22m
cert-manager-cainjector-6d5854cdd8-w4d8x   1/1     Running   0          22m
cert-manager-webhook-78b57fb6bb-jljdx      1/1     Running   0          22m

Что это за поды:

  • cert-manager — основной контроллер cert-manager, который управляет созданием и обновлением сертификатов.
  • cert-manager-cainjector — контроллер, который автоматически добавляет CA-сертификаты в секреты и ресурсы Kubernetes.
  • cert-manager-webhook — контроллер, который обрабатывает запросы на валидацию и мутацию ресурсов cert-manager.

Проверим, что Custom Resource Definitions (CRDs) для cert-manager созданы:

kubectl get crd | grep cert-manager

Должны быть certificates.cert-manager.io, issuers.cert-manager.io, `clusterissuers.cert-manager.io и другие:

certificaterequests.cert-manager.io         2025-04-27T16:29:56Z
certificates.cert-manager.io                2025-04-27T16:29:56Z
challenges.acme.cert-manager.io             2025-04-27T16:29:56Z
clusterissuers.cert-manager.io              2025-04-27T16:29:56Z
issuers.cert-manager.io                     2025-04-27T16:29:56Z
orders.acme.cert-manager.io                 2025-04-27T16:29:57Z

Создание ClusterIssuer (Эмитент сертификатов для всего кластера)

Создадим ClusterIssuer для Let's Encrypt. Это позволит cert-manager автоматически запрашивать и обновлять сертификаты. Разместим его в отдельном файле ~/k3s/cert-manager/letsencrypt-clusterissuer.yaml:

mkdir -p ~/k3s/cert-manager
nano ~/k3s/cert-manager/letsencrypt-clusterissuer.yaml

И вставим в него следующий код (не забудьте заменить <my@email.com> на свой email):

apiVersion: cert-manager.io/v1  # Версия API для cert-manager, v1 — текущая стабильная.
kind: ClusterIssuer             # Тип ресурса. ClusterIssuer — глобальный объект для выдачи сертификатов
                                # всему кластеру (в отличие от Issuer, который ограничен namespace).
metadata:
  name: letsencrypt-prod        # Имя `ClusterIssuer`, на него будем ссылаться в манифестах (например, в Certificate).
spec:
  acme:                         # Используется ACME-протокол (Automated Certificate Management Environment) Let's Encrypt.
    server: https://acme-v02.api.letsencrypt.org/directory  # URL сервера Let's Encrypt (боевой).
    email: <my@email.com>       # Логин-клиент Let's Encrypt (раньше так же использовался для уведомлений).
    privateKeySecretRef:
      name: letsencrypt-prod    # Имя секрета, где будет храниться приватные ключи для ACME-аккаунта.
    solvers:                    # Список способов "решения" ACME-задач для подтверждения владения доменом.
    - http01:                   # Используется HTTP-01 challenge (подтверждение через HTTP-запросы).
        ingress:
          class: traefik        # Указывает, что ingress-контроллер Traefik будет обрабатывать HTTP-01 challenge
                                # (создает временные маршруты для `/.well-known/acme-challenge`) для подтверждения
                                # владения доменом.

Применим манифест:

kubectl apply -f ~/k3s/cert-manager/letsencrypt-clusterissuer.yaml

Проверим, что ClusterIssuer создан:

kubectl get clusterissuer

Увидим что-то вроде:

NAME               READY   AGE
letsencrypt-prod   True    27s

Проверим статус:

kubectl describe clusterissuer letsencrypt-prod

Увидим что-то вроде:

...
...
Status:
  Acme:
    Last Private Key Hash:  тут-хранится-хэш-приватного-ключа
    Last Registered Email:  <my@email.com>
    Uri:                    https://acme-v02.api.letsencrypt.org/acme/acct/2365771147
  Conditions:
    Last Transition Time:  2025-04-27T17:00:56Z
    Message:               The ACME account was registered with the ACME server
    Observed Generation:   1
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
...

Должны быть Status: True и Reason: ACMEAccountRegistered, что означает, что ClusterIssuer успешно зарегистрирован и готов к использованию.

Создание сертификата

Эта общая часть, объясняющая как создавать манифесты для выпуска сертификатов.

В своем манифесте, например, в манифесте пода, создается блок (данный кусочек взят из манифеста пода Gitea и он находится в пространстве имен gitea):

---
# Манифест для получения сертификата от Let's Encrypt
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: gitea-tls     # Имя сертификата `gitea-tls`
  namespace: gitea    # В пространстве имен `gitea`
spec:
  secretName: gitea-tls   # Имя секрета для сертификата
  dnsNames:                   # Доменные имена для сертификата...
    - git.cube2.ru
  issuerRef:                  # Эмитент сертификата (Issuer)
    name: letsencrypt-prod      # Имя ClusterIssuer, который отвечает за получение сертификата: `letsencrypt-prod`
    kind: ClusterIssuer         # Тип эмитента

После применения манифеста, можно проверить статус сертификата:

kubectl get certificate -A

Увидим что-то вроде:

NAMESPACE   NAME                 READY   SECRET               AGE
ab-shelf    audiobookshelf-tls   False   audiobookshelf-tls   90m
gitea       gitea-tls            True    gitea-tls            66s

Тут можно заметить, что сертификат audiobookshelf-tls не готов (False), а gitea-tls готов (True).

Как проверить статус сертификата и причину его неготовности (на примере audiobookshelf-tls):

kubectl describe certificate audiobookshelf-tls -n ab-shelf

Увидим что-то вроде:

Name:         audiobookshelf-tls
Namespace:    ab-shelf
...
...
...
Spec:
  Dns Names:
    <тут-будет-доменное-имя-для-которого-выдается-сертификат>
  Issuer Ref:
    Kind:       ClusterIssuer
    Name:       letsencrypt-prod
  Secret Name:  audiobookshelf-tls
Status:
  Conditions:
    Last Transition Time:    2025-04-27T17:13:32Z
    Message:                 The certificate request has failed to complete and will be retried: Failed to wait for order resource "audiobookshelf-tls-1-722111375" to become ready: order is in "errored" state: Failed to create Order: 429 urn:ietf:params:acme:error:rateLimited: too many certificates (5) already issued for this exact set of domains in the last 168h0m0s, retry after 2025-04-28 19:30:09 UTC: see https://letsencrypt.org/docs/rate-limits/#new-certificates-per-exact-set-of-hostnames
    Observed Generation:     1
    Reason:                  Failed
    Status:                  False
    Type:                    Issuing
    Last Transition Time:    2025-04-27T17:13:29Z
    Message:                 Issuing certificate as Secret does not exist
    Observed Generation:     1
    Reason:                  DoesNotExist
    Status:                  False
    Type:                    Ready
...
...
...

Как видно, причина неготовности сертификата audiobookshelf-tls в том, что уже превышен лимит на количество сертификатов (5) для данного доменного имени за последние 168 часов (7 дней). Указано время после которого можно повторить запрос.

По идее запрос на повторный сертификат будет отправлен автоматически, но это может произойти спустя несколько часов после разрешенного времени (учтите что время указывается в UTC, делайте поправку по своему локальному времени).

Чтобы ускорить процесс, можно удалить сертификат и создать его заново (на примере audiobookshelf-tls):

kubectl delete certificate audiobookshelf-tls -n ab-shelf

А затем повторно принять манифест, в котором у вас находится kind: Certificate

Заметка
Пока Let's Encrypt не выдал сертификат, Traefik будет работать по SSL (https) на самоподписанном сертификате. Можно открыть анонимное окно браузера, согласится с предупреждениями безопасности и пользоваться сайтом.

Когда же все хорошо (на примере gitea-tls для домены git.cube2.ru, сайт которого вы сейчас читаете):

kubectl describe certificate gitea-tls -n gitea

Увидим что-то вроде:

Name:         gitea-tls
Namespace:    gitea
...
...
...
Spec:
  Dns Names:
    git.cube2.ru
  Issuer Ref:
    Kind:       ClusterIssuer
    Name:       letsencrypt-prod
  Secret Name:  gitea-tls
Status:
  Conditions:
    Last Transition Time:  2025-04-27T18:43:02Z
    Message:               Certificate is up to date and has not expired
    Observed Generation:   1
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2025-07-26T17:44:29Z
  Not Before:              2025-04-27T17:44:30Z
  Renewal Time:            2025-06-26T17:44:29Z
...
...
...

Видим что Status: True, Reason: Ready, а также время дату/время с которого сертификат действителен (время Not Before) и до которого он действителен (время Not After), а также дату/время, когда сертификат будет автоматически обновлен (время Renewal Time).

ВАЖНО: время указывается в UTC, делайте поправку на свой часовой пояс.