ansible一键搭建k8s-1.28-openEuler24.03
先换源(可选)
为什么我会换源,因为我在白天的时候,默认的源是很快的,但是一到晚上很慢,所以对于网路有问题的可以先尝试换源
mv /etc/yum.repos.d/openEuler.repo /etc/yum.repos.d/openEuler.repo.bak
vim /etc/yum.repos.d/openEuler.repo
#generic-repos is licensed under the Mulan PSL v2.
#You can use this software according to the terms and conditions of the Mulan PSL v2.
#You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
#THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
#PURPOSE.
#See the Mulan PSL v2 for more details.
[OS]
name=OS
baseurl=https://mirrors.163.com/openeuler/openEuler-24.03-LTS/OS/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.163.com/openeuler/openEuler-24.03-LTS/OS/$basearch/RPM-GPG-KEY-openEuler
[everything]
name=everything
baseurl=https://mirrors.163.com/openeuler/openEuler-24.03-LTS/everything/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.163.com/openeuler/openEuler-24.03-LTS/everything/$basearch/RPM-GPG-KEY-openEuler
[EPOL]
name=EPOL
baseurl=https://mirrors.163.com/openeuler/openEuler-24.03-LTS/EPOL/main/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.163.com/openeuler/openEuler-24.03-LTS/OS/$basearch/RPM-GPG-KEY-openEuler
[debuginfo]
name=debuginfo
baseurl=https://mirrors.163.com/openeuler/openEuler-24.03-LTS/debuginfo/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.163.com/openeuler/openEuler-24.03-LTS/debuginfo/$basearch/RPM-GPG-KEY-openEuler
[source]
name=source
baseurl=https://mirrors.163.com/openeuler/openEuler-24.03-LTS/source/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.163.com/openeuler/openEuler-24.03-LTS/source/RPM-GPG-KEY-openEuler
[update]
name=update
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-24.03-LTS/update/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-24.03-LTS/update/$basearch/RPM-GPG-KEY-openEuler
dnf clean all
dnf makecache
ansible搭建
三台 openEuler 24.03 虚拟机。
这个过程我们会分为三大块:
- 环境准备:在所有三台机器上执行一些必要的初始化操作。
- Ansible 安装与配置:在控制节点(我们选
k8s-master)上安装 Ansible,并配置免密登录到其他节点。 - Ansible 连通性验证:编写并执行一个简单的命令,测试 Ansible 是否能成功管理所有节点。
第一步:环境准备(所有三台机器都要执行)
这一步的目标是确保所有节点的基础环境一致,并且网络互通。请分别在 192.168.48.161、192.168.48.162、192.168.48.163 上执行以下命令。
1.1 关闭防火墙和 SELinux
这是为了避免网络和权限问题干扰 Ansible 和后续 K8s 的部署。
# 关闭并禁用 firewalld
sudo systemctl stop firewalld
sudo systemctl disable firewalld
# 临时关闭 SELinux
sudo setenforce 0
# 永久关闭 SELinux(需要重启生效)
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config
1.2 配置主机名和 /etc/hosts
为了方便机器之间通过名字访问,我们需要设置主机名并配置本地 DNS。
在 192.168.48.161 上执行:
# 设置主机名为 k8s-master
sudo hostnamectl set-hostname k8s-master
在 192.168.48.162 上执行:
# 设置主机名为 k8s-node1
sudo hostnamectl set-hostname k8s-node1
在 192.168.48.163 上执行:
# 设置主机名为 k8s-node2
sudo hostnamectl set-hostname k8s-node2
在所有三台机器上都执行:
# 编辑 /etc/hosts 文件
sudo vim /etc/hosts
# 在文件末尾添加以下内容,保存并退出
192.168.48.161 k8s-master
192.168.48.162 k8s-node1
192.168.48.163 k8s-node2
验证:在任意一台机器上 ping k8s-master、ping k8s-node1,看是否能通。
1.3 安装 Python
Ansible 是用 Python 写的,所以所有被管理节点都需要安装 Python。
# openEuler 24.03 默认可能没有安装 python3-pip
sudo dnf install -y python3 python3-pip
第二步:部署 Ansible(仅在控制节点 k8s-master 上执行)
控制节点是我们运行 Ansible 命令的机器,我们选择 k8s-master(192.168.48.161)作为控制节点。
2.1 安装 Ansible
# 更新系统并安装 Ansible
sudo dnf update -y
sudo dnf install -y ansible
2.2 配置 Ansible 控制节点免密登录所有节点
Ansible 默认使用 SSH 连接到各个节点。为了避免每次执行命令都输入密码,我们配置 SSH 密钥认证。
在 k8s-master 上执行:
-
生成 SSH 密钥对(一路回车,不要设置密码):
ssh-keygen -t rsa这会在
~/.ssh/目录下生成id_rsa(私钥)和id_rsa.pub(公钥)。 -
将公钥复制到所有节点(包括
k8s-master自己):# 复制到 master 自己 ssh-copy-id root@k8s-master # 复制到 node1 ssh-copy-id root@k8s-node1 # 复制到 node2 ssh-copy-id root@k8s-node2执行过程中会提示你输入目标节点的
root密码。
2.3 配置 Ansible 主机清单(Inventory)
Ansible 通过一个 “inventory” 文件来知道它要管理哪些机器。我们来创建一个。
在 k8s-master 上执行:
# 编辑 Ansible 的主配置文件所在目录下的 hosts 文件
sudo vim /etc/ansible/hosts
将文件内容清空,然后添加以下内容。这份配置将我们的节点按角色进行了分组,这在后续部署 K8s 时非常有用。
# /etc/ansible/hosts
# 定义一个名为 [k8s-masters] 的组,包含 k8s-master 节点
[k8s-masters]
k8s-master ansible_host=192.168.48.161
# 定义一个名为 [k8s-nodes] 的组,包含所有 worker 节点
[k8s-nodes]
k8s-node1 ansible_host=192.168.48.162
k8s-node2 ansible_host=192.168.48.163
# 定义一个名为 [k8s-cluster] 的“父组”,它包含了上面两个子组
[k8s-cluster:children]
k8s-masters
k8s-nodes
保存并退出文件。
第三步:验证 Ansible 安装与配置
现在,我们来检验一下前面的工作是否全部成功。所有操作都在 k8s-master 上执行。
3.1 检查 Ansible 版本
ansible --version
你应该能看到类似下面的输出,显示 Ansible 的版本号和配置文件路径。
ansible [core 2.15.4]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3.11/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.11.4 (main, Jun 12 2023, 05:16:11) [GCC 12.2.1 20220829 (Red Hat 12.2.1-2)] (/usr/bin/python3)
jinja version = 3.1.2
libyaml = True
# 然后配置一下主机文件 全局关闭 SSH 主机密钥检查
# 在master节点上配置
vim /etc/ansible/ansible.cfg
host_key_checking = False
3.2 执行第一个 Ad-Hoc 命令(关键验证)
Ad-Hoc 命令是 Ansible 的 “单行命令” 模式,非常适合快速执行简单任务。我们用 ping 模块来测试连通性。
测试所有节点:
ansible all -m ping
测试仅 master 节点:
ansible k8s-masters -m ping
测试仅 worker 节点:
ansible k8s-nodes -m ping
如果一切正常,你会看到类似下面的输出,每个节点的返回结果都是 SUCCESS。
k8s-node2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
k8s-master | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
k8s-node1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
3.3 执行一个命令来查看节点信息
让我们再执行一个命令,比如查看所有节点的操作系统版本,来进一步确认 Ansible 可以执行命令。
ansible all -a "cat /etc/os-release"
这个命令会在所有节点上执行 cat /etc/os-release 并返回结果。你应该能看到每个节点都返回了 openEuler 的版本信息。
总结
至此,你的 Ansible 环境已经搭建并验证成功!
- 控制节点:
k8s-master(192.168.48.161)已安装 Ansible,并可以免密登录到所有节点。 - 被管理节点:
k8s-node1和k8s-node2已准备就绪,可以被 Ansible 管理。 - 你已经掌握:如何配置 Ansible Inventory,以及如何使用 Ad-Hoc 命令执行简单任务。
接下来,我们就可以基于这个稳定的 Ansible 环境,编写 Playbook 来自动化部署 K8s 集群了。
ansible搭建k8s
简单思路
第一步:所有节点(Master+Worker)先做“基础准备”
先给所有机器把K8S需要的“环境铺垫”做好:
- 关swap(K8S不让用交换分区)、装依赖工具(比如curl、iptables这些网络工具);
- 加阿里云的K8S软件源(国内下载快);
- 装Containerd(用来跑容器的“引擎”),并改成阿里云的“pause镜像”(避免国外镜像拉不下来);
- 加载K8S必需的内核模块(比如桥接网络、IP负载均衡这些);
- 配置网络参数(让桥接流量能触发防火墙规则、开启IP转发);
- 装kubeadm(初始化集群的工具)、kubelet(节点上管容器的服务)、kubectl(操作集群的命令行工具),然后启动kubelet。
第二步:单独处理Master节点(集群的“大脑”)
Master节点要做“集群初始化”的工作:
- 先预拉取K8S的核心镜像(用阿里云源加速,避免初始化时下载慢);
- 清理旧的K8S残留(比如之前部署过的配置文件、数据目录);
- 用kubeadm初始化Master(指定阿里云镜像源、Pod网络段,忽略swap的检查);
- 检查初始化是否成功(看有没有生成admin.conf配置文件);
- 把初始化好的配置文件复制到root用户目录(方便用kubectl操作集群);
- 生成Worker节点“加入集群”的命令,保存到文件里;
- 部署Flannel网络插件(让Pod之间能互相通信);
- (用户要求的)删除所有Flannel的Pod(触发重新部署,解决可能的初始化问题)。
第三步:让Worker节点“加入集群”
Worker节点要做的就是“连接到Master”:
- 从Master节点拉取之前生成的“加入命令”;
- 保存到本地执行,把自己加入集群。
总结:整个Playbook就是“先给所有机器铺好环境→初始化Master→Worker加入”,核心是用阿里云源加速下载、解决K8S的依赖和网络问题,最后搭出一个能跑的K8S集群。
vim hosts
[k8s_cluster]
192.168.48.161
192.168.48.162
192.168.48.163
[k8s_master]
192.168.48.161
[k8s_workers]
192.168.48.162
192.168.48.163
vim deploy_k8s.yml
---
- name: Deploy Kubernetes cluster on OpenEuler 24.03 with AliYun sources
hosts: k8s_cluster
become: yes
vars:
k8s_version: "1.28"
pod_network_cidr: "10.244.0.0/16"
tasks:
- name: Disable swap
command: swapoff -a
ignore_errors: yes
- name: Remove swap from fstab
lineinfile:
path: /etc/fstab
regexp: '^.*swap.*$'
state: absent
- name: Install required system packages
dnf:
name:
- curl
- wget
- vim
- git
- conntrack
- socat
- ebtables
- ethtool
- ipset
- iptables
- rsync
state: present
- name: Add AliYun Kubernetes yum repo
copy:
dest: /etc/yum.repos.d/kubernetes.repo
content: |
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
- name: Install containerd from OpenEuler repos
dnf:
name: containerd
state: present
- name: Configure containerd with AliYun pause image
copy:
dest: /etc/containerd/config.toml
content: |
version = 2
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"
[plugins."io.containerd.grpc.v1.cri".containerd]
discard_unpacked_layers = true
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
- name: Start and enable containerd service
systemd:
name: containerd
state: restarted
enabled: yes
- name: Load kernel modules
modprobe:
name: "{{ item }}"
state: present
loop:
- br_netfilter
- ip_vs
- ip_vs_rr
- ip_vs_wrr
- ip_vs_sh
- nf_conntrack
# ====================== 您要求的【所有节点执行】的命令 ======================
- name: Force load br_netfilter module (your command)
modprobe:
name: br_netfilter
state: present
# ---------------------- 新增:持久化br_netfilter模块加载 ----------------------
- name: Persist br_netfilter module loading (fix restart issue)
copy:
content: "br_netfilter"
dest: /etc/modules-load.d/br_netfilter.conf
mode: '0644'
# -----------------------------------------------------------------------------
- name: Verify br_netfilter module is loaded (your command)
shell: lsmod | grep br_netfilter
register: br_netfilter_check
ignore_errors: yes
- name: Print br_netfilter status (your command)
debug:
msg: "br_netfilter module status: {{ 'Loaded' if 'br_netfilter' in br_netfilter_check.stdout else 'Not Loaded' }}"
- name: Create k8s sysctl config (your command)
copy:
content: |
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
dest: /etc/sysctl.d/k8s.conf
- name: Apply sysctl changes (your command)
command: sysctl --system
# ==========================================================================
- name: Configure sysctl parameters
copy:
dest: /etc/sysctl.d/k8s.conf
content: |
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
- name: Apply sysctl changes immediately
command: sysctl --system
- name: Force enable ip_forward
shell: |
echo 1 > /proc/sys/net/ipv4/ip_forward
sysctl -w net.ipv4.ip_forward=1
# ---------- 之前添加的CNI安装任务(保留) ----------
- name: Install CNI plugins (manual fix)
block:
- name: Download CNI plugins
get_url:
url: https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz
dest: /tmp/cni-plugins.tgz
- name: Create CNI bin directory
file:
path: /opt/cni/bin
state: directory
mode: '0755'
- name: Extract CNI plugins
unarchive:
src: /tmp/cni-plugins.tgz
dest: /opt/cni/bin
remote_src: yes
- name: Clean up CNI archive
file:
path: /tmp/cni-plugins.tgz
state: absent
# -------------------------------------------------------------
- name: Install specific version of kubeadm, kubelet and kubectl
dnf:
name:
- kubeadm-{{ k8s_version }}.0
- kubelet-{{ k8s_version }}.0
- kubectl-{{ k8s_version }}.0
state: present
skip_broken: yes
- name: Start and enable kubelet service
systemd:
name: kubelet
state: started
enabled: yes
- name: Pre-pull Kubernetes images on master
hosts: k8s_master
become: yes
vars:
pod_network_cidr: "10.244.0.0/16"
tasks:
- name: Pre-pull Kubernetes images
command: kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers
- name: Clean up existing Kubernetes installation on master
hosts: k8s_master
become: yes
tasks:
- name: Reset kubeadm
command: kubeadm reset -f
ignore_errors: yes
- name: Remove Kubernetes directories
file:
path: "{{ item }}"
state: absent
loop:
- /etc/kubernetes
- /var/lib/etcd
- /var/lib/kubelet
- ~/.kube
- name: Clean network configuration
shell: "{{ item }}"
loop:
- "ip link delete cni0 2>/dev/null || true"
- "ip route del 10.244.0.0/16 2>/dev/null || true"
ignore_errors: yes
- name: Initialize Kubernetes master
hosts: k8s_master
become: yes
vars:
pod_network_cidr: "10.244.0.0/16"
tasks:
- name: Check kubelet status before init
systemd:
name: kubelet
state: started
- name: Initialize Kubernetes cluster
command: kubeadm init --image-repository registry.aliyuncs.com/google_containers --pod-network-cidr={{ pod_network_cidr }} --ignore-preflight-errors=swap,FileContent--proc-sys-net-ipv4-ip_forward
register: kubeadm_init
ignore_errors: yes
- name: Check if kubeadm init succeeded
stat:
path: /etc/kubernetes/admin.conf
register: kube_config
- name: Create .kube directory
file:
path: /root/.kube
state: directory
mode: '0700'
when: kube_config.stat.exists
- name: Copy admin.conf to user home
copy:
src: /etc/kubernetes/admin.conf
dest: /root/.kube/config
remote_src: yes
owner: root
group: root
mode: '0600'
when: kube_config.stat.exists
- name: Extract join command from kubeadm output
shell: |
if [ -f /etc/kubernetes/admin.conf ]; then
kubeadm token create --print-join-command
else
echo "kubeadm init failed, check logs for details"
exit 1
fi
register: join_command
when: kube_config.stat.exists
- name: Save join command
copy:
content: "{{ join_command.stdout }} --ignore-preflight-errors=swap"
dest: /root/join-command.sh
mode: '0700'
when: kube_config.stat.exists
- name: Deploy Flannel network
command: kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
when: kube_config.stat.exists
# ====================== 您要求的【Master节点执行】的命令 ======================
- name: Delete all Flannel pods (your command)
command: kubectl delete pods -n kube-flannel --all
when: kube_config.stat.exists
ignore_errors: yes
# ==========================================================================
- name: Join worker nodes to cluster
hosts: k8s_workers
become: yes
tasks:
- name: Get join command from master
slurp:
src: /root/join-command.sh
register: join_command_content
delegate_to: "{{ groups['k8s_master'][0] }}"
- name: Save join command locally
copy:
content: "{{ join_command_content.content | b64decode }}"
dest: /tmp/join-command.sh
mode: '0700'
- name: Join Kubernetes cluster
command: sh /tmp/join-command.sh
然后执行
# 可以先检测一下文件的格式问题
ansible-playbook -i hosts test.yml --syntax-check
示例输出(语法正确时):
playbook: deploy.yml
# 执行
ansible-playbook -i hosts deploy_k8s.yml
deploy_k8s注释版本
---
# ------------------------------ 全局Play定义 ------------------------------
- name: Deploy Kubernetes cluster on OpenEuler 24.03 with AliYun sources # Play名称:在OpenEuler 24.03上用阿里云源部署K8S集群
hosts: k8s_cluster # 作用的主机组:所有K8S节点(Master+Worker)
become: yes # 提权为root用户(K8S部署需要root权限)
vars: # 全局变量定义(整个Play有效)
k8s_version: "1.28" # 要安装的Kubernetes版本(1.28.x系列)
pod_network_cidr: "10.244.0.0/16" # Pod网络的CIDR(Flannel默认使用此网段)
tasks:
# ------------------------------ 关闭交换分区 ------------------------------
- name: Disable swap
command: swapoff -a # 临时关闭交换分区(立即生效,重启失效)
ignore_errors: yes # 忽略错误(比如交换分区已关闭)
- name: Remove swap from fstab
lineinfile:
path: /etc/fstab # 操作的文件:系统自动挂载配置文件
regexp: '^.*swap.*$' # 匹配规则:所有包含swap的行
state: absent # 状态:删除匹配的行(永久关闭交换分区)
# ------------------------------ 安装系统依赖包 ------------------------------
- name: Install required system packages
dnf: # DNF包管理器模块(OpenEuler默认包管理器)
name: # 要安装的包列表
- curl # 网络请求工具
- wget # 文件下载工具
- vim # 文本编辑器
- git # 版本控制工具
- conntrack # 连接跟踪工具(K8S网络用)
- socat # 网络代理工具(K8S端口转发用)
- ebtables # 以太网桥防火墙工具
- ethtool # 网卡配置工具
- ipset # IP集合工具(K8S Service用)
- iptables # 防火墙工具
- rsync # 文件同步工具
state: present # 状态:确保包已安装
# ------------------------------ 添加阿里云K8S YUM源 ------------------------------
- name: Add AliYun Kubernetes yum repo
copy: # 复制文件模块(生成YUM源配置)
dest: /etc/yum.repos.d/kubernetes.repo # 目标路径:YUM源配置文件
content: | # 文件内容(阿里云K8S源)
[kubernetes]
name=Kubernetes # 源名称
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ # 源地址
enabled=1 # 启用该源
gpgcheck=1 # 验证包签名
repo_gpgcheck=1 # 验证源签名
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg # 签名密钥
# ------------------------------ 安装并配置Containerd ------------------------------
- name: Install containerd from OpenEuler repos
dnf:
name: containerd # 安装Containerd(K8S的容器运行时)
state: present
- name: Configure containerd with AliYun pause image
copy: # 生成Containerd配置文件
dest: /etc/containerd/config.toml # Containerd主配置文件路径
content: | # 配置内容:
version = 2 # 配置文件版本
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9" # 替换为阿里云的pause镜像(Pod沙箱)
[plugins."io.containerd.grpc.v1.cri".containerd]
discard_unpacked_layers = true # 丢弃解压后的镜像层(节省空间)
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2" # 使用runc v2运行时
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true # 启用Systemd cgroup(K8S推荐)
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin" # CNI插件二进制目录
conf_dir = "/etc/cni/net.d" # CNI配置文件目录
- name: Start and enable containerd service
systemd: # 系统服务管理模块
name: containerd # 要操作的服务名称(Containerd)
state: restarted # 状态:重启服务(确保新配置生效)
enabled: yes # 设置开机自启
# ------------------------------ 加载K8S所需内核模块 ------------------------------
- name: Load kernel modules
modprobe: # 内核模块管理模块
name: "{{ item }}" # 循环变量:依次加载列表中的模块
state: present # 状态:确保模块已加载
loop: # 要加载的内核模块列表
- br_netfilter # 桥接网络的iptables支持(K8S必需)
- ip_vs # IPVS负载均衡框架(K8S Service用)
- ip_vs_rr # IPVS轮询算法
- ip_vs_wrr # IPVS加权轮询算法
- ip_vs_sh # IPVS源哈希算法
- nf_conntrack # 连接跟踪模块(网络转发用)
# ====================== 您要求的【所有节点执行】的命令 ======================
- name: Force load br_netfilter module (your command)
modprobe:
name: br_netfilter # 强制加载br_netfilter模块(防止漏加载)
state: present
# ---------------------- 新增:持久化br_netfilter模块加载 ----------------------
- name: Persist br_netfilter module loading (fix restart issue)
copy: # 生成模块持久化配置
content: "br_netfilter" # 文件内容:仅模块名称
dest: /etc/modules-load.d/br_netfilter.conf # 目标路径:模块自动加载配置文件
mode: '0644' # 文件权限(只读)
# -----------------------------------------------------------------------------
- name: Verify br_netfilter module is loaded (your command)
shell: lsmod | grep br_netfilter # 检查模块是否已加载(lsmod列出所有模块,grep过滤)
register: br_netfilter_check # 将命令输出保存到变量
ignore_errors: yes # 忽略错误(比如模块未加载)
- name: Print br_netfilter status (your command)
debug: # 调试模块:打印信息
msg: "br_netfilter module status: {{ 'Loaded' if 'br_netfilter' in br_netfilter_check.stdout else 'Not Loaded' }}" # 根据输出判断状态
- name: Create k8s sysctl config (your command)
copy: # 生成K8S的sysctl配置
content: | # 配置内容:
net.bridge.bridge-nf-call-iptables = 1 # 桥接流量触发iptables规则
net.bridge.bridge-nf-call-ip6tables = 1 # 桥接流量触发ip6tables规则
net.ipv4.ip_forward = 1 # 启用IP转发(K8S网络必需)
dest: /etc/sysctl.d/k8s.conf # 目标路径:sysctl配置文件
- name: Apply sysctl changes (your command)
command: sysctl --system # 应用所有sysctl配置(立即生效)
# ==========================================================================
# ------------------------------ 再次配置sysctl(冗余保障) ------------------------------
- name: Configure sysctl parameters
copy:
dest: /etc/sysctl.d/k8s.conf
content: |
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
- name: Apply sysctl changes immediately
command: sysctl --system
# ------------------------------ 强制启用IP转发(兜底) ------------------------------
- name: Force enable ip_forward
shell: | # 执行Shell命令:
echo 1 > /proc/sys/net/ipv4/ip_forward # 临时启用IP转发(写入proc文件)
sysctl -w net.ipv4.ip_forward=1 # 永久启用IP转发(sysctl写入)
# ---------- 之前添加的CNI安装任务(保留) ----------
- name: Install CNI plugins (manual fix)
block: # 任务块:将多个任务分组
- name: Download CNI plugins
get_url: # 下载文件模块
url: https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz # CNI插件下载地址
dest: /tmp/cni-plugins.tgz # 下载到临时目录
- name: Create CNI bin directory
file:
path: /opt/cni/bin # CNI插件的二进制目录(Containerd配置中指定)
state: directory # 确保目录存在
mode: '0755' # 目录权限(所有用户可执行)
- name: Extract CNI plugins
unarchive: # 解压模块
src: /tmp/cni-plugins.tgz # 要解压的文件
dest: /opt/cni/bin # 解压到CNI二进制目录
remote_src: yes # 源文件在远程节点(本地临时目录)
- name: Clean up CNI archive
file:
path: /tmp/cni-plugins.tgz # 要删除的临时文件
state: absent # 状态:确保文件不存在
# -------------------------------------------------------------
# ------------------------------ 安装K8S组件 ------------------------------
- name: Install specific version of kubeadm, kubelet and kubectl
dnf:
name: # 要安装的K8S组件列表(指定版本)
- kubeadm-{{ k8s_version }}.0 # kubeadm(集群初始化工具)
- kubelet-{{ k8s_version }}.0 # kubelet(节点管理工具)
- kubectl-{{ k8s_version }}.0 # kubectl(集群命令行工具)
state: present
skip_broken: yes # 忽略依赖冲突(部分包可能依赖旧版本)
# ------------------------------ 启动kubelet服务 ------------------------------
- name: Start and enable kubelet service
systemd:
name: kubelet # 要操作的服务名称(kubelet)
state: started # 状态:启动服务
enabled: yes # 设置开机自启
# ------------------------------ Master节点专属Play:预拉取K8S镜像 ------------------------------
- name: Pre-pull Kubernetes images on master
hosts: k8s_master # 作用的主机组:仅K8S Master节点
become: yes
vars:
pod_network_cidr: "10.244.0.0/16"
tasks:
- name: Pre-pull Kubernetes images
command: kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers # 预拉取K8S所需镜像(用阿里云源加速)
# ------------------------------ Master节点专属Play:清理旧安装 ------------------------------
- name: Clean up existing Kubernetes installation on master
hosts: k8s_master
become: yes
tasks:
- name: Reset kubeadm
command: kubeadm reset -f # 重置Master节点(清理旧集群配置)
ignore_errors: yes # 忽略错误(比如未初始化过)
- name: Remove Kubernetes directories
file:
path: "{{ item }}" # 循环删除K8S相关目录
state: absent # 状态:确保目录不存在
loop: # 要删除的目录列表
- /etc/kubernetes # K8S配置目录
- /var/lib/etcd # etcd数据目录
- /var/lib/kubelet # kubelet数据目录
- ~/.kube # 用户kubeconfig目录
- name: Clean network configuration
shell: "{{ item }}" # 清理网络配置(删除旧虚拟网卡和路由)
loop:
- "ip link delete cni0 2>/dev/null || true" # 删除CNI虚拟网卡(忽略错误)
- "ip route del 10.244.0.0/16 2>/dev/null || true" # 删除Pod网络路由(忽略错误)
ignore_errors: yes
# ------------------------------ Master节点专属Play:初始化集群 ------------------------------
- name: Initialize Kubernetes master
hosts: k8s_master
become: yes
vars:
pod_network_cidr: "10.244.0.0/16"
tasks:
- name: Check kubelet status before init
systemd:
name: kubelet # 检查kubelet是否已启动(初始化前必需)
state: started
- name: Initialize Kubernetes cluster
command: kubeadm init --image-repository registry.aliyuncs.com/google_containers --pod-network-cidr={{ pod_network_cidr }} --ignore-preflight-errors=swap,FileContent--proc-sys-net-ipv4-ip_forward
# 初始化Master节点:
# --image-repository:用阿里云镜像源
# --pod-network-cidr:指定Pod网络CIDR
# --ignore-preflight-errors:忽略交换分区和ip_forward的预检查错误
register: kubeadm_init # 保存初始化输出到变量
ignore_errors: yes # 忽略初始化错误(方便后续检查)
- name: Check if kubeadm init succeeded
stat: # 检查初始化是否成功(admin.conf存在则成功)
path: /etc/kubernetes/admin.conf # Master节点的kubeconfig文件(初始化成功的标志)
register: kube_config # 保存检查结果到变量
- name: Create .kube directory
file:
path: /root/.kube # root用户的.kube目录(存储kubeconfig)
state: directory
mode: '0700'
when: kube_config.stat.exists # 仅当初始化成功(admin.conf存在)时执行
- name: Copy admin.conf to user home
copy: # 将admin.conf复制到用户目录(方便kubectl使用)
src: /etc/kubernetes/admin.conf # 源文件路径
dest: /root/.kube/config # 目标路径(用户的kubeconfig文件)
remote_src: yes # 源文件在远程节点(Master本地)
owner: root # 文件所有者
group: root # 文件所属组
mode: '0600' # 文件权限(仅root可读可写)
when: kube_config.stat.exists
- name: Extract join command from kubeadm output
shell: | # 生成Worker节点的join命令:
if [ -f /etc/kubernetes/admin.conf ]; then # 仅当初始化成功时执行
kubeadm token create --print-join-command # 创建join令牌并打印命令
else
echo "kubeadm init failed, check logs for details"
exit 1
fi
register: join_command # 保存join命令到变量
when: kube_config.stat.exists
- name: Save join command
copy: # 将join命令保存到文件(供Worker节点使用)
content: "{{ join_command.stdout }} --ignore-preflight-errors=swap" # 命令内容(添加忽略交换分区的参数)
dest: /root/join-command.sh # 保存路径(Master节点的root目录)
mode: '0700' # 文件权限(仅root可执行)
when: kube_config.stat.exists
- name: Deploy Flannel network
command: kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml # 部署Flannel网络插件(Pod网络)
when: kube_config.stat.exists
# ====================== 您要求的【Master节点执行】的命令 ======================
- name: Delete all Flannel pods (your command)
command: kubectl delete pods -n kube-flannel --all # 删除所有Flannel Pod(触发重新部署,解决初始化问题)
when: kube_config.stat.exists
ignore_errors: yes # 忽略错误(比如没有Flannel Pod)
# ==========================================================================
# ------------------------------ Worker节点专属Play:加入集群 ------------------------------
- name: Join worker nodes to cluster
hosts: k8s_workers # 作用的主机组:
# 不用着急 一开始在pending中
kubectl get pods -A
# 查看节点
kubectl get nodes
dashboard搭建
1. 下载Dashboard配置文件
wget -c https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
2. 修改Service类型
修改文件dashboard.yaml的39行内容,因为默认情况下,service的类型是cluster IP,需更改为NodePort的方式,便于访问,也可映射到指定的端口。
spec:
type: NodePort
ports:
- port: 443
targetPort: 8443
nodePort: 31001
selector:
k8s-app: kubernetes-dashboard
修改后的dashboard.yaml
apiVersion: v1
kind: Namespace
metadata:
name: kubernetes-dashboard
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
type: NodePort
ports:
- port: 443
targetPort: 8443
nodePort: 31001
selector:
k8s-app: kubernetes-dashboard
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-certs
namespace: kubernetes-dashboard
type: Opaque
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-csrf
namespace: kubernetes-dashboard
type: Opaque
data:
csrf: ""
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-key-holder
namespace: kubernetes-dashboard
type: Opaque
---
kind: ConfigMap
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-settings
namespace: kubernetes-dashboard
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
verbs: ["get", "update", "delete"]
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["kubernetes-dashboard-settings"]
verbs: ["get", "update"]
- apiGroups: [""]
resources: ["services"]
resourceNames: ["heapster", "dashboard-metrics-scraper"]
verbs: ["proxy"]
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
verbs: ["get"]
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
rules:
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kubernetes-dashboard
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubernetes-dashboard
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: kubernetes-dashboard
template:
metadata:
labels:
k8s-app: kubernetes-dashboard
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: kubernetes-dashboard
image: kubernetesui/dashboard:v2.7.0
imagePullPolicy: Always
ports:
- containerPort: 8443
protocol: TCP
args:
- --auto-generate-certificates
- --namespace=kubernetes-dashboard
- --token-ttl=43200
- --enable-skip-login
- --enable-insecure-login
volumeMounts:
- name: kubernetes-dashboard-certs
mountPath: /certs
- mountPath: /tmp
name: tmp-volume
livenessProbe:
httpGet:
scheme: HTTPS
path: /
port: 8443
initialDelaySeconds: 30
timeoutSeconds: 30
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
volumes:
- name: kubernetes-dashboard-certs
secret:
secretName: kubernetes-dashboard-certs
- name: tmp-volume
emptyDir: {}
serviceAccountName: kubernetes-dashboard
nodeSelector:
kubernetes.io/os: linux
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: dashboard-metrics-scraper
name: dashboard-metrics-scraper
namespace: kubernetes-dashboard
spec:
ports:
- port: 8000
targetPort: 8000
selector:
k8s-app: dashboard-metrics-scraper
---
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: dashboard-metrics-scraper
name: dashboard-metrics-scraper
namespace: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: dashboard-metrics-scraper
template:
metadata:
labels:
k8s-app: dashboard-metrics-scraper
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: dashboard-metrics-scraper
image: kubernetesui/metrics-scraper:v1.0.8
ports:
- containerPort: 8000
protocol: TCP
livenessProbe:
httpGet:
scheme: HTTP
path: /
port: 8000
initialDelaySeconds: 30
timeoutSeconds: 30
volumeMounts:
- mountPath: /tmp
name: tmp-volume
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
serviceAccountName: kubernetes-dashboard
nodeSelector:
kubernetes.io/os: linux
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
volumes:
- name: tmp-volume
emptyDir: {}
# 应用配置文件
kubectl apply -f dashboard.yaml
kubectl get pod -n kubernetes-dashboard
kubectl get svc -n kubernetes-dashboard
设置和绑定Dashboard权限,命令如下:
1. 创建Dashboard管理员账号
wget https://raw.githubusercontent.com/cby-chen/Kubernetes/main/yaml/dashboard-user.yaml
kubectl apply -f dashboard-user.yaml
2. 创建管理员token秘钥
kubectl -n kubernetes-dashboard create token admin-user
3. 访问Dashboard WEB界面
通过浏览器任意节点IP访问Dashboard WEB:`https://你的IP:31001/`,输入Token登录即可。
BUG(这个是博主之前遇到的问题)
flannel
从当前输出看,集群 CIDR 与 Flannel 配置完全一致(均为 10.244.0.0/16),排除了配置不匹配的问题。结合之前 Flannel 日志中的 br_netfilter 错误,核心问题仍未解决——br_netfilter 内核模块未加载,导致 Flannel 无法初始化网络规则。
现在需要解决的关键问题:加载 br_netfilter 模块并配置内核参数
以下是所有节点必须执行的操作(master + 所有 worker 节点):
1. 加载 br_netfilter 模块(临时生效,重启后需重新加载)
modprobe br_netfilter
2. 验证模块是否加载成功
lsmod | grep br_netfilter
若输出类似 br_netfilter 22256 0,说明加载成功。
3. 配置内核参数(永久生效)
创建 sysctl 配置文件,确保重启后参数仍有效:
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
4. 应用 sysctl 配置
sysctl --system
5. 重启 Flannel Pod(让配置生效)
在 master 节点执行:
kubectl delete pods -n kube-flannel --all
验证修复效果
-
检查 Flannel Pod 状态:
kubectl get pods -n kube-flannel若所有 Pod 状态变为
Running,说明 Flannel 已正常启动。 -
检查 Coredns Pod 状态:
kubectl get pods -n kube-system | grep corednsFlannel 正常后,Coredns 的
ContainerCreating状态会自动转为Running。 -
再次查看 Flannel 日志(确认无
br_netfilter错误):
选一个 Flannel Pod 查看日志:kubectl logs -n kube-flannel kube-flannel-ds-xxxxxx若日志中无
Failed to check br_netfilter错误,说明问题解决。
补充说明
-
为什么需要
br_netfilter?
Flannel 使用 VXLAN 后端时,需要通过br_netfilter模块将网桥(bridge)的网络流量转发到 iptables 链,实现 Pod 间的网络隔离与通信。若模块未加载,Flannel 无法完成网络初始化,导致 CrashLoopBackOff。 -
是否需要重新分配 Pod CIDR?
若之前已手动分配了正确的 Pod CIDR(如10.244.0.0/24、10.244.1.0/24等),且与集群 CIDR 一致,无需再次修改。可通过以下命令验证节点的 Pod CIDR:kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
最终验证
待 Flannel 和 Coredns 均 Running 后,可部署一个测试 Pod 验证网络通信:
# 部署一个 nginx Pod
kubectl run nginx --image=nginx
# 查看 Pod IP
kubectl get pod nginx -o wide
# 在另一个节点执行 ping 测试(替换为实际 Pod IP)
ping 10.244.1.xx
若能 ping 通,说明集群网络完全正常。
按以上步骤操作后,99% 的概率能解决当前问题。若仍有异常,可提供最新的 Flannel 日志或节点状态,我会进一步协助排查。
CNI
根据事件信息,问题明确是 CNI 插件缺失导致的。错误信息显示无法在路径 /opt/cni/bin 中找到 loopback 插件。
解决方案:
1. 检查 CNI 插件目录
ls -la /opt/cni/bin/
2. 安装缺失的 CNI 插件
如果目录为空或缺少插件,需要安装 CNI 插件:
wget https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz
mkdir -p /opt/cni/bin
tar -C /opt/cni/bin -xzf cni-plugins-linux-amd64-v1.3.0.tgz
3. 验证插件安装
ls /opt/cni/bin/ | grep loopback
4. 重启 kubelet 服务
systemctl restart kubelet
5. 检查 Pod 状态
kubectl get pods -n kube-system -w
如果节点是使用 kubeadm 部署的,还需要检查:
# 确认 kubelet 配置
cat /var/lib/kubelet/kubeadm-flags.env
# 检查 CNI 配置文件
ls /etc/cni/net.d/
预期结果:
安装完 CNI 插件并重启 kubelet 后,CoreDNS Pod 应该能正常进入 Running 状态。如果问题仍然存在,请检查所有工作节点的 CNI 插件安装情况。

更多推荐




所有评论(0)