LXDでk3sのマルチノード環境を作る

投稿者: | 3月 3, 2025

大学のICTフェロー業(20年以上続けている無料ボランティアのお兄さん)が完全にお留守で、大学にIPアドレスを返却したが、DNSの設定が残っていると指摘されて、DNSの設定変更をしようとしたら、接続できない。調べてみるとVPNの接続方法がガラリと変わっていた。そこからかぁ。さらにゼミのサーバがhttpsになっていなかったが発覚。というか、誰もhttps使っていない。それはそうだ、教えていない。近々、教えなきゃならない。という背景があって、

Lets Encryptの設定って、IPv6でも使えたんだっけ?

ブログにする必要もなく使えた。ちなみに、DNSの設定を変えるのが面倒なので、ワイルドカードDNSでIPアドレスベースのホスト名を使ったができた。

こういう、ことは、学生を送り出す方としては、最初に習ってしまえば後が楽なので、就職前に教えてしまえと。そういう意味だと、クラウド、コンテナはマストアイテムに。

ちなみに、学生は、主に文系でほぼほぼ女子。しかし、うちら就職氷河期世代と違って全然受け入れてくれる。就職先も結構すごい。ただ、端末持ってきてというとスマホをもってきて、そこからSSHしようとしちゃう時があるけど。

 

閑話休題

 

できるはずだろうと思ったができた。ただ、Longhornは使わず、NFS CSIドライバを使うことにした。多分、Longhornを使うには、各コンテナで独立したストレージデバイスが必要になるので面倒かと。Proxmoxでやろうとしたが、Proxmoxのコンテナはcloud-initが使えないので手間がかかる。LXDは、Cloud-initが使えるので便利。(となるとQNAPのコンテナでも作れると思う。)

環境

Ubuntu Server 24.04.2 amd64 1台 (多分arm64でも動く)

LXDのコンテナでk3sのノードを作る。

プライベートレジストリ:1コンテナ

K3sノード:3コンテナ (Master:1 / Worker:2)

LXDサーバは、NFSサーバも兼務させる。

 

スペックは上記が動作するCPU、メモリ、HDD領域がLXD側に必要なのは言うまでもない。コンテナはうちでの小槌ではないので。

 

事前準備

物理NICをBridgeにしたものも作成する。理由は、パブリックIPv6を使いたいから。

ちなみに、この環境は、パブリックIPv6が割り振られている環境。

ens33は、各自の環境に書き換える。

また、同時にNFSサーバも設定している。

cat << EOF > /etc/netplan/90-bridge.yaml
network:
version: 2
ethernets:
  ens33:
    dhcp4: no
bridges:
  br0:
    interfaces:
      - ens33
    dhcp-identifier: "Mac"
    dhcp4: yes
    parameters:
      forward-delay: 0
      stp: no
EOF
chmod 0600 /etc/netplan/90-bridge.yaml
apt -y install nfs-kernel-server
NFS_SHARE="/disk/nfs_csi/"
mkdir -p ${NFS_SHARE}
chmod 1777 /disk/nfs_csi
ls -ld /disk/nfs_csi
echo "/disk/nfs_csi 192.168.0.0/16(rw,async,no_root_squash,no_subtree_check)" >> /etc/exports
exportfs -ra
reboot

 

Lxdのインストール

lxdを初期化して使う必要があるが、初めて、lxd/lxcコマンドを使うとsnap経由でLXD一式のインストールが始まる。LXDがサーバプロセスで、LXCはクライアントとして使う。

lxd init

Installing LXD snap, please be patient.

LXDがサーバプロセスで、LXCはクライアントとして使う。

以下が設定。ストレージバックエンドをdirにした。わざわざブロックデバイスにする必要はないと思った次第。ただ、コンテナのファイルとベースOSのファイルシステムが同一なので、何かあった場合にはリスクがあるかもしれない。それを防ぐには、zfsとかbtrfsの方がいいかもしれない。

Would you like to use LXD clustering? (yes/no) [default=no]:

Do you want to configure a new storage pool? (yes/no) [default=yes]:

Name of the new storage pool [default=default]:

Name of the storage backend to use (powerflex, zfs, btrfs, ceph, dir, lvm) [default=zfs]: dir

Would you like to connect to a MAAS server? (yes/no) [default=no]:

Would you like to create a new local network bridge? (yes/no) [default=yes]:

What should the new bridge be called? [default=lxdbr0]:

What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:

What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:

Would you like the LXD server to be available over the network? (yes/no) [default=no]:

Would you like stale cached images to be updated automatically? (yes/no) [default=yes]:

Would you like a YAML “lxd init” preseed to be printed? (yes/no) [default=no]:

 

ブリッジ接続のプロファイルの作成

lxc profile create bridge
lxc network list
lxc network attach-profile br0 bridge eth0
lxc profile device add bridge root disk path=/ pool=default
lxc profile list
lxc profile show bridge

 

イメージのプル

Ubuntuのイメージをそのまま使う。OSのセットアップは不要。

lxc image copy ubuntu:24.04 local: --alias noble

 

k3s用のProfileの作成

ホストのリソース状況によるが、CPUとメモリのスペックは、もう少し大きくしてもいいかもしれない。

lxc profile create k3s
cat << EOF > k3s.cnf
config:
security.nesting: "true"
security.privileged: "true"
limits.cpu: "2"
limits.memory: 4GB
limits.memory.swap: "false"
linux.kernel_modules: overlay,nf_nat,ip_tables,ip6_tables,netlink_diag,br_netfilter,xt_conntrack,nf_conntrack,ip_vs,vxlan
raw.lxc: |
  lxc.apparmor.profile = unconfined
  lxc.cgroup.devices.allow = a
  lxc.mount.auto=proc:rw sys:rw
  lxc.cap.drop =
description: Profile settings for a bridged k3s container
devices:
eth0:
  name: eth0
  nictype: bridged
  parent: br0
  type: nic
kmsg:
  path: /dev/kmsg
  source: /dev/kmsg
  type: unix-char
root:
  path: /
  pool: default
  type: disk
name: k3s
used_by:
EOF
lxc profile edit k3s < k3s.cnf

 

プライベートレジストリコンテナの作成

USER_PASSWD="hogehoge"

CONTAINER=k3s-registry
lxc init local:noble ${CONTAINER} --profile=bridge
lxc list
lxc config set ${CONTAINER} user.user-data - <<EOF
#cloud-config
hostname: ${CONTAINER}
timezone: Etc/UTC
locale: en_US.utf8
package_update: true
package_upgrade: true
packages:
- openssh-server
- docker-registry
users:
- name: k3suser
  shell: /bin/bash
  ssh-authorized-keys:
    - $(cat ~/.ssh/id_rsa.pub)
  sudo: ALL=(ALL) NOPASSWD:ALL
  passwd: "${USER_PASSWD}"
  lock_passwd: false
chpasswd:
expire: false
list:
  - k3suser:${USER_PASSWD}
ssh_pwauth: true
write_files:
- content: |
    alias diff='diff --color=auto'
    alias ip='ip -color=auto'
  path: /etc/profile.d/Z99-addedcolour.sh
runcmd:
- sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config
- systemctl restart ssh
- sed -i -e "s/ htpasswd/# htpasswd/g" /etc/docker/registry/config.yml
- sed -i -e "s/ realm/# realm/g" /etc/docker/registry/config.yml
- sed -i -e "s/ path/# path/g" /etc/docker/registry/config.yml
- systemctl restart docker-registry
- systemctl enable docker-registry
EOF
lxc config show ${CONTAINER}
lxc start ${CONTAINER}
lxc list

 

各ノード(3ノード分)の作成

ループで回して、完全同一構成をミスなく作る。CONTAINERLISTに、コンテナノード名を必要な数だけ列記すればもっと多くできる。unzipは本来は不要。

USER_PASSWD="hogehoge"
export CONTAINERLIST="k3s-test1 k3s-test2 k3s-test3"

for CONTAINER in $CONTAINERLIST; do
  echo "Creating container: $CONTAINER"
  lxc init local:noble $CONTAINER --profile=k3s
  lxc config set $CONTAINER user.user-data - <<EOF
#cloud-config
hostname: $CONTAINER
timezone: Etc/UTC
locale: en_US.utf8
package_update: true
package_upgrade: true
packages:
- openssh-server
- bash-completion
- nfs-common
- jq
- apt-transport-https
- apparmor-utils
- unzip
users:
- name: k3suser
  shell: /bin/bash
  ssh-authorized-keys:
    - $(cat ~/.ssh/id_rsa.pub)
  sudo: ALL=(ALL) NOPASSWD:ALL
  passwd: ${USER_PASSWD}
  lock_passwd: false
chpasswd:
expire: false
list:
  - k3suser:${USER_PASSWD}
ssh_pwauth: true
write_files:
- content: |
    alias diff='diff --color=auto'
    alias ip='ip -color=auto'
  path: /etc/profile.d/Z99-addedcolour.sh
runcmd:
- sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config
- systemctl restart ssh
EOF
  lxc start $CONTAINER
done
lxc list

これでプライベートレジストリ1ノードとk3s用のノードができた。

 

K3sの構築

通常のk3sノードの構築方法と同じ。ほんと特殊なことは一切ない。

マスターノードでは以下を実行する。

基本的には、以下と同じ。ただし、Longhornは入れられるかどうかはやったことはない。

https://www.blog.slow-fire.net/2025/02/19/純粋にk3sだけを作る%E3%80%821ip/

curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=v1.31 K3S_KUBECONFIG_MODE="644" sh -
LOCALIPADDR=<プライベートレジストリノード>
REGISTRYHOST=${LOCALIPADDR}
REGISTRYPORT=5000
REGISTRY=${REGISTRYHOST}:${REGISTRYPORT}
REGISTRYURL=http://${REGISTRY}

cat <<EOF >/etc/rancher/k3s/registries.yaml
mirrors:
"${REGISTRY}":
  endpoint:
    - "${REGISTRYURL}"
EOF
systemctl restart k3s
cat /var/lib/rancher/k3s/server/node-token

ノードトークンをメモする。

ワーカーノードでは以下を実行する。また、ワーカーノードにもプライベートレジストリの設定をしないと、プライベートレジストリからイメージが取り出せない。マスターノードと違ってサービス名はk3s-agentなのに注意。

MASTER_IP=<マスターノードIP>
curl -sfL https://get.k3s.io | K3S_URL=https://${MASTER_IP}:6443 K3S_TOKEN=<ノードトークン> sh -
LOCALIPADDR=<プライベートレジストリノード>
REGISTRYHOST=${LOCALIPADDR}
REGISTRYPORT=5000
REGISTRY=${REGISTRYHOST}:${REGISTRYPORT}
REGISTRYURL=http://${REGISTRY}
mkdir -p /etc/rancher/k3s/
cat <<EOF >/etc/rancher/k3s/registries.yaml
mirrors:
"${REGISTRY}":
  endpoint:
    - "${REGISTRYURL}"
EOF
systemctl restart k3s-agent

マスターノードに戻り

export WORKERLIST="k3s-test2 k3s-test3"
for CONTAINER in $WORKERLIST; do
kubectl label node $CONTAINER node-role.kubernetes.io/worker=worker
done
kubectl get node -o wide

ざっくり書いたがこんな感じ。LXDだからと言って特殊なことはない。

マルチノードなので、ロードバランサーとかは入れ替えたくなるかもしれない。

 

以前、単体でK3sを動かす方法を書いたが、もう少し深く考慮して検証したい場合は、こっちのほうがいいかもしれない。このサーバのスペックはK3sを1台でネイティブで作った環境と同じスペック。また、k3sノードが必要になれば、LXDのノードを足して、コンテナを足せばいい。K3sは、足元のLXDを意識していないので、また、潰す場合もコンテナを停止して、削除するだけなので、ホストへの汚染も少ない。

それにしても、IPv6を有効にしていると、IPアドレスも個人情報になってしまうので、キャプチャーが面倒だな。

コメントを残す