ついSSL/TLS証明書の更新を忘れちゃう人へ――Kubernetesの「cert-manager」で始める証明書管理自動化入門Cloud Nativeチートシート(29)

Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する連載。今回は、cert-managerを利用したIngressによる自己署名証明書とLet's Encryptで発行した証明書の利用方法を解説し、Gateway APIでcert-managerを利用する方法を紹介する。

» 2024年10月25日 05時00分 公開
[岡本隆史株式会社NTTデータグループ]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

もう、手動で証明書を作成、取得、設定する世界には戻れない

 今やWeb公開で必須となったSSL(Secure Sockets Layer)/TLS(Transport Layer Security)ですが、素の「Kubernetes」において「Ingress」や「Gateway API」でSSL/TLS証明書を利用する場合、自分で証明書を作成し、Secretリソースとして定義してIngressやGateway APIに設定する必要があります。オープンソースソフトウェア(OSS)の「OpenSSL」といった証明書の作成に慣れていない人や初心者にとっては煩雑な作業となり、証明書の管理方法などを勉強する必要もあります。SSL/TLS証明書には有効期限が設けられており、証明書の更新を忘れてシステム障害が発生するといったことも起こるでしょう。

 そこで「cert-manager」を利用すると、IngressやGateway APIに設定されたホスト名から自動的に自己署名証明書を作成、設定してくれたり、「Let's Encrypt」などのCA(Certificate Authority、認証局)にSSL/TLS証明書の生成を依頼、設定してくれたりするので、初心者でも簡単に証明書を扱えます。また、自動的に証明書も更新してくれるので、煩雑な証明書管理や証明書の更新忘れから解放されます。

 Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する本連載「Cloud Nativeチートシート」。今回は、cert-managerを利用したIngressによる自己署名証明書とLet's Encryptで発行した証明書の利用方法を解説し、Gateway APIでcert-managerを利用する方法を紹介します。Let's Encryptの利用については、No-IPを用いたドメイン取得手順とIP設定も分かりやすく紹介します。

cert-managerとは、5つの特徴

 cert-managerは、KubernetesでTLS証明書の管理ツールです。下記のような特徴を持っています。

  1. 自己署名証明書の作成:インターネット非接続環境や開発環境で使える自己署名証明書を自動的に作成できる。本稿では紹介しないが、独自のCAを利用した証明書発行にも対応している
  2. SSL/TLS証明書発行機関からの証明書の自動取得:Let's EncryptなどのACME HTTP-01をサポートしたレジストラから自動的に証明書を取得する
  3. 証明書の自動設定:IngressやGateway APIといったリソースに作成、取得した証明書を自動的に設定(Secretリソースを作成)する
  4. 証明書の自動更新:証明書には有効期限があり、手動での運用では証明書の有効期限が切れるインシデントが発生することがある。cert-managerを利用すると証明書を自動的に更新してくれるので、証明書の期限切れを防ぐことがでる
  5. 設定が簡単:必要な準備をしていれば、リソースに対してアノテーションを1行追加するだけで、自動的に証明書を設定してくれる


本稿の前提条件

 本稿は、KubernetesクラスタとIngressもしくはGateway APIが利用できることを前提としています。また、Let's Encryptを利用する解説では、Let's Encryptが証明書生成時にIngressやGatewayにアクセスしてドメインの存在を確認するので、Ingressリソースや、Gatewayリソースがインターネットから接続できる必要があるのでご注意ください。

 cert-managerはIngressやGateway APIのコントローラの実装に依存せず、本稿で利用する環境以外でも動作しますが、本稿では下記の環境で動作を確認しています。

コンポーネント 利用ソフトウェア
Kubernetesクラスタ 「k3s」上のKubernetes v1.30.5
Ingress Ingress Nginx」のv1.11.2
Gateway API Traefik」のv3.1.5

 もしどうしてもうまく動作しない場合は、IngressやGateway APIに上記のものを利用してみてください。

