TT Lab
はじめる
学ぶ 学習パス コース

Kubespray と Terraform でクラスターを構築する

インベントリは役割の割り当て表、group_vars はインストール内容

TT Labで続きを見る

一言でいうと

kubesprayはKubernetesをインストールするAnsibleプレイブックの集まりで、人が書くのは、インベントリ(誰が何を担当するか)とgroup_vars(何をインストールするか)の2つだけです。

なぜ必要なのか

kubeadmは、ノード1台の上でコントロールプレーンを組み立てるツールです。サーバーが10台なら、誰かが10台にランタイムをインストールし、カーネル設定を揃え、最初の1台でkubeadm initを実行し、残りで順番にjoinを行う必要があります。この作業をシェルスクリプトで書き始めると、すぐに「2回実行すると壊れるスクリプト」になります。kubesprayは、その繰り返しをAnsibleのロール(role)に移したものです。公式の比較ドキュメントは、kubesprayが「OS側の一般的な構成管理」を担当し、クラスターのライフサイクルに関する知識は、v2.3から内部でkubeadmを呼び出して借りていると説明しています。つまり、kubesprayで構築したコントロールプレーンも、中身はkubeadmが作った静的Podで、違いは、その前後のホストの準備・etcd・CNI・アドオンを誰がやってくれるかです。

どう動くのか

インベントリは役割の割り当て表です。kubesprayのドキュメントが定めているグループは3つです。

kube_control_plane   API 서버·스케줄러·컨트롤러 매니저가 뜨는 노드
kube_node            파드가 올라가는 노드(워커)
etcd                 etcd 멤버. 장애 대비에는 3대 이상, 반드시 홀수
k8s_cluster          kube_node + kube_control_plane (+ calico_rr) — kubespray 가 실행 중에 만든다

このコードブロックの韓国語の部分は、kube_control_planeはAPIサーバー・スケジューラー・コントローラーマネージャーが動くノード、kube_nodeはPodが載るノード(ワーカー)、etcdはetcdのメンバーで、障害に備えるには3台以上で必ず奇数、k8s_clusterはkube_nodeとkube_control_planeと(calico_rr)で、kubesprayが実行中に作ることを述べています。

k8s_clusterには落とし穴が1つあります。v2.32.0のサンプルinventory.iniはこのグループを書いておらず、boilerplate.ymlのdynamic_groupsロールが、プレイブックの中でgroup_byによって作ります。そのため、プレイブックの外のツールはこのグループを知りません。このVMで実測すると、group_vars/k8s_cluster/k8s-cluster.ymlにkube_versionを書いても、ansible-inventory --host node1はその値をnullと表示し、ansible ... -m debugで尋ねるとgroup_vars/allの値が勝ちます。インストール自体は問題なく行われますが、インストール前に値を確認しようとしたツールが、嘘をつきます。このコースはそのため、[k8s_cluster:children]にkube_control_planeとkube_nodeを書いておきます。古いサンプルがやっていた方法で、kubesprayが実行中に同じグループを作り直しても、結果は同じです。

1つのノードがkube_control_planeとkube_nodeの両方にあると、コントロールプレーンが仕事もします。etcdを別に置かないなら、[etcd:children]としてkube_control_planeを丸ごと入れます(stacked etcd)。このコースはVM 1台なので、3つのグループに同じノード1つを入れます。

グループ名は、つづりまで契約です。プレイブックの先頭のboilerplate.ymlが、dynamic_groupsロールで古い名前(kube-master、kube-node)だけを新しい名前に移してくれますが、それ以外の名前は知りません。そのあとvalidate_inventoryロールがインベントリを検査しますが、実測すると、[masters]と書いたときに「kube_control_planeが空なら止まる」という検査は、むしろ通過します。存在しないグループをgroups.get()がNoneで返し、Noneは空のリストとは異なるからです。失敗は、すぐ次の検査である「etcdの数が偶数なら止まる」で、object of type 'dict' has no attribute 'kube_control_plane'として起きます。エラーがグループ名を教えてくれないので、インストールを実行する前に、ansible-inventory --graphでツリーを目で見るのが、最も安い確認です。

group_varsはインストール内容です。サンプルは2系統に分かれています。

