ある日 kops で構築した k8s の apiserver にブラウザからアクセスした時にオレオレ証明書使った時とかにでるようなプライバシーエラーになっていて、これを解消するために試行錯誤した話。
今回は kops の 1.18.0-alpha.3
を使っています。
cluster_spec に書いてあるのですが、kops にはもともと API server を ACM で https 化するためのオプションがついています。
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 をアタッチすることができます!!
kops で設定する時は externalLoadBalancers に ELB の名前を追加することで Auto Scaling Group を作る時にアタッチしてくれます。
最後に
今回は API Server へのアクセスを ACM で https にするための試行錯誤をしました。
実はもっと良い方法があるかもしれないし、設定が間違ってるかもしれないけどとりあえず今はこれで。。。。。
ちなみに kops は ELB を現状使ってるのですが、これを ALB/NLB にしてはどうかみたいな issue があがってるようです。