WESEEK Tech Blog

WESEEK のエンジニアブログです

Kubernetes + Let’s Encrypt でワイルドカード証明書を自動発行できる基盤を作ってみよう

こちらは 「Kubernetes + Let’s Encrypt でワイルドカード証明書を自動発行できる基盤を作ってみよう」からの転載です。


「SEROKU フリーランス(以下、SEROKU)」の中の人をやっている syunsuke です。SEROKU では主にインフラ面の担当をしています。

はじめに

SEROKU の開発フローでは、開発用のリポジトリMercurial を用い、各機能実装ごとにブランチを用意した上で開発を行っています。

その上でさらに、社内に Kubernetes クラスタを用意して、ブランチごとにデモ環境を立ち上げられるような仕組みを整備しています。

(詳しい内容については、SEROKU ジャーナルの別記事でご紹介している SEROKUを支える技術〜CI/CD編〜 をご覧ください)

当初は、各ブランチごとにドメインを作成し、Let’s Encrypt による SSL 証明書を発行していたのですが、並行で開発するブランチ数が多くなってくるとやがて rate-limit に当たるようになりました。 (参考: Rate Limits – Let’s Encrypt – Free SSL/TLS Certificates)

今回は、それを回避するために必要となった、Kubernetes クラスタ上で Let’s Encrypt を用いたワイルドカード証明書を自動発行・更新できる基盤の作り方についてご紹介します。

弊社では AWS Route 53 でドメインを管理しているため、本記事では Route 53 を利用したドメインを対象とします。

必要となる前提知識

本記事では、以下の技術用語については説明いたしませんので、まだご存知ないという方は参考サイトをご覧になってから本記事をご覧になると、より理解が進むと思います。

基盤構築にあたって必要となるスタート条件

本記事では、以下の条件が揃った状態をスタートに必要な条件とします。

  1. kubectl コマンドを利用して、Kubernetes クラスタにログイン・操作できること
  2. helm コマンドを利用して、Kubernetes クラスタに任意の Chart をインストールできること
  3. 証明書を発行する予定のドメインを保持していること
    • 本記事では、証明書を発行する対象ドメインexample.com とします
  4. AWS Route 53 で上記ドメインが管理できていること
    • ドメインの NS レコードが Route 53 に向いていることを前提とします

本記事のゴール

Kubernetes クラスタ上で以下ができるようになることをゴールとします。

本記事でご紹介するミドルウェア(cert-manager)について

本記事では、Kubernetes クラスタ上で証明書の自動発行・更新を担ってくれるミドルウェアとして cert-manager v0.3.0(執筆時最新) を利用します。

このミドルウェアは、Let’s Encrypt で証明書を発行・更新する上で必要となる以下の作業を自動化してくれます。

構築手順

1. AWS IAM role の用意

まず、cert-manager が Route 53 を操作するために必要なユーザを追加します。

  1. AWS IAM の Management Console へアクセスします
  2. 左側のサイドバーより「ユーザー」をクリックします
  3. 上部にある「ユーザーを追加」ボタンをクリックします
  4. 「ユーザー名」に適当な文字列を入れます
    • 自分が後で見て分かりやすい文字列を入力しておきましょう
    • 例: k8s-cert-manager など
  5. 「アクセスの種類」は「プログラムによるアクセス」にチェックを入れます
  6. 「次のステップ: アクセス権限」ボタンをクリックします
  7. 「xxx のアクセス権限を設定」の箇所で、「既存のポリシーを直接アタッチ」をクリックします
  8. 「ポリシーの作成」ボタンをクリックします
  9. 開いたタブ/ウィンドウの中で「JSON」タブをクリックします
  10. 出てきたテキストエリアに以下のテキストをコピーします

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": "route53:GetChange",
                "Resource": "arn:aws:route53:::change/*"
            },
            {
                "Effect": "Allow",
                "Action": "route53:ChangeResourceRecordSets",
                "Resource": "arn:aws:route53:::hostedzone/*"
            },
            {
                "Effect": "Allow",
                "Action": "route53:ListHostedZonesByName",
                "Resource": "*"
            }
        ]
    }
    
    • 公式ドキュメント に IAM に設定すべきポリシーが記載されていますが、その内容だと cert-manager がうまく Route 53 にアクセスできないため、こちらの Issue に挙がっているポリシーを使います*1
  11. 画面下部にある「Review Policy」ボタンをクリックします
  12. 「名前」に適当な文字列を入力します
    • 例: route53-access-for-cert-manager
  13. 「Create Policy」ボタンをクリックします
  14. 「xxx が作成されました」という表示が確認出来たら、そのウィンドウ/タブは閉じてください
  15. 元のユーザ追加画面で「更新」ボタンを押してから、作成したポリシーを検索し、画面左端のチェックボックスをオンにします
  16. 「次のステップ: 確認」ボタンをクリックします
  17. 内容に問題がないようであれば、そのまま「ユーザーの作成」ボタンをクリックします
  18. 作成に完了すると、アクセスキーID/シークレットアクセスキーが表示されますので、忘れずにメモしましょう
    • ここで表示されるアクセスキーID/シークレットアクセスキーをこの後の手順で利用します
    • ちなみにメモし忘れたとしても、今回作成したユーザについて別のアクセスキーを発行することが可能です

