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 ME2 台目以降のマシンは、とてもよく似たコマンドを実行する
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注意点:
- ネットワークインターフェースが正しいことを確認すること
- トークンは安全に生成すること
- cloud-provider=external がまだ必要かどうか確認すること。Kubernetes v1.29 以降では不要になっている可能性がある。
- 「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 statusEncryption 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 からの引用):
node.kubernetes.io/instance-typeラベルにサーバータイプを追加し、外部 IPv4 および IPv6 アドレスを設定し、Hetzner Cloud 上で削除されたノードを Kubernetes からも削除する。topology.kubernetes.io/regionおよびtopology.kubernetes.io/zoneラベルをノードに設定することで、Kubernetes にサーバーの failure domain を認識させる。- Hetzner Cloud プライベートネットワークを Pod のトラフィックに利用できるようにする。
- 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 NAMEhelm install hccm hcloud/hcloud-cloud-controller-manager -n kube-system --set networking.enabled=true --set networking.clusterCIDR=10.42.0.0/16clusterCIDR の設定に注意してほしい。もし 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 nodesNAME 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+k3s1Traefik による 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 を向けられる状態になった!
現状でもかなりうまく動いているが、次にやりたいことはまだたくさんある
- Longhorn でストレージをセットアップする。今はストレージがまったくないので
- モニタリングのセットアップ
- 自動化された agent セットアップのための cloud-init
- 手軽な VPN アクセス
- さらなるセキュリティ強化
記事をランダムに読む
コメント
ログインしてコメントする