Skip to content

Ubuntu 24.04 单节点 K8S 安装指南 ​

本教程介绍如何在一台 Ubuntu 24.04 服务器上安装 Kubernetes v1.31.14 单节点集群。按照下列步骤可依次完成 /data 数据目录规划、containerd 配置、轩辕镜像加速、集群初始化、Flannel 网络安装和运行状态验证。

版本说明

Kubernetes v1.31.14 是 v1.31 系列最后一个补丁版本,现已停止维护。本教程用于安装指定的 v1.31 环境;新建生产集群时应优先选择仍在维护的版本。

1. 部署约定 ​

项目配置
操作系统Ubuntu 24.04(Noble)
Kubernetesv1.31.14
容器运行时containerd 1.7
网络插件Flannel v0.26.7
服务管理systemctl 管理 containerd、kubelet
数据根目录/data
Pod 网段10.244.0.0/16
Service 网段10.96.0.0/12

控制平面与业务 Pod 共用一个节点。API Server、Scheduler、Controller Manager 和 etcd 是由 kubelet 管理的静态 Pod,不存在统一的 kubernetes.service。

开始前切换到 root,并设置安装参数:

bash
sudo -i
bash
set -euo pipefail

NODE_IP="192.168.1.10"
NODE_NAME="k8s-node01"

# 轩辕专属域名前缀,不包含 .xuanyuan.run
XY_ID="替换为你的专属前缀"

K8S_VERSION="v1.31.14"

先确认网络规划

NODE_IP 必须是服务器固定内网 IP。Pod 和 Service 网段不能与服务器内网、VPN 或已有路由重叠。修改 Pod 网段后,还要同步修改 Flannel 配置。

2. 初始化系统 ​

检查内存、swap、路由和 /data 挂载:

bash
free -h
swapon --show
ip -4 addr
ip -4 route
df -h /data

关闭 swap,并避免重启后重新启用:

bash
cp -a /etc/fstab "/etc/fstab.bak.$(date +%Y%m%d%H%M%S)"
swapoff -a

sed -ri '/^[[:space:]]*#/!{
  /[[:space:]]swap[[:space:]]/s/^/# /
}' /etc/fstab

如果系统使用 zram 或其他服务创建 swap,还需要禁用对应服务。重启后应确认 swapon --show 没有输出。

安装依赖并启用 Kubernetes 所需内核配置:

bash
apt-get update
apt-get install -y ca-certificates curl gpg conntrack socat iptables

cat > /etc/modules-load.d/kubernetes.conf <<'EOF'
overlay
br_netfilter
EOF

modprobe overlay
modprobe br_netfilter

cat > /etc/sysctl.d/99-kubernetes.conf <<'EOF'
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

sysctl --system

3. 规划 /data 数据目录 ​

用途存储位置
容器镜像、容器可写层/data/containerd
etcd 数据/data/k8s/etcd
kubelet 数据与 Pod 卷/data/k8s/kubelet
CNI 分配状态/data/k8s/cni
Pod 日志/data/k8s/logs
下载的部署清单/data/k8s/manifests

kubelet、CNI 和 Pod 日志通过 bind mount 保留标准路径,减少插件兼容问题:

bash
mkdir -p /data/containerd
mkdir -p /data/k8s/{etcd,kubelet,cni,logs,manifests}
chmod 700 /data/k8s/etcd

for target in /var/lib/kubelet /var/lib/cni /var/log/pods; do
  mkdir -p "$target"

  if mountpoint -q "$target" ||
     [ -n "$(find "$target" -mindepth 1 -maxdepth 1 -print -quit)" ]; then
    echo "发现已有挂载或数据,请先检查:$target"
    exit 1
  fi
done

cat >> /etc/fstab <<'EOF'

# Kubernetes data directories
/data/k8s/kubelet /var/lib/kubelet none bind,x-systemd.requires-mounts-for=/data 0 0
/data/k8s/cni /var/lib/cni none bind,x-systemd.requires-mounts-for=/data 0 0
/data/k8s/logs /var/log/pods none bind,x-systemd.requires-mounts-for=/data 0 0
EOF

systemctl daemon-reload
mount /var/lib/kubelet
mount /var/lib/cni
mount /var/log/pods

findmnt /var/lib/kubelet
findmnt /var/lib/cni
findmnt /var/log/pods

为什么使用 bind mount?

部分 Kubernetes 组件和第三方插件默认访问 /var/lib/kubelet、/var/lib/cni 与 /var/log/pods。保留这些标准路径,同时把实际数据放到 /data,兼容性通常好于直接修改所有组件参数。

