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

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

5 台構成のクラスターを VM 1 台で設計・検査する

TT Labで続きを見る

目標

コントロールプレーン3台(etcd兼用)とワーカー2台のインベントリを設計し、実際のノードなしでも確認できること、すなわちグループの配置、kubesprayのインベントリの検査、etcdの奇数のルールとクォーラム、APIサーバーのロードバランシング方式、ノードを追加して削除するときにプレイブックが対象にする場所を、VM 1台で確認します。

なぜ重要なのか

1台構成のクラスターから複数台に移るときに変わるのは、インストールのコマンドではなく、設計です。コントロールプレーンを何台置くか、etcdを兼ねさせるか、ワーカーがどのAPIサーバーに接続するか、ノードを追加して削除するときに、どのプレイブックをどこに絞って実行するかは、すべてインベントリと、いくつかの変数で決まります。 そして、これらの判断は、インストールを始めた後では変更しにくいです。実際のノードを作る前に、インベントリをツールで検査しておけば、インストールの途中で止まることの多くを、事前に防げます。 ノードのjoin・障害・ローリングアップグレードを、実際のVM複数台で行うラボは、1つのセッションでVMを複数台提供する機能が用意されたら、このモジュールの後に続きます。

ステップ

  1. /opt/ks/kubespray/inventory/sampleをコピーして(コピー先: /root/ks/inventory/ha)、cp1・cp2・cp3をkube_control_planeに(etcdはkube_control_planeを子として)、w1・w2をkube_nodeに入れ、k8s_clusterが2つのグループを子として受け取るように書いてください(書き込み先: /root/ks/inventory/ha/inventory.ini)。コントロールプレーンはワーカーを兼ねません。ノードごとに、ansible_hostとip(cp1=192.0.2.11からw2=192.0.2.15まで順に)とansible_user: ubuntuを置いてください(置き場所: /root/ks/inventory/ha/host_vars/<노드>.yml。プレースホルダーはノード名です)。
  2. /opt/ks/kubesprayでansible-playbook -i /root/ks/inventory/ha/inventory.ini playbooks/boilerplate.yml -e ansible_connection=localを実行して、出力の全体を残してください(保存先: /root/ks/ha/validate.log)。5つのノードすべてで、PLAY RECAPがfailed=0である必要があります。
  3. ステップ1のインベントリをコピーして、etcdをcp1とcp2の2台だけにして([etcd]に2つの名前)ください(コピー先: /root/ks/inventory/ha-even)。同じ方法(-e ansible_connection=local)でboilerplateを実行してみてください。次のフィールドを書いてください(書き込み先: /root/ks/ha/even.json)。failed_task(失敗したタスク名、ロールの接頭辞なし)、etcd_members(そのインベントリのetcdのホスト数)、quorum(その数の過半数)、tolerated_failures(何台まで失っても書き込めるか)です。
  4. etcdのメンバー数1・3・5・7のそれぞれについて、quorum(過半数)とtolerated_failures(書き込みを維持しながら失えるメンバー数)を、{"1": {"quorum": .., "tolerated_failures": ..}, "3": {...}, ...}の形で書いてください(書き込み先: /root/ks/ha/quorum.json)。
  5. kubesprayのroles/kubespray_defaults/defaults/main/main.ymlとdocs/operations/ha-mode.mdを読み取り、次のフィールドを書いてください(書き込み先: /root/ks/ha/lb.json)。localhost_lb(外部ロードバランサーを定義しなかったときのloadbalancer_apiserver_localhostの結果、ブール値)、lb_type(loadbalancer_apiserver_typeのデフォルト値)、lb_port(そのローカルプロキシが使うポート。loadbalancer_apiserver_portがないときに従う値、数値)、who_uses_it(そのローカルプロキシを使うノードのグループ: "kube_node"か"kube_control_plane"のうち、ドキュメントが述べているほう)です。
  6. ステップ1のインベントリのkube_nodeにw3(host_varsに192.0.2.16)を追加してください。そのあと/opt/ks/kubesprayで、scale.yml --list-hosts --limit w3とremove-node.yml --list-hosts -e node=w2を、インベントリ/root/ks/inventory/ha/inventory.iniで実行してみて、次のフィールドを書いてください(書き込み先: /root/ks/ha/plan.json)。scale_node_play_hosts(scale.ymlの「...(node)」で終わるplayが対象にするホストのソートした配列)、remove_confirm_hosts(remove-node.ymlの「Confirm node removal」のplayが対象にするホストのソートした配列)です。w3を追加するコマンド、w2を削除するコマンド、ノードを1台ずつ上げるアップグレードのコマンドを、1行ずつ書いてください(書き込み先: /root/ks/ha/runbook.sh。実行しません)。
  7. /opt/ks/kubespray/ansible.cfgを読み取り、次のフィールドを書いてください(書き込み先: /root/ks/ha/facts.json)。cache_plugin(fact_cachingの値)、cache_dir(fact_caching_connectionの値)、cache_timeout_sec(fact_caching_timeout、数値)、refresh_playbook(--limitを使う前に制限なしで実行するようドキュメントが述べているプレイブックのパス、kubesprayリポジトリを基準にした相対パス)です。

