はじめに
格安のIntel N100 CPUを搭載したMini PCを3台ほど入手したので、自分のテスト用にk8sクラスターをkubesprayを使って構築してみたので、その顛末をメモしておきます。
主な用途はCustom Controllerのテスト用なので、実用的なアプリケーションが稼動するかどうかにあまり関心はありませんが、本番機と同様にrook/cephも構成させるため、2台目のSSDが搭載できる点も機種選定の要件の1つでした。
格安Mini PCはM.2とWi-Fi用のPCIeスロットだけを備えている場合があります。M.2と2.5インチのSSDを両方利用できるかことが必要でした。
またWi-FiやBluetoothは使用しませんが、購入したEQ12には技適シールが外装に貼付されていました。電源もPSEマークに検査会社名が併記されていて、形式的にはEQ12の品質はかなり高い印象です。
数ヶ月利用した印象でも特に製品に問題がある印象はありません。ただ価格を考えると電源周りが突然壊れるといったことは想定しておくべきだとは思います。
Kubernetesのノードとしては突然停止しても問題ないので今回のような用途には向いているかもしれません。
環境
Workerノード用には次の機器を準備しました。(node2〜4の3台)
- Hardware: Beelink EQ12 (16GB Memory, 500GB NVMe SSD)
- OS: Ubuntu 22.04.02 Server版 (AutoInstallによる自動インストール)
- 追加Drive: 250GB (2.5inch SATA SSD)
- IP: 192.168.110.22〜24/24
Control-Planeノード用に余っていたMini PCを準備しました。(node1の1台のみ, 192.168.110.21)
- Hardware: Minisforum UM350 (32GB Memory, 500GB NVMe SSD)
- OS: Ubuntu 22.04.02 Server版 (AutoInstallによる自動インストール, Beelinkと同じイメージ)
- IP: 192.168.110.21/24
これらのノードをAnsibleを使って同じ構成にした上で、kubesprayを導入していきます。
実際にはnode2もControl-Planeとして構成しています。kubectlコマンドはnode1上のみで実行しています。
## EQ12
$ sudo hdparm -Tt /dev/nvme0n1
/dev/nvme0n1:
Timing cached reads: 18550 MB in 2.00 seconds = 9286.39 MB/sec
Timing buffered disk reads: 2340 MB in 3.00 seconds = 779.46 MB/sec
## UM350
$ sudo /sbin/hdparm -Tt /dev/nvme0n1
/dev/nvme0n1:
Timing cached reads: 19126 MB in 1.99 seconds = 9631.33 MB/sec
Timing buffered disk reads: 2854 MB in 3.00 seconds = 951.02 MB/sec
ネットワーク構成と準備作業
こちらの都合でAPU6を2台使って光ケーブル(MMF,OM3)で延伸していますが、先頭のAPU6でVPP(FD.io)を使ってプライベートネットワーク(192.168.110.0/24)を構成しています。
apu6で稼動するdnsmasqからDHCP経由で固定IPを割り当てるように設定しています。
dhcp-host=1c:83:41:30:aa:aa,192.168.110.21 # node1, UM350
dhcp-host=7c:83:34:b9:bb:bb,192.168.110.22 # node2, EQ12
dhcp-host=7c:83:34:b9:cc:cc,192.168.110.23 # node3, EQ12
dhcp-host=7c:83:34:b9:dd:dd,192.168.110.24 # node4, EQ12
ansibleを使用してopenntpdを導入し、時刻情報を合わせています。
$ ansible all -m command -a date
node2 | CHANGED | rc=0 >>
Fri Jun 23 09:10:54 AM UTC 2023
node3 | CHANGED | rc=0 >>
Fri Jun 23 09:10:54 AM UTC 2023
node4 | CHANGED | rc=0 >>
Fri Jun 23 09:10:54 AM UTC 2023
node1 | CHANGED | rc=0 >>
Fri Jun 23 09:10:54 AM UTC 2023
kubesprayによるk8sクラスターの構成
具体的な手順は下記のドキュメントを参照してください。
$ ansible-playbook -i inventory/mycluster/hosts.yaml --become --become-user=root うcluster.yml
dnsmasqが稼動するGatewayのAPU6上でkubesprayを利用して、k8sクラスターを構成しています。
この構成では実行が終わるまで約1時間かかりました。
PLAY RECAP **************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node1 : ok=826 changed=141 unreachable=0 failed=0 skipped=1300 rescued=0 ignored=8
node2 : ok=722 changed=124 unreachable=0 failed=0 skipped=1139 rescued=0 ignored=3
node3 : ok=583 changed=95 unreachable=0 failed=0 skipped=809 rescued=0 ignored=2
node4 : ok=523 changed=73 unreachable=0 failed=0 skipped=777 rescued=0 ignored=1
Friday 23 June 2023 22:31:19 +0900 (0:00:01.091) 0:54:45.648 ***********
===============================================================================
get nodeの出力は次のようになっています。
$ sudo kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
node1 Ready control-plane 11h v1.26.5 192.168.110.21 <none> Ubuntu 22.04.2 LTS 5.15.0-75-generic containerd://1.7.1
node2 Ready control-plane 11h v1.26.5 192.168.110.22 <none> Ubuntu 22.04.2 LTS 5.15.0-75-generic containerd://1.7.1
node3 Ready <none> 11h v1.26.5 192.168.110.23 <none> Ubuntu 22.04.2 LTS 5.15.0-75-generic containerd://1.7.1
node4 Ready <none> 11h v1.26.5 192.168.110.24 <none> Ubuntu 22.04.2 LTS 5.15.0-75-generic containerd://1.7.1
k8sクラスターの構成
今回利用するK8sクラスターは、実際に利用しているk8sクラスターに対する実験用の環境なので、kubesprayの機能によって次のような機能を有効にしています。
- OpenID Connect連携 (RBAC, メールアドレスによるClusterRole, Roleの作成)
- Ingress (namespace: ingress-nginx)
- MetalLB (LoadBalancer, namespace: metallb-system)
さらに次のようなコンポーネントを有効にします。
- Rook/Ceph (Block Storage, Shared Filesystem)
- Minio (Object Store)
Kubesprayでの追加機能の構成状況
git diffの出力は次のようになります。
diff --git a/inventory/mycluster/group_vars/k8s_cluster/addons.yml b/inventory/mycluster/group_vars/k8s_cluster/addons.yml
index cb7868846..216633ba2 100644
--- a/inventory/mycluster/group_vars/k8s_cluster/addons.yml
+++ b/inventory/mycluster/group_vars/k8s_cluster/addons.yml
@@ -4,7 +4,7 @@
# dashboard_enabled: false
# Helm deployment
-helm_enabled: false
+helm_enabled: true
# Registry deployment
registry_enabled: false
@@ -97,7 +97,7 @@ rbd_provisioner_enabled: false
# rbd_provisioner_reclaim_policy: Delete
# Nginx ingress controller deployment
-ingress_nginx_enabled: false
+ingress_nginx_enabled: true
# ingress_nginx_host_network: false
ingress_publish_status_address: ""
# ingress_nginx_nodeselector:
@@ -174,7 +174,7 @@ cert_manager_enabled: false
# - "--dns01-recursive-nameservers=1.1.1.1:53,8.8.8.8:53"
# MetalLB deployment
-metallb_enabled: false
+metallb_enabled: true
metallb_speaker_enabled: "{{ metallb_enabled }}"
# metallb_speaker_nodeselector:
# kubernetes.io/os: "linux"
@@ -199,15 +199,15 @@ metallb_speaker_enabled: "{{ metallb_enabled }}"
# value: ""
# effect: "NoSchedule"
# metallb_version: v0.13.9
-# metallb_protocol: "layer2"
+metallb_protocol: "layer2"
# metallb_port: "7472"
# metallb_memberlist_port: "7946"
-# metallb_config:
-# address_pools:
-# primary:
-# ip_range:
-# - 10.5.0.0/16
-# auto_assign: true
+metallb_config:
+ address_pools:
+ primary:
+ ip_range:
+ - 192.168.110.50-192.168.110.79
+ auto_assign: true
# pool1:
# ip_range:
# - 10.6.0.0/16
@@ -216,8 +216,8 @@ metallb_speaker_enabled: "{{ metallb_enabled }}"
# ip_range:
# - 10.10.0.0/16
# auto_assign: true
-# layer2:
-# - primary
+ layer2:
+ - primary
# layer3:
# defaults:
# peer_port: 179
diff --git a/inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml b/inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
index 2699eff2f..1036d1424 100644
--- a/inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
+++ b/inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
@@ -39,20 +39,20 @@ kube_log_level: 2
credentials_dir: "{{ inventory_dir }}/credentials"
## It is possible to activate / deactivate selected authentication methods (oidc, static token auth)
-# kube_oidc_auth: false
+kube_oidc_auth: true
# kube_token_auth: false
## Variables for OpenID Connect Configuration https://kubernetes.io/docs/admin/authentication/
## To use OpenID you have to deploy additional an OpenID Provider (e.g Dex, Keycloak, ...)
-# kube_oidc_url: https:// ...
-# kube_oidc_client_id: kubernetes
+kube_oidc_url: https://oidc.example.com/dex
+kube_oidc_client_id: k8s-rbac
## Optional settings for OIDC
# kube_oidc_ca_file: "{{ kube_cert_dir }}/ca.pem"
-# kube_oidc_username_claim: sub
+kube_oidc_username_claim: email
# kube_oidc_username_prefix: 'oidc:'
-# kube_oidc_groups_claim: groups
+kube_oidc_groups_claim: groups
# kube_oidc_groups_prefix: 'oidc:'
## Variables to control webhook authn/authz
@@ -126,7 +126,7 @@ kube_proxy_mode: ipvs
# configure arp_ignore and arp_announce to avoid answering ARP queries from kube-ipvs0 interface
# must be set to true for MetalLB, kube-vip(ARP enabled) to work
-kube_proxy_strict_arp: false
+kube_proxy_strict_arp: true
# A string slice of values which specify the addresses to use for NodePorts.
# Values may be valid IP blocks (e.g. 1.2.3.0/24, 1.2.3.4/32).
ここまでの構成でEQ12の各ノードは10GB程度の空きメモリ、4GB程度のCacheが利用されている状況でした。
$ ansible all -m command -a 'grep Mem /proc/meminfo'
node4 | CHANGED | rc=0 >>
MemTotal: 16141856 kB
MemFree: 11211012 kB
MemAvailable: 15146428 kB
node1 | CHANGED | rc=0 >>
MemTotal: 30744996 kB
MemFree: 24403472 kB
MemAvailable: 28995036 kB
node3 | CHANGED | rc=0 >>
MemTotal: 16141856 kB
MemFree: 10738372 kB
MemAvailable: 15065692 kB
node2 | CHANGED | rc=0 >>
MemTotal: 16141844 kB
MemFree: 9959016 kB
MemAvailable: 14665180 kB
Rook/Cephの導入
Helmではなく公式ガイドに従ってgit cloneしたリポジトリのYAMLファイルを利用します。
$ git clone https://github.com/rook/rook.git
$ cd rook
$ git checkout refs/tags/v1.11.8 -b t_v1.11.8
$ cd rook/deploy/examples
$ sudo kubectl create -f crds.yaml -f common.yaml -f operator.yaml
$ suod kubectl create -f cluster.yaml
ここで job.batch/rook-ceph-osd-prepare-node[1-4] が終わるまで状況を確認しながら待ちます。
$ sudo kubectl -n rook-ceph get pod -w
OSDが構成されてから、Block Storageと、Shared Filesystemを構成します。
$ sudo kubectl create -f csi/rbd/storageclass.yaml
$ sudo kubectl create -f filesystem.yaml
$ sudo kubectl create -f csi/cephfs/storageclass.yaml
StorageClassNameが登録されているかどうか確認します。
$ sudo kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
rook-ceph-block rook-ceph.rbd.csi.ceph.com Delete Immediate true 2m9s
rook-cephfs rook-ceph.cephfs.csi.ceph.com Delete Immediate true 24s
toolboxを登録し、ceph status
の出力を確認します。
$ sudo kubectl apply -f toolbox.yaml
$ sudo kubectl -n rook-ceph exec -it rook-ceph-tools-9b7967b5d-874ph -- ceph status
cluster:
id: cd847450-85ed-49fa-9e82-d713932809eb
health: HEALTH_OK
services:
mon: 3 daemons, quorum a,b,c (age 12m)
mgr: b(active, since 5m), standbys: a
mds: 1/1 daemons up, 1 hot standby
osd: 3 osds: 3 up (since 6m), 3 in (since 6m)
data:
volumes: 1/1 healthy
pools: 4 pools, 97 pgs
objects: 37 objects, 4.0 MiB
usage: 33 MiB used, 699 GiB / 699 GiB avail
pgs: 97 active+clean
Minioの導入
MinioはHelmを利用して導入します。2023年6月時点のドキュメントではいくつかのインストール方法が公式ガイドに掲載されていますが、今回は既存環境のテスト環境を構築したいため以前に実施した方法をそのまま利用します。
今回展開したChartは、minio-5.0.11.tgz でした。
導入が終わった直後は次のような状況になります。
$ sudo kubectl -n minio get all
NAME READY STATUS RESTARTS AGE
pod/my-minio-0 1/1 Running 0 60m
pod/my-minio-1 1/1 Running 0 60m
pod/my-minio-2 1/1 Running 0 60m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/my-minio ClusterIP 10.233.27.178 <none> 9000/TCP 60m
service/my-minio-console ClusterIP 10.233.15.130 <none> 9001/TCP 60m
service/my-minio-svc ClusterIP None <none> 9000/TCP 60m
NAME READY AGE
statefulset.apps/my-minio 3/3 60m
手元のWebブラウザーからMinioコンソールにアクセスしようとするには、External-IPを割り当てるのが簡単です。ポートフォワードを利用する方法はkubectlコマンドを手元のPCで動作している場合には有効ですが、リモートホストでkubectlを実行している場合には簡単な方法とはいえません。
作業をする時にだけMinioコンソールにアクセスするためのYAMLファイルを準備しておきます。
既存の svc/my-minio-console の定義を確認 ($ sudo kubectl -n minio get svc my-minio-console -o yaml
) し、type: LoadBalancerに変更したYAMLファイルを準備します。
---
apiVersion: v1
kind: Service
metadata:
namespace: minio
name: my-minio-console-lb
spec:
type: LoadBalancer
ports:
- name: http
port: 9001
targetPort: 9001
protocol: TCP
selector:
app: minio
release: my-minio
今回のシステム構成では外部からAPU6から内部に作られた192.168.110.0/24にアクセスする手段はないため、最終的にはopenvpnを構成しています。
特権ユーザーのID, パスワードについては、次のようなコマンドで抽出することができます。
$ sudo kubectl -n minio get secret my-minio -o jsonpath='{.data.rootUser}' | base64 -d && echo
BQUkgp6JDGOEp9K51YRz
$ sudo kubectl -n minio get secret my-minio -o jsonpath='{.data.rootPassword}' | base64 -d && echo
52grRijHveoO8QjQM9QUqf1ewIz5lqUqEi9IvEKV
ingress-nginxの構成
kubesprayで導入したingress-nginxの構成は次のようになっています。
$ sudo kubectl -n ingress-nginx get all
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-controller-6sgrq 1/1 Running 0 25h
pod/ingress-nginx-controller-7j4cl 1/1 Running 0 25h
pod/ingress-nginx-controller-cf9df 1/1 Running 0 25h
pod/ingress-nginx-controller-k5rxd 1/1 Running 0 25h
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/ingress-nginx-controller 4 4 4 4 4 kubernetes.io/os=linux 25h
これでは外部からのingressに対するアクセスを受け付ける処理などもできないため、svcオブジェクトやingress-nginx-controllerに指示を出すためのIngressオブジェクトなどを定義していきます。
気になったこと
K8sクラスターの再起動までに時間がかかる
もちろん単体での再起動に時間がかかるということではなく、k8sクラスターを構成しているとノード全体の再起動を試みた際になかなか停止せず再起動が終わらない現象に遭遇します。
全体のパフォーマンスが低い事の影響がこういうタイミングで発生していて、Minioなどが動作しているだけで自作のアプリケーションなどは稼動していない状況でも再起動を開始してから完了するまでに15分程度の時間が必要な状況が発生しています。
実際にはapi-serverが起動してから各種のReconcile処理が走るので全体が安定するまでには30分程度はかかる印象です。
突然に電源が落ちる
特定のメーカーなどとは無関係に、ほぼ全てのノードでこの5ヶ月ほどの間に突然電源が落ちています。
各ノード2回ぐらいで頻度は稼動時間などとは無関係ですが、6ノードあるのでサービスに長期的な影響は出ていません。
ただ10年以上使っている富士通製TX120 S3pの安定性に比べると、容積が小さいことで放熱に問題がある可能性を疑っています。
長期的に安定して使えるかというと、見た目の面白さはありますが、真面目にビジネス用途に使うことは絶対にお勧めしません。
さいごに
テスト用の環境なので再構築もするだろうと思い、自分用のメモとして作業内容をまとめました。
APU6などでの作業はansibleのplaybookでもまとめているので、適宜ローカルのgitlabリポジトリなども活用しています。
システムの最低限の構成が終った段階では、/proc/meminfoのMemAvailableは10GB程度あるので、かなり余裕があるという印象です。DBを活用する構成のアプリケーションを複数動作させても稼動自体は問題なさそうです。
Intel J1900 CPUを搭載したMini Fanless Serverを購入したことがありましたが8GBしかメモリを搭載できないこともあって、実用的なワークロードを稼動させることはできなかったことを考えると簡単に実験ができる良い時代になったなと思います。
魅力的な製品ではありますが、MiniPCメーカーはMSIやASUSなどを除いてUEFI BIOSのアップデートには消極的です。例えばRyzenなどのAMD系CPUでは、パフォーマンスやセキュリティ関連の問題が定期的に発見されており、AGESAの定期的な更新は避けられません。
また放熱性能や製造しているメーカーが想定している用途に応じて利用するのが大切かなと思いますので、外部に提供するサービスをこのクラスターで稼動させることはないと思います。
もしいま実用的なKubernetesクラスターをできるだけ安く準備する必要があれば、ワークロードにもよりますが、中古のPC4 RDIMM ECCメモリを128GB以上搭載した10〜12コア程度のXeonサーバーを5台以上準備すると思います。
以上