当前位置: 首页 > news >正文

ansible急速入门实战篇

                                              作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

目录
  • 一.主机清单管理
    • 1.安装ansible
    • 2.直接指定主机
    • 3.基于别名管理
    • 3.基于多个分组
    • 4.通过域名解析
    • 5.序列定义方式
    • 6.支持子组
    • 7.为组设置变量
    • 8.通过免密钥的方式配置Inventory
  • 二.ansible的模块
    • 1.查看模块文档
    • 2.参考资料
  • 三.Playbook实战之vars变量管理
    • 1.playbook概述
      • 1.1 Playbook的核心元素
      • 1.2 ansible运行结果颜色说明
    • 2.定义变量
      • 2.1 直接在play定义变量
      • 2.2 在文件中定义变量
      • 2.3 在主机清单中定义变量
      • 2.4 官方推荐的定义变量方式
    • 3.变量注册
      • 3.1 什么是变量注册
      • 3.2 变量注册案例
    • 4.层级定义变量
      • 4.1 使用点号'.'区分层级变量
      • 4.2 使用方括号'[]'区分层级变量 (官方推荐写法)
      • 4.3 语法风格对比
    • 5.Facts缓存
      • 5.1 facts概述
      • 5.2 facts内置变量
        • 5.2.1 系统发行版 & 内核架构
        • 5.2.2 CPU 硬件信息
        • 5.2.3 内存与 Swap
        • 5.2.4 磁盘与挂载设备
        • 5.2.5 网络相关(高频)
        • 5.2.6 虚拟化 / 云主机标识
        • 5.2.7 用户、环境、时间
        • 5.2.8 Windows 系统专属 Facts
        • 5.2.9 playbook 全局内置变量(非 setup 采集 facts,配套使用)
      • 5.3 测试案例
    • 6.template使用变量
      • 6.1 template的作用
      • 6.2 测试验证
  • 四.Playbook实战之流程控制
    • 1.playbook条件语句概述
    • 2.when判断实战
      • 2.1 when条件表达式
      • 2.2 when案例
      • 2.3 部署nginx服务案例
    • 3.Handlers实战
      • 3.1 handlers概述
      • 3.2 实战案例
    • 4.循环语句
      • 4.1 循环语句的作用
      • 4.2 实战案例
    • 5.任务标签
      • 5.1 什么是任务标签
      • 5.2 实战案例
    • 6.文件复用-task拆解
      • 6.1 什么是文件复用
      • 6.2 实战案例
    • 7.抑制changed
      • 7.1 什么是抑制changed
      • 7.2 实战案例
  • 五.jinja2模板及Role角色
    • 1.jinja2模板
      • 1.1 什么是jinja2模板
      • 1.2 实战案例-for循环和if语句
    • 2.Roles角色
      • 2.1 什么是Roles
      • 2.2 实战案例-部署rsync服务
      • 2.3 实战案例-部署nfs服务

一.主机清单管理

1.安装ansible

	1.安装ansible
[root@ansible99 ~]# apt -y install ansible sshpass2.关闭主机密钥检查
[root@ansible99 ~]# cat > /etc/ansible/ansible.cfg <<EOF
[defaults]
host_key_checking = False
deprecation_warnings=False
inventory = /etc/ansible/hosts
EOF

2.直接指定主机

	1.定义主机列表
[root@ansible99 ~]# mkdir etcansible
[root@ansible99 ~]# 
[root@ansible99 ~]# vim /etc/ansible/hosts
[root@ansible99 ~]# 
[root@ansible99 ~]# cat  /etc/ansible/hosts
10.0.0.231 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
10.0.0.232 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
10.0.0.233 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
[root@ansible99 ~]# 2.链接指定的主机进行检测
[root@ansible99 ~]# ansible 10.0.0.231 -m ping
10.0.0.231 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible 10.0.0.232 -m ping
10.0.0.232 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible 10.0.0.66 -m ping  # 如果没有定义主机,则无法使用
[WARNING]: Could not match supplied host pattern, ignoring: 10.0.0.66
[WARNING]: No hosts matched, nothing to do
[root@ansible99 ~]# 

3.基于别名管理

	1.定义主机列表
[root@ansible99 ~]# cat  /etc/ansible/hosts 
10.0.0.231 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
master232 ansible_ssh_host=10.0.0.232 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
master233 ansible_ssh_host=10.0.0.233 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
[root@ansible99 ~]# 
[root@ansible99 ~]# 2.基于别名管理
[root@ansible99 ~]# ansible 10.0.0.231 -m ping
10.0.0.231 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible master232 -m ping
master232 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible master233 -m ping
master233 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible 10.0.0.233 -m ping  # 基于别名就不能直接写主机IP了,如果非要用,也可以
[WARNING]: Could not match supplied host pattern, ignoring: 10.0.0.233
[WARNING]: No hosts matched, nothing to do
[root@ansible99 ~]#  

3.基于多个分组

	1.修改主机清单
[root@ansible99 ~]# cat  /etc/ansible/hosts 
[k8s_master]
master231 ansible_ssh_host=10.0.0.231 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
master232 ansible_ssh_host=10.0.0.232 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
master233 ansible_ssh_host=10.0.0.233 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'[k8s_worker]
worker66 ansible_ssh_host=10.0.0.66 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
worker77 ansible_ssh_host=10.0.0.77 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
[root@ansible99 ~]# 
[root@ansible99 ~]# 2.测试验证
[root@ansible99 ~]# ansible k8s_master -m ping
master231 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
master233 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
master232 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible k8s_worker -m ping
worker77 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
worker66 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible all -m ping  # all代表所有的分组。
worker77 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
master233 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
worker66 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
master232 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
master231 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 

4.通过域名解析

	1.添加域名配置
[root@ansible99 ~]# cat /etc/ansible/hosts 
www.yinzhengjie.com ansible_ssh_host=10.0.0.231 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'[k8s_master]
master231 ansible_ssh_host=10.0.0.231 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
master232 ansible_ssh_host=10.0.0.232 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
master233 ansible_ssh_host=10.0.0.233 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'[k8s_worker]
worker66 ansible_ssh_host=10.0.0.66 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
worker77 ansible_ssh_host=10.0.0.77 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
[root@ansible99 ~]# 2.由于我没有域名指向的服务器,因此在本地host做域名劫持
[root@ansible99 ~]# echo 10.0.0.231 www.yinzhengjie.com >> /etc/hosts
[root@ansible99 ~]# 
[root@ansible99 ~]# tail -1 /etc/hosts
10.0.0.231 www.yinzhengjie.com
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible www.yinzhengjie.com -m ping
www.yinzhengjie.com | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 

5.序列定义方式

	1.添加主机解析
[root@ansible99 ~]# cat >> /etc/hosts <<EOF
10.0.0.231 master231
10.0.0.232 master232
10.0.0.233 master233
EOF2.定义主机列表
[root@ansible99 ~]# cat /etc/ansible/hosts 
[k8s_master]
master23[1:3]  ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'[k8s_worker]
worker66 ansible_ssh_host=10.0.0.66 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
worker77 ansible_ssh_host=10.0.0.77 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'[root@ansible99 ~]# 2.测试验证
[root@ansible99 ~]# ansible k8s_master -m ping
master231 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
master233 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
master232 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 

6.支持子组

	1.定义主机
[root@ansible99 ~]# cat /etc/ansible/hosts 
[k8s01]
master231 ansible_ssh_host=10.0.0.231 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'[k8s02]
master232 ansible_ssh_host=10.0.0.232 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'[k8s03]
master233 ansible_ssh_host=10.0.0.233 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'[master:children]
k8s01
k8s03
[root@ansible99 ~]# 2.调用子组master
[root@ansible99 ~]# ansible master -m ping
master231 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
master233 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 

7.为组设置变量

	1.定义主机清单