事前準備

cert-managerのインストール

 cert-managerは下記コマンドでインストールします。

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

 下記のように「Pod」(コンテナの集合)が起動していれば、準備完了です。

$ kubectl get pods -ncert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-7fbbc65b49-p6z64              1/1     Running   0          12m34s
cert-manager-cainjector-6664fc84f6-nwb82   1/1     Running   0          12m34s
cert-manager-webhook-59598898fd-wpvht      1/1     Running   0          12m34s

テスト用Podの作成

 本稿では、テスト用に「Nginx」のPodを作成します。Ingressの自己署名証明書利用、Let's Encryptによる証明書利用、Gateway API利用の各シナリオで、このテスト用Podを利用します。ここでは、作業用に「test」ネームスペースを作成し、Podを起動します。

 まずネームスペースを作成し、コンテキストにネームスペースを設定しておきます。

$ kubectl create ns test
namespace/test created
$ kubectl config set-context --current --namespace=test
Context "mycluster" modified.

 Webサイトを公開するためのテスト用のNginxを起動します。起動時に「--expose」オプションでNginxにアクセスできるServiceも作成しておきます。

$ kubectl run --image=nginx nginx --port=80 --expose --labels=app=web
service/nginx created
pod/nginx created

 PodとServiceが作成されているかどうか確認します。

$ kubectl get pods --show-labels
NAME    READY   STATUS    RESTARTS   AGE   LABELS
nginx   1/1     Running   0          70s   app=web
$ kubectl get svc
NAME    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   10.96.79.217   <none>        80/TCP    8s

 テスト用のPodとServiceが作成されていれば、準備完了です。

自己署名証明書のIngressでの利用

 ここからは、自己署名証明書をIngressで利用する方法を紹介します。

ClusterIssuerの作成

 「Issuer」は、証明書を発行するリソースです。自己署名証明書を発行するIssuer、Let's Encryptで証明書を発行するIssuerなどを作成し、用途によって使い分けることができます。リソースとしては、各ネームスペースで利用する「Issuer」と全てのネームスペースで共通して利用できる「ClusterIssuer」があります。

 本稿では、証明書を発行するClusterIssuerを利用します。自己署名証明書用のClusterIssuerを作るには、次のYAMLを用意します。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-clusterissuer
spec:
  selfSigned: {}
selfsigned-clusterissuer.yaml

 このYAMLをデプロイすればOKです。

$ kubectl apply -f selfsigned-clusterissuer.yaml

ClusterIssuerの確認

 上記のClusterIssuerが生成されているかどうか、念のため確認します。

$ kubectl get clusterissuers
NAME                        READY   AGE
selfsigned-clusterissuer    True    20s

 ClusterIssuerが作成されていれば準備完了です。ClusterIssuerはクラスタで1つ用意しておけば使い回せるので、次回から作業を省略できます。

自己署名証明書の利用

 自己署名証明書を利用したIngressの設定方法を紹介します。Let's Encryptを利用した暗号化については、「Let's Encryptによる証明書の作成」をご覧ください。

Ingressの作成

 cert-managerで自己署名証明書を利用するIngressを作成しましょう。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress-selfsign
  annotations:
    cert-manager.io/cluster-issuer: selfsigned-clusterissuer
spec:
  tls:
  - hosts:
      - nginx.internal
    secretName: nginx-ingress-selfsign-tls
  rules:
  - host: nginx.internal
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80
selfsign-ingress.yaml

 cert-managerを利用するには、通常のIngressに、下記のように「selfsigned-clusterissuer」を指定したアノテーションを追加するだけでOKです。

  annotations:
    cert-manager.io/cluster-issuer: selfsigned-clusterissuer

HTTPSでの接続確認

 「curl」コマンドで動作を確認してみます。Ingressのエンドポイントを指定して、アクセスします。自己署名証明書なので、SSL/TLS証明書の検証を無効にする「-k」オプションが必要なことに注意してください。

