Ansible で作るおうちラボ HA Kubernetes クラスタ
イントロ
私はたびたびおうちラボを作っては全部真っ新にして作り直すということをしています。直近の環境の変化として、ハイパーバイザーとして動かせる程度の強力なマシンが仲間入りしましたので、以下を課題として新たにおうちラボを構築しなおすことにしました。
- サーバ
- より多くのVMを立てて多様なLinuxディストロを試す
- 構築・運用
- AnsibleやGitLab CI/CDで手作業を減らす
- Kubernetesクラスタのデザイン
- 外部Etcdトポロジー採用
- Control planeをkeepalivedとhaproxyで冗長化
-
Calicoの代わりとしてCiliumを試す
- LoadbalancerおよびL2広報機能に関して、MetalLBをお役御免に
- Gateway API実装に関して、NGINX fabric gatewayをお役御免に
- Volume provisioningに関して、MinIO DirectPVの代わりとしてLonghornを試す
- データベースに関連し、Bytebaseの代わりとしてPercona Everestを試す
この内、今回はAnsibleを用いたKubernetesクラスタの構築と、CiliumのL2AnnouncementのデモとしてCiliumのHubble UIへのアクセスを用意するところまでを記事にしたので、よろしければご覧ください。
本記事でカバーしている点
本記事で触れる内容について、次の通りです:
- サーバをAnsible管理用にオンボードさせる
- ちょっとしたAnsibleの紹介として
gather_facts
の実行や、単純なパッケージアップグレードや再起動をAnsible playbookとして実行 - Dockerで動かすDNSサーバをAnsibleで用意
- Kubernetesクラスタへの参加ノードとしての必要事前準備をAnsibleで実行
- Etcdクラスタ構築、Kubernetesクラスタ構築をAnsibleで実行
- Ciliumのインストール
- Ciliumのデモ
このプロジェクトの公開レポジトリはこちらに用意してあります: https://github.com/pkkudo/homelab-v3
本記事でカバーしていない点
サーバを立てるところはスキップしています。本記事はすでに稼働しているサーバがある状態から始めていきます。
実際の構築とは別に、本記事用にテスト環境をProxmoxとHyper-Vで用意したのですが、通常のインストーラーからOSインストールが完了したところから、あるいはCloud-initイメージからVMを立ち上げたところから本記事の作業手順が進んでいきます。
また今回、RedHat Enterprise Linuxもサーバリストに含めていますが、これはパッケージレポジトリが利用できる状態までしてから (rhc connect
だけでしょうか) 本記事でカバーしている作業を進めています。
Kubernetesクラスタのデザインの説明
まず、初めて私が構築したKubernetesクラスタと、続いて今回構築するKubernetesクラスタのデザインについて触れていきます。
ベーシックなKubernetesクラスタ
最初に構築したKubernetesクラスタはx1 Control plane、x2 Workersの、三つのノードからなっているものでした。サーバを三台、Kubernetesの要件に合うよう設定変更し、パッケージをインストールし、control planeでクラスタ構築、worker二台をクラスタに参加させるといった手順で構築しました。
Control planeノードはオペレータやKubernetesのコンポーネント自身らがクラスタとやり取りするための唯一の宛先です。
クラスタ情報のメモリストアであるetcdも同じControl plane上で稼働している重要サービスです。
今回構築するKubernetesクラスタ
今回はせっかくたくさんのVMを立ち上げられるようになったので外部etcdクラスタ用に三台、KubernetesクラスタのControl planeとして三台、そしてWorkerとして一台用意することにしました。
EtcdサービスはこれでControl planeとは独立して存在することとなり、Control Planeから見ると利用できるEtcdサービスが冗長化されていることとなります。
Control Plane自体も冗長化されます。
それぞれのControl Planeノードはkeepalivedサービスで高可用なVirtual IP Address (VIP)を設け、haproxyサービスでkube-apiserverへのロードバランサを設けます。
KeepalivedでVIPを設ける
Keepalivedの簡単な図です。Control Plane各ノード上のKeepalivedサービスはお互いに連携し、誰かしらがVIP (ここでは192.0.2.8)を担当するよう調整します。
Haproxyでロードバランサを設ける
Haproxyの図です。Control Plane各ノード上のHaproxyサービスは8443ポートで通信を受け付け、生きているどれかのkube-apiserverへ通信を流します。
Kube-apiserverはControl Plane上でデフォルトでは6443ポートでサービス応答している、Control Planeに対するメインコンタクトポイントです。今回構築するKubernetesクラスタではクラスタエンドポイント、クラスタへの通信先をVIP:8443とし (192.0.2.8:8443)、その通信はその時点でVIPを担当しているControl Plane上のHaproxyサーバが受け取り、そしてHaproxyはその時点でヘルスチェックに合格しているkube-apiserverへ通信を流します。
サーバリストとIPアドレス
以下が本記事で登場するサーバ一覧です。
記事上およびレポジトリ上ではドキュメント用の192.0.2.0/24サブネットを用いていますが、実際は普通のプライベートIPアドレスレンジを使っています。
ドメインについても同様で、今回はlab.example.netとしてあります。
そしてディストロはDebian, Ubuntu, RHEL, Rocky, OracleLinuxなどいろいろ混ざっていますが、今回用意してあるAnsible playbookは少なくともこれらのOSでは通るように作られています。
hostname | ipaddr | role | os | cpu | memory | disk | hypervisor |
---|---|---|---|---|---|---|---|
lab-cp1 | 192.0.2.1 | kubernetes control plane | debian | 4 | 4GB | 64GB | hyper-v |
lab-cp2 | 192.0.2.2 | kubernetes control plane | rocky | 4 | 4GB | 64GB | proxmox |
lab-cp3 | 192.0.2.3 | kubernetes control plane | ubuntu | 4 | 4GB | 64GB | proxmox |
lab-worker1 | 192.0.2.4 | kubernetes worker node | rhel | 4 | 4GB | 64GB | hyper-v |
lab-etcd1 | 192.0.2.5 | etcd node | debian | 2 | 4GB | 64GB | hyper-v |
lab-etcd2 | 192.0.2.6 | etcd node | debian | 2 | 4GB | 64GB | proxmox |
lab-etcd3 | 192.0.2.7 | etcd node | oracle | 2 | 4GB | 64GB | proxmox |
lab-ns1 | 192.0.2.16 | run dns server using docker | rhel | 1 | 1GB | 32GB | proxmox |
lab-ns2 | 192.0.2.17 | run dns server using docker | debian | 1 | 2GB | 10GB | hyper-v |
サーバ以外に少しだけIPアドレスが登場します。
- VIP、192.0.2.8
- エンドポイントはlab-kube-endpoint.lab.example.net:8443となる
- DNSで名前解決され、192.0.2.8となる
- 192.0.2.8:8443への通信はkeepalived + haproxyでいずれかのControl Planeで受け取られ、いずれかのControl Plane上でデフォルトでは6443ポートへと流れる
- エンドポイントはlab-kube-endpoint.lab.example.net:8443となる
- 192.0.2.9、本記事最後の方で取り上げるHubble UIへのアクセス、CiliumのL2Announcementで使われる
プロジェクトの準備
プロジェクトの準備を進めましょう。大まかな作業ステップは次の通りです:
- レポジトリを
git clone
する - Pythonを用意し、Ansibleをインストールする
- Ansible collectionをインストールする
- 今回用意したAnsibleのコンフィグについて説明
- Ansibleのインベントリ、変数、Ansible Vaultを用いた暗号化について説明
- Ansibleマスターのための名前解決手段をhostsファイルないしDNSで用意
- Ansibleユーザで用いるSSHキーペアの用意
- Ansibleインベントリ、変数などの見直し、更新必要部分の対応
- kube-endpoint用のVIP
- nameserversリスト
- DNSレコード用のテンプレートファイルなどなど
レポジトリ
公開されているこちらのレポジトリをクローンしてきてください。
git clone -b zenn-post https://github.com/pkkudo/homelab-v3
cd homelab-v3
# optionally, set your own origin
git remote remove origin
git remote add origin {your_repo}
Ansibleのインストール
MiseやPoetryといったツールで用意するのもよいですが、以下の手順でインストールできる最新のもので差し支えないはずです。
sudo apt install python3 python3-venv
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip ansible
ansible-galaxy collection install -r requirements.yml
実際に私が本手順を通しでやり直した際にはcloud-init Debian 12イメージでやったのですが、その際はmiseをインストールしてpython 3.12を用意し、poetryをインストールしてpoetry経由でAnsibleをインストールする、といった以下の手順を踏んでいます。
# install mise
curl https://mise.run | sh
echo "eval \"\$(/home/YOUR_USERNAME_HERE/.local/bin/mise activate bash)\"" >> ~/.bashrc
# re-logon and come back to the cloned homelab-v3 directory
# and have mise trust .mise.toml file in this project directory
mise trust
# install python version as described in .mise.toml file
mise up
# create venv at .venv and load the environment
python -m venv ~/homelab-v3/.venv
source .venv/bin/activate
# enable mise experimental feature to have mise automatically activate python venv
mise settings experimental=true
# get latest pip, install poetry, and install ansible using poetry
pip install -U pip
pip install poetry
# install ansible as defined in pyproject.toml file
poetry install --no-root
# install ansible collections
ansible-galaxy collection install -r requirements.yml
もしsshpassがなければ
少し後の手順となりますが、もしAnsible playbook実行時に以下のようなエラーが出た場合はsshpassをインストールするなどして解消できます (sudo apt install sshpass
など)。
lab-etcd1 | FAILED! => {
"msg": "to use the 'ssh' connection type with passwords or pkcs11_provider, you must install the sshpass program"
}
Ansibleのコンフィグファイル
本プロジェクトで用意したAnsibleコンフィグについてです。
[genera]
セクション:
-
inventory
はデフォルトで使うインベントリファイルの指定 -
vault_password_file
はデフォルトで使うAnsible Vault用パスワードファイルの指定 -
roles_path
はAnsible Rolesのパス指定 -
collections_path
も同様にAnsibleのCollectionsのパス指定
[ssh_connection]
セクション:
-
ssh_args
はリモートホストへsshアクセスする際に用いる引数の設定で、セキュアではないがおうちラボ規模で便利に使うには良い設定
本来もっと膨大な数のコンフィグが用意されていますが、それに関しては公式を参照してください。すべてのコンフィグを含んだサンプルコンフィグファイルが簡単に生成できます。
Ansibleのインベントリ、変数、暗号化など
インベントリファイルはAnsibleで扱うホストのリストが含まれています。複数のホストをグループ化することもできます。Ansibleのタスク実行時、対象をホストおよびグループで指定できます。
# list "all" hosts defined in the inventory file at ./inventory/hosts.yml
ansible -i inventory/hosts.yml all --list-hosts
# since the default inventory file is set in the ansible config file, you can omit -i option to specify which inventory file to use
ansible all --list-hosts
# confirm hosts in different groups
ansible lab --list-hosts
ansible lab_kubernetes --list-hosts
ansible lab_k8s_cp --list-hosts
ansible lab_k8s_worker --list-hosts
ansible lab_etcd --list-hosts
各グループごとおよびホスト単位でも、変数を設定することができます。例として./inventory/group_vars/lab
をみてください。ここで指定された変数はlabグループのホスト全てに適用されます。
例えばdomain_suffix
がlab.example.net
と設定されており、ansible_host
が"インベントリホスト名".domain_suffix
と設定されています。すると例えばlab-cp1ホストの場合、そのansible_host
の値はlab-cp1.lab.example.net
となります。
# ansible remote access
ansible_user: "{{ vault_ansible_user }}"
ansible_username: "{{ vault_ansible_user }}" # required by bootstrap playbook
ansible_ssh_private_key_file: "{{ playbook_dir }}/files/ssh/id_ed25519_ansible"
ansible_ssh_pubkey: "{{ playbook_dir }}/files/ssh/id_ed25519_ansible.pub"
domain_suffix: "lab.example.net"
ansible_host: "{{ inventory_hostname_short }}.{{ domain_suffix }}"
そして、ansible_user
はまた別の変数、vault_ansible_user
を指しています。ansible_username
も同様です。これはAnsibleプロジェクトにおいて重要な情報を暗号化して保持する方法の一つです。./inventory/group_vars/lab/vars.yml
ファイル上で、これらの変数が設定されていることが分かりやすいようここに記載しており、更にその実際の内容はvault_*
と銘打った別の暗号化されたファイルにあることを明示しています。
本プロジェクトではAnsible Vaultのパスワードは./.vault_pass
というファイルを用いるよう設定ファイルにも書いており、(当然実際はパブリックな場所に出すべきではないですが)パスワードファイルもレポジトリに含めてあるので、以下の暗号化された出力、複合化された出力が同じようにみられると思います。
$ cat inventory/group_vars/lab/vault.yml
$ANSIBLE_VAULT;1.1;AES256
30656633313135653464373834663137646662633137376564653864653565333661333339616661
6133336638316131643831333033353334366435613062610a373234333434313734333333346164
35353339343862666466653233353961663661363762353939616662356237626163323634363630
3132653733616464390a316332316332663331656236386331656630623364343061393835393038
63623466383437633162353533373036623038346433616361363839643433343563636466363363
34313264623833626363363834393837366465666534623465626237383532623939356664353261
373531613066666539643962393862323761
$ ansible-vault view inventory/group_vars/lab/vault.yml
# ansible remote access
vault_ansible_user: "ansible-hlv3"
Ansibleマスターのための名前解決の用意について
Ansibleマスターはタスク実行のために、リモートホストへsshアクセスする必要があります。ただ、インベントリファイルを見ると記載されているのはlab-cp1やlab-worker1などのホスト名だけです。実際にAnsibleが宛先として用いるのはansible_host
であり、この値はlabグループの変数ファイルで小細工することによりlab-cp1の場合はlab-cp1.lab.example.net
となり、lab-worker1の場合はlab-worker1.lab.example.net
となるようにしています。
名前解決で悩みたくないならば、一応labグループ変数ファイルからansible_host
の行を削除し、インベントリファイルにIPアドレスベタ打ちといったやり方でもいけるとは思います。
lab:
children:
lab_kubernetes:
children:
lab_k8s_cp:
hosts:
192.0.2.1: # instead of lab-cp1
lab_k8s_worker:
hosts:
192.0.2.4: # instead of lab-worker1
ただ本記事では、ひとまずの対応策としてAnsibleマスターのhostsファイルの書き換え、そしてのちのステップでDNSサーバを設けることとします。
用意されている./inventory/hosts-list.txt.example
を元にAnsibleインベントリのホストの一時的な名前解決用にhostsファイルを用意しましょう。自身の環境にあるサーバの実際のIPアドレスで用意すると良いです。
cp inventory/hosts-list.txt.example inventory/hosts-list.txt
# edit inventory/hosts-list.txt
# and set the actual IP addresses for the hosts
# append the hosts-list.txt lines to the /etc/hosts file
sudo tee -a /etc/hosts < inventory/hosts-list.txt
# test
ping lab-cp2
ping lab-cp3.lab.example.net.
ちなみに、もちろんドメイン名も好きに変更して本手順をなぞっていただけるので、labグループの変数ファイルなどでdomain_suffix
の値を更新してください。
次に、準備の一環としてのちに用意するDNSサーバ用のレコードのテンプレートファイルも用意しておきましょう。フォーマットは先と同様、見たままかと思います。各行各ホスト用にIPアドレスを書き換えてください。
なおDNSサーバ用のこちらのファイルではホスト以外のレコードがあるのに気づくと思います。一つはlab-kube-endpoint.lab.example.net
であり、これはControl PlaneノードらのVIP用です。もう一つはhubble-ui.lab.example.net
であり、Ciliumのデモで使います。
cp roles/dns/templates/a-records.j2.example roles/dns/templates/a-records.j2
# edit this a-records.j2 file
# to match the actual environment
#
# for example, if your homelab subnet is 10.8.0.0/24,
# some of your records might look as follows:
# local-data: "lab-cp1.{{ domain_suffix }}. IN A 10.8.0.20"
# local-data: "lab-cp2.{{ domain_suffix }}. IN A 10.8.0.21"
Ansibleユーザ用SSHキーペア
次です。Ansibleユーザ用にSSHキーペアを用意しましょう。ユーザ名はlabグループ変数ファイル(の暗号化されたvault.ymlファイルの方で指定されている)"ansible-hlv3"となります。
私のおうちラボバージョン3ということで仮にこのような名前にしていますが、ぜひ変更してください。変更される場合はせっかくですのでAnsible Vaultのキーも更新してみましょう。
手順としては:
-
./.vault_pass
および./inventory/group_vars/lab/vault.yml
ファイルを削除 - 新たに
./.vault_pass
ファイルを用意 -
ansible-vault create inventory/group_vars/lab/vault.yml
で新たに暗号化された変数ファイルを作成し、vault_ansible_user
をセットする
なお参考までに、私は以下のコマンドで./.vault_pass
ファイルを用意しています。
# generating random 31 characters alpha-numerical string used as ansible vault password
tr -dc '[:alnum:]' < /dev/urandom | head -c 31 > .vault_pass
ユーザ名に関しては以上とし、SSHキーペアの話題に戻りましょう。ssh-keygen
コマンドで生成できますので、新たなキーペアを./playbooks/files/ssh
以下に用意しましょう。
元々用意したサーバで用いているものを設置するのでも全く問題ありません。ファイル名に関してはlabグループ変数ファイルで指定しているので、用意したファイル名と変数ファイルで指定しているファイル名が合致するようにしてください。
# prepare playbooks dir
# and files/ssh directory to place ssh key pair used by ansible master
mkdir -p playbooks/files/ssh
cd playbooks/files/ssh
ssh-keygen -t ed25519 -f id_ed25519_ansible
こうしてansible_host
とansible_ssh_private_key_file
変数および用意したファイルが組み合わさることによって、Ansibleマスターは例えばlab-cp1ホストにアクセスしに行く際、実質的にssh ansible-hlv3@lab-cp1.lab.example.net -i playbooks/files/ssh/id_ed25519_ansible
としてアクセスすることになります。
そのほかに設定すべき変数について
実際にAnsibleを実行し始める前に、もういくつか変数を確認・更新する必要があります。
Nameservers
本プロジェクトではのちにlab-ns1とlab-ns2ホストがDNSサーバとして構築されます。これら2サーバのIPアドレスで./roles/dns/defaults/main.yml
内で指定されているnameservers
変数を更新してください。仮で["192.0.2.16", "192.0.2.17"]
あたりが設定されています。
のちに実行するplaybookで、各ホストのnameserverをここで指定した宛先に書き替えることになります。
なお、例えばrolesではなくlabグループの変数ファイルなどでnameservers
変数を設定するのでも大丈夫です。そしてのちのち、例えばdevグループなどが作られたとして、そのグループには別のnameservers
の値をセットするといったこともできます。
デフォルトでは./roles/dns/defaults/main.yml
で指定されているもの、そしてグループごとにそれぞれの変数ファイルで上書き指定することもできる、とイメージして頂ければ良いです。
kube-endpoint用のVIP
kube-endpoint用のVIPを./roles/kubernetes/defaults/main.yml
内で指定してください。デフォルトで192.0.2.8となっているので、ご自身の環境のサブネットよりVIPとして割り当てたい値をセットしてください。これは先にDNSサーバのテンプレートファイルで触れたlab-kube-endpoint.lab.example.net.
と同じものです。
ちなみにこちらもnameserversと同様に、グループ変数ファイルなどの方で設定するのでも問題ありません。
kube_endpoint: "lab-kube-endpoint.{{ domain_suffix }}"
kube_endpoint_port: 8443
kube_endpoint_vip: 192.0.2.8 # CHANGE THIS
本プロジェクトで設けられている他の変数について
本プロジェクトではこれまで触れた以外にもあちこちで変数がセットされています。
それら全て説明していくより、一般的に気になるであろう、のちのち変更したいと思うであろう変数を以下にリストします:
- version of kubernetes, cni, runc, containerd, etcd found in
./roles/kubernetes/defaults/main.yml
- 1.32.2 for kubernetes
- 2.0.2 for containerd
- 1.2.5 for runc
- 1.6.2 for cni
- 3.5.18 for etcd
- image and image tag for keepalived and haproxy found in
./roles/kubernetes/defaults/main.yml
- osixia/keepalived:stable for keepalived last updated in 2020
- 古い!?
- 私の実環境では、自分でビルドし、プライベートのイメージレジストリに載せているバージョン2.3.2のイメージを使っています
- これに関して、できたら今後別記事で紹介したいです
- haproxy:3.1.5-alpine for haproxy
- osixia/keepalived:stable for keepalived last updated in 2020
- unbound DNS image and tag in
./roles/dns/templates/env.j2
- mvance/unbound:1.21.1
- docker version in
./roles/docker/defaults/main.yml
- 27.5.1
- ansible collections and their versions in
./requirements.yml
上記いずれも大本のサービスのURLをファイル内に記載してあるので、そちらから最新バージョンが確認できます。
最初のplaybook - bootstrap
準備作業がやっと終わりました。最初のplaybookを実行して、リモートホストに対するアクセスクレデンシャルを整理・更新しましょう。
実行するタスクは次の通りです:
-
ansible-admin
グループの作成 -
ansible_user
をansible-admin
グループのメンバーとして作成 - sudoのインストールおよび
ansible-admin
グループに対してパスワードレスsudo実行許可 -
ansible_user
にsshアクセス許可用の公開鍵をセット
ちなみに私のテストランで用いたVMのうちcloud-initから用意したものは、作成時点ですでにユーザ、ssh公開鍵セットが済んでいるため、以下ではそれに応じた実行コマンドを例として記載しています。
# bootstrap on cloud-init VMs
ansible-playbook playbooks/bootstrap.yml --limit lab-cp2:lab-cp3:lab-etcd2:lab-etcd3:lab-ns1
# bootstrap on the other VMs
# specifying username and password
# if your existing username to use is happyansibleuser, "-e ansible_user=happyansibleuser"
# if ssh key logon is not setup for that user, you can omit "-e ansible_ssh_private_key_file=..."
# if no remote host is going to ask for logon password, you can omit "-k" option
# the capital "-K" option is always needed to enter privilege password to execute tasks that require sudo or su
ansible-playbook playbooks/bootstrap.yml --limit lab-cp1:lab-etcd1:lab-worker1:lab-ns2 -e ansible_user=$USER -e ansible_ssh_private_key_file=~/.ssh/id_ed25519 -k -K
# ansible will ask you for "-k" password and "-K" password
タスクが実施されると、指定のユーザ名およびSSHキーで対象ホストにアクセスできるようになっています。
$ ssh ansible-hlv3@lab-etcd3 -i playbooks/files/ssh/id_ed25519_ansible cat /etc/hostname /etc/os-release
lab-etcd3
NAME="Oracle Linux Server"
VERSION="9.5"
ID="ol"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="9.5"
PLATFORM_ID="platform:el9"
PRETTY_NAME="Oracle Linux Server 9.5"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:oracle:linux:9:5:server"
HOME_URL="https://linux.oracle.com/"
BUG_REPORT_URL="https://github.com/oracle/oracle-linux"
ORACLE_BUGZILLA_PRODUCT="Oracle Linux 9"
ORACLE_BUGZILLA_PRODUCT_VERSION=9.5
ORACLE_SUPPORT_PRODUCT="Oracle Linux"
ORACLE_SUPPORT_PRODUCT_VERSION=9.5
$ ssh ansible-hlv3@lab-cp2 -i playbooks/files/ssh/id_ed25519_ansible id && cat /etc/hostname /etc/os-release
uid=1000(ansible-hlv3) gid=1000(ansible-hlv3) groups=1000(ansible-hlv3),1001(admin-ansible) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
crocus
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
また、例えば以下のコマンドでちょっとしたピンポンチェックが実施できます。
# targeting hosts in "lab" group
ansible lab -m ping -e ansible_ssh_private_key_file=playbooks/files/ssh/id_ed25519_ansible
Gathering Facts
次に、Ansibleのgather_facts
タスクを走らせて、Ansibleが対象ホストについてどれだけの情報を確認しているのか見てみましょう。
用意されているplaybookを実行するとローカルにディレクトリとファイルが作られます。ファイルにはホストごとの情報が含まれています。それらのファイルを見てみると、例えばパッケージマネージャ、サービスマネジャ、OSファミリー、ディストロ情報やNIC情報など、他にも膨大な情報があることが分かります。
# run the playbook
ansible-playbook playbooks/gather_facts.yml
# facts gathered are stored locally in ./playbooks/facts/
ls -1 playbooks/facts
出力例:
$ grep -C2 family playbooks/facts/lab-etcd2.json
],
"nodename": "lab-etcd2",
"os_family": "Debian",
"pkg_mgr": "apt",
"proc_cmdline": {
$ grep distribution playbooks/facts/lab-etcd2.json
"distribution": "Debian",
"distribution_file_parsed": true,
"distribution_file_path": "/etc/os-release",
"distribution_file_variety": "Debian",
"distribution_major_version": "12",
"distribution_minor_version": "9",
"distribution_release": "bookworm",
"distribution_version": "12.9",
$ grep mgr playbooks/facts/lab-etcd2.json
"pkg_mgr": "apt",
"service_mgr": "systemd",
$ grep -A8 default_ipv4 playbooks/facts/lab-etcd2.json
"default_ipv4": {
"address": "REDACTED",
"alias": "eth0",
"broadcast": "REDACTED",
"gateway": "REDACTED",
"interface": "eth0",
"macaddress": "bc:24:11:00:df:33",
"mtu": 1500,
"netmask": "255.255.255.0",
単純なパッケージアップグレードや再起動タスク
レポジトリ上には、単純なパッケージアップグレードを実行するplaybookが用意されています。
# run package upgrades, apt or dnf
ansible-playbook playbooks/pkg_upgrade.yml
再起動するplaybookもあります。対象ホストを一斉に再起動したい時に使えます。
# reboots hosts
ansible-playbook playbooks/reboot.yml
# reboot just specified targets
ansible-playbook playbooks/reboot.yml --limit lab-ns1
Docker実行ホストの用意
DNSサーバとして動かす予定のlab-ns1とlab-ns2に、Dockerをインストールするplaybookを実行しましょう。
なおこれまでに使ったplaybookとは違い、./playbooks/docker.yml
はタスク実行対象を"docker"グループと"lab_docker"グループとしてハードコードされています。
# run the playbook
ansible-playbook playbooks/docker.yml --tags cleaninstall
# test docker by running hello-world
ansible-playbook playbooks/docker.yml --tags test
# see running docker container list
ansible-playbook playbooks/docker.yml --tags status
最後の--tags status
に関しては、その時点で稼働しているDockerコンテナはないため何も出力されません。次のセクションでDNSサーバを動かした後にまた実行すると違う結果が見られます。
DockerでDNSサーバを走らせる
では次にDockerでDNSサーバを立ち上げるplaybookを実行しましょう。
先の準備セクションですでに必要なDNSサーバのレコード用ファイルは./roles/dns/templates/a-records.j2
に用意してあります。今回用意するDNSサーバは、そのファイルに含まれるレコードは名前解決し、それ以外の問い合わせはCloudflareのDNSサーバを頼るよう設定されています。
Playbookのタスクとしては次の通りです:
- もしすでにDNSサービスのコンテナが動いていれば止める
- Ansibleマスターから最新のコンフィグファイルをアップロードする
- DNSサービスを動かす (
docker compose up -d
) -
host
コマンドかdig
コマンドを使ってAnsibleマスタからサーバに対して名前解決のクエリを試す
# run the playbook
ansible-playbook playbooks/dns.yml --tags start
名前解決のテスト
レポジトリに含まれているバージョンではlabグループを対象としたときにしか動作しません。
具体的なタスクは次の通りです:
- Ansibleマスター上に
host
コマンドがあるか確認 - Ansibleマスター上に
dig
コマンドがあるか確認 - 名前解決テスト試行 (コマンドがない場合はスキップ)
- cloudflare.com. SOAレコードを問い合わせ (外部の名前解決)
- labグループ内のランダムなホストのAレコードの名前解決、例えばlab-cp1.lab.example.net.
- happyansibleuser.lab.example.net.の名前解決 (NXDOMAIN結果を期待)
例えばansible-playbook playbooks/dns.yml --tags start --limit lab-ns2 -v
というコマンドで実際の問い合わせ結果が確認できます。
host
やdig
で名前解決テストする際、問い合わせるサーバを明示的に指定しているので、Ansibleマスターのhostsファイルがどうであろうと、設定されているnameserversが何であろうと関係なく、playbookの対象ホスト上に関してテストできるようにしています。
DNSサーバのコンフィグ
いずれかのホストにログインし、docker exec
コマンドでDNSサーバのコンテナ内のファイルを確認できます。
# list of configuration files
docker exec dns ls -1 /opt/unbound/etc/unbound
# forwarder config
# where you can find the forwarder settings to use TLS for root ".",
# and that the upstream servers are 1.1.1.1 and 1.0.0.1
docker exec dns cat /opt/unbound/etc/unbound/forward-records.conf
稼働しているコンテナリストの確認
先ほど登場したコマンド、ansible-playbook playbooks/docker.yml --tags status
を実行すると、以下のようにその時対象ホストで稼働しているコンテナリストが確認できます。
TASK [Container status] ********************************************************************************************************************************************************************
included: docker for lab-ns1, lab-ns2
TASK [docker : Gather information on running containers] ***********************************************************************************************************************************
ok: [lab-ns2]
ok: [lab-ns1]
TASK [docker : Show information on running containers if any] ******************************************************************************************************************************
ok: [lab-ns1] => (item={'Id': '83836f4f4abace42234143e547c4b8447d58013fdb9b39bf1dcc49cdf57dbc5c', 'Image': 'mvance/unbound:1.21.1', 'Command': '/unbound.sh', 'Created': 1741051361, 'Status': 'Up 27 minutes (healthy)', 'Ports': [{'IP': '0.0.0.0', 'PrivatePort': 53, 'PublicPort': 53, 'Type': 'tcp'}, {'IP': '::', 'PrivatePort': 53, 'PublicPort': 53, 'Type': 'tcp'}, {'IP': '0.0.0.0', 'PrivatePort': 53, 'PublicPort': 53, 'Type': 'udp'}, {'IP': '::', 'PrivatePort': 53, 'PublicPort': 53, 'Type': 'udp'}], 'Names': ['/dns']}) => {
"msg": [
"Image: mvance/unbound:1.21.1",
"Status: Up 27 minutes (healthy)"
]
}
ok: [lab-ns2] => (item={'Id': 'c9e524ea4ec1f55cab26722fd6c133fbb33c16ad206fa637e3b3a495692c3ae1', 'Image': 'mvance/unbound:1.21.1', 'Command': '/unbound.sh', 'Created': 1741052118, 'Status': 'Up 25 minutes (healthy)', 'Ports': [{'IP': '0.0.0.0', 'PrivatePort': 53, 'PublicPort': 53, 'Type': 'tcp'}, {'IP': '::', 'PrivatePort': 53, 'PublicPort': 53, 'Type': 'tcp'}, {'IP': '0.0.0.0', 'PrivatePort': 53, 'PublicPort': 53, 'Type': 'udp'}, {'IP': '::', 'PrivatePort': 53, 'PublicPort': 53, 'Type': 'udp'}], 'Names': ['/dns']}) => {
"msg": [
"Image: mvance/unbound:1.21.1",
"Status: Up 25 minutes (healthy)"
]
}
Systemd上でDNSサービスをEnable状態に
オプショナルですが、--tags enable
でsystemdのサービスユニットファイルを用意し、サーバ再起動時などに自動的にDNSサービスが立ち上がるようsystemdに登録することができます。
ansible-playbook playbooks/dns.yml --tags enable
Nameserversの更新
DNSサーバが用意できたので、各ホストのnameservers設定を更新しましょう。以下を実行するとnameservers
変数として指定したサーバらを使うよう、各ホストの設定が更新されます。
./roles/dns/defaults/main.yml
なり、labグループの変数ファイルなりでlab-ns1とlab-ns2のIPアドレスが指定されていれば完璧です。
# update nameservers settings on each host
ansible-playbook playbooks/nameservers.yml --tags update
このplaybookは以下のネットワークサービスに対応しています:
- networking, 通常の/etc/resolv.confファイルの更新
- NetworkManager (多くのRedHatディストロといくつかのDebianディストロ)
- もしDNS設定がNetworkManager側の設定に含まれていない場合は、/etc/resolv.confが更新される
- netplan (だいたいUbuntuで見られる)
Kubernetes用ホストのセットアップ
では残りのホストをKubernetesレディなホストにセットアップするためのplaybookを実行しましょう。
たくさんの設定変更、たくさんのパッケージインストールが実施されますが、まずは確認用のタスクを実施して現時点の状態を確認してみましょう。
生成されるファイルを見ると、kubernetes関連パッケージのバージョンやcontainerdなどのバージョン、swapメモリの有無などなどが確認できます。
# check the host and generate report
ansible-playbook playbooks/kubernetes.yml --tags check
# see the report
cat playbooks/files/kubernetes/lab.md
cat playbooks/files/kubernetes/lab-etcd.md
次に実際にセットアップタスクを実行して、変更後の結果も見てましょう。
# prepare kubernetes-ready hosts
ansible-playbook playbooks/kubernetes.yml --tags prepare
# run another check and see what's in the report
ansible-playbook playbooks/kubernetes.yml --tags check
cat playbooks/files/kubernetes/lab.md
cat playbooks/files/kubernetes/lab-etcd.md
各ホストはKubernetesクラスタを構築するために必要なものすべてのインストール、設定がなされた状態になっています。この時点で、もし単一Control Planeのクラスタを立ち上げるならば、
Control Planeでkubeadm init
を実行してクラスタを立ち上げ、Workerとなるホストでkubeadm join
すれば完成です。
今回は更に準備を進めて冗長化されたKubernetesクラスタを構築していきます。
Etcdクラスタの構築
次にetcdクラスタを構築します。タスクリストとしては次の通りです:
- etcdctlのインストール (あとでヘルスチェックに使用)
- etcdサービスを管理するようkubeletを設定
- etcdクラスタ用のCA生成
- etcdメンバー同士やKubernetesのControl Planeがetcdとやり取りするために用いるその他のTLS証明書、鍵を生成
- etcdクラスタを走らせるためのstatic podマニフェストファイルを用意
- (するとkubeletが自動的にetcdのコンテナを立ち上げ、各ホストのetcdが連携し一つのクラスタとして動作するようになる)
# run the playbook to spin up an etcd cluster
ansible-playbook playbooks/etcd.yml --tags cluster
# run manual etcd cluster check tasks to verify
ansible-playbook playbooks/etcd.yml --tags healthcheck
Kubernetesクラスタの構築
次のplaybookでKubernetesクラスタを立ち上げます。タスクリストは次の通りです:
- クラスタのコンフィグファイルの用意
- keepalivedのコンフィグファイルおよびヘルスチェックスクリプトの用意
- haproxyのコンフィグファイルの用意
- keepalivedとhaproxyのstatic podマニフェストファイルの用意
- Etcdクラスタ構築時に用意したTLS証明書や鍵ファイルをアップロード
- Control Planeの一台で
kubeadm init
を実行してクラスタを立ち上げ - 立ち上げ時に生成されたKubernetesクラスタの証明書、鍵ファイルを他のControl Planeにもコピーし、クラスタにControl Planeとして参加させる
- Workerノードらもクラスタに参加させる
# form a cluster on one control plane node
# and join other control plane nodes
ansible-playbook playbooks/kubernetes.yml --tags cluster
# join worker nodes to the cluster
ansible-playbook playbooks/kubernetes.yml --tags worker
# see the output of "kubectl get nodes" taken after the tasks above
cat playbooks/files/kubernetes/kubectl_get_nodes.txt
Kubernetesクラスタ立ち上げ直後
クラスタ立ち上げ後、kubectl
などのツールでクラスタとやり取りするために必要な/etc/kubernetes/admin.conf
ファイルを~/.kube/config
にコピーするタスクも実行されています。
crictl ps
やkubectl get pods
コマンドなどで稼働しているコンテナを確認できます。
# output example of crictl ps
$ ssh ansible-hlv3@lab-cp2 -i playbooks/files/ssh/id_ed25519_ansible sudo crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD NAMESPACE
6308f99d17515 d04966a100a7a 31 minutes ago Running keepalived 0 a7f6f6d6b1025 keepalived-lab-cp2 kube-system
9a22ab5f6fc82 cf865d0b2bcd1 31 minutes ago Running haproxy 0 e1079c84cf6d1 haproxy-lab-cp2 kube-system
9bb4f91523f67 85b7a174738ba 31 minutes ago Running kube-apiserver 0 b557f1c7c4ef5 kube-apiserver-lab-cp2 kube-system
541b4d84f26c8 b6a454c5a800d 31 minutes ago Running kube-controller-manager 0 62c785292829b kube-controller-manager-lab-cp2 kube-system
3dfb7d3646135 d8e673e7c9983 31 minutes ago Running kube-scheduler 0 b204de296f5a8 kube-scheduler-lab-cp2 kube-system
# output example of kubectl get pods, node ipaddr edited
$ ssh ansible-hlv3@lab-cp1 -i playbooks/files/ssh/id_ed25519_ansible kubectl get pods -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-668d6bf9bc-bbldg 0/1 Pending 0 31m <none> <none> <none> <none>
coredns-668d6bf9bc-qtcrw 0/1 Pending 0 31m <none> <none> <none> <none>
haproxy-lab-cp1 1/1 Running 0 31m 192.0.2.1 lab-cp1 <none> <none>
haproxy-lab-cp2 1/1 Running 0 31m 192.0.2.2 lab-cp2 <none> <none>
haproxy-lab-cp3 1/1 Running 0 31m 192.0.2.3 lab-cp3 <none> <none>
keepalived-lab-cp1 1/1 Running 0 31m 192.0.2.1 lab-cp1 <none> <none>
keepalived-lab-cp2 1/1 Running 0 31m 192.0.2.2 lab-cp2 <none> <none>
keepalived-lab-cp3 1/1 Running 0 31m 192.0.2.3 lab-cp3 <none> <none>
kube-apiserver-lab-cp1 1/1 Running 0 31m 192.0.2.1 lab-cp1 <none> <none>
kube-apiserver-lab-cp2 1/1 Running 0 31m 192.0.2.2 lab-cp2 <none> <none>
kube-apiserver-lab-cp3 1/1 Running 0 31m 192.0.2.3 lab-cp3 <none> <none>
kube-controller-manager-lab-cp1 1/1 Running 0 31m 192.0.2.1 lab-cp1 <none> <none>
kube-controller-manager-lab-cp2 1/1 Running 0 31m 192.0.2.2 lab-cp2 <none> <none>
kube-controller-manager-lab-cp3 1/1 Running 0 31m 192.0.2.3 lab-cp3 <none> <none>
kube-scheduler-lab-cp1 1/1 Running 0 31m 192.0.2.1 lab-cp1 <none> <none>
kube-scheduler-lab-cp2 1/1 Running 0 31m 192.0.2.2 lab-cp2 <none> <none>
kube-scheduler-lab-cp3 1/1 Running 0 31m 192.0.2.3 lab-cp3 <none> <none>
# kubectl get nodes
$ ssh ansible-hlv3@lab-cp3 -i playbooks/files/ssh/id_ed25519_ansible kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
lab-cp1 NotReady control-plane 38m v1.32.2 192.0.2.1 <none> Debian GNU/Linux 12 (bookworm) 6.1.0-31-amd64 containerd://2.0.2
lab-cp2 NotReady control-plane 37m v1.32.2 192.0.2.2 <none> Rocky Linux 9.5 (Blue Onyx) 5.14.0-503.26.1.el9_5.x86_64 containerd://2.0.2
lab-cp3 NotReady control-plane 37m v1.32.2 192.0.2.3 <none> Ubuntu 24.04.2 LTS 6.8.0-54-generic containerd://2.0.2
lab-worker1 NotReady <none> 37m v1.32.2 192.0.2.4 <none> Red Hat Enterprise Linux 9.5 (Plow) 5.14.0-503.26.1.el9_5.x86_64 containerd://2.0.2
もし何かがうまくいかない場合は
自分で確認のために手順を再走した時には、何度かVIPの変数を192.0.2.8のままにしてしまっていたことがあります。
この変数の値が更新されていることを確認しましょう。
grep endpoint roles/kubernetes/defaults/main.yml
他には、Ansibleのplaybook実行のコンソール出力を見ていれば何でひっかかっているのか分かるかもしれません。
また、もしkubeadm init
でひっかかっている場合は、stdoutとstderr出力がいつもAnsibleマスター上に保存されているので確認してみてください。
# if things go wrong during the "kubeadm init" task,
# see the stdout and stderr logs available here
cat playbooks/files/kubernetes/kubeadm.log
cat playbooks/files/kubernetes/kubeadm.err
もしやりなおししたい場合は、--tags reset
が用意されているのでご利用ください。kubeadm reset
やディレクトリのクリーンアップが実行されます。なお全ステップのetcdクラスタ構築や設定変更、パッケージインストールは巻き戻されません。
ansible-playbook playbooks/kubernetes.yml --tags reset
ネットワークアドオンのインストール - Cilium
クラスタ構築後のチェックの出力から見て取れるように、参加ノードはしっかり認識されているしいろいろなpodももう立ち上がっています。しかし同時に、全てのノードは"NotReady"状態ですし、CoreDNSのpodは"pending state"でスタックしています。
次にやることはネットワークアドオンのインストールです。ここではCiliumをインストールします。
Kubernetesクラスタはネットワークコンポーネントがインストールされてようやく正常稼働できる状態になります。ネットワークアドオンにはflannel, calico, Cisco ACI, VMware NSX-Tなどなどたくさんの選択肢があります。
詳しく触れていませんが、クラスタ構築タスクでは、CiliumおよびCiliumの特定の機能を利用するための要件に合うようにクラスタコンフィグを調整してました。
https://docs.cilium.io/en/stable/installation/k8s-install-external-etcd/#requirements
https://docs.cilium.io/en/stable/network/l2-announcements/#prerequisites
https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/gateway-api/#prerequisites
そしてCiliumのインストール時にもそういったカスタム設定を用意する必要があります。今回はhelmを用いて、valuesファイル上で必要なカスタム設定を指定してインストールすることにします。
カスタム内容がファイルとして手元に、そしてVCSなどに載せられるというのはとても便利だと思います。
Helmを用いたCiliumのインストール
インストール作業はKubernetesクラスタのオペレートに使用するホストから実施する必要があります。Ansibleマスターとしているホストでも良いですし、Control Planeのどれか一台でも良いです。要はkubectl
などでクラスタを操作できる状態である必要があります。
タスクリストとしては次の通りです:
- helmのインストール
- インストールするciliumのバージョンを確認
- そのバージョンのciliumのvaluesファイルをダウンロードする
- valuesファイル上で必要なカスタム設定をする
- 用意したvaluesファイルを用いてciliumのhelm chartをKubernetesクラスタにインストールする
実行したコマンドは次の通りです:
# on one of the control plane node
# for example...
# ssh ansible-hlv3@lab-cp1 -i playbooks/files/ssh/id_ed25519_ansible
# install helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# add cilium repository on helm
helm repo add cilium https://helm.cilium.io/
# confirm the latest version of cilium
helm search repo cilium
helm search repo cilium -l # to see all available versions
# download the values file for version 1.17.1
helm show values cilium/cilium --version 1.17.1 > values.yaml
# edit the values file
# OR, you can use one prepared in the repository
# scp -i playbooks/files/ssh/id_ed25519_ansible playbooks/files/cilium/values.yaml ansible-hlv3@lab-cp1:/home/ansible-hlv3/.
# STILL, MAKE SURE TO EDIT THE ETCD ENDPOINTS
# as they point to the doc ipaddr 192.0.2.x
# nl values.yaml | grep -C4 "endpoints:$"
# create secret for cilium containing etcd cert files
sudo cp /etc/kubernetes/pki/etcd/ca.crt .
sudo cp /etc/kubernetes/pki/apiserver-etcd-client.crt client.crt
sudo cp /etc/kubernetes/pki/apiserver-etcd-client.key client.key
sudo chown $USER:$USER *.crt
sudo chown $USER:$USER *.key
kubectl create secret generic -n kube-system cilium-etcd-secrets \
--from-file=etcd-client-ca.crt=ca.crt \
--from-file=etcd-client.key=client.key \
--from-file=etcd-client.crt=client.crt
sudo rm *.crt *.key
# install
helm install cilium cilium/cilium --version 1.17.1 --values values.yaml -n kube-system
Cilium 1.17.1のValuesファイルへの変更リスト
変更点は次の通りです。また、変更後のファイルはレポジトリ上に含まれているのでよければご利用ください。
要注意点はetcdのendpointsです。実際のetcdクラスタのメンバーのIPアドレスに更新してください。
ファイル: ./playbooks/files/cilium/values.yaml
- k8sServiceHost: lab-kube-endpoint.lab.example.net
- k8sServicePort: "8443"
- k8sClientRateLimit.qps: 33
- k8sClientRateLimit.burst: 50
- kubeProxyReplacement: "true"
- kubeProxyReplacementHealthzBindAddr: "0.0.0.0:10256"
- l2announcements.enabled: true
- l2announcements.leaseDuration: 3s
- l2announcements.leaseRenewDeadline: 1s
- l2announcements.leaseRetryPeriod: 200ms
- externalIPs.enabled: true
- gatewayAPI.enabled: true
- etcd.enabled: true
- etcd.ssl: true
- etcd.endpoints: ["https://192.0.2.5:2379", "https://192.0.2.6:2379", "https://192.0.2.7:2379"]
- hubble.ui.enabled: true
- hubble.relay.enabled: true
- hubble.peerService.clusterDomain: lab.example.net
Ciliumインストール後の状態
# Nodes are in Ready state
$ ssh ansible-hlv3@lab-cp2 -i playbooks/files/ssh/id_ed25519_ansible kubectl get nodes
NAME STATUS ROLES AGE VERSION
lab-cp1 Ready control-plane 69m v1.32.2
lab-cp2 Ready control-plane 69m v1.32.2
lab-cp3 Ready control-plane 69m v1.32.2
lab-worker1 Ready <none> 69m v1.32.2
# CoreDNS and all the other pods are ready and runnning
$ ssh ansible-hlv3@lab-cp3 -i playbooks/files/ssh/id_ed25519_ansible kubectl get all -n kube-system
NAME READY STATUS RESTARTS AGE
pod/cilium-6npvn 1/1 Running 0 5m54s
pod/cilium-dzflv 1/1 Running 0 5m54s
pod/cilium-envoy-c6wnr 1/1 Running 0 5m54s
pod/cilium-envoy-mh4h4 1/1 Running 0 5m54s
pod/cilium-envoy-nprtn 1/1 Running 0 5m54s
pod/cilium-envoy-zrzl5 1/1 Running 0 5m54s
pod/cilium-l5dq9 1/1 Running 0 5m54s
pod/cilium-m5pbg 1/1 Running 0 5m54s
pod/cilium-operator-5f59576-qnxzv 1/1 Running 0 5m54s
pod/cilium-operator-5f59576-txw7h 1/1 Running 0 5m54s
pod/coredns-668d6bf9bc-bbldg 1/1 Running 0 67m
pod/coredns-668d6bf9bc-qtcrw 1/1 Running 0 67m
pod/haproxy-lab-cp1 1/1 Running 0 67m
pod/haproxy-lab-cp2 1/1 Running 0 67m
pod/haproxy-lab-cp3 1/1 Running 0 67m
pod/hubble-relay-7bf4fc498b-rnfkq 1/1 Running 0 5m54s
pod/hubble-ui-69d69b64cf-hv7mk 2/2 Running 0 5m54s
pod/keepalived-lab-cp1 1/1 Running 0 67m
pod/keepalived-lab-cp2 1/1 Running 0 67m
pod/keepalived-lab-cp3 1/1 Running 0 67m
pod/kube-apiserver-lab-cp1 1/1 Running 0 67m
pod/kube-apiserver-lab-cp2 1/1 Running 0 67m
pod/kube-apiserver-lab-cp3 1/1 Running 0 67m
pod/kube-controller-manager-lab-cp1 1/1 Running 0 67m
pod/kube-controller-manager-lab-cp2 1/1 Running 0 67m
pod/kube-controller-manager-lab-cp3 1/1 Running 0 67m
pod/kube-scheduler-lab-cp1 1/1 Running 0 67m
pod/kube-scheduler-lab-cp2 1/1 Running 0 67m
pod/kube-scheduler-lab-cp3 1/1 Running 0 67m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/cilium-envoy ClusterIP None <none> 9964/TCP 5m54s
service/hubble-peer ClusterIP 10.96.232.5 <none> 443/TCP 5m54s
service/hubble-relay ClusterIP 10.96.122.84 <none> 80/TCP 5m54s
service/hubble-ui ClusterIP 10.96.208.145 <none> 80/TCP 5m54s
service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 67m
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/cilium 4 4 4 4 4 kubernetes.io/os=linux 5m54s
daemonset.apps/cilium-envoy 4 4 4 4 4 kubernetes.io/os=linux 5m54s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/cilium-operator 2/2 2 2 5m54s
deployment.apps/coredns 2/2 2 2 67m
deployment.apps/hubble-relay 1/1 1 1 5m54s
deployment.apps/hubble-ui 1/1 1 1 5m54s
NAME DESIRED CURRENT READY AGE
replicaset.apps/cilium-operator-5f59576 2 2 2 5m54s
replicaset.apps/coredns-668d6bf9bc 2 2 2 67m
replicaset.apps/hubble-relay-7bf4fc498b 1 1 1 5m54s
replicaset.apps/hubble-ui-69d69b64cf 1 1 1 5m54s
完成
完成です!!Etcdクラスタが立ち上がっており、Kubernetesクラスタも立ち上がっておりネットワークアドオンもインストールされ、全て正常動作している状態です。ハッピーです。
以上が外部Etcdトポロジー採用の冗長構成Kubernetesクラスタの構築作業です。Etcdノードの一個や二個クラッシュしたとしても、Kubernetesクラスタは応答し、正常稼働し続けます。Control Planeノードが落ちてもKubernetesクラスタは応答・機能し続けます。
今回利用したレポジトリで用意されている設定では、lab-cp1のkeepalivedが基本的にマスターとしてVIPの応答責任者となります。lab-cp1をシャットダウンすると、他のいずれかのControl PlaneノードがVIPをテイクオーバーし、そのノードのhaproxyがkube-endpoint宛の通信を受け取り、そしてHaproxyはその時点でヘルスチェックに合格しているlab-cp2かlab-cp3いずれかのkube-apiserverへ通信を流します。
Cilium L2Announcementのデモンストレーション
最後にサッとCiliumのL2Announcement機能のデモをします。
OPTIONAL) Control Planeにもワークロードをスケジュールする
デフォルトでは通常のワークロードはControl Planeノードにはスケジュールされません。
エンタープライズ環境ではなく個人のおうちラボ規模ですので、私はいつもこの設定を変更してControl Planeのリソースも活用するようにしています。
# on lab-cp1 or any node that can run kubectl to manage the cluster
kubectl taint nodes lab-cp1 node-role.kubernetes.io/control-plane:NoSchedule-
kubectl taint nodes lab-cp2 node-role.kubernetes.io/control-plane:NoSchedule-
kubectl taint nodes lab-cp3 node-role.kubernetes.io/control-plane:NoSchedule-
# to confirm the taint settings of a node
kubectl describe node lab-cp1 | grep Taints
OPTIONAL) CoreDNSのコンフィグの更新
ワークロードの名前解決の挙動は、どのノード上で動作しているかによって変わりうることがあります。差異ができるだけないよう、CoreDNSのコンフィグを変更します。
# retrieve the live configmap from the cluster
kubectl get configmap coredns -n kube-system -o yaml > cm-coredns.yaml
# edit the configmap and then apply it
kubectl replace -f cm-coredns.yaml
# restart coredns deployment to recreate coredns pods with the updated configmap
kubectl -n kube-system rollout restart deployment coredns
更新した箇所は"forward ."の5行です。root forwarderとして、Dockerで今回構築したDNSサーバ同様、CloudflareのDNSサービスを利用するよう設定しています。
なお私の実際の環境では自分で管理している別のDNSサーバへ向けていますし、このあたりはご自身の環境で利用できる名前解決サービスに応じて好きに変更することができます。
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes lab.example.net in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . tls://1.1.1.1 tls://1.0.0.1 {
tls_servername cloudflare-dns.com
health_check 5s
max_concurrent 1000
}
cache 30 {
disable success lab.example.net
disable denial lab.example.net
}
loop
reload
loadbalance
}
Cilium L2Announcement
クラスタ上で走っているサービスへは、クラスタに参加しているノードからはアクセスできます。
# on lab-cp1
$ kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cilium-envoy ClusterIP None <none> 9964/TCP 24m
hubble-peer ClusterIP 10.96.232.5 <none> 443/TCP 24m
hubble-relay ClusterIP 10.96.122.84 <none> 80/TCP 24m
hubble-ui ClusterIP 10.96.208.145 <none> 80/TCP 24m
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 86m
# works on kubernetes cluster nodes, but the network is inaccessible from other hosts on the homelab subnet
$ host kubernetes.default.svc.lab.example.net. 10.96.0.10
Using domain server:
Name: 10.96.0.10
Address: 10.96.0.10#53
Aliases:
kubernetes.default.svc.lab.example.net has address 10.96.0.1
しかし、ここで見えている"10.96.."といったアドレスへは、クラスタ外からはアクセスできません。これをアクセスできるようにする一つのオプションが、L2広報です。
今回のデモでは、Ciliumインストール時に有効にしたHubble UIを取り上げます。ゴールとしてはクラスタ外からも (おうちラボのネットワーク上から)このHubble UIへアクセスできるようにすることです。
まずはhubble-uiポッドの識別に適当なラベルが何か確認します。
# looking at the defined labels on the hubble-ui deployment
$ kubectl get deploy hubble-ui -n kube-system -o jsonpath='{.spec.template.metadata.labels}'
{"app.kubernetes.io/name":"hubble-ui","app.kubernetes.io/part-of":"cilium","k8s-app":"hubble-ui"}o
# double check that the label works
$ kubectl get pods -l 'k8s-app=hubble-ui' -n kube-system
NAME READY STATUS RESTARTS AGE
hubble-ui-68bb47466-6gkwb 2/2 Running 0 100m
次にhubble-ui用のサービスをもう一つ自前で作ります。作るのはLoadBalancerタイプのサービスです。
# create this service "l2-hubble-ui" on kube-system namespace
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: l2-hubble-ui
namespace: kube-system
labels:
app.kubernetes.io/name: l2-hubble-ui
spec:
type: LoadBalancer
ports:
- port: 80
protocol: TCP
targetPort: 8081
selector:
k8s-app: hubble-ui
EOF
既存のものと同様にhubble-uiへアクセスするためのサービスが作れました。
# the created service with "pending" external IP address allocation
$ kubectl get svc l2-hubble-ui -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
l2-hubble-ui LoadBalancer 10.96.81.203 <pending> 80:32334/TCP 36s
次に、作った"l2-hubble-ui"サービスのためのCilium IP Poolを用意します。
この時、設定するIPアドレスはDNSサーバのレコード用に用意したテンプレートファイルにあったhubble-ui.lab.example.netのIPアドレスを同じにするのが良いでしょう。
# CHANGE the IP address from 192.0.2.24 to whichever IP address you want to assign for hubble-ui
# the IP address you set in the DNS server configuration file should be the one
cat <<EOF | kubectl apply -f -
apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
name: "ippool-hubble-ui"
spec:
blocks:
- start: "192.0.2.24"
stop: "192.0.2.24"
serviceSelector:
matchExpressions:
- { key: app.kubernetes.io/name, operator: In, values: [l2-hubble-ui] }
EOF
するとExternal IPとして用意したプールよりIPアドレスが割り当てられます。
$ kubectl get svc l2-hubble-ui -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
l2-hubble-ui LoadBalancer 10.96.81.203 192.0.2.24 80:32334/TCP 4m
OKです!もうアクセスできるでしょうか?まだです。
まだどのKubernetesクラスタのノードも、ネットワーク上に自身がそのIPアドレスを担当しているとネットワーク上に広報していないためです。ですので次に必要なのは、L2Announcementのポリシーを作成することです。
この時、クラスタに参加しているノードのインタフェース名に応じてinterfaces
のリストを更新してください。おおよそこの内容でどんなインタフェース名でもマッチできるとは思います。
cat <<EOF | kubectl apply -f -
apiVersion: "cilium.io/v2alpha1"
kind: CiliumL2AnnouncementPolicy
metadata:
name: l2-hubble-ui
spec:
serviceSelector:
matchLabels:
app.kubernetes.io/name: l2-hubble-ui
interfaces:
- ^eth[0-9]+
- ^eno[0-9]+
- ^enp[0-9]s[0-9]+
loadBalancerIPs: true
EOF
L2AnnouncementポリシーができるとIPアドレスがsubnet上に広報され、Kubernetesクラスタ外の端末のウェブブラウザなどからhttp://hubble-ui.lab.example.netがアクセスできるようになっています。
ちなみにクラスタ外のその端末でhubble-ui.lab.example.netが名前解決できるようになっていなければ、IPアドレスでアクセスするのでも大丈夫です。
http://192.0.2.24
Hubble UI
https://github.com/cilium/hubble-ui
Observability & Troubleshooting for Kubernetes Services
これはクラスタ上で何が起こっているのか見るためのツールですので、何か走らせましょう。
Cilium公式ドキュメントのインストール手順のページには、インストール後に実行するテストマニフェストが紹介されています。これをこのまま動かしましょう。
https://docs.cilium.io/en/latest/installation/k8s-install-helm/#validate-the-installation
やることは簡単で、namespaceを作る、マニフェストをapplyする、Hubble UIで確認してみる、(用が済めば)namespaceを削除する、といったステップで完了します。
# on lab-cp1 or any control plane node
# create the namespace cilium-test
kubectl create ns cilium-test
# run the connecitvity check pods in the cilium-test namespace
kubectl apply -n cilium-test -f https://raw.githubusercontent.com/cilium/cilium/1.17.1/examples/kubernetes/connectivity-check/connectivity-check.yaml
# clean up
kubectl delete ns cilium-test
こちらが画面のキャプチャです。
終わりに
以上となります。
Ciliumで利用するGateway APIなども、今後続編として投稿するかもしれません。
以前試したNginx Gateway Fabricに関してはMetalLBも使っているパターンで、zenn上で記事にしていました。もしよろしければご覧ください。
Gateway APIとNGINX Gateway Fabricを自前のKubernetesクラスタで
また、CertManagerも組み合わせてTLS自動化楽々設定にした記録はCloudflarePages上でホストしているこちらにありますので、もしよろしければご覧ください。
building homelab cluster part 5
またCilium以外では、本記事の構築作業中に少し触れたkeepalivedが古いという点で、 新しいバージョンのイメージの構築の仕方の記事なりパブリックなイメージレジストリでの公開なり、何か用意出来たら更新したいと思います。
Discussion