返回

Kubernetes inside LXD

在 LXD 内搭建 Kubernetes 实践环境

为什么使用 LXD

首先,我们搭建的是一个 Kubernetes 集群,所以需要多台计算机,这对于一个普通初学者来说是承担不起的,由于是实践环境(学习环境),我们可以使用虚拟来虚拟出多台计算机,但这会浪费计算机大量的内存与磁盘资源,下面是虚拟机与 LXD 的对比:

  • 共享内核,且不需要预分配内存,使用多少就是多少
  • 由于共享内核,需要的内核功能只要在主机操作一遍即可
  • 因为容器的体量小,创建、删除的速度极快,出现误操作可以直接删除重来

注意

  • 本文章仅在 ArchLinux 环境下有效,在 Ubuntu 等环境可能会出现 cgroups 功能缺失,内核模块加载失败等错误,若仍要参考此文章请自行在网络上搜索问题的解决方案。

  • 很不幸,Kubernetes 暂不支持在使用 Btrfs 文件格式的主机的容器中部署,当然,如果要坚持使用 LXD 创建 Kubernetes 集群,可以参考以下解决方案:

    • 将空闲分区格式化为 ext4 文件格式并挂载。
    • 创建虚拟磁盘(img 文件)格式化为 ext4 文件格式并挂载。
    • 创建虚拟机。
  • 本文章使用了大量的国内镜像源,如果您在境外或可以直接访问 Docker、Kubernetes 等网站建议使用官方软件源与镜像

创建存储池

下面指令将使用 /mnt/vol/lxd 目录创建一个名为 k8s 的存储池,如有特殊需求请自行更改位置。

因下文配置含有存储池相关,不建议更改名称。

lxc storage create k8s dir source=/mnt/vol/lxd

创建容器配置模板

下面指令将创建一个名为 k8s 的配置模板:

lxc profile create k8s && echo 'name: k8s
config:
  boot.autostart: "true"
  linux.kernel_modules: ip_tables,ip6_tables,netlink_diag,nf_nat,overlay,br_netfilter
  limits.memory.swap: "false"
  raw.lxc: |
    lxc.apparmor.profile=unconfined
    lxc.mount.auto=proc:rw sys:rw cgroup:rw
    lxc.cgroup.devices.allow=a
    lxc.cap.drop=
  security.nesting: "true"
  security.privileged: "true"
description: "K8s on LXD"
devices:
  eth0:
    name: eth0
    nictype: bridged
    parent: lxdbr0
    type: nic
  root:
    path: /
    pool: k8s
    type: disk' | lxc profile edit k8s

验证配置是否创建:

lxc profile show k8s

创建容器

使用配置 k8s 创建一个名为 k-master 的 Debian 11 容器

创建非 Ubuntu 容器需要设置镜像,请参考:ArchLinux 安装 LXD#添加国内镜像

lxc launch -p k8s mirrors:debian/11 k-master

你可以选择在配置完 master 控制平面节点后一个个创建 node 节点容器,也可以使用下面指令一次性创建三个 node 容器:

for N in 1 2 3 ; do lxc launch -p k8s mirrors:debian/11 k-node$N ; done

查看容器列表:

lxc list

注意:以下内容直到 拉取部署 K8s 所需的 Images 章节前的内容,需要在所有容器中操作一遍。

登录容器并更新系统

请自行将下文的 <容器名> 关键词替换为你环境中的容器名

lxc exec <容器名> -- /bin/bash

解决 kubelet 无法读取 kmsg 的问题:(原理创建/dev/kmsg 到/dev/null 的链接,需要重启生效)

echo 'L /dev/kmsg - - - - /dev/null' > /etc/tmpfiles.d/kmsg.conf

设置 apt 镜像并完全更新系统

echo 'deb http://mirrors.aliyun.com/debian/ bullseye main contrib non-free
deb http://mirrors.aliyun.com/debian/ bullseye-updates main contrib non-free
deb http://mirrors.aliyun.com/debian/ bullseye-backports main contrib non-free
deb http://mirrors.aliyun.com/debian-security/ bullseye-security main contrib non-free' | \
tee /etc/apt/sources.list > /dev/null
apt update && apt full-upgrade

注: 以下步骤与物理机安装无区别。除无需再次设置内核流量转发外,其余部分均可参考:使用 kubeadm 引导集群

安装 Docker

添加 Docker 软件源并安装 Docker

# 安装 apt-https 和 gpg 密钥支持
apt install apt-transport-https ca-certificates curl gnupg -y
# 下载并存储密钥
curl -fsL http://mirrors.aliyun.com/docker-ce/linux/debian/gpg | \
gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 添加软件源
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] http://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装
apt update && apt install docker-ce docker-ce-cli containerd.io -y

创建 Docker 配置并设置镜像源与 cgroupdriver

echo \
'{
    "registry-mirrors": ["https://dockerhub.azk8s.cn"],
    "exec-opts": ["native.cgroupdriver=systemd"],
    "storage-driver": "overlay2",
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "100m"
    }
}' | tee /etc/docker/daemon.json > /dev/null

重启 Docker 使配置生效:(Docker 安装后会默认启动并设置开机自启动,无需再次手动设置)

systemctl restart docker
systemctl status docker # 查看状态

安装 kubeadm 等工具

# 下载并存储密钥
curl -fsL http://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | \
gpg --dearmor -o /usr/share/keyrings/kubernetes-archive-keyring.gpg
# 添加软件源
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] http://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main" | \
tee /etc/apt/sources.list.d/kubernetes.list > /dev/null
# 安装
apt update && apt install -y kubelet kubeadm kubectl

拉取部署 K8s 所需的 Images

更简单的方法:参考 使用 kubeadm 初始化控制平面节点注4,但 coredns 仍需要使用下列步骤拉取,但是要在修改 tag 时将 k8s.gcr.io 改为你参数指定的值(当前不可用,原因:coredns)

由于网络原因,我们无法直接使用 kubeadm 部署,但是我们可以通过提前从镜像源拉取并打 tag 的方式来解决。

使用 kubeadm config images list 指令获取当前版本的镜像列表,比如在我写这篇文章的时候版本如下:

k8s.gcr.io/kube-apiserver:v1.22.1
k8s.gcr.io/kube-controller-manager:v1.22.1
k8s.gcr.io/kube-scheduler:v1.22.1
k8s.gcr.io/kube-proxy:v1.22.1
k8s.gcr.io/pause:3.5
k8s.gcr.io/etcd:3.5.0-0
k8s.gcr.io/coredns/coredns:v1.8.4

将其中的 k8s.gcr.io 修改为 registry.aliyuncs.com/google_containers 就可从阿里的镜像中拉取,但 coredns 除外,coredns 位于 Docker 官方仓库,我们只需直接拉取就行了,但是要注意:k8s.gcr.io 中的 coredns 的版本有 v,Docker 官方的没有

拉取镜像:

docker pull registry.aliyuncs.com/google_containers/kube-apiserver:v1.22.1
docker pull registry.aliyuncs.com/google_containers/kube-controller-manager:v1.22.1
docker pull registry.aliyuncs.com/google_containers/kube-scheduler:v1.22.1
docker pull registry.aliyuncs.com/google_containers/kube-proxy:v1.22.1
docker pull registry.aliyuncs.com/google_containers/pause:3.5
docker pull registry.aliyuncs.com/google_containers/etcd:3.5.0-0
docker pull coredns/coredns:1.8.4 # 去掉 v

拉取完之后只需要按照 docker tag <修改后名称> <修改前名称> 的格式打上 tag 便可让 kubeadm 检测到并使用

docker tag registry.aliyuncs.com/google_containers/kube-apiserver:v1.22.1 k8s.gcr.io/kube-apiserver:v1.22.1
docker tag registry.aliyuncs.com/google_containers/kube-controller-manager:v1.22.1 k8s.gcr.io/kube-controller-manager:v1.22.1
docker tag registry.aliyuncs.com/google_containers/kube-scheduler:v1.22.1 k8s.gcr.io/kube-scheduler:v1.22.1
docker tag registry.aliyuncs.com/google_containers/kube-proxy:v1.22.1 k8s.gcr.io/kube-proxy:v1.22.1
docker tag registry.aliyuncs.com/google_containers/pause:3.5 k8s.gcr.io/pause:3.5
docker tag registry.aliyuncs.com/google_containers/etcd:3.5.0-0 k8s.gcr.io/etcd:3.5.0-0
docker tag coredns/coredns:1.8.4 k8s.gcr.io/coredns/coredns:v1.8.4

使用 kubeadm 初始化控制平面节点

注:

  1. 在此操作之前强烈建议使用 kubelet --cgroup-driver=systemd 检测 kubelet 是否能正常运行
  2. 若要重新初始化请先使用 kubeadm reset 删除之前初始化产生的配置
  3. 更多 kubeadm 参数请参考:初始化控制平面节点
  4. 可设置 --image-repository 参数选择用于拉取控制平面镜像的容器仓库,以达到加速的目的,例如:--image-repository registry.aliyuncs.com/google_containers--image-repository k8s-gcr-io.mirrors.sjtug.sjtu.edu.cn (对 coredns 并不生效),当然使用 kubeadm init 的时候也需要加上
  5. kubeadm init 之前运行 kubeadm config images pull,以验证与容器镜像仓库的连通性,并提前拉取镜像。
  6. 命令行参数 --kubernetes-version 会影响到镜像的版本。
kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--control-plane-endpoint=10.161.141.34 \
--apiserver-advertise-address=10.161.141.34 # 填你的eth0 IP

等待几分钟即可

要使非 root 用户可以运行 kubectl,请运行以下命令, 它们也是 kubeadm init 输出的一部分:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

或者,如果你是 root 用户,则可以运行:

export KUBECONFIG=/etc/kubernetes/admin.conf

加入节点

节点容器也需要拉取 k8s.gcr.io/pause 这个 Image

在 node 容器中运行(其中的参数来自 kubeadm init,请注意查看)

kubeadm join --token <token> <control-plane-host>:<control-plane-port> --discovery-token-ca-cert-hash sha256:<hash>

安装 Flannel Pod 网络插件

docker pull quay.mirrors.ustc.edu.cn/coreos/flannel:v0.14.0
docker tag quay.mirrors.ustc.edu.cn/coreos/flannel:v0.14.0 quay.io/coreos/flannel:v0.14.0
kubectl apply -f https://raw.fastgit.org/coreos/flannel/v0.14.0/Documentation/kube-flannel.yml

由于 LXC 容器的限制,容器无法直接修改内核配置,会导致 kube-proxykube-flannel 无法正常启动。临时解决办法:使用 kubectl logs kube-proxy-<填写自己的pod名> --namespace kube-system 查看日志,并在主机中将参数手动写入 /sys/module/nf_conntrack/parameters/hashsize/proc/sys/net/netfilter/nf_conntrack_max (具有风险,谨慎操作)

kubectl get nodes
kubectl get pods --namespace kube-system

STATUSReadyRunning 则表明部署成功

(可选) 部署 Dashboard UI

默认情况下不会部署 Dashboard。可以通过以下命令部署:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.2.0/aio/deploy/recommended.yaml

上面是官方网的文档,我们可以用FastGit来加速一下

kubectl apply -f https://raw.fastgit.org/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml
kubectl proxy

http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

问题

coredns 停滞在 Pending 状态

这一行为是预期之中的,因为系统就是这么设计的。 kubeadm 的网络供应商是中立的,因此管理员应该选择 安装 pod 的网络插件。 你必须完成 Pod 的网络配置,然后才能完全部署 CoreDNS。 在网络被配置好之前,DNS 组件会一直处于 Pending 状态。

请参考:安装 Flannel Pod 网络插件

查看 Pods 运行状态

# 查看所有 pods
kubectl get pods --all-namespaces
kubectl get pods --namespace kube-system
# 查看单个 pod 日志
kubectl logs <name>
Built with Hugo
Theme Stack designed by Jimmy