Rancher Harvesterを使っておうちHCIを構築する話
HarvesterはRancher社が管理しているKubernetes上でHCIを構築するツールです
自宅で若干持て余している計算資源を使ってHarvesterをインストールし、Hyper-Converged Infrastructureをおうちに構築してみます

Hyper-Converged Infrastructure
通称HCI、これはサーバーの仮想化技術のひとつで、計算資源やネットワーキング、ストレージなどをソフトウェアベースで管理できるようにした製品のことを指すらしいです。
いろいろなアプライアンス製品があるそうですが、残念ながら自分はHCI製品に触れたことがないので細かい解説ができないため詳細はWiki先生を参照ください。 詰まるところ、HCI製品を利用すると簡易なオレオレAWS/GCPが作れるといったところでしょうか?
Harvester
冒頭に話をした通り、HarvesterはRancher社がメンテナンスしているHCI製品です。Kubernetesのエコシステムをフルに活用しながら、Rancher製品をうまく組み合わせてHCIを実現しています。
Harbesterが利用しているツールとしては
- 計算資源管理: KubeVirt
- ネットワーク管理: Multus CNI, VLAN Plugin
- ストレージ管理: Longhorn
が挙げられます。
Harvesterを使ってみる
実際にHarvesterをインストールして仮想マシンを作成してみます。今回の検証環境は下記の通りです。
- 物理サーバー
- 第6世代 Intel NUC NUC6i5SYH
- Harvester
- 0.2.0
ISOインストール
Harvesterはインストール方法にISOによるフルインストール(直接インストールとPXEインストールが選択可能)と既存のKubernetesにインストールするRun as Appの2種類があります。今回はまず動かしてみるということでISOを直接物理サーバーにインストールしていきます。
ISOダウンロード〜インストール完了
GithubのreleaseからISOをダウンロードしてきます。
ダウンロードしてきたISOを適当なUSBメモリに焼き込み、物理サーバーにインストールしていきます。インストール中いくつかオプションの指定ができますが、今回はすべてデフォルト値のまま進みます。

トラブルなくスムーズにインストールが完了するとHarvesterのコンソールが表示されます。

仮想マシンの起動
さっそく仮想マシンを起動してみます。コンソールに指定されているアドレスにアクセスしてダッシュボードを開きます。

仮想マシンを作成するには、事前にインストールするISOファイルとネットワークの設定が必要です。
今回はお試しにUbuntu20.04のISOを用意することに。ISOのURLを指定することになるので、ubuntu.comのURLをそのまま指定します。 ImagesタブのCreateボタンからImageの作成をします。

次に仮想ネットワーク、VLANを作成します。NetworksタブのCreateボタンからNetworkの作成をします。

これで仮想サーバーを作成する準備できました。実際に仮想マシンを作成してみます。 Virtual MachinesタブのCreateボタンから仮想マシンを作成します。今回はお試しということで、先ほど登録したUbuntuのISOとVLANを利用することにします。


Createボタンを押すと実際にサーバーが起動し、HarvesterのWebConsoleからUbuntuのインストール画面へアクセスすることができました。 いやーこれは非常に楽ですね!

少し覗いてみる
少し動きについてみてみます。とりあえずPodの状態を確認。
~ ❯ kubectl get pods NAME READY STATUS RESTARTS AGE virt-launcher-vlan-sample-4bssz 1/1 Running 0 23h
Podの名前から見て、どうもこのPodが先ほど作成した仮想マシン本体のようです。KubeVirtの文字が輝いてますね。
でこのPodのownerReferenceを見てみると下記の通り
❯ kubectl get pods -o json virt-launcher-vlan-sample-4bssz | jq .metadata.ownerReferences
[
{
"apiVersion": "kubevirt.io/v1",
"blockOwnerDeletion": true,
"controller": true,
"kind": "VirtualMachineInstance",
"name": "vlan-sample",
"uid": "d3cab107-362b-4dfa-a0e7-122457f719ac"
}
]
なるほど、この仮想マシンはkubevirt.io/v1のVirtualMachineInstanceで作成されているとのこと。
Harvesterの領域ではなくKubeVirtの領域になりますがもう少し深掘りしてみます。このVirtualMachineInstanceのownerReferenceを覗いてみると下記の通り
❯ kubectl get VirtualMachineInstance -o json vlan-sample | jq .metadata.ownerReferences
[
{
"apiVersion": "kubevirt.io/v1",
"blockOwnerDeletion": true,
"controller": true,
"kind": "VirtualMachine",
"name": "vlan-sample",
"uid": "b6d42e46-0004-47a3-a86d-78411d878cfc"
}
]
つまりVirtualMachineを作成することでVirtualMachineInstanceが作成され、そのVirtualMachineInstanceに合わせてPodが起動する感じでしょうか?
kubectl explainで覗いてみる限り
- VirtualMachineは仮想マシンのテンプレートとそのステータス(起動中かどうか)を管理
- VirtualMachineInstanceは仮想マシンそのものを管理
- Podは物理的な(?)仮想マシン
という関係性っぽいですね。なるほどー
コードを読んでみると、どうもHarvesterはどんなVirtualMachineオブジェクトでも管理対象とするみたいですね。つまりマニュアルで仮想マシンを作ることができそうかな……?
ためしに下記のようなマニフェストで作成してみます。(ちょっと長い)
apiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: manual-vm spec: running: true dataVolumeTemplates: - apiVersion: cdi.kubevirt.io/v1beta1 kind: DataVolume metadata: annotations: harvesterhci.io/imageId: default/image-9dk4s name: manual-vm-cdrom-disk spec: pvc: accessModes: - ReadWriteMany resources: requests: storage: 10Gi storageClassName: longhorn-image-9dk4s volumeMode: Block source: blank: {} - apiVersion: cdi.kubevirt.io/v1beta1 kind: DataVolume metadata: name: manual-vm-rootdisk spec: pvc: accessModes: - ReadWriteMany resources: requests: storage: 10Gi storageClassName: longhorn volumeMode: Block source: blank: {} template: metadata: annotations: harvesterhci.io/diskNames: '["manual-vm-rootdisk","manual-vm-cdrom-disk"]' harvesterhci.io/sshNames: '["github.com"]' labels: harvesterhci.io/creator: RyuSA harvesterhci.io/vmName: manual-vm spec: domain: cpu: cores: 1 devices: disks: - bootOrder: 2 cdrom: bus: sata name: cdrom-disk - bootOrder: 1 disk: bus: virtio name: rootdisk - disk: bus: virtio name: cloudinitdisk interfaces: - macAddress: 52:54:00:57:4c:53 #適当 masquerade: {} model: virtio name: default - bridge: {} macAddress: 2e:61:8d:3d:35:4c #適当 model: virtio name: vlan machine: type: q35 resources: requests: memory: 2Gi evictionStrategy: LiveMigrate hostname: manual-vm networks: - name: default pod: {} - multus: networkName: example-vlan name: vlan volumes: - dataVolume: name: manual-vm-cdrom-disk name: cdrom-disk - dataVolume: name: manual-vm-rootdisk name: rootdisk - cloudInitNoCloud: userData: | #cloud-config ssh_authorized_keys: - >- ssh-rsa (snip) name: cloudinitdisk
これでkubectl createでオブジェクトを作成してみると
一部の部分(labelやannotation)のお作法さえ守れば、しっかり動きますね。まぁできたからと言ってなにか良いことがあるわけではないのですが……w
まとめ
Harvesterを利用して、Kubernetesクラスタ上にHCIを構築して仮想マシンをKubernetesリソースベースで作成してみました。HarvesterのGUIで指定できる項目がまだ少なかったりしましたが、簡単な用途であれば現在のバージョンでもいい感じに動かせると思います。
本当は既存のKubernetes上にデプロイすることまでやりたかったのですが、手元にあるKuberenetesクラスターはRaspberry Piで構築していたため今回は断念。 というのもKubeVirtのARM64のサポートはまだ完成しておらず、それに引きずられてHarvesterのARM64サポートはこれからの状態です。
なので、気長に待ちつつ、可能なら貢献して行こうかなと思います。
Raspberry Pi上にWi-Fi APを自動構築するKubernetes Operatorを作った話
元ネタ、というよりWhy/作ったモチベはこちらに書いてあります👀
雑に言えば「自宅にメッシュWi-Fiを導入したかったので、Raspberry PiでWi-Fi APを実装してみた」という感じ。
TL;DR
最終形として、こんなOperatorを作成しました。

