JavaScriptを有効にしてください

自宅Kubernetesクラスタはじめました

 ·   ·  ☕ 12 min read

流行りのKubernetesってやつを触ってみたので頭の整理も兼ねて手順をまとめておく。

Q. GKEなどのKaaSを使えばいいじゃん
A. オタクなの

本記事に書いてあること

  • kubeadmと各種OSSで適当にKubernetesクラスタを構築する方法

本記事に書いてないこと

  • Kubernetesの使い方
  • 本番環境に求められるようなアレコレ

概要

ゴール: 当ブログのホスティング

デファクトスタンダードのコンテナオーケストレータであるKubernetesは、アプリをコンテナイメージで用意し、インフラをYAMLにイライラしながら宣言的に設定するだけで、いろんなことをイイ感じにやってくれて、サービスの運用をめっちゃ簡単にしてくれる。詳細はグーグル大先生に譲るとして、本記事ではこのブログをホスティングできる程度までがんばる

何をすればいいの?

Kubernetesで当ブログをホスティングするまでの道のりはそれなりに遠い。

  1. システム構成の検討
    1. ハードウェアを用意する
    2. ソフトウェア構成を検討する
  2. Kubernetesクラスタの構築
    1. 前提条件を満たす
    2. コンテナランタイムcontainerdをインストール
    3. kubeadmでクラスタを構築する
    4. Calicoでコンテナネットワークを構築する
  3. 必要な機能の導入
    1. MetalLBでLoadbalancer Serviceを導入する
    2. NGINX Ingress ControllerでIngress Serviceを導入する
    3. cert-managerでTLS証明書を供給できるようにする
    4. NFS subdir external provisionerでNFSサーバをPersistent Volumeにする
  4. 外部公開
    1. アプリのデプロイ
  5. (追記)アップグレード
    1. v1.20.5 -> v1.21.7
    2. v1.21.7 -> v1.22.4

やる

実際にはほぼAnsibleでやったけどわかりやすさのためコマンドで載せる

1. システム構成の検討

構成について述べる。

1.1. ハードウェアを用意する

一般のご家庭にあるようなネットワークを用意した。

  • 1つのパブリックIPを持ち、ポートフォワーディングでサービスを公開できる
  • 名前解決のためにdnsmasqでDHCPとDNSをやる。hostsに書いてもいいと思う
  • 内部用のドメイン名: lab.in.ebiiim.com
  • 外部用のドメイン名: lab.ebiiim.com

物理マシンとしてIntel NUCを用意した。

  • Core-i5 / RAM 16GB / SSD 500GB
  • 十分な台数があるので仮想化はせずにそのまま使う
  • マスターノードのホスト名: km0
  • ワーカーノードのホスト名: kw0 kw1 kw2

ストレージはNFSにした。Cephなど分散ストレージもそのうち試したい。

  • NFSサーバをインストールしたUbuntu
  • ホスト名: pv0

1.2. ソフトウェア構成を検討する

梅雨くらいの時期にやっていたので、だいたいそのときの最新版。

要素名称バージョン
OSUbuntu20.04.2
本体kubelet1.20.5-00
CLIツールkubectl1.20.5-00
デプロイツールkubeadm1.20.5-00
CRIcontainerd1.4.4
CNICalico3.18.1

2. Kubernetesクラスタの構築

インストールしたばかりのUbuntuがある想定で、KubernetesクラスタができてPod間で通信ができるところまでやる。

2.1. 前提条件を満たす

対象: 全ノード

いろいろあるけど公式のドキュメントに全部書いてある。

スワップの無効化

/etc/fstab のswapって書いてある行をコメントアウトして再起動

カーネルモジュール&カーネルパラメータ

ネットワーク関連のパラメータ。再起動後反映される。

/etc/modules-load.d/k8s-cri.conf

1
2
overlay
br_netfilter

/etc/sysctl.conf

1
2
3
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
レガシーiptables

いくつかのOSでデフォルトになっているnftablesだと動かないのでiptablesをインストールする。

1
2
3
4
5
6
sudo apt install -y iptables arptables ebtables

sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy
その他
1
2
3
4
5
sudo apt install -y \
  curl \
  apt-transport-https \
  ca-certificates \
  software-properties-common

2.2. コンテナランタイムcontainerdをインストール

対象: 全ノード

