Linux-Tutorial 中的 Kubernetes 集群部署实战:基于 kubespray + Ansible 的自动化安装与排错

原创2026-10-05 12:59:211,901 阅读
文章标签:文档教程技术博客

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、容器运行时等组件的文件访问与端口绑定,部署前必须关闭:

  1. 编辑配置文件:vim /etc/selinux/config
  2. 将 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 解决方案

  1. 到 Docker Hub 搜索该镜像,找到了社区维护的同名镜像 mirrorgooglecontainers/cluster-proportional-autoscaler-amd64(当时该用户位于北京,镜像同步及时);
  2. 先在任意装有 Docker 的服务器上验证镜像可用:
docker pull mirrorgooglecontainers/cluster-proportional-autoscaler-amd64
  1. 验证通过后,将 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 部署场景。

登录后查看全文
Linux-Tutorial