これによりCRDであるAccessPointリソースをKubernetesにデプロイすることで、KubernetesにjoinしているRaspberry Pi上に適切に設定されたWi-Fiアクセスポイントを作成することができるようになりました。
これでkubectlひとつでメッシュなWi-Fiを構築したり来客向けのゲストWi-Fiを用意することができるようになりました、やったぜ✨
Rapsberry PiのAP化 #とは
Raspberry PiがWi-FiのAPとして動かせる話は昔からあり、いろんな方が紹介してくださってます🥰
つまるところRaspberry Piのワイヤレスのネットワークインタフェースとイーサネットのネットワークインタフェースをブリッジでつなぎ、ワイヤレスネットワークインタフェースをAPモードで起動しようということですね。ソフトウェアとしてはhostapdがよく使われているみたいです🤔
Raspberry Pi自体は安価に調達できて、かつPoE Hatを利用することでLANケーブル一本で動かすことができるなど取り回しが良さそう……今回やりたいことにピッタリ。
hostapd
hostapdはネットワークインターフェースカードをアクセスポイントとして動かすためのソフトウェアで、設定ファイルを咬まして起動することで指定したSSID/パスワード/その他設定のアクセスポイントを起動することができます。すごく便利👏
とりあえず手動オペレーション💪
一度、手動でWi-Fi APをRaspberry Pi上に展開するための手順を確認してみます。
- ネットワーク内にDHCPを用意する
- Raspberry Piのネットワークインタフェースを変更する(ブリッジ作ったり)
- Raspberry Piにhostapdをインストール、デーモンとして登録する
- configファイルを作成する
- configファイルにSSIDとパスワード、その他設定を入れる
- デーモンのリロード&再起動
手動オペレーションの場合、この手順をAPにしたいRaspberry Piすべてに実施していくことになります。実際に自宅に転がってたRaspberry Pi上でオペレーションしてみたところ、1つあたり新規作成に1時間程度時間がかかりました。これで運用するのは……かなりいやですね🤔
hostapdのインストールの手順そのものはコンテナを配布することで解決自体はできます。ついでだったのでコンテナイメージを作りました
しかし、コンテナを起動したりconfigファイルを配布したりと運用作業を真心込めて1台1台手作業するのはちょっとありえないという気持ち🤮
Operatorにしていく🦾
ルーチンな運用作業はKubernetesのOperatorにするに限る。ということでOperatorで実装することを決意しました。
Kubernetes Operatorとは、KubernetesのAPIリソースを監視して「現在の状況」と「理想の状況」を計算し、現在の状況をあるべき姿にドリブンしていくアプリケーションのことを指します。通常、後述するCRDと共に実装を行い日々の運用の自動化などを行うことが多いです。
有名なOperatorとしてはPrometheusのデプロイや設定をKubernetesリソースで行えるようにしたPrometheus Operatorや、RabbitMQのクラスターデプロイをKubernetesリソースで定義できるようにしたRabbitMQ Cluster Operatorなどがあります。
スコープ決め
手動オペレーションの各タスクのうち、自動化できそうな範囲を考えてみます。
- ネットワーク内にDHCPを用意する
- これは各ネットワーク内で決定するべき事柄、ここまで責任を持つのは過剰
- → スコープ外
- Raspberry Piのネットワークインタフェースを変更する
- これはcloud-initを利用して自動化(=k8s関係ない範囲)
- → スコープ外
- Raspberry Piにhostapdをインストール、デーモンとして登録する
- 指定したノードにPodとして起動すれば……?
- → DaemonSetとして配布します
- configファイルを作成する
- 最低限の設定を外から渡せればConfigMapに入れるところまで自動化できそう
- → CRDとして定義し、そこからConfigMapを作成&DaemonSetと一緒に配布します
- configファイルにSSIDとパスワード、その他設定を入れる
- 4.と同様
- デーモンのリロード&再起動
- Kubernetesの機能をそのまま利用します
- → 実装しない(DaemonSetに任せる)
ということで、1.と2.以外のタスクを自動化する方針で実装していきます。
Kubebuilder
Operatorを実装するにあたり、ほしいのは"Kubernetesのエコシステム"であってOperatorのフルスクラッチではないです🤔
Kubernetes Operatorを実装するためのフレームワークとして有名なものが2つあります。
- Kubebuilder
- Operator SDK
それぞれを触ってみた感じ、個人的にはKubebuilderの方が使いやすそうに感じたので今回はKubebuilderを利用して実装していこうと思います🥰
こちら参考書籍です。KubernetesのOperatorとはなにか、KubebuilderとOperator SDKの違いとサンプル実装と非常にわかりやすく解説されててマジで助かりました。
CRD
KubernetesにはCuston Resource Definitionというリソースの拡張機能が存在しており、いわば「俺専用のKubernetesリソースを作ってやるぜ〜〜^^」ができます。CRD自体はKubernetes 1.16でGAとなっているAPIです。
今回のパターンでは2種類のCRDを実装しました。
AccessPoint- アクセスポイントそのものを表すリソース
- SSIDの設定やパスワード、あと細かい設定とかと記述する
AccessPointDevice- アクセスポイントをデプロイする仮想的なデバイス
- ブリッジの設定や操作するネットワークインタフェースなどを記述する
- 用途としては"Aliceの部屋"とか"1F リビング"とかの単位で書いていく
ストーリーとしては
- まずユーザーが
AccessPointDeviceをデプロイ - ユーザーが
AccessPointDeviceを指定したAccessPointをデプロイ - AccessPoint Operatorが
AccessPointの作成イベントを受信 AccessPointDeviceと合わせてAccessPoint Operatorが適切なDaemonSetを作成してデプロイ- DaemonSetの力で各Raspberry Pi上にhostapdの設定とhostapdコンテナがデプロイされていく
……といった流れでリソースを操作していくかな?と考えています。
最終的に出来上がったもの
開発そのものについては(ただ実装しただけなので)割愛します。興味がある人が多そうであればどこかに書くなりしておきます。
最初にも紹介しましたが、こんな感じに完成しました。細かい使い方等は一応READMEに書いてあります。なお現時点ですでに可愛いバグ(AccessPointDevice消してもDaemonSetが消えない等)があったりするのは許してください。。。

開発の環境としては使い捨てできるKubernetesを作成するためのkindを利用しました。便利ですね〜
以前構築したRaspberry Piクラスターのネットワーク設定を変更して……
作成したOperatorをデプロイしてみると……

