Day 0:5 节点裸 Ubuntu → K8s 装机基线
拿到 5 台裸 Ubuntu VPS 之后,在 kubeadm 跑起来之前应该做什么。30-45 分钟内把 SSH / 主机名 / 时间 / 基线工具全部理顺,后面 14 天不会因为基础设施层面的坑被打断。
整篇围绕 4 步基线展开,每步都给「为什么这么做」和「一个真实踩过的坑」。
环境前置
5 台节点跑 Ubuntu 22.04 LTS,单机 8C / 8G RAM / KVM,内网在同一个 /24:
| 节点 | 内网 IP | 别名 | 角色 |
|---|---|---|---|
| k8s-cp-1 | 10.0.24.28 | m1 | 控制面(init master) |
| k8s-cp-2 | 10.0.24.29 | m2 | 控制面 |
| k8s-cp-3 | 10.0.24.30 | m3 | 控制面 |
| k8s-w-1 | 10.0.24.31 | m4 | worker |
| k8s-w-2 | 10.0.24.32 | m5 | worker |
为什么 3 CP + 2 W:3 CP 是 etcd raft 的最小 quorum(能容忍 1 个 down);只起 1 CP 学不到 leader election 和选主时序,面试常被追问「你做过 HA 控制面吗」。
第 1 步:探机器底子
装 K8s 之前 5 分钟,需要确认 3 件事:虚拟化类型、cgroup 版本、默认服务是否有干扰。
# 1. 虚拟化类型 —— 必须是 kvm / vmware / hyperv 等真虚拟化
systemd-detect-virt # 期望:kvm
systemd-detect-virt --container # 期望:none(不是 LXC 套娃)
# 2. cgroup 版本 —— K8s 1.25+ 在 v2 下更稳
stat -f -c %T /sys/fs/cgroup # 期望:cgroup2fs
# 3. IDC 默认监控 / 后台服务
systemctl list-unit-files --state=enabled | grep -iE 'agent|aliyun|aegis|nezha|monitor'
ls /opt /usr/local # 大部分情况应该是空
ss -lntp # 监听端口只应有 22 + 53(systemd-resolved)
# 4. 磁盘真实拓扑
lsblk -nbo NAME,SIZE,TYPE,MOUNTPOINT
LXC 容器里跑 K8s 极难 —— cgroup namespace 是共享的,modprobe 不可用,kubelet 装不上。一些小 IDC 把 LXC 当 VPS 卖,外层 systemd-detect-virt 看不出,内层 --container 才会暴露。检测到 LXC 直接换机器,不要硬怼。
lsblk 是装机第一个该跑的命令,不要只看 df -h /。df 只显示已挂载的文件系统;lsblk 显示所有块设备,可能有大盘没挂载、或者挂在莫名其妙的 /www 路径上等着 Day 1 处理。
第 2 步:SSH 免密 + 硬化
新机器还是密码登录,先用 sshpass 把公钥推过去,验证 key auth 工作后再关密码。顺序不能反,否则把自己锁在外面。
2.1 推公钥(幂等)
PUBKEY=$(cat ~/.ssh/id_rsa.pub)
for ip in 10.0.24.28 10.0.24.29 10.0.24.30 10.0.24.31 10.0.24.32; do
sshpass -p "$IP_PWD" ssh -o StrictHostKeyChecking=accept-new root@$ip "
mkdir -p /root/.ssh && chmod 700 /root/.ssh
grep -qxF '$PUBKEY' /root/.ssh/authorized_keys 2>/dev/null \
|| echo '$PUBKEY' >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
"
done
两个细节:
grep -qxF+||实现幂等:已存在不重复 append。x整行匹配,F不当 regex 解析(公钥里有+//等特殊字符),q静默。StrictHostKeyChecking=accept-new:首次自动信任并写入known_hosts,比=no安全 —— 这是 TOFU(Trust On First Use)模型,只在第一次接受。
2.2 真坑:IDC 默认关了 PubkeyAuthentication
公钥已经在 authorized_keys 里,ssh root@ip 'echo OK' 仍然 Permission denied。
排查命令:
sshpass -p "$IP_PWD" ssh root@$ip 'sshd -T | grep -i pubkey'
# pubkeyauthentication no <- 根因
这家 IDC 的 Ubuntu 模板默认把公钥认证关了(可能是为了方便 console 重置密码)。修复:
ssh root@$ip "
sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
# Ubuntu 22 还有 override 目录,要一并改
for f in /etc/ssh/sshd_config.d/*.conf; do
[ -f \"\$f\" ] && grep -q PubkeyAuthentication \"\$f\" && \
sed -i 's/^PubkeyAuthentication.*/PubkeyAuthentication yes/' \"\$f\"
done
sshd -t && systemctl reload sshd
"
两点教训:
- 改 sshd 必看
/etc/ssh/sshd_config.d/*.conf,这里的设置 override 主配置。「改了主文件不生效」十有八九是这个原因。 - 任何时候 reload sshd 必须先
sshd -t验证语法,否则配错了把自己锁外面。
2.3 关密码登录
key auth 验证可用后(ssh -o BatchMode=yes root@ip 'echo OK' 成功),再关密码登录:
ssh root@$ip "
sed -i \
-e 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' \
-e 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' \
-e 's/^#*KbdInteractiveAuthentication.*/KbdInteractiveAuthentication no/' \
/etc/ssh/sshd_config
for f in /etc/ssh/sshd_config.d/*.conf; do
[ -f \"\$f\" ] && sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' \"\$f\"
done
sshd -t && systemctl reload sshd
"
2.4 本地 SSH alias
cat >> ~/.ssh/config <<'EOF'
# === k8s-bootcamp ===
Host k8s-cp-1 m1
HostName 10.0.24.28
User root
IdentityFile ~/.ssh/id_rsa
# ... 其余 4 段同理
EOF
# 用 ssh -G 验证 alias 解析(不实际连接)
ssh -G k8s-cp-1 | grep -E "^(hostname|user|identityfile)"
ssh -G <alias> 是 ssh config 调试的关键命令 —— 它 dump 解析后的最终配置但不实际连接,可以验证 alias 写对没。直接 ssh <alias> 测试万一连失败,原因可能是网络也可能是 config,分不清。
第 3 步:系统升级 + 装基线工具
ssh root@$ip 'bash -s' <<'EOF'
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y --no-install-recommends \
curl wget ca-certificates apt-transport-https gnupg lsb-release \
vim jq tmux htop iotop \
tcpdump dnsutils iperf3 mtr-tiny net-tools \
chrony \
git unzip zip rsync less \
bash-completion \
ipset conntrack \
python3-pip
# 升级时遇到 conf 冲突保留旧的(防 sshd_config 被覆盖)
apt-get upgrade -y -o Dpkg::Options::="--force-confold"
EOF
关键包速记:
| 包 | 用途 |
|---|---|
ipset conntrack | kube-proxy 必需 |
dnsutils(含 dig / nslookup) | CoreDNS 调试 |
tcpdump | 网络故障抓包 |
iperf3 | 跨节点带宽测试、CNI 数据面对比 |
chrony | NTP 同步 —— etcd raft 对时间漂移敏感 |
jq | 解析 JSON 输出,几乎每天用 |
远程脚本 No.1 陷阱:ssh ... 'bash -s' <<'EOF' 必须用单引号包 EOF。双引号或不加引号会让本地 shell 提前 eval 掉 $(...) 和 $var,远端实际跑到的命令变成空。SRE / DevOps 写远程脚本踩这个坑的概率是 100%,记牢。
第 4 步:hostname / hosts / 关无用服务 / 时间同步
HOSTNAME=k8s-cp-1 # 各机器自己的名字
ssh root@$ip "
# 1. hostname
hostnamectl set-hostname $HOSTNAME
# 2. /etc/hosts(全集群一致)
cat > /etc/hosts <<'HOSTS'
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
10.0.24.28 k8s-cp-1 m1
10.0.24.29 k8s-cp-2 m2
10.0.24.30 k8s-cp-3 m3
10.0.24.31 k8s-w-1 m4
10.0.24.32 k8s-w-2 m5
HOSTS
# Ubuntu sudo 要求 hostname 能解析到 127.0.1.1
echo '127.0.1.1 $HOSTNAME' >> /etc/hosts
# 3. 时区
timedatectl set-timezone Asia/Shanghai
# 4. 关无用服务 + 启 chrony
for svc in ModemManager pollinate lxd-agent multipathd iscsid \
unattended-upgrades apport \
apt-daily.timer apt-daily-upgrade.timer; do
systemctl disable --now \$svc 2>/dev/null || true
done
rm -f /etc/cron.daily/apport
systemctl enable --now chrony
chronyc -a makestep
"
必须禁的服务
| 服务 | 为什么必须禁 |
|---|---|
unattended-upgrades | 最重要。自动升级可能在任意时刻装新内核 / kubelet,K8s 不允许 minor 跨版本(1.28 → 1.30 必须经过 1.29),一旦自动升错版本整个集群可能在你睡觉的时候炸。这是 K8s 第一禁忌,所有生产集群必关。 |
multipathd / iscsid | SAN 多路径 / iSCSI 存储用,本地盘场景无用。留着会偶发干扰 device-mapper 设备命名,影响 PV 挂载。 |
apport | Ubuntu crash reporter,每次进程 crash 写 /var/crash/,占盘但没人会把这些 crash 发回 Canonical。 |
lxd-agent pollinate ModemManager | 桌面 / 容器模板留下来的服务,服务器场景毫无意义。 |
lxd-agent.service 在 Ubuntu 模板里默认 enabled —— 别看到 enabled 就以为这是 LXC 容器,要靠 systemd-detect-virt 判定虚拟化类型,systemctl enabled ≠ running。
为什么必须装 chrony
etcd 用 raft 协议,leader 给 follower 发心跳带 timestamp。节点间时差 > electionTimeout(默认 1 秒),follower 会误判 leader 失联触发重新选主。生产环境见过节点时间漂移 5 秒导致 etcd 反复选主、apiserver 几小时不稳定的场景 —— 装机第一天就装好 chrony 不要省。
chronyc -a makestep 是首次硬跳同步(直接把时间调对),而不是慢慢 slew(每秒只调几 ppm)。装机时用 makestep,运行中让 chrony 自己 slew。
为什么不用 systemd-timesyncd:它只支持 SNTP 模式(无 drift 校准)。chrony / ntpd 是 NTP 全协议,实现 PLL/FLL drift 校准,在虚拟化环境(VM 时间易漂移)更稳。
一键验证
5 步做完后跑下面这条命令逐项核对。11 个状态全跑在一次 SSH 连接里,5 台同时验:
for h in m1 m2 m3 m4 m5; do
ssh $h "
echo hostname: \$(hostname)
echo timezone: \$(timedatectl show --property=Timezone --value)
echo chrony: \$(chronyc tracking 2>/dev/null | awk '/Reference/{print \$NF,\$(NF-1)}')
echo sshd-pw: \$(sshd -T | awk '/^passwordauthentication /{print \$2}')
echo sshd-pk: \$(sshd -T | awk '/^pubkeyauthentication /{print \$2}')
echo unatt: \$(systemctl is-enabled unattended-upgrades 2>&1)
echo jq: \$(which jq)
echo conntrack: \$(which conntrack)
echo disk-root: \$(df -h / | awk 'NR==2{print \$4}') free
echo peer: \$(getent hosts k8s-cp-2 | head -c 30)
"
done
正确状态:
sshd-pw: no—— 密码登录关了sshd-pk: yes—— 公钥登录开了unatt: disabled—— 自动升级禁了chrony行有合理的 Reference 服务器(不是 0.0.0.0)peer:行能解析出10.0.24.29 k8s-cp-2 m2(/etc/hosts写对了)
验证脚本要一次连接 + 多输出,不要
for 项目; do ssh; done起 11 个 ssh,又慢又对 sshd 不友好。
getent hosts <name>比dig <name>更适合测/etc/hosts—— getent 走 nsswitch(先 files 后 dns),反映真实解析顺序;dig 只走 DNS。
典型坑速查
| 现象 | 根因 | 修复 |
|---|---|---|
key 推过去了但 ssh 还是 Permission denied | sshd_config 默认 PubkeyAuthentication no(IDC 模板乱改) | 改 sshd_config 和 sshd_config.d/*.conf 两个地方,再 reload |
| 改 sshd 主配置不生效 | /etc/ssh/sshd_config.d/*.conf 里有 override | 两个目录都要看,sshd -t 验证再 reload |
远程 heredoc 用双引号,$(hostname) 被本地 shell 展开 | 双引号内的 $() 会被外层 bash 先 eval | 用单引号 <<'EOF' |
改 hostname 后 sudo 报 unable to resolve | /etc/hosts 没有 127.0.1.1 <hostname> | 补上 127.0.1.1 $(hostname) |
chronyc tracking 报 506 Cannot talk to daemon | chrony 没启动 | systemctl enable --now chrony |
sshpass 并发太多,部分机器返回 Permission denied | sshd 速率限制 MaxStartups 10:30:100 | 错开几秒 / 减少并发 / 改 MaxStartups |
面试常见题
Q1:你接手一台新机器准备装 K8s,前 30 分钟做什么?
四步:
- 探虚拟化和内核 ——
systemd-detect-virt必须是 kvm / vmware 等真虚化(LXC 难装),uname -r看内核 ≥ 4.18(Cilium / br_netfilter 要求) - SSH 免密 + 禁密码 + 禁
unattended-upgrades—— 防自动升级炸集群 - 装 chrony 同步时间 —— etcd raft 对时差敏感
- 改 hostname +
/etc/hosts—— K8s join 用 hostname 解析
深问点:为什么必须禁 unattended-upgrades? —— 避免自动升级 kubelet / containerd 跨 minor 版本,K8s 不支持。
Q2:SSH 公钥配了但登不上,从哪里查?
三层查:
- 客户端:
ssh -v root@ip看协商哪个 key、IdentityFile路径对不对 - 服务端:
sshd -T | grep -i pubkey看PubkeyAuthentication是否 yes - 文件权限:
authorized_keys必须 600,.ssh必须 700,owner 必须 root
实战踩的坑:小 IDC 模板默认 PubkeyAuthentication no,本地 key 拷过去后登不上,排查 30 分钟才发现是服务端默认就关了。
Q3:5 台机器初始化,你怎么并发 + 防 SSH 限流?
- 少量(≤ 10 台):
ssh ... &+wait并发即可 - 多机:Ansible(幂等 + 模块化)+ Mitogen 插件(连接复用加速 5-10×)
- sshd 默认
MaxStartups 10:30:100,大批量要先改这个或错开几秒 - 远程脚本永远用
ssh root@ip 'bash -s' <<'EOF'单引号保护
Q4:为什么生产 K8s 节点必须装 chrony?
etcd 用 raft 协议,leader 给 follower 发心跳带 timestamp,节点之间时差 > electionTimeout(默认 1 秒)会误判 leader 失联触发重新选主。线上见过节点时间漂移 5 秒导致 etcd 反复选主、apiserver 几小时不稳定。
深问:用 systemd-timesyncd 行不行? —— 能用但只能 SNTP 模式(无 drift 校准)。chrony / ntpd 是 NTP 全协议,实现 PLL/FLL drift 校准,在 VM 时间易漂移的环境下更稳。
Q5:装 K8s 之前你不会做哪些事?
- 不装 docker —— K8s 1.24+ 已去 dockershim,装 docker 还要走 cri-dockerd 适配层,白绕。直接装 containerd
- 不启 swap —— K8s 准入条件,kubeadm 1.22+ 默认 fail
- 不开 SELinux enforcing(RHEL 系)—— 要 permissive 或 disabled
- 不在 K8s 节点装监控 agent —— 会跟 cAdvisor 端口冲突 / 重复采集
延伸
vdb那 100G 怎么用 —— 这台机器 IDC 默认把 100G 数据盘挂在/www,Day 1 第一件事就是 remount 到/var/lib/containerd(containerd 的 image 缓存是 K8s 节点最吃盘的部分)。SSH ProxyJump—— 如果以后要给同事访问,加一台 bastion + ProxyJump 配置,5 台机器只开 bastion 的 22 公网,其他全走内网。比 VPN 轻量,生产常用。cloud-init模板化 —— 部分 IDC 支持开机注入 cloud-init,把 Day 0 整套配置做进装机模板,新机 5 分钟内即用。- 替代方案 —— 如果以后要做 immutable infra,看看 Talos OS(K8s 原生 OS,无 systemd,只跑 K8s)。这次走的是「传统 Ubuntu + K8s」的常规姿势,Talos 是另一条线。
下一步
Day 0 结束后这 5 台机器具备:任意机器之间 0 密码 SSH、hostname / /etc/hosts 一致、时间同步、自动升级关闭、基线工具齐全。
Day 1 继续:kubeadm 初始化 3 CP HA + 2 worker,装 Calico / Cilium CNI 并对比,CoreDNS 调优 + node-local-dns。