2. Kubernetes クラスタ上に cert-manager のインストール

公式ドキュメント通り、helm を用いて cert-manager を Kubernetes クラスタへインストールします。

$ helm install \
    --name cert-manager \
    --namespace kube-system \
    stable/cert-manager

インストールが完了すると、以下の様に kube-system namespace に cert-manager の Pod が動き始めます。

$ kubectl -n kube-system get pod
NAME                                           READY     STATUS    RESTARTS   AGE
cert-manager-cert-manager-56fcf79bc8-dx85q     1/1       Running   0          39m

3. 証明書を発行するための設定を行う

cert-manager では、証明書を発行するための ACME プロパイダ(Let’s Encrypt)や DNS プロパイダなどの設定を Issuer/ClusterIssuer というリソースで管理します。

Issuer/ClusterIssuer は namespace 毎に利用可能とするか、クラスタ全体で利用可能とするかの違いで、本記事ではクラスタ全体で利用できるように ClusterIssuer として登録します。

  1. まず、IAM ユーザー作成時に発行したシークレットアクセスキーを Secret として登録します

     $ kubectl -n kube-system create secret generic prod-route53-credentials-secret --from-literal=secret-access-key=<シークレットアクセスキー>
    
  2. 登録できたか確認します

     $ kubectl -n kube-system get secret prod-route53-credentials-secret
     NAME                              TYPE      DATA      AGE
     prod-route53-credentials-secret   Opaque    1         59d
    
  3. DNS-01 でドメインを検証できるようにする ClusterIssuer を作成し、Kubernetes クラスタ上に登録します

     $ cat <<'EOF'> clusterissuer-letsencrypt.yaml
     apiVersion: certmanager.k8s.io
     kind: ClusterIssuer
     metadata:
       name: letsencrypt
     spec:
       acme:
         email: <任意のメールアドレス>
         server: https://acme-staging-v02.api.letsencrypt.org/directory
         privateKeySecretRef:
           name: letsencrypt-private-key
         dns01:
           providers:
           - name: route53
             route53:
               accessKeyID: <アクセスキーID>
               region: us-east-1
               secretAccessKeySecretRef:
                 key: secret-access-key
                 name: prod-route53-credentials-secret
     EOF
     $ kubectl apply -f clusterissuer-letsencrypt.yaml
    
    • <任意のメールアドレス> の部分は、ご自身で受信できるメールアドレスを記載してください
      • 発行した証明書の期限が近づくと、メールで通知が来るようになります
    • <アクセスキーID> の部分は、IAM ユーザー作成時に発行したアクセスキーIDを記載してください
  4. Let’s Encrypt のアカウント発行状況を確認します

     $ kubectl describe clusterissuer letsencrypt
     ...(snip)...
     Status:
       Acme:
         Uri:  https://acme-v02.api.letsencrypt.org/acme/acct/XXXXXXXX
       Conditions:
         Last Transition Time:  2018-05-18T15:20:48Z
         Message:               The ACME account was registered with the ACME server
         Reason:                ACMEAccountRegistered
         Status:                True
         Type:                  Ready
     Events:                    <none>
    
    • 上記のように、Message が The ACME account was registered with the ACME server となっていれば、Let’s Encrypt アカウントを正常に作成できています

4. 証明書の発行

ここまで準備できたら、いよいよ証明書を発行してみます。

cert-manager では、発行する証明書の中身を定義する設定を Certificate というリソースで管理します。