手元のスマホからWi-Fiに接続&インターネットへアクセスできました!やったぜ🤪
ためしにhostapdがデプロイされている環境でRaspberry Piをインストールしてある部屋から部屋へ移動してみましたが、同じSSID/パスワードでうまくローミング(?)してシームレスに接続を継続してくれました。これはいい感じのモノが作れた……!
完走した感想
実はGo言語でがっつり開発したのは初挑戦だったので色々雑な作りになってしまいました。が、それでも今日も自宅で動いてくれています🥰毎日SSIDとパスワードを変えたい気持ちになりますねw
一方でKubebuilder側がまだM1 Mac(自分の開発環境)に正式対応しておらず、Operatorのテスト機構については全く手が出せていない状況です。ARM対応の実装を自分ですれば良いだけなのですが、なかなか食指が動かず……orz
定期的に開発できたらいいな〜と思ってます。
Kubernetesの仮想化ツールvclusterに触れてみる話
vclusterはKubernetes上にKubernetesを起動して仮想的なKubernetesを楽しむことができるツールです。特定の名前空間上にk3sをデプロイしその中でリソースを作成、Syncerがリソースをホスト側のKubernetesへ同期をすることで実現しています。
結構楽しそうなツールだったので遊んでみたログを残しておきます。
# 環境 # Kubernetes Providor : Amazon EKS ❯ kubectl version --short Client Version: v1.19.7 Server Version: v1.19.8-eks-96780e
Hello Worldしてみる
インストール (QuickStart)
専用のCLI(=vclusterCLI)、Helm、kubectlなどいくつかデプロイする方法はありますが、とりあえず動かしてみる分にはvclusterCLIをインストールして動かしてみると良いと思います。
vclusterCLIのインストールが完了したら、あとはvclusterCLIを叩くだけでホスト側のKubernetesクラスター(=ホストクラスター)上に仮想的なKubernetes(=仮想クラスター)が動き始めます。
❯ vcluster create foo-cluster -n foo [info] Creating namespace foo [info] execute command: helm upgrade foo-cluster vcluster --repo https://charts.loft.sh --kubeconfig /var/folders/qs/_40g0sgj1bz5lsc4pbbq7sn00000gn/T/689594003 --namespace foo --install --repository-config='' --values /var/folders/qs/_40g0sgj1bz5lsc4pbbq7sn00000gn/T/116523222 [done] √ Successfully created virtual cluster foo-cluster in namespace foo. Use 'vcluster connect foo-cluster --namespace foo' to access the virtual cluster
仮想クラスターから触ってみる
仮想クラスターへアクセスするためのkubeconfigを生成して実際にアクセスしてみます。CLIの記載通りにvcluster connect foo-cluster --namespace fooを叩くと仮想クラスターへの接続に必要なkubeconfigが生成されるので、kubeconfigを利用して接続してみます。
❯ vcluster connect foo-cluster --namespace foo [done] √ Virtual cluster kube config written to: ./kubeconfig.yaml. You can access the cluster via `kubectl --kubeconfig ./kubeconfig.yaml get namespaces` [info] Starting port forwarding: kubectl port-forward --namespace foo foo-cluster-0 8443:8443 Forwarding from 127.0.0.1:8443 -> 8443 Forwarding from [::1]:8443 -> 8443 ... # 別のセッションを起動 ❯ kubectl get all --kubeconfig ./kubeconfig.yaml NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.100.229.214 <none> 443/TCP 3m49s
初期起動完了したので、ためしにいくつかリソースを仮想クラスター上に作成して動作確認してみます。
# Deploymentを作成 ❯ kubectl create deployment --image nginx nginx --replicas 3 --kubeconfig ./kubeconfig.yaml deployment.apps/nginx created # 名前空間を作成 ❯ kubectl create namespace mginx --kubeconfig ./kubeconfig.yaml namespace/mginx created # 作成した名前空間上にDeploymentを作成 ❯ kubectl create deployment --image nginx nginx --replicas 3 --kubeconfig ./kubeconfig.yaml -n mginx deployment.apps/nginx created # 作成したDeployment向けのServiceを作成する ❯ kubectl expose deployment -n mginx nginx --port 80 --kubeconfig ./kubeconfig.yaml service/nginx exposed # 作成したリソース一覧を確認する ❯ kubectl get all -A --kubeconfig ./kubeconfig.yaml NAMESPACE NAME READY STATUS RESTARTS AGE kube-system pod/coredns-66c464876b-xrbxj 1/1 Running 0 18m default pod/nginx-6799fc88d8-tttl2 1/1 Running 0 5m51s default pod/nginx-6799fc88d8-hfs6z 1/1 Running 0 5m51s default pod/nginx-6799fc88d8-xt67s 1/1 Running 0 5m51s mginx pod/nginx-6799fc88d8-8skv7 1/1 Running 0 4m50s mginx pod/nginx-6799fc88d8-96q2w 1/1 Running 0 4m50s mginx pod/nginx-6799fc88d8-lzxlb 1/1 Running 0 4m50s NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default service/kubernetes ClusterIP 10.100.229.214 <none> 443/TCP 18m kube-system service/kube-dns ClusterIP 10.100.253.26 <none> 53/UDP,53/TCP,9153/TCP 18m mginx service/nginx ClusterIP 10.100.148.16 <none> 80/TCP 7s NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE kube-system deployment.apps/coredns 1/1 1 1 18m default deployment.apps/nginx 3/3 3 3 5m51s mginx deployment.apps/nginx 3/3 3 3 4m50s NAMESPACE NAME DESIRED CURRENT READY AGE kube-system replicaset.apps/coredns-66c464876b 1 1 1 18m default replicaset.apps/nginx-6799fc88d8 3 3 3 5m52s mginx replicaset.apps/nginx-6799fc88d8 3 3 3 4m51s # 'alice'というServiceAccountを作成する ❯ kubectl create serviceaccount alice --kubeconfig ./kubeconfig.yaml serviceaccount/alice created ❯ kubectl get serviceaccounts --kubeconfig ./kubeconfig.yaml NAME SECRETS AGE default 1 19m alice 1 5m51s
ざっと見た限り、普通のKubernetesのように動作しているように見えます。すごくいい感じ
ホストクラスターから眺めてみる
仮想クラスターで作成したリソースがホストクラスターからどのように見えるのかを確認してみます。
# すべてのリソースを取得する(関連する部分のみ抜粋) ❯ kubectl get all -A NAMESPACE NAME READY STATUS RESTARTS AGE foo pod/coredns-66c464876b-xrbxj-x-kube-system-x-foo-cluster 1/1 Running 0 22m foo pod/foo-cluster-0 2/2 Running 0 23m foo pod/nginx-6799fc88d8-8skv7-x-mginx-x-foo-cluster 1/1 Running 0 9m17s foo pod/nginx-6799fc88d8-96q2w-x-mginx-x-foo-cluster 1/1 Running 0 9m17s foo pod/nginx-6799fc88d8-hfs6z-x-default-x-foo-cluster 1/1 Running 0 10m foo pod/nginx-6799fc88d8-lzxlb-x-mginx-x-foo-cluster 1/1 Running 0 9m17s foo pod/nginx-6799fc88d8-tttl2-x-default-x-foo-cluster 1/1 Running 0 10m foo pod/nginx-6799fc88d8-xt67s-x-default-x-foo-cluster 1/1 Running 0 10m kube-system pod/coredns-59847d77c8-8zpd4 1/1 Running 0 5h26m kube-system pod/coredns-59847d77c8-vxqq5 1/1 Running 0 5h26m kube-system pod/kube-proxy-9wh2q 1/1 Running 0 5h4m kube-system pod/kube-proxy-xqtb7 1/1 Running 0 5h4m NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 5h27m foo service/foo-cluster ClusterIP 10.100.229.214 <none> 443/TCP 23m foo service/foo-cluster-headless ClusterIP None <none> 443/TCP 23m foo service/kube-dns-x-kube-system-x-foo-cluster ClusterIP 10.100.253.26 <none> 53/UDP,53/TCP,9153/TCP 22m foo service/nginx-x-mginx-x-foo-cluster ClusterIP 10.100.148.16 <none> 80/TCP 4m34s kube-system service/kube-dns ClusterIP 10.100.0.10 <none> 53/UDP,53/TCP 5h27m NAMESPACE NAME READY AGE foo statefulset.apps/foo-cluster 1/1 23m # ServiceAccountを確認する ❯ kubectl get serviceaccounts | grep alice // NO RESULT
仮想マシン上で作成したリソースの一部がホストクラスターから見えず(Deploymentとか)、一方仮想マシンで見えていたいくつのそれっぽいリソースがホストクラスターから見えています(Podとか)。 理由は単純で、仮想クラスター側で作成したリソースは仮想クラスター側のk3sで管理されています。そして一部のリソースを仮想クラスターからホストクラスターに同期し、そのあとのリソースの管理をホストクラスター側に委譲しています。

