Helmでインストールした Cert Manager をk8sマニフェストで再セットアップ

Let’s Encrypt 証明書の期限管理(最新化)は Cert Managerに任せっきりだったのですが、あるとき一部サイトの証明書更新がされないまま失効してしまいました。どうやら Helm を用いてセットアップしていた Cert Manager が古くなりすぎていたようで、アップデートをする必要がありました。

で、今回久しぶりに Helm を触ったため操作方法に戸惑ってしまいました。利用頻度が低いツールを使ってしまうと後々苦労するので、今回は Helm を利用せずにKubernetes へ直接デプロイする手順でセットアップすることにします。

前提

本記事のマニフェストにで登場する変数について、ここでは以下の通りとします。 なお、ここではKubernetes上で動かしていた「Redmine」を対象に操作しているためそれっぽい文字が頻出しますが、Redmineを前提とした手順ではなく汎用的なものです。アプリ名や、ツール名相当の意味と捉えてください。

意味
mynamespaceアプリケーションをデプロイするためのネームスペース
mysite.example.comサイトの公開FQDN
redmine-tls証明書。Ingressへ適用するためのSecret
redmine-certredmine-tlsを作成するための、cert-managerのリソース
your-main@example.comLet’s Encrypt で SSL 証明書作成要求をするための自身のメールアドレス

アップデート前の状態

再さっとアップに着手する前に現在の状態をメモ。

$ kubectl -n mynamespace describe Certificate redmine-tls   
Spec:
  Acme:
    Config:
      Domains:
        mysite.example.com
      Http 01:
        Ingress:        
        Ingress Class:  nginx
  Dns Names:
    mysite.example.com
  Issuer Ref:
    Kind:       ClusterIssuer
    Name:       letsencrypt-prod
  Secret Name:  redmine-tls
Status:
  Acme:
    Order:
      URL:  
  Conditions:
    Last Transition Time:  2019-12-18T07:33:25Z
    Message:               Failed to create new order: acme: urn:ietf:params:acme:error:rateLimited: Your ACME client is too old. Please upgrade to a newer version.
    Reason:                ValidateError
    Status:                False
    Type:                  Ready
    Last Transition Time:  2019-10-19T08:33:22Z
    Message:               Order validated
    Reason:                OrderValidated
    Status:                False
    Type:                  ValidateFailed
Events:
  Type     Reason          Age                    From          Message
  ----     ------          ----                   ----          -------
  Warning  ErrCreateOrder  2m22s (x757 over 12h)  cert-manager  Error creating order: acme: urn:ietf:params:acme:error:rateLimited: Your ACME client is too old. Please upgrade to a newer version.

動かしていた Cert Manager は v0.5.2. Upgrading | cert-manager によると v0.12 くらいまで出ているので、かなり古くなってしまった。

Upgrading from v0.5 to v0.6 | cert-manager

一度クリーン削除したほうがいいらしい。

$ helm search cert-manager
NAME               	CHART VERSION	APP VERSION	DESCRIPTION                  
stable/cert-manager	v0.5.2       	v0.5.2     	A Helm chart for cert-manager


$ helm list
NAME         	REVISION	UPDATED                 	STATUS  	CHART              	APP VERSION	NAMESPACE  
cert-manager 	1       	Sat Jan 12 23:34:05 2019	DEPLOYED	cert-manager-v0.5.2	v0.5.2     	kube-system
$ helm delete --purge cert-manager
release "cert-manager" deleted

$ helm list
NAME         	REVISION	UPDATED                 	STATUS  	CHART              	APP VERSION	NAMESPACE
(関係ないやつなので非表示)
$ kubectl delete crd \
    certificates.certmanager.k8s.io \
    issuers.certmanager.k8s.io \
    clusterissuers.certmanager.k8s.io

customresourcedefinition.apiextensions.k8s.io "certificates.certmanager.k8s.io" deleted
customresourcedefinition.apiextensions.k8s.io "issuers.certmanager.k8s.io" deleted
customresourcedefinition.apiextensions.k8s.io "clusterissuers.certmanager.k8s.io" deleted

