8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Kubernetes cert-managerでRoute53で作成したドメインの証明書を作成し、Ingressで利用する。

Last updated at Posted at 2020-04-29

はじめに

無料でKubernetes IngressでTLS化するためのメモです。

無料と言いつつ、自分は以前AWSのRoute53で購入したパブリックドメインがあるので、ドメインを持っている前提となりますのでご了承ください。

前回の記事でセットアップしたGKEでHTTPSのIngressを使えるようにしたいと思います。
ALBなどを利用するのであれば、ACMで無料で証明書を作れるのですが、個人利用のためロードバランサーの追加費用は払えません。。
またGKEについても、Network Load Balancerのコストは払えません。。

そのため、今回はGKEにCert-Managerをデプロイし、Let's Encryptから証明書を発行し、Ingress ControllerのNodePortを外部公開してTLS通信を実現します。

環境

前回構築した環境です。

  • Kubernetes:

    • Master: GKE v1.15.3
    • Worker: e2-small(preemptive instance) × 1 Node
  • Domain:

    • AWS Route53 Domainで取得
    • 取得したドメイン(例:example.com)のRoute53 hostedzoneがある。
    • example.comのhostedzoneに、上記のWorkerのIPがAレコードで設定されている。

cert-manager

kubernetes上で証明書の発行、更新、管理なんかをしてくれます。https://cert-manager.io/

cert-managerのインストール

Ingress Controller

Contourのインストール

なんでも良いので、今回はContourを使用します。
こちらもインストールは簡単ですが、自分はGCPのNetwork Load Balancerを使いたくないので、少し変更します。

kubectl apply -f https://projectcontour.io/quickstart/contour.yaml

Daemonsetで配置されるEnvoyのServiceがtype: Loadbalancerなので、外部Load Balancerを使う前提になっています。

今回は外部ロードバランサーは使わないので、type: NodePortに変更し、NodePortを固定化するため80 -> 30080, 443 -> 30443 にしました。

Serviceの編集
kubectl edit service/envoy -n projectcontour
...
ports:
 type: NodePort # <- NodePortに変更
  - name: http
    nodePort: 30080 # <- 追加
    port: 80
    protocol: TCP
    targetPort: 80
  - name: https
    nodePort: 30443 # <- 追加
    port: 443
    protocol: TCP
    targetPort: 443
...

hostPortで80と443を開けても良かったのですが、バックエンドのアプリしか動かさない予定なので、特にポートにこだわりはありません。
Ingress自体にCluster外からどうやって到達するかについては、ingress-nginxに様々なパターンを紹介してくれているドキュメントがあります。

Bare-metal considerations
https://kubernetes.github.io/ingress-nginx/deploy/baremetal/#via-the-host-network

最終的にこんな感じでインストール完了です。

kubectl get all -n projectcontour
NAME                           READY   STATUS      RESTARTS   AGE
pod/contour-5f5bf9cd8c-r2bwf   1/1     Running     6          21h
pod/contour-certgen-f5hd4      0/1     Completed   0          21h
pod/envoy-j6fh6                2/2     Running     0          6h19m


NAME              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                      AGE
service/contour   ClusterIP   10.0.13.41   <none>        8001/TCP                     21h
service/envoy     NodePort    10.0.3.136   <none>        80:30080/TCP,443:30443/TCP   21h

NAME                   DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/envoy   1         1         1       1            1           <none>          21h

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/contour   1/1     1            1           21h

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/contour-5f5bf9cd8c   1         1         1       21h



NAME                        COMPLETIONS   DURATION   AGE
job.batch/contour-certgen   1/1           5s         21h

issuerの作成

Route53にLet's EncryptでDNS検証で証明書を作成します。
公式ドキュメントに沿ってRoute53にアクセスするためのIAMを作成します。

Configuration / ACME / DNS01 / Route53
https://cert-manager.io/docs/configuration/acme/dns01/route53/

普通はk8s on AWSやEKSで利用すると思うので、IAM Roleを使えばいいと思いますが、
今回はGKEから使うので、IAM Userに上記で作成したポリシーをアタッチして、アクセスキーとシークレットをCluster内に置いちゃいます。

シークレットアクセスキーのsecretを作成します。(アクセスキーは後述するissuerのyamlに)

secret作成
kubectl -n  cert-manager create secret generic prod-route53-credentials-secret --from-literal=secret-access-key=<SECRET_ACCESS_KEY>

IssuerやClusterIssuerというCRDができているので、Let's Encryptのissuerを作成します。
Cluster全体で使うので、今回はClusterIssuerを作成します。

letsencrypte-issuer.yaml
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-issuer
spec:
  acme:
    # まずはstagingで試して、問題なく証明書ができたらprodで再実行
    #server: https://acme-v02.api.letsencrypt.org/directory # prod
    server: https://acme-staging-v02.api.letsencrypt.org/directory # staging
    email: <YOUR_MAIL>
    privateKeySecretRef:
      name: letsencrypt-issuer
    solvers:
      - selector:
          dnsZones:
          - "example.com" // Route53 Domainsで取得したドメイン
        dns01:
          route53:
            region: ap-northeast-1
            accessKeyID: <ACCESS_KEY> // アクセスキー
            secretAccessKeySecretRef:
              name: prod-route53-credentials-secret
              key: secret-access-key