仮想クラスターが実際にどのリソースをホストクラスターへ委譲しているのかは把握してませんが、ざっと見た限り仮想クラスターからホストクラスターへPodやService、IngressやPVCなどを委譲しているようです。
少しだけ深堀り
別の仮想クラスターをHelmでインストールしてみます。vclusterはHelmコマンドを代替しているだけなので等価なHelmコマンドを叩くことでもvclusterをインストールすることが可能です。
❯ cat <<EOF > values.yaml
vcluster:
image: rancher/k3s:v1.19.5-k3s2
extraArgs:
# ホストクラスターのService CIDRをズラしてみる
- --service-cidr=10.96.0.0/12
storage:
size: 5Gi
EOF
❯ helm repo add loft https://charts.loft.sh
❯ helm repo update
# インストール
❯ helm upgrade --install bar loft/vcluster \
--values values.yaml \
--namespace bar \
--create-namespace
# ホストクラスター側から見えるリソース
❯ kubectl get all -n bar
NAME READY STATUS RESTARTS AGE
pod/bar-0 2/2 Running 0 19s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/bar ClusterIP 10.100.50.206 <none> 443/TCP 19s
service/bar-headless ClusterIP None <none> 443/TCP 19s
NAME READY AGE
statefulset.apps/bar 1/1 19s
仮想クラスターコントロールプレーンのパラメータ調査
Helmでデプロイする際には細かいパラメータを指定することができます。仮想クラスターのコントロールプレーンであるk3sにデフォルトで指定されているパラメータを確認してみます。
❯ kubectl get pod -n bar bar-0 -o yaml
apiVersion: v1
kind: Pod
metadata:
name: bar-0
namespace: bar
spec:
containers:
- args:
- server
- --write-kubeconfig=/k3s-config/kube-config.yaml
- --data-dir=/data
- --disable=traefik,servicelb,metrics-server,local-storage
- --disable-network-policy
- --disable-agent
- --disable-scheduler
- --disable-cloud-controller
- --flannel-backend=none
- --kube-controller-manager-arg=controllers=*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle
- --service-cidr=10.96.0.0/12
command:
- /bin/k3s
image: rancher/k3s:v1.19.5-k3s2
name: vcluster
volumeMounts:
- mountPath: /data
name: data
...(snip)
k3sのパラメータを覗いて上記のパラメータと比較してみると
- --disable=traefik,servicelb,metrics-server,local-storage
- traefikやmetrics-serverを無効
- --disable-network-policy
- NetworkPolicy無効
- --disable-agent
- ローカルのエージェントを起動しない
- --disable-scheduler
- スケジューラーを無効(Podの配置がされない)
- --disable-cloud-controller
- Cloud Controllerを無効
- --flannel-backend=none
- Flannelを無効
- --kube-controller-manager-arg=controllers=*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle
- いろいろ無効にしてる
k3sのもつController周りを無効にし、物理的な操作が必要になるリソースに関して何もしないようにしているように見えます。これで仮想クラスター内でDeploymentやStafulsetを扱い、PodやServiceをホストクラスターに委譲することができるわけですね。
仮想クラスターへの接続
実装を覗いてみるとvclusterCLIの各種コマンドはkubectlを利用して実現されています。それらを使ってvclusterCLI-lessで仮想クラスターに接続して見ます。
# kubeConfigを持ってきます # https://github.com/loft-sh/vcluster/blob/v0.2.0/cmd/vclusterctl/cmd/connect.go#L93 ❯ kubectl exec -n bar bar-0 -c syncer -- cat /root/.kube/config > barConfig.yaml # ポートフォワードします(vcluster connectでの挙動を一緒) # https://github.com/loft-sh/vcluster/blob/v0.2.0/cmd/vclusterctl/cmd/connect.go#L170 ❯ kubectl port-forward -n bar bar-0 8443:8443 Forwarding from 127.0.0.1:8443 -> 8443 Forwarding from [::1]:8443 -> 8443 ... # 別のセッションを起動 ❯ kubectl get all -A --kubeconfig ./barConfig.yaml NAMESPACE NAME READY STATUS RESTARTS AGE kube-system pod/coredns-66c464876b-qrm6t 1/1 Running 0 102m NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default service/kubernetes ClusterIP 10.100.50.206 <none> 443/TCP 102m kube-system service/kube-dns ClusterIP 10.100.38.116 <none> 53/UDP,53/TCP,9153/TCP 102m NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE kube-system deployment.apps/coredns 1/1 1 1 102m NAMESPACE NAME DESIRED CURRENT READY AGE kube-system replicaset.apps/coredns-66c464876b 1 1 1 102m
おわりに
ざっくりとvclusterを触ってみましたが、名前空間ごとの環境分離がいい感じにできそうなツールに見えました。
さすがにこれを本番環境に導入、は今の段階では想定できませんが……例えば社内の勉強用/検証用の環境として用意したりする分には非常に便利だろうなと感じます。 仮想クラスターを作成して証明書情報を取得、LoadBalancerを立ててポートフォワード抜きで接続できるように設定すればお手軽Kubernetes as a Serviceの完成ですね。
おうちKubernetesをそれっぽく加工する話 - MetalLBとかExternalDNSとかcert-managerとか
おうちKubernetes(を含むベアメタルKubernetes)を「マネージドサービスっぽく」使えるようにしよう!というのが本記事の目標
一応前日譚
ストレージのプロビジョニング
生のKubernetesで適当なPVCを作成しても何も反応しません。これは当然で、生のKubernetesにはストレージをどのように用意すれば良いのかが定義されていないからです。 プラグインなしでPVCとPVを紐つける場合、ユーザー自身がPVを作成してPVCと紐つける必要があります。このやり方は静的なプロビジョニングと呼ばれています。
例えば
apiVersion: v1 kind: PersistentVolume metadata: name: static-pv spec: storageClassName: manual capacity: storage: 1Gi accessModes: - ReadWriteOnce hostPath: path: "/tmp/static-pv"
というPVと
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: static-pvc spec: storageClassName: manual resources: requests: storage: 1Gi accessModes: - ReadWriteOnce
こんなPVCを作成することで、PVCstatic-pvcはPVstatic-pvを消費することができます。
さらに何かしらのPodがこのPVCを消費すると、PodからPVCへの書き込みがPVが定義されているノード上の/tmp/static-pvに書き込まれるようになります。
一応この運用をしていくことでアプリケーションが永続化ボリュームを利用することはできます。……が、こんなトイルをPVCを作成するたびにやっていてはキリがない!もっとクラウドサービスっぽくやりたい!
そこで、NFSを利用した動的プロビジョナー「nfs-subdir-external-provisioner」を利用してPVCから動的にPVを作成して消費する動的プロビジョニングのセットアップについて紹介します。

