Kubernetes on Rapsberry Pi Part 3: クラスタ構築 (Kubernetes 1.23対応 + HA対応)
- Kubernetes on Rapsberry Pi Part 1: 前書き
- Kubernetes on Rapsberry Pi Part 2: ハードウェアと事前準備
- Kubernetes on Rapsberry Pi Part 3: クラスタ構築 (Kubernetes 1.23 + HA対応)
(予定)Kubernetes on Rapsberry Pi Part 4: 高可用性クラスタ→この記事で
いよいよk8sクラスタ構築
前回の記事から随分と時間がたってしまいました・・・もはやブログと呼べなくなっている気がするので、今後はちょこちょこと小さめの記事をもう少し高頻度で投稿していきたいです。
ということで前回マシンの初期設定が完了したので、ここからk8sスペシフィックな設定を行っていきます。
ちなみにインストールにはkubeadm
を利用します。
各コンポネントを一からインストールする方法もありますが、とりあえずラズパイ上にk8sをデプロイして色々動かしてみたかったので、手早くクラスタ環境を構築できるkubeadm
を採用しました。
Kubernetesの構造をちゃんと理解するために一から構築してみたい!という方はKubernetes The Hard Wayが参考になると思います。(そのうち試したい)
またkubeadm
の他にもkubesprayやRancherなどのデプロイメントツールがあるので、気になる方は調べてみてください。
kubeadmインストール準備
ここからの手順は、指定のない限りすべてのホスト(HA構成の場合はロードバランサ以外)で行ってください。
(tmuxだったり自動化ツールだったりは適宜利用してください。)
公式導入手順(日本語ページ)に詳しく書いてありますが、kubeadm
インストールのために以下の条件を満たす必要があります。
この手順に限らず英語ページと日本語ページで微妙に内容の異なっていることがあるので、なにか調べるときはどちらも読んでみるといいかもしれません。
- Debian系またはRHEL系のLinuxマシン(日本語ページに詳しく載ってます)
- 2GB以上のメモリ/台
- 2コア以上のCPU
- ノード間通信可能なネットワーク(プライベートでもパブリックでも)
- ノードごとに固有のhostname、MACアドレス、product_uuid
- 特定のポート開放
- swapはオフ
このうち1~5,7についてはラズパイ4の4GB(2GBでも)以上かつPart2の通りにOSインストールを行っていればクリアできているはずです。ネットワークに関してもPart2で同じサブネットにしていれば大丈夫です。
一応swapがオフになっているかはswapon --show
等のコマンドで確認できます。何も出てこなければ大丈夫です。
ちなみにコミュニティではswapをサポートする話も出ているようです。
UFW(Uncomplicated FireWall)のはなし
6に関して、今回はUbuntuを利用しているので、ファイアウォールの設定にはufw
を利用しましょう。
ufw
はUbuntuに標準搭載されているソフトウェアファイアウォールです。Uncomplicated Firewallという名の通り、お手軽にファイアウォールを設定できる便利ツールとなってます。RHEL系OSになじみのある方はfirewalld
(firewall-cmd
)のUbuntuバージョンだと思っていただければ大丈夫です。
ファイアウォールの設定といえばiptables
がありますが、設定の覚えられなさ複雑さが難点です。
その点ufw
やfirewall-cmd
の半分はやさしさでできており、単純な設定なら簡単に行うことができます。
ufw
やfirewall-cmd
はあくまでiptables
(もしくはnftables
)のフロントエンドであり、内部的にはそれら経由でカーネルのパケットフィルタであるnetfilter
を呼んでいます。
だいぶ主旨からそれてしまいましたが、firewalldのサイトやこのQ&Aに分かりやすい説明や図があるので興味があれば見てみてください。
設定
ということで設定を行っていきましょう。ufw
はデフォルトで無効となっていると思います。
公式にもありますが、クラスタを組むうえで今回開放すべきポートは以下になります。そのほか必要に応じて開いてください。
ノード | ポート | 目的 |
---|---|---|
Control plane (master) | 6443/TCP | Kubernetes API server |
Control plane | 2379-2380/TCP | etcd server client API |
Control plane | 10250/TCP | Kubelet API |
Control plane | 10259/TCP | kube-scheduler |
Control plane | 10257/TCP | kube-controller-manager |
Worker | 10250/TCP | Kubelet API |
Worker | 30000-32767/TCP | NodePort Services |
私の場合クラスタ外部からsshで繫いでいるので、22も開けます。現実的にリモートでの操作が楽なのでとりあえず開けておくのが無難だと思います。Ubuntu ServerであればOpenSSHはデフォルトでサービスとして登録されているので、ポートではなくサービス指定で許可をすることもできます。
|
|
|
|
ちなみにufw
はデフォルトですべてのインバウンド通信を拒否するので、sshの許可を忘れたままufw enable
してしまうと面倒なことになるかもしれません。
コンテナランタイムのはなし
ここからいよいよKubernetesっぽい話をしていきます。
Kubernetesというのはご存じの通りコンテナオーケストレーションシステムと言われる、コンテナのデプロイだったり管理だったりを行ってくれるツールです。つまりコンテナを操作します。そしてコンテナを操作するのに必要なのがコンテナランタイムです。
コンテナランタイムはハイレベルランタイムとローレベルランタイムに区別できます。ここでは詳しく書きませんが
- ハイレベルランタイム:CRI(Container Runtime Interface)というインターフェース通してDockerやKubernetesから受け取った指示を、ローレベルランタイムに伝える中継役的なやつ
- containerd
- cri-o
- rkt (プロジェクト終了)
- ローレベルランタイム:OCIという標準に則った、ハイレベルランタイムからの指示に従いoverlayfs、namespaces、cgroupsといったカーネル機能を利用することでコンテナの作成、操作を行ってくれるやつ
といった感じです。他にもいくつかあると思います。
今回はハイレベルランタイムにcri-o、ローレベルランタイムにruncを利用します。
CRI-O導入
現在(1.23)、Kubernetesに利用できるハイレベルランタイムはdockerd(Docker Engine)、containerd、cri-oの三種類になります。 dockerdなんて上の表にないじゃんと思われた方はこの記事内のKubernetesがDockerサポートを終了した件についてを読んでいただくと幸せになれるかもしれません。
dockerdは後述の理由でそもそも採用しないと決めていました。なのでcontainerdとcri-oのどちらを使うかで少し悩みましたが、結局「軽量」という言葉に惹かれcri-oを採用しました。(Ubuntuを使いながらRedHat主導のプロジェクトを採用とはいかに)
- 導入手順
-
必要なコマンドのインストール
これからの作業に必要なコマンドを一気にインストールしておきましょう。
デフォルトでほとんど入っていると思いますが念のため・・1
sudo apt install -y ca-certificates curl gnupg lsb-release apt-transport-https ca-certificates
-
カーネルモジュールのロード
overlay
とbr_netfilter
と呼ばれるカーネルモジュールのロード及び起動時の自動ロード設定を行います。overlay
はOverlayFSと呼ばれるコンテナ周りに不可欠なファイルシステムモジュールで、br_netfilter
は異なるホストのPod間通信に必要なモジュールです。1 2 3 4 5 6 7
cat <<EOF | sudo tee /etc/modules-load.d/crio.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter
-
カーネルパラメータの設定
次に必要なカーネルパラメータを設定、永続化します。
この設定により、Kubernetesからiptablesでのブリッジのトラフィック制御が可能になります。1 2 3 4 5 6 7
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF sudo sysctl --system
-
環境変数
$OS
と$VERSION
の設定
cri-oのインストールのため、2つの環境変数を設定する必要があります。 設定方法は特に指定しませんが、例えば1
echo -e "export OS=xUbuntu_20.04\nexport VERSION=1.23" | sudo tee /etc/profile.d/crio.sh
といった感じで環境変数を設定するスクリプトを作成し、
1
. /etc/profile
で反映させます。
-
コンテナランタイムのインストール
cri-oおよびcri-o推奨のruncをインストールします。1 2 3 4 5 6 7 8 9
sudo echo "deb [signed-by=/usr/share/keyrings/libcontainers-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list sudo echo "deb [signed-by=/usr/share/keyrings/libcontainers-crio-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list sudo mkdir -p /usr/share/keyrings sudo curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | gpg --dearmor -o /usr/share/keyrings/libcontainers-archive-keyring.gpg sudo curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/Release.key | gpg --dearmor -o /usr/share/keyrings/libcontainers-crio-archive-keyring.gpg sudo apt-get update sudo apt-get install cri-o cri-o-runc
ちなみにもし推奨されたrunc(
cri-o-runc
)ではなくUbuntu標準のruncを利用したい場合、1 2 3 4 5 6
cat <<EOF | sudo tee /etc/crio/crio.conf.d/oci.conf [crio.runtime.runtimes.runc] runtime_path = "" runtime_type = "oci" runtime_root = "/run/runc" EOF
といった感じでデフォルトのruncを利用するためのコンフィグを作成してください。(この場合
cri-o-runc
のインストールは不要です。) -
cri-oの自動起動設定
daemon-reload
は必要ないと思いますが一応1 2 3
sudo systemctl daemon-reload sudo systemctl enable crio --now sudo systemctl status crio
KubernetesがDockerサポートを終了した件について
ここ最近、KubernetesがDockerのサポートを打ち切ったという話を耳にしたことがある思います。確かに間違ってはいないのですが、Dockerで作ったコンテナがk8sで管理できなくなるなんて話ではありません。これには上のほうで説明したコンテナランタイムが関わってきます。
もともとコンテナの作成といえばDocker Engine (dockerd)でした。そしてこのDocker Engineに内包されるコンテナランタイムこそ現在のcontainerdなのです。kubernetesもDocker Engine内のcontainerdを利用しポッドの作成を行っていました。
しかしコンテナランタイムであるcontainerdはあくまでDocker Engineのコンポネントの一つにすぎません。(とはいえ核部分なのは間違いありません)それに加えDocker Engine自体はCRIに対応していないという問題がありました。
なのでKubernetesはdockershimというモジュールを実装し、CRIをDocker APIに変換することで、Docker内部のコンテナランタイムを利用していました。コンテナランタイムのはなしにあるリストにdockerdを含まなかったのは、厳密にはdockerd自体がコンテナランタイムではないからです。
2017年頃、containerdが単体でCloud Native Computing Foundation(CNCF)に寄贈されました。そしてこのcontainerdはCRIに対応しています。つまり、KubernetesはDockershimを経由せずとも、コンテナランタイムであるcontainerdを利用することが可能になったわけです。
dockerdを利用する場合、Kubernetesはdockershimを利用しDocker内部のcontainerdを呼ぶので、CRIに対応したランタイムを利用するのに比べオーバーヘッドが発生します。それに加え、dockershimをサポートし続けるのはコミュニティにとって大変です。(公式でもa heavy burdenと表現されるほど)
そんなこんなでKubernetesコミュニティは今後dockershimをサポートしないことを決めました。具体的には今回利用している1.23の次バージョンである1.24から、dockerdが利用できなくなります。
厳密にはMirantisというところがdockershimのサポートを引き継ぐ事になったので、彼らの提供するアダプタを利用することで引き続きdockerdの利用が可能です。ただKubernetes自体がdockerdをサポートすることはなくなります。
この辺りの話について更に詳しく知りたい方は、以下の記事を参考にしてください。
DockerからContainerdやCRI-Oへの移行
新規クラスタ作成者にはあまり関係ありませんが、CRIの移行を考えている方に役立ちそうなリンクを貼っておきます。
kubeadm,kubelet,kubectlのインストール
クラスタ作成にあまり関係のない話が続いてしまいましたが、いよいよkubeadm
およびKubernetesコンポネントをインストールする準備が整いました・・・!
早速導入していきましょう。
- 手順
-
Google Cloud public signing keyのダウンロード
1
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
-
Kubernetesリポジトリの追加
1
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
-
kubeadm
,kubelet
,kubectl
のインストール
kubeadm
自体がkubelet
やkubectl
のインストールおよび管理を行ってくれるわけではないので、個別にインストールする必要があります。この際、各コンポネントのバージョン(今回の場合1.23
)を一致させることに注意してください。1 2
sudo apt update sudo apt install -y kubelet kubeadm kubectl
-
バージョンの固定
最後に各コンポネントのバージョンを固定しましょう。これで勝手にアップデートされたりバージョンがちぐはぐになってしまうことを防げます。ついでにcrio-oも固定しておきましょう。1
sudo apt-mark hold kubelet kubeadm kubectl cri-o cri-o-runc
Q.
kubelet
ってマスターノードに必要なの?
A.kubeadm
を利用する場合は必須です。
kubeadm
はコンテナ(ポッド)を使いkubelet経由でetcdやAPI serverをデプロイするので、マスターにもkubelet
が必要です。他のクラスタ作成ツールでは必要のない場合もあります。
(参考)Why kubelet is running on kubernetes master node?
Q.
kubectl
ってワーカーノードに必要なの?
A. 必須ではありませんが、クラスタのどのノードからも操作できるとなにかと便利です。
(参考)is kubectl required on the worker nodes ?
(Option)HA用の設定
ここでの作業は2台のロードバランサ用マシン(前回の記事を参照)でのみ行います。 なのでシングルマスターで組む方は読み飛ばしてください。
当初はHAクラスタについて次回の記事で書こうと思っていたのですが、設定する場合kubeadm init
によるデプロイより前に行わなければいけないのと、そもそもデプロイ後のHA化は推奨されていないようなので、まとめて書くことにしました。
Part 1でも軽く触れましたが、Control Planeを冗長化は大きく分けて2パターンあり、違いはetcdを個別ノードで冗長化させるかどうかです。また、どちらの方法でもAPI serverへのロードバランサを導入する必要があります。
-
内部etcd構成
-
外部etcd構成
今回は前者のetcdをControl Planeの一部として冗長化するアーキテクチャを採用しています。
ということで、今回のHA化ではロードバランサにKeepalivedとHAProxyを利用します。パブリッククラウド上で構築される方は、サービスとして提供されているロードバランサ(AWSのELBだったり)を使ってもいいと思います。
それでは早速インストールしていきましょう。
|
|
これでインストールは完了したので、個々の設定を行っていきます。
KeepalivedとHAProxyの設定はどちらからでもいいのですが、HAProxyへの設定を反映した時点から1つめのコントロールプレーンをデプロイするまでの間エラー出るので、反映はデプロイ直前にするのが無難だと思います。
Keepalivedの設定
ルータやロードバランサの冗長化を行うためのソフトウェアです。
今回のような構成の場合、上の図のようにロードバランサが一つではそこがSPOFとなってしまうため、複数のHAProxyを用意しKeepalivedと組み合わせることで、エンドポイントの冗長化を図ります。
VRRP(Virtual Router Redundancy Protocol)というプロトコルにより仮想IP(VIP)を用いるため、クライアントやワーカーノードはエンドポイントとなるHAProxyが何らかの原因で落ち、別ホストに切り替わった場合でも、物理IPの変化を意識する必要がなくなります。
インストールが完了すると/etc/keepalived/
というディレクトリが生成されていると思うので(なければ作りましょう)、そこにkeepalived.conf
とcheck_apiserver.sh
というファイルを作成し、それぞれ以下のように編集しましょう。
また、sudo chmod +x check_apiserver.sh
等で実行権限の付与も忘れずに行ってください。
/etc/keepalived/keepalived.conf
|
|
-
ステイト
一台目にMASTER
、二台目(以降)にBACKUP
と設定しましょう。仮想IPは最初MASTER
に振り分けられます。 -
インターフェース
利用するNICを指定しましょう。ラズパイなら変更していない限りeth0
で大丈夫です。 -
ルーターID
複数のkeepalivedを同じNICで実行させている時の識別に利用されます。なんでもかまいませんが各ホストで同じ数値にしてください。(51がよく使われるよう?) -
優先度
MASTER
を100、BACKUP
をそれ以下の数値で設定しましょう。他の数でもかまいませんがMASTER
が最大となるよう気を付けてください。 -
認証用パスワード
フェイルオーバーの際の同期に利用されるパスワードです。なんでもかまいませんが各ホストで同じ数値にしてください。 -
仮想IP
使用したい仮想IPを指定してください。今回の場合ホストやk8sクラスタと同じサブネットで指定します。
/etc/keepalived/check_apiserver.sh
|
|
-
仮想IP
上記で設定した仮想IPを指定してください。 -
エンドポイントのポート
APIサーバへのエンドポイントとして利用するポートです。HAProxyはここへのアクセスを各コントロールプレーンに振り分けます。
k8s APIサーバのデフォルトである6443
を使ってもいいですが、私はロードバランサを使っていることを分かりやすくするため別ポートの8443
を採用しました。
HAProxyの設定
高性能なソフトウェアリバースプロキシサーバです。 複数のマスター(Control Plane)へのロードバランシングを行ってくれます。
インストールが完了すると/etc/haproxy/haproxy.cfg
というファイルが生成されていると思うので(なければ作りましょう)、ファイルを以下のように編集します。
|
|
-
エンドポイントのポート
上記で設定したポート(私の場合8443
)を指定してください。 -
Control Plane XのIP
各コントロールプレーンのIPです。5台,7台とコントロールプレーンを追加していくたびに、ここへ追記・リロードをしてください。
ファイアウォールの設定
ufwで必要なポートの開放を行いましょう。
|
|
- エンドポイントのポート
上記で設定したポート(私の場合8443
)を指定してください。
KeepalivedとHAProxyの実行、設定の反映
以下のコマンドで設定の反映、実行を行います。 私の環境だとすでに実行されていたためにリロードが必要でした。
|
|
確認
この時点でVRRPがノード間で通っているかを確認しましょう。
簡単な確認方法として、ip a
コマンドでMASTER
のNICのみに仮想IPが振り分けられているかをチェックします。
(私の場合ファイアウォールの設定を失念しており、仮想IPが両方のホストにふりわけられていました。)
|
|
仮想IPが振り分けられているホストを再起動することで、一時的にバックアップのほうに振り分けられることも確認することができます。
Control Planeのデプロイ
必要コンポネントが全て揃ったので、デプロイしていきましょう!
ここでの手順はControl Plane用のマシン1台でのみ行います。
以下のコマンドで最初のControl Planeをデプロイします。
|
|
kubeadm init
はKubernetesコンポネントをデプロイするためのコマンドです。
-
--pod-network-cidr
Container Network Interface(CNI)
が利用する仮想ネットワークです。Pod間通信で使います。
10.244.0.0/16
は導入予定のFlannelというネットワークプラグインがデフォルトで指定しているネットワークになります。
他のネットワークを使いたい場合、flannel導入時に利用するマニフェスト(例:Flannel)を書き換える必要があるので注意してください。
flannel以外のプラグインを利用したい場合、プラグインデフォルトのネットワークを指定、もしくはマニフェストの書き換えを適宜行ってください。 -
--control-plane-endpoint
IPとポートを指定します。シングルマスターの場合は自らのIPと任意のポート(デフォルトで6443)、HAの場合上記で設定した仮想IPとエンドポイントのポートを指定します。 -
--upload-certs
(オプション)
コントロールプレーンの追加を行う場合、デプロイ時に生成される証明書を追加するコントロールプレーンに共有する必要があります。このパラメータを設定することにより、2台目以降のコントロールプレーンへの手動での証明書受け渡しが不要になります。シングルマスターの場合は不要です。
cgroup問題
ラズパイでデプロイする場合、以下のようにpreflight段階でエラーが発生する場合があります。
(発生しない場合もあるようですが、私の環境では発生しました。)
1 2 3 4 5
... error execution phase preflight: [preflight] Some fatal errors occurred: [ERROR SystemVerification]: missing required cgroups: memory [preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...` To see the stack trace of this error execute with --v=5 or higher
この場合、
/boot/firmware/cmdline.txt
に以下の設定を追記し、再起動を行うことで解決します。
1
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
参考リンク
デプロイが成功すると、以下のように表示されます。
|
|
ここには
- kubectlを一般ユーザが利用する方法
- ネットワークプラグインの設定方法
- コントロールプレーンの追加方法
- ワーカーの追加方法
が記されています。この後の作業のために、以下のコマンドでkubectlを一般ユーザから使えるようにしておきましょう。
|
|
kubectl get pods --all-namespaces
等でkubectlを使えることが確認出来たら、ネットワークプラグインを導入していきましょう。
Flannelの導入
CNI準拠のネットワークプラグインは多々ありますが、その中でおそらく最もシンプルかつ導入の容易なものがFlannelになります。デプロイ時に--pod-network-cidr=10.244.0.0/16
と指定しておけば、以下のコマンドから用意されているマニフェストをそのまま導入することで、ポッド間通信を実現できます。
|
|
flannel用のポッドが起動しているか確認します。
|
|
こんな感じでkube-flannel-xx-xxxxx
というポッドのステータスがRunningとなっていれば成功です。
今後はノードを追加していくたびに自動でポッドが生成され、ネットワークの設定が行われるため安心ください。
それではいよいよノードを追加していきましょう。
ノードの追加
ここからノードの追加を行いますが、基本的にはkubeadm init
時に表示されたコマンドをたたくだけです。まずはコントロールプレーンから追加していきましょう。
kubeadm init
成功時に各環境で表示されたコマンドを、追加したいホスト上でroot権限にて実行してください。
|
|
kubeadm init
時にも表示されますが、--upload-certs
オプションによる証明書の自動共有機能は2時間で消失するのでご注意ください。(厳密には、一時的にSecretにアップロードされた証明書が2時間で無効になります。)
後日ノードの追加などで再アップロードが必要な場合、以下のコマンドを1台目のコントロールプレーンで実行してください。
|
|
それでは次にワーカーノードを追加していきます。コントロールプレーンと同じく、kubeadm init
成功時に各環境で表示されたコマンドを、追加したいホスト上でroot権限にて実行してください。
|
|
最後にkubectl get nodes
やkubectl get pods --all-namespaces
でノードやポッドが正常に起動していることを確認しましょう。
|
|
これにてKubernetesクラスタの完成です!お疲れさまでした!!!
おまけ:PCからクラスタへアクセス
普段利用しているPCからssh接続せず、直接クラスタ管理をしたくなると思います。
そんなときはそのPCにkubectlをインストールしましょう。Debian系のマシンであれば上記の手順でインストールできます。 その他OSの手順は公式ページを参考にしてください。
kubectlのインストールが完了したら、kubeadm init
を行ったノードで以下のコマンドを叩き、出力を手元のマシンの~/.kube/config
として保存しましょう。
|
|
|
|
kubectl get nodes
等のコマンドでノードが確認できたら成功です。
ユーザや認証周りについて詳しくは触れませんが、とりあえず管理者として作成したクラスタにアクセスしたいのであれば、この方法がおそらく最も楽だと思います。(そもそもAPIサーバにアクセスできるNW環境かどうかは確認してください。)
そのあたりについて詳しく知りたい方は、さくらインターネットさんの記事がとても参考になると思います。
おわりに
今回はHA化も含めてんこ盛りな内容となりました。ものすごく疲れました。
前回からだいぶ経ってしまいましたが、最新バージョン対応だったり、一部ですが日本語ではあまりまとまってないようなことも書けた気がするので、参考にしていただけると嬉しいです。
ネタがたまっていく一方なので、次回からはもう少し細かいネタで書いていけたらと思います。
k8s上でのアプリケーション関係だったりRustやGoの勉強進捗だったりを書いていくかもしれません。
画像引用元・参考URL
その他参考にしたページのリンクです。