Setting up k3s on Hetzner Cloud

Ellie Huxtable

Hetzner Cloud で k3s をセットアップする

原文は Ellie Huxtable により に公開されました。 このブログを購読する

最近、Atuin 用に HA 構成の k3s クラスタを構築した!

HA の etcd を使っているので、「server」ノードは奇数かつ 1 台より多く必要になる。つまり最小で 3 台だ。

サーバーはすべてプライベートネットワーク内の独自のサブネットに配置しており、ARM インスタンスを使っている。ファイアウォールはほぼすべてのインバウンドと、ほとんどのアウトバウンドを拒否するように設定してある。

正直、これほど簡単だったことに驚いている。気づいていないだけで何かミスをしている可能性もあるが、数年前に kubeadm を使ったときの経験はまったく楽なものではなかった。k3s 万歳!

参考資料

いろいろと読み漁った。

このガイドが良い出発点になった: https://community.hetzner.com/tutorials/k3s-glusterfs-loadbalancer

ただ、k3s のドキュメントが本当に素晴らしく、結局ほとんどそれを読んだ: https://docs.k3s.io/

(おそらく非常に優れた)自動化された Hetzner 向け k3s セットアップツールは使わなかった。何が起きているのかをきちんと理解しておきたかったし、以前ベアメタルで Kubernetes を何度か運用したことがあるからだ。もっとも kubeadm での話で、k3s ではなかったが。

サーバー

まずは最初の server だ!k3s ではコントロールプレーンを実行するノードを「server」、それ以外のノードを「agent」と呼ぶことに注意してほしい。デフォルトでは master ノードでも通常のワークロードをスケジュールできるようになっており、自分のユースケースではおそらくそれで問題ない。

Hetzner 専用のものを別途インストールするので cloud controller は無効化し、Longhorn を使う予定なので local storage も無効化した。トークンはしっかりしたものを選ぶこと!

それ以外では、Hetzner でプライベートネットワークを有効にしており、クラスタにもそれを使わせたいので、flannel がプライベートネットワークのインターフェースを向くように指定している。

curl -sfL https://get.k3s.io | sh -s - server \
	--cluster-init \
    --disable-cloud-controller \
    --disable local-storage \
    --node-name="$(hostname -f)" \
    --flannel-iface=enp7s0 \
    --kubelet-arg="cloud-provider=external" \
    --secrets-encryption \
    --disable=traefik \
    --token=CHANGE ME

2 台目以降のマシンは、とてもよく似たコマンドを実行する

curl -sfL https://get.k3s.io | sh -s - server \
	--server SERVER ADDRESS \
    --disable-cloud-controller \
    --disable local-storage \
    --node-name="$(hostname -f)" \
    --flannel-iface=enp7s0 \
    --kubelet-arg="cloud-provider=external" \
    --secrets-encryption \
    --disable=traefik \
    --token=CHANGE ME

注意点:

  1. ネットワークインターフェースが正しいことを確認すること
  2. トークンは安全に生成すること
  3. cloud-provider=external がまだ必要かどうか確認すること。Kubernetes v1.29 以降では不要になっている可能性がある。
  4. 「cluster init」が必要なのは最初の server だけだ。その後はすでにクラスタが存在するので、init は不要!

最初に、Hetzner Cloud Controller のセットアップまで一通り進めてから、ドキュメントで各ノードに --kubelet-arg="cloud-provider=external" フラグを渡す必要があると知った。

ほとんどのフラグは、インストーラーを再実行すれば設定が調整されてノードが再起動される。ただ、このフラグに限っては、付け忘れるとクラスタを作り直す必要がある。HCCM は最初から正しくセットアップされたノードにしかラベルを付けず、ラベルのないノードは LB と正しく連携しない。