nfs-subdir-external-provisioner
nfs-subdir-external-provisionerは「すでに稼働している」NFSサーバーと接続し、PVCに合わせてNFSがストレージを用意してくれる動的プロビジョナーです。Helmでサクッとデプロイできるのも良いところです。 今回は下記のような環境で構築をします。
NOTE: 本音を言うと自宅にNFS搭載のNASが欲しかったのですが予算がないため購入を断念。代わりに家に転がっていたSSDをUbuntuサーバーに追加でマウントしてNFSサーバーを構築しました。
インストール
インストールはHelmチャートが用意されているのでそれをそのまま利用するのが良いと思います。
# Helmリポジトリを追加
> helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
> helm repo update
# helm show values nfs-subdir-external-provisioner/nfs-subdir-external-provisioner で設定できる項目一覧を確認できます
> cat <<EOF > values.yaml
nfs:
server: 192.168.100.101
path: /exports/kubernetes/ # 専用のサブディレクトリを切っておきました(個人的な趣向)
storageClass:
provisionerName: nfs
defaultClass: true # デフォルトのStorageClassに設定
name: nfs
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
EOF
# kube-system名前空間内にインストール
> helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner -f values.yaml -n kube-system
動作確認
適当にPVCとそれを消費するPodを用意して動作確認をしてみます。
> cat <<EOF > dynamic-pvc-pod.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-pvc
spec:
resources:
requests:
storage: 1Gi
accessModes:
- ReadWriteOnce
---
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
volumeMounts:
- mountPath: /data
name: pvc
restartPolicy: Always
volumes:
- name: pvc
persistentVolumeClaim:
claimName: dynamic-pvc
EOF
> kubectl apply -f dynamic-pvc-pod.yaml
> kubectl get pods nginx
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 3m6s
実際にPodにPVCが消費されて、NFSのディレクトリ配下にサブディレクトリが作成されていれば動作確認完了です。
ロードバランサーの作成
生のKubernetes上にデプロイされているPodの中に外からアクセスするには、kubectl port-forwardを利用するかtype: NodePortのServiceを利用するしか方法がありません。カッコ悪いですし、可用性にも欠けます。
もっとクールに、クラウドサービスのようなロードバランサーがほしいところです。
そこで、MetalLBとNGINX Ingress Controllerというツールを導入して、クラウドサービスのようなロードバランサーを自動で作成させてみましょう。
MetalLB
ベアメタルKubernetes定番のMetalLBです。細かい挙動に関しては解説しませんが、このMetalLBを利用することでtype: LoadBalancerなServiceを利用してKubernetesクラスタの外部から内部へアクセスすることができるようになります。

インストール
インストールするために、まず環境のネットワークで予約できるIPアドレス帯を確認しておいてください。今回は192.168.100.200から192.168.100.250までのIPアドレスが予約できていると仮定して話を進めます。
# MetalLBが利用するConfigmapを作成、MetalLBは起動時にこのConfigを読み込みます
> cat <<EOF > metallb-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: my-ip-space
protocol: layer2
addresses:
- 192.168.100.200-192.168.100.250 # 予約されたIPアドレスを指定する
EOF
# metallb-system名前空間を作成します(metallbが起動するデフォルトの名前空間)
> kubectl create namespace metallb-system
# リソースをデプロイします、今回は検証時最新だったMetalLB v0.9.6を利用しています
> kubectl apply -f metallb-config.yaml -f https://raw.githubusercontent.com/metallb/metallb/v0.9.6/manifests/metallb.yaml
動作確認
metallb-system名前空間上のPodがすべてRunningになったところで、適当なServiceを作成して動作確認をしましょう。
> cat <<EOF > lb.yaml
apiVersion: v1
kind: Service
metadata:
name: lb
spec:
ports:
- port: 80
selector: {}
type: LoadBalancer
EOF
> kubectl apply -f lb.yaml
> kubectl get svc lb
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
lb LoadBalancer 10.97.232.211 192.168.100.201 80:31932/TCP 11s
作成したServiceにEXTERNAL-IPが振られていれば、正常に動作しています。
NGINX Ingress Controller
L7ロードバランサーも欲しくなってきましたね?欲しくなりませんか??
NGINX Ingress ControllerはKubernetesのIngressリソースを管理するIngressコントローラーです。個人的にIngressコントローラーの中で一番シンプルで使いやすいコントローラーだと思います。
NGINX Ingress Controllerはその名の通りNGINXを利用してIngressを実装しているControllerで、Ingressリソースを作成するとそのIngressの要求に合わせたNGINXが設定されます。

インストール
Helmでシンプルにインストールできます。
# Helmリポジトリを登録します > helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx > helm repo update # helm show values ingress-nginx/ingress-nginx で指定できるValueを確認できます > helm install ingress-nginx ingress-nginx/ingress-nginx
動作確認
ingress-nginx-controllerのPodが起動した後に、Ingressリソースを作成して動作確認してみます。
> cat <<EOF > ingress-sample.yaml
# 適当なPod
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: http
---
# Podに紐つけるService
apiVersion: v1
kind: Service
metadata:
name: ingress-sample
spec:
ports:
- port: 80
name: http
selector:
app: nginx
---
# Serviceに紐つけるIngress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sample
spec:
rules:
- http:
paths:
- backend:
service:
name: ingress-sample
port:
number: 80
path: /
pathType: Prefix
EOF
# IngressとServiceとPodを作成
> kubectl apply -f ingress-sample.yaml
# Ingressリソースの状態を確認してみる
> kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress <none> * 192.168.100.202 80 64s
# 実際にアクセスしてみる
> curl 192.168.100.202
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
しっかりIngressのAddressからPodへアクセスできることを確認して動作確認完了です。
ドメインとTLS
ここまでで、おうちKubernetes(ベアメタル環境)においてもロードバランサーを経由してKubernetes外部から内部へアクセスできるようになりました。しかし、今のままではIPアドレスでアクセスせざるをえないためやっぱりカッコ悪いです。 またTLSを利用したセキュアな接続も実現できないため、安心安全なKubernetes生活にはまだ程遠いです。
そこで、External DNSとcert-managerを利用してServiceやIngressに対して自動的にドメインを発行&TLS化を行ってみます。
External DNS
External DNSは外部のDNSサーバーやDNSサービスに対してリクエストを行い、DNSレコードの変更を行ってくれるツールです。Kubernetesのリソースを監視して、リソースの状態に合わせて動的にDNSレコードの変更を行うこともできます。 自分でDNSサーバーを構築して利用する方法もありますが、後でTLS化を行うことも踏まえてPublicなDNSであるCloudflare DNSを利用することにします。