コンテナを動かすためにコンテナランタイムをインストールする必要がある。最新の情報はこのあたりにある。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# aptのリポジトリを設定
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"
# インストール
sudo apt update && sudo apt install -y containerd.io
# 設定ファイルを作る
containerd config default > /etc/containerd/config.toml

2.3. kubeadmでクラスタを構築する

kubeadmをインストールして、Kubernetesクラスタを構築する。

パッケージの取得

対象: 全ノード

ここに書いてある

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

# インストール; holdでバージョンを固定する
sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

# ブート後にkubeletを起動する
sudo systemctl enable kubelet

ここで再起動しておく。

sudo reboot
kubeadmの実行

対象: マスターノード

ここに書いてある。

kubeadm initをマスターノードで実行してクラスタを初期化する。その後、表示されたkubeadm join ...をワーカーノードで実行してクラスタに参加する。ターミナルを閉じるなどしてコンソールのログが消えた場合はkubeadm token create --print-join-commandでクラスタに参加するためのコマンドを再度作成する。

1
2
3
4
5
6
7
8
# 初期化
sudo kubeadm init --control-plane-endpoint km0:6443

# kubectlの設定ファイルをホームディレクトリにコピーする
# kubeadm init実行後に同じコマンドが表示される
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

対象: ワーカーノード

kubeadm initを実行するとクラスタに参加するためのコマンドが表示されるので、コピーしてきて実行する。

# だいたいこんな感じ↓
# kubeadm join km0:6443 --token {{ トークン }} --discovery-token-ca-cert-hash sha256:{{ ハッシュ }}

2.4. Calicoでコンテナネットワークを構築する

もうすでにPodを起動できる状態になったが、このままでは異なるノード間でPod間通信ができない。これを解消するためにCNIプラグインのCalicoをインストールする。

詳細はドキュメントに書いてある。

1
2
3
curl https://docs.projectcalico.org/manifests/calico.yaml -O
# 何も変えなくても動いた。自動でIPアドレスを検知するっぽい?
kubectl apply -f calico.yaml

3. 必要な機能の導入

2までの手順でPodの起動やPod同士の通信が可能になるが、今回の目的は本ブログのホスティングなのでそれだけではダメ。データの保持や外部からのアクセスを可能にしなきゃいけない。そのために必要なことをやる。

3.1. MetalLBでLoadBalancer Serviceを導入する

クラスタの外にサービスを公開するためにはLoadBalancerタイプのServiceを使う。オンプレでこれを実現するためにMetalLBを使う。

インストール手順はちょっと複雑なのでドキュメントを見てほしい。

1
2
3
4
5
6
7
8
# configmapをいじってstrictARPをtrueにする
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl apply -f - -n kube-system

# manifestをapplyする
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.6/manifests/namespace.yaml && \
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.6/manifests/metallb.yaml
アドレスプールの設定

今回はL2モードを使い、サブネットの一部をKubernetes用とした。VRRPのVirtual IPみたいにGARPでフェールオーバーすると思う。L3モードでやる場合はBGPに対応したルータを用意しよう。

LoadBalancer Serviceで使うために10.0.0.0/24のうち10.0.0.201-10.0.0.220までを割り当てる。また、後述するIngress Serviceで使うために10.0.0.221を割り当てる。

metallb-pool-conf.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
---
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 10.0.0.201-10.0.0.220
    - name: ingress-nginx-pool
      protocol: layer2
      addresses:
      - 10.0.0.221/32    
1
kubectl apply -f metallb-pool-conf.yml

3.2. NGINX Ingress ControllerでIngress Serviceを導入する

外部公開がIPレベルしかないと不便じゃん?そこでリバースプロキシ的な機能を追加してくれるのがIngress Serviceである。オンプレでこれを実現するためにINGINX Ingress Controllerを使う。

詳細はここを読んで。

MetalLBと併用するためのアノテーション

manifestをapplyするだけなんだけど、MetalLBと併用する&MetalLBのプール名がdefaultでない場合はアノテーションを1行入れなきゃいけない。

まずはmanifestをダウンロードしてくる。注意点:provider/cloud/deploy.yamlを使うこと

1
curl https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/cloud/deploy.yaml > ingress-nginx.yml

263行目あたりにこれを追記する。
metallb.universe.tf/address-pool: {{ プール名 }}

before

1
2
3
4
5
6
# Source: ingress-nginx/templates/controller-service.yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
  labels:

after