4. 安装并配置 containerd 1.7 ​

通过 Docker 官方 APT 仓库安装 containerd.io,只安装容器运行时:

bash
install -m 0755 -d /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable" \
> /etc/apt/sources.list.d/docker.list

apt-get update

CONTAINERD_PKG="$(
  apt-cache madison containerd.io |
  awk '$3 ~ /^1\.7\./ {print $3}' |
  sort -Vr |
  head -n 1
)"

: "${CONTAINERD_PKG:?软件源中未找到 containerd.io 1.7}"

apt-get install -y "containerd.io=${CONTAINERD_PKG}"
apt-mark hold containerd.io
systemctl stop containerd
containerd --version

写入 containerd 1.7 配置:

bash
mkdir -p /etc/containerd

if [ -f /etc/containerd/config.toml ]; then
  cp -a /etc/containerd/config.toml \
    "/etc/containerd/config.toml.bak.$(date +%Y%m%d%H%M%S)"
fi

cat > /etc/containerd/config.toml <<'EOF'
version = 2

root = "/data/containerd"
state = "/run/containerd"

[grpc]
  address = "/run/containerd/containerd.sock"

[plugins."io.containerd.grpc.v1.cri"]
  sandbox_image = "registry.k8s.io/pause:3.10"

[plugins."io.containerd.grpc.v1.cri".containerd]
  default_runtime_name = "runc"

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  runtime_type = "io.containerd.runc.v2"

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
  SystemdCgroup = true

[plugins."io.containerd.grpc.v1.cri".cni]
  bin_dir = "/opt/cni/bin"
  conf_dir = "/etc/cni/net.d"

[plugins."io.containerd.grpc.v1.cri".registry]
  config_path = "/etc/containerd/certs.d"
EOF

配置轩辕镜像 ​

轩辕需要按原始仓库分别配置映射:

原始仓库轩辕镜像地址
docker.io${XY_ID}.xuanyuan.run
registry.k8s.io${XY_ID}-k8s.xuanyuan.run
ghcr.io${XY_ID}-ghcr.xuanyuan.run
quay.io${XY_ID}-quay.xuanyuan.run

使用 containerd 的 hosts.toml 配置,YAML 中仍保留官方镜像名:

bash
write_mirror() {
  local registry="$1"
  local mirror="$2"

  mkdir -p "/etc/containerd/certs.d/${registry}"

  cat > "/etc/containerd/certs.d/${registry}/hosts.toml" <<EOF
server = "https://${mirror}"

[host."https://${mirror}"]
  capabilities = ["pull", "resolve"]
EOF
}

write_mirror docker.io       "${XY_ID}.xuanyuan.run"
write_mirror registry.k8s.io "${XY_ID}-k8s.xuanyuan.run"
write_mirror ghcr.io         "${XY_ID}-ghcr.xuanyuan.run"
write_mirror quay.io         "${XY_ID}-quay.xuanyuan.run"

为 containerd 和 kubelet 添加 /data 挂载依赖:

bash
for service in containerd kubelet; do
  mkdir -p "/etc/systemd/system/${service}.service.d"

  cat > "/etc/systemd/system/${service}.service.d/20-data.conf" <<'EOF'
[Unit]
RequiresMountsFor=/data/containerd /data/k8s/etcd /var/lib/kubelet /var/lib/cni /var/log/pods
EOF
done

systemctl daemon-reload
systemctl enable --now containerd
systemctl is-active containerd

5. 安装 kubeadm、kubelet 和 kubectl ​

添加 Kubernetes v1.31 独立软件源并锁定版本:

bash
curl -fsSL \
  https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key \
  -o /tmp/kubernetes-release.key

gpg --batch --yes --dearmor \
  -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg \
  /tmp/kubernetes-release.key

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' \
> /etc/apt/sources.list.d/kubernetes.list

apt-get update

K8S_PKG="$(
  apt-cache madison kubeadm |
  awk '$3 ~ /^1\.31\.14-/ {print $3; exit}'
)"

: "${K8S_PKG:?软件源中未找到 Kubernetes 1.31.14}"

apt-get install -y \
  "kubeadm=${K8S_PKG}" \
  "kubelet=${K8S_PKG}" \
  "kubectl=${K8S_PKG}" \
  cri-tools

apt-mark hold kubeadm kubelet kubectl
systemctl daemon-reload
systemctl enable kubelet

配置 crictl:

bash
cat > /etc/crictl.yaml <<'EOF'
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 120
debug: false
EOF