インストール
External DNSを利用する前に、ご自身のドメインを持っていることをご確認ください。ドメインをCloudflareに登録したのち、Cloudflare DNSのAPIキーを取得してきます。
APIキーを元に、SecretリソースとExternal DNSをデプロイします。
# マニフェストを作成
# YOUR_DOMAIN/YOUR_API_KEY/YOUR_EMAIL を適切な値に置き換えてください
> cat <<EOF > externaldns.yaml
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-secret
namespace: kube-system
type: Opaque
stringData:
api-key: YOUR_API_KEY
email: YOUR_EMAIL
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
namespace: kube-system
spec:
selector:
matchLabels:
app: external-dns
strategy:
type: Recreate
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
args:
- --source=service
- --source=ingress
- --domain-filter=YOUR_DOMAIN
- --provider=cloudflare
env:
- name: CF_API_KEY
valueFrom:
secretKeyRef:
name: cloudflare-secret
key: api-key
- name: CF_API_EMAIL
valueFrom:
secretKeyRef:
name: cloudflare-secret
key: email
resources:
limits:
cpu: 50m
memory: 50Mi
requests:
memory: 50Mi
cpu: 10m
EOF
# External DNSをデプロイ
> kubectl apply -f externaldns.yaml
動作確認
デプロイしExternalDNSが起動した後、Ingressを作成してドメインが自動的に登録されていることを確認してみます。
> cat <<EOF > externaldns-sample.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: http
---
apiVersion: v1
kind: Service
metadata:
name: ingress
spec:
ports:
- port: 80
name: http
selector:
app: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
spec:
rules:
- host: nginx.YOUR_DOMAIN # 自身のドメインに置き換えてください
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80
EOF
# Ingressたちをデプロイ
> kubectl apply -f externaldns-sample.yaml
デプロイ完了後、しばらくするとnginx.YOUR_DOMAINのドメイン名に対してPrivateIPアドレスが登録されることを確認して動作確認完了です。(dig nginx.YOUR_DOMAINなど)
cert-manager
cert-managerはTLS証明書の発行やローテーションを自動化するためのツールで、Let's EncryptやHashicorp Vaultなどの認証局と連携することができます。cert-managerはCRDとしてデプロイされるIssuer(認証局)とCertificate(証明書)を元にTLS証明書を発行しSecretsに保存してくれます。

インストール
Helmでシンプルにインストールできます。
# Helmチャートを追加 > helm repo add jetstack https://charts.jetstack.io > helm repo update # cert-managerをインストール > helm install cert-manager jetstack/cert-manager \ --namespace kube-system \ --set installCRDs=true # ビルドインのCRDをデプロイ
動作確認
今回は、CloudflareのDNSとLet's Encryptを利用して動作確認をします。先のセクションでExternal DNSをデプロイしたのでそれに合わせてリソースをデプロイします。
まずはIssuerを用意します。
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-staging namespace: kube-system spec: acme: email: YOUR_EMAIL privateKeySecretRef: name: letsencrypt-staging-privatekey server: https://acme-staging-v02.api.letsencrypt.org/directory # server: https://acme-v02.api.letsencrypt.org/directory 本番環境はこちらのURL solvers: - dns01: cloudflare: apiKeySecretRef: name: cloudflare-secret key: api-key email: YOUR_EMAIL
ClusterIssuerはクラスタ全体で管理できるIssuerです。マルチテナントな環境では利用しにくいですが、今回はおうちKubernetesを対象としているのでClusterIssuerを利用しています。(Issuerも同じように定義できます)
なお、spec.acme.privateKeySecretRef.nameで指定されたSecretにTLS証明書に利用する秘密鍵が保管されます。
次にCertificateをデプロイします。
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: nginx-tls-staging spec: dnsNames: - nginx.YOUR_DOMAIN issuerRef: group: cert-manager.io kind: ClusterIssuer name: letsencrypt-staging secretName: nginx-tls-staging
Certificateをデプロイすると、cert-managerがIssuerの情報とともに実際にTLS証明書を取得してきてくれます。取得してきたTLS証明書はCertificateのspec.secretNameで指定したSecretに保存されます。
実際にIngressでTLS証明書を指定してデプロイすると、エンドポイントのTLS化がされていることを確認できると思います。
> cat <<EOF > secure-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: http
---
apiVersion: v1
kind: Service
metadata:
name: secure-nginx
spec:
ports:
- port: 80
name: http
selector:
app: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: secure-nginx
spec:
rules:
- host: nginx.YOUR_DOMAIN # 自身のドメインに置き換えてください
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80
tls:
- hosts:
- nginx.YOUR_DOMAIN
secretName: nginx-tls-staging
EOF
# Ingressたちをデプロイ
> kubectl apply -f secure-nginx.yaml
# 証明書の発行を待ち、実際にアクセスしてみる
> curl https://nginx.YOUR_DOMAIN
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
おわりに
さっくりとおうちKubernetesをセットアップして、マネージドサービスっぽいKubernetesにお着替えさせてあげることができました。
一方で、Secretsの暗号化がされていなかったり監視機構が働いていなかったりと安心安全なKubernetes生活はまだ遠いです。この辺りも少しずつ改善していこうかなと思います。
おうちKubernetesを構築した話

