Unyablog.

のにれんのブログ

個人で運用しているサーバーを Kubernetes に整理する

個人で運用している VPSKubernetes を乗せて既存のシステム(Web アプリとか Slack Bot とか Cron ジョブとか)をガッと移行した。

もともとは itamae 使って Systemd でサービス立ち上げていたのだけど、ソフトウェア依存とか権限分離とか冪等性とか面倒になってきて、うおお Docker でデプロイするぞ!となっていた。

初めは Docker Compose で立てようと思っていたけど、k8s のほうが楽しそうなので k8s にした。実際楽しいので良いと思う。

何を使って Kubernetes を立てるか

Microk8s

Microk8sCanonical が配布しているもので、Ubuntu にログインすると広告が出てくるアレ。

インストールしたらすぐにシングルノードで動かせるように作られている。アドオンもいくつかあって、k8s を自前で運用するときのネットワークのめんどいところを解決してくれるのが魅力的。

初めはこれを使って立てていたのだけど、しばらく運用していると色々デメリットが見えてきてやめてしまった。

メリット

  • 入れるのが簡単
  • いくつかアドオンが用意されていて、ネットワークプラグインなどを簡単に有効化できる
  • シングルノードで動かすようにパラメータなどが予め設定されている
  • (k3s とは違って)動いているのは k8s そのものである

