Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する連載。今回は、cert-managerを利用したIngressによる自己署名証明書とLet's Encryptで発行した証明書の利用方法を解説し、Gateway APIでcert-managerを利用する方法を紹介する。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
今や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は、KubernetesでTLS証明書の管理ツールです。下記のような特徴を持っています。
本稿は、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は下記コマンドでインストールします。
$ 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
本稿では、テスト用に「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で利用する方法を紹介します。
「Issuer」は、証明書を発行するリソースです。自己署名証明書を発行するIssuer、Let's Encryptで証明書を発行するIssuerなどを作成し、用途によって使い分けることができます。リソースとしては、各ネームスペースで利用する「Issuer」と全てのネームスペースで共通して利用できる「ClusterIssuer」があります。
本稿では、証明書を発行するClusterIssuerを利用します。自己署名証明書用のClusterIssuerを作るには、次のYAMLを用意します。
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-clusterissuer
spec:
  selfSigned: {}
このYAMLをデプロイすればOKです。
$ kubectl apply -f selfsigned-clusterissuer.yaml
上記のClusterIssuerが生成されているかどうか、念のため確認します。
$ kubectl get clusterissuers NAME READY AGE selfsigned-clusterissuer True 20s
ClusterIssuerが作成されていれば準備完了です。ClusterIssuerはクラスタで1つ用意しておけば使い回せるので、次回から作業を省略できます。
自己署名証明書を利用したIngressの設定方法を紹介します。Let's Encryptを利用した暗号化については、「Let's Encryptによる証明書の作成」をご覧ください。
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
cert-managerを利用するには、通常のIngressに、下記のように「selfsigned-clusterissuer」を指定したアノテーションを追加するだけでOKです。
  annotations:
    cert-manager.io/cluster-issuer: selfsigned-clusterissuer
「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 ★
生成された証明書を確認してみましょう。
$ 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】で次回証明書の自動更新日時を確認できます。自動更新日時が来たら自動的に証明書を更新してくれます。
自己署名証明書は、あくまでも「簡易的な証明書」なので、安全に通信できるとは限りません。安全な通信には、認証局に署名された証明書を利用できます。証明書を認証局から取得する手順は煩雑ですが、HTTP-01やDNS-01といった証明書の自動発行プロトコルに対応した認証局を利用すると、簡単に証明書を発行できます。
ここからは、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
作成したYAMLファイルをKubernetesクラスタに適用してClusterIssuerを作成します。
$ kubectl apply -f letsencrypt-clusterissuer.yaml
上記のClusterIssuerが生成されているかどうか、念のため確認します。
$ kubectl get clusterissuers NAME READY AGE letsencrypt-prod True 1m10s
自己署名証明書のClusterIssuerと同じく、クラスタで1つ作成しておけばよいので、次回からは作業を省略できます。
Let's Encryptで証明書を利用するには、ドメインレジストラ、もしくは「Amazon Route 53」といったクラウドサービスによって発行されたドメインが必要になります。
ここでは、「Dynamic DNS」によるドメインを無料で簡単に発行できる「No-IP」を利用してドメインを発行し、証明書を取得します。既にドメインをお持ちの方はスキップしてください。
No-IPのWebサイトで登録してログインします。Googleアカウントを利用してログインすると、NO-IPのアカウントを作成することなく簡単にログインできます。ログインしたら、ドメイン取得(「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を作成します。ほとんど自己署名証明書を利用した場合と同じですが、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
YAMLをクラスタに適用します。
$ kubectl apply -f letsencrypt-ingress.yaml
しばらく待って、動作を確認します。
$ curl https://cmtest.ddns.net/ <!DOCTYPE html> <html> <head> ...
認証局(Let's Encrypt)によって署名された証明書を利用したので、-kオプション(自己署名証明書の許可)なしにアクセスできます。ブラウザで証明書を確認すると、通信が信頼できることを確認できます。
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 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 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については、正しい手順がなかなか見つからなかったので、参考にしていただけると幸いです。
 SSL/TLS証明書の「不正発行」を防ぐ多視点ドメイン検証、Let's Encryptが開始
SSL/TLS証明書の「不正発行」を防ぐ多視点ドメイン検証、Let's Encryptが開始 手作業によるデジタル証明書管理は限界? エントラスト、世界17カ国のPKI動向を調査
手作業によるデジタル証明書管理は限界? エントラスト、世界17カ国のPKI動向を調査 OpenSSL Project、「OpenSSL 3.2.0」を公開
OpenSSL Project、「OpenSSL 3.2.0」を公開Copyright © ITmedia, Inc. All Rights Reserved.