group_vars/all/*.yml            etcd 를 포함한 모든 노드 — 프록시, 오프라인 저장소, etcd 설정
group_vars/k8s_cluster/*.yml    클러스터 노드 — kube_version, container_manager, kube_network_plugin, 대역, 애드온
group_vars/kube_control_plane.yml   컨트롤 플레인만

このコードブロックの韓国語の部分は、group_vars/allはetcdを含むすべてのノード向けでプロキシ・オフラインリポジトリ・etcd設定、group_vars/k8s_clusterはクラスターノード向けでkube_version・container_manager・kube_network_plugin・CIDR範囲・アドオン、group_vars/kube_control_plane.ymlはコントロールプレーンだけ向けであることを述べています。

kubesprayのドキュメントの変数の層の表は短いです。インベントリのgroup_varsが最もよく使われ、host_varsはノードごとの例外で、extra vars(-e)は常に勝ちます。そして-eは、kubesprayがユーザーに約束していない内部変数を上書きするときに使うよう書かれています。Ansibleのルール上、同じ名前がgroup_vars/allとgroup_vars/k8s_clusterの両方にあると、より具体的な子グループ(k8s_cluster)が勝ちます。そのため、誰かがall.ymlにバージョン番号を書いておくと、その行は何の働きもしないまま、次の人をだまします。

ansible-core 2.19(Ansible 12)からは、条件式が必ずブール値である必要があります。-e key=valueは常に文字列を渡すので、v2.32.0のリリースノートは、ブール値を-e '{"drain_nodes": true}'のようにJSONで渡すよう書いています。

バージョンはチェックサムで決まります。kube_versionのデフォルト値は、roles/kubespray_defaults/vars/main/checksums.ymlにあるkubeletのチェックサム一覧の最初のキーで、受け入れる最小のバージョンは最後のキーです。v2.32.0では、デフォルトが1.36.4、最小が1.34.0です。チェックサムがないバージョンは、書いてもインストールされません。そしてkube_versionをインベントリに書かないと、kubesprayを新しいタグに上げた瞬間に、Kubernetesも一緒に上がります。このコースは1.35.8を書いておき、アップグレードのモジュールで1.36.4へ1つ上げます。

現場での姿

1つ目は、ディレクトリの落とし穴です。kubesprayリポジトリのansible.cfgは、inventory_ignore_extensionsに.iniを入れています。そのため、-i inventory/myclusterのようにディレクトリを渡すと、inventory.iniをスキップして「No inventory was parsed」という警告だけを残し、ホスト0個で動きます。このVMで実測した結果で、公式ドキュメントの例がいつも-i inventory/mycluster/inventory.iniとファイルを指している理由でもあります。

2つ目は、接続方式をどこに書くかです。ノードが1台のときは、node1 ansible_connection=localのようにインベントリの行に付けても、同じように動きます。ところが、ノードを増やす日にその行をコピーすると、新しいノードもlocalで接続され、コントロールノード自身に2回インストールする事故が起きます。接続情報をhost_vars/<노드>.ymlに出しておけば(プレースホルダーはノード名です)、インベントリは役割の割り当て表としてだけ残り、ノードを追加するときは、グループに名前を1行と、host_varsファイルを1つ(ansible_host、ansible_user)加えるだけで済みます。このコースのすべてのラボが、この形を使います。

3つ目は、コントロールノードのAnsibleのバージョンです。kubesprayはタグごとにrequirements.txtでAnsibleを固定していて、v2.32.0はansible==12.3.0(ansible-core 2.19)を要求します。ドキュメントは、仮想環境にそのままインストールするよう勧めていて、合わないバージョンなら、ansible_version.ymlが最初のタスクで止まります。システムパッケージのAnsibleで実行して詰まることが、よくあります。

次のラボですること

サンプルをコピーして、ノード1台を3つのグループに入れ、接続方式をhost_varsに出します。ディレクトリとファイルを渡したときにホスト数がどう違うかを数え、バージョンをgroup_varsに固定したうえで、all・k8s_cluster・-eのどれが勝つかを確認します。最後に、グループ名を間違えたインベントリがどこでどんなメッセージで止まるかを見て、自分のインベントリがboilerplateの検査を通過するかを確認します。