参考

コントロールプレーン3台、ワーカー2台

/opt/ks/kubespray/inventory/sampleをコピーして(コピー先: /root/ks/inventory/ha)、cp1・cp2・cp3をkube_control_planeに(etcdはkube_control_planeを子として)、w1・w2をkube_nodeに入れ、k8s_clusterが2つのグループを子として受け取るように書いてください(書き込み先: /root/ks/inventory/ha/inventory.ini)。コントロールプレーンはワーカーを兼ねません。ノードごとに、ansible_hostとip(cp1=192.0.2.11からw2=192.0.2.15まで順に)とansible_user: ubuntuを置いてください(置き場所: /root/ks/inventory/ha/host_vars/<노드>.yml。プレースホルダーはノード名です)。

モジュール1の1台構成のインベントリと形は同じで、グループに名前が増え、host_varsのファイルが増えるだけです。接続方式をインベントリの行に置かなかった理由が、ここで明らかになります。ipはkubesprayがAPIサーバーとetcdを結び付けるアドレスで、ansible_hostはAnsibleがsshで届くアドレスです。管理ネットワークとサービスネットワークが異なれば、両者は異なります。192.0.2.0/24はドキュメント用に予約されたCIDR範囲なので、実際には届きません。

sshなしでインベントリの検査だけ

/opt/ks/kubesprayでansible-playbook -i /root/ks/inventory/ha/inventory.ini playbooks/boilerplate.yml -e ansible_connection=localを実行して、出力の全体を残してください(保存先: /root/ks/ha/validate.log)。5つのノードすべてで、PLAY RECAPがfailed=0である必要があります。

boilerplateはfactsを集めず、インベントリと変数だけを検査するので、接続をlocalで上書きすれば、実際のノードなしでも実行できます。-eはhost_varsより優先されるので、この実行でだけ接続方式が変わります。インストールを始める前のインベントリのレビューに使える、最も安い確認です。

etcdが2台なら

ステップ1のインベントリをコピーして、etcdをcp1とcp2の2台だけにして([etcd]に2つの名前)ください(コピー先: /root/ks/inventory/ha-even)。同じ方法(-e ansible_connection=local)でboilerplateを実行してみてください。次のフィールドを書いてください(書き込み先: /root/ks/ha/even.json)。failed_task(失敗したタスク名、ロールの接頭辞なし)、etcd_members(そのインベントリのetcdのホスト数)、quorum(その数の過半数)、tolerated_failures(何台まで失っても書き込めるか)です。

etcdは、書き込みのたびに過半数(クォーラム)の同意が必要です。過半数は、メンバー数を2で割った商に1を足したものです。2台なら過半数が2なので、1台を失うだけで止まりますが、1台のときも1台を失うと止まるので、2台は1台よりよいところがなく、メンバーだけが増えます。kubesprayは、これをインベントリの検査で止めます。

何台まで失ってもよいか