1
2
3
4
5
6
7
# Source: ingress-nginx/templates/controller-service.yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    metallb.universe.tf/address-pool: ingress-nginx-pool
  labels:
そしてmanifestを適用する
1
kubectl apply -f ingress-nginx.yml

3.3. cert-managerでTLS証明書を供給できるようにする

TLS証明書を自動で用意してくれないとかマジ無理なのでcert-managerを使う。

cert-managerをインストールする

manifestをapplyするだけ

1
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.2.0/cert-manager.yaml
issuerを設定する

lab.ebiiim.comのサブドメインを使うことにする。まずはレジストラのDNSサーバに次のようなAレコードを登録する。

1
A *.lab.ebiiim.com. あなたのパブリックIPアドレス

次にこのようなClusterIssuerのmanifestをapplyする。

issuer-prod.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-production
  namespace: cert-manager
spec:
  acme:
    email: [email protected]
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-production
    # HTTP-01 challenge solver
    solvers:
      - http01:
          ingress:
            class: nginx

これで*.lab.ebiiim.comのTLS証明書を自動発行できる。Let’s Encryptのレート制限に注意。

利用するとき

こんな感じのアノテーションを書く:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: hoge-ingress-tls
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-production"
spec:
  tls:
  - hosts:
    - hoge.lab.ebiiim.com
    secretName: hoge-tls
  rules:
  - host: hoge.lab.ebiiim.com
    http:
      paths:
      - path: /
        ...

3.4. NFS subdir external provisionerでNFSサーバをPersistent Volumeにする

Q. NFSサーバしかないのですが、自動プロビジョンに対応した永続ボリュームを使いたいです。
A. nfs-subdir-external-provisionerでできます。

これはhelmを使った。helmのインストールはバイナリをダウンロードしてくるだけなので省略。

1
2
3
4
5
6
# リポジトリの追加
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
# nfs-subdir-external-provisionerのインストール
# - nfs.server=NFSサーバのアドレスor名前
# - nfs.path=NFSのパス
helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --set nfs.server=pv0.lab.in.ebiiim.com --set nfs.path=/exports/nfs

4. 外部公開

3までの手順によりPodで動いているウェブサーバをHTTPSで公開できたのであとは本ブログを公開するだけ。

4.1. アプリをデプロイする

普通にやるだけなので省略

5. (追記)アップグレード

最新版を含む3つのマイナーバージョンがサポートされる。1.20はもう古いので公式のガイド(kubeadmの場合)に従って1.22にアップグレードする。

バージョン間の互換性はここに書いてある。アップグレードの際は要注意。

5.1. v1.20.5 -> v1.21.7

このとおりにやる

kubeadmのアップグレード

1
2
3
4
sudo apt update
sudo apt-mark unhold kubeadm
sudo apt install -y kubeadm=1.21.7-00
sudo apt-mark hold kubeadm

コントロールプレーンのアップグレード

1
2
3
sudo kubeadm upgrade plan
# 注意点とか警告とかが出てくるので読んでおく
sudo kubeadm upgrade apply v1.21.7
1
sudo kubeadm upgrade node

ノードのアップグレード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# kubectlが使えるところでやる
kubectl drain {ホスト名} --ignore-daemonsets
# 対象ホストでやる
sudo apt-mark unhold kubelet kubectl
sudo apt install -y kubelet=1.21.7-00 kubectl=1.21.7-00
sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload
sudo systemctl restart kubelet
# kubectlが使えるところでやる
kubectl uncordon {ホスト名}

確認

1
kubectl get nodes

5.2. v1.21.7 -> v1.22.4

このとおりにやる

前の項と同じなので省略。

Q. アップグレードしたら○○が動かなくなった
A. それもアップグレードしなきゃいけないかも。今回の例だとcert-manage v1.2.0はここに書いてあるようにK8s 1.21までしか対応していないので最新版のv1.6.1にする。
Q. めっちゃ大変じゃん
A. 大変さをアプリから切り離すっていう目的は達成できているから……

その他

Q. Ubuntuがスリープになるんだけど
A. sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
Q. Ubuntuで一部のNICが繋がっていないとブート時にめっちゃ待たされるんだけど
A. systemctl disable systemd-networkd-wait-online.service && systemctl mask systemd-networkd-wait-online.service

感想

クソ面倒くさい楽しい!✌(‘ω’)✌

後日談

このうち1台がUltra HD ブルーレイ再生機になりました。