AI Infra 训练营
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • Day 0 · 新手接管 Runbook
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • Day 0 · 新手接管 Runbook
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
  • 实操 Runbook

    • Runbook 总览:从零部署、查看、调试
    • 完整安装总 Runbook:5 台 Ubuntu 到可用平台
    • 核心 K8s Runbook:apiserver / etcd / kubelet / containerd / HAProxy
    • Cilium 网络 Runbook:安装、查看、调试
    • Longhorn 存储 Runbook:安装、查看、调试
    • 监控日志 Runbook:Prometheus / Grafana / Loki / Alertmanager
    • CI / GitOps Runbook:Harbor / Gitea / Jenkins / Kaniko / ArgoCD
    • 安全准入 Runbook:RBAC / PSA / Kyverno / ResourceQuota

完整安装总 Runbook:5 台 Ubuntu 到可用平台

这篇回答“我拿到 5 台空 Ubuntu 机器,怎么一步步装到当前这套平台”。如果机器上已经有集群,先不要重跑安装命令,先用 Day 0 新手接管 Runbook 看状态。


0. 安装目标

装完后具备:

  • 3 control plane + 2 worker 的 K8s v1.30 集群。
  • 本地 HAProxy 做 apiserver 高可用入口:k8s-api:16443。
  • containerd 作为容器运行时。
  • Cilium 做 CNI 和 Hubble 流量观测。
  • Longhorn 做默认 StorageClass。
  • kube-prometheus-stack + Loki 做监控和日志。
  • Harbor + Gitea + Jenkins + ArgoCD 做镜像、源码、CI、CD。

组件关系:

kubectl -> k8s-api:16443 -> HAProxy -> 3 个 apiserver
                                   -> etcd 存集群状态

Pod -> Cilium -> 跨节点网络 / Service / NetworkPolicy
PVC -> Longhorn CSI -> 多副本块存储
Prometheus -> ServiceMonitor -> 采集指标 -> Grafana 展示
Promtail -> Loki -> Grafana 查日志
Gitea -> Jenkins/Kaniko -> Harbor -> ArgoCD -> K8s

1. 机器规划

节点公网 IP内网 IPhostname角色
1154.201.73.3110.0.24.31k8s-cp-1init control plane
2154.201.73.8110.0.24.29k8s-cp-2control plane
345.205.31.21410.0.24.32k8s-cp-3control plane
445.205.31.18010.0.24.28k8s-w-1worker
545.205.31.1010.0.24.30k8s-w-2worker

为什么 3 个 control plane:etcd 使用 Raft,多数派为 2,3 节点能容忍 1 台坏掉。2 个 control plane 坏 1 台就没有多数派,不是 HA。

为什么 worker 只有 2 台:学习集群资源有限,业务负载和系统组件可以先混跑;生产应把 control plane 和业务节点隔离。


2. 从本机检查 SSH 和系统

for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
  ssh -o BatchMode=yes -o ConnectTimeout=8 root@$ip '
    echo "== $(hostname) =="
    hostname -I
    . /etc/os-release && echo "$PRETTY_NAME"
    uname -r
    systemd-detect-virt || true
  '
done

为什么先做这一步:后面所有安装都是远程批量执行。SSH、hostname、内网 IP 任意一个错,kubeadm join 时会把错误节点注册进集群。


3. 写 hostname 和 hosts

只在空机器安装时执行:

ssh root@154.201.73.31 'hostnamectl set-hostname k8s-cp-1'
ssh root@154.201.73.81 'hostnamectl set-hostname k8s-cp-2'
ssh root@45.205.31.214 'hostnamectl set-hostname k8s-cp-3'
ssh root@45.205.31.180 'hostnamectl set-hostname k8s-w-1'
ssh root@45.205.31.10 'hostnamectl set-hostname k8s-w-2'

然后 5 台统一写 /etc/hosts:

for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
  ssh root@$ip 'set -eu
    cp -a /etc/hosts /etc/hosts.bak.$(date +%Y%m%d-%H%M%S)
    cp -a /etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.bak.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true

    if grep -q "^manage_etc_hosts:" /etc/cloud/cloud.cfg 2>/dev/null; then
      sed -i "s/^manage_etc_hosts:.*/manage_etc_hosts: false/" /etc/cloud/cloud.cfg
    else
      printf "\nmanage_etc_hosts: false\n" >> /etc/cloud/cloud.cfg
    fi

    cat > /etc/hosts <<HOSTS
127.0.0.1 localhost k8s-api
127.0.1.1 $(hostname)
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

10.0.24.31 k8s-cp-1 m1
10.0.24.29 k8s-cp-2 m2
10.0.24.32 k8s-cp-3 m3
10.0.24.28 k8s-w-1  m4
10.0.24.30 k8s-w-2  m5
HOSTS

    getent hosts k8s-api
  '
done

为什么 k8s-api 指向 127.0.0.1:每台机器本地跑 HAProxy,监听 127.0.0.1:16443,再转发到 3 个 apiserver。这样 kubelet 和 kubectl 都只连本机入口,不依赖外部 LB。


4. 节点基础配置

5 台都执行:

for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
  ssh root@$ip 'set -eu
    swapoff -a
    sed -i.bak "/ swap / s/^/#/" /etc/fstab

    cat > /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF
    modprobe overlay
    modprobe br_netfilter

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

    apt-get update
    apt-get install -y ca-certificates curl gnupg lsb-release chrony jq ipset ipvsadm conntrack socat ebtables ethtool
    systemctl enable --now chrony
  '
done

为什么:

  • swapoff:kubelet 默认不允许节点开启 swap,避免内存压力时调度判断失真。
  • br_netfilter:让桥接网络经过 iptables/nftables,CNI 和 Service 转发需要。
  • ip_forward:Pod 跨节点、Service 转发都需要 Linux 转发包。
  • chrony:etcd 对时间敏感,时间漂移会导致证书和 Raft 异常。

5. 安装 containerd

5 台都执行:

for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
  ssh root@$ip 'set -eu
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
      | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    chmod a+r /etc/apt/keyrings/docker.gpg
    . /etc/os-release
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${VERSION_CODENAME} stable" \
      > /etc/apt/sources.list.d/docker.list

    apt-get update
    apt-get install -y containerd.io

    mkdir -p /etc/containerd
    containerd config default > /etc/containerd/config.toml
    sed -i "s/SystemdCgroup = false/SystemdCgroup = true/" /etc/containerd/config.toml
    sed -i "s|config_path = \"\"|config_path = \"/etc/containerd/certs.d\"|" /etc/containerd/config.toml

    mkdir -p /etc/containerd/certs.d/docker.io
    cat > /etc/containerd/certs.d/docker.io/hosts.toml <<EOF