etcdのメンバー数1・3・5・7のそれぞれについて、quorum(過半数)とtolerated_failures(書き込みを維持しながら失えるメンバー数)を、{"1": {"quorum": .., "tolerated_failures": ..}, "3": {...}, ...}の形で書いてください(書き込み先: /root/ks/ha/quorum.json)。

過半数はn//2 + 1、耐えられる障害はn - 過半数です。メンバーを増やすと、耐えられる数は増えますが、書き込みのたびに、より多くのメンバーの同意を待つ必要があります。etcdのドキュメントが、本番のクラスターに3台か5台を勧め、7台を超えないよう言っている理由が、この表にあります。

ワーカーはどのAPIサーバーに接続するか

kubesprayのroles/kubespray_defaults/defaults/main/main.ymlとdocs/operations/ha-mode.mdを読み取り、次のフィールドを書いてください(書き込み先: /root/ks/ha/lb.json)。localhost_lb(外部ロードバランサーを定義しなかったときのloadbalancer_apiserver_localhostの結果、ブール値)、lb_type(loadbalancer_apiserver_typeのデフォルト値)、lb_port(そのローカルプロキシが使うポート。loadbalancer_apiserver_portがないときに従う値、数値)、who_uses_it(そのローカルプロキシを使うノードのグループ: "kube_node"か"kube_control_plane"のうち、ドキュメントが述べているほう)です。

コントロールプレーンが複数あると、ワーカーのkubeletとkube-proxyが、どのAPIサーバーに接続するかを決める必要があります。kubesprayのデフォルトは、外部ロードバランサー(loadbalancer_apiserver)を別に定義しないと、各ワーカーにnginxプロキシを起動して、localhostで受け取り、すべてのAPIサーバーに振り分ける方式です。ドキュメントは、この方式が専用のLBより効率は劣りますが、VIPの管理が面倒な場所では実用的だと説明しています。

ノードを追加して削除するコマンドが対象にする場所

ステップ1のインベントリのkube_nodeにw3(host_varsに192.0.2.16)を追加してください。そのあと/opt/ks/kubesprayで、scale.yml --list-hosts --limit w3とremove-node.yml --list-hosts -e node=w2を、インベントリ/root/ks/inventory/ha/inventory.iniで実行してみて、次のフィールドを書いてください(書き込み先: /root/ks/ha/plan.json)。scale_node_play_hosts(scale.ymlの「...(node)」で終わるplayが対象にするホストのソートした配列)、remove_confirm_hosts(remove-node.ymlの「Confirm node removal」のplayが対象にするホストのソートした配列)です。w3を追加するコマンド、w2を削除するコマンド、ノードを1台ずつ上げるアップグレードのコマンドを、1行ずつ書いてください(書き込み先: /root/ks/ha/runbook.sh。実行しません)。

--list-hostsは、何も変更せずに、各playが誰を対象にするかだけを表示します。scale.ymlは新しいノードにだけインストールするので、--limitで絞り、remove-node.ymlは、-e node=<名前>で削除するノードを受け取ります。kubesprayのドキュメントは、--limitを使う前にfacts.ymlを制限なしで1回実行して、factsのキャッシュを新しくするよう勧めています。1台ずつ上げるアップグレードは、serial変数で調整します。

--limitの前にfacts.ymlを実行する理由

/opt/ks/kubespray/ansible.cfgを読み取り、次のフィールドを書いてください(書き込み先: /root/ks/ha/facts.json)。cache_plugin(fact_cachingの値)、cache_dir(fact_caching_connectionの値)、cache_timeout_sec(fact_caching_timeout、数値)、refresh_playbook(--limitを使う前に制限なしで実行するようドキュメントが述べているプレイブックのパス、kubesprayリポジトリを基準にした相対パス)です。

--limitで新しいノードだけを対象にすると、その実行は他のノードのfactsを集めず、キャッシュを使います。ところが、設定ファイル(例: /etc/hosts、etcdのメンバーの一覧、ロードバランサーのバックエンド)は、すべてのノードのfactsから作られます。キャッシュが空か古いと、新しいノードが見当違いのアドレスを受け取ります。アップグレードのドキュメントのNode-based upgradeの節を見てください。