$ curl -H "Host: nginx.internal" https://localhost -k
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
 
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
 
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

 証明書やSecretを作成する必要もなく、HTTPS通信が可能になったと思います。下記のコマンドで証明書を確認します。「nginx.internal」が「Subject Alternative Name」に設定されていることを確認できます。

$ openssl s_client -connect nginx.internal:443  -servername nginx.internal  |openssl x509 -text
depth=0
verify error:num=18:self-signed certificate
verify return:1
depth=0
verify return:1
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Alternative Name: critical
                DNS:nginx.internal ★

cert-managerによって生成された証明書の確認

 生成された証明書を確認してみましょう。

$ kubectl get secret
NAME                         TYPE                DATA   AGE
nginx-ingress-selfsign-tls   kubernetes.io/tls   3      7m47s

 Ingressのsecretに設定した「nginx-ingress-selfsign-tls」が作成されているのが分かります。Secretの中身を見ると、証明書が設定されていることが分かります。

$ kubectl get secret -oyaml nginx-ingress-selfsign-tls
apiVersion: v1
data:
  ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJ...
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZ...
  tls.key: LS0tLS1CRUdJTiBSU0EgUFJ...
kind: Secret
metadata:
  annotations:
    cert-manager.io/alt-names: nginx.internal
    cert-manager.io/certificate-name: nginx-ingress-selfsign-tls
    cert-manager.io/common-name: ""
    cert-manager.io/ip-sans: ""
    cert-manager.io/issuer-group: cert-manager.io
    cert-manager.io/issuer-kind: ClusterIssuer
    cert-manager.io/issuer-name: selfsigned-clusterissuer
    cert-manager.io/uri-sans: ""
  creationTimestamp: "2024-09-14T02:41:58Z"
  labels:
    controller.cert-manager.io/fao: "true"
  name: nginx-ingress-selfsign-tls

 「annotaions」に、「cert-manager.io」で始まるアノテーションが幾つか追加されています。アノテーションを見ると、ホスト名(alt-names)や、どのIssuerから発行されたか(issuer-name)などが分かり、cert-managerで生成されたSecretであることが分かるようになっています。

 次に証明書情報を確認するCertificateリソースを確認してみます。下記の通りに実行すると、Certificateリソースが作成されていることを確認できます。

$ kubectl get certificate
NAME                             READY   SECRET                           AGE
nginx-ingress-selfsign-tls       True    nginx-ingress-selfsign-tls       3m20s

 Certificateリソースの中身を見てみます。

$ kubectl get certificate nginx-ingress-selfsign-tls -oyaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  creationTimestamp: "2024-09-14T02:41:58Z"
  generation: 1
  name: nginx-ingress-selfsign-tls
  namespace: test
  ownerReferences:
  - apiVersion: networking.k8s.io/v1
    blockOwnerDeletion: true
    controller: true
    kind: Ingress
    name: nginx-ingress-selfsign
    uid: d19458ad-d98f-4e03-8102-70b6e1c69efe
  resourceVersion: "1772036"
  uid: 0f4e69e8-9def-4b43-91b9-883de3c6539a
spec:
  dnsNames:
  - nginx.internal
  issuerRef:
    group: cert-manager.io
    kind: ClusterIssuer
    name: selfsigned-clusterissuer
  secretName: nginx-ingress-selfsign-tls    【1】
  usages:
  - digital signature
  - key encipherment
status:
  conditions:
  - lastTransitionTime: "2024-09-14T02:41:58Z"
    message: Certificate is up to date and has not expired
    observedGeneration: 1
    reason: Ready
    status: "True"
    type: Ready
  notAfter: "2024-12-13T02:41:58Z"
  notBefore: "2024-09-14T02:41:58Z"
  renewalTime: "2024-11-13T02:41:58Z"       【2】
  revision: 1

 secretName【1】に先ほど確認した「Secret nginx-ingress-selfsign-tls」と記載されていることが分かります。また、cert-managerは証明書を自動更新する機能がありますが、renewalTime【2】で次回証明書の自動更新日時を確認できます。自動更新日時が来たら自動的に証明書を更新してくれます。