Kubernetesの資格CKA/CKADを取得し、なにか自分にご褒美を与えたいな〜と思い……おうちKubernetesを構築することにしました!楽しみにしてたんだ!!
前日譚: ryusa.hatenablog.com
モチベーション 🦾
そもそもなんでおうちKubernetesなんて?と言う話から……
自分の仕事柄、職場のエンジニアの多くが自宅になにかしら機材を持ち込んで幸せになってる人が多いんですよね
- おうちKubernetes
- おうちサーバー&おうち iLO
- おうちESXi
- おうちクラウドサービス
- おうちハニーポット
- おうちBGPフルルート
先日も仕事の帰り道に数万する機材をポチって自宅に搬入したとか話を聞き、これはもはや一種の宗教じゃないのか とても羨ましい!ぜひ我が家にも!!と、ぼくも家に機材を搬入してみたいな〜と思ってました。
とはいえ、残念ながら自分は仕事で機材に触る機会も少なく、いきなり高価な機材に手を出すのは難易度が高すぎると感じていたので、まず簡単なKubernetesクラスターを家に置いておきたい欲求があったわけでした。
構築 - 物理編
まずは機材の調達から。 今回はRaspberry Pi 4 3台をクラスタリングするので備品を調達。
それらをPoEで繋ぐので適当なLANケーブルとハブを用意
SDカードは適当に家に転がっていた32GBのものを利用しています。
先にネタばらししてしまうと、Raspberry Piのみでクラスターを構築した際にどうしてもコントロールプレーンが不安定になってしまったため、家に転がっていたノートPCをコントロールプレーンとして活用しています。
コントロールプレーン:DELL Inspiron13 7378 (Ubuntu Server化済み)
構築 - 論理編
一通り機材のインストールが完了した後、いよいよKubernetesクラスターの構築作業です。microk8sで構築してもいいかな〜とも思いましたが、やはりここはおうちクラスター、妥協はしたくありません。kubeadmでインストールをしました。
今回のクラスターでは、下記の構成で構築していきました。
- OS:Ununtu20.04
- Kubernetes: v1.21.0
- コンテナランタイム:containerd 1.4.4
- CNI:Calico v3.18.3
またStorageのprovisionerとしてNFSを利用したいので、コントロールプレーンにはNFSもインストールしておきます。
最終的な我が家のネットワークはこちら
┌──────────────┐
│ The Internet │
└──────────────┘
▲
│
│
│
Router
192.168.0.0/16
│
│ ┌───────────────────────┐
│ │ Home Network │
├─────────┤ 192.168.1.0/24 (DHCP) │
│ └───────────────────────┘
│
│ ┌────────────────────────────────────────────┐
│ │ Kubernetes Cluster Network │
│ │ 192.168.100.0/24 │
│ │ │
│ │ │
│ │ ┌──────────────────────────────┐ │
│ │ │ Seagull Ship(Control Plane) │ │
│ │ │ 192.168.100.101 │ │
│ │ └──────────────────────────────┘ │
│ │ │
│ │ ┌──────────────────────────────┐ │
│ │ │ Seagull (Data Plane) │ │
│ │ │ 192.168.100.[1-3] │ │
│ │ └──────────────────────────────┘ │
└─────────┤ │
│ ┌──────────────────────────────┐ │
│ │ MetalLB │ │
│ │ 192.168.100.[200-250] │ │
│ └──────────────────────────────┘ │
│ │
└────────────────────────────────────────────┘
これに合わせてuser-dataなどに色々インストールしていきます。
Control Plane
適当に転がっているUSBメモリにUbuntuイメージを焼き、ブートさせました。公式のKubernetesのインストール手順に従ってKubernetesをインストールしました。
/etc/netplan/99-manual.yamlにIPアドレス周りの設定を記入して完了(諸事情によりWifiでコントロールプレーンの運用をしてみました、今のところ安定して動いてます)
network: version: 2 renderer: NetworkManager wifis: wlp1s0: dhcp4: no dhcp6: no addresses: [192.168.100.101/24] gateway4: 192.168.0.1 nameservers: addresses: - 192.168.0.1 access-points: "YOUR_SSID": password: "YOUR_PASSWORD"
Data Plane
Raspberry Piにインストールする予定のSDカードにuser-dataを埋め込み全自動kubernetesインストールまで組み込みました。下記のようなuser-dataを作成し、/system-boot/配下に配置しておくとRaspberry Pi起動時に自動的にkubeadm/kubeletのインストールがされるようになります。
#cloud-config # hostname hostname: seagull01 # Japan timezone: "Asia/Tokyo" # Never allow to ssh using password ssh_pwauth: false # User "user" users: - name: user gecos: I am user primary_group: user groups: [adm, audio, cdrom, dialout, dip, floppy, lxd, netdev, plugdev, sudo, video] shell: /bin/bash sudo: ALL=(ALL) NOPASSWD:ALL lock_passwd: true ssh_import_id: - gh:RyuSA # Update packages package_update: true package_upgrade: true packages: - apt-transport-https - ca-certificates - curl - gnupg - lsb-release - nfs-common # for nfs client mounts: - [ tmpfs, /tmp, tmpfs, "defaults,size=256m", "0", "0" ] - [ tmpfs, /var/tmp, tmpfs, "defaults,size=256m", "0", "0" ] write_files: # iptablesがブリッジを通過するトラフィックを処理できるようにする - content: | net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 owner: root:root path: /etc/sysctl.d/k8s.conf permissions: '0644' # containerdに必要な設定 - content: | overlay br_netfilter owner: root:root path: /etc/modules-load.d/containerd.conf permissions: '0644' # 必要なカーネルパラメータ - content: | net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 owner: root:root path: /etc/sysctl.d/99-kubernetes-cri.conf permissions: '0644' runcmd: - sudo swapoff -a - sudo sysctl --system # install containerd - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg - echo "deb [arch=arm64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - sudo apt-get update - sudo apt-get install containerd.io - sudo mkdir -p /etc/containerd - containerd config default | sed -e '/containerd.runtimes.runc.options/a \ \ \ \ \ \ \ \ \ \ \ SystemdCgroup = true' | sudo tee /etc/containerd/config.toml - sudo systemctl restart containerd # install kubeadm - sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg - echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list - sudo apt-get update - sudo apt-get install -y kubelet kubeadm kubectl - sudo apt-mark hold kubelet kubeadm kubectl containerd.io
起動して30分程度放置すると完了しているので、再起動して各ノードでkubeadm joinコマンドを叩いてKubernetesクラスターに参加すればOKです。かんたん!

不安定なクラスター
実は一度Raspberry Piのみでクラスターを構築してみました。……が、かなり不安定になってしまいすぐコントロールプレーンが死んでしまう事態に。 原因としては、どうやらSDカードのI/Oスループットが遅すぎるせいでetcdの書き込みが遅れてしまいkube-apiserverが死んでしまう……ということっぽかったです。(詳細は不明)
そのため、急遽家に転がっていたDellのノートPCをUbuntu Serverにしてコントロールプレーンとして再起動することでクラスターを安定化させました。
おわりに
さて、これでKubernetesクラスターが出来上がりましたが……知ってる人は知っての通り、このままでこのクラスターはあまり美味しくありません。
- 永続化ボリュームが自動で作成されない
- provisioner/storageclassがない
- なんらかの方法でボリュームを作成して使いたい人に渡してあげる必要がある
- Serviceのtype: LoadBalancerが使えない
- Kubernetesの外へアプリケーションの公開が面倒になる
- Ingressが使えない
- Ingress Controllerがない
かなり美味しくないです。美味しくないので、加工してより美味しくするための加工作業を進めていきます。(書いたら↓に追加していきます)
追記:書いた ryusa.hatenablog.com
CKA(Certified Kubernetes Administrator)とCKAD(Certified Kubernetes Application Developer)の受験ログ
先月Linux Foundationがホストしている資格を取得しました🎉
- CKA(Certified Kubernetes Administrator)
- CKAD(Certified Kubernetes Application Developer)