本記事では、*.example.com に関するワイルドカード証明書を発行する、という想定で設定を記載していきます。

  1. Certificate を作成し、Kubernetes クラスタ上に登録します

     $ cat <<'EOF'> cert-wildcard-example.yaml
     apiVersion: certmanager.k8s.io/v1alpha1
     kind: Certificate
     metadata:
       name: wildcard-example
     spec:
       acme:
         config:
         - dns01:
             provider: route53
           domains:
           - '*.example.com'
       commonName: '*.example.com
       issuerRef:
         kind: ClusterIssuer
         name: letsencrypt
       secretName: cert-wildcard-example
     EOF
     $ kubectl apply -f cert-wildcard-example.yaml
    
    • kubectl apply によるリソースの登録が完了すると、cert-manager によってすぐに証明書発行作業が開始されます
    • 証明書発行が完了すると、 spec.secretName に設定された Secret に取得した証明書の内容が出力されます
  2. 証明書の発行状況を確認します

     $ kubectl describe cert wildcard-example
     ...(snip)...
     Status:
       Acme:
         Order:
           URL:  https://acme-v02.api.letsencrypt.org/acme/order/XXXXXXXX/XXXXXXXX
       Conditions:
         Last Transition Time:  2018-06-18T03:26:11Z
         Message:               Certificate renewed successfully
         Reason:                CertRenewed
         Status:                True
         Type:                  Ready
         Last Transition Time:  <nil>
         Message:               Order validated
         Reason:                OrderValidated
         Status:                False
         Type:                  ValidateFailed
    
    • DNS-01 によるドメイン検証は DNS 伝搬に時間がかかるため、証明書発行が完了するまで少し待つ必要があります
      • DNS 伝搬が完了するまで待機する作業も cert-manager ではやってくれています!
    • 証明書発行が完了すると、上記のように Certificate renewed successfully というメッセージが確認できます
  3. 証明書が出力されたことを確認します

     $ kubectl get secret cert-wildcard-example
     NAME                    TYPE                DATA      AGE
     cert-wildcard-example   kubernetes.io/tls   2         25d
    
    • 上記のように kubernetes.io/tls タイプの Secret の存在が確認できれば、証明書は発行できています!

5. Kubernetes クラスタ上に立てた Web サービスの公開

本記事では、サンプルのための nginx Pod を Kubernetes クラスタ上に立てて、それを外部に公開するための Service/Ingress を登録してみます。 Ingress で指定する証明書に、今回発行した証明書を指定しています。

  1. サンプルアプリを立ち上げます

     $ cat <<'EOF' > example-nginx.yaml
     apiVersion: extensions/v1beta1
     kind: Deployment
     metadata:
       name: nginx-example-deployment
       labels:
         app: example-nginx
     spec:
       replicas: 1
       template:
         metadata:
           labels:
             app: example-nginx
         spec:
           containers:
           - name: nginx
             image: nginx
             ports:
             - containerPort: 80
     ---
     apiVersion: v1
     kind: Service
     metadata:
       name: example-nginx
       labels:
         app: example-nginx
     spec:
       ports:
       - name: http
         port: 80
         protocol: TCP
         targetPort: 80
       selector:
         app: example-nginx
       type: NodePort
     ---
     apiVersion: extensions/v1beta1
     kind: Ingress
     metadata:
       name: example-nginx
       labels:
         app: example-nginx
     spec:
       rules:
       - host: nginx.example.com
         http:
           paths:
           - backend:
               serviceName: example-nginx
               servicePort: 80
       tls:
       - hosts:
         - nginx.example.com
         secretName: cert-wildcard-example
     EOF
     $ kubectl apply -f example-nginx.yaml
    
  2. Ingress で取得できたアドレスを確認します(以下の「ADDRESS」の箇所)

     $ kubectl get ing
     NAME            HOSTS               ADDRESS                  PORTS     AGE
     example-nginx   nginx.example.com   XXX.XXX.XXX.XXX          80, 443   3m
    
    • このアドレスに対して名前が引けるように DNS を設定します

6. AWS Route 53 の設定

AWS Route53 へアクセスし、上記のアドレスに対して nginx.example.com でアクセスできるように A レコードを追加します。

7. Let’s アクセス!!

https://nginx.example.com へアクセスして、以下の様に発行された証明書がブラウザ上で確認できれば、証明書発行基盤の構築は完了です!

f:id:weseek:20190912125006p:plain

サンプルで利用したサービスの設定は以下で全部削除できます。

$ kubectl delete deploy,svc,ing -l 'app=example-nginx'

その他のドメイン検証方法

本記事では、ワイルドカード証明書を発行するために必要な DNS-01 方式のドメイン検証をする設定を作成しましたが、cert-manager では DNS-01 方式以外にも HTTP-01 方式でのドメイン検証にも対応しています。

詳しくは、cert-manager の公式ドキュメントもご覧ください。

まとめ

いかがでしたでしょうか。本記事で紹介した基盤を Kubernetes クラスタ上に用意しておくことで、デモ環境をよりたくさん、安全に用意することが可能になります。

是非、CI/CD と併せて皆さんの開発環境に取り入れていただき、本記事が開発・リリース速度を向上させる上で参考になれば幸いです。

*1:2019/09/12 現在修正されている可能性があります