先换源(可选)

为什么我会换源,因为我在白天的时候,默认的源是很快的,但是一到晚上很慢,所以对于网路有问题的可以先尝试换源

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 虚拟机。

这个过程我们会分为三大块:

  1. 环境准备:在所有三台机器上执行一些必要的初始化操作。
  2. Ansible 安装与配置:在控制节点(我们选 k8s-master)上安装 Ansible,并配置免密登录到其他节点。
  3. Ansible 连通性验证:编写并执行一个简单的命令,测试 Ansible 是否能成功管理所有节点。

第一步:环境准备(所有三台机器都要执行)

这一步的目标是确保所有节点的基础环境一致,并且网络互通。请分别在 192.168.48.161192.168.48.162192.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-masterping 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 上执行:

  1. 生成 SSH 密钥对(一路回车,不要设置密码):

    ssh-keygen -t rsa
    

    这会在 ~/.ssh/ 目录下生成 id_rsa(私钥)和 id_rsa.pub(公钥)。

  2. 将公钥复制到所有节点(包括 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-node1k8s-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

验证修复效果

  1. 检查 Flannel Pod 状态

    kubectl get pods -n kube-flannel
    

    若所有 Pod 状态变为 Running,说明 Flannel 已正常启动。

  2. 检查 Coredns Pod 状态

    kubectl get pods -n kube-system | grep coredns
    

    Flannel 正常后,Coredns 的 ContainerCreating 状态会自动转为 Running

  3. 再次查看 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/2410.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 插件安装情况。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