自宅サーバを Kubernetes を使って自宅クラスタにしてみた

## はじめに

一家に一台 Kubernetes クラスタがある時代がやってきました。[Z Lab Advent Calendar 2018 の 1日目](https://qiita.com/superbrothers/items/0fa69a9554654388f2d2) では、@superbrothers がインスタ映えするクラスタの作り方を記事にしていました。そこで今度はより実践的に Kubernetes クラスタで自宅サーバを組もうと思った場合、どのような構成になるかを紹介します。

## はじめに その2

最初の会社に入社して、最初のボーナスで買ったのは自宅サーバ用に [VIA C3](https://ja.wikipedia.org/wiki/VIA_C3) がオンボードで乗った　Mini-ITX のマザーボードだったことを思い出す今日この頃。途中で [Transmeta の Efficeon](https://ja.wikipedia.org/wiki/Crusoe) だったり、Pentium M だったり、 Mac mini に移行したり色々移り変わりはあったものの、自宅サーバを始めた頃のデータをそのまま引き継いで今にいたります。

直近の構成は Intel NUC のメモリ 16GB / Ubuntu Trusty のシングルホストで Docker でした。アプリケーション (Docker コンテナ) のデプロイには Ansible を使っていました。この構成で今まで自宅サーバで運用していた諸処の問題が解決するにいたり、 **コンテナ素晴らしいな！** と認識したものです。そこで今度はそれをクラスターで運用して、さらにコンテナのメリットを享受したくなったのでした。

## 基本方針

-   全てを Kubernetes / コンテナで管理する。
-   ネットワークと電源の冗長化は諦める。(それ以外は冗長化する)

ステートフルなアプリケーション、Kubernetes それ自体を含めて Kubernetes で管理したいと思います。クラスタの運用は全て `kubectl` コマンドで！

と、言っても、自分の技術的な未熟のせいで結局この基本方針の対象外としたものは以下になります。

-   クラスターそれ自体の `Manifest` の置き場
    -   クラスターが壊れた時の復旧ができないので、GitHub のプライベートレポジトリです…。
-   ルーター
    -   自作して `kubelet` でサービスを管理すれば行けそう。
    -   とりあえずルータの監視は [Prometheus と連携](https://github.com/mdlayher/edgemax_exporter)したい。
-   自宅ネットワークの DHCP サーバ
    -   これも Kubernetes 上に乗っけることは可能なんじゃ？と思わなくもないですが、既存のネットワークをいじるのが面倒なので、ルーター付属のものを使ってます。
-   スイッチングハブ
    -   自作するのも現実的ではないので。

他にもある気がしますが、とりあえず思いつくものは以上。

## 物理構成

![図1.png](https://qiita-image-store.s3.amazonaws.com/0/9958/3b45c089-056c-337f-e527-3acfc9e249bb.png)

Kubernetes クラスタ自体は、Kubernetes のコントロールプレーンを配置するノード 3 台と、通常のワーカーノード 3 台、バックアップ用途の NFS+ZFS 専用ノード 1 台の計 7 台構成です。VM　とかは介さず、ベアメタル。

概要は以下。

-   マスターロールノード (Container Linux by CoreOS stable)
    -   Minnowboard Turbot
    -   WesternDigital SSD Green 120GB
-   ワーカーロールノード (Container Linux by CoreOS stable)
    -   ECS Liva Z
    -   DDR3 SO-DIMM PC3-12800 16GB (8GB x 2)
    -   WesternDigital SSD Blue 250GB
    -   INTEL SSD 525 Series mSATA 120GB
-   バックアップロールノード (Ubuntu 18.04)
    -   ASRock J3160-ITX
    -   DDR3 SO-DIMM PC3-12800 8GB (4GB x 2)
    -   WesternDigital SSD Green 120GB
    -   WesternDigital Red 6TB x 3
    -   WesternDigital Blue 6TB x 3

![スクリーンショット 2018-11-30 16.48.11.png](https://qiita-image-store.s3.amazonaws.com/0/9958/0003dcb0-cecc-335c-9e7d-deeefe130bc7.png)

### マスターロールノード

そもそも自分がなぜ Kubernetes を自宅で飼ってみようと思ったかというと、きっかけは以下のツイートでした。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">It&#39;s alive! My home Kubernetes cluster is alive! <a href="https://t.co/BH4P6ysbJJ">pic.twitter.com/BH4P6ysbJJ</a></p>&mdash; Ian Lewis (@IanMLewis) <a href="https://twitter.com/IanMLewis/status/812255557096599552?ref_src=twsrc%5Etfw">2016年12月23日</a></blockquote>

ここで使われている SoC が [Minnowboard Turbot](https://minnowboard.org/) というボードである、ということを知ると、一瞬考えたのちに、6枚発注してました。結局、後々、[他に良いボードがあること](https://up-board.org/) や、ワーカーに使うにはいささか貧弱であるということが発覚したため、とりあえずマスターノード用途に使うことにしました。

また、マスターノードは etcd を飼う計画であるため、[ストレージには SSD　を利用しています](http://www.fraction.jp/log/archives/2017/03/31/minnowboard-using-ssd)。

![IMG_1831.JPG](https://qiita-image-store.s3.amazonaws.com/0/9958/43b44141-6bef-7125-c647-36f858c97872.jpeg)

### ワーカーロールノード

[ECS　の Liva Z, Pentium N4200 モデル](https://www.amazon.co.jp/dp/B01N30Z8I7)　にメモリ 16GB、そして内臓の eMMC で容量に問題が出たため `M.2 2242` 接続の SSD 120GB を増設して `/var/lib/docker` にマウントしてあります。また、Rook でデプロイした Ceph の `osd` を飼うノードを兼任するために、USB 3.0 接続の SSD も追加しました。Ceph　を飼うには少し CPU のスペックが足りない感じがしなくはないです。

![IMG_1832.JPG](https://qiita-image-store.s3.amazonaws.com/0/9958/9aeaa149-9cf8-26a0-b2a6-450bf983daa4.jpeg)

### バックアップロールノード

全てのデータを Ceph on Rook on Kubernetes で管理しても良いのですが、このうちどれかが壊れたら全てのデータが飛ぶんじゃないかと言う恐れから、バックアップ用途にディスクをHDD6台, システム用に SSD 1台積んだノードを追加しました。

![IMG_5106.JPG](https://qiita-image-store.s3.amazonaws.com/0/9958/a25a06a7-d040-446b-b986-ce3a789b2857.jpeg)

WesternDigital の Red 6TB x3, Blue 6TB x3 で `raidz2` の構成で 21TB です。Kubernetes からは `NFS` で接続します。使っているマザーボードは ASRock の J3160-ITX なので `SATA` の口が微妙に足りないため PCIe 2.0 to SATA III 3 port のアダプタカードで拡張してあります。

## ミドルウェア

### Kubernetes

何はともあれ Kubernetes です。自宅クラスターで起動しているプロセスは全て Kubernetes で管理されています。が、この自宅クラスターを管理している Kubernetes 自体も Kubernetes で管理されています。現時点での構成はこんな感じ。

![Kubernetes.png](https://qiita-image-store.s3.amazonaws.com/0/9958/213f4294-2b90-f3ba-1af9-821cf5212cce.png)

すみません、図を描いてて気づきました。Docker と `kubelet` は `systemd` で管理されてますね。それ以外だと `etcd` が `Static pod`、`kube-apiserver` がマスターロールノード上で `DaemonSet` で起動、`HAProxy/Keepalived` で冗長化されています。`kube-controller-manager` や `kube-scheduler`、`CoreDNS` といったどこで起動してても構わないプロセスは `Deployment` としてデプロイされています。

ちなみにデプロイ方法は、[自前のスクリプト](https://github.com/yuanying/remora) です。

`kube-system` はこちら。

```
$ k get pod -n kube-system
NAME                                       READY   STATUS    RESTARTS   AGE
coredns-7fbd6fbf45-6f5xf                   1/1     Running   0          2d5h
coredns-7fbd6fbf45-bbt6d                   1/1     Running   0          9d
etcd-192.168.1.111                         1/1     Running   0          42d
etcd-192.168.1.112                         1/1     Running   0          42d
etcd-192.168.1.113                         1/1     Running   0          42d
kube-apiserver-ncfxr                       1/1     Running   0          42d
kube-apiserver-t5p87                       1/1     Running   0          42d
kube-apiserver-whsf9                       1/1     Running   0          42d
kube-controller-manager-68cd59c45f-2h9kt   1/1     Running   1          42d
kube-controller-manager-68cd59c45f-cjzxv   1/1     Running   0          42d
kube-dns-autoscaler-65659d5f76-sk4tt       1/1     Running   0          2d5h
kube-flannel-ds-d25kd                      1/1     Running   0          14d
kube-flannel-ds-hbnzw                      1/1     Running   0          14d
kube-flannel-ds-mp4g9                      1/1     Running   1          14d
kube-flannel-ds-ssw24                      1/1     Running   0          14d
kube-flannel-ds-zb6ht                      1/1     Running   0          14d
kube-flannel-ds-zftd5                      1/1     Running   0          14d
kube-flannel-ds-zhgpl                      1/1     Running   0          14d
kube-keepalived-4jg94                      2/2     Running   0          42d
kube-keepalived-5ppxx                      2/2     Running   0          42d
kube-keepalived-db7v6                      2/2     Running   0          42d
kube-proxy-8kwqp                           1/1     Running   1          14d
kube-proxy-d4b6r                           1/1     Running   0          42d
kube-proxy-frj2n                           1/1     Running   0          42d
kube-proxy-h8j2t                           1/1     Running   0          42d
kube-proxy-n855q                           1/1     Running   0          42d
kube-proxy-sn57r                           1/1     Running   0          42d
kube-proxy-sp2pm                           1/1     Running   0          42d
kube-scheduler-76798585f7-7z6hg            1/1     Running   1          42d
kube-scheduler-76798585f7-bd7bj            1/1     Running   0          42d
pod-checkpointer-9l4n2                     1/1     Running   0          42d
pod-checkpointer-9l4n2-192.168.1.113       1/1     Running   0          42d
pod-checkpointer-bcmsr                     1/1     Running   0          42d
pod-checkpointer-bcmsr-192.168.1.112       1/1     Running   0          42d
pod-checkpointer-qqk68                     1/1     Running   0          42d
pod-checkpointer-qqk68-192.168.1.111       1/1     Running   0          42d
```

### Ceph

`Ceph` は [Rook](https://rook.io) でデプロイしてます。もう、だんだん面倒になって来たので簡単に cluster manifest を載せます。

```yaml
---
apiVersion: ceph.rook.io/v1beta1
kind: Cluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
  dashboard:
    enabled: true
  placement:
    osd:
      tolerations:
      - effect: NoSchedule
        operator: Exists
  dataDirHostPath: /var/lib/rook-ceph
  serviceAccount: rook-ceph-cluster
  mon:
    count: 3
    allowMultiplePerNode: true
  network:
    # toggle to use hostNetwork
    hostNetwork: false
  storage:
    useAllNodes: false
    useAllDevices: false
    storeConfig:
      storeType: bluestore
      databaseSizeMB: 1024
      journalSizeMB: 1024
    nodes:
    - name: "192.168.1.131"
      resources:
        limits:
          cpu: "500m"
          memory: "2048Mi"
        requests:
          cpu: "500m"
          memory: "1024Mi"
      devices:
      - name: "sdb"
    - name: "192.168.1.133"
      resources:
        limits:
          cpu: "500m"
          memory: "2048Mi"
        requests:
          cpu: "500m"
          memory: "1024Mi"
      devices:
      - name: "sdb"
    - name: "192.168.1.132"
      resources:
        limits:
          cpu: "500m"
          memory: "2048Mi"
        requests:
          cpu: "500m"
          memory: "1024Mi"
      devices:
      - name: "sdb"
```

最初、`resources.limits.memory` を `1024Mi` で運用していたのですが、妙に不安定で、`2048Mi` にしてからなんとか安定した感じです。ちゃんとリソースプランニングしないとダメですな！

あと、`Rook` のログを見てて思ったのだが、`device` の名前はもしかしたら `/dev/disk/by-id/` とかでも指定できそうであった。

### NFS

`gcr.io/google_containers/volume-nfs:0.8` のコンテナイメージを利用。バックアップロールのノードにディスク六本まとめた `raidz2` 構成のストレージをマウントした `DaemonSet` を起動しています。

バックアップ方法は、

1.  毎晩 Ceph のファイルシステムから `rsync` する `CronJob` を起動。
2.  手作業で `zfs snapshot`

## おわりに

とりあえずこんなな感じで、自宅でまあまあ安定して運用できる Kubernetes クラスタの作り方を紹介しました。実際に `Ceph` の `C` の字もわからないで運用始めると、安定どころかデータをクラッシュしまくるので、だれか `Ceph` の運用方法教えてください。

---

このエントリは、弊社 Z Lab のメンバーによる [Z Lab Advent Calendar 2018](https://qiita.com/advent-calendar/2018/zlab) の5日目として業務時間中に書きました。6日目は @ysakashita の担当です。