外出先でも快適なお家k8sクラスタを構築する

1 なぜお家Kubernetesクラスタか

なんでもクラウドサービスな時代だが、やはりそれなりのスペックを求めるとそれなりのお値段になってしまう。また、サービスによっては下回りの構成変更の柔軟性や新機能が利用できるようになるまでラグもサービスに依存する。そんな状況を鑑みると、実験場として請求書に怯えることなく使えるお家クラスタが欲しくなってくるものだ。

2 Workerのハードウェア構成と価格

Figure 1: (左): 届いたパーツ群、(右): 配線が完了したNUC

Master用のNUCは自宅のものを再利用したため、ここには含まれていない。こちらのスペックは数世代前のi5 NUCで32GBメモリ、2TBのディスクを積んでいる。メモリが市場に溢れてる今が買い時ということで8GBと悩んだ結果16GBにした。ツクモで全て買おうとしていたが購入制限に引っかかったので1台だけソフマップとなっている。また、スイッチのポートも足りなくなってきたので一緒に購入した(最近のNETGEARは謎にスマホアプリから設定変更や状態確認ができて面白い。

Table 1: パーツと価格
Shop Item Price(JPY) Number Total(JPY)
TSUKUMO NUC8I3BEH (BOXNUC8I3BEH) 35,366 2 70,732
CT2K8G4SFS824A (Crucial 16GB:8GBx2) 11,858 3 35,574
WDS500G2B0B (WD 500GB SSD) 6,980 3 20,940
Sofmap NUC8I3BEH (BOXNUC8I3BEH) 35,366 1 35,366
Amazon GS105E-200JPS (NETGEAR Switching Hub 5port) 3,570 1 3,570
BSACC0802BKA (BUFFALO power cable 3pin/2pin plug) 482 3 1,446
Yodobashi LA-SL6-005BK (SANWA SUPPLY CAT6 LAN cable 0.5m Black) 230 5 1,150
168,778

新世代のNUCは電源ボタンが写真右の底面にある。この向きでそのまま置いてしまうと電源ボタンが押されて切れてしまうため、割り箸を下に引いてスペーサにするという微妙な配置を行なっている。

3 マシンにUbuntuサーバをインストールする

3.1 Live USBの作成

  1. UbuntuサーバのISOイメージを公式ページからダウンロードする
  2. lsusb で操作するUSBデバイスが認識されていることを確認する
  3. USBを抜き差しして ls /dev/sd* から該当デバイスを特定する
  4. sudo umount /dev/sd* でデバイスをアンマウントする
  5. sudo mkfs.ext4 /dev/sd* でext4にフォーマットする
  6. sudo dd if=/path/to/ubuntu-server.iso of=/dev/sda でISOイメージをUSBに書き込む1

3.2 インストーラの進化

BIOSは Intel ロゴが表示されたタイミングで F10 を押すと起動する2。最近はOSのインストールを自動化してしまってなかなかインストーラの画面を見ることがないが、久しぶりに使ったらGitHubのユーザ名でSSH鍵指定できるようになっていて便利だった 👏

Figure 2: SSH鍵を指定している様子

3.3 LVMのディスク容量を増やす

Ubuntuのインストール時に無心3でぽちぽちしてしまった結果、数百メガしかないrootができあがった。結果EphemeralStorageが足りなくてPodがEvictionされてるということに。LVMの意味という感じだが、論理ボリュームを100%まで拡張することにした。

# 指定するLG(logical group)とLV(logical volume)名は =df= で調べることができる
root@worker1:/home/ladicle# df |grep vg
/dev/mapper/ubuntu--vg-ubuntu--lv   4062912 2609448   1227368  69% /

# 100%まで拡張する
root@worker1:/home/ladicle# lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv
  Size of logical volume ubuntu-vg/ubuntu-lv changed from 4.00 GiB (1024 extents) to <464.26 GiB (118850 extents).
  Logical volume ubuntu-vg/ubuntu-lv successfully resized.

# 最後にリサイズしたら完了 (容量を明示的に指定しなければ、自動的にサイズを合わせてくれる)
root@worker1:/home/ladicle# resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv
resize2fs 1.44.1 (24-Mar-2018)
Filesystem at /dev/mapper/ubuntu--vg-ubuntu--lv is mounted on /; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 59

# dfで確認すると増えていることが分かる
root@worker1:/home/ladicle# df -h|grep vg
/dev/mapper/ubuntu--vg-ubuntu--lv  457G  2.6G  436G   1% /
Code 1: lvextendとresize2fsによるLVMのディスク容量拡張

4 Kubernetesの構成検討と構築

Figure 3: 構築後のノードステータス

せっかくのお家クラスタなので普段使わない構成で組むことにしたが、それでも躓くくことなく構築が完了する。Kubernetesの安定感が実感できた。4

4.1 cri-oのセットアップ

cri-o を手順に沿ってセットアップする。以下の手順には含まれていないが、k8sから利用するためには /etc/default/kubelet の KUBELET_EXTRA_ARGSに --cgroup-driver=systemd フラグを追加する必要がある。

# 必要なモジュールの追加と設定
$ modprobe overlay
$ modprobe br_netfilter
$ cat > /etc/sysctl.d/99-kubernetes-cri.conf <<EOF
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
$ sysctl --system

# CRI-Oと依存パッケージのインストール
$ apt-get update -y
$ apt-get install software-properties-common -y
$ add-apt-repository ppa:projectatomic/ppa
$ apt-get update -y
$ apt-get install cri-o-1.13 -y

# CRI-Oの有効化と起動
systemctl enable crio
systemctl start crio
Code 2: cri-oのセットアップ手順

dockerに慣れすぎて、コンテナ周りで何か困るとしまってついつい docker コマンドを叩いてしまう。しかし、慣れ以外はランタイムをcri-oに変えた事による悩み事は発生していない。

4.2 kubeadmによるクラスタ構築

4.2.1 事前準備

事前準備は、 イメージレジストリの指定 / Swapの無効化 / kubelet, kubeadm, kubectlのインストール の3つ。イメージレジストリは、 /etc/containers/registries.conf にイメージをPullするレジストリを指定しないと no registries configured エラーになる。また、Swapが有効になっていると kubeadm init でエラーになる。そのため、 swapoff -a でオフにする必要がある。ツール群のインストール時は、 apt update したときに勝手にアップデートされると困るのでついでに apt-mark hold しておくと良いだろう。

4.2.2 Master/Workerの構築

cri-o用のフラグを忘れないようにしつつMasterは kubeadm init で構築していく。最初にバリデーションが走るので何か問題があれば事前に教えてくれる。また、中途半端に構築されてしまうと面倒なのでdry-runオプションで動作は事前に確認しておくと安心。 Workerのセットアップはmasterセットアップ時にgetしたtokenを使ってセットアップしていく。cri-oの設定も必要なのでcri-socketのオプションをMasterと同様に追加する。

# Masterノードで以下を実行
$ kubeadm init --cri-socket "/var/run/crio/crio.sock"

# Workerノードで、Master構築時に取得したトークンを利用して以下を実行
$ kubeadm join <master>:6443 --token <token> \
    --discovery-token-ca-cert-hash <cert> \
    --cri-socket "/var/run/crio/crio.sock"
Code 3: kubeadmによるクラスタの構築

4.3 ciliumのCNIプラグインを適用する

cri-o用のプラグインも提供されているのでこれを適用する。

$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.5.0/examples/kubernetes/1.14/cilium-crio.yaml
Code 4: CNIプラグイン(cilium)の適用

4.4 MetalLBの構築

MetalLBのマニフェストからControllerとSpeakerがインストールできる。設定はConfigMapで管理できる。構築後に作成しても、Speakerが変更を検出して再読み込みしてくれる。ここでは、BGPルータ無いので、L2な設定を選択。DHCPの範囲外を指定している。

$ kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.7.3/manifests/metallb.yaml

# 全部立ち上がっていたらok
$ kubectl get pods -n metallb-system -w
NAME                         READY   STATUS    RESTARTS   AGE
controller-cd8657667-hz47m   1/1     Running   0          32s
speaker-f9wb4                1/1     Running   0          32s
speaker-jllwx                1/1     Running   0          32s
speaker-tvl59                1/1     Running   0          32s
Code 5: MetalLBマニフェストの適用と確認
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: my-ip-space
      protocol: layer2
      addresses:
      - 10.0.1.200-10.0.1.250
Code 6: IP範囲の指定

5 CloudflareのArgo経由でSSHする

Figure 4: Cloudflare AccessとArgoによるお家クラスタへのSSH

今までddnsで自宅サーバを直接晒していたが、流石にそろそろ不安なのでCloudflare Argoに移行することにした。このArgoというのは、CloudflareのCDN経由で、トンネルを貼った先にアクセスできるサービス。直接サーバを晒さないで良いので安全 & CDN通せるので早いというメリットがある。金額も月額$5 + $0.10/GBのため個人利用しやすい。トンネルを貼る先はHTTPでもSSHでもよい。HTTPの方はよく記事があるので、ここではSSHの設定をのせていく。

5.1 Server設定

CloudflareのDomain Dashboardで、 Traffic > Argo を有効にすると、Tunnelも利用できるようになる。利用にはcloudflaredが必要なので、該当バイナリをココからダウンロードする。バイナリのインストールが完了した後、 tunnel login を実行するとブラウザからどのサービスとtunnelするかを確認できる。認証に成功すると、証明書が保存される。

# cloudflareコマンドのインストール
$ wget https://bin.equinox.io/c/VdrWdbjqyF/cloudflared-stable-linux-amd64.tgz
$ tar -zxvf cloudflared-stable-linux-amd64.tgz
$ sudo mv cloudflared /usr/local/bin
# バージョンを確認する
$ cloudflared --version
cloudflared version 2019.4.1 (built 2019-04-19-2149 UTC)

# 実際にサーバからCloudflareにトンネルが貼れること確認する
$ cloudflared tunnel login
Please open the following URL and log in with your Cloudflare acchostname: ssh.example.com
url: ssh://localhost:22ount:hostname: ssh.example.com
url: ssh://localhost:22

https://dash.cloudflare.com/argotunnel?callback=<loginurl>

Leave cloudflared running to download the cert automatically.
You have successfully logged in.
If you wish to copy your credentials to a server, they have been saved to:
~/.cloudflared/cert.pem
Code 7: cloudflareコマンドのセットアップと動作確認

常に手動でトンネルを貼ることはできないので、Systemdで起動できるようにServiceを作成する。cloudflareコマンドにはサービスを作成するための service install コマンドがあるのでこれを使っていく。このコマンドは、実行ディレクトりにある conf.yaml/etc/cloudflared にコピーし、この設定ファイルを読み込むService作成する。

$ cat <<EOF > conf.yaml
hostname: ssh.example.com
url: ssh://localhost:22
EOF

$ sudo cloudflared service install
Code 8: コマンドを使ったServiceの作成

5.2 Client設定

Tunnel張るために必要なので、Server側と同じように cloudflared コマンドをインストールする。コマンド直打ちでも問題ないが、毎回プロキシ設定を入力するのは面倒なので ~/.ssh/config に以下のような設定を追加する。

Host home-k8s
  User ladicle
  HostName ssh.example.com
  ProxyCommand cloudflared access ssh --hostname %h
Code 9: Argo用のSSH設定

5.3 Cloudflare Accessでアクセスユーザを制限する

Cloudflare Accessを使うとアクセス制限を設定することができる。5ユーザまでは$3/monthなのでご家庭用には3ドルで十分。色々な設定ができるが、今回は先ほどのGifアニメーションの通りで、SSHの前段にGitHub認証を挟んだ。設定はGUIからAccessPolicyをぽちぽちするだけ。注意点としては、サブドメインにWildcardは使えないことくらい。

また、一人ずつアカウント設定するのは面倒だったため、家庭用GitHub Organizationを作成してそのOrganizationに対してアクセスを許可するようにした。GitHub側ではAuthAppを作成してCloudflareを設定する形になる。

6 kubeadmによるクラスタアップグレード

Figure 5: v1.15.2にアップグレードされたクラスタ

4月に構築したときはk8sの最新バージョンがv1.14だったが、8月5日にはv1.15.2もリリースされた。Kubernetesのドキュメントには親切にもアップデート方法も書かれているのでこの通りに進めていく。kubeadmによるk8s本体のアップグレードは今回特にハマらなかった。どちらかというと上にのっているリソースを確認してバージョンに合わせて修正する方に時間がかかる。

# バージョン固定しているので一時的に解除する
$ sudo apt update -y
$ sudo apt-cache policy kubeadm
$ sudo apt-mark unhold kubeadm && \
  apt-get update && apt-get install -y kubeadm=1.15.2-00 && \
  apt-mark hold kubeadm

# upgrade planコマンドでサポートバージョンを確認した後、適用する
$ sudo kubeadm upgrade plan
$ sudo kubeadm upgrade apply v1.15.2
# Masterは1台構成なのでupgrade nodeする(HAのばあい2台目以降はapply)
$ sudo kubeadm upgrade node

# kubeletを更新する
$ sudo apt-mark unhold kubelet kubectl && \
  sudo apt-get update && sudo apt-get install -y kubelet=1.15.2-00 kubectl=1.15.2-00 && \
  sudo apt-mark hold kubelet kubectl && sudo systemctl restart kubelet
$ sudo systemctl restart kubelet
Code 10: Masterのアップグレード
# 一台ずつdrainして上のアプリケーションを別のノードに退避させる
$ kubectl drain worker1 --ignore-daemonsets

# 同じようにkubeadmを更新する
$ sudo apt update -y
$ sudo apt-cache policy kubeadm
$ sudo apt-mark unhold kubeadm && \
  apt-get update && apt-get install -y kubeadm=1.15.2-00 && \
  apt-mark hold kubeadm

# 同じようにkubeletを更新する
$ sudo apt-mark unhold kubelet kubectl && \
  sudo apt-get update && sudo apt-get install -y kubelet=1.15.2-00 kubectl=1.15.2-00 && \
  sudo apt-mark hold kubelet kubectl && sudo systemctl restart kubelte
$ sudo systemctl restart kubelet

# メンテモードを終了する
$ kubectl uncordon worker1
Code 11: Workerのアップグレード

クラスタ上にのっているリソースはChangeLogに合わせて事前に修正しておく

6.1 CNIプラグインのアップグレード

Ciliumの公式サイトにはアップグレードガイドがあり、アップグレード前にすべきことも書かれている。が、読み落として壊しかけてしまった。今回は設定ファイルに変更があるため、事前にConfigMapの修正が必要だった。


  1. 数分くらいかかるので終わるまで待機 [return]
  2. インテル® NUC にオペレーティング・システムをインストールする方法 [return]
  3. 無心ダメ絶対 [return]
  4. 構築であって、安定した運用は別の話 [return]
.