完走してきてすでに半月ほど経ってしまったので、忘れないうちにアウトプットしておこうと思い試験の概要や参考にした資料など公開しておきます。
メタ情報 👀
受験した際のメタ情報を書いておきます、人によって、受験時期によって違いますからね……
- 自分はアプリケーションエンジニアです
- Linuxのことなんもわからん……
- Docker歴3年、Kubernetes歴10ヶ月
- 勉強期間1ヶ月
- 受験したのは4月中旬
- Kubernetesのバージョンはv1.20
概要 📄
Kubernetesはオープンソースのコンテナオーケストレーションツールで、CNCF(Cloud Native Computing Foundation)のGraduatedなプロジェクトです。 Kubernetesはコンテナ化されたアプリケーションやミドルウェアを管理し、複数台のサーバークラスター上にデプロイして水平スケールしたり自動復旧の機構を提供してくれます。
CKA
CKAはKubernetesの管理者として、基本的な操作からメンテナンス、アップグレードなど多様な知識を持っていることを証明する資格です。試験内容はペーパーテストではなく実技テスト、実際にWebコンソールを叩いて操作を行い点数がつけられます。CKAは後述するCKADと比べより広い知識が要求されます。そりゃインストールからメンテナンスと、要求されている内容が広いですからね……
受験当時のシラバスは下記の通りでした。
- ストレージ10%
- トラブルシューティング30%
- ワークロードとスケジューリング15%
- クラスタのアーキテクチャ、インストール、構成25%
- サービスとネットワーキング20%
CKAD
CKADはKubernetesでアプリケーションの開発を行う開発者として、基本的な操作や監視、アプリケーションデザインの選択などより専門的な知識を持っていることを証明する資格です。CKAと同様、試験内容はペーパーテストではなく実技テストです。
受験当時のシラバスは下記の通りでした。
- コア コンセプト13%
- 構成18 %
- マルチコンテナPod10%
- マルチコンテナPodのデザイン パターン
- 可観測性18%
- Podのデザイン20%
- サービスとネットワーキング13%
- ステートの持続性8%
なおCKADは2021年にコンテンツが大幅に変わるらしく、すでに専用サイトでアナウンスがなされています。
より開発者らしく、OCI準拠のコンテナイメージの作成やCRD、Admission Controllなどの知識が増えるようですね。(難化する予感)
参考資料 👀
先人と同じく、勉強の際にいくつか資料を参考にしました。いずれも非常に優秀な資料で、CKA/CKADを受験目的としない人もKubernetesに携わる人には手を取って欲しいと思います。
Certified Kubernetes Administrator (CKA) with Practice Tests - Udemy
正直、CKA/CKAD共にこのUdemyクラスでほぼ十分でした。若干インド訛りが入っている英語での解説でしたが、字幕が用意されておりかつ丁寧に解説してあるので非常にわかりやすかったです。 やや古いKubernetes上での解説ですが、Kubernetesのコアコンセプトなどは現在最新のv.1.21でも通用する内容だと思います。
特に、このクラスには本番さながらのハンズオンがついています。実際にWebコンソールでコンテンツのPractice Testを受けることができるので自習する上で非常に助かりました。
Kubernetes完全ガイド 第2版 (Top Gear)
日本語で書かれている、名前通りKubernetesの完全ガイドの本です。Kubernetesの基本的なコンセプトからアーキテクチャの話など幅広く解説してくださってます。「はじめに」の章で書かれていますが、この本一冊読み切るとCKA/CKADの取得に必要な知識はだいたい手に入ります。(インストールやメンテナンス周りは追加で勉強が必要そうですが……)
自分はKubernetes完全ガイドは辞書のようにして必要な時に字引きするといった使い方でした。もっと活用すればよかった……
個人的な体験談 👍
体験談はたくさんあるので箇条書きにて……
試験申し込みの話
- 監督官の言語に注意
- 監督官の言語選択であって、試験自体の言語とは関係なし(好きな言語で受験可能)
- 英語でサクサクコミュニケーションが取れる人は通常のCKA/CKADで申し込みすればOK
- 英語に難あり、という人はCKA-JP/CKAD-JPというセッションで申し込みすると監督官が日本語で対応してくれるらしいです(詳細は先人の体験談参照)
- たまにセールやってます
"試験"そのものの話
- 試験受ける場所は事前に決めておきましょう
- 僕はCKA試験前日になってア○ホテル予約しました(個室で安定したワイヤードネットワークあり)
- デイタイム利用で3k円、助かる……
- ハンドブック(Linux Foundation Certification Exams: Candidate Handbook - T&C DOC)は読みましょう
- PDFとして出力できるのでGoogle翻訳にぶち込んで日本語にしました
- 「え、これダメなの!?」的なものもあるので要注意
- 試験環境は事前チェックを通しておきましょう
- 試験を予約するときに環境のpre-checkができます、5秒でチェック終わるのでやっておくことをおすすめします
- 自分はM1 MacbookProで受験しましたが、マイク/カメラ共に組み込みのものを使用できました
- マイク、カメラ、画面共有のChrome権限が必要になります
勉強とかの話
- 基本的なLinuxコマンドは使えるようにしましょう
- kubernetes.io に出てくる範囲のコマンドは使えるようにしておく
- 一方、Linuxの基本的なコマンド(systemctlやjournalctl、bashのリダイレクトなど)も使えるようにしておく
- 同僚にもらったLPICの教科書熟読した(試験直前はこれしかやらなかった)
- kubectlを使いこなせるように練習をしておく
- 例えば
kubectl run --image nginx nginx --port 80 --env "FOO=BAR" --labels "env=prod" --command -- ls /etcとか kubectl explain pod.spec --recursive | lessはCKAD試験で何度か使いました
- 例えば
- 一番効果的な勉強は「Kubernetesを作って壊して直してみる」こと
- kubeadmでクラスター作る際にパラメータを変えてみたりコンテナランタイムを変更してみたり
- kube-proxyを弄るとどんな感じに壊れるかとか、kube-controller-managerを破壊するとどうなるのかとか
- OSを強制シャットダウンするとどうなるかとか
- 体験記を漁っておく
- 試験自体のトラブルとか事前に知っておくとPanicになりにくくなります
- CKAD試験開始と同時にWebコンソールにInternal Server Errorが表示されましたが、チャットにHelp Me!!!と訴えるとすぐ対応してくれました
試験中の話
- 言語設定は選べます
- 日本語/英語どちらでも受験できます
- ただ日本語は機械翻訳感があり、意味が不明になっている部分もあるので困ったら英語に戻しましょう
- UdemyのCKAクラスを受講していると英語の表現に慣れることができてGood
- わからない問題は捨てる
- 先人方の体験記にもある通り、わからん問題は捨てる
- 捨てたところで数点失うだけ、満点を取ることが試験の目的ではないので……
- トイレに行っておく
- 試験中もトイレ退室は可能ですが、当然試験前に行っておきましょう
- 一敗
- Don't Panic
試験後の話
- 試験終了後24時間以内にメールで結果が返ってきます
- 自分は20時間くらいで返ってきました
- 試験に受かると、credlyというサービスのバッチがもらえます
- ↓のような感じ、SNSで自慢しよう!ww
完走した感想 🏃♂️
正直なところ、CKAもCKADも1回目は落ちるかも?と思ってましたが一発で受かってほっとしてます。😎 試験自体もその勉強も、かなり楽しく終わらせることができて個人的にはかなり良い試験体験だったかな?と思ってます。
超個人的な感想なのですが……
- CKA : 想像以上に簡単、コントローラーや証明書などの動きがわかってれば余裕
- CKAD : 想像以上に時間が足りない、完答するにはガチタイピング能力が求められる
- 共通 : kubectlが使いこなせればたぶん受かる
と、正直kubectlをしっかり使えれば受かるんじゃないかな?と思います。
ブログ開設 🎉
試しにQiitaから脱してはてなブログで情報のアウトプットをしていこうかなと思い開設しました
Who Am I
RyuSAと申します、某金融系事業に携わっている今年で4年目のエンジニアです。 お仕事ではフロントエンド/バックエンドのアプリケーションの設計や実装、最近はソフトウェアアーキテクトなどを担当してます。
JavaやSpring Bootでのバックエンド、TypeScriptとReactのフロントエンド……というありきたりな構成を担当しつつ、KubernetesなどのCloudNativeな技術スタックを利用したソフトウェアの開発などに興味持ってます
なんでブログ開設?
結論から言ってしまえば、一度ブログをメンテナンスしてみたかったからです、いや本当にしょうもない理由なんですが…… 今まではQiitaやZenn.devに色々アウトプットしてきたのですが、そろそろここらで試してみよ〜と思ったんですね
自分の個人感ですが、エンジニアとしての活動はアウトプットありき(俗に言う「アウトプット駆動」)だと思ってますので、ここらでプラットフォームを変えて気分一転してみるのもいいかなと
なんではてなブログ?
実はnoteも開きました。 > note.com
エンジニア向けのブログサイトといえば、だいたい
- はてなブログ
- つよつよな人が多い気もする
- note
- キラキラしてる……
- Medium
- セルフホスト
が鉄板なのかなと思ってます(他にあれば教えて欲しい)
セルフホストする程トラフィック持ってないだろうし、Mediumは日本語フォントがちょっと苦手だし……という気持ちではてなブログとnoteの2つで試してみようと思い立った次第です。なのでしばらくははてなとnoteに同じ内容の記事が載ることになるかと思います。
どこかで統一しよう……
さいごに
ブログを書きながら、noteとはてなブログそれぞれの良いところの比較ができたらいいな〜と思ってます。今後もアウトプット駆動で学習を継続していきます。
現場からは以上です






