Kubernetes についてほぼ何も知らない状態から,自宅用にクラスタを設置してみました.
はじめに
自宅のサーバーでは家族用の下記のようなサービスを動かしています.
これらの環境を Ubuntu 22.04 LTS に置き換えていっているときに,また 2 年後も似たような作業が発生することに思い当たり,思い切って Kubrenetes を使ったコンテナベースの環境に移行することを決断しました.
コンテナであれば,OS の更新に合わせて各種サービスのコードを修正する必要もなくなり,負担が減ることが期待できます.
事前に知っておいた方が良いこと
Kubernetes について調べ始めると,新しい用語や概念が沢山出てきてちょっと混乱するのですが,その情報の波を乗り越えるに当たって役に立った内容を紹介します.
Youtube 解説動画
Kubernetes の全体像をコンパクトにわかりやすく説明されています.2回ほど見たら大体全体像がつかめ,その後の学習の見通しがとても良くなりました.
クラスタは簡単にリセットできる
作成したクラスタは sudo kubeadm reset
コマンド一発で簡単にリセットできます.
ソフトの中には,一度インストールすると削除しても各種設定が残ってしまって挙動が変わってしまうものがありますが,Kubernetes はそういったことがありません.そのため,とりあえず試してみてよく分からなくなったらリセットする,といった試行錯誤が気兼ねなくできます.
なお,クラスタに対する設定を全て YAML ファイルで行うようにしておけば,リセットしたあとに元に戻すのも簡単です.
検索の期間指定
Web 検索をするときは,なるべく期間の指定をすることがお勧めです.
Kubernetes は現在ホットなソフトだけあって進化のスピードが速く,3年以上前に通用した内容が現在では古くなっていることが少なくないです.そのため,期間を指定せずに普通に検索すると,一昔前には重宝されたけれども既に正しくなくなってしまった情報が上位に来てしまいます.
Google 検索の場合,「ツール」ボタンを押すことで検索結果の対象となるページ更新日の期間を指定できます.
クラスタの構築
ここからはクラスタの構築方法について説明してきます.
ワーカーノードでコンテナを動かすのに必要な作業は,次の4ステップになります.【】は,どこで実施する作業なのかを示しています.
- kubeadm のインストール 【全ノード】
- クラスタの初期化 【マスタノード】
- クラスタネットワークの設定 【マスタノード】
- クラスタへのワーカの追加【ワーカノード】
以降では,それぞれの手順について説明してきます.
kubeadm のインストール 【全ノード】
基本的には,下記の公式サイトの手順を追っていけば OK です.
ただ,Ubuntu 22.04 の場合,cgroup が v1 → v2 に変わったのに伴い,そのままだと Kubernetes が正常に動作しません.次の記事にあるように,containerd の設定ファイルで SystemdCgroup を true に指定する必要があります.
これらを Ansible のレシピにしたのが下記です.レシピを用意しておくとワーカーノードの追加が簡単にできるのでお勧めです.コンテナランタイムは containerd を採用しています.
なお,最低限必要になる設定に対して,下記を追加しています.
- Docker のインストール
イメージの構築用に,containerd の他に docker.ce をインストールしています. - inotify の上限数の拡大
Pod を配置していくと,inotify のリソースが不足することがあるので,上限値を拡大しています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
- name: Install Packages package: name: - apt-transport-https - ca-certificates - curl - name: Install Docker public signing key shell: curl -sSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor --batch --yes --output /usr/share/keyrings/docker-archive-keyring.gpg - name: Add the Docker apt repository lineinfile: path: /etc/apt/sources.list.d/docker.list line: deb [signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu jammy stable create: yes - name: Install Docker public signing key shell: curl -fsSL https://dl.k8s.io/apt/doc/apt-key.gpg | sudo gpg --dearmor --batch --yes --output /etc/apt/keyrings/kubernetes-archive-keyring.gpg - name: Add the Kubernetes apt repository lineinfile: path: /etc/apt/sources.list.d/kubernetes.list line: deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main create: yes - name: Install containerd Kubernetes Packages apt: pkg: - docker.ce - containerd - kubelet - kubeadm - kubectl update_cache: yes - name: Hold Kubernetes Packages dpkg_selections: name: '{{item}}' selection: hold with_items: - kubelet - kubeadm - kubectl - name: Setting kernel module lineinfile: path: /etc/modules-load.d/kubernetes.conf line: "{{item}}" create: yes with_items: - overlay - br_netfilter - name: Load kernel module modprobe: name: br_netfilter state: present - name: Setting Sysctl lineinfile: path: /etc/sysctl.conf regexp: '^{{item.regexp}}.*' line: '{{item.line}}' with_items: - regexp: net.ipv4.ip_forward line: net.ipv4.ip_forward=1 - regexp: net.bridge.bridge-nf-call-ip6table line: net.bridge.bridge-nf-call-ip6table=1 - regexp: net.bridge.bridge-nf-call-iptables line: net.bridge.bridge-nf-call-iptables=1 - regexp: fs.inotify.max_user_instances line: fs.inotify.max_user_instances=100000 - regexp: fs.inotify.max_user_watches line: fs.inotify.max_user_watches=100000 - name: Create containerd config directory file: path: /etc/containerd state: directory mode: '0755' - name: Disable swap lineinfile: path: /etc/fstab regexp: '\sswap\s' state: absent - name: Create containerd config file shell: containerd config default > /etc/containerd/config.toml - name: Setting crictl endpoint shell: crictl config --set runtime-endpoint=unix:///run/containerd/containerd.sock --set image-endpoint=unix:///run/containerd/containerd.sock - name: Enable systemd cgroup lineinfile: path: /etc/containerd/config.toml regexp: '{{item.regexp}}' line: '{{item.line}}' insertafter: '{{item.insertafter}}' with_items: - regexp: ' SystemdCgroup = false' line: ' SystemdCgroup = true' insertafter: 'plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options' - name: Join docker group user: name: '{{ ansible_env.SUDO_USER }}' groups: docker append: yes |
ここで一旦,ノードを reboot しておきます.
実際に運用で使っている最新のレシピは下記に置いています.
クラスタの初期化 【マスタノード】
次のコマンドを実行するだけです.
1 |
$ sudo kubeadm init --control-plane-endpoint=192.168.0.20:6443 --pod-network-cidr=10.0.0.0/16 --upload-certs |
「192.168.0.20」はマスターノードの IP アドレスです.マスターノードを複数設置する場合は,ロードバランサ―の IP アドレスを指定します.ホスト名でもOK.
「10.0.0.0/16」はクラスタで使用するネットワークです.マスターノード等が属するネットワークでは使われていないプライベートネットワークを指定しておきます.基本的には下記の中から選択します.
- 10.0.0.0/24
- 172.16.0.0/20
- 192.168.0.0/16
この後,kubeadm
コマンドでクラスタを操作するための設定ファイルをユーザアカウントにコピーします.
1 2 3 |
$ mkdir -p $HOME/.kube $ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config $ sudo chown $(id -u):$(id -g) $HOME/.kube/config |
この時点で kubectl get pod,svc --all-namespaces -o wide
を実行して,下記のような出力が得られれば OK です.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ kubectl get pod,svc --all-namespaces -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system pod/coredns-6d4b75cb6d-vg9v6 0/1 Pending 0 4h10m kube-system pod/coredns-6d4b75cb6d-vjm74 0/1 Pending 0 4h10m kube-system pod/etcd-kubectl 1/1 Running 2 4h10m 192.168.0.189 kubectl kube-system pod/kube-apiserver-kubectl 1/1 Running 2 4h10m 192.168.0.189 kubectl kube-system pod/kube-controller-manager-kubectl 1/1 Running 2 4h10m 192.168.0.189 kubectl kube-system pod/kube-proxy-66j75 1/1 Running 0 4h10m 192.168.0.189 kubectl kube-system pod/kube-scheduler-kubectl 1/1 Running 2 4h10m 192.168.0.189 kubectl NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR default service/kubernetes ClusterIP 10.96.0.1 443/TCP 4h10m kube-system service/kube-dns ClusterIP 10.96.0.10 53/UDP,53/TCP,9153/TCP 4h10m k8s-app=kube-dns |
3,4行目の coredns だけ STATUS が「Pending」になっていますが,これはこの次のステップでクラスタネットワークの設定を行うと解消します.
クラスタネットワークの設定 【マスタノード】
クラスタのノード間のネットワーク機能の設定を行います.いくつかのコンポーネントがあるのですが,現時点では特にこだわりがなければ Calico を選んでおくのがおすすめです.
詳細が気になる方は,次の資料がおすすめです.
オンプレミス Kubernetes のネットワーク [キャッシュ]
Calico の導入にあたり設定ファイルのひな型をダウンロードします.
1 |
$ curl https://raw.githubusercontent.com/projectcalico/calico/v3.25.1/manifests/calico.yaml -O |
なお,ここで紹介するのは Calico の『Install Calico networking and network policy for on-premises deployments』で Manifest を使った方のインストール手順になります.デフォルトで表示されている Operator を使った手順だと,以降で説明している IP_AUTODETECTION_METHOD の指定ができず,運よくデフォルト設定で正しいネットワークIFが選択されない限り正常動作しないです(これに気付かず数日無駄にしました).
設定ファイルは基本的にはそのままで OK なのですが,ネットワーク関係の次の2項目だけ変更します.
- 使用するネットワークIFの自動検出方法の指定
- デフォルトだと,最初に見つかったネットワークIFが使われてしまうので,IP_AUTODETECTION_METHOD の指定を行います.指定できるパラメータはいくつかあるのですが,自宅用だと下記の「cidr=CIDR」方式が便利だと思います.
- アドレスプールの指定
-
クラスタで使用するネットワークを指定します.前のステップで
kubeadm init
を実行した際と同じものを指定します.
私の環境の場合,具体的な修正内容は次のようになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
vi calico.yaml @@ -4430,10 +4432,12 @@ - name: CLUSTER_TYPE value: "k8s,bgp" # Auto-detect the BGP IP address. - name: IP value: "autodetect" + - name: IP_AUTODETECTION_METHOD + value: "cidr=192.168.0.0/21" # Enable IPIP - name: CALICO_IPV4POOL_IPIP value: "Always" # Enable or Disable VXLAN on the default IP pool. - name: CALICO_IPV4POOL_VXLAN @@ -4460,12 +4464,12 @@ name: calico-config key: veth_mtu # The default IPv4 pool to create on startup if none exists. Pod IPs will be # chosen from this range. Changing this value after installation will have # no effect. This should fall within `--cluster-cidr`. - # - name: CALICO_IPV4POOL_CIDR - # value: "192.168.0.0/16" + - name: CALICO_IPV4POOL_CIDR + value: "10.0.0.0/16" # Disable file logging so `kubectl logs` works. - name: CALICO_DISABLE_FILE_LOGGING value: "true" # Set Felix endpoint to host default action to ACCEPT. - name: FELIX_DEFAULTENDPOINTTOHOSTACTION |
ファイルを編集し終わったら次のようにしてクラスタに適用します.
1 |
$ kubectl apply -f calico.yaml |
しばらくすると,先ほどまで STATUS が「Pending」だった coredns が「Running」になり,動き始めます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ kubectl get pod,svc --all-namespaces -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system pod/calico-kube-controllers-555bc4b957-pkz4t 0/1 Pending 0 47s <none> <none> <none> <none> kube-system pod/calico-node-5rjq5 1/1 Running 0 47s 192.168.0.189 kubectl <none> <none> kube-system pod/coredns-6d4b75cb6d-vg9v6 1/1 Running 0 5h16m 10.0.92.66 kubectl <none> <none> kube-system pod/coredns-6d4b75cb6d-vjm74 1/1 Running 0 5h16m 10.0.92.65 kubectl <none> <none> kube-system pod/etcd-kubectl 1/1 Running 2 5h16m 192.168.0.189 kubectl <none> <none> kube-system pod/kube-apiserver-kubectl 1/1 Running 2 5h16m 192.168.0.189 kubectl <none> <none> kube-system pod/kube-controller-manager-kubectl 1/1 Running 2 5h16m 192.168.0.189 kubectl <none> <none> kube-system pod/kube-proxy-66j75 1/1 Running 0 5h16m 192.168.0.189 kubectl <none> <none> kube-system pod/kube-scheduler-kubectl 1/1 Running 2 5h16m 192.168.0.189 kubectl <none> <none> NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR default service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5h16m <none> kube-system service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 5h16m k8s-app=kube-dns |
一方,calico-kube-controllers が追加されて,STATUS が「Pending」になっています.これは,次のステップでワーカを追加すると解消します.
クラスタへのワーカの追加【ワーカノード】
マスターノードで kubeadm token create --print-join-command
を実行すると,ワーカで実行すべきコマンドが出力されるのでメモしておきます.
1 2 |
$ kubeadm token create --print-join-command kubeadm join 192.168.0.20:6443 --token lbn5q5.746hp86d3t73crai --discovery-token-ca-cert-hash sha256:9dcdd99f0e53e5c072d7905d85e25453af60a31cfb34ecfab8531f2d8d6bc92b |
ワーカーにするサーバで,「kubeadm のインストール 【全ノード】」の内容を実行した後,上記でメモした kubeadm join
を sudo 付きで実行すると,クラスターに追加できます.
1 |
$ sudo kubeadm join 192.168.0.20:6443 --token lbn5q5.746hp86d3t73crai --discovery-token-ca-cert-hash sha256:9dcdd99f0e53e5c072d7905d85e25453af60a31cfb34ecfab8531f2d8d6bc92b |
しばらくして,下記のように全ての Pod が「Running」になっていれば正常完了です.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ kubectl get pod,svc --all-namespaces -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system pod/calico-kube-controllers-555bc4b957-mg7l4 1/1 Running 0 3m10s 10.0.194.128 tanzania <none> <none> kube-system pod/calico-node-mbp76 1/1 Running 0 106s 192.168.0.10 tanzania <none> <none> kube-system pod/calico-node-pjjcj 1/1 Running 0 3m10s 192.168.0.21 kubectl <none> <none> kube-system pod/coredns-6d4b75cb6d-flqtc 1/1 Running 0 4m35s 10.0.92.66 kubectl <none> <none> kube-system pod/coredns-6d4b75cb6d-z9n8d 1/1 Running 0 4m35s 10.0.92.65 kubectl <none> <none> kube-system pod/etcd-kubectl 1/1 Running 1 4m47s 192.168.0.21 kubectl <none> <none> kube-system pod/kube-apiserver-kubectl 1/1 Running 1 4m47s 192.168.0.21 kubectl <none> <none> kube-system pod/kube-controller-manager-kubectl 1/1 Running 5 4m47s 192.168.0.21 kubectl <none> <none> kube-system pod/kube-proxy-m98fp 1/1 Running 0 4m35s 192.168.0.21 kubectl <none> <none> kube-system pod/kube-proxy-zqgv4 1/1 Running 0 106s 192.168.0.10 tanzania <none> <none> kube-system pod/kube-scheduler-kubectl 1/1 Running 5 4m47s 192.168.0.21 kubectl <none> <none> NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR default service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4m51s <none> kube-system service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 4m47s k8s-app=kube-dns |
同じことをワーカーにしたいすべてのサーバで実行していきます.
動作確認
クラスターが正常に動作しているか確認するため,Hello World を出力する Pod を動かしてみます.
まず,次の内容の test_hello_world.yml
ファイルを用意します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
kind: Namespace apiVersion: v1 metadata: name: test labels: name: test --- kind: Pod apiVersion: v1 metadata: name: hello-world namespace: test spec: containers: - name: hello-world image: hello-world restartPolicy: Never |
次のコマンドを実行すると,Pod がノードに配置され,実行されます.
1 |
$ kubectl apply -f test_hello_world.yml |
少し時間をおいてから下記のコマンドを実行します.
1 |
$ kubectl logs -n test pods/hello-world |
次のように「Hello from Docker!」が表示されれば,クラスターは正常に動作しています.
1 2 3 4 5 |
$ kubectl logs -n test pods/hello-world Hello from Docker! This message shows that your installation appears to be working correctly. (以下,省略) |
動作確認が終わったら,次のコマンドで削除しておきます.
1 |
$ kubectl delete -f test_hello_world.yml |
まとめ
自宅サーバーに Kubernetes クラスターを構築する方法を紹介しました.
次回以降,自宅ネットワーク内にコンテナレジストリを設置したり,クラスター上のコンテナのサービスを外部に公開していく方法について紹介したいと思います.
参考文献
- 自宅Kubernetesクラスタはじめました
- 自宅でクラスターを構築するときに欲しくなる要素全体がカバーされています.Kubernetes について書いたブログは結構ありますが,単なる作業手順の羅列がほとんどの中で,作業の意味・位置付けがセットで記載されていてとても分かりやすいです.
- Kubernetesで実践するクラウドネイティブDevOps
- Kubernetes の全体像についてバランスよく解説されていてお勧めです.最初の一冊としてちょうどよい構成です.これで概要を掴んでから,個々のトピックスについて Web で検索すると良いと思います.
コメント