applyしてステータスが問題なさそうなら大丈夫だと思います。

describe
kubectl describe clusterissuer
...

Status:
  Acme:
    Last Registered Email:  <YOUR_MAIL>
    Uri:                    https://acme-v02.api.letsencrypt.org/acme/acct/xxxxxxxx
  Conditions:
    Last Transition Time:  2020-04-21T05:28:09Z
    Message:               The ACME account was registered with the ACME server
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

証明書の作成

以下の2つの方法があります。

  1. cert-managerのIngress自動証明書作成機能を使用して、Certificateリソースを自動作成する。
  2. 手動でCertificateリソースを作成し、作成された証明書シークレットをIngressから参照する。

Ingress自動証明書作成機能

Ingressに特定のアノテーションを付与することで、cert-managerがIngressの内容から自動で証明書を作成してくれます。

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-issuer # <- 大事なのはここ
spec:
  ingressClassName: contour
  tls:
  - hosts:
    - "*.example.com" # <- この名前の証明書が作成されます。ワイルドカード指定も可能
    secretName: example-com-tls # <- 存在しない名前ならなんでもいいです。この名前の証明書シークレットが作成されます。
  rules:
  - host: app.example.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: example-app
            port: 8080

手動でCertificateリソースを作成

基本的には上記の方法で自動で作成するのがいいと思いますが、手動で作成してみると勉強になると思います。
Certificateリソースを自前で作成します。

certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com
  namespace: <YOUR_NAMESPACES>
spec:
  secretName: example-com-tls
  duration: 2160h # 90d
  renewBefore: 360h # 15d
  organization:
  - example
  commonName: "*.example.com" // ワイルドカード証明書
  isCA: false
  keySize: 2048
  keyAlgorithm: rsa
  keyEncoding: pkcs1
  usages:
    - server auth
    - client auth
  dnsNames:
  - example.com // Route53に登録した
  - "*.example.com"  // commonNameに指定した名前がdnsNamesにないと失敗します。
  # Issuer references are always required.
  issuerRef:
    name: letsencrypt-issuer
    kind: ClusterIssuer

applyしてしばらく待ちます。Certificateリソースを作成すると

Certificate -> CertificateRequest -> Order -> Challenge

の順にリソースが作成されていきます。

注意点としては、エラーの場合も、ずっとCertificateのステータスがInProgressのままになっていることです。(リトライしようとしているっぽい)

email検証だから時間がかかると思いつつも、おかしいと思って上記のリソースを順番にDescribeして見てみると、Challengeなどで失敗していることがありました。

しばらく経ってもCertificateがReadyにならない場合は、CertificateRequest以降が失敗している可能性があります。どれかのリソースのDescribeしてEventのところにエラーが出ているはずです。

トラブルシューティングに調べ方の記載がありますが、InProgressだとエラーになっているかわからないです。

Troubleshooting Issuing ACME Certificates
https://cert-manager.io/docs/faq/acme/

問題なくCertificateができると、大丈夫そうなステータスになります。
またCertificateRequest以降のリソースはしばらくすると削除されていなくなります。

describe
kubectl describe certificate -A
...
Status:
  Conditions:
    Last Transition Time:  2020-04-22T05:41:38Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2020-07-21T04:41:38Z
Events:
  Type    Reason        Age   From          Message
  ----    ------        ----  ----          -------
  Normal  GeneratedKey  32m   cert-manager  Generated a new private key
  Normal  Requested     32m   cert-manager  Created new CertificateRequest resource "example-tls-2084312343"
  Normal  Issued        30m   cert-manager  Certificate issued successfully

作成が完了すると、Certificateと同じNamespace内にSecretが作成されています。
Ingressでこれを指定します。

$ kubectl describe secret example-com-tls

Name:         example-com-tls
Namespace:    <YOUR_NAMESPACE>
Labels:       <none>
Annotations:  cert-manager.io/alt-names: *.example.com,example.com
              cert-manager.io/certificate-name: example-com
              cert-manager.io/common-name: *.example.com
              cert-manager.io/ip-sans: 
              cert-manager.io/issuer-kind: ClusterIssuer
              cert-manager.io/issuer-name: letsencrypt-issuer
              cert-manager.io/uri-sans: 

Type:  kubernetes.io/tls

Data
====
tls.key:  1675 bytes
ca.crt:   0 bytes
tls.crt:  3574 bytes

Ingressで証明書シークレットを利用する。

あとはIngressを作成するだけです。

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-app
spec:
  ingressClassName: contour
  tls:
  - hosts:
    - "*.example.com"
    secretName: example-com-tls # <- 先ほど確認した証明書シークレットの名前
  rules:
  - host: example.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: example-app
            port: 8080

最後に

最後に忘れてはいけないファイアウォールルールの設定です。
NodeのVMインスタンスのファイアウォールルールで先ほど公開したNodePortを開きます。

これで、https://example.com:30443 で繋がるようになると思います。
GKEとRoute53という謎構成でしたが、少しでもご参考になれば幸いです。

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?