- 文档/教程
【免费下载链接】90DaysOfDevOps
This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Principles, Processes, Tooling and Use Cases surrounding this vast topic.
本篇技术指南围绕 90DaysOfDevOps 项目第 68 天的 Ansible 自动化实战展开,系统讲解 Tags 标签机制、两类变量的定义与优先级、Inventory 清单文件的按环境拆分,以及如何从零编写 MySQL role 完成数据库服务器的自动化部署。读完本文,你将能够用--tags精准控制大规模 playbook 的执行范围,用group_vars/host_vars让 playbook 与配置解耦,并通过 roles 模板化的方式一键部署并验证一个 MySQL 数据库节点。
背景与实验环境
本篇对应的实验发生在 Configmgmt 目录 下的 Vagrant 多机环境中(Vagrantfile 定义了 webservers、proxy、database 等若干虚拟机),控制节点上运行 Ansible,通过默认的/etc/ansible/hosts定义主机分组。前几天的实验已完成 webservers 与 loadbalancer(nginx 反向代理)的 playbook,day68.md 是这一系列的延续:先完善 playbook 的标签与变量管理,再补上最后一台尚未启动的数据库服务器 db01。
一、Tags:让大型 Playbook 按需执行
1.1 为什么需要 Tags
前面的 playbook 一旦运行,会把webservers与proxy两个 play 从头到尾全部执行。当 playbook 变得庞大复杂时,往往只希望针对其中一部分 plays 或 tasks 做变更。Ansible 的 tags 机制正是为此设计的:它可以把 plays 和 tasks 打上标签,再由命令行按标签筛选执行范围。
以场景 5 的 playbook5.yml 为例,两个 play 分别被打上了web与proxy标签:
- hosts: webservers become: yes vars: http_port: 8000 https_port: 4443 html_welcome_msg: "Hello 90DaysOfDevOps - Welcome to Day 66!" roles: - common - apache2 tags: web - hosts: proxy become: yes roles: - common - nginx tags: proxy1.2 查看与按标签执行
先列出 playbook 中定义过的所有标签,确认标签名称无误:
ansible-playbook playbook5.yml --list-tags只针对 proxy 组执行:
ansible-playbook playbook5.yml --tags proxy此时只有proxyplay 及其关联的common、nginxroles 会被执行,webservers相关任务全部跳过。
1.3 Tags 的粒度:task 级标签与always
除了给 play 打标签,tag 还可以加在单个 task 上,实现更细粒度的控制。例如可以按用途给 tasks 打上installation、configuration、removal等应用型标签,运行时按需挑选。
特别值得记住的是tag: always:任何被标记为always的 task,无论命令行传入什么--tags值,都会被执行。这对那些"任何场景都必须先跑"的基础任务(如安装公共依赖)非常有用。
1.4 多标签组合与多标签定义
多个标签可以一起使用,命令行以逗号分隔:
ansible-playbook playbook5.yml --tags proxy,web上面这条命令会执行所有带proxy或web标签的 plays——在当前 playbook 中恰好等价于执行整个 playbook,但当 playbook 新增了其他 plays 时,这种组合筛选的价值就体现出来了。另外,一个 play 或 task 也可以同时定义多个标签(YAML 列表形式,如后文 scenario7 的写法)。
二、Variables:Ansible Facts 与用户自定义变量
Ansible 中有两大类变量:Ansible Facts(自动采集的系统事实)与User created(用户自定义变量)。
2.1 Ansible Facts:系统自动采集的"免费变量"
每次运行 playbook 时,第一阶段都会自动执行一个未在 playbook 中显式定义的 task——"Gathering Facts(收集事实)"。这些 facts 是目标主机的系统级信息,可以直接在 playbook、模板和条件判断中引用,用来驱动自动化决策。
把事实数据导出成文件,便于检索分析:
ansible proxy -m setup >> facts.jsonsetup模块会返回目标主机的大量 JSON 事实数据。本仓库中的 facts.json 就是 loadbalancer 主机的完整事实快照(共约 990 行),从中可以看到ansible_all_ipv4_addresses(如192.168.169.134、10.0.2.15)、ansible_architecture(x86_64)、ansible_bios_vendor(innotek GmbH/VirtualBox)等丰富信息,这些都可以在 playbook 中按需引用。
2.2 用 Facts 动态生成 nginx 负载均衡配置
场景 5 的 mysite.j2 最初是把两台 webserver 的 IP 硬编码进 nginx 的 upstream 块:
upstream webservers { server 192.168.169.131:8000; server 192.168.169.132:8000; }硬编码的缺陷在于:新增或移除 webserver 时,代理配置不会自动跟随。可以利用 facts 与groups['webservers']循环,把 nginx 配置模板改为动态生成:
#Dynamic Config for server {{ ansible_facts['nodename'] }} upstream webservers { {% for host in groups['webservers'] %} server {{ hostvars[host]['ansible_facts']['nodename'] }}:8000; {% endfor %} } server { listen 80; location / { proxy_pass http://webservers; } }要点解释:
groups['webservers']是 Ansible 内置的魔法变量,返回webservers主机组的所有成员;hostvars[host]['ansible_facts']['nodename']用于跨主机访问组内每台机器的 fact,即取其主机名;- 运行效果与当前硬编码配置完全一致,但此后无论往
webservers组里添加还是移除机器,重新渲染出的代理配置都会自动同步变化; - 前提是目标环境已配置好 name resolution(名称解析),否则主机名无法正确解析为 IP。
2.3 用户自定义变量:从 playbook 内嵌到 group_vars 解耦
用户自定义变量由我们自己定义。此前变量直接写在 playbook 的vars:块中,例如:
- hosts: webservers become: yes vars: http_port: 8000 https_port: 4443 html_welcome_msg: "Hello 90DaysOfDevOps - Welcome to Day 68!" roles: - common - apache2 tags: web为了让 playbook 更干净、变量更易维护,可以把变量移到独立文件中。进入场景 6 目录 ansible-scenario6,创建目录结构group_vars/all/,并在其中新建 common_variables.yml:
http_port: 8000 https_port: 4443 html_welcome_msg: "Hello 90DaysOfDevOps - Welcome to Day 68!"同时把 playbook 中的vars:块及其变量删除。由于放在group_vars/all/下,这些变量对所有主机组全局生效;还可以把 NTP、DNS 服务器等全局配置一并加进来。此时 playbook6.yml 变得非常简洁:
- hosts: webservers become: yes roles: - common - apache2 tags: web - hosts: proxy become: yes roles: - common - nginx tags: proxy全局变量在模板中同样可用。把 mysite.j2 中硬编码的端口号替换为变量{{ http_port }}:
#Dynamic Config for server {{ ansible_facts['nodename'] }} upstream webservers { {% for host in groups['webservers'] %} server {{ hostvars[host]['ansible_facts']['nodename'] }}:{{ http_port }}; {% endfor %} } server { listen 80; location / { proxy_pass http://webservers; } }同理,在 apache2 role 的 index.html.j2 中,既引用自定义变量html_welcome_msg,又引用 factansible_facts['nodename'],用于标识当前响应请求的是哪台 webserver:
<html> <h1>{{ html_welcome_msg }}! I'm webserver {{ ansible_facts['nodename'] }} </h1> </html>执行ansible-playbook playbook6.yml后,轮询负载均衡器地址,会发现请求交替落在两台 webserver 上,页面中显示的nodename也随之切换,直观验证了变量与 facts 的联动效果。
更细粒度的场景:还可以创建host_vars目录并在其中放web01.yml,实现"按单台主机"定制消息或页面外观。
2.4 变量优先级与目录约定小结
group_vars/all/:对所有主机组生效的全局变量;group_vars/<group>/:只对指定主机组生效;host_vars/<host>.yml:只对单台主机生效,优先级高于 group_vars;- play 内联
vars:的优先级高于上述基于目录的文件变量。 - 这些目录需与 playbook 同级(即 playbook 所在的"根"目录),Ansible 会按目录位置自动加载。
三、Inventory:为不同环境准备独立主机清单
此前一直使用/etc/ansible/hosts这个默认清单文件。对于 production、staging 等多环境场景,可以为每个环境准备独立的 inventory 文件,运行 playbook 时通过-i指定:
ansible-playbook -i dev playbook.ymlInventory 文件里除了定义主机与分组,同样可以声明变量。例如可以在 dev 清单中定义env: dev之类的环境变量,然后在负载均衡器网页模板中引用该变量,把环境名渲染到页面消息里,让访问者一眼看出当前处于哪个环境。
四、实战:自动化部署 MySQL 数据库服务器
4.1 启动虚拟机并打通 SSH
最后一台 db01 机器此前尚未启动。在存放 Vagrantfile 的目录下执行:
vagrant up db01机器启动并可达后,把控制节点的 SSH 公钥复制过去,以便免密管理:
ssh-copy-id db014.2 初始化 MySQL role 骨架
进入场景 7 目录 ansible-scenario7,用ansible-galaxy生成名为 mysql 的 role 目录骨架:
ansible-galaxy init roles/mysql该命令会自动创建tasks/、handlers/、templates/、defaults/、vars/、meta/、tests/等标准目录与 README,见 roles/mysql 目录结构。
4.3 在 playbook 中新增 database play
在 playbook7.yml 中追加第三个 play 块:目标组为/etc/ansible/hosts中定义的database组,挂载common与新建的mysql两个 roles,并打上database标签(此处采用列表形式定义标签):
- hosts: webservers become: yes roles: - common - apache2 tags: web - hosts: proxy become: yes roles: - common - nginx tags: proxy - hosts: database become: yes roles: - common - mysql tags: database4.4 编写 handler:重启 MySQL 服务
handlers/main.yml 定义了一个名为restart mysql的 handler,由任务通过notify触发,仅在状态发生变化时才执行:
# handlers file for roles/mysql - name: restart mysql service: name: mysql state: restarted4.5 编写 tasks:安装、建库、建用户
tasks/main.yml是纯指针文件,通过import_tasks依次引入两个任务文件(从 源码 可见):
# tasks file for roles/mysql - import_tasks: install_mysql.yml - import_tasks: setup_mysql.ymlinstall_mysql.yml负责安装 MySQL 相关软件包并确保服务运行,详见 install_mysql.yml:
- name: "Install Common packages" apt: name={{ item }} state=latest with_items: - python3-pip - mysql-client - python3-mysqldb - libmysqlclient-dev - name: Ensure mysql-server is installed latest version apt: name=mysql-server state=latest - name: Installing python module MySQL-python pip: name: PyMySQL - name: Ensure mysql-server is running service: name: mysql state: started各 task 作用说明:
- 安装
python3-pip、mysql-client、python3-mysqldb、libmysqlclient-dev等依赖(with_items遍历列表,逐项执行apt); - 以
state=latest安装最新版mysql-server; - 通过 pip 安装
PyMySQL,它是后续mysql_user/mysql_db模块与 MySQL 通信所需的 Python 驱动; - 确保
mysql服务处于started状态。
setup_mysql.yml负责生成配置文件、创建数据库用户与数据库(见 setup_mysql.yml):
- name: Create my.cnf configuration file template: src=templates/my.cnf.j2 dest=/etc/mysql/conf.d/mysql.cnf notify: restart mysql - name: Create database user with name 'devops' and password 'DevOps90' with all database privileges community.mysql.mysql_user: login_unix_socket: /var/run/mysqld/mysqld.sock login_user: "{{ mysql_user_name }}" login_password: "{{ mysql_user_password }}" name: "{{db_user}}" password: "{{db_pass}}" priv: '*.*:ALL' host: '%' state: present - name: Create a new database with name '90daysofdevops' mysql_db: login_user: "{{ mysql_user_name }}" login_password: "{{ mysql_user_password }}" name: "{{ db_name }}" state: present各 task 关键点:
- 用模板生成
/etc/mysql/conf.d/mysql.cnf,并notify触发restart mysqlhandler; community.mysql.mysql_user创建数据库用户:用户名/密码分别取db_user、db_pass,priv: '*.*:ALL'授予全部库全部权限,host: '%'允许从任意主机登录,state: present表示存在即幂等;login_unix_socket: /var/run/mysqld/mysqld.sock用于通过本地 socket 连接 db01 上的 MySQL 实例——这是作者实测后补充的关键连接参数,否则无法成功连接;mysql_db模块创建名为db_name的数据库。
4.6 变量集中管理与模板
上面任务引用的用户名、密码、库名等敏感配置全部来自 common_variables.yml:
http_port: 8000 https_port: 4443 html_welcome_msg: "Hello 90DaysOfDevOps - Welcome to Day 68!" mysql_user_name: root mysql_user_password: "vagrant" db_user: devops db_pass: DevOps90 db_name: 90DaysOfDevOps模板目录中的 my.cnf.j2 内容如下(注意仓库实际模板比文中示例多了[client]段,内置了 root 登录凭据以便模块连接):
[mysql] bind-address = 0.0.0.0 [client] user=root password=vagrantbind-address = 0.0.0.0让 MySQL 监听所有网络接口,从而允许从外部(如负载均衡器或控制节点)访问;[client]段为客户端工具提供默认登录凭据。
4.7 运行 playbook 与排错
全量执行(包含此前所有 webservers、proxy 的 plays)或只针对数据库组执行:
ansible-playbook playbook7.yml ansible-playbook playbook7.yml --tags database只运行--tags database时,作者遇到了一个典型错误:提示未安装 pip3(Python)。修复方式是把 pip3 的安装补充进 common 角色的公共任务里,再重新运行 playbook,即可成功完成变更。
4.8 验证数据库服务器
从控制节点登录 db01:
ssh db01以 root 身份连接 MySQL(提示时输入 root 的 vagrant 密码):
sudo /usr/bin/mysql -u root -p确认用户是否已创建:
select user, host from mysql.user;查看新数据库是否已就绪:
SHOW DATABASES;还可以用刚创建的 DevOps 账号以同样方式登录验证(密码为DevOps90):
sudo /usr/bin/mysql -u devops -p作者在验证过程中还发现一个待优化点:setup_mysql.yml中必须显式加上login_unix_socket: /var/run/mysqld/mysqld.sock才能成功连接 db01 的 MySQL 实例,但每次运行时创建用户的 task 都会报告"变更"(幂等性不理想),这正是把用户名、密码抽到group_vars管理后,进一步向 Ansible Vault 迁移敏感信息的动机所在。
五、小结
回顾本系列的技术要点:
| 主题 | 核心命令/文件 | 作用 |
|---|---|---|
| Tags | --list-tags、--tags proxy、--tags proxy,web、tag: always | 按 play/task 粒度控制执行范围 |
| Facts | ansible -m setup、facts.json、ansible_facts['nodename'] | 利用系统自动采集信息驱动模板与任务 |
| 用户变量 | group_vars/all/、host_vars/、vars: | 配置与 playbook 解耦,按组/按主机分层 |
| Inventory | ansible-playbook -i dev playbook.yml | 多环境独立清单,可内嵌变量 |
| MySQL role | ansible-galaxy init roles/mysql | 标准化目录骨架,一键安装、建库、建用户 |
至此,90DaysOfDevOps 的 Ansible 自动化场景已覆盖 Web 服务器、负载均衡器与数据库服务器三大类节点,并引入了标签筛选、事实变量、分组变量与自定义 inventory 等核心工程实践。下一阶段可继续探索 第 69 天 的内容,把这套配置管理的知识体系推向更深的自动化场景。
- 文档/教程
【免费下载链接】90DaysOfDevOps
This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Principles, Processes, Tooling and Use Cases surrounding this vast topic.
相关推荐
90DaysOfDevOps Day 68:Ansible Tags、Variables、Inventory 与 MySQL 数据库服务器自动化配置实战
90DaysOfDevOps Day 68:Ansible Tags、Variables、Inventory 与 MySQL 数据库服务器自动化配置实战 本篇是
文档/教程90DaysOfDevOps 实战 Day 68:Ansible Tags、Variables、Inventory 与 MySQL 数据库服务器部署
90DaysOfDevOps 实战 Day 68:Ansible Tags、Variables、Inventory 与 MySQL 数据库服务器部署 本篇技术指
文档/教程90DaysOfDevOps Day 68:Ansible Tags、变量、Inventory 与 MySQL 数据库服务器配置实战
90DaysOfDevOps Day 68:Ansible Tags、变量、Inventory 与 MySQL 数据库服务器配置实战 本篇技术指南围绕 90Da
文档/教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考