kops で構築した k8s クラスターの apiserver を ACM で https にしてアクセスする

ある日 kops で構築した k8s の apiserver にブラウザからアクセスした時にオレオレ証明書使った時とかにでるようなプライバシーエラーになっていて、これを解消するために試行錯誤した話。

今回は kops の 1.18.0-alpha.3 を使っています。

cluster_spec に書いてあるのですが、kops にはもともと API server を ACMhttps 化するためのオプションがついています。

spec:
  api:
    loadBalancer:
      type: Public
      sslCertificate: arn:aws:acm:<region>:<accountId>:certificate/<uuid>

これを設定したら終わりだったらよかったのですが、自分の環境ではうまくいかなかった。

sslCertificate を設定するとなぜうまくいかなかったのか?

kops は API Server をたたく時に外からはもちろんですが、node からリクエストする時も https を使用しています。

その時の証明書として kops ではクラスタを構築するに kops create secret keypair ca を使って登録します。
たしかこの時にオレオレ証明書を 作った気がする。
このコマンドで登録しておくと master, node などのインスタンスを立ち上げる時に配布して https リクエストする時に使用する。

具体的には ~/.kube/config を見た時に下記のように certificate-authority-data が鍵データがおかれます。
クライアントでは kops export kubecfg k8s-api.example.com でファイルを出力します。

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: xxxxxxx
    server: https://k8s-api.example.com
  name: k8s.example.com

この鍵データを使って https を使うので、 node では kubelet, クライアントでは kubectl で証明書エラーにならずに API にリクエストができています。

sslCertificate のオプションを指定すると ELB に対して ACM で発行された証明書を使うのですが、kubectl などを使う時は問題なかったのですが、 node の kubelet 側で証明書エラーになっていました。

先ほどの ~/.kube/config を クライアントと node で確認してみます。
クライアントは下記のように certificate-authority-data がない状態でした。

apiVersion: v1
clusters:
- cluster:
    server: https://k8s-api.example.com
  name: k8s.example.com

node 側の kubelet が参照しているファイルを見てみます。
ちなみにデフォルトでは /var/lib/kubelet/kubeconfig におかれています。

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: xxxxxxx
    server: https://k8s-api.example.com
  name: k8s.example.com

オレオレ証明書の時の鍵データが残っている。。。。。
これにより ACM による https が提供されているけどオレオレ証明書の時の鍵データを使おうとしてエラーになっているようでした。

どう解決するのか

とりあえず解決できそうな案を考えてみます。

  • node も クライアントの時と同じように certificate-authority-data を出力しない
  • node->masterのインターナルな通信だし http ??
  • node -> master ようにもう一台ELBを用意する

結果から先にいうと一番最後の ELB を追加する案を採用しました。
正確には他の二つがうまくいかなかった。

ひとつひとつみていきます。

node も クライアントの時と同じように certificate-authority-data を出力しない

これができるオプションを探しました。
現状はありませんでした。

コードだとこのあたり https://github.com/kubernetes/kops/blob/87681780820deed795954836d74d44e1cf7f6f37/nodeup/pkg/model/context.go#L209-L226 CAがあるのが前提になってますね。

ちなみにクライアント側のコードはこのあたり
https://github.com/kubernetes/kops/blob/87681780820deed795954836d74d44e1cf7f6f37/pkg/kubeconfig/create_kubecfg.go#L89-L103 ちゃんと sslCertificate を設定した時は certificate-authority-data を出力しないように分岐してますね。

ということでこの案は却下しました。

node->masterのインターナルな通信だし http ??

https でエラーがでるならサーバー間の通信だし http で良いのではと考えしらべます。

コードだとこのあたり
https://github.com/kubernetes/kops/blob/c73659561e4c638c59e632f8c825a683ce837f73/nodeup/pkg/model/context.go#L242 https がしっかり設定されてますねw

node -> master ようにもう一台ELBを用意する

他にも出力されたファイルから certificate-authority-data を消すなど考えましたが、ACM が設定されている public な ELB とは別にサーバー間の通信に必要な ELB をたてることにしました。

ELB 自体は terraform でさくっと作れるのですが、問題は API Server のたつ master インスタンスにどうやって紐づけるかです。
というのもクラスターのアップグレードする時にはインスタンスがいれかわるので、これにうまいこと対応させる必要があります。

幸いなことに master は Auto Scaling Group で管理されているので ELB をアタッチすることができます!!

docs.aws.amazon.com

kops で設定する時は externalLoadBalancers に ELB の名前を追加することで Auto Scaling Group を作る時にアタッチしてくれます。

最後に

今回は API Server へのアクセスを ACMhttps にするための試行錯誤をしました。
実はもっと良い方法があるかもしれないし、設定が間違ってるかもしれないけどとりあえず今はこれで。。。。。

ちなみに kops は ELB を現状使ってるのですが、これを ALB/NLB にしてはどうかみたいな issue があがってるようです。

github.com