続いて Fresh Install すればいい様子。なお、この操作をしても、説明にある通り各サービスに適用している(稼働中の)SSL証明書は消えなかったので、安心して実行可能。

Kubernetes | cert-manager

以下公式の説明通りですが、一部コメントしつつ記載。

Installing with regular manifests

$ kubectl create namespace cert-manager

Install the CustomResourceDefinitions and cert-manager itself

$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.12.0/cert-manager.yaml

... created のような実行ログが数十行でてくる

ここで CRD (Custom Resource Definition) が追加されるので、kubectl get certificate --all-namespaces とかが使えるようになる。

Permissionエラーが発生することがあるようで、以下の記載の通りにコマンドを打つ必要があるそう(GKE環境の場合)

Note: When running on GKE (Google Kubernetes Engine), you may encounter a ‘permission denied’ error when creating some of these resources. This is a nuance of the way GKE handles RBAC and IAM permissions, and as such you should ‘elevate’ your own privileges to that of a ‘cluster-admin’ before running the above command. If you have already run the above command, you should run them again after elevating your permissions:

$ kubectl create clusterrolebinding cluster-admin-binding \
    --clusterrole=cluster-admin \
    --user=$(gcloud config get-value core/account)

(今回、自分のケースにおいては不要でした。もともとGKEに対していろんな操作をしてきた経緯もあり。)

Verifying the installation

cert-manager 関連の Pod が起動しているかを確認します。

$ kubectl get pods --namespace cert-manager

NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-cainjector-58f48c4cb9-pkdzq   1/1     Running   0          11m
cert-manager-cb5f48858-gsbx4               1/1     Running   0          11m
cert-manager-webhook-74d98fdc7b-flx6v      1/1     Running   0          11m

Configuration

IssuerClusterIssuer の違い

An Issuer is a namespaced resource, and it is not possible to issue certificates from an Issuer in a different namespace. This means you will need to create an Issuer in each namespace you wish to obtain Certificates in.

If you want to create a single Issuer that can be consumed in multiple namespaces, you should consider creating a ClusterIssuer resource. This is almost identical to the Issuer resource, however is non-namespaced so it can be used to issue Certificates across all namespaces.

via Issuer | cert-manager

Issuer は Namespace ごとに作成する必要がある(裏返すと、Namespace ごとに異なるものを利用することができる)。一方で ClusterIssuer は Namespace をまたがって利用される。

その対象の Kubernetes クラスタがどのような編成(企業・組織・チーム・個人等)のポリシーで運営しているかだと思いますので、自身の用途に合うものを選べばいいと思います。今回は個人用クラスタなので ClusterIssuer で記載していきます。

$ kubectl apply -f ingress/cluster-issuer.yml

clusterissuer.cert-manager.io/letsencrypt-staging created
clusterissuer.cert-manager.io/letsencrypt-prod created

ingress/cluster-issuer.yml の中身:

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging # テスト用
spec:
  acme:
    email: "your-main@example.com"
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-staging
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: nginx
---
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod # 本番用
spec:
  acme:
    email: "your-main@example.com"
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: nginx

詳細はここを参考に ACME | cert-manager

Certificate

次は、Issuer に基づいて過各種サービス用の証明書を適用(作成)するもの。

$ kubectl -n mynamespace get certificate
No resources found.

まだ何もしていないので存在しない。

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: redmine-tls
  namespace: mynamespace
spec:
  commonName: mysite.example.com
  secretName: redmine-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer

spec.issuerRef にある kind については、消すと正しく動かず、20分くらい様子を見ていたものの Requested ステータスから繊維しなかった。(はず。いろいろ試行錯誤した部分があり記録が曖昧)。

ドキュメントにはデフォルト Issuer となっているので、意図的に変更する必要があるみたい。 Certificate Resources | cert-manager

manifestをデプロイ。

$ kubectl -n mynamespace apply -f redmine/certificate.yml
certificate.cert-manager.io/redmine-tls created