これについて少し背景を説明する。Kubernetes には CCM(cloud controller manager)が多数存在し、基本的に k8s とクラウドプロバイダーをうまく統合する役割を担っている。外部 CCM をインストールするには、現状では前述のフラグを設定する必要がある。ただし、このフラグはすでに長い間非推奨になっている。本来は v1.24 で削除される予定だったが、まだ削除されていない。

自分の理解では、現在 kubelet にはいくつかの CCM がバンドルされており、このフラグによってバンドルされていない CCM を使えるようになる。将来的には CCM のバンドルが廃止される予定で、そうなればこの引数は不要になる(だから非推奨なのだ)

詳しく知りたい場合はこちらの Issue をどうぞ: https://github.com/kubernetes/kubernetes/issues/110018

リンク先の PR によると、これは v1.29 に含まれるかもしれない。なので v1.29 以降を使っているなら、cloud provider フラグは不要かもしれない!

この時点で、次のコマンドを

kubectl get nodes

セットアップしたどのマシンからでも実行でき、次のような結果が得られる:

NAME       STATUS   ROLES                       AGE    VERSION
server-1   Ready    control-plane,etcd,master   4m4s   v1.27.6+k3s1
server-2   Ready    control-plane,etcd,master   47s    v1.27.6+k3s1
server-3   Ready    control-plane,etcd,master   19s    v1.27.6+k3s1

また、secret encryption の状態は次のコマンドで確認できる

k3s secrets-encrypt status
Encryption Status: Enabled
Current Rotation Stage: start
Server Encryption Hashes: All hashes match

Active  Key Type  Name
------  --------  ----
 *      AES-CBC   aescbckey

最高だ!

アクセス

さらにセットアップを進める前に、自分のラップトップから kubectl でアクセスできるようにしたかった。ノード上で直接コマンドを実行するのは、どうも気持ち悪い。

セットアップが完全に終わったらアクセス用に Tailscale(あるいは innernet)を導入する予定だが、とりあえずは SSH のポートフォワーディングで済ませる。kubeconfig は次の方法で取得できる

cat /etc/rancher/k3s/k3s.yaml

いずれかのノードで実行する。

あとは

ssh -L 6443:localhost:6443 root@a server ip

とすれば、ローカル端末から kubectl を使えるようになる。とはいえ、もう少し堅牢な方法はきちんと設定しておこう 😊

Hetzner Cloud Controller Manager

早口で 3 回言ってみてほしい。ともあれ、hccm はクラスタと Hetzner Cloud API を統合するもので、これにより次のことが可能になる(README からの引用):

  1. node.kubernetes.io/instance-type ラベルにサーバータイプを追加し、外部 IPv4 および IPv6 アドレスを設定し、Hetzner Cloud 上で削除されたノードを Kubernetes からも削除する。
  2. topology.kubernetes.io/region および topology.kubernetes.io/zone ラベルをノードに設定することで、Kubernetes にサーバーの failure domain を認識させる。
  3. Hetzner Cloud プライベートネットワークを Pod のトラフィックに利用できるようにする。
  4. Kubernetes の Service で Hetzner Cloud Load Balancer を利用できるようにする。

Hetzner のブログ記事ではマニフェストを直接 apply することが推奨されていたが、しかし hccm のドキュメントでは Helm チャートが推奨されている。自分はプライベートネットワーキングを有効にしてセットアップした(そもそもこれをパブリックネットワークで動かす理由がわからない、たぶんやめておいた方がいい)。

helm repo add hcloud https://charts.hetzner.cloud
helm repo update hcloud

次に、Hetzner Cloud API トークンとネットワーク名を含む k8s Secret を作成する必要がある(secrets を保存時に暗号化しておきたかった理由のひとつがこれだ)

kubectl -n kube-system create secret generic hcloud --from-literal=token=SOME SECRET --from-literal=network=NETWORK NAME
helm install hccm hcloud/hcloud-cloud-controller-manager -n kube-system --set networking.enabled=true --set networking.clusterCIDR=10.42.0.0/16

clusterCIDR の設定に注意してほしい。もし k3s のデフォルト を変更していなければ、10.42.0.0/16 で問題ない。