デメリット

  • 入れ方が通常と異なるのでハマる
    • kubectlmicrok8s.kubectl になってるし kubelet の動く Systemd unit も普通と違う
    • Docker ではなく containerd を直接使っていてハマる(ハマった例
    • コンポーネント/snap に入っていて全体的にムズい
  • 時々変な挙動になる ()
    • Cilium 対応してるけど Pod の IP range 変だったり…
  • 依存ソフトウェアが古かったりする ()
    • 新しいのを入れようとすると Microk8s の環境に合わせる必要があってダルかったり
  • アドオンを入れるときに細かい設定ができず、結局自分で入れることになる
  • snap 使いたくない

やってることは複雑ではないので自力で修正可能だけど、変な挙動を割と多く見てしまったし、そこで苦労する必要ないなと思った。

kubeadm

kubeadmKubernetes の標準的なインストール方法。kubeadm をインストールしてコマンドで kubeadm init すると証明書等が作成されてクラスタが立ち上がる。

初めは面倒くさそうで避けていたけど、やってみると特段難しいこともなかった *1 ので Microk8s ではなくこれを使うことにした。

メリット

  • 素の Kubernetes が入るのでパッケージに特有なことでハマらない
    • Ubuntu は利用者が多いのでちゃんと動く
  • apt で入るので分かりやすい

デメリット

  • アップグレードやネットワークプラグインの選定など、自分で管理する必要がある
    • とは言ってもドキュメント読んでたら難しくない

他に考えたこと

  • k3s

    軽いのは魅力的だが、使いたい機能が無くて困りそうなことや、周辺コンポーネントが動かなかったりしてハマりそうなのでやめた。

  • minikube

    VM 上で動かすのを前提としている*2っぽいのでやめた。

  • kubespray

    kubespray は内部で kubeadm を使っている Ansible 用の Playbook。これを使うとアップグレードとかもよしなにやってくれるらしいが、今回はシングルノードかつ kubeadm がそんなに複雑でなかったので不要と判断した。

kubeadm を使ったセットアップは itamae に任せることにした。とは言ってもこれぐらいのシンプルなもの。

# kubeadm や kubelet のインストール
include_recipe "../kubeadm"

kubeadm_conf = '/tmp/kubeadm.conf'

remote_file kubeadm_conf do
  owner "root"
  group "root"
  mode "644"
  not_if "test -f /etc/kubernetes/admin.conf"
end

execute "kubeadm init --ignore-preflight-errors Swap --config #{kubeadm_conf}" do
  not_if "test -f /etc/kubernetes/admin.conf"
end

ネットワークプラグイン

ネットワークプラグインには Cilium を使うことにした。Flannel とは違って NetworkPolicy 使えるのと、BPF 使ってて面白そう + 軽そうだから。

実際、 Calico と比べると iptables のルールがとてもスッキリしている。Cilium コマンドを使えば今どんな policy を適用しているかが分かりやすく出てきて良い感じ。

通信周りで mTLS などより細かい制御をするには Istio あたりが必要になるが、今回は重くなるだろうと思ったので入れていない。

Volume 周り

static だけどたまに編集していくようなウェブコンテンツ(https://nonylene.net/ とか)を serve することを考えると、ノード上の適当な場所にコンテンツを置きつつ、 nginx のコンテナから Persistent Volume としてマウントするのが良さそう。

ただ、 Persistent Volume を使うためだけに NFS 立てたりするのは大変なので、去年出た Local Persistent Volume を使うことにした*3

具体的には、 nonylene.net/storage-web のラベルがあるノードに PV を作って PVC を発行し、Pod に紐付けるようにした。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: web
  labels:
    app.kubernetes.io/name: web
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /data/web/html
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: nonylene.net/storage-web
          operator: In
          values:
          - "true"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web
spec:
  storageClassName: local-storage
  accessModes:
    - ReadWriteOnce
  selector:
    matchLabels:
      app.kubernetes.io/name: web
  resources:
    requests:
      storage: 1Gi

オブジェクト管理

Kubernetes は Deployment や Service のオブジェクトを置くことでデプロイを行うのだけど、設定ファイル流し込みとかパッケージ管理とかしたくなる。

まず、設定ファイル流し込みなど YAML の統合的な管理には kustomize を使っている。kustomize を使うと

  • 複数の YAML を一つの YAML にマージして適用できる
  • アプリを構成するオブジェクトに共通のラベルを付けて管理ができる
  • ConfigMap をファイルから生成する
  • Secret を env やファイルから一元的に管理・生成する

ので k8s の管理がだいぶ楽になる。itamae あたりとちょっと近い。

次に、パッケージ管理としては Helm を使う。これは自分で YAML を触ることがないような Cilium や ingress-nginx あたりのインストールに使う。

初めはこの辺も直接 YAML ダウンロードして kustomzie 配下に置けばいいやと思っていたのだけど、

  • アップデートしないまま忘れる
  • ビルドした YAML が巨大になる→適用が遅くなる
    • CRD やそのドキュメント定義してたりすると非常に大きな YAML になってしまう

のでやめた。

とはいっても、 Helm のコマンドだけでは管理しづらいので Helmfile を使ってパラメータの管理をしている。

アプリケーションの移行

今まで Systemd で適当に動かしていたアプリケーションを、 Docker で動かすようにコンテナ化していく。

とはいっても

  • できるかぎり環境変数からパラメータを取れるようにする
    • 今までは config.py を作ることが多かった
  • Dockerfile 作る
  • オープンソースで作っていたので) Dockerhub に上げてビルドしてもらう

ぐらいでそこまで大変な作業ではなかった*4

今は Dockerhub に上げているけど、ビルドキューが捌けるのがとても遅いのがネック。我慢できなくなったら GitHub Actions でやるようになると思う。

感想・補足

  • コンテナ化

    従来と比べてデプロイ周りがとても楽になった。 git clone してセットアップして…というサーバー上で動かさないといけないことが随分と減った。 さらに、冪等性がどうこうとか考えなくてよく、統一的な方法でデプロイできるので、各種ミドルウェアを導入するのが今までよりも気軽にできるようになった。apt で配ってないソフトウェアでも Docker では多く配布されてるので、それらが引っ張ってくるだけで動くのが嬉しい。

  • Kubernetes

    Kuberentes の一番大きなメリットはコンテナを良い感じのホストに配置してくれることだが、今回はシングルノードのため関係ない。その他の点で Docker Compose と比べると

    • 複数のサービスを統合的に管理できる
      • Docker Compose とかだとレポジトリごとの管理になるから Systemd unit 増えていくし、共通のデータストアとか作るのめんどい
    • オブジェクトの変更を行うと自動的に変更が反映される
      • ssh しなくても kubectl apply だけで完結できる
    • Ingress という、初めに HTTP リクエストを振り分けるためのロードバランサが定義されており、周辺ツールとも統合的に使える
      • 例えば cert-manager を用いれば Let's encrypt の証明書取得を自動的に行なってくれる

    のが良いと思った。

    ただ、YAML を書きまくるのは体験が良くない*5。TOML で書けたり aws-cdk 的なのを使えればいいのにな。

    また、たまに「これが欲しいけど Issue になったあと動きがない」というケースがあり、まだ開発中なのだなぁと思う。

  • リソース

    悪い点としてはリソースをたくさん食うことで、k8s だけで 900MB ぐらいはメモリを持っていかれる。今の所メモリ2GBのVPSで動かしているけど、アプリケーションを色々乗せるとカツカツになってしまった。3GB~4GBあると余裕だと思う。

  • HTTP 以外

    メールや DNS の権威サーバーを外部に出したい場合は、Ingress は使えないので MetalLB あたりで仮想のロードバランサーを作る必要がある。今回は DNS とメール転送は Google Domains のマネジメントサービスに移した結果、HTTP だけになっていたので使わなかった。

  • 開発環境

    あくまでも k8s はデプロイする環境として使うので、開発環境としては Docker Compose や直接プロセス立ち上げたりする。

これで気軽にサービスをデプロイできる環境が整った。自分で Web アプリケーション作ってもデプロイや運用めんどいな…とか、MongoDB 使うとそのデプロイが…とか最近思っていたけど、これからはどんどん使っていこうと思う。

*1:シングルノードだからといって何かしないといけないこともなかった

*2:vm-driver=none すれば使えるけど非推奨 https://minikube.sigs.k8s.io/docs/reference/drivers/none/

*3:シングルノードなので hostPath を使っても良かったけど、こちらはノード本体の設定を触る用途で使われることが多そう

*4:Docker は普段から開発に使っているので

*5:YAML は書きづらいし読みづらいと常々思っている