[root@ansible99 ~]# cat /etc/ansible/hosts 
[k8s01]
master231 ansible_ssh_host=10.0.0.231[k8s02]
master232 ansible_ssh_host=10.0.0.232[k8s03]
master233 ansible_ssh_host=10.0.0.233 [master:children]
k8s01
k8s03[all:vars]  # 此处的all表示所有主机,这里的vars表示设置变量。
ansible_ssh_user=root 
ansible_ssh_port=22 
ansible_ssh_pass='1'
[root@ansible99 ~]# 2.测试验证
[root@ansible99 ~]# ansible master -m ping
master231 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
master233 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible k8s02 -m ping
master232 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 

8.通过免密钥的方式配置Inventory

	1.生成密钥
[root@ansible99 ~]# ssh-keygen -t rsa -f ~/.ssh/id_rsa -P '' -q2.配置免密链接
[root@ansible99 ~]# ssh-copy-id 10.0.0.231
[root@ansible99 ~]# ssh-copy-id 10.0.0.232
[root@ansible99 ~]# ssh-copy-id 10.0.0.233
[root@ansible99 ~]# ssh-copy-id 10.0.0.66
[root@ansible99 ~]# ssh-copy-id 10.0.0.773.修改主机清单
[root@ansible99 ~]# cat  /etc/ansible/hosts 
[master]
10.0.0.231
10.0.0.232
10.0.0.233[worker]
10.0.0.66
10.0.0.77
[root@ansible99 ~]# 4.测试验证
[root@ansible99 ~]# ansible master -m ping
10.0.0.233 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
10.0.0.232 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
10.0.0.231 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible worker -m ping
10.0.0.66 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
10.0.0.77 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 
[root@ansible99 ~]# ansible all -m ping
10.0.0.77 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
10.0.0.66 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
10.0.0.233 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
10.0.0.232 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
10.0.0.231 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
[root@ansible99 ~]# 

二.ansible的模块

1.查看模块文档