kubectl logs -n kube-system deployment/hcloud-cloud-controller-manager

を実行すると何らかの出力が表示されるはずで、

kubectl describe node agent-1

を実行すると、追加の情報やアノテーションが表示されるはずだ:

node.kubernetes.io/instance-type=cax21
topology.kubernetes.io/region=fsn1
topology.kubernetes.io/zone=fsn1-dc14

エージェント

k3s のデフォルトの動作では、すべてのデプロイメントが実際にすべてのノードにスケジュールされるので、この動作で問題なければ agent はそれほど多く必要ないかもしれない

セットアップは server とかなり似ている!設定が少ないだけだ。前に使ったトークンも必要になる

curl -sfL https://get.k3s.io | sh -s - agent \
	--server SERVER ADDRESS \
	--node-name="$(hostname -f)" \
	--flannel-iface=enp7s0 \
	--kubelet-arg="cloud-provider=external" \
	--token=CHANGE ME

好きなだけ agent をセットアップできる

kubectl get nodes
NAME       STATUS   ROLES                       AGE    VERSION
agent-1    Ready    <none>                      9s     v1.27.6+k3s1
server-1   Ready    control-plane,etcd,master   100m   v1.27.6+k3s1
server-2   Ready    control-plane,etcd,master   96m    v1.27.6+k3s1
server-3   Ready    control-plane,etcd,master   96m    v1.27.6+k3s1

Traefik による Ingress

先ほどノードのセットアップ時に Traefik を無効化したのに、今度はセットアップするのか?

基本的に、k3s デフォルトの Traefik は独自のロードバランサーを使う。これ自体はまったく問題ないのだが、自分の LB は hccm に管理させたかった。そうすれば、すべてのターゲットがクラスタによって自動的に管理される、ちゃんとしたクラウド LB が手に入る。

動くまでに少し手こずった(後述する設定ミスをいくつかやらかしていた)

helm repo add traefik https://traefik.github.io/charts
helm repo update

次に traefik.values.yaml を作成した。ここに全体を貼り付けるつもりはない —— 基本的にはデフォルトの values を取得してファイルに保存し、必要に応じて編集している。もちろん、自分の好みに合わせて設定するのが一番だ!

ただ、Service に関して次のアノテーションは非常に重要だと言っておきたい:

service:
  enabled: true
  ## -- Single service is using `MixedProtocolLBService` feature gate.
  ## -- When set to false, it will create two Service, one for TCP and one for UDP.
  single: true
  type: LoadBalancer
  # -- Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config)
  annotations:
    load-balancer.hetzner.cloud/location: fsn1
    load-balancer.hetzner.cloud/name: lb
    load-balancer.hetzner.cloud/use-private-ip: "true"

まず LB のロケーションを設定し、名前を付ける。そしてプライベート IP を使うように指示する。最初はこのオプションを付けていなかったので、ロードバランサーが動かなかった!すべてのターゲットが unhealthy になっていた。

デフォルトでは、hccm は LB にパブリック IP アドレスしか追加していなかった。ファイアウォールがそれをブロックしていたため(ノードへのパブリックな受信は許可していない)、何も正しくルーティングされなかった。この変更で、すべてうまくいった 😇

自分は Cloudflare を使って SSL を終端している。いずれ cert-manager も導入するかもしれないが、クラスタはできるだけステートレスに保ちたい。そもそもエッジで SSL を終端するのは手軽で良い。

次のステップ

これで、ステートレスなサービスをデプロイして、Cloudflare で DNS を向けられる状態になった!

現状でもかなりうまく動いているが、次にやりたいことはまだたくさんある

  1. Longhorn でストレージをセットアップする。今はストレージがまったくないので
  2. モニタリングのセットアップ
  3. 自動化された agent セットアップのための cloud-init
  4. 手軽な VPN アクセス
  5. さらなるセキュリティ強化

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント