Linux-Tutorial 中的 Kubernetes 集群部署实战:基于 kubespray + Ansible 的自动化安装与排错
Linux-Tutorial 中的 Kubernetes 集群部署实战:基于 kubespray + Ansible 的自动化安装与排错
本篇指南聚焦于《Java 程序员眼中的 Linux》开源仓库(Linux-Tutorial)中记载的 Kubernetes 集群部署方案,以 kubespray(kubeadm 的 Ansible 自动化封装)为核心工具,完整还原一套 1 Master + 2 Worker 集群从环境准备、镜像源替换、Inventory 编写到 Playbook 执行的全过程,并深入剖析部署中两个最具代表性的故障(镜像拉取失败、脚本 CRLF 换行符错误)的定位与修复思路。读完本篇,你将掌握一套不依赖海外网络的 Kubernetes 集群自动化搭建流程,并具备独立排查同类部署问题的实战能力。
一、方案选型与集群环境规划
1.1 为什么选择 kubespray
在容器编排领域,Kubernetes(简称 K8S)是当前最流行的方案。仓库内另一篇文档 K8S-Install-And-Usage.md 对 K8S 的核心能力做了总结:调度、生命周期及健康状况、服务发现、监控、认证、容器聚合,主要角色分为 Master 与 Node。K8S 官方也提供了 kubeadm 手工引导工具(该文档即为 kubeadm 1.13 路线的完整记录)。
而 kubespray 则是在 kubeadm 之上的更高一层抽象:它把 K8S 集群的所有安装、配置、证书签发、容器镜像拉取等步骤全部封装成标准化的 Ansible Playbook。这意味着你只需维护一份 inventory(主机清单)与若干 group_vars 变量文件,即可在任意数量、任意角色的节点上可重复地批量部署集群。与 kubeadm 手工逐节点操作相比,kubespray 的优势在于:
- 声明式配置:集群拓扑(master / worker / etcd)通过 inventory 分组声明,一处修改、全量生效;
- 幂等执行:Ansible 任务可重复执行,失败后修复问题再次运行即可继续;
- 版本可控:通过 checkout 固定版本 tag,保证部署结果可复现。
1.2 部署环境与硬件规划
原文记载的部署环境为:
- 系统:CentOS 7.4(64 位)
- 服务器:阿里云 ECS,华南地区(国内节点)
- 前提:所有机器均为干净系统,不需要预先安装 Docker 等容器相关软件(kubespray 会自行安装)
- 建议:若条件允许优先选择海外服务器,可省去大量镜像源替换带来的网络问题
| 机器简称 | 内网 IP 地址 | 部署软件 | 系统环境 | 硬件配置 |
|---|---|---|---|---|
| 操作机 | 172.20.229.224 | ansible | CentOS 7.4 | 1 vCPU + 2 GB |
| node1 | 172.20.229.225 | master | CentOS 7.4 | 1 vCPU + 4 GB |
| node2 | 172.20.229.226 | worker | CentOS 7.4 | 1 vCPU + 4 GB |
| node3 | 172.20.229.227 | worker | CentOS 7.4 | 1 vCPU + 4 GB |
从仓库内 kubeadm 方案(K8S-Install-And-Usage.md)的硬件建议"推荐最低 2C2G,优先 2C4G 或以上"可以看出,Master 节点承载 apiserver、etcd、scheduler、controller-manager 等核心组件,Worker 节点承载业务 Pod,生产环境应在此基础上适当提升配置。上述规划中操作机仅作控制端(Ansible 控制节点),不参与集群数据面。
二、所有节点的统一环境准备
无论节点后续扮演 master、worker 还是 etcd 角色,集群对基础系统环境有一致性要求。原文给出的准备步骤如下。
2.1 关闭 SELinux
SELinux 的强制访问控制策略在默认配置下会干扰 kubelet、容器运行时等组件的文件访问与端口绑定,部署前必须关闭:
- 编辑配置文件:
vim /etc/selinux/config - 将
SELINUX=enforcing修改为SELINUX=disabled
注意:修改后需重启或使用
setenforce 0临时生效。K8S-Install-And-Usage.md 中 kubeadm 方案采用了一行命令完成:setenforce 0 && sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config,两条路线思路一致,可互为印证。
2.2 关闭防火墙
所有节点统一执行:
systemctl stop firewalld && systemctl disable firewalld
Kubernetes 集群内部组件之间(如 kubelet 与 apiserver、etcd 集群、CNI 网络)需要大量端口通信,直接关闭 firewalld 可避免端口放行遗漏导致的部署失败。仓库中的阿里云 Docker/K8S 安装脚本 install_docker_k8s_disable_firewalld_centos7-aliyun.sh 同样以 systemctl stop firewalld.service && systemctl disable firewalld.service 作为环境准备的第一步,说明这是该仓库作者在 K8S 相关部署中的标准前置动作。
2.3 安装常用组件(非必须)
yum install -y vim zsh zip unzip lrzsz git
其中 lrzsz 提供 rz / sz 命令,便于后续将本地压缩后的 kubespray 项目上传到操作机;zip / unzip 用于项目打包与解压。
三、操作机(Ansible 控制节点)准备
3.1 安装 Ansible 及其依赖
在操作机(172.20.229.224)上执行:
yum install -y epel-release
yum install -y python34 python34-pip python-pip python-netaddr ansible git
pip install --upgrade Jinja2
yum install -y gcc libffi-devel python-devel openssl-devel
各依赖的用途说明:
epel-release:提供 EPEL 扩展软件源,Ansible、pip 等软件包依赖它;python34 / python34-pip / python-pip:Python 运行环境与包管理器(kubespray 后续的 inventory 生成脚本需要 Python3);python-netaddr:kubespray 的 inventory 生成脚本 依赖它计算网络地址段(IP 网段、网关等);ansible:Playbook 执行引擎;gcc / libffi-devel / python-devel / openssl-devel:编译 Python 原生扩展(如 cryptography 等)所需,pip 升级 Jinja2 及其依赖时可能需要现场编译。
延伸:Ansible 本身的使用方式与 inventory 分组概念可参考仓库文档 Ansible-Install-And-Settings.md,其中解释了 Ansible 配置文件的查找顺序(
ANSIBLE_CONFIG环境变量 → 当前目录ansible.cfg→ 家目录~/.ansible.cfg→/etc/ansible/ansible.cfg),以及 Inventory 分组写法(如[hadoop-host]定义一组主机)——kubespray 的hosts.ini正是这种分组语法的进阶应用。
3.2 操作机到各节点的免密登录
Ansible 默认通过 SSH 通道执行任务,为避免每个任务都提示输入密码,需要将操作机的公钥分发到所有目标节点:
ssh-keygen
ssh-copy-id -i /root/.ssh/id_rsa.pub -p 22 root@172.20.229.225
ssh-copy-id -i /root/.ssh/id_rsa.pub -p 22 root@172.20.229.226
ssh-copy-id -i /root/.ssh/id_rsa.pub -p 22 root@172.20.229.227
验证是否可免密登录:
ssh -p 22 root@172.20.229.225
ssh -p 22 root@172.20.229.226
ssh -p 22 root@172.20.229.227
延伸:SSH 免密登录的完整原理与 pem 登录场景下的替代方案(手动追加公钥到
authorized_keys并设置chmod 600)详见仓库文档 SSH-login-without-password.md。核心要点是:谁的公钥被目标机器信任,谁就能免密登录;公钥存放于/root/.ssh/id_rsa.pub,目标机的授权文件为/root/.ssh/authorized_keys。
四、kubespray 项目获取与镜像源替换
4.1 获取并固定版本
在本地机器上克隆 kubespray 项目:
git clone https://github.com/kubernetes-incubator/kubespray.git
按原文记载,当时(2018-02)最新版本为 v2.4.0,因此 checkout 该 tag:
git checkout v2.4.0
说明:固定版本 tag 是保证集群可复现的关键操作——kubespray 不同版本对应不同 Kubernetes 小版本与默认镜像版本,混用版本会导致 Playbook 与镜像清单不一致。
4.2 批量替换容器镜像地址(国内网络关键一步)
kubespray 默认从 Google 容器镜像仓库 gcr.io(https://cloud.google.com/container-registry/)拉取集群组件镜像。国内网络环境无法访问该地址,因此需要批量替换为阿里云镜像仓库:
- 在 IntelliJ IDEA 中打开该项目,全局批量替换:
gcr.io/→registry.cn-hangzhou.aliyuncs.com/ - 注意:gcr.io 下的镜像阿里云不一定全部同步,稳妥做法是先到阿里云镜像搜索页或 Docker Hub 核实所需镜像是否存在,再决定替换目标。
五、Inventory 编写与集群角色划分
5.1 基于 sample 生成自定义 inventory
将 kubespray 自带的示例 inventory 复制为自定义目录(kubespray 约定非 git 管理的自定义目录):
cd /opt/kubespray && cp -rfp inventory/sample inventory/mycluster
5.2 自动生成主机清单
kubespray 提供了 inventory 生成工具 contrib/inventory_builder/inventory.py,传入各节点 IP 即可自动生成初始清单(含网段、网关等网络变量):
python3 contrib/inventory_builder/inventory.py 172.20.229.225 172.20.229.226 172.20.229.227
然后检查两个核心变量文件(kubespray 的全部可调参数都集中在 group_vars 下):
cat inventory/mycluster/group_vars/all.yml
cat inventory/mycluster/group_vars/k8s-cluster.yml
5.3 手工修订 hosts.ini
修改 /opt/kubespray/inventory/mycluster/hosts.ini 为最终形态:
node1 ansible_ssh_host=172.20.229.225 ansible_user=root ip=172.20.229.225
node2 ansible_ssh_host=172.20.229.226 ansible_user=root ip=172.20.229.226
node3 ansible_ssh_host=172.20.229.227 ansible_user=root ip=172.20.229.227
[kube-master]
node1
[etcd]
node1
[kube-node]
node2
node3
[k8s-cluster:children]
kube-node
kube-master
逐行解读这份 Inventory:
- 每行主机定义包含四个关键变量:
ansible_ssh_host(SSH 连接地址)、ansible_user(SSH 登录用户)、ip(节点内网 IP,用于 Kubernetes 网络插件与 API Server 证书 SAN)、主机名(node1/node2/node3); [kube-master]:承载控制平面的节点组(apiserver、controller-manager、scheduler),本案例为 node1;[etcd]:etcd 集群节点组,本案例将 etcd 与 master 同机部署(单节点 etcd,适合测试/小规模集群);[kube-node]:承载业务负载的 Worker 节点组,本案例为 node2、node3;[k8s-cluster:children]:children关键字将 kube-node 与 kube-master 合并为父组k8s-cluster,Playbook 中面向整个集群的角色(如 kubelet、CNI 插件)通过该父组生效。
5.4 上传项目到操作机
将修改完成的项目在本地压缩为 zip,上传至操作机 /opt 目录后解压:
# 本地执行(需已安装 zip)
zip -r kubespray.zip kubespray
# 操作机执行
cd /opt && unzip kubespray.zip
提示:此步骤使用
rz/sz(前面安装的 lrzsz 包)即可在终端直接完成传输。
六、执行集群部署
在操作机上运行 kubespray 的主 Playbook:
cd /opt/kubespray && ansible-playbook -i inventory/mycluster/hosts.ini cluster.yml
cluster.yml 是 kubespray 的入口 Playbook,其内部串联了 Docker 安装、etcd 部署、证书签发(make-ssl 系列脚本)、控制平面组件(static Pod 方式)、Worker 节点 kubelet、CNI 网络插件安装等全部任务。执行期间终端会滚动输出大量任务结果,全程预计需要 20~60 分钟(视节点数量与网络状况而定)。
延伸:这一步的本质是"用 Ansible 驱动 kubeadm 思路的自动化实现"。对比仓库中 kubeadm 手工方案(K8S-Install-And-Usage.md)可以看到,手工方案需要逐节点完成设置主机名、配置
/etc/hosts、同步时间、禁用 swap、配置 yum 源、安装 kubelet/kubeadm/kubectl、kubeadm init、安装 flannel、kubeadm join等十余步操作;而 kubespray 将这些步骤全部收敛为一个命令,这正是自动化部署的价值所在。
七、实战排错一:容器镜像拉取失败(Image Pull 404)
7.1 故障现象
Playbook 执行到镜像下载阶段反复重试后失败,日志形如:
FAILED - RETRYING: container_download | Download containers if pull is required or told to always pull (all nodes) (4 retries left).
...
fatal: [node1]: FAILED! => {"attempts": 4, "changed": true,
"cmd": ["/usr/bin/docker", "pull", "registry.cn-hangzhou.aliyuncs.com/google_containers/cluster-proportional-autoscaler-amd64:1.1.2"],
"msg": "non-zero return code", "rc": 1,
"stderr": "Error response from daemon: repository registry.cn-hangzhou.aliyuncs.com/google_containers/cluster-proportional-autoscaler-amd64 not found: does not exist or no pull access"}
node1、node2、node3 三个节点同时失败。
7.2 原因分析
从 cmd 字段可以清晰看到,kubespray 正在执行的命令是 docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/cluster-proportional-autoscaler-amd64:1.1.2。也就是说:虽然 4.2 节把 gcr.io/ 批量替换成了阿里云镜像仓库前缀,但阿里云镜像仓库中并不存在 cluster-proportional-autoscaler-amd64 这个镜像(返回 404 / no pull access)。这印证了 4.2 节强调的"官网镜像阿里云不一定全有"的风险。
7.3 解决方案
- 到 Docker Hub 搜索该镜像,找到了社区维护的同名镜像
mirrorgooglecontainers/cluster-proportional-autoscaler-amd64(当时该用户位于北京,镜像同步及时); - 先在任意装有 Docker 的服务器上验证镜像可用:
docker pull mirrorgooglecontainers/cluster-proportional-autoscaler-amd64
- 验证通过后,将 kubespray 配置中该镜像的仓库前缀由阿里云地址替换为
mirrorgooglecontainers/,重新执行 Playbook。
经验总结:国内环境替换 gcr.io 前缀时,不能盲目相信任何单一镜像仓库的完整同步度。对于缺失的镜像,可先
docker pull验证目标仓库确实存在该镜像(含 tag),再写入配置;也可以结合离线镜像包方案(见第九节)彻底规避网络问题。
八、实战排错二:etcd 证书脚本 CRLF 换行符错误
8.1 故障现象
镜像问题解决后再次执行,etcd 角色报错:
fatal: [node1 -> 172.20.229.225]: FAILED! => {"changed": true,
"cmd": ["bash", "-x", "/usr/local/bin/etcd-scripts/make-ssl-etcd.sh", "-f", "/etc/ssl/etcd/openssl.conf", "-d", "/etc/ssl/etcd/ssl"],
"stderr": "+ $'\\r'\n/usr/local/bin/etcd-scripts/make-ssl-etcd.sh: line 2: $'\\r': command not found
+ set -o $'errexit\\r'\n/usr/local/bin/etcd-scripts/make-ssl-etcd.sh: line 17: set: errexit\r: invalid option name
...
syntax error near unexpected token `$'\\r''"}
8.2 原因分析
错误信息中的 $'\r'、set: errexit\r: invalid option name 是典型的 CRLF 换行符问题:脚本文件 make-ssl-etcd.sh 使用 Windows 的 \r\n 行尾(很可能是在 Windows 下用 IntelliJ IDEA 编辑后随 zip 包上传,或项目压缩/解压过程引入了 CRLF),而 Linux 的 bash 只识别 \n,导致每一行末尾都多出一个 \r 字符,脚本解析彻底失败。
原文明确指出该脚本来源:/opt/kubespray/roles/etcd/files 目录(kubespray 的 etcd 角色自带文件)。同类风险还包括 kubernetes 角色的证书签发脚本 make-ssl.sh。
8.3 解决方案
在操作机上安装换行符转换工具,并对涉及的两个脚本执行转换:
yum install -y dos2unix unix2dos
cd /opt/kubespray/roles/etcd/files && dos2unix make-ssl-etcd.sh
cd /opt/kubespray/roles/kubernetes/secrets/files && dos2unix make-ssl.sh
转换完成后重新执行 ansible-playbook 即可继续。注意 dos2unix 是就地转换,转换前建议确认目标文件路径与 cluster.yml 中实际调用的路径一致(错误信息中的 /usr/local/bin/etcd-scripts/make-ssl-etcd.sh 是脚本被分发到节点后的落地路径,源文件则在操作机的 roles 目录下)。
经验总结:凡是"从 Windows 编辑/打包后上传到 Linux 执行"的 shell 脚本,都应先做
dos2unix处理。这是跨平台协作部署中最容易忽略、也最容易造成"莫名奇妙"报错的一类问题。
九、部署验证
SSH 连上 master 节点:
ssh root@172.20.229.225
执行:
kubectl get nodes
预期输出:
NAME STATUS AGE VERSION
node1 Ready,SchedulingDisabled 1m v1.6.1+coreos.0
node2 Ready 1m v1.6.1+coreos.0
node3 Ready 1m v1.6.1+coreos.0
结果解读:
- 三个节点均显示
Ready,说明 kubelet 已成功注册且健康检查通过; - node1 状态为
Ready,SchedulingDisabled,这是 master 节点默认行为:控制平面节点被打了污点(taint),不允许普通业务 Pod 调度上去,从而保证集群资源优先供给系统组件; - 版本号为
v1.6.1+coreos.0,说明该 kubespray 版本选用的是 CoreOS 维护的 Kubernetes 构建。
延伸:如果节点状态异常(NotReady / Pending / ImagePullBackOff),可参考仓库内 kubeadm 方案文档 K8S-Install-And-Usage.md 的排查手法:
kubectl get pods --all-namespaces查看未就绪 Pod,kubectl describe pod <Pod Name> --namespace=kube-system或kubectl logs <Pod Name> -n kube-system查看具体原因,必要时kubeadm reset重置后重新加入。
十、附录:离线镜像包与主机名设置
针对国内网络环境的另一个备选方案是离线镜像包。原文记载的操作如下。
10.1 解压离线镜像包
安装 bzip2 后解压镜像归档(tar.bz2 格式):
yum install -y bzip2
tar -xjvf k8s_images.tar.bz2
随后将包内镜像逐一导入节点 Docker(docker load),可完全绕开公网镜像仓库。该方案适合有离线环境要求的集群,与 7.3 节的"逐镜像验证替换"形成互补。
10.2 设置主机名与 hosts 解析
为各节点设置规范的主机名(便于 kubectl 输出可读性):
hostnamectl --static set-hostname k8s-master
hostnamectl --static set-hostname k8s-node-1
并在 master 与各 node 的 /etc/hosts 中统一加入主机名解析:
172.18.218.96 k8s-master
172.18.218.96 etcd
172.18.218.96 registry
172.18.218.97 k8s-node-1
这里的主机名规划与 K8S-Install-And-Usage.md 中 kubeadm 方案的 k8s-master-1 / k8s-node-1 / k8s-node-2 命名风格一致。统一的主机名与 hosts 映射是 etcd 集群通信、节点间互访的基础,注意生产环境应将 etcd、registry 等解析到各自独立 IP 而非与 master 复用。
十一、方案对比与适用场景小结
| 维度 | kubespray(本文方案) | kubeadm 手工方案(K8S-Install-And-Usage.md) |
|---|---|---|
| 操作入口 | 单个 ansible-playbook 命令 |
逐节点十余步手工命令 |
| 集群规模适配 | 适合 3 节点以上、可扩展 | 适合小型集群与学习验证 |
| 镜像源 | 需替换 gcr.io 前缀并逐镜像核验 | 1.13 起可用 --image-repository 参数指定 |
| 网络插件 | Playbook 内置 | 需手动 kubectl apply -f kube-flannel.yml |
| 排错成本 | 集中在 inventory 与镜像/脚本三类问题 | 分散在各步骤 |
从仓库的整体定位(《Java 程序员眼中的 Linux》,README 与 SUMMARY.md 收录了从 Linux 基础、Docker(Docker-Install-And-Usage.md)到 Ansible(Ansible-Install-And-Settings.md)的完整技术栈)来看,本方案是"自动化运维 + 容器编排"两条知识线的交汇点:既要会用 Ansible 管理 inventory 与 Playbook,也要理解 Kubernetes 各角色节点的底层组件分工。本文档所在仓库目录为 markdown-file/Kubernetes-Install-And-Usage.md,配套的 Docker/K8S 阿里云环境准备脚本见 favorite-file/shell/install_docker_k8s_disable_firewalld_centos7-aliyun.sh,可作为部署前的系统环境参考。
部署要点速查:所有节点关 SELinux + 关防火墙 → 操作机装 Ansible 并分发免密 → 本地替换 gcr.io 镜像前缀(逐镜像核验)→ 生成并修订 hosts.ini(master/etcd/kube-node 分组)→ ansible-playbook -i inventory/mycluster/hosts.ini cluster.yml → 按需 dos2unix 修复脚本换行符 → kubectl get nodes 验证 Ready。遇到镜像 404 换 Docker Hub 镜像源,遇到 $'\r' 报错做换行符转换,这两条排错经验可复用于绝大多数 kubespray 部署场景。