はじめに
SEROKU の開発フローでは、開発用のリポジトリに Mercurial を用い、各機能実装ごとにブランチを用意した上で開発を行っています。
その上でさらに、社内に Kubernetes クラスタを用意して、ブランチごとにデモ環境を立ち上げられるような仕組みを整備しています。
関連記事
Kubernetes時代のCI/CD Jenkins Xとは?-前編
当初は、各ブランチごとにドメインを作成し、Let’s Encrypt による SSL 証明書を発行していたのですが、並行で開発するブランチ数が多くなってくるとやがて rate-limit に当たるようになりました。
(参考: Rate Limits – Let’s Encrypt – Free SSL/TLS Certificates)
今回は、それを回避するために必要となった、Kubernetes クラスタ上で Let’s Encrypt を用いたワイルドカード証明書を自動発行・更新できる基盤の作り方についてご紹介します。
弊社では AWS Route 53 でドメインを管理しているため、本記事では Route 53 を利用したドメインを対象とします。
必要となる前提知識
本記事では、以下の技術用語については説明いたしませんので、まだご存知ないという方は参考サイトをご覧になってから本記事をご覧になると、より理解が進むと思います。
- Kubernetes
- Ingress
- Helm
- Kubernetes: パッケージマネージャHelm – Qiita
基盤構築にあたって必要となるスタート条件
本記事では、以下の条件が揃った状態をスタートに必要な条件とします。
-
kubectl
コマンドを利用して、Kubernetes クラスタにログイン・操作できること -
helm
コマンドを利用して、Kubernetes クラスタに任意の Chart をインストールできること -
証明書を発行する予定のドメインを保持していること
- 本記事では、証明書を発行する対象ドメインを
example.com
とします
- 本記事では、証明書を発行する対象ドメインを
-
AWS Route 53 で上記ドメインが管理できていること
- ドメインの NS レコードが Route 53 に向いていることを前提とします
本記事のゴール
Kubernetes クラスタ上で以下ができるようになることをゴールとします。
- 保持しているドメインに関するワイルドカード証明書を発行できること
- 発行した証明書を利用して、任意の Web サービスを公開できること
本記事でご紹介するミドルウェア(cert-manager)について
本記事では、Kubernetes クラスタ上で証明書の自動発行・更新を担ってくれるミドルウェアとして cert-manager v0.3.0(執筆時最新) を利用します。
このミドルウェアは、Let’s Encrypt で証明書を発行・更新する上で必要となる以下の作業を自動化してくれます。
- Let’s Encrypt API へ適宜アクセスし、ドメイン検証用コードを発行し、証明書を発行する
- Route 53 へのドメイン検証用 TXT レコードの作成
- 発行した証明書を Kubernetes クラスタへ格納する
構築手順
1. AWS IAM role の用意
まず、cert-manager が Route 53 を操作するために必要なユーザを追加します。
- AWS IAM の Management Console へアクセスします
- 左側のサイドバーより「ユーザー」をクリックします
- 上部にある「ユーザーを追加」ボタンをクリックします
- 「ユーザー名」に適当な文字列を入れます
- 自分が後で見て分かりやすい文字列を入力しておきましょう
- 例:
k8s-cert-manager
など
- 「アクセスの種類」は「プログラムによるアクセス」にチェックを入れます
- 「次のステップ: アクセス権限」ボタンをクリックします
- 「xxx のアクセス権限を設定」の箇所で、「既存のポリシーを直接アタッチ」をクリックします
- 「ポリシーの作成」ボタンをクリックします
- 開いたタブ/ウィンドウの中で「JSON」タブをクリックします
- 出てきたテキストエリアに以下のテキストをコピーします
{ "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 に挙がっているポリシーを使います((2019/09/12 現在修正されている可能性があります))
- 画面下部にある「Review Policy」ボタンをクリックします
- 「名前」に適当な文字列を入力します
- 例:
route53-access-for-cert-manager
- 例:
- 「Create Policy」ボタンをクリックします
- 「xxx が作成されました」という表示が確認出来たら、そのウィンドウ/タブは閉じてください
- 元のユーザ追加画面で「更新」ボタンを押してから、作成したポリシーを検索し、画面左端のチェックボックスをオンにします
- 「次のステップ: 確認」ボタンをクリックします
- 内容に問題がないようであれば、そのまま「ユーザーの作成」ボタンをクリックします
- 作成に完了すると、アクセスキー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 として登録します。
- まず、IAM ユーザー作成時に発行したシークレットアクセスキーを Secret として登録します
$ kubectl -n kube-system create secret generic prod-route53-credentials-secret --from-literal=secret-access-key=<シークレットアクセスキー>
- 登録できたか確認します
$ kubectl -n kube-system get secret prod-route53-credentials-secret NAME TYPE DATA AGE prod-route53-credentials-secret Opaque 1 59d
- 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を記載してください
- 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 アカウントを正常に作成できています
- 上記のように、Message が
4. 証明書の発行
ここまで準備できたら、いよいよ証明書を発行してみます。
cert-manager では、発行する証明書の中身を定義する設定を Certificate というリソースで管理します。
本記事では、*.example.com
に関するワイルドカード証明書を発行する、という想定で設定を記載していきます。
- 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 に取得した証明書の内容が出力されます
-
証明書の発行状況を確認します
$ 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</nil>
- DNS-01 によるドメイン検証は DNS 伝搬に時間がかかるため、証明書発行が完了するまで少し待つ必要があります
- DNS 伝搬が完了するまで待機する作業も cert-manager ではやってくれています!
- 証明書発行が完了すると、上記のように
Certificate renewed successfully
というメッセージが確認できます
- DNS-01 によるドメイン検証は DNS 伝搬に時間がかかるため、証明書発行が完了するまで少し待つ必要があります
- 証明書が出力されたことを確認します
$ 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 で指定する証明書に、今回発行した証明書を指定しています。
- サンプルアプリを立ち上げます
$ 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
- 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 へアクセスして、以下の様に発行された証明書がブラウザ上で確認できれば、証明書発行基盤の構築は完了です!
- 注: example.com のドメインは所有できないものなので、この画像は合成です
サンプルで利用したサービスの設定は以下で全部削除できます。
$ kubectl delete deploy,svc,ing -l 'app=example-nginx'
その他のドメイン検証方法
本記事では、ワイルドカード証明書を発行するために必要な DNS-01 方式のドメイン検証をする設定を作成しましたが、cert-manager では DNS-01 方式以外にも HTTP-01 方式でのドメイン検証にも対応しています。
詳しくは、cert-manager の公式ドキュメントもご覧ください。
まとめ
いかがでしたでしょうか。本記事で紹介した基盤を Kubernetes クラスタ上に用意しておくことで、デモ環境をよりたくさん、安全に用意することが可能になります。
是非、CI/CD と併せて皆さんの開発環境に取り入れていただき、本記事が開発・リリース速度を向上させる上で参考になれば幸いです。