GKEで組んだクラスタへのHTTPS接続を試みる

GKEではGAEやAWSロードバランサーのようにボタンポチーでHTTPS対応!みたいにはいかないようなのでメモ。

単純に実現しようとすると cert-manager でLet's Encryptの証明書を取得するのが最も手っ取り早そうなので、これを試してみることにします。
ちなみに 公式ドキュメント まんまなのでこっちを見たほうが早いと思います。

Helmのインストール

HelmはKubernetesのパッケージマネージャーです。
Macなら brew install kubernetes-helm で入る。
スクリプト叩くだけでもいい。詳しくは こちら

サービスアカウントの作成

サービスアカウントを作る

$ kubectl create serviceaccount tiller --namespace=kube-system

作成したサービスアカウントに権限をつける

$ kubectl create clusterrolebinding tiller-admin --serviceaccount=kube-system:tiller --clusterrole=cluster-admin

この例では管理者権限を付与してますが本番環境で使うときはしっかり権限のハンドリングをしておいたほうが良さそうです。参考

Tillerのインストール
サービスアカウントを渡してhelmをイニシャライズします。
これでKubernetesクラスタにTillerが立ちます。
Tillerはhelmのクライアントから送られてくる情報を元にうまいことk8sAPIを叩いてくれるいい奴です。

$ helm init --service-account=tiller

nginx-ingressを立てる

$ helm install stable/nginx-ingress

これで各クラウドベンダのロードバランサーも立ちます。お金がかかります。しばらくすると外部IPが生えます。

外部IPにドメインを紐付ける

DNSの設定を行う。

アプリケーションのデプロイ

アプリケーションのServiceを立てておく。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: exmaple-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"    
    # certmanager.k8s.io/issuer: "letsencrypt-staging"
    # certmanager.k8s.io/acme-challenge-type: http01

spec:
  tls:
  - hosts:
    - example.example.com
    secretName: example-tls
  rules:
  - host: example.example.com  # ドメイン名
    http:
      paths:
      - path: /
        backend:
          serviceName: example-service  # アプリケーションのService名
          servicePort: 80

これを適当なファイル(今回はingress.yml)に保存して

$ kubectl apply -f ingress.yml

ここまでで外部からの通信が可能になる。

cert-managerのインストール

$ kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.6/deploy/manifests/00-crds.yaml

Issuer の登録

Let's Encryptには証明書の取得制限があります。
基本的に気にしなくてもいいレベルの制限ですが、同じドメインの証明書の取得は一週間に5度までです。
作り直したりする過程で制限に入っても困るのでstagingで試してからにします。
なお、DNS-01方式はだるそうだったので、HTTP-01方式でドメインの認証を行います。

apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # The ACME server URL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: user@example.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-staging
    # Enable the HTTP-01 challenge provider
    http01: {}
$ kubectl apply -f staging-issuer.yaml

本番用のIssuerも作成しておく。

apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: user@example.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    http01: {}
$ kubectl apply -f production-issuer.yaml

再度ingress.ymlを開いてコメントアウトを外した後にapplyする。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: exmaple-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"    
    certmanager.k8s.io/issuer: "letsencrypt-staging"
    certmanager.k8s.io/acme-challenge-type: http01

spec:
  tls:
  - hosts:
    - example.example.com
    secretName: example-tls
  rules:
  - host: example.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: example-service
          servicePort: 80
$ kubectl apply -f ingress.yml

applyすることでOrderが動いてLet's Encryptに対してリクエストが飛びます。
証明書の発行状態は kubectl describe certificate example-tls で表示されるEventから見れる。

完了していた場合はSecretが作成されている。

$ kubectl describe secret example-tls

で確認できる。

問題なければIssuerを本番のものに切り替える。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: exmaple-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"    
    certmanager.k8s.io/issuer: "letsencrypt-prod"  # changed
    certmanager.k8s.io/acme-challenge-type: http01

spec:
  tls:
  - hosts:
    - example.example.com
    secretName: example-tls
  rules:
  - host: example.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: example-service
          servicePort: 80
$ kubectl apply -f ingress.yml

Issuerを変更した後に、Secretを削除することで変更後のIssuerで証明書を取得します。

$ kubectl delete secret example-tls

ちょっとだけ時間がかかる。 状態はOrderから見れる。

$ kubectl get order
NAME                    AGE
example-tls-2000613234   2h
$ kubectl describe order example-tls-2000613234

以上でHTTPS接続が確認できた。
Helmとか知ってればあんまり手間じゃないのかもしれないけど結構面倒くさかったです…