kubeadm join はノードをクラスターにどのように追加しているのか
kubeadm では、kubeadm join コマンドの実行で、新しいノードの追加がかんたんにできます。 このとき何が行われているのかを調べてみました。
ノード追加のワークフロー
kubeadm join
では次のようなステップが行われる。
- API Server から必要となるクラスターの情報をダウンロードする
- Bootstrap token と CA key hash を使ってデータの信頼性を検証する
- クラスターの情報が取得できたら kubelet は TLS bootstrapping プロセスを開始できる
- TLS Bootstrap では shared token を使用して Kubernetes API Server で一時的に認証をし、証明書署名要求 (CSR) を送信する
- コントロール プレーンは既定でこの CSR リクエストに自動的に署名する
- ノードに割り当てられた identity を使って kubelet から API Server に接続するよう構成する
コントロール プレーンのノードを追加する場合では、上記に加え、コントロール プレーンのコンポーネントの構成や etcd メンバーの追加が行われる。
kubeadm join のログを追ってみる
kubeadm join phases internal design
実際に kubeadm join
コマンドを実行し、ログ出力からどのような処理が行われているかを観察します。
root@node02:~# kubeadm join 192.168.56.10:6443 \
--token w7b0wx.ekyppyi0gpy54eb7 \
--discovery-token-ca-cert-hash sha256:1c4dd8edc15c4e3fa422c373407ba40e71f4fe140865ebb3c7878d70502fa235 \
-v 10
以下では各フェーズのログを抜粋/整形して紹介します。
Preflight checks フェーズでは、クラスターの動作要件を満たしているかどうかのチェックが行われます。
[preflight] Running pre-flight checks
I0226 05:39:19.365646 6279 preflight.go:92] [preflight] Running general checks
続いて Discovery cluster-info フェーズが実行されます。
このフェーズは Shared token discovery と File/https discovery の2種類がありますが、今回は --token
オプションを渡しているため Shared token discovery となります。
--token string Use this token for both discovery-token and tls-bootstrap-token when those values are not provided.
Shared token discovery では、CA 証明書を kube-public ネームスペースの cluster-info ConfigMap から取得します。
I0226 05:39:19.467065 6279 join.go:530] [preflight] Discovering cluster-info
I0226 05:39:19.467085 6279 token.go:80] [discovery] Created cluster-info discovery client, requesting info from "192.168.56.10:6443"
I0226 05:39:19.467432 6279 round_trippers.go:466] curl -v -XGET -H "Accept: application/json, */*"
'https://192.168.56.10:6443/api/v1/namespaces/kube-public/configmaps/cluster-info?timeout=10s'
...
I0226 05:39:19.482307 6279 request.go:1181] Response Body:
{
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"name": "cluster-info",
"namespace": "kube-public",
...
}
"data": {
"kubeconfig": "apiVersion: v1
clusters:
- cluster:
certificate-authority-data: <CA cert data>
server: https://192.168.56.10:6443
...
}
}
取得した CA 証明書は --discovery-token-ca-cert-hash
フラグのハッシュ値を利用して公開鍵が検証されます。
その後追加の検証として、セキュアな接続を介して再度 cluster-info の取得を行い、最初に取得した CA との比較が行われます。
I0226 05:39:19.483177 6279 token.go:118] [discovery] Requesting info from "192.168.56.10:6443" again to validate TLS against the pinned public key
I0226 05:39:19.483702 6279 round_trippers.go:466] curl -v -XGET -H "Accept: application/json, */*"
'https://192.168.56.10:6443/api/v1/namespaces/kube-public/configmaps/cluster-info?timeout=10s'
kube-system ネームスペースの ConfigMap から、クラスターの構成情報を含む kubeadm の設定と、kube-proxy / kubelet の kubeconfig が取得されます。
I0226 05:39:19.492823 6279 token.go:135] [discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "192.168.56.10:6443"
I0226 05:39:19.493000 6279 discovery.go:52] [discovery] Using provided TLSBootstrapToken as authentication credentials for the join process
I0226 05:39:19.493169 6279 join.go:544] [preflight] Fetching init configuration
I0226 05:39:19.493347 6279 join.go:590] [preflight] Retrieving KubeConfig objects
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
I0226 05:39:19.495291 6279 round_trippers.go:466] curl -v -XGET -H "Accept: application/json, */*" -H "Authorization: Bearer <masked>"
'https://192.168.56.10:6443/api/v1/namespaces/kube-system/configmaps/kubeadm-config?timeout=10s'
I0226 05:39:19.499289 6279 round_trippers.go:466] curl -v -XGET -H "Authorization: Bearer <masked>" -H "Accept: application/json, */*"
'https://192.168.56.10:6443/api/v1/namespaces/kube-system/configmaps/kube-proxy?timeout=10s'
I0226 05:39:19.869234 6279 round_trippers.go:466] curl -v -XGET -H "Accept: application/json, */*" -H "Authorization: Bearer <masked>"
'https://192.168.56.10:6443/api/v1/namespaces/kube-system/configmaps/kubelet-config-1.23?timeout=10s'
クラスターの情報が揃うと bootstrap-kubelet.conf
ファイルが書き込まれます。
I0226 05:39:19.881471 6279 kubelet.go:119] [kubelet-start] writing bootstrap kubelet config file at /etc/kubernetes/bootstrap-kubelet.conf
I0226 05:39:19.882004 6279 kubelet.go:134] [kubelet-start] writing CA certificate at /etc/kubernetes/pki/ca.crt
I0226 05:39:19.882379 6279 loader.go:372] Config loaded from file: /etc/kubernetes/bootstrap-kubelet.conf
その後 kubelet が開始されます。
kubelet は、起動後に bootstrap-kubelet.conf
が存在する場合に、TLS Bootstrap のプロセスを実行します。
TLS Bootstrap では、bootstrap token を利用して一時的に Kubernetes API Server の認証を行い、ノード用の CSR リクエストを送信します。
CSR リクエストは自動的に承認され、ノードのクライアント証明書や kubelet.conf
が生成されて、ノードのクラスターへの参加処理が完了します。
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
I0226 05:39:25.111213 6279 loader.go:372] Config loaded from file: /etc/kubernetes/kubelet.conf
kubeadm join
完了時のメッセージ。
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
TLS Bootstrapping をより詳しく見ていく
実際のクラスター コンポーネントの設定やリソース内容を観察してみます。
さらに、kubeadm join
コマンドと同じステップを手動で行うことで、ノードが追加されるのか試してみよう。
コントロール プレーン コンポーネントのフラグ
kube-apiserver には、Bootstrap Token による認証が利用できるように --enable-bootstrap-token-auth=true
フラグが設定されています。
$ ps aux | grep kube-apiserver
root 7139 2.8 15.5 1112196 315216 ? Ssl 14:21 14:55 kube-apiserver
... --enable-bootstrap-token-auth=true ...
CSR の署名は kube-controller-manager によって行われます。
--cluster-signing-cert-file
と --cluster-signing-key-file
フラグで、CA 証明書と鍵ファイルが指定されていまする。
$ ps aux | grep kube-controller-manager
root 7149 0.8 5.1 825164 104044 ? Ssl 14:21 4:18 kube-controller-manager
... --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt --cluster-signing-key-file=/etc/kubernetes/pki/ca.key ...
Bootstrap Token
Bootstrap Token は kube-system ネームスペース内に Secret として配置されています。
$ kubeadm token list
TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
w7b0wx.ekyppyi0gpy54eb7 6h 2022-02-27T05:28:24Z authentication,signing The default bootstrap token generated by 'kubeadm init'. system:bootstrappers:kubeadm:default-node-token
$ kubectl get secret -n kube-system bootstrap-token-w7b0wx
NAME TYPE DATA AGE
bootstrap-token-w7b0wx bootstrap.kubernetes.io/token 7 17h
kubectl get secret -n kube-system bootstrap-token-w7b0wx -o yaml
で Secret の中身を確認すると以下のようになっています (各値をデコード済み)。
apiVersion: v1
kind: Secret
metadata:
creationTimestamp: "2022-02-26T05:28:24Z"
name: bootstrap-token-w7b0wx
namespace: kube-system
resourceVersion: "219"
uid: 0d01669e-65cb-413c-add4-c6ced5219283
type: bootstrap.kubernetes.io/token
data:
auth-extra-groups: system:bootstrappers:kubeadm:default-node-token
description: The default bootstrap token generated by 'kubeadm init'.
expiration: 2022-02-27T05:28:24Z
token-id: w7b0wx
token-secret: ekyppyi0gpy54eb7
usage-bootstrap-authentication: true
usage-bootstrap-signing: true
token-id
と token-secret
を連結して、この場合 w7b0wx.ekyppyi0gpy54eb7
が token となります。
また、auth-extra-groups
が CSR リクエストを行う際のグループ名となります。
RBAC による CSR 作成/自動承認/自動更新の許可
Bootstrap Token で設定された auth-extra-groups
のグループに対して、CSR リクエストを作成できる権限が付与されます。
system:bootstrappers:kubeadm:default-node-token
Group に対して system:node-bootstrapper
が割り当てられています。
$ kubectl get clusterrolebinding kubeadm:kubelet-bootstrap
NAME ROLE AGE
kubeadm:kubelet-bootstrap ClusterRole/system:node-bootstrapper 17h
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
creationTimestamp: "2022-02-26T05:28:25Z"
name: kubeadm:kubelet-bootstrap
resourceVersion: "222"
uid: 4f53106c-7925-40ec-a578-de39bca774d1
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:node-bootstrapper
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:bootstrappers:kubeadm:default-node-token
こちらは kubelet が CSR を Auto-Approve できるように許可する ClusterRoleBinding。
$ kubectl get clusterrolebinding kubeadm:node-autoapprove-bootstrap
NAME ROLE AGE
kubeadm:node-autoapprove-bootstrap ClusterRole/system:certificates.k8s.io:certificatesigningrequests:nodeclient 17h
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
creationTimestamp: "2022-02-26T05:28:25Z"
name: kubeadm:node-autoapprove-bootstrap
resourceVersion: "223"
uid: e77928e5-8a2c-428c-9c52-6b046cc8c556
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:nodeclient
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:bootstrappers:kubeadm:default-node-token
こちらは kubelet が証明書を自動更新することを許可するための ClusterRoleBinding。
$ kubectl get clusterrolebinding kubeadm:node-autoapprove-certificate-rotation
NAME ROLE AGE
kubeadm:node-autoapprove-certificate-rotation ClusterRole/system:certificates.k8s.io:certificatesigningrequests:selfnodeclient 17h
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
creationTimestamp: "2022-02-26T05:28:25Z"
name: kubeadm:node-autoapprove-certificate-rotation
resourceVersion: "224"
uid: 66e996da-dbb3-4012-870d-d2522dc77104
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:nodes
CSR 作成や Auto-Approve では、クラスターにノードが参加する段階であるため system:bootstrappers
の Group が使用されています。
一方、証明書の自動更新の段階では、ノードの bootstrap は終了しており Node として動作をしているため、system:nodes
に対して権限を割り当てます。
新規ノードの追加
それでは実際にクラスターへ新規ノードを追加してみよう。 あらかじめ kubeadm 環境で必要なパッケージをインストールしておきます。
/etc/systemd/system/kubelet.service.d/10-kubeadm.conf
から、kubelet に読み込まれる設定を確認し、必要なファイルのパスを確認しておきます。
# Note: This dropin only works with kubeadm and kubelet v1.11+
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use
# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.
EnvironmentFile=-/etc/default/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
以下のファイルを用意します:
- /etc/kubernetes/pki/ca.crt
- /etc/kubernetes/bootstrap-kubelet.conf
- /var/lib/kubelet/config.yaml
- /var/lib/kubelet/kubeadm-flags.env
ca.crt の取得
初回アクセスは TLSVerify なしの insecure な通信になっているよう。
curl --insecure 'https://192.168.56.10:6443/api/v1/namespaces/kube-public/configmaps/cluster-info' | jq -r '.data.kubeconfig' > cluster-info
certificate-authority-data
フィールドからエンコードされた証明書データを取り出し、デコードする。
% echo 'LS0tLS...' | base64 -d > ca.crt
% file ca.crt
ca.crt: PEM certificate
% mkdir /etc/kubernetes/pki
% mv ca.crt /etc/kubernetes/pki/
取得した ca.crt を使って API Server にアクセスができることを確認します。
curl --cacert /etc/kubernetes/pki/ca.crt -v \
'https://192.168.56.10:6443/api/v1/namespaces/kube-public/configmaps/cluster-info'
Kubelet Config の取得
kubelet-config-1.23
ConfigMap を取得します。
API Server へアクセスする際の cacert には、先程取得した ca.crt ファイルを使用し、Authorization ヘッダーには Bootstrap Token を使用します。
% mkdir /var/lib/kubelet
% curl -s --cacert /etc/kubernetes/pki/ca.crt \
-H "Authorization: Bearer w7b0wx.ekyppyi0gpy54eb7" \
https://192.168.56.10:6443/api/v1/namespaces/kube-system/configmaps/kubelet-config-1.23 | jq -r '.data.kubelet' > /var/lib/kubelet/config.yaml
bootstrap-kubelet.conf の作成
kubectl
コマンドで bootstrap-kubelet.conf
ファイルを生成します。
sudo su -
kubectl config --kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
set-cluster bootstrap \
--server='https://192.168.56.10:6443' \
--certificate-authority=/etc/kubernetes/pki/ca.crt
kubectl config --kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
set-credentials kubelet-bootstrap \
--token=w7b0wx.ekyppyi0gpy54eb7
kubectl config --kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
set-context bootstrap \
--user=kubelet-bootstrap \
--cluster=bootstrap
kubectl config --kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
use-context bootstrap
kubeadm-flags.env の作成
以下の内容で /var/lib/kubelet/kubeadm-flags.env
ファイルを作成します。
KUBELET_KUBEADM_ARGS="--container-runtime=remote --container-runtime-endpoint=/run/containerd/containerd.sock --pod-infra-container-image=k8s.gcr.io/pause:3.6"
kubelet を開始する
あとは kubelet サービスを開始するだけです。
kubelet が開始されたときに、--kubeconfig
で指定されたファイルが存在しない場合は、--bootstrap-kubeconfig
で指定された kubeconfig を利用して TLS Bootstrapping のプロセスが行われます。
CSR リクエストが完了しノードの証明書が発行されると、証明書ファイルが /var/lib/kubelet/pki
に配置され、その証明書を参照する --kubeconfig
ファイルが生成されます。
systemctl start kubelet
journalctl -u kubelet -f
node03
が追加されました。
% kubectl get nodes
NAME STATUS ROLES AGE VERSION
node01 Ready control-plane,master 19h v1.23.4
node02 Ready <none> 18h v1.23.4
node03 Ready <none> 107s v1.23.4
参考リンク
Previous Post
Next Post