kubeadm version -o short
kubectl version --client
crictl info

test -x /opt/cni/bin/bridge
test -x /opt/cni/bin/host-local
test -x /opt/cni/bin/loopback
test -x /opt/cni/bin/portmap

初始化前 kubelet 没有完整配置,反复重启属于正常现象;安装 CNI 前,运行时报告网络尚未就绪也属正常。

6. 初始化 Kubernetes ​

生成 kubeadm 配置:

bash
cat > /root/kubeadm-init.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: "${NODE_IP}"
  bindPort: 6443
nodeRegistration:
  name: "${NODE_NAME}"
  criSocket: unix:///run/containerd/containerd.sock
  kubeletExtraArgs:
    - name: node-ip
      value: "${NODE_IP}"
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: "${K8S_VERSION}"
imageRepository: registry.k8s.io
networking:
  podSubnet: "10.244.0.0/16"
  serviceSubnet: "10.96.0.0/12"
  dnsDomain: cluster.local
etcd:
  local:
    dataDir: /data/k8s/etcd
apiServer:
  certSANs:
    - "${NODE_IP}"
    - "${NODE_NAME}"
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
failSwapOn: true
containerLogMaxSize: 20Mi
containerLogMaxFiles: 5
EOF

先验证配置和镜像,再执行初始化:

bash
kubeadm config validate --config /root/kubeadm-init.yaml
kubeadm config images list --config /root/kubeadm-init.yaml
kubeadm config images pull --config /root/kubeadm-init.yaml
crictl images

kubeadm init --config /root/kubeadm-init.yaml

配置 root 用户的访问凭据:

bash
install -d -m 700 /root/.kube
install -m 600 /etc/kubernetes/admin.conf /root/.kube/config
kubectl get nodes -o wide

此时节点显示 NotReady 是正常的,安装 CNI 后才会变为 Ready。

7. 安装 Flannel ​

下载固定版本的清单。Flannel 镜像来自 GHCR,会通过前面配置的轩辕 GHCR 地址拉取:

bash
curl -fL --retry 3 \
  https://raw.githubusercontent.com/flannel-io/flannel/v0.26.7/Documentation/kube-flannel.yml \
  -o /data/k8s/manifests/kube-flannel.yml

crictl pull ghcr.io/flannel-io/flannel:v0.26.7
crictl pull ghcr.io/flannel-io/flannel-cni-plugin:v1.6.2-flannel1

kubectl apply -f /data/k8s/manifests/kube-flannel.yml

# 单节点允许控制平面调度业务 Pod
kubectl taint nodes "${NODE_NAME}" \
  node-role.kubernetes.io/control-plane:NoSchedule-

如果 UFW 已启用,允许 Pod 网络转发:

bash
ufw status

ufw route allow in on cni0
ufw route allow out on cni0
ufw route allow in on flannel.1
ufw route allow out on flannel.1

防火墙

只有 ufw status 显示 active 时才需要添加上述规则。不要把 API Server 的 TCP 6443 直接暴露到公网;远程访问应限制来源地址。增加工作节点时,还要放行节点间的 TCP 10250、Flannel UDP 8472 等流量。

8. 验证集群 ​

等待节点和系统组件就绪:

bash
kubectl wait --for=condition=Ready "node/${NODE_NAME}" --timeout=300s

kubectl -n kube-flannel rollout status \
  daemonset/kube-flannel-ds --timeout=300s

kubectl -n kube-system rollout status \
  deployment/coredns --timeout=300s

kubectl get nodes -o wide
kubectl get pods -A -o wide

验证 Docker Hub 镜像映射和集群 DNS:

bash
kubectl run dns-check \
  --image=docker.io/library/busybox:1.36.1 \
  --restart=Never \
  --command -- sh -c \
  'nslookup kubernetes.default.svc.cluster.local && sleep 300'

kubectl wait --for=condition=Ready pod/dns-check --timeout=180s
kubectl logs dns-check
kubectl delete pod dns-check

检查 systemd 服务、挂载和磁盘占用:

bash
systemctl is-enabled containerd kubelet
systemctl is-active containerd kubelet

findmnt /var/lib/kubelet
findmnt /var/lib/cni
findmnt /var/log/pods

