
记录一次课程实验环境下的 Kubernetes 单 master + 单 node 集群搭建:kubeadm 初始化、flannel 网络、Dashboard、Nginx NodePort 发布,以及一路上踩过的 Docker 版本、镜像格式、DNS 和时间同步的坑。
部署时间:2026-10-22
环境规划
实验指导书的原始规划是 master 192.168.100.60、node 192.168.100.61,实际环境与指导书不一致,本文按真实 IP 记录:
| 角色 | 主机名 | IP | 说明 |
|---|---|---|---|
| master | master | 192.168.100.233 | 控制面、Dashboard |
| node | node | 192.168.100.144 | 工作节点 |
| 网关 / DNS | - | 192.168.100.2 | VMware NAT |
软件版本:
| 组件 | 版本 |
|---|---|
| 操作系统 | CentOS 7 (kernel 3.10.0-1160) |
| Docker | 18.09.6 |
| kubeadm / kubelet / kubectl | 1.14.1 |
| Pod 网段 | 10.16.0.0/16 |
| 网络插件 | flannel v0.11.0-amd64 |
提示:如果你的环境 IP 不同,按同样思路替换即可,关键是两台机器的
/etc/hosts、kubeadm 的--apiserver-advertise-address和 join 命令里的地址保持一致。
1. 基础环境配置(master、node 均执行)
1.1 配置主机名解析
两台机器的 /etc/hosts 保持一致:
vi /etc/hosts127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4::1 localhost localhost.localdomain localhost6 localhost6.localdomain6192.168.100.233 master192.168.100.144 node设置主机名:
# masterhostnamectl set-hostname master
# nodehostnamectl set-hostname node验证:
getent hosts mastergetent hosts nodeping -c 2 master这里踩过一个坑:
/etc/hosts里把node拼成了ndoe,同时没写 master 的条目,导致后面 chrony 一个时间源都没有。hosts 是整套集群的基础,先确认再往下走。
1.2 关闭防火墙、SELinux、swap
systemctl stop firewalldsystemctl disable firewalld
setenforce 0sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
swapoff -ased -i '/ swap / s/^/#/' /etc/fstab1.3 内核参数
cat >> /etc/sysctl.conf << EOFnet.ipv4.ip_forward = 1net.bridge.bridge-nf-call-ip6tables = 1net.bridge.bridge-nf-call-iptables = 1EOF
modprobe br_netfiltersysctl -pbr_netfilter 是 net.bridge.* 这些参数存在的前提;不加载模块直接 sysctl -p 会报 No such file or directory。
1.4 开启 IPVS 相关模块
vi /etc/sysconfig/modules/ipvs.modules#!/bin/bashmodprobe -- ip_vsmodprobe -- ip_vs_rrmodprobe -- ip_vs_wrrmodprobe -- ip_vs_shmodprobe -- nf_conntrack_ipv4chmod 755 /etc/sysconfig/modules/ipvs.modulesbash /etc/sysconfig/modules/ipvs.moduleslsmod | grep -e ip_vs -e nf_conntrack_ipv41.5 时间同步(chrony)
master:
yum -y install chronyvi /etc/chrony.conf删除默认的 4 行 server *.centos.pool.ntp.org,加入:
local stratum 10server master iburstallow allsystemctl start chronydsystemctl enable chronydchronyc sources -vnode:
yum -y install chronyvi /etc/chrony.conf删除默认 4 行 server,加入:
server master iburstsystemctl start chronydsystemctl enable chronydchronyc sources出现 ^* master 即同步成功。若显示 0 个源,先查 getent hosts master 和 journalctl -u chronyd。
2. 准备 YUM 源
课程实验是离线环境,源分两部分:
- CentOS 7 ISO:基础系统包(本文环境用的是
CentOS-7-x86_64-Minimal-2009.iso,包比 DVD 少,缺什么再补) K8S.tar.gz解压出的Kubernetes/目录:kubeadm/kubelet/kubectl、docker-ce 等 RPM
本实验 master 和 node 都使用本地 file:// 源,不使用 FTP。node 上原本没有 K8S 包,需要从 master 把 /opt/K8S 整个复制过去,保持路径一致。
2.1 上传安装介质
先把两个文件通过 SFTP 或 VMware 共享文件夹传到 master 的 /root:
CentOS-7-x86_64-Minimal-2009.isoK8S.tar.gz(课程提供的离线包)
ls -lh /root/CentOS-7-x86_64-Minimal-2009.iso /root/K8S.tar.gzK8S.tar.gz 解压后主要有:
Kubernetes/:kubeadm/kubelet/kubectl、docker-ce 等 RPMimages/:各组件镜像 taryaml/:flannel、Dashboard 等清单kubernetes_base.sh:批量导入镜像的脚本
2.2 master 准备源
cd /root
# base 源:把 ISO 内容放到 /opt/centosmkdir -p /opt/centosmount /dev/cdrom /mntcp -r /mnt/* /opt/centos/
# 离线 RPM:解压 K8S 包mkdir -p /opt/K8Star -zxf K8S.tar.gz -C /opt/K8S配置 repo(master、node 完全相同):
cd /etc/yum.repos.d/rm -f *vi local.repo[centos]name=centosbaseurl=file:///opt/centosgpgcheck=0
[K8S]name=K8Sbaseurl=file:///opt/K8S/Kubernetesgpgcheck=0yum clean allyum makecache2.3 node 同步源目录
# master → node,保持 /opt/K8S 路径一致scp -r /opt/K8S root@192.168.100.144:/opt/
# 基础源目录同样需要scp -r /opt/centos root@192.168.100.144:/opt/若 node 上已存在
/opt/K8S,先把旧目录移走,避免 scp 嵌套成/opt/K8S/K8S。
然后在 node 上写一份和 master 完全相同的 local.repo:
cd /etc/yum.repos.d/rm -f *vi local.repo[centos]name=centosbaseurl=file:///opt/centosgpgcheck=0
[K8S]name=K8Sbaseurl=file:///opt/K8S/Kubernetesgpgcheck=0yum clean allyum makecache不想把 ISO 内容复制到 node 时,才需要 master 开 FTP/HTTP 共享,让 node 的
[centos]指向ftp://192.168.100.233/centos;本实验两台机器都用本地源,最省事。
3. 安装 Docker 18.09.6
Docker 的 RPM 已经包含在上一节的 [K8S] 离线源里,不需要额外添加 Docker 官方源:
yum -y install docker-ceyum 会自动解析依赖,装上 docker-ce、docker-ce-cli、containerd.io 三个包。离线源里只有 18.09.6,所以不会装到新版本。
为什么不装最新版?kubelet 1.14 官方验证的是 Docker 18.09,而且 flannel 等 2018 年的老镜像是 Docker schema1 格式,Docker 26 默认拒绝使用,会直接导致节点 NotReady。若额外启用了 Docker 官方源,
yum install docker-ce就会装到 26.x,务必避免。
配置 daemon.json:
mkdir -p /etc/dockervi /etc/docker/daemon.json{ "exec-opts": ["native.cgroupdriver=systemd"]}Docker Hub 直连被 DNS 污染的环境,建议同时配置镜像加速器(按需保留):
{ "exec-opts": ["native.cgroupdriver=systemd"], "registry-mirrors": ["https://docker.m.daocloud.io"]}systemctl daemon-reloadsystemctl start dockersystemctl enable docker
docker info | grep -E "Server Version|Cgroup"应看到 Server Version: 18.09.6、Cgroup Driver: systemd。kubelet 和 Docker 的 cgroup 驱动必须一致。
4. 安装 kubeadm、kubelet、kubectl(1.14.1)
master 和 node 都执行:
yum -y install kubelet-1.14.1 kubeadm-1.14.1 kubectl-1.14.1
systemctl enable kubeletsystemctl start kubelet初始化完成前 kubelet 会反复重启,这是正常现象,不用管。
5. 加载离线镜像
第 2 节已经把 K8S 包解压到 /opt/K8S,1.14.1 所需的镜像都在 /opt/K8S/images。master 上:
cd /opt/K8S./kubernetes_base.sh脚本会批量 docker load 镜像目录中的 tar 包。确认:
docker images | grep -E "kube-|etcd|flannel|coredns|pause"6. kubeadm 初始化 master
kubeadm init \ --kubernetes-version="v1.14.1" \ --image-repository=registry.aliyuncs.com/google_containers \ --apiserver-advertise-address=192.168.100.233 \ --pod-network-cidr=10.16.0.0/16参数说明:
--apiserver-advertise-address:必须是 master 的真实 IP(不是指导书里的 .60)--pod-network-cidr:必须与 flannel 的Network配置一致--image-repository:与离线镜像的名称前缀对应
初始化成功后按提示配置 kubectl:
mkdir -p $HOME/.kubecp -i /etc/kubernetes/admin.conf $HOME/.kube/configchown $(id -u):$(id -g) $HOME/.kube/config检查控制面:
kubectl get cskubectl get nodes7. 部署 flannel 网络
cd /opt/K8S/yamlkubectl apply -f kube-flannel.yaml
kubectl get pods -n kube-system -o widemaster 上的 kube-flannel-ds-amd64-xxx Running 后,控制面节点会先变为 Ready。
8. node 加入集群
8.1 在 node 上准备离线镜像
第 2 节已经把 /opt/K8S 同步到了 node,镜像就在 /opt/K8S/images,直接运行加载脚本:
cd /opt/K8S./kubernetes_base.shdocker images | grep -E "kube-|etcd|flannel|coredns|pause"若镜像目录或脚本不完整,至少确保 pause、kube-proxy、flannel 镜像存在,可用 docker save / docker load 从 master 补齐。
8.2 执行加入命令
在 master 上生成 join 命令(token 默认 24 小时过期,过期可重新生成):
kubeadm token create --print-join-command在 node 上执行打印出的命令,例如:
kubeadm join 192.168.100.233:6443 --token xxxxxx.xxxxxxxxxxxxxxxx \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx成功标志:
This node has joined the cluster8.3 踩坑:node 一直 NotReady,flannel 镜像 ImagePullBackOff
现象:
kube-flannel-ds-amd64-xxxxx 0/1 Init:ImagePullBackOff查事件:
kubectl describe pod -n kube-system kube-flannel-ds-amd64-xxxxx | tail -20如果看到:
Failed to pull image "quay.io/coreos/flannel:v0.11.0-amd64":[DEPRECATION NOTICE] Docker Image Format v1 and Docker Image manifest version 2,schema 1 support is disabled by default ...原因就找到了:这个 flannel 镜像是 Docker schema1 格式,node 上的 Docker 26 拒绝使用。解法:
- 把 node 的 Docker 降级到 18.09.6(与 master 一致);
- 在 node 上确保镜像存在;
- 删掉 flannel Pod 触发重建。
按本实验离线源正常安装的话,node 的 Docker 本来就会是 18.09.6,不会出现这个问题;这里是环境中混入了外来的 Docker 26。
降级 Docker(node):
systemctl stop kubeletsystemctl stop docker
yum remove -y docker-ce docker-ce-cli containerd.io docker-ce-rootless-extras \ docker-buildx-plugin docker-compose-plugin
# containerd 1.6 的元数据 1.2 读不了,挪走旧状态mv /var/lib/containerd /var/lib/containerd.bak
yum install -y docker-ce-18.09.6 docker-ce-cli-18.09.6 containerd.io-1.2.6-3.3.el7daemon.json 保持 cgroup systemd,然后启动 Docker 和 kubelet:
systemctl daemon-reloadsystemctl start dockersystemctl enable dockerdocker info | grep -E "Server Version|Cgroup"
systemctl start kubeletsystemctl status kubelet --no-pager加载 flannel 镜像(node):
# 若 node 上没有镜像包,从 master 导出再传过去# master:docker save -o /root/flannel.tar quay.io/coreos/flannel:v0.11.0-amd64scp /root/flannel.tar root@192.168.100.144:/root/
# node:docker load -i /root/flannel.tardocker images | grep flannel触发重建:
kubectl delete pod -n kube-system kube-flannel-ds-amd64-xxxxxkubectl get pods -n kube-system -o widekubectl get nodes两个节点都 Ready 后:
NAME STATUS ROLES AGE VERSIONmaster Ready master ... v1.14.1node Ready <none> ... v1.14.19. 部署 Dashboard
cd /opt/K8S/yamlkubectl create -f kubernetes-dashboard.yamlkubectl create -f dashboard-adminuser.yaml检查:
kubectl get pods -n kube-system -o widekubectl get svc -n kube-system服务类型为 NodePort:
kubernetes-dashboard NodePort 10.102.208.171 <none> 443:30000/TCP本次环境中 Dashboard Pod 调度到了 master,所以访问:
https://192.168.100.233:30000不是 http://,也不要只认 node 的 IP(Pod 在哪个节点,用哪个节点 IP 最稳;NodePort 正常时任一节点 IP 都可以)。
浏览器提示证书不受信任,选择”高级 → 继续前往”。
获取登录 token:
kubectl -n kube-system get secret | grep adminkubectl -n kube-system describe secret \ $(kubectl -n kube-system get secret | grep admin | awk '{print $1}')复制 token: 后的字符串登录。
10. 部署 Nginx 示例应用
node 上准备镜像(离线包或镜像加速器):
docker load -i /root/nginx_latest.tardocker images | grep nginx
# 或走加速器docker pull docker.m.daocloud.io/library/nginx:latestdocker tag docker.m.daocloud.io/library/nginx:latest nginx:latestmaster 上创建 Deployment:
kubectl create deployment nginx --image=nginxkubectl get pods -o widekubectl get deployment
--image=nginx等价于nginx:latest,默认拉取策略是Always,kubelet 仍会尝试拉镜像。离线环境如果拉取失败,把策略改成IfNotPresent:Terminal window kubectl patch deployment nginx -p \'{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx","imagePullPolicy":"IfNotPresent"}]}}}}'
发布服务:
kubectl expose deployment nginx --port=80 --type=NodePortkubectl get svc nginx输出中的 80:3xxxx/TCP 即 NodePort:
NAME TYPE CLUSTER-IP PORT(S) AGEnginx NodePort 10.100.233.224 80:31507/TCP 19s浏览器访问:
http://192.168.100.144:31507看到 “Welcome to nginx!” 即成功。
扩容:
kubectl scale deployment nginx --replicas=3kubectl get pods -o wide清理:
kubectl delete deployment nginxkubectl delete svc nginx11. 踩坑速查表
| 问题 | 原因 | 解决 |
|---|---|---|
docker pull 超时,解析到奇怪 IP | Docker Hub 域名被 DNS 污染 | 配置 registry-mirrors,或用镜像站显式拉取后 docker tag |
network 启动失败,连续 RTNETLINK answers: File exists | 残留的 br0 设备 / 重复的地址或网关配置 | 清理残留网桥和重复 ifcfg,用 network 服务而非 nmcli |
chronyc sources 显示 0 个源 | /etc/hosts 缺 master、主机名拼错 | 修正 hosts,确保 getent hosts master 有返回 |
node NotReady,flannel Init:ImagePullBackOff | flannel 镜像是 schema1,Docker 26 默认拒绝 | node Docker 降级到 18.09.6,加载镜像后重建 Pod |
| Dashboard 打不开 | 用了 http:// 或访问了没有 Pod 的节点 | 用 https://,优先访问 Dashboard Pod 所在节点的 IP |
| 配了加速器仍走 Docker Hub | daemon.json 未加载或镜像源失效 | docker info 确认 Registry Mirrors,换可用源 |
| node 加入报 token 无效 | token 默认 24 小时过期 | master 上 kubeadm token create --print-join-command |
| Nginx Pod 一直 ImagePullBackOff | nginx:latest 默认 Always 拉取,离线拉不到 | 加载镜像并改 imagePullPolicy: IfNotPresent |
结语
这套环境的核心难点不在 kubeadm 本身,而在老版本组件的兼容性和离线/受限网络:
- 版本组合要对:kubelet 1.14.1 + Docker 18.09.6;
- 老镜像格式要认:schema1 镜像在 Docker 26 上不可用;
- 网络细节要通:hosts、chrony、flannel、NodePort 任一环出问题都会表现为集群异常。
集群跑起来之后,先给两台虚拟机打快照,再继续后面的 OpenShift 等实验,能省掉大量返工。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
最后更新于 ,距今已过 0 天
部分内容可能已过时