[root@ansible99 ~]# ansible-doc ping
> ANSIBLE.BUILTIN.PING    (/usr/lib/python3/dist-packages/ansible/modules/ping.py)A trivial test module, this module always returns `pong' on successful contact. Itdoes not make sense in playbooks, but it is useful from `/usr/bin/ansible' to verifythe ability to login and that a usable Python is configured. This is NOT ICMP ping,this is just a trivial test module that requires Python on the remote-node. ForWindows targets, use the [ansible.windows.win_ping] module instead. For Networktargets, use the [ansible.netcommon.net_ping] module instead.ADDED IN: historicalOPTIONS (= is mandatory):- dataData to return for the `ping' return value.If this parameter is set to `crash', the module will cause an exception.default: pongtype: strATTRIBUTES:check_mode:description: Can run in check_mode and return changed status prediction withoutmodifying targetsupport: fulldiff_mode:description: Will return details on what has changed (or possibly needs changingin check_mode), when in diff modesupport: noneplatform:
[root@ansible99 ~]# 

2.参考资料

推荐阅读:https://www.cnblogs.com/yinzhengjie/p/10447587.html

三.Playbook实战之vars变量管理

1.playbook概述

1.1 Playbook的核心元素

Hosts:定义可以操作的主机,Tasks:定义我们需要执行的任务列表。Variable:  支持变量,流程控制语句,如循环语句等等。(YAML支持编程语言的基本语法,如条件表达式,循环语句,流程控制等等,这也是为什么ansible选择它的一个原因吧!)Templates:包含了模板语法的文本文件。Handlers:处理器是由特定条件触发的任务。Roles:定义角色,其实我们可以理解角色是将服务器主机进行分组,然后基于组的方式进行批量管理。这个角色和MySQL8.0之后版本的角色功能很相似!

1.2 ansible运行结果颜色说明

颜色 含义 描述
绿色 执行成功且未发生状态改变 任务成功完成,但目标系统没有发生变化(幂等性体现)。
黄色 成功执行且发生了状态改变 任务成功完成,并且目标系统的状态发生了改变(如文件创建,服务启动)。
红色 执行失败 执行过程中出现错误,需要排查原因。
紫色 告警信息 提示可能存在的问题或建议,但任务可能继续执行。
ansible输出的颜色,表示不同的涵义。

2.定义变量

2.1 直接在play定义变量

	1.查看主机清单
[root@ansible99 playbook]# cat /etc/ansible/hosts 
[master]
10.0.0.231
10.0.0.232
10.0.0.233[worker]
10.0.0.66
10.0.0.77
[root@ansible99 playbook]# 2.编写剧本
[root@ansible99 playbook]# cat 01-play.yaml 
- hosts: mastervars:- file01: /tmp/xixi.txt- file02: haha.txttasks:- name: create {{ file01 }} filefile:path: "{{ file01 }}"owner: rootgroup: rootmode: 0644state: touch- name: create {{ file02 }} filefile:path: /tmp/{{ file02 }}state: touch
[root@ansible99 playbook]# 3.测试验证
[root@ansible99 playbook]# ansible-playbook  01-play.yaml PLAY [master] ********************************************************************************************************TASK [Gathering Facts] ***********************************************************************************************
ok: [10.0.0.231]
ok: [10.0.0.233]
ok: [10.0.0.232]TASK [create /tmp/xixi.txt file] *************************************************************************************
changed: [10.0.0.231]
changed: [10.0.0.233]
changed: [10.0.0.232]TASK [create haha.txt file] ******************************************************************************************
changed: [10.0.0.231]
changed: [10.0.0.233]
changed: [10.0.0.232]PLAY RECAP ***********************************************************************************************************
10.0.0.231                 : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.232                 : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.233                 : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   [root@ansible99 playbook]# 

2.2 在文件中定义变量

	1.编写剧本
[root@ansible99 playbook]# cat pkg.yml 
pkg01: wget
pkg02: tree
[root@ansible99 playbook]# 
[root@ansible99 playbook]# cat  02-files.yaml 
- hosts: mastervars_files:- pkg.ymltasks:- name: Install packageapt:name:- "{{ pkg01 }}"- "{{ pkg02 }}"state: present  # 卸载的话可以改成absent
[root@ansible99 playbook]# 2.测试验证
[root@ansible99 playbook]# ansible-playbook 02-files.yaml  PLAY [master] ********************************************************************************************************TASK [Gathering Facts] ***********************************************************************************************
ok: [10.0.0.231]
ok: [10.0.0.233]
ok: [10.0.0.232]TASK [Install package] ***********************************************************************************************
changed: [10.0.0.231]
changed: [10.0.0.232]
changed: [10.0.0.233]PLAY RECAP ***********************************************************************************************************
10.0.0.231                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.232                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.233                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   [root@ansible99 playbook]# 

2.3 在主机清单中定义变量

	1.在主机清单中定义变量
[root@ansible99 playbook]# cat  /etc/ansible/hosts 
[master]
10.0.0.231
10.0.0.232
10.0.0.233[worker]
10.0.0.66
10.0.0.77[master:vars]
pkg01=wget
pkg02=tree
pkg03=lrzsz
[root@ansible99 playbook]# 2.编写playbook
[root@ansible99 playbook]# cat 03-hosts.yaml 
- hosts: mastertasks:- name: Install packageapt:name:- "{{ pkg01 }}"- "{{ pkg02 }}"- "{{ pkg03 }}"state: present
[root@ansible99 playbook]# 3.测试验证
[root@ansible99 playbook]# ansible-playbook 03-hosts.yaml PLAY [master] **********************************************************************TASK [Gathering Facts] *************************************************************
ok: [10.0.0.233]
ok: [10.0.0.232]
ok: [10.0.0.231]TASK [Install package] *************************************************************
changed: [10.0.0.231]
changed: [10.0.0.233]
changed: [10.0.0.232]PLAY RECAP *************************************************************************
10.0.0.231                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.232                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.233                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   [root@ansible99 playbook]#  

2.4 官方推荐的定义变量方式

	1.在运行playbook的目录下创建两个目录
[root@ansible99 playbook]# mkdir group_vars hosts_vars
[root@ansible99 playbook]# 目录作用说明:group_vars:在目录下面以"组名称"命名的文件,然后在文件中直接定义变量。hosts_vars:在目录下面以"主机"命名的文件,然后在文件中直接定义变量。2.编辑主机清单
[root@ansible99 playbook]# cat /etc/ansible/hosts 
[master]
10.0.0.231
10.0.0.232
10.0.0.233[worker]
10.0.0.66
10.0.0.77
[root@ansible99 playbook]# 3.在group_vars或者hosts_vars目录下定义变量
[root@ansible99 playbook]# cat group_vars/all 
pkg01: wget
pkg02: tree
pkg03: lrzsz[root@ansible99 playbook]# 
[root@ansible99 playbook]# cat group_vars/master 
pkg01: curl
pkg02: tree
[root@ansible99 playbook]# 
[root@ansible99 playbook]# cat hosts_vars/10.0.0.66 
pkg01: nginx
pkg02: curl
[root@ansible99 playbook]# 4.编写playbook
[root@ansible99 playbook]# cat 04-group-vars.yaml 
- hosts: mastertasks:- name: Install packageapt:name:- "{{ pkg01 }}"- "{{ pkg02 }}"state: absent
[root@ansible99 playbook]# 5.测试验证
[root@ansible99 playbook]# ansible-playbook 04-group-vars.yaml PLAY [master] **********************************************************************TASK [Gathering Facts] *************************************************************
ok: [10.0.0.233]
ok: [10.0.0.232]
ok: [10.0.0.231]TASK [Install package] *************************************************************
changed: [10.0.0.231]
changed: [10.0.0.233]
changed: [10.0.0.232]PLAY RECAP *************************************************************************
10.0.0.231                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.232                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.233                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   [root@ansible99 playbook]# 

3.变量注册

3.1 什么是变量注册

当ansible的模块在运行之后,其实都会返回一些result结果,就像是执行脚本,我们有的时候需要脚本给我们一些return返回值,我们才知道,上一步是否只想成功。默认情况下,ansible的result并不会显示出来,所以,我们可以把这些返回值'存储'到变量中,这样我们就能通过'调用'对应的变量名,从而获取到这些result,这种将模块的返回值,写入变量中的方法我们称之为变量注册。

3.2 变量注册案例

	1.编写剧本
[root@ansible99 playbook]# cat 05-register.yaml 
- hosts: 10.0.0.232tasks:- name: Install nginxapt:name:- nginxstate: present- name: nginx check syntaxcommand: 'nginx -t'# 注册变量register: nginx_result- name: print nginx_result variablesdebug:# 一般我们不需要查看所有的输出,看指定字段的数据,可以使用"."来取数据即可。# msg: "{{ nginx_result }}"msg: "{{ nginx_result.stderr_lines }}"- name: list nginx conf dircommand: 'ls -lh /etc/nginx/'register: list_result- name: print list_result variablesdebug:msg: "{{ list_result.stdout_lines }}"[root@ansible99 playbook]# 2.测试验证
[root@ansible99 playbook]# ansible-playbook 05-register.yaml PLAY [10.0.0.232] ******************************************************************TASK [Gathering Facts] *************************************************************
ok: [10.0.0.232]TASK [Install nginx] ***************************************************************
ok: [10.0.0.232]TASK [nginx check syntax] **********************************************************
changed: [10.0.0.232]TASK [print nginx_result variables] ************************************************
ok: [10.0.0.232] => {"msg": ["nginx: the configuration file /etc/nginx/nginx.conf syntax is ok","nginx: configuration file /etc/nginx/nginx.conf test is successful"]
}TASK [list nginx conf dir] *********************************************************
changed: [10.0.0.232]TASK [print list_result variables] *************************************************
ok: [10.0.0.232] => {"msg": ["total 68K","drwxr-xr-x 2 root root 4.0K Jun  9 11:45 conf.d","-rw-r--r-- 1 root root 1.1K Nov 30  2023 fastcgi.conf","-rw-r--r-- 1 root root 1.1K Nov 30  2023 fastcgi_params","-rw-r--r-- 1 root root 2.8K Nov 30  2023 koi-utf","-rw-r--r-- 1 root root 2.2K Nov 30  2023 koi-win","-rw-r--r-- 1 root root 5.4K Nov 30  2023 mime.types","drwxr-xr-x 2 root root 4.0K Jun  9 11:45 modules-available","drwxr-xr-x 2 root root 4.0K Jun  9 11:45 modules-enabled","-rw-r--r-- 1 root root 1.5K Nov 30  2023 nginx.conf","-rw-r--r-- 1 root root  180 Nov 30  2023 proxy_params","-rw-r--r-- 1 root root  636 Nov 30  2023 scgi_params","drwxr-xr-x 2 root root 4.0K Jun 10 08:46 sites-available","drwxr-xr-x 2 root root 4.0K Jun 10 08:46 sites-enabled","drwxr-xr-x 2 root root 4.0K Jun 10 08:46 snippets","-rw-r--r-- 1 root root  664 Nov 30  2023 uwsgi_params","-rw-r--r-- 1 root root 3.0K Nov 30  2023 win-utf"]
}PLAY RECAP *************************************************************************
10.0.0.232                 : ok=6    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   [root@ansible99 playbook]# 

4.层级定义变量

4.1 使用点号'.'区分层级变量

	1.定义层级变量
[root@ansible99 playbook]# cat pkg2.yml 
lnmp:tools:pkg: wget
[root@ansible99 playbook]# 2.编写playbook局部
[root@ansible99 playbook]# cat 06-layer-var.yaml 
- hosts: mastervars_files:- pkg2.ymltasks:- name: remove "{{ lnmp.tools.pkg }}" packageapt:name:- "{{ lnmp.tools.pkg }}"state: absent
[root@ansible99 playbook]#3.测试验证
[root@ansible99 playbook]# ansible-playbook  06-layer-var.yaml PLAY [master] **********************************************************************TASK [Gathering Facts] *************************************************************
ok: [10.0.0.233]
ok: [10.0.0.231]
ok: [10.0.0.232]TASK [remove "wget" package] *******************************************************
changed: [10.0.0.231]
changed: [10.0.0.233]
changed: [10.0.0.232]PLAY RECAP *************************************************************************
10.0.0.231                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.232                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.233                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   [root@ansible99 playbook]# 

4.2 使用方括号'[]'区分层级变量 (官方推荐写法)

	1.定义层级变量
[root@ansible99 playbook]# cat pkg2.yml 
lnmp:tools:pkg: wget
[root@ansible99 playbook]# 2.编写playbook局部
[root@ansible99 playbook]# cat 07-layer2-var.yaml 
- hosts: mastervars_files:- pkg2.ymltasks:- name: install "{{ lnmp['tools']['pkg'] }}" packageapt:name:- "{{ lnmp['tools']['pkg'] }}"state: present
[root@ansible99 playbook]# 3.测试验证
[root@ansible99 playbook]# ansible-playbook 07-layer2-var.yaml PLAY [master] **********************************************************************TASK [Gathering Facts] *************************************************************
ok: [10.0.0.233]
ok: [10.0.0.232]
ok: [10.0.0.231]TASK [install "wget" package] ******************************************************
ok: [10.0.0.233]
ok: [10.0.0.231]
ok: [10.0.0.232]PLAY RECAP *************************************************************************
10.0.0.231                 : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.232                 : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.0.0.233                 : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   [root@ansible99 playbook]# 温馨提示:语法风格差异:

4.3 语法风格对比

类型 描述 风格
点号(".") 键名必须是有效的标识符,不能包含特殊字符,空格,连字符等。 类似于python的对象属性访问。
方括号("[]") 可以处理任何字符串键名 类似于python字典键值对访问。
如上所示,官方两种风格都支持,但对于特殊的键名称推荐使用方括号来引用。举个例子:包含特殊字符的键名:lnmp['web-server']['php-version']以数字开头的键名:lnmp['2nd-level']['key']包含空格的键名:lnmp['my key']['sub key']

5.Facts缓存

5.1 facts概述

ansible facts是被管理主机上通过ansible自动采集发现的变量,facts包含每台特定的主机信息。facts包含被控制端的主机名,IP地址,系统版本,CPU数量,内存状态,磁盘状态等等。

5.2 facts内置变量

5.2.1 系统发行版 & 内核架构

变量名 用途说明
ansible_distribution 系统发行版名称(CentOS/Ubuntu/Rocky/Windows)
ansible_distribution_version 系统完整版本号
ansible_distribution_major_version 系统主版本号
ansible_os_family 系统家族(RedHat/Debian/Suse/Windows)
ansible_system 内核标识(Linux/Windows/Darwin)
ansible_kernel 内核版本号
ansible_architecture CPU 架构(x86_64/aarch64/armv7l)
ansible_pkg_mgr 系统包管理器(yum/dnf/apt/chocolatey)
ansible_timezone 系统时区
ansible_selinux SELinux 完整状态字典
ansible_selinux_mode SELinux 运行模式

5.2.2 CPU 硬件信息

变量名 用途说明
ansible_processor CPU 型号列表
ansible_processor_count 物理 CPU 颗数
ansible_processor_cores 单 CPU 物理核心数
ansible_processor_vcpus 逻辑 CPU 总数量(含超线程)
ansible_threads_per_core 每核心线程数
ansible_cpu_min_freq_hz CPU 最小运行频率
ansible_cpu_max_freq_hz CPU 最大运行频率

5.2.3 内存与 Swap

变量名 用途说明
ansible_memtotal_mb 物理总内存 (MB)
ansible_memfree_mb 空闲物理内存 (MB)
ansible_swaptotal_mb Swap 分区总大小 (MB)
ansible_swapfree_mb 空闲 Swap 大小 (MB)
ansible_memory_mb 内存复合字典(real/swap 分组存储 total/free/cached)

5.2.4 磁盘与挂载设备

变量名 用途说明
ansible_mounts 所有挂载点数组,包含路径、设备、容量、文件系统
ansible_devices 磁盘设备字典(sda/vda 等),包含分区信息

5.2.5 网络相关(高频)

变量名 用途说明
ansible_hostname 主机短名
ansible_fqdn 完整主机域名
ansible_nodename uname -n 获取的主机名
ansible_default_ipv4 默认出口 IPv4 完整字典(地址 / 网关 / 网卡 / 掩码)
ansible_default_ipv6 默认出口 IPv6 完整字典
ansible_interfaces 全部网卡名称数组
ansible_网卡名 (ansible_eth0/ens33) 单网卡详情(IP、MAC、MTU)

5.2.6 虚拟化 / 云主机标识

变量名 用途说明
ansible_virtualization_type 虚拟化类型(kvm/qemu/vmware/docker/physical)
ansible_virtualization_role guest 虚拟机 /host 宿主机
ansible_container 是否为容器(true/false)
ansible_container_type 容器类型(docker/podman/lxc)
ansible_ec2_* AWS EC2 云主机信息
ansible_azure_* Azure 云主机信息
ansible_gce_* GCP 谷歌云主机信息
ansible_system_vendor 服务器硬件厂商(Dell/VMware/QEMU)

5.2.7 用户、环境、时间

变量名 用途说明
ansible_user_id 当前执行 ansible 用户 UID
ansible_group_id 当前执行 ansible 用户 GID
ansible_user_dir 当前用户家目录
ansible_env 系统环境变量全量字典
ansible_date_time 时间复合字典
ansible_date_time.iso8601 ISO 标准格式时间
ansible_date_time.epoch 时间戳

5.2.8 Windows 系统专属 Facts

变量名 用途说明
ansible_os_version Windows 系统版本
ansible_windows_domain Windows 域信息
ansible_windows_systemroot Windows 系统目录
ansible_total_physical_memory_mb Windows 总物理内存

5.2.9 playbook 全局内置变量(非 setup 采集 facts,配套使用)

变量名 用途说明
inventory_hostname Inventory 清单内定义的主机名
inventory_hostname_short Inventory 主机短名
groups 全部分组主机字典
group_names 当前主机所属分组列表
play_hosts 当前 play 执行的主机列表
ansible_version Ansible 工具版本信息字典

5.3 测试案例

	1.查看资源清单
(venv) [root@ansible99 playbook]# cat /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini
[all]
master231 ansible_host=10.0.0.231  ip=10.0.0.231
master232 ansible_host=10.0.0.232  ip=10.0.0.232
master233 ansible_host=10.0.0.233  ip=10.0.0.233
worker66 ansible_host=10.0.0.66  ip=10.0.0.66
worker77 ansible_host=10.0.0.77  ip=10.0.0.77[kube_control_plane]
master231
master232
master233[etcd]
master231
master232
master233[kube_node]
worker66
worker77(venv) [root@ansible99 playbook]# 2.编写playbook
(venv) [root@ansible99 playbook]# cat 08-facts.yaml 
- hosts: kube_control_plane# 获取客户端信息tasks:- name: print server infodebug:msg: '"{{ ansible_hostname }}" --- "{{ ansible_default_ipv4.address }}" --- {{ ansible_virtualization_type }}'- name: mkdir dirfile:path: /tmp/{{ ansible_hostname }}-{{ ansible_default_ipv4.address }}state: directory
(venv) [root@ansible99 playbook]# 3.测试验证
(venv) [root@ansible99 playbook]# ansible-playbook  -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini  08-facts.yaml PLAY [kube_control_plane] **************************************************************************************************************************************************************************************TASK [Gathering Facts] *****************************************************************************************************************************************************************************************
ok: [master232]
ok: [master233]
ok: [master231]TASK [print server info] ***************************************************************************************************************************************************************************************
ok: [master231] => {"msg": "\"master231\" --- \"10.0.0.231\" --- VMware"
}
ok: [master232] => {"msg": "\"master232\" --- \"10.0.0.232\" --- VMware"
}
ok: [master233] => {"msg": "\"master233\" --- \"10.0.0.233\" --- VMware"
}TASK [mkdir dir] ***********************************************************************************************************************************************************************************************
ok: [master231]
ok: [master232]
ok: [master233]PLAY RECAP *****************************************************************************************************************************************************************************************************
master231                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
master232                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
master233                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   (venv) [root@ansible99 playbook]# 
(venv) [root@ansible99 playbook]# 

6.template使用变量

6.1 template的作用

如果使用file拷贝一个文件到目的主机,正常情况下就会直接将文件拷贝到目标主机。但是,如果要拷贝的文件中用到了变量,则用file拷贝并不会引用变量,而是直接拷贝文件内容,使用template拷贝则拷贝的文件有变量引用的话,拷贝过去时也会引用相应的变量。

6.2 测试验证

	1.准备模板文件
(venv) [root@ansible99 playbook]# cat  server.info 
------------------------------------------------------------------------------------------------------
- 服务器名称:     -  {{ ansible_hostname }}                                                      
- 服务器IP地址:   -  {{ ansible_default_ipv4.address }}                                         
- 服务器内存:     -  {{ ansible_memtotal_mb }}                                                  
- 虚拟化类型      -  {{ ansible_virtualization_type }} / {{ ansible_virtualization_role }}
- CPU数量:        -  {{ ansible_processor_count }}                                                    
- 逻辑 CPU 总数量 -  {{ ansible_processor_vcpus }}                                        
- CPU型号         -  {{ ansible_processor }}                                                          
-----------------------------------------------------------------------------------------------------
(venv) [root@ansible99 playbook]# 2.编写playbook
(venv) [root@ansible99 playbook]# cat 09-template.yaml
- hosts: kube_control_planetasks:- name: copy server.infotemplate:src: server.infodest: /root/server.txt
(venv) [root@ansible99 playbook]# 
(venv) [root@ansible99 playbook]# 3.执行playbook
(venv) [root@ansible99 playbook]# cat /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini
[all]
master231 ansible_host=10.0.0.231  ip=10.0.0.231
master232 ansible_host=10.0.0.232  ip=10.0.0.232
master233 ansible_host=10.0.0.233  ip=10.0.0.233
worker66 ansible_host=10.0.0.66  ip=10.0.0.66
worker77 ansible_host=10.0.0.77  ip=10.0.0.77[kube_control_plane]
master231
master232
master233(venv) [root@ansible99 playbook]# 
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 09-template.yamlPLAY [kube_control_plane] **************************************************************************************************************************************************************************************TASK [Gathering Facts] *****************************************************************************************************************************************************************************************
ok: [master231]
ok: [master233]
ok: [master232]TASK [copy server.info] ****************************************************************************************************************************************************************************************
changed: [master231]
changed: [master233]
changed: [master232]PLAY RECAP *****************************************************************************************************************************************************************************************************
master231                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
master232                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
master233                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   (venv) [root@ansible99 playbook]# 4.查看目标主机信息
[root@master231 ~]# cat server.txt 
------------------------------------------------------------------------------------------------------
- 服务器名称:     -  master231                                                      
- 服务器IP地址:   -  10.0.0.231                                         
- 服务器内存:     -  3868                                                  
- 虚拟化类型      -  VMware / guest
- CPU数量:        -  2                                                    
- 逻辑 CPU 总数量 -  2                                        
- CPU型号         -  ['0', 'GenuineIntel', 'Intel(R) Core(TM) Ultra 7 356H', '1', 'GenuineIntel', 'Intel(R) Core(TM) Ultra 7 356H']                                                          
-----------------------------------------------------------------------------------------------------
[root@master231 ~]# [root@master232 ~]# cat server.txt 
------------------------------------------------------------------------------------------------------
- 服务器名称:     -  master232                                                      
- 服务器IP地址:   -  10.0.0.232                                         
- 服务器内存:     -  3868                                                  
- 虚拟化类型      -  VMware / guest
- CPU数量:        -  2                                                    
- 逻辑 CPU 总数量 -  2                                        
- CPU型号         -  ['0', 'GenuineIntel', 'Intel(R) Core(TM) Ultra 7 356H', '1', 'GenuineIntel', 'Intel(R) Core(TM) Ultra 7 356H']                                                          
-----------------------------------------------------------------------------------------------------
[root@master232 ~]# [root@master233 ~]# cat server.txt 
------------------------------------------------------------------------------------------------------
- 服务器名称:     -  master233                                                      
- 服务器IP地址:   -  10.0.0.233                                         
- 服务器内存:     -  3867                                                  
- 虚拟化类型      -  VMware / guest
- CPU数量:        -  2                                                    
- 逻辑 CPU 总数量 -  2                                        
- CPU型号         -  ['0', 'GenuineIntel', 'Intel(R) Core(TM) Ultra 7 356H', '1', 'GenuineIntel', 'Intel(R) Core(TM) Ultra 7 356H']                                                          
-----------------------------------------------------------------------------------------------------
[root@master233 ~]# 

四.Playbook实战之流程控制

1.playbook条件语句概述

任何编程语言几乎都支持流程控制,条件判断,在我们使用ansible的过程中,条件判断也是经常会被使用的。举个例子:- 1.安装同一种软件在不同系统上其软件包名称可能不一致(比如nfs服务),可以通过判断系统来对软件包名称进行修改;- 2.在部署K8S集群之前,我们可以判断现有的环境,是否需要做Linux基础优化(比如禁用swap,修改内核参数,开启本地DNS缓存等);

2.when判断实战

2.1 when条件表达式

精确匹配:when: ansible_hostname == "master231"模糊匹配:when: ansible_hostname is match "master232"when: ansible_hostname is search "master233"

2.2 when案例

	1.编写playbook
(venv) [root@ansible99 playbook]# cat 10-when.yaml 
- hosts: kube_control_planetasks:- name: remove wgetapt:name: wgetstate: absentwhen: ansible_hostname is match "master"- name: install treeapt:name: treestate: presentwhen: ansible_hostname == "master232"- name: remove curlapt:name: curlstate: absentwhen: - ansible_hostname is match "master" - ansible_default_ipv4.address == "10.0.0.233"# 上面3行,我们可以简写为下面的一行代码# when: (ansible_hostname is match "master") and (ansible_default_ipv4.address == "10.0.0.233")- name: install lrzszapt:name: lrzszstate: presentwhen: (ansible_hostname is match "master233") or (ansible_default_ipv4.address == "10.0.0.231")(venv) [root@ansible99 playbook]# 2.测试验证
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 10-when.yaml 

2.3 部署nginx服务案例

	1.准备nginx的配置文件
[root@ansible99 playbook]# cat nginx.conf
error_log stderr notice;worker_processes 2;
worker_rlimit_nofile 130048;
worker_shutdown_timeout 10s;events {multi_accept on;use epoll;worker_connections 16384;
}stream {upstream kube_apiserver {least_conn;server 10.0.0.231:6443;server 10.0.0.232:6443;server 10.0.0.233:6443;}server {listen        127.0.0.1:6443;proxy_pass    kube_apiserver;proxy_timeout 10m;proxy_connect_timeout 1s;}
}http {aio threads;aio_write on;tcp_nopush on;tcp_nodelay on;keepalive_timeout 5m;keepalive_requests 100;reset_timedout_connection on;server_tokens off;autoindex off;server {listen 8081;location /healthz {access_log off;return 200;}location /stub_status {stub_status on;access_log off;}}
}
[root@ansible99 playbook]# 2.编写playbook剧本
(venv) [root@ansible99 playbook]# cat  11-nginx-when.yaml 
- hosts: kube_control_planetasks:- name: install nginx serviceapt:name: nginxstate: present- name:  configure nginx copy:src: nginx.confdest: /root/nginx.conf- name: nginx -tcommand: nginx -tregister: nginx_result# ansible遇到错误就会退出剧本往下执行,如果有错误我们护理后,才能继续往下执行哟。ignore_errors: yes- name: print nginx_resultdebug:msg: "{{ nginx_result }}" - name: start nginx servicesystemd:name: nginxstate: restartedenabled: yes# 如果'nginx_result.rc'的结果为0,表示没有错误,于是就可以重启nginx服务。when: nginx_result.rc is match "0"# 也可以使用'search'进行搜索。# when: nginx_result.rc is search "0"(venv) [root@ansible99 playbook]# 2.执行剧本
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 11-nginx-when.yaml 

3.Handlers实战

3.1 handlers概述

handler用来执行某些条件下的任务,比如当配置发生变化的时候,通过notify出发handler去重启服务。

3.2 实战案例

	1.编写playbook
(venv) [root@ansible99 playbook]# cat 12-nginx-notify.yaml 
- hosts: kube_control_planetasks:- name: install nginx serviceapt:name: nginxstate: present- name:  configure nginx copy:src: nginx.confdest: /root/nginx.conf# 监控配置文件是否发生变化,如果有发生变化则触发同名称的handlers重启服务。notify: Restart Nginx- name: nginx -tcommand: nginx -tregister: nginx_resultignore_errors: yes- name: start nginx servicesystemd:name: nginxstate: restartedenabled: yeswhen: nginx_result.rc is match "0"# 定义触发器handlers:# 当前的触发器我们的操作是用来重启服务的。- name: Restart Nginxsystemd:name: nginxstate: restartedwhen: nginx_result.rc is search "0"
(venv) [root@ansible99 playbook]# 2.第一次执行,会按照服务,触发HANDLER执行
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 12-nginx-notify.yaml PLAY [kube_control_plane] **************************************************************************************************************************************************************************************TASK [Gathering Facts] *****************************************************************************************************************************************************************************************
ok: [master232]
ok: [master231]TASK [install nginx service] ***********************************************************************************************************************************************************************************
changed: [master232]
changed: [master231]TASK [configure nginx] *****************************************************************************************************************************************************************************************
changed: [master232]
changed: [master231]TASK [nginx -t] ************************************************************************************************************************************************************************************************
changed: [master231]
changed: [master232]TASK [start nginx service] *************************************************************************************************************************************************************************************
changed: [master231]
changed: [master232]RUNNING HANDLER [Restart Nginx] ********************************************************************************************************************************************************************************
changed: [master232]
changed: [master231]PLAY RECAP *****************************************************************************************************************************************************************************************************
master231                  : ok=6    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
master232                  : ok=6    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   (venv) [root@ansible99 playbook]# 2.第二次执行如果配置文件没有发生变化,则不会触发HANDLER
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 12-nginx-notify.yaml PLAY [kube_control_plane] **************************************************************************************************************************************************************************************TASK [Gathering Facts] *****************************************************************************************************************************************************************************************
ok: [master231]
ok: [master232]TASK [install nginx service] ***********************************************************************************************************************************************************************************
ok: [master232]
ok: [master231]TASK [configure nginx] *****************************************************************************************************************************************************************************************
ok: [master231]
ok: [master232]TASK [nginx -t] ************************************************************************************************************************************************************************************************
changed: [master231]
changed: [master232]TASK [start nginx service] *************************************************************************************************************************************************************************************
changed: [master232]
changed: [master231]PLAY RECAP *****************************************************************************************************************************************************************************************************
master231                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
master232                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   (venv) [root@ansible99 playbook]# 

4.循环语句

4.1 循环语句的作用

和其他编程语言类似,循环语句的作用就是减少重复性代码,让代码看起来更简洁。举个例子,如果我们创建10个文件,我们可以写10个file模块,那如果要创建1w个文件呢?

4.2 实战案例

	1.编写playbook
(venv) [root@ansible99 playbook]# cat 13-loop-casedemo.yaml 
- hosts: kube_control_plane# 执行时直接跳过 Gathering Facts 步骤(不再运行 setup 模块)gather_facts: falsetasks:- name: restart ssh nginx servicesystemd:name: "{{ item }}"state: restartedloop:- nginx- ssh- name: create filefile:path: /tmp/{{ item.path }}state: touchowner: "{{ item.owner }}"group: "{{ item.group }}"mode: "{{ item.mode }}"loop:- { path: a.txt, owner: yinzhengjie, group: yinzhengjie, mode: '0600' }- { path: b.txt, owner: root, group: root, mode: '0666' }- name: copy filecopy:src: "{{ item.src }}"dest: "{{ item.dest }}"owner: "{{ item.owner }}" loop:- { src: /etc/hosts, dest: /opt, owner: yinzhengjie }- { src: /etc/passwd, dest: /root, owner: root }- name: create groupgroup:name: "{{ item.name }}"gid: "{{ item.gid }}"loop:- { name: k8s, gid: '456'}- { name: bigdata, gid: '789'}- name: create useruser:name: "{{ item.name }}"uid: "{{ item.uid }}"group: "{{ item.group }}"shell: "{{ item.shell }}"create_home: "{{ item.home }}"loop:- { name: test01, uid: '111', group: k8s, shell: /bin/bash, home: true }- { name: test02, uid: '222', group: bigdata, shell: /sbin/nologin, home: false }(venv) [root@ansible99 playbook]# 
(venv) [root@ansible99 playbook]# 2.执行剧本
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 13-loop-casedemo.yaml 

5.任务标签

5.1 什么是任务标签

默认情况下,ansible在执行一个playbook时,会执行playbook中定义的所有任务。ansible的标签功能给单独任务甚至整个playbook打上标签,然后利用这些标签来指定要运行playbook中的个别任务,活不执行指定的任务。

5.2 实战案例

 	1.编写playbook(venv) [root@ansible99 playbook]# cat  14-tags-casedemo.yaml
- hosts: kube_control_planegather_facts: falsetasks:- name: restart ssh nginx servicesystemd:name: "{{ item }}"state: restartedloop:- nginx- ssh# 给指定的个别任务打标签tags: xixi- name: create filefile:path: /tmp/{{ item.path }}state: touchowner: "{{ item.owner }}"group: "{{ item.group }}"mode: "{{ item.mode }}"loop:- { path: a.txt, owner: yinzhengjie, group: yinzhengjie, mode: '0600' }- { path: b.txt, owner: root, group: root, mode: '0666' }tags: xixi- name: copy filecopy:src: "{{ item.src }}"dest: "{{ item.dest }}"owner: "{{ item.owner }}" loop:- { src: /etc/hosts, dest: /opt, owner: yinzhengjie }- { src: /etc/passwd, dest: /root, owner: root }tags: haha- name: create groupgroup:name: "{{ item.name }}"gid: "{{ item.gid }}"loop:- { name: k8s, gid: '456'}- { name: bigdata, gid: '789'}- name: create useruser:name: "{{ item.name }}"uid: "{{ item.uid }}"group: "{{ item.group }}"shell: "{{ item.shell }}"create_home: "{{ item.home }}"loop:- { name: test01, uid: '111', group: k8s, shell: /bin/bash, home: true }- { name: test02, uid: '222', group: bigdata, shell: /sbin/nologin, home: false }(venv) [root@ansible99 playbook]# 2.查看剧本存在的标签列表
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 14-tags-casedemo.yaml --list-tagsplaybook: 14-tags-casedemo.yamlplay #1 (kube_control_plane): kube_control_plane	TAGS: []TASK TAGS: [haha, xixi]
(venv) [root@ansible99 playbook]# 3.只执行包含一个或多个tags的tasks
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 14-tags-casedemo.yaml -t xixi(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 14-tags-casedemo.yaml -t xixi,haha4.跳过带有指定标签的tasks
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 14-tags-casedemo.yaml --skip-tags xixi(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini 14-tags-casedemo.yaml --skip-tags xixi,haha

6.文件复用-task拆解

6.1 什么是文件复用

在之前写playbook的过程中,我们发现写多个playbook没有办法一键执行,这样我们还要单个playbook挨个去执行,很鸡肋。所以在playbook中有一个功能,叫做'include_tasks'用来动态调用task任务列表。这样做的目的可以避免单个playbook文件内容过大的情况。当然,也支持直接导入一个完整的palybook,则需要使用'import_playbook'关键字导入即可。

6.2 实战案例

	1.准备任务文件
(venv) [root@ansible99 playbook]# cat user-group.task 
- name: delete groupgroup:name: "{{ item.name }}"gid: "{{ item.gid }}"state: absentloop:- { name: dba, gid: '456'}- { name: linux, gid: '789'}- name: delete useruser:name: "{{ item.name }}"uid: "{{ item.uid }}"group: "{{ item.group }}"shell: "{{ item.shell }}"create_home: "{{ item.home }}"state: absentloop:- { name: mongodb, uid: '333', group: dba, shell: /bin/bash, home: true }- { name: clickhouse, uid: '555', group: linux, shell: /sbin/nologin, home: false }(venv) [root@ansible99 playbook]# 2.准备剧本
(venv) [root@ansible99 playbook]# cat 15-include_tasks.yaml 
- hosts: kube_control_planegather_facts: falsetasks:- name: xixiinclude_tasks:  remove-nginx.task# 由于我关闭了facts的采集,此处就不要用ansible_hostname变量来判断了。when: inventory_hostname is match "master231"- name: hahainclude_tasks: user-group.taskwhen: inventory_hostname is match "master232"
(venv) [root@ansible99 playbook]# 3.测试验证
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini  15-include_tasks.yaml PLAY [kube_control_plane] **************************************************************************************************************************************************************************************TASK [xixi] ****************************************************************************************************************************************************************************************************
skipping: [master232]
included: /root/playbook/remove-nginx.task for master231TASK [remove nginx service] ************************************************************************************************************************************************************************************
changed: [master231]TASK [configure nginx] *****************************************************************************************************************************************************************************************
ok: [master231]TASK [haha] ****************************************************************************************************************************************************************************************************
skipping: [master231]
included: /root/playbook/user-group.task for master232TASK [delete group] ********************************************************************************************************************************************************************************************
ok: [master232] => (item={'name': 'dba', 'gid': '456'})
ok: [master232] => (item={'name': 'linux', 'gid': '789'})TASK [delete user] *********************************************************************************************************************************************************************************************
ok: [master232] => (item={'name': 'mongodb', 'uid': '333', 'group': 'dba', 'shell': '/bin/bash', 'home': True})
ok: [master232] => (item={'name': 'clickhouse', 'uid': '555', 'group': 'linux', 'shell': '/sbin/nologin', 'home': False})PLAY RECAP *****************************************************************************************************************************************************************************************************
master231                  : ok=3    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
master232                  : ok=3    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   (venv) [root@ansible99 playbook]# 

7.抑制changed

7.1 什么是抑制changed

被管理主机没有发生变化,可以使用参数将change状态改为ok。

7.2 实战案例

	1.编写剧本
(venv) [root@ansible99 playbook]# cat  16-changed.yaml 
- hosts: kube_control_planegather_facts: falsetasks:- name: install nginx serviceapt:name: nginxstate: present- name:  configure nginx copy:src: nginx.confdest: /root/nginx.confnotify: Restart Nginx- name: nginx -tcommand: nginx -tregister: nginx_resultignore_errors: yes# 如果没有任何变化,颜色变为绿色。changed_when: false- name: start nginx servicesystemd:name: nginxstate: restartedenabled: yeswhen: nginx_result.rc is match "0"handlers:- name: Restart Nginxsystemd:name: nginxstate: restartedwhen: nginx_result.rc is search "0"
(venv) [root@ansible99 playbook]# 2.测试验证
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini  16-changed.yaml 

五.jinja2模板及Role角色

1.jinja2模板

1.1 什么是jinja2模板

jinja2是python大全功能模板引擎,ansible通常会使用jinja2模板来修改被管理主机的配置文件等。使用ansible的jinja2模板也就是使用template模块,该模块和copy模块一样,都是将文件复制到远端主机上去,但是区别在于,template模块可以获取到文件的变量,而copy则是原封不动的把文件内容复制过去。

1.2 实战案例-for循环和if语句

	1.编写jinja2模板文件内容
(venv) [root@ansible99 playbook]# cat nginx.conf.j2 
upstream {{ server_name }} {{% for apiserver_ip in range(231,234) %}server 10.0.0.{{ apiserver_ip }}:{{ apiserver_port }};{% endfor %}
}server {listen        80;proxy_pass    {{ server_name }};location / {root /yinzhengjie/code;index index.html;proxy_pass http://www.yinzhengjie.com;proxy_set_header Host $http_host;}
}
(venv) [root@ansible99 playbook]# 
(venv) [root@ansible99 playbook]# cat keepalived.conf.j2 
global_defs {router_id {{ ansible_hostname }}
}vrrp_script chk_apiserver {script "/etc/keepalived/check_apiserver.sh"interval 2weight -20
}vrrp_instance VI_K8S_APISERVER {
{% if ansible_fqdn == "master231" %} state MASTERpriority 150
{% else %}state BACKUPpriority 100
{% endif %}interface ens192virtual_router_id 51advert_int 1nopreemptauthentication {auth_type PASSauth_pass yinzhengjie@k8s}virtual_ipaddress {10.0.0.100/24 dev ens192 label ens192:vip}track_script {chk_apiserver}
}
(venv) [root@ansible99 playbook]# 2.编写剧本
(venv) [root@ansible99 playbook]# cat 17-copy-nginx-conf.yaml 
- hosts: kube_control_planegather_facts: truevars:- apiserver_port: 6443- server_name: www.yinzhengjie.comtasks:- name:  copy nginx  conftemplate:src: nginx.conf.j2dest: /root/apiserver.conf- name:  copy keepalived  conftemplate:src: keepalived.conf.j2dest: /root/keepalved.conf(venv) [root@ansible99 playbook]# 3.测试验证
(venv) [root@ansible99 playbook]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini  17-copy-nginx-conf.yaml 

2.Roles角色

2.1 什么是Roles

roles对playbook的一种重新编排,我们可以将一个项目定义为一个角色,将该项目的所有项目都放在该角色目录。roles角色默认的目录有:- defaults:存放默认的配置信息。- files:存放的是配置文件,软件包,依赖库之类的文件。- handlers:定义触发器相关的动作。- meta:存放依赖的文件。- tasks:定义任务。- templates存放jinja2模板文件。- tests:存放测试的相关文件。- vars:存放变量的目录。- README.md:存放项目的说明文件。

2.2 实战案例-部署rsync服务

	1.创建存储角色的目录
(venv) [root@ansible99 playbook]# mkdir roles
(venv) [root@ansible99 playbook]# 
(venv) [root@ansible99 playbook]# cd roles/
(venv) [root@ansible99 roles]# 
(venv) [root@ansible99 roles]# ll
total 8
drwxr-xr-x 2 root root 4096 Jun 15 19:21 ./
drwxr-xr-x 3 root root 4096 Jun 15 19:21 ../
(venv) [root@ansible99 roles]# 2.生成默认的角色目录结构
(venv) [root@ansible99 roles]# ansible-galaxy init k8s
- Role k8s was created successfully
(venv) [root@ansible99 roles]# 
(venv) [root@ansible99 roles]# ll
total 12
drwxr-xr-x  3 root root 4096 Jun 15 19:21 ./
drwxr-xr-x  3 root root 4096 Jun 15 19:21 ../
drwxr-xr-x 10 root root 4096 Jun 15 19:21 k8s/
(venv) [root@ansible99 roles]# 
(venv) [root@ansible99 roles]# tree k8s/
k8s/
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars└── main.yml9 directories, 8 files
(venv) [root@ansible99 roles]# 
(venv) [root@ansible99 roles]# 
(venv) [root@ansible99 roles]# cd k8s/
(venv) [root@ansible99 k8s]# 
(venv) [root@ansible99 k8s]# ll
total 44
drwxr-xr-x 10 root root 4096 Jun 15 19:21 ./
drwxr-xr-x  3 root root 4096 Jun 15 19:21 ../
drwxr-xr-x  2 root root 4096 Jun 15 19:21 defaults/
drwxr-xr-x  2 root root 4096 Jun 15 19:21 files/
drwxr-xr-x  2 root root 4096 Jun 15 19:21 handlers/
drwxr-xr-x  2 root root 4096 Jun 15 19:21 meta/
-rw-r--r--  1 root root 1328 Jun 15 19:21 README.md
drwxr-xr-x  2 root root 4096 Jun 15 19:21 tasks/
drwxr-xr-x  2 root root 4096 Jun 15 19:21 templates/
drwxr-xr-x  2 root root 4096 Jun 15 19:21 tests/
drwxr-xr-x  2 root root 4096 Jun 15 19:21 vars/
(venv) [root@ansible99 k8s]# 3.编写剧本文件内容
(venv) [root@ansible99 roles]# cat  k8s/tasks/main.yml 
---
# tasks file for k8s- name: Install Rsync Serverapt:name: rsyncstate: presenttags: xixi- name: Configure Rsync Server# 不用添加路径,他会自动去"template"目录下找文件template:src: "{{ item.src }}"dest: "{{ item.dest }}"mode: "{{ item.mode }}"loop:- { src: rsyncd.conf.j2, dest: /etc/rsyncd.conf, mode: '0644'}- { src: rsync.passwd, dest: /etc/rsync.passwd, mode: '0600' }notify: Restart Rsync Server- name: Create Group "{{ group }}"group:name: "{{ group }}"gid: 888- name: Create User "{{ user }}"user:name: "{{ user }}"uid: 666group: "{{ group }}"shell: /sbin/nologincreate_home: false- name: Create dir "{{ dir }}"file:path: "{{ dir }}"state: directoryowner: "{{ user }}"group: "{{ group }}"- name: Start Rsync Serversystemd:name: rsyncstate: startedenabled: yes
(venv) [root@ansible99 roles]# 4.编写配置文件目录
(venv) [root@ansible99 k8s]# cat  templates/rsyncd.conf.j2 
uid = {{ user }}
gid = {{ group }}
port = {{ port }}
fake super = yes
use chroot = no
max connections = 200
timeout = 600
ignore errors
read only = false
list = false
secrets file = /etc/rsync.passwd
log file = /var/log/rsyncd.log[backup]
path = {{ dir }}
comment = 备份目录[data]
path = /data
(venv) [root@ansible99 k8s]# 5.准备认证文件
(venv) [root@ansible99 k8s]# cat templates/rsync.passwd 
rysnc_backup:123
(venv) [root@ansible99 k8s]# 6.定义变量
(venv) [root@ansible99 k8s]# cat   vars/main.yml 
---
# vars file for k8suser: yinzhengjie
group: k8s
port: 873
dir: /yinzhengjie/data/k8s
(venv) [root@ansible99 k8s]# 7.定义触发器
(venv) [root@ansible99 k8s]# cat  handlers/main.yml 
---
# handlers file for k8s- name: Restart Rsync Serversystemd:name: rsyncstate: restarted
(venv) [root@ansible99 k8s]# 8.删除不必要的文件
(venv) [root@ansible99 roles]# rm -rf k8s/{defaults,meta,tests,README.md}
(venv) [root@ansible99 roles]# 
(venv) [root@ansible99 roles]# tree k8s
k8s
├── files
├── handlers
│   └── main.yml
├── tasks
│   └── main.yml
├── templates
│   ├── rsyncd.conf.j2
│   └── rsync.passwd
└── vars└── main.yml6 directories, 5 files
(venv) [root@ansible99 roles]# 9.playbook调用角色
(venv) [root@ansible99 roles]# cat start.yaml
- hosts: allroles:- role: backupwhen: ansible_hostname is match "master232"
(venv) [root@ansible99 roles]# 10.测试验证
(venv) [root@ansible99 roles]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini  start.yaml  

2.3 实战案例-部署nfs服务

	1.准备环境初始目录
(venv) [root@ansible99 roles]# mkdir nfs
(venv) [root@ansible99 roles]# 
(venv) [root@ansible99 roles]# cd nfs/
(venv) [root@ansible99 nfs]# 
(venv) [root@ansible99 nfs]# ls ../k8s/ | xargs mkdir
(venv) [root@ansible99 nfs]# 
(venv) [root@ansible99 nfs]# ll
total 28
drwxr-xr-x 7 root root 4096 Jun 15 21:01 ./
drwxr-xr-x 4 root root 4096 Jun 15 21:01 ../
drwxr-xr-x 2 root root 4096 Jun 15 21:01 files/
drwxr-xr-x 2 root root 4096 Jun 15 21:01 handlers/
drwxr-xr-x 2 root root 4096 Jun 15 21:01 tasks/
drwxr-xr-x 2 root root 4096 Jun 15 21:01 templates/
drwxr-xr-x 2 root root 4096 Jun 15 21:01 vars/
(venv) [root@ansible99 nfs]# 
(venv) [root@ansible99 nfs]# tree .
.
├── files
├── handlers
├── tasks
├── templates
└── vars6 directories, 0 files2.编写任务
(venv) [root@ansible99 nfs]# cat tasks/main.yaml 
- name: Install nfs Serverapt:name: nfs-kernel-serverstate: present- name: Configure NFS Servertemplate:src: exports.j2dest: /etc/exportsnotify: Restart NFS Service- name: Create Group "{{ group }}"group:name: "{{ group }}"gid: "{{ gid }}"- name: Create User "{{ user }}"user:name: "{{ user }}"uid: "{{ uid }}"group: "{{ group }}"shell: /sbin/nologincreate_home: false- name: create "{{ dir }}"file:path: "{{ dir }}"state: directoryowner: "{{ user }}"group:  "{{ group }}"- name: Start nfs Serversystemd:name: nfs-serverstate: started
(venv) [root@ansible99 nfs]# 3.编写handlers
(venv) [root@ansible99 nfs]# cat handlers/main.yaml 
- name: Restart NFS Servicesystemd:name: nfs-serverstate: restarted(venv) [root@ansible99 nfs]# 4.准备模板文件
(venv) [root@ansible99 nfs]# cat templates/exports.j2 
{{ dir }} {{ ip }}(rw,sync,all_squash,anonuid={{ uid }},anongid={{ gid }})
(venv) [root@ansible99 nfs]# 5.查看变量
(venv) [root@ansible99 nfs]# cat vars/main.yaml 
user: yinzhengjie
group: k8s
dir: /yinzhengjida/data
ip: 10.0.0.0/24
uid: 666
gid: 888
(venv) [root@ansible99 nfs]# 6.编写playbook调用角色
(venv) [root@ansible99 nfs]# cd ..
(venv) [root@ansible99 roles]# ll
total 20
drwxr-xr-x 4 root root 4096 Jun 15 21:45 ./
drwxr-xr-x 3 root root 4096 Jun 15 19:21 ../
drwxr-xr-x 7 root root 4096 Jun 15 20:04 k8s/
drwxr-xr-x 7 root root 4096 Jun 15 21:01 nfs/
-rw-r--r-- 1 root root  154 Jun 15 21:45 start.yaml
(venv) [root@ansible99 roles]# 
(venv) [root@ansible99 roles]# cat start.yaml 
- hosts: allroles:- role: k8swhen: ansible_hostname is match "master232"- role: nfswhen: ansible_hostname is match "master231"
(venv) [root@ansible99 roles]# 7.测试验证
(venv) [root@ansible99 roles]# ansible-playbook -i /root/kubespray/inventory/yinzhengjie-k8s/inventory.ini  start.yaml  
http://www.jsqmd.com/news/1038482/

相关文章:

  • 2026苏州市APP开发公司排名:十大定制开发服务商推荐 - IT老炮老刘
  • 项目管理:从需求蔓延到交付可控的工程化管控框架
  • DeepSeek R1不是GPT蒸馏产物:从软标签缺失到VCOT架构的真相
  • 2026年6月市政水务在线余氯监测仪知名品牌排行榜:技术迭代、国产替代与全场景选型深度分析 - 液体流量液位品牌推荐
  • 2026南京市APP定制开发公司排名:哪家更适合企业长期合作 - IT老炮老刘
  • MSC8102分组电话农场卡硬件设计深度解析:从多处理器架构到电信级板卡实战
  • 2026世界杯竞猜福利!免费赢AI尚运动相机+五折购机券
  • MAX795TESA+T是一款8 脚工业级监控芯片 + 3.3V 系统 RAM 断电存储方案
  • 2026无锡市APP软件开发公司排名:企业选型参考 - IT老炮老刘
  • 跨端体验一致性:CodePlus前端的响应式设计与无障碍访问探索
  • 深入解析PowerPC 601整数加载/存储指令:寻址模式与内存同步机制
  • 2026年6月钢塑土工格栅厂家推荐指南 - 多才菠萝
  • 2026年抚顺搬家公司选购指南:抚顺居民搬家、公司搬厂、空调移机服务厂家选择,服务、效率、口碑三维度解析 - 海棠依旧大
  • 2026年6月三向土工格栅厂家推荐优质企业指南 - 多才菠萝
  • 2026无锡汽车音响改装权威评测:音乐人生全维度深度解析与选型指南 - 音乐人生汽车音响
  • Node.js + Express 入门实战笔记-02-中间件详解
  • 普中51开发板上用HC-SR04做实时测距+蜂鸣提醒(带原理图和可烧录工程)
  • MPC5643L ADC双读与硬件自检:实现ASIL D功能安全的关键机制
  • 如何永久保存你的微信聊天记忆?这个开源工具让珍贵对话永不丢失
  • 2026年6月玻纤土工格栅实力厂家推荐指南 - 多才菠萝
  • Code-Text-Code:语义也需要一道闸门
  • Libero的下载与认证
  • 2026常德家长必读:10所叛逆青少年戒网瘾军事化管教学校深度测评 - 辛云教育资讯
  • 传统观念:市盈率越低股票越值得买,编程批量筛选低PE个股,统计后续一年超额收益,识别低PE陷阱。
  • Sketch Find and Replace插件:专业设计师必备的批量文本替换工具
  • 福州仓山买宠干货测评|金山3家同商圈猫犬舍对比,盆地高湿+台风季养宠避坑指南 - 萌宠俱乐部
  • Ryzen AI 端侧算力与 Radeon GPU 协同实测大纲
  • Playnite便携版配置方案实践指南:跨设备游戏库管理的技术实现
  • 珠三角口碑好五金模具企业哪家强:从四大维度解析优质之选 - 资讯纵览
  • 山东国泰金属容器:全场景适配不锈钢储罐定制服务商 - 起跑123