du -sh /data/containerd /data/k8s/*

9. 让普通用户使用 kubectl ​

先退出 root Shell,回到需要使用 kubectl 的普通用户,然后执行:

bash
mkdir -p "$HOME/.kube"
chmod 700 "$HOME/.kube"

if [ -f "$HOME/.kube/config" ]; then
  cp -p "$HOME/.kube/config" \
    "$HOME/.kube/config.bak.$(date +%Y%m%d%H%M%S)"
fi

sudo install -m 600 -o "$(id -u)" -g "$(id -g)" \
  /etc/kubernetes/admin.conf "$HOME/.kube/config"

unset KUBECONFIG
kubectl get nodes
kubectl get pods -A

管理员权限

admin.conf 授予集群管理员权限,只应复制给可信用户。

10. 日常管理与排错 ​

bash
# 服务状态
systemctl status containerd kubelet --no-pager

# 启停与重启
systemctl restart containerd kubelet
systemctl stop kubelet containerd
systemctl start containerd kubelet

# 服务日志
journalctl -u containerd -n 100 --no-pager
journalctl -u kubelet -n 100 --no-pager

# 集群状态与事件
kubectl get pods -A -o wide
kubectl get events -A --sort-by=.lastTimestamp

# API Server 不可用时从运行时排查
crictl ps -a
# crictl logs <容器ID>

停止 kubelet 或 containerd 不代表所有业务容器都会立刻停止。业务启停应通过 Deployment、StatefulSet 等 Kubernetes 工作负载管理。

镜像拉取失败 ​

先直接拉取镜像获取详细错误:

bash
crictl pull <镜像名>

再依次检查:

  1. XY_ID 和轩辕专属域名是否正确。
  2. 轩辕账号和对应仓库加速是否可用。
  3. /etc/containerd/certs.d/<原始仓库>/hosts.toml 是否存在。
  4. 修改配置后是否执行了 systemctl restart containerd。
  5. Pod 为 ImagePullBackOff 时,使用下面命令查看事件:
bash
kubectl describe pod <Pod名> -n <命名空间>

11. 常见问题:APT 混入旧版 Ubuntu 源 ​

如果 apt-get update 出现 hirsute Release 404,说明软件源中残留了已停止维护的 Ubuntu 21.04(hirsute)配置。Ubuntu 24.04 应使用代号 noble 的软件源。

先确认真实系统版本:

bash
cat /etc/os-release

应包含:

text
VERSION_ID="24.04"
VERSION_CODENAME=noble

不要跨版本硬改源

如果实际系统不是 Ubuntu 24.04,不要直接把旧系统的软件源代号替换成 noble。

备份 APT 配置并定位问题来源:

bash
APT_BACKUP="/root/apt-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$APT_BACKUP"
cp -a /etc/apt/. "$APT_BACKUP/"

grep -nHE \
  'hirsute|docker-ce/linux/ubuntu|download\.docker\.com/linux/ubuntu|Signed-By' \
  /etc/apt/sources.list \
  /etc/apt/sources.list.d/*.list \
  /etc/apt/sources.list.d/*.sources \
  2>/dev/null || true

对传统 .list 文件,注释有效的 hirsute 条目:

bash
for source_file in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do
  [ -f "$source_file" ] || continue

  sed -Ei \
    '/^[[:space:]]*deb(-src)?[[:space:]].*[[:space:]]hirsute(-updates|-backports|-security)?([[:space:]]|$)/s/^/# disabled-old-release: /' \
    "$source_file"
done

如果问题位于 .sources 文件:

  • 整个配置块都是 hirsute:添加 Enabled: no。
  • Suites: 混有 noble 和 hirsute:只移除 hirsute 及其派生项。

12. 常见问题:Docker 软件源缺少 GPG 公钥 ​

如果更新软件源时出现以下错误,说明 Docker 软件源没有配置正确的签名公钥:

text
NO_PUBKEY 7EA0A9C3F273FCD8
The repository ... noble InRelease is not signed.

重新安装 Docker 官方公钥:

bash
install -m 0755 -d /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc

chmod 0644 /etc/apt/keyrings/docker.asc

然后修改已有 Docker 源,不要重复添加相同仓库。.list 格式示例:

text
deb [signed-by=/etc/apt/keyrings/docker.asc] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu noble stable

.sources 格式示例:

text
Types: deb
URIs: https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu
Suites: noble
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc

两种格式保留一种即可。最后重新验证:

bash
apt-get update

成功时不应再请求 hirsute,也不应出现 NO_PUBKEY、does not have a Release file 或 is not signed。

两类镜像配置不要混淆

APT 软件源用于下载 containerd.io 等系统软件包;轩辕镜像用于 containerd 拉取容器镜像。修复其中一项不会自动修复另一项。

参考资料 ​