$ kubectl -n mynamespace get certificate
NAME          READY   SECRET        AGE
redmine-tls   False   redmine-tls   5s

redmine/certificate.yml の中身(ドキュメントの通りだけど一応):

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: redmine-cert
  namespace: mynamespace
spec:
  commonName: mysite.example.com
  secretName: redmine-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  - mysite.example.com

dnsNames をしっかり書かないといつまでも証明書作成が完了しないので注意

Helm利用時(移行前)のコンフィグには記載していたのが、再セットアップ用にコンフィグを見直した再、commonName だけあればいいんじゃないかと思いこんで削除してしまったのですが、それが理由で作成要求が完了せずに時間を要してしまいました。ドキュメントには以下の通り記載されています。

The dnsNames field specifies a list of Subject Alternative Names to be associated with the certificate. If the commonName field is omitted, the first element in the list will be the common name.

Certificates — cert-manager documentation

ほか、

Certificate Resources | cert-manager

  # At least one of a DNS Name, USI SAN, or IP address is required.
  dnsNames:
  - example.com
  - www.example.com

結果、ログがこうなればOK

Events:
  Type    Reason        Age                    From          Message
  ----    ------        ----                   ----          -------
  Normal  GeneratedKey  15m                    cert-manager  Generated a new private key
  Normal  Requested     15m                    cert-manager  Created new CertificateRequest resource "redmine-cert-114396219"
  Normal  Requested     7m46s                  cert-manager  Created new CertificateRequest resource "redmine-cert-387630196"
  Normal  Requested     3m19s                  cert-manager  Created new CertificateRequest resource "redmine-cert-2198555332"
  Normal  Issued        2m54s (x2 over 7m20s)  cert-manager  Certificate issued successfully

まとめ

cert-manager 関連の Pod のセットアップは1,2行で完了してしまい、それ以降のコンフィグレーションは Helm 利用時とも変わらないため、これについてはむしろ Helm を使わないほうが楽でさえありました。

今度はどこかの機会でアップデートも試さないといけないですね。

(参考)トラブルシュート

最初いつまでも証明書作成が完了せず(要求のままでストップする)、様々な切り分けをしたので順序が前後してしまっている可能性あり。 上記の手順でうまく完了しない場合は以下の点もチェックしてみましょう。

certificate イベント状況を見る(describe

$ kubectl -n mynamespace describe -f redmine/certificate.yml
...
Events:
  Type    Reason     Age   From          Message
  ----    ------     ----  ----          -------
  Normal  Requested  14s   cert-manager  Created new CertificateRequest resource "redmine-tls-61386848"

既存の証明書を消してみる

Events:
  Type    Reason        Age   From          Message
  ----    ------        ----  ----          -------
  Normal  GeneratedKey  15s   cert-manager  Generated a new private key
  Normal  Requested     15s   cert-manager  Created new CertificateRequest resource "redmine-tls-3517813469"

Helm でセットアップしたときにできている kube-system 内の secret を消してみる

$ kubectl get secret --all-namespaces | grep letsencrypt
cert-manager   letsencrypt-prod                                 Opaque                                1      140m
cert-manager   letsencrypt-staging                              Opaque                                1      140m
kube-system    letsencrypt-prod                                 Opaque                                1      369d
kube-system    letsencrypt-staging                              Opaque                                1      369d
$ kubectl -n kube-system delete secret letsencrypt-prod
$ kubectl -n kube-system delete secret letsencrypt-staging

証明書作成要求のステータスを見る

$ kubectl -n mynamespace describe CertificateRequest redmine-cert-114396219

(注)certificateの名前の語尾にくっつく数字は作成要求の都度違う様子。対象のEventログから確認可能(上述の通り)

Events:
  Type     Reason        Age    From          Message
  ----     ------        ----   ----          -------
  Warning  InvalidOrder  6m44s  cert-manager  The CSR PEM requests a commonName that is not present in the list of dnsNames. If a commonName is set, ACME requires that the value is also present in the list of dnsNames: "mysite.example.com" does not exist in []