server = "https://docker.io"
[host."https://docker.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
EOF

    systemctl enable --now containerd
    systemctl restart containerd
    systemctl is-active containerd
  '
done

为什么不用 Docker:K8s 1.24 起移除了 dockershim,直接用 containerd 更短、更接近生产。

为什么 SystemdCgroup=true:Ubuntu 22.04 默认 systemd 管 cgroup,kubelet 和 containerd 必须一致,否则资源限制和 Pod 退出时容易异常。


6. 安装 kubeadm / kubelet / kubectl

5 台都执行:

for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
  ssh root@$ip 'set -eu
    curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key \
      | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
    echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /" \
      > /etc/apt/sources.list.d/kubernetes.list
    apt-get update
    apt-get install -y kubelet kubeadm kubectl
    apt-mark hold kubelet kubeadm kubectl

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

为什么 apt-mark hold:避免系统自动升级 kubelet/kubeadm/kubectl,K8s 版本升级必须按顺序做,不能让 apt 自动滚。


7. 安装 HAProxy 本地 apiserver 入口

5 台都执行:

for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
  ssh root@$ip 'set -eu
    apt-get install -y haproxy
    cat > /etc/haproxy/haproxy.cfg <<EOF
global
    log /dev/log local0
    log /dev/log local1 notice
    daemon

defaults
    log global
    mode tcp
    option tcplog
    option dontlognull
    timeout connect 5s
    timeout client  1m
    timeout server  1m

frontend k8s_api
    bind 127.0.0.1:16443
    default_backend k8s_api_backends

backend k8s_api_backends
    option tcp-check
    server cp1 10.0.24.31:6443 check
    server cp2 10.0.24.29:6443 check
    server cp3 10.0.24.32:6443 check
EOF
    systemctl enable haproxy
    systemctl restart haproxy
    ss -tlnp | grep 16443 || true
  '
done

为什么不用公网 IP:apiserver 和 etcd 走内网,减少延迟和暴露面。


8. kubeadm init 第一个控制面

在 k8s-cp-1 执行:

ssh root@154.201.73.31

写 kubeadm 配置:

cat > /root/kubeadm-init.yaml <<'EOF'
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.30.14
controlPlaneEndpoint: k8s-api:16443
networking:
  podSubnet: 10.244.0.0/16
  serviceSubnet: 10.96.0.0/12
apiServer:
  certSANs:
  - k8s-api
  - 127.0.0.1
  - 10.0.24.31
  - 10.0.24.29
  - 10.0.24.32
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: 10.0.24.31
  bindPort: 6443
nodeRegistration:
  criSocket: unix:///run/containerd/containerd.sock
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF

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

mkdir -p ~/.kube
cp -f /etc/kubernetes/admin.conf ~/.kube/config
chmod 600 ~/.kube/config

kubectl get nodes

此时节点大概率是 NotReady,因为 CNI 还没装。这是正常状态。

保存 kubeadm 输出里的两条 join 命令:

  • control plane join:给 cp-2/cp-3 用,带 --control-plane --certificate-key。
  • worker join:给 w-1/w-2 用。

9. 加入其余节点

在 cp-2/cp-3 执行 control-plane join。示例:

kubeadm join k8s-api:16443 \
  --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash> \
  --control-plane \
  --certificate-key <cert-key> \
  --cri-socket unix:///run/containerd/containerd.sock

在 w-1/w-2 执行 worker join:

kubeadm join k8s-api:16443 \
  --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash> \
  --cri-socket unix:///run/containerd/containerd.sock

如果 token 过期,在 cp-1 重新生成:

kubeadm token create --print-join-command
kubeadm init phase upload-certs --upload-certs

验收:

kubectl get nodes -o wide

此时 5 台都应出现,但还是可能 NotReady,等 Cilium 装完恢复。


10. 安装平台组件

按顺序安装,具体细节看组件 Runbook:

后续按组件 Runbook 执行:

  • CNI 网络:Cilium 网络 Runbook
  • 存储:Longhorn 存储 Runbook
  • 监控日志:监控日志 Runbook
  • 镜像仓库、Git、CI、CD:CI / GitOps Runbook
  • 安全和准入策略:安全准入 Runbook

顺序不能乱:

  1. Cilium 必须最先装,否则节点一直 NotReady,Pod 没网络。
  2. Longhorn 依赖 K8s 网络正常。
  3. 监控日志依赖 StorageClass 提供 PVC。
  4. Harbor/Gitea/Jenkins/ArgoCD 也依赖 PVC 和网络。

11. 最终验收

kubectl get nodes -o wide

kubectl get pods -A \
  --field-selector=status.phase!=Running,status.phase!=Succeeded \
  -o wide

helm list -A

cilium status --wait=false

kubectl get sc

kubectl get pvc -A

kubectl get svc -A | grep NodePort

期望:

  • 5 个节点都是 Ready。
  • 除演练 Pod 外,没有业务组件 CrashLoop。
  • longhorn 是默认 StorageClass。
  • Cilium Desired: 5, Ready: 5/5。
  • Grafana、Harbor、Jenkins、ArgoCD、Longhorn 都有 NodePort。
在 GitHub 上编辑此页
Prev
Runbook 总览:从零部署、查看、调试
Next
核心 K8s Runbook:apiserver / etcd / kubelet / containerd / HAProxy