完整安装总 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 | 内网 IP | hostname | 角色 |
|---|---|---|---|---|
| 1 | 154.201.73.31 | 10.0.24.31 | k8s-cp-1 | init control plane |
| 2 | 154.201.73.81 | 10.0.24.29 | k8s-cp-2 | control plane |
| 3 | 45.205.31.214 | 10.0.24.32 | k8s-cp-3 | control plane |
| 4 | 45.205.31.180 | 10.0.24.28 | k8s-w-1 | worker |
| 5 | 45.205.31.10 | 10.0.24.30 | k8s-w-2 | worker |
为什么 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
顺序不能乱:
- Cilium 必须最先装,否则节点一直 NotReady,Pod 没网络。
- Longhorn 依赖 K8s 网络正常。
- 监控日志依赖 StorageClass 提供 PVC。
- 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。