Let's Encryptによる証明書の作成

 自己署名証明書は、あくまでも「簡易的な証明書」なので、安全に通信できるとは限りません。安全な通信には、認証局に署名された証明書を利用できます。証明書を認証局から取得する手順は煩雑ですが、HTTP-01やDNS-01といった証明書の自動発行プロトコルに対応した認証局を利用すると、簡単に証明書を発行できます。

 ここからは、Let's Encryptを利用して証明書を自動的に発行してみます。

ClusterIssuerの作成(Let's Encrypt)

 まずは、Let's Encryptの証明書を発行するClusterIssuerを定義します。利用しているIngressコントローラのIngressClass名を確認します。

$ kubectl get ingressclass
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       10s

 Ingress Classが「nginx」となっていることを確認できました。

 次に、「letsencrypt-clusterissuer.yaml」を作成します。「email」には、ご利用のメールアドレスを入力してください。ここでは「class」にnginxを指定していますが、別のIngressの場合は、nginxの部分を書き換えてください。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    privateKeySecretRef:
      name: letsencrypt-prod-key
    server: https://acme-v02.api.letsencrypt.org/directory
    email:  {メールアドレス}
    solvers:
    - http01:
       ingress:
         class: nginx
letsencrypt-clusterissuer.yaml

 作成したYAMLファイルをKubernetesクラスタに適用してClusterIssuerを作成します。

$ kubectl apply -f letsencrypt-clusterissuer.yaml

 上記のClusterIssuerが生成されているかどうか、念のため確認します。

$ kubectl get clusterissuers
NAME                        READY   AGE
letsencrypt-prod            True    1m10s

 自己署名証明書のClusterIssuerと同じく、クラスタで1つ作成しておけばよいので、次回からは作業を省略できます。

(No-IPによる)ドメイン取得

 Let's Encryptで証明書を利用するには、ドメインレジストラ、もしくは「Amazon Route 53」といったクラウドサービスによって発行されたドメインが必要になります。

 ここでは、「Dynamic DNS」によるドメインを無料で簡単に発行できる「No-IP」を利用してドメインを発行し、証明書を取得します。既にドメインをお持ちの方はスキップしてください。

 No-IPのWebサイトで登録してログインします。Googleアカウントを利用してログインすると、NO-IPのアカウントを作成することなく簡単にログインできます。ログインしたら、ドメイン取得(「Create Hostname」)を選択します。

ドメイン取得(「Create Hostname」)を選択

 ドメイン取得画面で、ドメインを指定し、IngressのグローバルIP(パブリックIP)を指定して作成します。

ドメイン取得画面

 ここでは、「cmtest.ddnys.net」を取得しました。本ドメインは既に筆者が取得済みなので、別のドメインを取得してください。ドメインを作成したら、しばらく時間をおいてpingコマンドを実行してドメインが解決していることを確認します。

$ ping cmtest.ddnys.net  (※取得したドメイン名に変更してください)
PING cmtest.ddnys.net (13.223.96.4) 56(84) bytes of data.
64 bytes from 13.223.96.4: icmp_seq=1 ttl=64 time=2.06 ms
64 bytes from 13.223.96.4: icmp_seq=2 ttl=64 time=0.054 ms
...

Ingressの作成(Let's Encrypt)

 次に、Ingressを作成します。ほとんど自己署名証明書を利用した場合と同じですが、annotationで先ほど作成したLet's Encrypt用のClusterIssuerである「letsencrypt-prod」を指定します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress-letsencrypt
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
  - hosts:
      - cmtest.ddns.net
    secretName: nginx-ingress-letsencrypt-tls
  rules:
  - host: cmtest.ddns.net
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80
letsencrypt-ingress.yaml

 YAMLをクラスタに適用します。

$ kubectl apply -f letsencrypt-ingress.yaml

 しばらく待って、動作を確認します。

$ curl https://cmtest.ddns.net/
<!DOCTYPE html>
<html>
<head>
...

 認証局(Let's Encrypt)によって署名された証明書を利用したので、-kオプション(自己署名証明書の許可)なしにアクセスできます。ブラウザで証明書を確認すると、通信が信頼できることを確認できます。

ブラウザでの確認画面

Gateway APIでのcert-managerの利用

 Gateway APIは、Ingressの次世代版として設計されたL4/L7ルーティングのためのKubernetes拡張です。Ingressでは、ホストとルートの定義を分離できず、ネットワーク管理者と開発者で設定責任を分割できませんでしたが、Gateway APIでは、ロールに応じた設計ができるようになっています。またIngressでは、HTTPプロトコルのみだったのに対して、TCP(Transmission Control Protocol)やUDP(User Datagram Protocol)といったプロトコルも利用できます。

 ここからは、Gateway APIを用いたHTTP通信でcert-managerを利用する方法について紹介します。

cert-managerのインストール(Gateway API)

 cert-managerはデフォルトではGateway APIをサポートしていません。cert-manager 1.16.0において、Gateway APIは実験的なサポートとなっており、Gateway APIを有効にするオプションを利用して「helm」コマンドでcert-managerをインストールする必要があります。

$ helm install cert-manager jetstack/cert-manager -ncert-manager --version v1.16.0 --create-namespace --set crds.enabled=true --set config.apiVersion="controller.config.cert-manager.io/v1alpha1" --set config.kind="ControllerConfiguration" --set config.enableGatewayAPI=true

 現時点では、インストール後、cert-managerのPodを再起動する必要があるので、下記のコマンドでcert-managerを再起動します。

$ kubectl rollout restart deployment cert-manager -n cert-manager

Gatewayリソースの設定

 Gateway APIでcert-managerを利用するには、HTTPS用に設定したGatewayリソースにIngressと同じようにアノテーションを追加するだけです。Gateway APIの利用方法の詳細は省略しますが、Traefik(OSSのアプリケーションプロキシ)でcert-managerを利用するサンプルを紹介しておきます。

apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
metadata:
  name: traefik-gateway
  namespace: traefik
  annotations:
    cert-manager.io/cluster-issuer: selfsigned-cluster-issuer
spec:
  gatewayClassName: traefik
  listeners:
  ...
    - name: https
      hostname: cmtest.ddns.net
      protocol: HTTPS
      port: 8443
      allowedRoutes:
        namespaces:
          from: All
      tls:
        mode: Terminate
        certificateRefs:
        - kind: Secret
          group: ""
          name: cmtest-gatewayapi-tls

 上記のGatewayを利用したNginxのサンプルに対してルートを定義するHTTPRouteリソースは、上記で定義したhttpsリスナーを利用し、「sectionName」欄にリスナー名を記載して次のように利用します。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: nginx-httproute
  namespace: test
spec:
  parentRefs:
  - name: traefik-gateway
    namespace: traefik
    sectionName: https
  hostnames:
  - cmtest.ddns.net
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: nginx
      namespace: test
      port: 80

 HTTPRouteについては、特にcert-manager固有の記述はありませんが、GatewayでHTTPSプロトコルを定義したリスナーを利用する必要があるので注意してください。

おわりに

 本稿では、cert-managerを利用した証明書管理の簡易化を紹介しました。証明書を作成、取得、さらには更新する必要がなくなるので、証明書管理の手間を大幅に削減でき、証明書の更新忘れといった初歩的なトラブルも防ぐことができます。また、まだ実験的サポートではありますが、Gateway APIでの利用方法も紹介しました。Gateway APIについては、正しい手順がなかなか見つからなかったので、参考にしていただけると幸いです。

Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。