首页 文章 精选 留言 我的

精选列表

搜索[技巧],共4633篇文章
优秀的个人博客,低调大师

Superpowers 核心原理与高阶使用技巧

Superpowers 是面向 Claude Code、Codex、Gemini CLI、OpenCode 等主流 AI 编码智能体的标准化软件工程工作流框架。区别于普通 AI 代码生成插件,它不只是简单补全代码,而是通过可组合技能集+强制约束指令,将“快速堆代码”的AI编码模式,改造为贴合资深工程师的规范化研发流程,从根源解决AI编码需求理解偏差、缺失测试、代码粗糙、隐藏技术债务等痛点。

优秀的个人博客,低调大师

DolphinScheduler 6 个高频 SQL 操作技巧

摘要: Apache DolphinScheduler系列4-后台SQL经验分享 关键词: 大数据、数据质量、数据调度 整体说明 在调研了 DolphinScheduler 之后,在项目上实际使用了一段时间,有了一些后台SQL实际经验,分享如下。 进入DolphinScheduler 后台数据库,我这里使用的是MySQL数据库。 以任务名称包含"ods_xf_act" 的任务为例。 一、修改任务组操作 UPDATE t_ds_task_definition a join t_ds_task_definition_log b on a.`code`=b.`code`and a.version=b.version set a.task_group_id = 19,b.task_group_id=19 where a.name like'%ods_xf_act%' 二、批量修改任务执行类型 UPDATE t_ds_process_definition a join t_ds_process_definition_log b on a.code=b.code and a.version=b.version set a.execution_type = 1,b.execution_type=1 where a.name like'%ods_xf_act%'; 三、查看定时器配置情况 根据此来选择配置定时器 select crontab,count(*) from t_ds_schedules groupby crontab orderbycount(*) desc 四、批量更改定时器 定时器,在前台页面修改很麻烦,一个个改很慢,所以想着从后台批量修改。 确定需要更新的定时器列表 select t1.id,t1.process_definition_code,crontab,t2.name from t_ds_schedules t1 join t_ds_process_definition t2 on t1.process_definition_code = t2.code wherenamelike'%ods_xf_act%' and crontab like'%0 0 5 *%' 更新成需要的crontab定时器 update t_ds_schedules t1 join t_ds_process_definition t2 on t1.process_definition_code = t2.code set t1.crontab = '0 0 11 * * ? *' wherenamelike'%ods_xf_act%' and crontab like'%0 0 5 *%' 更新成需要的crontab定时器触发表 由于定时器已经 5 -> 11修改完成, 所以后面的where 条件都是 11 update qrtz_cron_triggers t1 set t1.CRON_EXPRESSION = '0 0 11 * * ? *' where t1.TRIGGER_NAME in ( selectconcat("job_",t1.id) from t_ds_schedules t1 join t_ds_process_definition t2 on t1.process_definition_code = t2.code wherenamelike'%ods_xf_act%' and crontab like'%0 0 11 *%' ) 更新成最新crontab定时触发时间的起始时间 由于NEXT_FIRE_TIME有更新时差,所以往前推8小时 update qrtz_triggers t1 set t1.NEXT_FIRE_TIME = round(UNIX_TIMESTAMP(date_sub("2024-07-23 11:00:00", INTERVAL8HOUR) )*1000) where t1.TRIGGER_NAME in ( selectconcat("job_",t1.id) from t_ds_schedules t1 join t_ds_process_definition t2 on t1.process_definition_code = t2.code wherenamelike'%ods_xf_act%' and crontab like'%0 0 11 *%' ) 五、通知策略修改为"都不发",仍然告警 现象: 原先选择"失败发",后面修改为"都不发" 原因: 原先有告警组,然后修改为都不发,原告警组后台并没有修改,是一个bug。 临时解决方案: select t1.* from t_ds_schedules t1 join t_ds_process_definition t2 on t1.process_definition_code = t2.`code` wherenamelike'%ods_xf_act%' 把warning_type = 0 的,对应warning_group_id 都修改为 0 六、任务组队列,页面没有任务,已用资源却占满 查看任务组列表 select * from t_ds_task_group orderby create_time desc 如果遇到任务组是满的,页面查询却没有任务,可以手动修改字段值图片 查看任务组队列列表,找出没有完成,修改成已完成,就是修改值为2。 -- t_ds_task_group_queue.`status` tinyint(4) DEFAULT '-1' COMMENT '-1: waiting 1: running 2: finished' select * from t_ds_task_group_queue where1=1 andstatus <> 2 -- finished 完成 orderby create_time desc 查看任务列表,找出没有完成,修改成已完成,就是修改值为7。 -- t_ds_task_instance.`state` tinyint(4) DEFAULT NULL COMMENT 'Status: 0 commit succeeded, 1 running, 2 prepare to pause, 3 pause, 4 prepare to stop, 5 stop, 6 fail, 7 succeed, 8 need fault tolerance, 9 kill, 10 wait for thread, 11 wait for dependency to complete' -- id 是上面t_ds_task_group_queue的task_id select * from t_ds_task_instance where state <> 7-- success andidin ( selectidfrom t_ds_task_group_queue where1=1 andstatus <> 2-- finished 完成 orderby create_time desc ) orderby submit_time desc limit100 转载自鹏说大数据 原文链接:Apache DolphinScheduler系列4-后台SQL经验分享 本文由 白鲸开源科技 提供发布支持!

优秀的个人博客,低调大师

GreatSQL优化技巧:半连接(semijoin)优化

何为半连接? 半连接是在GreatSQL内部采用的一种执行子查询的方式,semi join不是语法关键字,不能像使用inner join、left join、right join这种语法关键字一样提供给用户来编写SQL语句。 两个表t1表和t2表进行半连接的含义是:对于t1表的某条记录来说,我们只关心在t2表中是否存在与之匹配的记录,而不关心有多少条记录与之匹配,最终的结果集中只保留t1表的记录。 前面文章也提到过,含in、exists子查询的语句通常会采用半连接方式执行查询,但这不绝对,也有一些情况不适用半连接。比如: (1)外查询的where子句中,存在其他搜索条件使用OR操作符与IN子查询的条件连接起来 (2)IN子查询位于Select子句中 (3)IN子查询中含有union的情况 (4)IN子查询中含group by、having或聚合函数的情况 GreatSQL执行半连接的优化策略 本文实验使用数据库版本为GreatSQL 8.0.32-25。 创建两张实验表来说明。 greatsql> create table t1( c1 varchar(30), c2 int ); greatsql> create table t2( id int primary key, c1 varchar(30), key idx_c1(c1) ); --插入几条测试数据 greatsql> insert into t1 values('a',1); greatsql> insert into t1 values('b',3); greatsql> insert into t1 values('a',5); greatsql> insert into t1 values('c',7); greatsql> insert into t1 values('d',9); greatsql> insert into t2 values(1,'a'); greatsql> insert into t2 values(2,'a'); greatsql> insert into t2 values(3,'b'); greatsql> insert into t2 values(4,'b'); greatsql> insert into t2 values(5,'c'); greatsql> insert into t2 values(6,'b'); GreatSQL执行半连接的方式大致有以下5种: 1.Table pullout(子查询中的表上拉) 当子查询的查询列表处只有主键或者唯一索引列时,可以直接把子查询中的表上拉到外层查询的FROM子句中,并把子查询的查询条件合并到外层查询的搜索条件中。所以选择这种方式是有先决条件的,子查询的查询列表处必须只有主键或唯一索引列。有没有选择这种方式,可以通过执行explain展示计划后,使用show warnings命令查看优化器改写后的语句。 例如下面这个语句: select * from t1 where c2 in (select id from t2 where t2.c1='b'); 这个语句种子查询的id列是t2表的主键列,满足这种方式的先决条件,看一下执行计划。 greatsql> explain select * from t1 where c2 in (select id from t2 where t2.c1='b'); +----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+ | 1 | SIMPLE | t2 | NULL | ref | PRIMARY,idx_c1 | idx_c1 | 123 | const | 3 | 100.00 | Using index | | 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where; Using join buffer (hash join) | +----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+ 2 rows in set, 1 warning (0.00 sec) greatsql> show warnings; +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t2` join `test`.`t1` where ((`test`.`t1`.`c2` = `test`.`t2`.`id`) and (`test`.`t2`.`c1` = 'b')) | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.01 sec) 从warning信息可以看出,优化器改执行连接方式是,t1表与t2表通过内连接来关联,原子查询内部t2表的过滤条件放到了整个语句where条件的后面,原语句与优化器执行的语句之所以等价,是因为子查询的查询列id列是主键列,不会有重复值,跟外表t1使用inner join连接后,不会造成关联后结果集数据量的放大。一般情况下子查询的查询列表处只有主键或者唯一索引列时都会转化为这种方式来执行。对于这种业务,无论开发者怎么编写SQL,使用inner join 也好,exists也好,最后优化器执行方式可能都是一样的。 可以看一下将原语句改造为inner join 与 exists语句的执行计划,是不是都是一样的。 greatsql> explain select * from t1 where exists (select 1 from t2 where t2.id=t1.c2 and t2.c1='b'); +----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+ | 1 | SIMPLE | t2 | NULL | ref | PRIMARY,idx_c1 | idx_c1 | 123 | const | 3 | 100.00 | Using index | | 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where; Using join buffer (hash join) | +----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+ 2 rows in set, 2 warnings (0.00 sec) greatsql> show warnings; +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1276 | Field or reference 'test.t1.c2' of SELECT #2 was resolved in SELECT #1 | | Note | 1003 | /* select#1 */ select `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t2` join `test`.`t1` where ((`test`.`t1`.`c2` = `test`.`t2`.`id`) and (`test`.`t2`.`c1` = 'b')) | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 2 rows in set (0.00 sec) greatsql> explain select t1.* from t1 inner join t2 on t1.c2=t2.id where t2.c1='b'; +----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+ | 1 | SIMPLE | t2 | NULL | ref | PRIMARY,idx_c1 | idx_c1 | 123 | const | 3 | 100.00 | Using index | | 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where; Using join buffer (hash join) | +----+-------------+-------+------------+------+----------------+--------+---------+-------+------+----------+--------------------------------------------+ 2 rows in set, 1 warning (0.01 sec) greatsql> show warnings; +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` join `test`.`t2` where ((`test`.`t1`.`c2` = `test`.`t2`.`id`) and (`test`.`t2`.`c1` = 'b')) | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) 这种执行方式本质上已经转换为内连接了。 2.FirstMatch(首次匹配) 这种方式先取外层查询的一条记录,到子查询的表中寻找符合匹配条件的记录,如果能找到一条,则将外层查询的记录放入到最终结果集中并且停止查找匹配更多的记录,如果找不到,则把该外层查询的记录丢弃掉,然后再开始取下一条外层查询中的记录,这个过程一直持续到外层查询获取不到记录为止。 看一个简单语句的执行计划 select * from t1 where c1 in (select c1 from t2); greatsql> explain select * from t1 where c1 in (select c1 from t2); +----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+-------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+-------------------------------+ | 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | Using where | | 1 | SIMPLE | t2 | NULL | ref | idx_c1 | idx_c1 | 123 | test.t1.c1 | 2 | 100.00 | Using index; FirstMatch(t1) | +----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+-------------------------------+ 2 rows in set, 1 warning (0.01 sec) greatsql> show warnings; +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` semi join (`test`.`t2`) where (`test`.`t2`.`c1` = `test`.`t1`.`c1`) | +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) 从warning信息可以看到 semi join 的字样,优化器使用半连接方式执行的子查询。从执行计划可以看到 extra 列有FirstMatch(t1) 的字样,表示对t1表外查询传入的每个c1值在t2表上都进行了首次匹配,这种方式也是我最初理解的in子查询的含义,只关心有无匹配上,不关心匹配上多少。 3.LooseScan(松散扫描) LooseScan是使用子查询的查询列上的索引,只针对相同索引列值的第一条记录,去外查询找对应的记录。使用了这种优化方式的半连接,在explain的计划的Extra列会有LooseScan字样。 还是上面的语句,使用semijoin的hint干涉优化器,使其选择LooseScan的优化策略。 select /*+ semijoin(@subq1 loosescan) */ * from t1 where c1 in (select /*+ qb_name(subq1)*/ c1 from t2 ); greatsql> explain select /*+ semijoin(@subq1 loosescan) */ * from t1 where c1 in (select /*+ qb_name(subq1)*/ c1 from t2 ); +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------------------------+ | 1 | SIMPLE | t2 | NULL | index | idx_c1 | idx_c1 | 123 | NULL | 6 | 50.00 | Using index; LooseScan | | 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 20.00 | Using where; Using join buffer (hash join) | +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------------------------+ 2 rows in set, 1 warning (0.01 sec) greatsql> show warnings; +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select /*+ SEMIJOIN(@`subq1` LOOSESCAN) */ `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` semi join (`test`.`t2`) where (`test`.`t1`.`c1` = `test`.`t2`.`c1`) | +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) 从执行计划可以看出,子查询的表t2作为驱动表,t2表的c1列上有索引,对表t2进行访问时,使用其c1列的索引,对相同的索引列值只取第一条记录去t1表中找对应记录,将所有外查询表t1对应的记录都加入到最终结果集,可以理解为对子查询t2表的索引扫描方式是跳跃式的。 4.Duplicate Weedout重复值消除 这种方式是借助临时表来消除重复值,explain展示计划时,在extra列会出现Start temporary 和 End temporary的字样。 还是上面的语句,我们使用semijoin的hint干涉优化器,使其选择dupsweedout优化策略。 greatsql> explain select /*+ semijoin(@subq1 dupsweedout)*/ * from t1 where c1 in (select /*+ qb_name(subq1)*/ c1 from t2); +----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+---------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+---------------------------------------------+ | 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | Using where | | 1 | SIMPLE | t2 | NULL | ref | idx_c1 | idx_c1 | 123 | test.t1.c1 | 2 | 100.00 | Using index; Start temporary; End temporary | +----+-------------+-------+------------+------+---------------+--------+---------+--------------+------+----------+---------------------------------------------+ 2 rows in set, 1 warning (0.00 sec) greatsql> show warnings; +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select /*+ SEMIJOIN(@`subq1` DUPSWEEDOUT) */ `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` semi join (`test`.`t2`) where (`test`.`t2`.`c1` = `test`.`t1`.`c1`) | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) 例如:t1表的记录('b',3),可以匹配上t2表的两条记录(3,'b'),(4,'b'),为了消除关联结果的重复值,可以想象建立这样一个临时表: create table tmp(rowid int primary key); 当把t1表的记录加入到结果集时,先把这条记录的rowid加入到临时表中,如果添加成功,说明这条记录并没有加入到最后的结果集,如果添加失败,则说明t1表的这条记录已经加入到最终结果集了 个人感觉这种方式比其他方式效率低。 5.Semi-join Materialization(半连接物化) 先把IN 子句中的不相关子查询进行物化,然后再将外层查询的表与物化表进行连接。子查询内部有分组聚合运算时通常会先进行物化处理。 还是上面的语句,使用semijoin的hint干涉优化器,使其选择materialization的优化策略。 select /*+ semijoin(@subq1 materialization) */ * from t1 where c1 in (select /*+ qb_name(subq1)*/ c1 from t2 ); greatsql> explain select /*+ semijoin(@subq1 materialization) */ * from t1 where c1 in (select /*+ qb_name(subq1)*/ c1 from t2 ); +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+--------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+--------------+------+----------+-------------+ | 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where | | 1 | SIMPLE | <subquery2> | NULL | eq_ref | <auto_distinct_key> | <auto_distinct_key> | 123 | test.t1.c1 | 1 | 100.00 | NULL | | 2 | MATERIALIZED | t2 | NULL | index | idx_c1 | idx_c1 | 123 | NULL | 6 | 100.00 | Using index | +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+--------------+------+----------+-------------+ 3 rows in set, 1 warning (0.00 sec) greatsql> show warnings; +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select /*+ SEMIJOIN(@`subq1` MATERIALIZATION) */ `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` semi join (`test`.`t2`) where (`<subquery2>`.`c1` = `test`.`t1`.`c1`) | +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) 从执行计划可以看出,先对子查询t2表做了物化表处理,物化表会生成自动索引<auto_distinct_key>,外查询表t1再与物化表做Nest loop连接。 补充说明 对于上面的语句 select * from t1 where c1 in (select c1 from t2);,优化器默认选择了firstmatch方式,其他方式都是使用hint来干涉的优化器的选择,可以看到这个hint包含两部分,一个是使用qb_name()给子查询分配一个名称,一个是使用semijoin([@query_block_name] [strategy]),指定子查询块使用半连接策略,可以指定多个策略。同时semijoin的优化策略的选择还受优化开关参数optimize_switch的影响,该参数里有semijoin,loosescan,firstmatch,duplicateweedout的开关,默认都是开启的,所以也可以使用优化开关来干涉优化器的选择。 优化举例 select count(*) from t1 a where substr(a.modifytime, 1, 8) = '20240301' and a.sospecnumber in (select a.sospecnumber from t1 a where substr(a.modifytime, 1, 8) < '20240301'); 这条SQL只涉及一张表t1,表中数据200万左右,modify_time为字符类型,存储从2009年开始的时间串。看一下该表的索引情况。 greatsql> show index from t1; +-------+------------+------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | +-------+------------+------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+ | t1 | 1 | idx_sospecnumber | 1 | SOSPECNUMBER | A | 133 | NULL | NULL | YES | BTREE | | | YES | NULL | | t1 | 1 | idx_modifytime | 1 | MODIFYTIME | A | 634186 | NULL | NULL | | BTREE | | | YES | NULL | +-------+------------+------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+ 2 rows in set (0.01 sec) explain的执行计划如下: greatsql> explain -> select count(*) -> from t1 a -> where substr(a.modifytime, 1, 8) ='20240301' -> and a.sospecnumber in -> (select a.sospecnumber -> from t1 a -> where substr(a.modifytime, 1, 8) < '20240301') ; +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+---------------------+---------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+---------------------+---------+----------+-------------+ | 1 | SIMPLE | a | NULL | ALL | idx_sospecnumber | NULL | NULL | NULL | 2426414 | 100.00 | Using where | | 1 | SIMPLE | <subquery2> | NULL | eq_ref | <auto_distinct_key> | <auto_distinct_key> | 131 | test.a.SOSPECNUMBER | 1 | 100.00 | NULL | | 2 | MATERIALIZED | a | NULL | ALL | idx_sospecnumber | NULL | NULL | NULL | 2426414 | 100.00 | Using where | +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+---------------------+---------+----------+-------------+ 3 rows in set, 1 warning (0.00 sec) 优化器选择的半连接优化策略是物化的方式。 explain analyze的实际计划如下: greatsql> explain analyze -> select count(*) -> from t1 a -> where substr(a.modifytime, 1, 8) ='20240301' -> and a.sospecnumber in -> (select a.sospecnumber -> from t1 a -> where substr(a.modifytime, 1, 8) < '20240301') \G *************************** 1. row *************************** EXPLAIN: -> Aggregate: count(0) (cost=1177497474524.58 rows=1) (actual time=4442.499..4442.500 rows=1 loops=1) -> Nested loop inner join (cost=588748984584.98 rows=5887484899396) (actual time=4438.967..4442.408 rows=1346 loops=1) -> Filter: ((substr(a.MODIFYTIME,1,8) = '20240301') and (a.SOSPECNUMBER is not null)) (cost=252003.98 rows=2426414) (actual time=1550.096..1552.027 rows=1346 loops=1) -> Table scan on a (cost=252003.98 rows=2426414) (actual time=0.050..1189.136 rows=2493198 loops=1) -> Single-row index lookup on <subquery2> using <auto_distinct_key> (sospecnumber=a.SOSPECNUMBER) (cost=494645.48..494645.48 rows=1) (actual time=2.147..2.147 rows=1 loops=1346) -> Materialize with deduplication (cost=494645.38..494645.38 rows=2426414) (actual time=2888.845..2888.845 rows=165 loops=1) -> Filter: (a.SOSPECNUMBER is not null) (cost=252003.98 rows=2426414) (actual time=0.215..1927.315 rows=2487547 loops=1) -> Filter: (substr(a.MODIFYTIME,1,8) < '20240301') (cost=252003.98 rows=2426414) (actual time=0.214..1745.562 rows=2487547 loops=1) -> Table scan on a (cost=252003.98 rows=2426414) (actual time=0.211..1235.738 rows=2493198 loops=1) 1 row in set (4.45 sec) 优化分析: 这条SQL总体耗时4.45s,耗时主要分布在两处: 一处消耗在外表的查询,对t1进行了全表扫描,回表过滤后剩余1346行数据,耗时1552ms,此处虽然modifytime列有索引,但是因为在条件列上施加了substr函数,导致索引用不上,改为modifytime like '20240301%'的方式,也表示了查询2024年3月1日的数据,同时用上了索引。 另一处消耗在子查询的物化上,子查询结果集有2487547行数据,表扫描、过滤、物化整个过程耗时约2888ms,对大结果集进行物化消耗比较大,同时IN子查询的查询列sospecnumber列上是有索引的,虽然选择性不好,但是这个子查询的含义是只需要判断子查询结果集中有无记录能匹配上,而不关心匹配上多少条,所以这种情况采用first match方式比较好。 SQL改写如下: select /*+ semijoin(@subq firstmatch)*/ count(*) from t1 a where a.modifytime like '20240301%' and a.sospecnumber in (select /*+ qb_name(subq)*/ a.sospecnumber from t1 a where substr(a.modifytime, 1, 8) < '20240301') 改写后执行计划如下: *************************** 1. row *************************** EXPLAIN: -> Aggregate: count(0) (cost=11052513.72 rows=1) (actual time=157.570..157.570 rows=1 loops=1) -> Nested loop semijoin (cost=8596909.70 rows=24556040) (actual time=0.203..157.450 rows=1346 loops=1) -> Filter: (a.SOSPECNUMBER is not null) (cost=606.05 rows=1346) (actual time=0.057..7.610 rows=1346 loops=1) -> Index range scan on a using idx_modifytime over ('20240301' <= MODIFYTIME <= '20240301????????????????????????????????????????????????'), with index condition: (a.MODIFYTIME like '20240301%') (cost=606.05 rows=1346) (actual time=0.055..7.406 rows=1346 loops=1) -> Filter: (substr(a.MODIFYTIME,1,8) < '20240301') (cost=83255911.06 rows=18244) (actual time=0.111..0.111 rows=1 loops=1346) -> Index lookup on a using idx_sospecnumber (SOSPECNUMBER=a.SOSPECNUMBER) (cost=83255911.06 rows=18244) (actual time=0.111..0.111 rows=1 loops=1346) 1 row in set, 1 warning (0.16 sec) 改写后耗时0.16s,性能提升近30倍,在对子查询通过索引idx_sospecnumber搜索数据时,查到一条就会停止继续搜索了。 结语 GreatSQL的 IN 子查询适用于半连接时,优化器提供了5种优化策略:Table pullout、FirstMatch、LooseScan、Duplicate weedout、materialize。 一般外查询表结果集小,子查询结果集太大时,不希望通过物化这种方式来执行连接,因为物化表的代价太大,可能通过FirstMatch或者LooseScan很快就可以执行出结果了。那反之外查询结果集大,子查询结果集小时,通过物化表这种方式可能就会取得很好的效果。很多时候都不用过多干涉优化器做选择,但是如果懂得原理,当优化器选错的时候我们也可以通过hint来稳定计划,让SQL保持高效的执行。 Enjoy GreatSQL :) 关于 GreatSQL GreatSQL是适用于金融级应用的国内自主开源数据库,具备高性能、高可靠、高易用性、高安全等多个核心特性,可以作为MySQL或Percona Server的可选替换,用于线上生产环境,且完全免费并兼容MySQL或Percona Server。 相关链接: GreatSQL社区 Gitee GitHub Bilibili GreatSQL社区: 社区有奖建议反馈: https://greatsql.cn/thread-54-1-1.html 社区博客有奖征稿详情: https://greatsql.cn/thread-100-1-1.html (对文章有疑问或者有独到见解都可以去社区官网提出或分享哦~) 技术交流群: 微信&QQ群: QQ群:533341697 微信群:添加GreatSQL社区助手(微信号:wanlidbc )好友,待社区助手拉您进群。

优秀的个人博客,低调大师

遗留代码处理技巧与案例演示

1 什么是遗留代码 本质是一种技术债务,产生原因一方面是业务原因:如业务本身场景繁多、流程复杂等;另一方面是技术原因:如代码不规范、设计不合理、祖传代码文档注释缺失等。它会影响我们的程序很多方面:如可读性、可修改性、可复用性、可维护性、可测试性等。 2 遗留代码处理过程拆解 划分为梳理->重构/重写->替换/验证三个阶段 2.1 梳理 遗留代码的处理是一种逆向工程,从已有的代码+数据模型+文档倒推出业务模型、交互和规则,在保真的前提下再重新构建代码+数据模型+文档。 我们这里可以参考下DDD领域驱动设计里战略设计部分常用的工具(事件风暴法)来进行这部分梳理工作。 事件风暴本质上是一种系统建模的方法,与它处于对等位置的,会有“UML建模”、“事件驱动建模”等。事件风暴跟敏捷开发里的一些理念(如用户故事)的产生背景类似,都是在理性思考无法应对变化频繁且文字难以描述的情况下,通过一些辅助性的提示卡片、视觉手段,辅以相关人员的集中、高频沟通来完成对于业务的准确把握和抽象建模。 事件风暴的过程: 通过梳理业务流程,创建相应的领域事件(Event) 补充引发每个领域事件的命令(Command) 通过实体/聚合把命令和事件关联起来 划分领域边界及事件流动线条 识别用户操作所需的关联视图及其角色 事件风暴的产物: 领域对象 即实体/聚合。这里的领域对象并非数据库模型, 而是与业务紧密联系的“对象”。因为事件风暴是一种面向对象的建模方式, 而不是面向数据库的建模方式。 领域事件 即对象在某些操作或特点时点下所产生的事件, 这些事件将决定之后多个聚合和限界上下文(BC)之间的通讯方式。 限界上下文 当所有的对象(实体/聚合)被梳理出来后,属于同一种“通用语言”的对象, 则会被归入同一个限界上下文边界内;不属于同一种“通用语言”的对象, 则会被边界给分割开,划入不同的子域或限界上下文。 梳理结果示例: 2.2 重构/重写 通过重构/重写对软件要素进行重新组织,使其不改变外部行为的情况下,提升代码的可读性或使其结构更合理。 针对不同层次的软件要素要做不同的处理和控制: 并且整个重构/重写过程有些需要遵照的原则: 单一职责:可以将依赖归拢,统一行为和控制。权责明确,场景明确。 单一原则:消除重复的数据声明、行为;因为单一所以保证了复用,统一标准 ,可装配性。 封装原则:不需要过度关心依赖类内部实现,最好一个.就能调用。 归属原则:上帝的归上帝,凯撒的归凯撒。谁提供的数据更多,归属于谁。 抽象层次:越高层的抽象越稳定,越细节的东西越容易变化。举例:接口应传递职责而非实现细节。 开闭原则:对修改关闭,对扩展开放。 kiss原则: 好理解,好维护。 清晰原则:只读小部分代码就可以知道怎么改逻辑,做扩展。而不是要通读所有代码,才能理清。 其中有两点落地细节我们具体分析下: 业务逻辑的处理 业务代码和技术代码解耦 主流程代码和附加流程代码解耦 长链路的拆解编排 关注点的分离 双向依赖:上下文之间缺少一层未被澄清的上下文,或者两个上下文其实可被合为一个; 循环依赖:任何一个上下文发生变更,依赖链条上的上下文均需要改变; 过深的依赖:自身依赖的信息不能直接从依赖者获取到,需要通过依赖者从其依赖的上下文获取并传递,依赖链路过长,依赖链条上的任何一个上下文发生变更,其链条后的任何一个上下文均可能需要改变; 2.3 替换验证 大概分为以下几个要点: 领会意图,抽取用例,增加可复测性 增加可监测性 分成小块,逐步替换 试点、看到成效 可借助过程管理工具如PDCA法进行管理 3 案例演示 3.1 案例1:针对强耦合的实现做重构 原始需求:案例为一个转账服务,用户可以通过银行网页转账给另一个账号,支持跨币种转账。同时因为监管和对账需求,需要记录本次转账活动。 原始架构:是一个传统的三层分层结构:UI层、业务层、和基础设施层。上层对于下层有直接的依赖关系,导致耦合度过高。在业务层中对于下层的基础设施有强依赖,耦合度高。我们需要对这张图上的每个节点做抽象和整理,来降低对外部依赖的耦合度。 重构关键设计点: 重构后代码特征: 业务逻辑清晰,数据存储和业务逻辑完全分隔。 Entity、Domain Primitive、Domain Service都是独立的对象,没有任何外部依赖,但是却包含了所有核心业务逻辑,可以单独完整测试。 原有的转账服务不再包括任何计算逻辑,仅仅作为组件编排,所有逻辑均delegate到其他组件。 3.2 案例2:提高老代码的复用性 原始需求:现有几个策略实现类,被很多代码使用。现在需要根据不同的业务方在每个策略执行前做不同的前置逻辑处理。 解法分析:尽量避免把逻辑耦合到已有的实现类中。引入外部类进行控制反转。这里我们使用访问者模式。 访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。 具体实现: 重构后代码特征: 可以通过访问者对老代码逻辑进行编排,将修改外置,减少对老逻辑的影响 通过java8默认接口实现提供默认访问行为,避免大量策略子类的感知,只需要需要提供自己实现行为的子类对默认实现进行覆写 4 总结 遗留代码的处理能力一方面是对技术的要求,另一方面也是对业务掌握的挑战。希望我们可以跨越荆棘、穿过迷雾,顺利到达成功的彼岸! 作者:冯鸿儒

优秀的个人博客,低调大师

递推的思维构建与技巧实现

递推是一种用若干步可重复运算来解决复杂问题的方法。 1.一维递推 1.1 问题描述 有一个n层的楼梯,每次只可以向上爬1层或者2层,问爬完n层共有多少种不同的方式呢? 1.2 分析 设f(n)表示n层楼总共不同的方式。 假设此时位于第i层,因为每次只能爬1层或2层,所以到第i层只有2种方式。 从第i-1层爬上来。 从第i-2层爬上来。 所以得到递推公式为f(n)=f(n-1)+f(n-2)。前2项之和等于第3项,其实就是斐波那契数列,1,1,2,3,5,8,13,21... 1.3 代码实现 f[0]=1;f[1]=1; for(inti=2;i<n;i++){ f[i]=f[i-1]+f[i-2] } cout<<f[n-1]<<endl; 1.4 空间优化 每一步的递推只与前2步有关,所以只需要记录前2步的方案数,用滚动数组,而不需要开O(n)的空间。手动赋值 intf[3]; f[0]=1; f[1]=1; for(inti=2;i<10;++i){ f[2]=f[1]+f[0]; f[0]=f[1]; f[1]=f[2]; cout<<f[2]<<endl; } 取模滚动 intf[3]; f[0]=1; f[1]=1; for(inti=2;i<10;++i){ f[i%3]=f[(i-1)%3]+f[(i-2)%3]; cout<<f[i%3]<<endl; } 如果只与前一个状态有关,比如f[n]=f[n-1]+1,可以用0,1滚动,这个在动态规划中会比较常用。 intf[2],t=0; f[0]=1; for(inti=2;i<10;++i){ t=1-t; f[t]=f[1-t]+1; cout<<f[t]<<endl; } 递推和动态规划最大的区别:递推的每一步是所有方案数的加和,而动态规划在每一步递推中,需要用来选取一个最优策略。本质其实都是通过重复的小规模子问题推导出大规模的结果。 1.5 时间优化 斐波那契数列递推公式很简单,但数据很大时,效率就比较低,因为递推是O(n)复杂度。 通过矩阵公式变换可将加法变为乘法 如下将递推公式放入矩阵: 假设:则: 可以通过矩阵幂乘快速求出,时间复杂度为,再带入上式即可获得数列值。 具体可以看另一篇递推优化-矩阵幂乘 2.多维递推 2.1 问题描述 从原点出发,每次只能向东,向北,向西走,且不能走已经走过的地方,问走n步共有多少种不同的方式呢? 2.2 分析 假设已经走到第i步,因为不能走已经走过的地方,那这一步能走的方式只会与上一步有关。因为每次走一步,要保证不走回头路,就保证不走上一步走过的地方就行了。 每次有3个选择,即向东,向北,向西。 第i-1步向东走,那么第步只能向北、向东。 第i-1步向西走,那么第步只能向北、向西。 第i-1步向北走,那么第步可以向北、向东,向西。 一维的f[n]只能记录一个总数,而不能记录状态,所以要再多一维记录上一步走的状态。 设f[n][0], f[n][1], f[n][2]分别表示:第步向东、向西、向北走总共不同的方式。 则有如下递推关系: f[n][0] = f[n - 1][0] + f[n - 1][2]; f[n][1] = f[n - 1][1] + f[n - 1][2]; f[n][2] = f[n - 1][0] + f[n - 1][1] + f[n - 1][2]; 2.3 代码实现 intf[100][3]={0}; f[0][0]=1; f[0][1]=1; f[0][2]=1; for(inti=1;i<n;++i){ f[i][0]=f[i-1][0]+f[i-1][2]; f[i][1]=f[i-1][1]+f[i-1][2]; f[i][2]=f[i-1][0]+f[i-1][1]+f[i-1][2]; } cout<<f[n-1][0]+f[n-1][1]+f[n-1][2]<<endl; 2.4 进一步优化 设第n步的总方案数为s[n], s[n]=f[n][0]+f[n][1]+f[n][2]。 s[n]=2f[n-1][0]+2f[n-1][1]+3f[n-1][2]。 s[n]=2s[n-1]+f[n-1][2]。 而f[n-1][2]=f[n-2][0]+f[n-2][1]+f[n-2][2]=s[n-2]。 得s[n]=2s[n-1]+s[n-2]。 所以对公式变形,也可以通过一维的方式完成递推,但这个关系无法直接通过建模构造出来。 3.图递推 3.1 问题描述 在一个的二维地图中,一个人从左上角走到右下角,每次只能向右或者向下走,问到终点共有多少种不同的方式呢? 3.2 分析 假设已经位于某个位置,因为只能向右或者向下走,那上一步只能从上或者从左走过来。 设f[i][j]表示走到坐标总共的方案数。 则f[i][j]=f[i-1][j]+f[i][j-]。 3.3 代码实现 intf[10][10]={0}; f[0][0]=1; for(inti=0;i<n;++i){ for(intj=0;j<m;++j){ if(i-1>=0){ f[i][j]+=f[i-1][j]; } if(j-1>=0){ f[i][j]+=f[i][j-1]; } } } 3.4 进一步思考 要到达终点,一定要向下走n-1步,向右走m-1步。 把每一步组合在一起来看,其实问题就等价于在n+m-2步中选择n-1步向下走,或者选择m-1步向右走,通过排列组合公式就可以直接得到结果。 4.状态压缩递推 4.1 问题描述 在一个的棋盘中放置棋子,有一些地方不能放置。要求放置棋子时任意2个棋子不能在同1行或同1列,问放置k个棋子有多少种不同的方式呢? 4.2 分析 对于每1个位置,只会有2种情况,就是放或不放。在数据规模不大的情况下可以用DFS(深度优先搜索)枚举所有的情况就可以了。 那有没有更好的方法呢? 这个最终是要求方案总数,而不需要考虑每一步是否需要择优,所以是符合递推模型,接下来就是怎么找出递推关系。 先分析一些隐含的规律,把问题理得更清晰: 每1行或者每1列都只能放置1个棋子,所以按每一行来枚举放置方法。 在尝试第i行时,每一个位置(i,j)能不能放置,不只是跟上一行有关,而是跟之前的所有行都有关。这就说明需要记录之前放置的方法,也就是状态。 那怎么记录之前放置的方案状态呢,这就要用到状态压缩。状态压缩:本质就是用二进制记录对应位置的2种状态,0表示不放,1表示放。 对于n个位置,就可以用个十进制数来表示所有放置的方案。 继续回到上面的问题,在尝试第i行时,能否放置跟之前的i-1行都有关,意味着需要记录之前所有行放置的状态。 但看下面2种情况,图1和图2对于在尝试放置第3行时,其实是等价的,前2列都是不能放置。也就是说这2种方案数是可以直接合并的,因为每1列也只能放一个,所以放置的方案状态也可以直接合并成一行。 用f[i][j]表示前i行,放置方案为j总共的方案数。 第0行的过程如下: 第1行的过程如下: 如此递推求出n行,种放置方案的总数,。因为只能放置k个棋子,所以在种放置方案中找出刚好是k个棋子的方案,也就是对应的状态j转化为二进制时,有k个1。 4.3 二进制包含1的个数 目标数n,通过n&(n-1)运算,包含多少个1就刚好进行多少次该运算,可以快速求出1的个数。 4.4 代码实现 计算数n中包含1的个数 intcountOne(intn){ inttotal=0; while(n>0){ total++; n&=n-1; } returntotal; } 变量定义及初始化 inti,n,k,line[8],f[2][256],num[256]; for(i=0;i<256;++i)num[i]=countOne(i); memset(f,0,2*256*4); f[0][0]=1; intj,c,now=0; //棋盘读入 for(i=0;i<n;++i){ intt=0; for(intj=0;j<n;++j){ t<<=1; cin>>ch; if(ch=='.')t+=1; } line[i]=t; } 核心递推 for(i=0;i<n;++i){ now=1-now; for(j=0;j<256;++j) if(num[j]<=k){ //第i行不放棋子 f[now][j]+=f[1-now][j]; //第i行放棋子 for(c=0;c<n;++c){ if((j&1<<c)==0&&(line[i]&1<<c)==0){ f[now][j|1<<c]+=f[1-now][j]; } } } } //枚举所有包含k个棋子的方案数 intans=0; for(i=0;i<256;++i){ if(num[i]==k){ ans+=f[now][i]; } } cout<<ans<<endl; 5.总结 递推最重要的思想,就是通过每一小步,找出与下一步之间的关系。关键在于思考问题的本质,对问题进行建模。常用f[i][j][k]等类似数组来记录,多一维就可以多记录一维状态信息,要思考上一步真正有多少个因素会影响当前步,那一般这些就是一定要记录的信息。 关注我,涨知识,微信公众号:几何思维 往期精彩回顾 蚂蚁走迷宫 老鼠与毒药 通过6人介绍可以认识世界上任何一个人?

优秀的个人博客,低调大师

Ansible 日常使用技巧 - 运维总结

Ansible默认只会创建5个进程并发执行任务,所以一次任务只能同时控制5台机器执行。如果有大量的机器需要控制,例如20台,Ansible执行一个任务时会先在其中5台上执行,执行成功后再执行下一批5台,直到全部机器执行完毕。使用-f选项可以指定进程数,指定的进程数量多一些,不仅会实现全并发,对异步的轮训poll也会有正面影响。 Ansible默认是同步阻塞模式,它会等待所有的机器都执行完毕才会在前台返回。Ansible可以采取异步执行模式。异步模式下,Ansible会将节点的任务丢在后台,每台被控制的机器都有一个job_id,Ansible会根据这个job_id去轮训该机器上任务的执行情况,例如某机器上此任务中的某一个阶段是否完成,是否进入下一个阶段等。即使任务早就结束了,但只有轮训检查到任务结束后才认为该job结束。Ansible可以指定任务检查的时间间隔,默认是10秒。除非指定任务检查的间隔为0,否则会等待所有任务都完成后,Ansible端才会释放占用的shell。如果指定时间间隔为0,则Ansible会立即返回(至少得连接上目标主机,任务发布成功之后立即返回),并不会去检查它的任务进度。 Ansible的同步模式与异步模式同步模式: 如果节点数太多,ansible无法一次在所有远程节点上执行任务,那么将先在一部分节点上执行一个任务(每一批节点的数量取决于fork进程数量,默认为5个,可设置),直到这一批所有节点上该任务完全执行完毕才会接入下一个批节点,直到所有节点将该任务都执行完毕,然后重新回到第一批节点开始执行第二个任务。依次类推,直到所有节点执行完所有任务,ansible端才会释放shell。这是默认同步模式,也就是说在未执行完毕时,ansible是占用当前shell的,任务执行完后,释放shell了才可以输入其他命令做其他动作。 异步模式:假如fork控制的并发进程数为5,远程控制节点为24个,则ansible一开始会将5个节点的任务扔在后台,并每隔一段时间去检查这些节点的任务完成情况,当某节点完成不会立即返回,而是继续等待直到5个进程都空闲了,才会将这5个节点的结果返回给ansible端,ansible会继续将下一批5个节点的任务扔在后台并每隔一段时间进行检查,依次类推,直到完成所有任务。 在异步模式下,如果设置的检查时间间隔为0,在将每一批节点的任务丢到后台后都会立即返回ansible,并立即将下一批节点的任务丢到后台,直到所有任务都丢到后台完后,才返回ansible端,ansible才会立即释放占用的shell。即此时ansible是不会管各个节点任务执行情况的,不管执行成功或失败。因此在轮训检查时间内,ansible仍然正在运行(尽管某批任务已经被放到后台执行了),当前shell进程仍被占用处于睡眠状态,只有指定的检查时间间隔为0,才会尽快将所有任务放到后台并释放shell。 一、Ansible的异步和轮询 [async、poll]Ansible有时候要执行等待时间很长的操作,这个操作可能要持续很长时间,设置超过ssh的timeout。这种情况下可以选择在step中指定async和poll来实现异步操作。其中:async:表示这个step的最长等待时长, 如果设置为0, 表示一直等待下去直到动作完成;poll:表示检查step操作结果的间隔时长。 ansible默认的清单文件是/etc/ansible/hosts,也就是ansible和ansible-ploybook执行时默认读的清单文件。这个可以自行定义。 [root@hostname~]#cat/etc/ansible/ansible.cfg|grepinventory #inventory=/etc/ansible/hosts [root@hostname~]#cat/etc/ansible/hosts|tail-2 [test_server]#组名最好不要使用"-",可以使用"_" 172.16.60.241 1)先来看下面初始配置 [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server tasks: -name:ansible-test shell:sleep10 #async表示上述shell命令的等待时间,设置为0时会一直等待命令结束 async:5 #poll表示检查step操作结果的间隔时长,设置为0表示不用等待结果,继续做下面的操作,我们可以在下面的step中来验证这个命令是否成功执行. poll:2 执行下看看是否成功: [root@hostname~]#ansible-playbook/etc/ansible/test.yml PLAY[test_server]******************************************************************************************************************************* TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.241] TASK[ansible-test]****************************************************************************************************************************** fatal:[172.16.60.241]:FAILED!=>{"changed":false,"msg":"asynctaskdidnotcompletewithintherequestedtime-5s"} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=1changed=0unreachable=0failed=1skipped=0rescued=0ignored=0 如上,这个step失败,因为ansible的任务(就是上面配置中的shell动作)操作时间(10s)超过了最大等待时长(5s) 2)如果将上面的async异步等待时间设置为大于10s,比如12s,则执行就成功了! [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server tasks: -name:ansible-test shell:sleep10 #async表示上述shell命令的等待时间,设置为0时会一直等待命令结束 async:12 #poll表示检查step操作结果的间隔时长,设置为0表示不用等待结果,继续做下面的操作,我们可以在下面的step中来验证这个命令是否成功执行. poll:2 [root@hostname~]#ansible-playbook/etc/ansible/test.yml PLAY[test_server]******************************************************************************************************************************* TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.241] TASK[ansible-test]****************************************************************************************************************************** changed:[172.16.60.241] PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 这时候就不怕任务超时了。可以执行一个12s的任务(大于上面shell执行的时间)。另外,如果poll为0,就相当于一个不关心结果的任务。 3)或者将上面的poll数值设置为0,即不用等待ansible任务执行的结果,立即执行下一个step。 即只需要将任务命令推送到ansible客户机上,不需要等待任务执行完成就立即执行下一个step。 [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server tasks: -name:ansible-test shell:sleep10 #async表示上述shell命令的等待时间,设置为0时会一直等待命令结束 async:5 #poll表示检查step操作结果的间隔时长,设置为0表示不用等待结果,继续做下面的操作,我们可以在下面的step中来验证这个命令是否成功执行. poll:0 [root@hostname~]#ansible-playbook/etc/ansible/test.yml PLAY[test_server]******************************************************************************************************************************* TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.241] TASK[ansible-test]****************************************************************************************************************************** changed:[172.16.60.241] PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 4)如果还想要更方便地看轮询结果,ansible还提供了这个模块async_status。 [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server tasks: -name:ansible-test shell:sleep3 async:8 poll:2 register:kevin_result -name:'checkansible-testtaskpollingresults' async_status:jid={{kevin_result.ansible_job_id}} register:job_result until:job_result.finished retries:10 [root@hostname~]#ansible-playbook/etc/ansible/test.yml PLAY[test_server]******************************************************************************************************************************* TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.241] TASK[ansible-test]****************************************************************************************************************************** changed:[172.16.60.241] TASK[checkansible-testtaskpollingresults]*************************************************************************************************** changed:[172.16.60.241] PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=3changed=2unreachable=0failed=0skipped=0rescued=0ignored=0 第一个job执行异步任务sleep,并且注册了一个名字叫kevin-result的register变量,用于提供给第二个job作为轮询对象,并且它自己poll设为2(即自己轮询2次)。 register用于在ansible的playbook中task之间的相互传递变量, register这个功能非常有用。当我们需要判断对执行了某个操作或者某个命令后,如何做相应的响应处理(执行其他ansible语句),则一般会用到register。 until表示循环。 第二个job使用async_status模块,进行轮询并返回轮询结果。准备检查10次。 async参数值:代表了这个任务执行时间的上限值。即任务执行所用时间如果超出这个时间,则认为任务失败。此参数若未设置,则为同步执行。poll参数值:代表了任务异步执行时轮询的时间间隔。 二、Ansible的并发限制 [serial、max_fail_percentage]当ansible清单文件里设置的组里有很多机器,可以限制一下ansible任务的并发。ansible的并发功能可以在ansible.cfg里修改配置,也可以在playbook中限制服务端的并发数量,这是ansible经常用到的一个关键功能。ansible默认情况下只会创建5个进程,所以一次任务只能同时控制5台机器执行。如果有大量的机器需要控制,或者希望减少进程数,那就可以采取异步执行(async),ansible的模块可以把task放进后台,然后轮询它(poll)。 使用async和poll这两个关键字便可以并行运行一个任务,即在所有机器上一次性运行。async这个关键字会触发ansible并行运作任务,async的值是ansible等待运行这个任务的最大超时值(如果执行超时任务会强制中断导致失败),而poll就是ansible检查这个任务是否完成的频率时间。 1)serial参数设置并发数 ===================================================================== 一般情况下,ansible会同时在所有服务器上执行用户定义的操作,但是用户可以通过serial参数来定义同时可以在多少太机器上执行操作。 [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server serial:3 tasks: -name:Installtelnet yum:name=telnetstate=installed 即test_server组内的3台机器完全执行完成play后,其他机器才能开始执行。 接着看下面的配置 [root@hostname~]#cat/etc/ansible/test.yml -hosts:all serial:7 tasks: -name:Installtelnet yum:name=telnetstate=installed -name:RunServerstart.sh command:/bin/bash/opt/scripts/Serverstart.sh async:300 poll:10 register:kevin_result 如上配置,发现当ansible配置控制超过5台机器时,上面ansible中: a)yum模块会先在5台机器上跑,完成后再继续剩余2台的机器; b)command模块的任务会一次性在所有机器上都执行了,然后监听它的回调结果; 这里需要注意下面两种情况 a)情况一:设置poll=0 如果上面command模块是控制机器开启一个进程放到后台,那就不需要检查这个任务是否完成了,只需要继续其他的动作, 最后再使用wait_for这个模块去检查之前的进程是否按预期中开启了便可。 这时只需要把poll这个值设置为0,便可以按上面的要求配置ansible不等待job的完成。 b)情况二:设置async=0 如果有一种需求是有一个task它是需要运行很长的时间,那就需要设置一直等待这个job完成。 这个时候只需要把async的值设成0便可。 简单总结下,适合使用到ansible的polling特性的场景 -有一个task需要运行很长的时间,这个task很可能会达到timeout; -有一个任务需要在大量的机器上面运行; -有一个任务是不需要等待它完成的; 不适合使用polling特性的场景 -task任务是需要运行完后才能继续另外的任务的; -task任务能很快的完成; 2)max_fail_percentage:最大失败百分比 ===================================================================== 默认情况下,只要ansible的group中还有server没有失败,ansible就是继续执行tasks。实际上,用户可以通过max_fail_percentage(最大失败百分比)来限制ansible的并发执行。 只要超过max_fail_percentage的server失败,ansible就可以中止tasks的执行。serial参数在ansible-1.8以后就开始支持百分比功能了!! 试想一下如果group组里有200台机器,那么如果使用serial来限制并发数量,比如设置serial=10,意思就是一次只执行10台,一直到200台完成。 只要组内还有server没有失败,ansible就是继续执行tasks。这样就显得效率很低了,很不方便!这时就可以使用类似控制流的max_fail_percentage功能了!! [root@hostname~]#cat/etc/ansible/test.yml -hosts:all max_fail_percentage:30 serial:10 tasks: -name:Installtelnet yum:name=telnetstate=installed -name:RunServerstart.sh command:/bin/bash/opt/scripts/Serverstart.sh async:300 poll:10 register:kevin_result 如上配置,即10台机器里有30%的机器执行yum模块的task任务失败,那么就终止这个10台机器的task任务的执行,接着执行下一组10台机器的task任务,这样效果就很棒了。 温馨提示: 实际失败机器必须大于这个百分比时,tasks任务才会被中止;如果等于这个百分比时,task任务是不会被终止的! 踩坑经验:Ansible并发失败(fork=100. 但是真正执行playbook时并没有实现并发) [root@hostname~]#cd/usr/lib/python2.7/site-packages/ansible/ [root@hostnameansible]#find.-namessh.py ./plugins/connection/ssh.py [root@hostnameansible]#vimplugins/connection/ssh.py ......... ......... ifC.HOST_KEY_CHECKINGandnot_in_host_file: #lockaroundtheinitialSSHconnectivitysotheuserpromptaboutwhethertoadd #thehosttoknownhostsisnotintermingledwithmultiprocessoutput. fcntl.lockf(self.runner.process_lockfile,fcntl.LOCK_EX) fcntl.lockf(self.runner.output_lockfile,fcntl.LOCK_EX) #createprocess (p,stdin)=self._run(ssh_cmd,in_data) ......... ......... 通过以上文件代码可以看出: 如果ansible配置"HOST_KEY_CHECKING=True",并且ansible客户机信息没有在ansible服务端的~/.ssh/known_hosts里面,一个进程就会锁死~/.ssh/known_hosts文件。 这样ansible就不能实现并发! 解决方案: 在ansible服务端的/etc/ansible/ansible.cfg文件里配置"host_key_checking=False"[其实ansible.cfg文件里该项默认配置的就是False] 三、Ansible的任务委托 [delegate_to、delegate_facts、run_once]默认情况下,ansible的所有任务都是在指定的机器上运行的。当在一个独立的群集环境中配置时,只是想操作其中的某一台主机,或者在特定的主机上运行task任务,此时就需要用到ansible的任务委托功能。使用delegate_to关键字可以配置task任务在指定的机器上执行,就是说其他的task任务还是在hosts关键字配置的机器上运行,到了这个关键字所在的任务时,就使用委托的机器运行。 1)委托 ===================================================================== 通过"delegate_to",ansible可以把某一个task任务放在委托的机器上执行。即在指定的组内的某一台或多台机器上执行task任务。 [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server serial:10 tasks: -name:test-haha shell:echo"test">/root/test.list delegate_to:172.16.60.245 则上面的shell模块的task任务只会在172.16.60.245这台节点上执行,test_server组内其他的机器不会执行shell任务。 --------------------- 如果"delegate_to:127.0.0.1"则可以用local_action来代替。即下面两个配置效果是一样的!! [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server serial:10 tasks: -name:test-haha shell:echo"test">/root/test.list delegate_to:127.0.0.1 [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server serial:10 tasks: -name:test-haha local_action:shellecho"test">/root/test.list ------------------- 如果设置了多个delegate_to,则执行时只会匹配最下面那个。 例如下面配置中,只会执行"delegate_to:172.16.60.245",上面那个"delegate_to:172.16.60.241"就会被忽略了。 [root@hostnameansible]#cat/etc/ansible/test.yml -hosts:all serial:10 tasks: -name:test-haha shell:echo"test">/root/test.list delegate_to:172.16.60.241 delegate_to:172.16.60.245 ------------------- delegate_to默认后面只能跟一个主机ip,不能跟多个主机ip。即默认委托到单个主机。 如果有多个ip需要委托,则可以将这些ip重新放一个group,然后delegate_to委托给group组。 delegate_to委托到组的方式:通过items变量方式!!! [root@hostnameansible]#cat/etc/ansible/hosts|tail-8 [test_server] 172.16.60.241 172.16.60.245 172.16.60.246 127.0.0.1 [kevin_server] 172.16.60.246 127.0.0.1 [root@hostnameansible]#cat/etc/ansible/test.yml -hosts:all tasks: -name:test-haha shell:echo"test">/root/test.list delegate_to:"{{item}}" with_items:"{{groups['kevin_server']}}" 即将shell这个task任务委托给kevin_server组内的机器执行。 2)委托者的facts ===================================================================== 默认情况下,ansible委托任务的facts是inventory_hostname中主机的facts,而不是被委托机器的facts。 a)delegate_facts 在ansible2.0中,通过设置"delegate_facts:True"可以让task任务去收集被委托机器的facts。 [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server tasks: -name:test-haha shell:echo"test">/root/test.list delegate_to:"{{item}}" delegate_facts:True with_items:"{{groups['kevin_server']}}" 如上配置,表示会收集kevin_server的facts并分配给这些机器,而不会去收集test_server的facts b)RUNONCE 通过设置"run_once:true"来指定该task只能在委托的某一台机器或委托的组内机器上执行一次!!可以和delegate_to结合使用。 如果没有delegate_to,那么这个task默认就会在第一台机器上执行!!! [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server tasks: -name:test-haha shell:echo"test">/root/test.list delegate_to:"{{item}}" run_once:true delegate_facts:True with_items:"{{groups['kevin_server']}}" 四、Ansible的任务暂停 [local_action、wait_for]当Ansible一些任务的运行需要等到一些状态的恢复,比如某一台主机或者应用刚刚重启,需要等待其某个端口开启,这个时候就需要用到Ansible的任务暂停功能。Ansible任务的暂停操作是通过local_action配合wait_for模块来完成的。 [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server remote_user:root gather_facts:no tasks: -name:kevin_test local_action: module:wait_for#模块名字 port:2379 host:172.16.60.241 delay:10 timeout:300 state:started 使用local_action配合wait_for模块来完成任务的暂停操作。 上面配置说明kevin_test任务每隔10s检查指定主机上的2379端口是否开启,如果操作300s,2379端口任未开启,将返回失败信息。 上面host指定了一台机器,如果是需要指定多台机器呢? 可以将执行的多台机器放在一台新group内,然后通过变量去指定group。 [root@hostname~]#cat/etc/ansible/test.yml -hosts:test_server remote_user:root gather_facts:no tasks: -name:kevin_test local_action: module:wait_for port:2379 host:"{{item}}" delay:10 timeout:300 state:started with_items:"{{groups['kevin_server']}}" 如上面配置,每间隔10s检查指定的kevin_server组内的主机的2379端口是否开启,如果操作300s,2379端口没开启,则返回失败信息。 注意:上面的"with_items"这一项配置要和"local_action"对齐!!否则会报错! 五、Ansible如何判断并中断执行 [when、fail]在使用ansible-playbook在执行一个脚本时,如何根据脚本返回的内容判断是否继续往下执行还是中断执行?查询官网可以发现使用register寄存器可以实现记录脚本输出,并且使用when+fail模块来判断是否往下继续执行或者中断。 远端机器172.16.60.242有如下脚本: [root@242~]#cat/mnt/scripts/test.sh #!/bin/bash TEXT=$1 if[$1=="kevin"];then echo"Success" else echo"Failed" fi [root@242~]#/bin/bash/mnt/scripts/test.shkevin Success [root@242~]#/bin/bash/mnt/scripts/test.shkevin234 Failed 现在要求: a)通过ansible执行172.16.60.242的test.sh脚本,当脚本返回Success时,在172.16.60.242机器上创建一个目录/opt/kevin。 b)通过ansible执行172.16.60.242的test.sh脚本,当脚本返回Failed时,则中断执行。 在ansible服务端配置yml文件,相关配置过程如下: 1)如下配置,将command模块的task任务委托给kevin_server组内的172.16.60.242机器执行。 先使用了register寄存器,具体寄存了什么内容,可以使用-v参数来查看输出 [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root tasks: -name:an_bo command:/bin/bash/mnt/scripts/test.shkevin delegate_to:172.16.60.242 register:result [root@hostname~]#ansible-playbook-v/etc/ansible/test.yml Using/etc/ansible/ansible.cfgasconfigfile PLAY[kevin_server]****************************************************************************************************************************** TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.242] ok:[172.16.60.241] TASK[an_bo]************************************************************************************************************************************* changed:[172.16.60.242->172.16.60.242]=>{"changed":true,"cmd":["/bin/bash","/mnt/scripts/test.sh","kevin"],"delta":"0:00:00.004078", "end":"2019-10-1115:35:49.850430","rc":0,"start":"2019-10-1115:35:49.846352","stderr":"","stderr_lines":[],"stdout":"Success", "stdout_lines":["Success"]} changed:[172.16.60.241->172.16.60.242]=>{"changed":true,"cmd":["/bin/bash","/mnt/scripts/test.sh","kevin"],"delta":"0:00:00.004502", "end":"2019-10-1115:35:49.852445","rc":0,"start":"2019-10-1115:35:49.847943","stderr":"","stderr_lines":[],"stdout":"Success", "stdout_lines":["Success"]} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 172.16.60.242:ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 可以看出: register保存的信息就是上面执行结果中"=>"后面的字典信息,信息保存在result变量中。 并且看到"stdout"就是脚本的标准输出信息,这时可以使用"when"来判断是否执行或者跳过。 2)使用"when"来判断是否执行或者跳过。 [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root tasks: -name:an_bo command:/bin/bash/mnt/scripts/test.shkevin delegate_to:172.16.60.242 register:result -name:ru_bo file:path=/opt/kevinstate=directory delegate_to:172.16.60.242 when:result.stdout=='Success' 查看执行结果: [root@hostname~]#ansible-playbook-v/etc/ansible/test.yml Using/etc/ansible/ansible.cfgasconfigfile PLAY[kevin_server]****************************************************************************************************************************** TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.241] ok:[172.16.60.242] TASK[an_bo]************************************************************************************************************************************* changed:[172.16.60.242->172.16.60.242]=>{"changed":true,"cmd":["/bin/bash","/mnt/scripts/test.sh","kevin"],"delta":"0:00:00.002337","end":"2019-10-1115:48:20.427582","rc":0,"start":"2019-10-1115:48:20.425245","stderr":"","stderr_lines":[],"stdout":"Success","stdout_lines":["Success"]} changed:[172.16.60.241->172.16.60.242]=>{"changed":true,"cmd":["/bin/bash","/mnt/scripts/test.sh","kevin"],"delta":"0:00:00.002579","end":"2019-10-1115:48:20.425082","rc":0,"start":"2019-10-1115:48:20.422503","stderr":"","stderr_lines":[],"stdout":"Success","stdout_lines":["Success"]} TASK[ru_bo]************************************************************************************************************************************* changed:[172.16.60.241->172.16.60.242]=>{"changed":true,"gid":0,"group":"root","mode":"0755","owner":"root","path":"/opt/kevin","size":6,"state":"directory","uid":0} ok:[172.16.60.242->172.16.60.242]=>{"changed":false,"gid":0,"group":"root","mode":"0755","owner":"root","path":"/opt/kevin","size":6,"state":"directory","uid":0} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=3changed=2unreachable=0failed=0skipped=0rescued=0ignored=0 172.16.60.242:ok=3changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 可以发现,当脚本返回Success时,已经在172.16.60.242机器上创建一个目录/opt/kevin。 3)现在将脚本输出内容修改为"Failed"(即执行脚本时,$1为非kevin字符) [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root tasks: -name:an_bo command:/bin/bash/mnt/scripts/test.shshibo delegate_to:172.16.60.242 register:result -name:ifstdout'Failed',Interruptexecution delegate_to:172.16.60.242 fail:msg="CheckFailed" when:result.stdout=='Failed' -name:ru_bo file:path=/opt/kevinstate=directory delegate_to:172.16.60.242 when:result.stdout=='Success' 查看执行结果: [root@hostname~]#ansible-playbook-v/etc/ansible/test.yml Using/etc/ansible/ansible.cfgasconfigfile PLAY[kevin_server]****************************************************************************************************************************** TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.242] ok:[172.16.60.241] TASK[an_bo]************************************************************************************************************************************* changed:[172.16.60.241->172.16.60.242]=>{"changed":true,"cmd":["/bin/bash","/mnt/scripts/test.sh","shibo"],"delta":"0:00:00.002767","end":"2019-10-1115:57:56.049142","rc":0,"start":"2019-10-1115:57:56.046375","stderr":"","stderr_lines":[],"stdout":"Failed","stdout_lines":["Failed"]} changed:[172.16.60.242->172.16.60.242]=>{"changed":true,"cmd":["/bin/bash","/mnt/scripts/test.sh","shibo"],"delta":"0:00:00.002698","end":"2019-10-1115:57:56.051455","rc":0,"start":"2019-10-1115:57:56.048757","stderr":"","stderr_lines":[],"stdout":"Failed","stdout_lines":["Failed"]} TASK[ifstdout'Failed',Interruptexecution]**************************************************************************************************** fatal:[172.16.60.241->172.16.60.242]:FAILED!=>{"changed":false,"msg":"CheckFailed"} fatal:[172.16.60.242->172.16.60.242]:FAILED!=>{"changed":false,"msg":"CheckFailed"} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=2changed=1unreachable=0failed=1skipped=0rescued=0ignored=0 172.16.60.242:ok=2changed=1unreachable=0failed=1skipped=0rescued=0ignored=0 可以看出: playbook运行到第二个task时就会报错并抛出msg!根据第二个task任务,脚本输出结果为"Failed",直接中断任务执行。那么第三个task任务就不会被执行了。 注意: result寄存器中的数据都可以拿来使用,如"rc","stderr"等。 当然也有很多种方法,文中的"Failed"是严格匹配,也可以使用模糊查找,如"result.stdout.find('Failed')!=-1"也可以达到相同的效果 [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root tasks: -name:an_bo command:/bin/bash/mnt/scripts/test.shshibo delegate_to:172.16.60.242 register:result -name:ifstdout'Failed',Interruptexecution delegate_to:172.16.60.242 fail:msg="CheckFailed" when:result.stdout.find('Failed')!=-1#等同于when:result.stdout=='Failed' -name:ru_bo file:path=/opt/kevinstate=directory delegate_to:172.16.60.242 when:result.stdout=='Success' 六、Ansible之条件判断 [when]在日常运维工作中,在有的时候ansble-playbook的结果依赖于变量、fact或者是前一个任务的执行结果,从而需要使用到条件语句。使用ansible-playbook时,可能需要对某些条件进行判断,只有当满足条件才执行相应的tasks。有下面几种条件判断: 1)when条件判断:只条满足when的条件时才执行对应的tasks ===================================================================== 需要注意:when关键字后面跟着的是python的表达式,在表达式中我们能够使用任何的变量或者facts。 另外注意:当需要用远程主机的一些信息时,gather_facts必须要开启,默认是开启状态!!!!! [root@hostname~]#cat/etc/ansible/hosts|tail-3 [kevin_server] 172.16.60.241 172.16.60.242 注意:下面debug中msg后面引用的变量都是在setup模块中查询出来的(可直接作为变量引用) [root@hostname~]#ansible172.16.60.242-msetup|grepansible_fqdn "ansible_fqdn":"webserver02", [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root gather_facts:True tasks: -name:Host172.16.60.242runthistask debug:'msg="{{ansible_default_ipv4.address}}"' when:ansible_default_ipv4.address=="172.16.60.242" -name:memtotal<500Mandprocessor_cores==2runthistask debug:'msg="{{ansible_fqdn}}"' when:ansible_memtotal_mb<500andansible_processor_cores==2 -name:allhostrunthistask shell:hostname register:info -name:Hostnameiswebserver01Machierunthistask debug:'msg="{{ansible_fqdn}}"' when:info['stdout']=="webserver01" -name:Hostnameisstartswithlrunthistask debug:'msg="{{ansible_fqdn}}"' when:info['stdout'].startswith('l') 查看执行结果: [root@hostname~]#ansible-playbook-v/etc/ansible/test.yml Using/etc/ansible/ansible.cfgasconfigfile PLAY[kevin_server]****************************************************************************************************************************** TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.242] ok:[172.16.60.241] TASK[Host172.16.60.242runthistask]********************************************************************************************************** skipping:[172.16.60.241]=>{} ok:[172.16.60.242]=>{ "msg":"172.16.60.242" } TASK[memtotal<500Mandprocessor_cores==2runthistask]************************************************************************************ skipping:[172.16.60.241]=>{} skipping:[172.16.60.242]=>{} TASK[allhostrunthistask]******************************************************************************************************************** changed:[172.16.60.241]=>{"changed":true,"cmd":"hostname","delta":"0:00:00.003661","end":"2019-10-1117:19:29.912525","rc":0, "start":"2019-10-1117:19:29.908864","stderr":"","stderr_lines":[],"stdout":"webserver01","stdout_lines":["webserver01"]} changed:[172.16.60.242]=>{"changed":true,"cmd":"hostname","delta":"0:00:00.004133","end":"2019-10-1117:19:29.922962","rc":0, "start":"2019-10-1117:19:29.918829","stderr":"","stderr_lines":[],"stdout":"webserver02","stdout_lines":["webserver02"]} TASK[Hostnameiswebserver01Machierunthistask]********************************************************************************************** ok:[172.16.60.241]=>{ "msg":"k8s-master01" } skipping:[172.16.60.242]=>{} TASK[Hostnameisstartswithlrunthistask]**************************************************************************************************** skipping:[172.16.60.241]=>{} skipping:[172.16.60.242]=>{} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=3changed=1unreachable=0failed=0skipped=3rescued=0ignored=0 172.16.60.242:ok=3changed=1unreachable=0failed=0skipped=3rescued=0ignored=0 2)when条件判断之引用变量 ===================================================================== when变量引用错误提示:[WARNING]:whenstatementsshouldnotincludejinja2templatingdelimiterssuchas{{}}or{%%}. 正确的引用方式:将{{}}or{%%}改为() 错误写法示例:when:ansible_default_ipv4.address=={{webserver01}} 正确写法示例:when:ansible_default_ipv4.address==(webserver01) [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root gather_facts:True tasks: -name:Host192.168.1.101runthistask #debug:'msg="{{ansible_default_ipv4.address}}"' shell:hostname when:ansible_default_ipv4.address==(webserver02) 查看执行结果: [root@hostname~]#ansible-playbook-v/etc/ansible/test.yml-e"webserver02=172.16.60.242" Using/etc/ansible/ansible.cfgasconfigfile PLAY[kevin_server]****************************************************************************************************************************** TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.241] ok:[172.16.60.242] TASK[Host192.168.1.101runthistask]********************************************************************************************************** skipping:[172.16.60.241]=>{"changed":false,"skip_reason":"ConditionalresultwasFalse"} changed:[172.16.60.242]=>{"changed":true,"cmd":"hostname","delta":"0:00:00.004349","end":"2019-10-1117:23:39.961860","rc":0, "start":"2019-10-1117:23:39.957511","stderr":"","stderr_lines":[],"stdout":"webserver02","stdout_lines":["webserver02"]} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=1changed=0unreachable=0failed=0skipped=1rescued=0ignored=0 172.16.60.242:ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 3)changed_when:先执行task,并对task返回的值进行判断,当满足changed_when指定的条件时说明是执行成功的 ===================================================================== 需要注意:默认情况下执行了命令的主机状态都为changed,本例对输出进行判断,包含是某个指定字符才能为changed; [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root gather_facts:True tasks: -name:allhostrunthistask shell:hostname register:info changed_when:'"webserver01"ininfo.stdout' 查看执行结果: [root@hostname~]#ansible-playbook-v/etc/ansible/test.yml Using/etc/ansible/ansible.cfgasconfigfile PLAY[kevin_server]****************************************************************************************************************************** TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.241] ok:[172.16.60.242] TASK[allhostrunthistask]******************************************************************************************************************** changed:[172.16.60.241]=>{"changed":true,"cmd":"hostname","delta":"0:00:00.004531","end":"2019-10-1117:25:15.865591","rc":0, "start":"2019-10-1117:25:15.861060","stderr":"","stderr_lines":[],"stdout":"webserver01","stdout_lines":["webserver01"]} ok:[172.16.60.242]=>{"changed":false,"cmd":"hostname","delta":"0:00:00.004694","end":"2019-10-1117:25:15.872135","rc":0, "start":"2019-10-1117:25:15.867441","stderr":"","stderr_lines":[],"stdout":"webserver02","stdout_lines":["webserver02"]} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 172.16.60.242:ok=2changed=0unreachable=0failed=0skipped=0rescued=0ignored=0 4)failed_when ===================================================================== failed_when:当执行失败后,会将信息存在register的stderr中,通过判断指定的字符是否在stderr中来确定是否真的失败; [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root gather_facts:True tasks: -name:thiscommandprintsFAILEDwhenitfails command:echo"FAILED" register:command_result failed_when:"'FAILED'incommand_result.stdout" -name:thisisatest shell:echo"haha" [root@hostname~]#ansible-playbook-v/etc/ansible/test.yml Using/etc/ansible/ansible.cfgasconfigfile PLAY[kevin_server]****************************************************************************************************************************** TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.242] ok:[172.16.60.241] TASK[thiscommandprintsFAILEDwhenitfails]************************************************************************************************** fatal:[172.16.60.241]:FAILED!=>{"changed":true,"cmd":["echo","FAILED"],"delta":"0:00:00.002550","end":"2019-10-1119:19:47.918921","failed_when_result":true,"rc":0,"start":"2019-10-1119:19:47.916371","stderr":"","stderr_lines":[],"stdout":"FAILED","stdout_lines":["FAILED"]} fatal:[172.16.60.242]:FAILED!=>{"changed":true,"cmd":["echo","FAILED"],"delta":"0:00:00.002410","end":"2019-10-1119:19:47.943843","failed_when_result":true,"rc":0,"start":"2019-10-1119:19:47.941433","stderr":"","stderr_lines":[],"stdout":"FAILED","stdout_lines":["FAILED"]} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=1changed=0unreachable=0failed=1skipped=0rescued=0ignored=0 172.16.60.242:ok=1changed=0unreachable=0failed=1skipped=0rescued=0ignored=0 可以看出,第一个task任务的failed_when已经满足了,所以就此停止playbook的运行了,下面的task任务也不会执行了! failed_when其实是ansible的一种错误处理机制,是由fail模块使用了when条件语句的组合效果。 所以,上面的配置也可以调整成下面写法(上面第一个task可以调整为下面第1和第2个task的写法,是一样的效果): [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root gather_facts:True tasks: -name:thiscommandprintsFAILEDwhenitfails command:echo"FAILED" register:command_result -name:failtheplayifthepreviouscommanddidnotsucceed fail:msg="thecommandfailed" when:"'FAILED'incommand_result.stdout" -name:thisisatest shell:echo"haha" [root@hostname~]#ansible-playbook-v/etc/ansible/test.yml Using/etc/ansible/ansible.cfgasconfigfile PLAY[kevin_server]****************************************************************************************************************************** TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.242] ok:[172.16.60.241] TASK[thiscommandprintsFAILEDwhenitfails]************************************************************************************************** changed:[172.16.60.241]=>{"changed":true,"cmd":["echo","FAILED"],"delta":"0:00:00.003989","end":"2019-10-1119:19:06.741840","rc":0,"start":"2019-10-1119:19:06.737851","stderr":"","stderr_lines":[],"stdout":"FAILED","stdout_lines":["FAILED"]} changed:[172.16.60.242]=>{"changed":true,"cmd":["echo","FAILED"],"delta":"0:00:00.003135","end":"2019-10-1119:19:06.744136","rc":0,"start":"2019-10-1119:19:06.741001","stderr":"","stderr_lines":[],"stdout":"FAILED","stdout_lines":["FAILED"]} TASK[failtheplayifthepreviouscommanddidnotsucceed]************************************************************************************* fatal:[172.16.60.241]:FAILED!=>{"changed":false,"msg":"thecommandfailed"} fatal:[172.16.60.242]:FAILED!=>{"changed":false,"msg":"thecommandfailed"} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=2changed=1unreachable=0failed=1skipped=0rescued=0ignored=0 172.16.60.242:ok=2changed=1unreachable=0failed=1skipped=0rescued=0ignored=0 这里就可以看出"failed_when"的作用,它的作用就是当failed_when关键字对应的条件成立时,failed_when会将对应的任务的执行状态设置为失败,以停止playbook的运行! 但是需要注意的时:failed_when虽然会将任务的执行状态设置为失败,但是它并不代表任务真的失败了!就以上面例子来说,上面的command模块的确时完全正常的执行了, 只不过在执行之后,failed_when对应的条件成立了,failed_when将command模块的执行状态设置为失败而已!所以,failed_when并不会影响command模块的执行过程, 只会在条件成立时影响command模块最终的执行状态,以便于停止playbook的运行。 因此需要注意: failed_when:关键字的作用是在条件成立时,将对应任务的执行状态设置为失败! changed_when:关键字的作用是在条件成立时,将对应任务的执行状态设置为changed! 七、Ansible之性能优化 [提升ansible执行效率]最初,ansible的执行效率和saltstack(基于zeromq消息队列的方式)相比要慢的多的多,特别是被控节点量很大的时候。但是ansible发展到现在,它的效率得到了极大的改善。在被控节点不太多的时候,默认的设置已经够快。即使被控节点数量巨大的时候,也可以通过一些优化去极大的提高ansible的执行效率。所以在使用 Ansible 的过程中,当管理的服务器数量增加时,不得不面对一个无法避免的问题执行效率慢,这里列出一些解决办法。 1. 关闭gathering facts功能 如果观察过ansible-playbook的执行过程,就会发现ansible-playbook的第1个步骤总是执行gatherfacts,不论你有没有在playbook设定这个tasks。 如果你不需要获取被控机器的fact数据的话,就可以关闭获取fact数据功能。关闭之后,可以加快ansible-playbook的执行效率,尤其是你管理很大量的机器时,这非常明显。 关闭获取facts很简单,只需要在playbook文件中加上"gather_facts:False"或者"gather_facts:No"即可(False和No都为小写也可以)。 [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root tasks: -name:thisisatest shell:echo"haha" 执行这个paly,会发现第一个执行的是gatherfacts,因为默认是打开gatherfacts功能的!!!! [root@hostname~]#ansible-playbook-v/etc/ansible/test.yml Using/etc/ansible/ansible.cfgasconfigfile PLAY[kevin_server]****************************************************************************************************************************** TASK[GatheringFacts]*************************************************************************************************************************** ok:[172.16.60.242] ok:[172.16.60.241] TASK[thisisatest]**************************************************************************************************************************** changed:[172.16.60.241]=>{"changed":true,"cmd":"echo\"haha\"","delta":"0:00:00.002949","end":"2019-10-1119:33:54.883702","rc":0,"start":"2019-10-1119:33:54.880753","stderr":"","stderr_lines":[],"stdout":"haha","stdout_lines":["haha"]} changed:[172.16.60.242]=>{"changed":true,"cmd":"echo\"haha\"","delta":"0:00:00.003409","end":"2019-10-1119:33:54.884398","rc":0,"start":"2019-10-1119:33:54.880989","stderr":"","stderr_lines":[],"stdout":"haha","stdout_lines":["haha"]} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 172.16.60.242:ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 现在关闭gatheringfacts功能 [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root gather_facts:False tasks: -name:thisisatest shell:echo"haha" 再执行这个play,就会发现没有了gatheringfacts执行过程,整个执行速度也快了! [root@hostname~]#ansible-playbook-v/etc/ansible/test.yml Using/etc/ansible/ansible.cfgasconfigfile PLAY[kevin_server]****************************************************************************************************************************** TASK[thisisatest]**************************************************************************************************************************** changed:[172.16.60.242]=>{"ansible_facts":{"discovered_interpreter_python":"/usr/bin/python"},"changed":true,"cmd":"echo\"haha\"","delta":"0:00:00.002571","end":"2019-10-1119:35:06.821842","rc":0,"start":"2019-10-1119:35:06.819271","stderr":"","stderr_lines":[],"stdout":"haha","stdout_lines":["haha"]} changed:[172.16.60.241]=>{"ansible_facts":{"discovered_interpreter_python":"/usr/bin/python"},"changed":true,"cmd":"echo\"haha\"","delta":"0:00:00.003121","end":"2019-10-1119:35:06.842207","rc":0,"start":"2019-10-1119:35:06.839086","stderr":"","stderr_lines":[],"stdout":"haha","stdout_lines":["haha"]} PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=1changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 172.16.60.242:ok=1changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 2. 开启 SSH pipelining pipeline是openssh的一个特性,sshpipelining是一个加速Ansible执行速度的简单方法。 在ansible执行每个任务的整个流程中,有一个过程是将临时任务文件put到远程的ansible客户机上,然后通过ssh连接过去远程执行这个任务。 如果开启了pipelining,一个任务的所有动作都在一个ssh会话中完成,也会省去sftp到远端的过程,它会直接将要执行的任务在ssh会话中进行。 sshpipelining默认是关闭!!!!之所以默认关闭是为了兼容不同的sudo配置,主要是requiretty选项。如果不使用sudo,建议开启!!! 打开此选项可以减少ansible执行没有传输时ssh在被控机器上执行任务的连接数。 不过,如果使用sudo,必须关闭requiretty选项。修改/etc/ansible/ansible.cfg文件可以开启pipelining [root@hostname~]#vim/etc/ansible/ansible.cfg ........ pipelining=True 这样开启了pipelining之后,ansible执行的整个流程就少了一个PUT脚本去远程服务端的流程,然后就可以批量对机器执行命令试下,可以明显感受到速度的提升。 —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— 但是要注意的是: 如果在ansible中使用sudo命令的话(sshuser@hostsudocmd),需要在被控节点的/etc/sudoers中禁用"requiretty"!!!! 之所以要设置/etc/sudoers中的requiretty,是因为ssh远程执行命令时,它的环境是非登录式非交互式shell,默认不会分配tty,没有tty,ssh的sudo就无法关闭密码回显(使用 "-tt"选项强制SSH分配tty)。所以出于安全考虑,/etc/sudoers中默认是开启requiretty的,它要求只有拥有tty的用户才能使用sudo,也就是说ssh连接过去不允许执行sudo。 可以通过visudo编辑配置文件,注释该选项来禁用它。 [root@webserver01~]#greprequiretty/etc/sudoers #Defaultsrequiretty 3. 开启SSH长连接 (ControlPersist特性) ansible天然支持openssh,默认连接方式下,它对ssh的依赖性非常强。所以优化ssh连接,在一定程度上也在优化ansible。其中一点是开启ssh的长连接,即长时间保持连接状态。 Ansible模式是使用SSH和远程主机进行通信,所以Ansible对SSH的依赖性非常强,在OpenSSH5.6版本以后SSH就支持了Multiplexing(多路复用)。 所以如果Ansible中控机的SSH-V版本高于5.6时,就可以使用ControlPersist来提高ssh连接速度,从而提高ansible执行效率。 [root@hostnameansible]#cat/etc/redhat-release CentOSLinuxrelease7.6.1810(Core) [root@hostnameansible]#ssh-V OpenSSH_7.4p1,OpenSSL1.0.2k-fips26Jan2017 我们可以直接在ansible.cfg文件中设置SSH长连接,设置参数如下: [root@hostnameansible]#vim/etc/ansible/ansible.cfg .......... ssh_args=-C-oControlMaster=auto-oControlPersist=5d 注意: ConrolPersist=5d,这个参数是设置整个长连接保持时间为5天。 开启此参数的ssh长连接功能后,在会话过期前会一直建立连接,在netstat的结果中会看到ssh连接是一直established状态,且通过SSH连接过的设备都会在当前用户家目录的 ".ansible/cp"目录下生成一个socket文件,每个会话对应生成一个socket文件。也可以通过netstat命令查看,会发现有一个ESTABLISHED状态的连接一直与远程设备进行着TCP连接。 [root@hostnameansible]#ps-ef|grepssh|grepansible root56141023:09?00:00:00ssh:/root/.ansible/cp/7e37065045[mux] root56171023:09?00:00:00ssh:/root/.ansible/cp/e2056334cd[mux] [root@hostnameansible]#netstat-anptu|grepESTABLISHED|grepssh|grep/root tcp00172.16.60.246:44430172.16.60.242:22ESTABLISHED5617/ssh:/root/.an tcp00172.16.60.246:43498172.16.60.241:22ESTABLISHED5614/ssh:/root/.an [root@hostnameansible]#ls/root/.ansible/cp/ 7e37065045e2056334cd 需要注意: ControlPersist特性需要高版本的SSH才支持,CentOS6默认是不支持的,如果需要使用,需要自行升级openssh(确保SSH-V版本高于5.6)。 ControlPersist即持久化socket,一次验证,多次通信。并且只需要修改ssh客户端就行,也就是Ansible机器即可。 4. 开启accelerate模式 [ 注意:这个只针对centos6系统 ] Ansible还有一个accelerate模式,这和前面的Multiplexing有点类似,因为都依赖Ansible中控机跟远程机器有一个长连接。 但是accelerate是使用python程序在远程机器上运行一个守护进程,然后Ansible会通过这个守护进程监听的端口进行通信。 开启accelerate模式很简单,只要在playbook中配置accelerate:true即可. 但是需要注意的是: 如果开启accelerate模式,则需要在Ansible中控机与远程机器都安装python-keyczar软件包。 下面是在ansible.cfg文件中定义一些accelerate参数,当然也可以在写playbook的时候再定义 第一步:ansible服务端和客户端都要安装python-keyczar [root@hostname~]#yuminstall-ypython-keyczar 第二步:修改ansible服务端的ansible.cfg文件 [root@hostname~]#vim/etc/ansible/ansible.cfg .......... [accelerate] accelerate_port=5099 accelerate_timeout=30 accelerate_connect_timeout=5.0 第三步:修改ansible服务端的ansible-playbook的剧本文件,加入accelerate:true [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root gather_facts:False accelerate:true tasks: -name:thisisatest shell:echo"haha" 需要注意: 这种优化方式只针对centos6系统来提高连接速度。在centos7下不可用,否则会报错:"ERROR!'accelerate'isnotavalidattributeforaPlay" 如果ansible没有性能瓶颈的情况下,不推荐使用这种优化措施! 5. 设置facts缓存 如果细心的话,就会发现执行playbook的时候,默认第一个task都是GATHERINGFACTS,这个过程就是Ansible在收集每台主机的facts信息。 方便我们在playbook中直接饮用facts里的信息,当然如果你的playbook中不需要facts信息,可以在playbook中设置"gather_facts:False"来提高playbook效率. 但是如果我们既想在每次执行playbook的时候都能收集facts,又想加速这个收集过程,那么就需要配置facts缓存了。 目前Ansible支持使用json文件存储facts信息。 第一种缓存方式:使用json文件缓存 [root@hostname~]#vim/etc/ansible/ansible.cfg ......... gathering=smart fact_caching_timeout=86400 fact_caching=jsonfile fact_caching_connection=/dev/shm/ansible_fact_cache 正常配置palybook,不需要关闭gatheringfacts功能 [root@hostname~]#cat/etc/ansible/test.yml -hosts:kevin_server remote_user:root tasks: -name:thisisatest shell:echo"haha" 查看这个playbook过程,用时1.102s(第一次可能稍微慢点,缓存之后,后面执行就很快了) [root@hostname~]#timeansible-playbook/etc/ansible/test.yml PLAY[kevin_server]****************************************************************************************************************************** TASK[thisisatest]**************************************************************************************************************************** changed:[172.16.60.241] changed:[172.16.60.242] PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=1changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 172.16.60.242:ok=1changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 real0m1.102s user0m0.879s sys0m0.179s 如果去掉上面的facts缓存的四行配置,再次执行上面的playbok,发现用时10s左右!!! 查看缓存文件: [root@hostname~]#ls/dev/shm/ansible_fact_cache/ 172.16.60.241172.16.60.242 第二种缓存方式:使用redis存储facts文件需安装redis,还需要安装python库 [root@hostname~]#yuminstallredis [root@hostname~]#yum-yinstallepel-release [root@hostname~]#yuminstallpython-pip [root@hostname~]#pipinstallredis [root@hostname~]#vim/etc/ansible/ansible.cfg ........ gathering=smart facts_caching_timeout=86400#设置缓存过期时间86400秒 facts_caching=redis#使用redis或者(或者使用memcached,即"facts_caching=memcached") fact_caching_connection=127.0.0.1:6379 #若redis设置了密码,比如密码为"admin",则配置修改如下: #fact_caching_connection=localhost:6379:0:admin 启动redis [root@hostname~]#systemctlstartredis [root@hostname~]#lsof-i:6379 COMMANDPIDUSERFDTYPEDEVICESIZE/OFFNODENAME redis-ser29218redis4uIPv42917862090t0TCPlocalhost:6379(LISTEN) 执行上面的palybook [root@hostname~]#timeansible-playbook/etc/ansible/test.yml PLAY[kevin_server]****************************************************************************************************************************** TASK[thisisatest]**************************************************************************************************************************** changed:[172.16.60.241] changed:[172.16.60.242] PLAYRECAP*************************************************************************************************************************************** 172.16.60.241:ok=1changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 172.16.60.242:ok=1changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 real0m1.132s user0m0.909s sys0m0.178s 需要注意: 在使用redis缓存后,如果出现异常(若未出现,请忽略):TypeError:theJSONobjectmustbestr,not'bytes'。 解决办法: [root@hostname~]#find/-nameansible [root@hostname~]#vim/usr/lib/python2.7/site-packages/ansible/plugins/cache/redis.py .......... self._cache[key]=json.loads(value.decode('utf-8'))#修改为这个 查看redis存储情况 [root@hostname~]#redis-cli 127.0.0.1:6379>keys* 1)"ansible_facts172.16.60.242" 2)"ansible_facts172.16.60.241" 3)"ansible_cache_keys" 总之:不同网络环境下的耗时肯定是不同的,但是设置缓存是肯定可以加快Ansible运行速度的,特别是playbook的运行。 6. Ansible取消交互 [root@hostname~]#vim/etc/ansible/ansible.cfg ........ host_key_checking=False#打开注释即可 取消ssh的yes和no的交互: [root@hostname~]#vim/root/.ssh/config UserKnownHostsFile/dev/null ConnectTimeout15 StrictHostKeyCheckingno 或者直接ssh时增加一个参数 [root@hostname~]#ssh-oStrictHostKeyChecking=no-p22root@172.16.60.247 7. Ansible的-t选项,提高ansible执行效率 ansible的"-t"或"--tree"选项是将ansible的执行结果按主机名保存在指定目录下的文件中。 有些时候,ansible执行起来的速度会非常慢,这种慢体现在即使执行的是一个立即返回的简单命令(如ping模块),也会耗时很久,且不是因为ssh连接慢导致的。 如果使用-t选项,将第一次执行得到的结果按inventory中定义的主机名保存在文件中,下次执行到同一台主机时速度将会变快很多,即使之后不再加上-t选项, 也可以在一定时间内保持迅速执行。即使执行速度正常(如执行一个Ping命令0.7秒左右),使用-t选项也可以在此基础上变得更快。 除了使用-t选项,使用重定向将结果重定向到某个文件中也是一样的效果。 这也算是一种ansible提速方式,但在centos6上使用低版本ansible时,有时会出现执行很慢的现象,但不是每次都这样,且centos7执行速度正常 所以这也是一种"bug"式问题,故这种方式没有通用性。 [root@hostname~]#timeansiblekevin_server-mcommand-a"hostname" [root@hostname~]#timeansiblekevin_server-mcommand-a"hostname"-t/tmp/test [root@hostname~]#ll/tmp/a total8 -rw-r--r--1rootroot2780Oct1202:03172.16.60.241 -rw-r--r--1rootroot2776Oct1202:03172.16.60.242 上面做了对比,发现使用-t或重定向方式,将ansible的执行结果按主机名保存在指定目录下的文件中,ansible执行效率会有所提升。 八、Ansible之变量设置首先Ansible通过facts组件来收集被管理节点信息,facts收集的信息是json格式的,其中任一项都可以当作变量被直接引用,如在ansible-playbook、jinja2模板中引用。 1. Ansible factsfacts组件是用来收集被管理节点信息的,使用setup模块可以获取这些信息。 [root@ss-server~]#ansible-doc-ssetup -name:Gathersfactsaboutremotehosts setup: .................. 如下示例,是setup收集客户机172.16.60.21的信息示例,由于收集的信息项非常多,这里只是截取了部分内容项。 [root@ss-server~]#ansible172.16.60.21-msetup 172.16.60.21|SUCCESS=>{ "ansible_facts":{ "ansible_all_ipv4_addresses":[ "172.16.60.21" ], "ansible_all_ipv6_addresses":[ "fe80::20c:29ff:fe03:a452" ], "ansible_apparmor":{ "status":"disabled" }, "ansible_architecture":"x86_64", "ansible_bios_date":"07/02/2019", "ansible_bios_version":"6.00", "ansible_cmdline":{ "BOOT_IMAGE":"/vmkivin-3.10.0-327.el7.x86_64", "LANG":"en_US.UTF-8", "biosdevname":"0", "crashkernel":"auto", "net.ifnames":"0", "quiet":true, "ro":true, "root":"UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8" }, ........................................ "ansible_default_ipv6":{}, "ansible_devices":{ "sda":{ "holders":[], "host":"SCSIstoragecontroller:LSILogic/SymbiosLogic53c1030PCI-XFusion-MPTDualUltra320SCSI(rev01)", "model":"VMwareVirtualS", "partitions":{ "sda1":{ "holders":[], "sectors":"512000", "sectorsize":512, "size":"250.00MB", "start":"2048", "uuid":"367d6a77-033b-4037-bbcb-416705ead095" }, "sda2":{ "holders":[], "sectors":"37332992", "sectorsize":512, "size":"17.80GB", "start":"514048", "uuid":"b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8" }, ................................ "ansible_user_dir":"/root", "ansible_user_gecos":"root", "ansible_user_gid":0, "ansible_user_id":"root", "ansible_user_shell":"/bin/bash", "ansible_user_uid":0, "ansible_userspace_architecture":"x86_64", "ansible_userspace_bits":"64", "ansible_virtualization_role":"guest", "ansible_virtualization_type":"VMware", "module_setup":true }, "changed":false } 使用filter可以筛选指定的facts信息,如下示例: [root@ss-server~]#ansible172.16.60.21-msetup-a"filter=changed" 172.16.60.21|SUCCESS=>{ "ansible_facts":{}, "changed":false } [root@ss-server~]#ansiblelocalhost-msetup-a"filter=*ipv4" localhost|SUCCESS=>{ "ansible_facts":{ "ansible_default_ipv4":{ "address":"172.16.60.20", "alias":"eth0", "broadcast":"172.16.60.255", "gateway":"172.16.60.2", "interface":"eth0", "macaddress":"00:0c:29:d9:0b:71", "mtu":1500, "netmask":"255.255.255.0", "network":"172.16.60.0", "type":"ether" } }, "changed":false } 总之,facts收集的信息是json格式的,其里面的任一项都可以在playbook、jinja2模板中当作变量被直接引用。 2. 变量引用json数据的方式在ansible中,任何一个模块都会返回json格式的数据,即使是错误信息都是json格式的。在ansible中,json格式的数据,其中每一项都可以通过变量来引用它。当然,引用的前提是先将其注册为变量。例如下面的playbook是将shell模块中echo命令的结果注册为变量,并使用debug模块输出。 --- -hosts:172.16.60.21 tasks: -shell:echogoodwork register:kevin_bo -debug:var=kevin_bo debug输出的结果如下: TASK[debug]********************************************* ok:[172.16.60.21]=>{ "kevin_bo":{ "changed":true, "cmd":"echogoodwork", "delta":"0:00:00.002086", "end":"2019-05-2011:23:40.484507", "rc":0, "start":"2019-05-2011:23:40.482421", "stderr":"", "stderr_lines":[], "stdout":"goodwork", "stdout_lines":[ "goodwork" ] } } 可以看出,结果是一段json格式的数据,最顶端的key为kevin_bo,内部是一大段的字典(即使用大括号包围的),其中的stdout_lines还包含了一个json数组,也就是所谓的yaml列表项(即使用中括号包围的)。 2.1 引用json字典数据的方式如果想要输出json数据的某一字典项,则应该使用"key.dict"或"key['dict']"的方式引用。例如最常见的stdout项"good work"是想要输出的项,以下两种方式都能引用该字典变量。 --- -hosts:172.16.60.21 tasks: -shell:echogoodwork register:kevin_bo -debug:var=kevin_bo.stdout -debug:var=kevin_bo['stdout'] ansible-playbook的部分输出结果如下: TASK[debug]************************************************ ok:[172.16.60.21]=>{ "kevin_bo.stdout":"goodwork" } TASK[debug]************************************************ ok:[172.16.60.21]=>{ "kevin_bo['stdout']":"goodwork" } "key.dict"或"key['dict']"的方式都能引用,但在dict字符串本身就包含"."的时候,应该使用中括号的方式引用。例如: anykey['172.16.60.21'] 2.2 引用json数组数据的方式如果想要输出json数据中的某一数组项(列表项),则应该使用"key[N]"的方式引用数组中的第N项,其中N是数组的index,从0开始计算。如果不使用index,则输出的是整个数组列表。例如想要输出上面的stdout_lines中的"good work",它是数组stdout_lines中第一项所以使用stdout_lines[0]来引用,再加上stdout_lines上面的kevin_bo,于是引用方式如下: --- -hosts:172.16.60.21 tasks: -shell:echogoodwork register:kevin_bo -debug:var=kevin_bo.stdout_lines[0] 由于stdout_lines中仅有一项,所以即使不使用index的方式即kevin_bo.stdout_lines也能得到期望的结果。输出结果如下: TASK[debug]************************************************* ok:[172.16.60.21]=>{ "kevin_bo.stdout_lines[0]":"goodwork" } 接着看下面一段json数据: "ipv6":[ { "address":"fe80::20c:29ff:fe26:1498", "prefix":"64", "scope":"link" } ] 其中key=ipv6,其内部有且仅有是一个列表项,但该列表内包含了数个字典项。要引用列表内的字典,例如上面的address项。应该如下引用: ipv6[0].address 2.3 引用facts数据如上介绍已经可以了解json数据中的字典和列表项的引用方式,显然facts中的一大堆数据就能被引用并派上用场了。例如以下是一段facts数据。 [root@ss-server~]#ansiblelocalhost-msetup-a"filter=*eth*" localhost|SUCCESS=>{ "ansible_facts":{ "ansible_eth0":{ "active":true, "device":"eth0", "features":{ "busy_poll":"off[fixed]", "fcoe_mtu":"off[fixed]", "generic_receive_offload":"on", ......................... }, "ipv4":{ "address":"172.16.60.18", "broadcast":"172.16.60.255", "netmask":"255.255.255.0", "network":"172.16.60.0" }, "macaddress":"00:0c:29:d9:0b:71", "module":"e1000", ............................ } }, "changed":false } 显然,facts数据的顶级key为ansible_facts,在引用时应该将其包含在变量表达式中。但自动收集的facts比较特殊,它以ansible_facts作为key,ansible每次收集后会自动将其注册为变量,所以facts中的数据都可以直接通过变量引用,甚至连顶级key即ansible_facts都要省略。 例如引用上面的ipv4的地址address项,需要写成: ansible_eth0.ipv4.address 而不能写成: ansible_facts.ansible_eth0.ipv4.address 但其他任意时候,都应该带上所有的key! 3. 设置本地facts在ansible收集facts时,还会自动收集/etc/ansible/facts.d/*.fact文件内的数据到facts中,且以ansible_local做为key。目前fact支持两种类型的文件:ini和json。当然,如果fact文件的json或ini格式写错了导致无法解析,那么肯定也无法收集。例如,在/etc/ansible/facts.d目录下存在一个kevin.fact的文件,其内数据如下: [root@ss-server~]#cat/etc/ansible/facts.d/kevin.fact { "member":{ "wangbo":{ "address":"anhui", "age":"31" }, "liuxiaoru":{ "address":"hebei", "age":"28" } } } ansible收集facts后的本地facts数据如下: [root@ss-server~]#ansiblelocalhost-msetup-a"filter=ansible_local" localhost|SUCCESS=>{ "ansible_facts":{ "ansible_local":{ "kevin":{ "member":{ "wangbo":{ "age":"31", "address":"anhui" }, "liuxiaoru":{ "age":"28", "address":"hebei" } } } } }, "changed":false } 可见,如果想要引用本地文件中的某个key,除了带上ansible_local外,还必须得带上fact文件的文件名。例如,引用father的name。 ansible_local.kevin.member.wangbo.address 4. 输出和引用变量上面已经展示了一种变量的引用方式:使用debug的var参数。debug的另一个参数msg也能输出变量,且msg可以输出自定义信息,而var参数只能输出变量。另外,msg和var引用参数的方式有所不同。例如: --- -hosts:172.16.60.21 tasks: -debug:'msg="ipv4address:{{ansible_eth0.ipv4.address}}"' -debug:var=ansible_eth0.ipv4.address msg引用变量需要加上双大括号包围,既然加了大括号,为了防止被解析为内联字典,还得加引号包围。这里使用了两段引号,因为其内部还包括了一个": ",加引号可以防止它被解析为"key: "的格式。而var参数引用变量则直接指定变量名。这就像bash中引用变量的方式是一样的,有些时候需要加上$,有些时候不能加$。即当引用的是变量的值的时候,msg就需要加双大括号,就像加$一样,而当引用的是变量本身的时候,则msg不能加双大括号。其实双大括号是jinja2中的分隔符。执行的部分结果如下: TASK[debug]***************************************************** ok:[172.16.60.21]=>{ "msg":"ipv4address:172.16.60.21" } TASK[debug]***************************************************** ok:[172.16.60.21]=>{ "ansible_eth0.ipv4.address":"172.16.60.21" } 在Ansible使用中,几乎所有地方都可以引用变量,例如循环、when语句、信息输出语句、template文件等等。只不过有些地方不能使用双大括号,有些地方需要使用。 5. 注册和定义变量的各种方式Ansible中定义变量的方式有很多种,大致有下面七种:1) 将模块的执行结果注册为变量;2) 直接定义字典类型的变量;3) role中文件内定义变量;4) 命令行传递变量;5) 借助with_items迭代将多个task的结果赋值给一个变量;6) inventory中的主机或主机组变量;7)内置变量。 5.1 register注册变量使用register选项,可以将当前task的输出结果赋值给一个变量。例如,下面的示例中将echo的结果"good work"赋值给kevin_bo变量。注意,模块的输出结果是json格式的,所以,引用变量时要指定引用的对象。 --- -hosts:localhost tasks: -shell:echogoodwork register:kevin_bo -debug:var=kevin_bo.stdout 5.2 set_fact定义变量set_fact和register的功能很相似,也是将值赋值给变量。它更像shell中变量的赋值方式,可以将某个变量的值赋值给另一个变量,也可以将字符串赋值给变量。例如: --- -hosts:172.16.60.21 tasks: -shell:echogoodwork register:kevin_bo -set_fact:var1="{{kevin_bo.stdout}}" -set_fact:var2="yournameis" -debug:msg="{{var2}}{{var1}}" 5.3 vars定义变量可以在play或task层次使用vars定义字典型变量。如果同名,则task层次的变量覆盖play层次的变量。例如: --- -hosts:localhost vars: var1:anhui var2:beijing tasks: -debug:msg="{{var1}}{{var2}}" vars: var2:shanghai 输出结果为: TASK[debug]******************************************** ok:[localhost]=>{ "msg":"anhuishanghai" } 5.4 vars_files定义变量和vars一样,只不过它是将变量以字典格式定义在独立的文件中,且vars_files不能定义在task层次,只能定义在play层次。 --- -hosts:localhost vars_files: -/tmp/var_file1.yml -var_file2.yml tasks: -debug:msg="{{var1}}{{var2}}" 上面var_file2.yml使用的是相对路径,基于playbook所在的路径。例如该playbook为/tmp/x.yml,则var_file2.yml也应该在/tmp下。当然,完全可以使用绝对路径。 5.5 roles中的变量由于role是整合playbook的,它有默认的文件组织结构。其中有一个目录vars,其内部的main.yml用于定义变量。还有defaults目录内的main.yml则是定义role默认变量的,默认变量的优先级最低。 [root@ss-server~]#tree/yaml /yaml ├──roles │└──nginx │├──defaults │└──main.yml │├──files │├──handlers │├──meta │├──tasks │├──templates │└──vars │└──main.yml └──site.yml main.yml中变量定义方式也是字典格式,例如: --- mysql_port:3306 5.6 命令行传递变量ansible和ansible-playbook命令的"-e"选项都可以传递变量,传递的方式有两种:-e key=value和-e @var_file。注意:当key=value方式传递变量时,如果变量中包含特殊字符,必须防止其被shell解析。例如: ansiblelocalhost-mshell-a"echo{{kevin_bo}}"-e'kevin_bo="goodwork"' ansiblelocalhost-mshell-a"echo{{kevin_bo}}"-e@/tmp/var_file1.yml 其中/tmp/var_file1.yml中的内容如下: --- kevin_bo:goodwork 5.7 借助with_items叠加变量ansible中可以借助with_items实现列表迭代的功能,作用于变量注册的行为上,就可以实现将多个结果赋值给同一个变量。例如下面的playbook中,给出了3个item列表,并在shell模块中通过固定变量"{{item}}"分别迭代,第一次迭代的是anhui,第二次迭代的是beijing,第三次迭代的是shanghai,也就实现了3次循环。最后,将结果注册为变量kevin_bo。 --- -hosts:localhost remote_user:root tasks: -name:test shell:echo"{{item}}" with_items: -anhui -beijing -shanghai register:kevin_bo -debug:var=kevin_bo.results[0].stdout -debug:var=kevin_bo.results[1].stdout -debug:var=kevin_bo.results[2].stdout 每次迭代的过程中,调用item的模块都会将结果保存在一个key为results的数组中。因此,引用迭代后注册的变量时,需要在变量名中加上results,并指定数组名。例如上面的kevin_bo.results[N].stdout。此外,还可以使用for循环遍历列表。例如: -debug:msg="{%foriinkevin_bo.results%}{{i.stdout}}{%endfor%}" 其实,看一下kevin_bo的输出就很容易理解了。以下是kevin_bo的第一个列表的输出。 "kevin_bo":{ "changed":true, "msg":"Allitemscompleted", "results":[ { "_ansible_item_result":true, "_ansible_no_log":false, "_ansible_parsed":true, "changed":true, "cmd":"echo\"anhui\"", "delta":"0:00:00.001942", "end":"2019-05-1204:45:57.032946", "invocation":{ "module_args":{ "_raw_params":"echo\"anhui\"", "_uses_shell":true, "chdir":null, "creates":null, "executable":null, "removes":null, "warn":true } }, "item":"anhui", "rc":0, "start":"2019-05-1204:45:57.031004", "stderr":"", "stderr_lines":[], "stdout":"anhui", "stdout_lines":[ "anhui" ] } 5.8 inventory中主机变量和主机组变量在inventory文件中可以为主机和主机组定义变量,不仅包括内置变量赋值,还包括自定义变量赋值。例如以下inventory文件(/root/ansible/test.yml)。 172.16.60.20ansible_ssh_port=22var1=1#变量优先级别排第二 [webserver]#变量优先级别排第一 172.16.60.18 172.16.60.19 172.16.60.20var1=2 [webserver:vars]#变量优先级别排第三 var1=2.2 var2=3 [all:vars]#变量优先级别排第四 var2=4 其中ansible_ssh_port是主机内置变量,为其赋值22,这类变量是内置类变量。此外还在多处为主机172.16.60.20进行了赋值。其中[webserver:vars]和[all:vars]表示为主机组赋值,前者是为webserver这个组赋值,后者是为所有组赋值。以下是执行语句: [root@ss-server~]#ansible172.16.60.20-i/root/ansible/test.yml-mshell-a'echo"{{var1}}{{var2}}{{ansible_ssh_port}}"' 172.16.60.20|SUCCESS|rc=0>> 2322 从结果可知:主机组里面的主机变量优先级高于无主机组的主机变量,设定的主机组变量优先级高于all特殊组。除了在inventory文件中定义主机、主机组变量,还可以将其定义在host_vars和group_vars目录下的独立的文件中,但要求这些host_vars或group_vars这两个目录和inventory文件或playbook文件在同一个目录下,且变量的文件以对应的主机名或主机组名命名。例如,inventory文件路径为/etc/ansible/hosts,playbook文件路径为/root/ansible/play.yml,则主机172.16.60.20和主机组webserver的变量文件路径可以为以下几种: /etc/ansible/host_vars/172.16.60.20 /etc/ansible/group_vars/webserver /root/ansible/host_vars/172.16.60.20 /root/ansible/group_vars/webserver 如下是一个host_vars目录下的文件内容: [root@ss-server~]#cat/root/ansible/host_vars/172.16.60.20 var1:1.1 var2:2.2 var3:3.3 var4:4.4 以下为/root/ansible/play.yml的内容 --- -hosts:172.16.60.20 tasks: -debug:msg='{{var1}}{{var2}}{{var3}}{{var4}}' 执行结果如下: TASK[debug]********************************************** ok:[172.16.60.20]=>{ "msg":"1.12.23.34.4" } 5.9 内置变量ansible除了inventory中内置的一堆不可被引用的设置类变量,还有几个全局都可以引用的内置变量,主要有以下几个:inventory_hostname、inventory_hostname_short、groups、group_names、hostvars、play_hosts、inventory_dir和ansible_version。 1)inventory_hostname 和 inventory_hostname_short分别代表的是inventory中被控节点的主机名和主机名的第一部分,如果定义的是主机别名,则变量的值也是别名。例如inventory中webserver主机组定义为如下: [webserver] 172.16.60.20 web01ansible_ssh_host=172.16.60.21 www.kevin.comansible_ssh_host=172.16.60.22 分别输出它们的inventory_hostname和inventory_hostname_short。 [root@ss-server~]#ansiblewebserver-mdebug-a'msg="{{inventory_hostname}}&{{inventory_hostname_short}}"' 172.16.60.20|SUCCESS=>{ "msg":"172.16.60.20&172" } web01|SUCCESS=>{ "msg":"web01&web01" } www.kevin.com|SUCCESS=>{ "msg":"www.kevin.com&www" } 2)groups 和 group_namesgroup_names返回的是主机所属主机组,如果该主机在多个组中,则返回多个组,如果它不在组中,则返回ungrouped这个特殊组。例如,某个inventory文件如下: 172.16.60.20 172.16.60.21 172.16.60.22 172.16.60.23 [webserver] 172.16.60.20 [dbserver] 172.16.60.21 db01ansible_ssh_host=172.16.60.22 www.kevin.comansible_ssh_host=172.16.60.23 [server:children] webserver dbserver 其中172.16.60.20定义在webserver和server中,所以返回这两个组。同理172.16.60.21返回dbserver和server。172.16.60.22和172.16.60.23则返回ungrouped,虽然它们在dbserver中都定义了别名,但至少将172.16.60.22和172.16.60.23作为主机名时,它们是不在任何主机中的。另一方面,db01和www.kevin.com这两个别名主机都返回dbserver和server两个组。groups变量则是返回其所在inventory文件中所有组和其内主机名。注意:该变量对每个控制节点都返回一次,所以返回的内容可能非常多。例如,上面的inventory中,如果指定被控节点为dbserver,则会重复返回3次(因为有3台被控主机)该inventory文件。其中的第三台主机www.kevin.com的返回结果为: www.kevin.com|SUCCESS=>{ "msg":{ "all":[ "172.16.60.20", "172.16.60.21", "172.16.60.22", "172.16.60.23", "db01", "www.kevin.com" ], "server":[ "172.16.60.20", "172.16.60.21", "db01", "www.kevin.com" ], "webserver":[ "172.16.60.20" ], "dbserver":[ "172.16.60.21", "db01", "www.kevin.com" ], "ungrouped":[ "172.16.60.20", "172.16.60.21", "172.16.60.22", "172.16.60.23" ] } } 3)hostvars该变量用于引用其他主机上收集的facts中的数据,或者引用其他主机的主机变量、主机组变量。其key为主机名或主机组名。举个例子,假如使用ansible部署一台nginx服务器web01,且配置文件内需要指向另一台数据库服务器web02的ip地址ip2,可以直接在配置文件中指定ip2,但也可以在模板配置文件中直接引用host2收集的facts数据中的ansible_eth0.ipv4.address变量。例如,dbserver主机组中包含了172.16.60.[21:23]共3台主机。playbook内容如下: --- -hosts:dbserver tasks: -debug:msg="{{hostvars['172.16.60.21'].ansible_eth0.ipv4.address}}" 执行结果如下: TASK[debug]********************************************************* ok:[172.16.60.21]=>{ "msg":"172.16.60.21" } ok:[172.16.60.22]=>{ "msg":"172.16.60.21" } ok:[172.16.60.23]=>{ "msg":"172.16.60.21" } 但是请注意:在引用其他主机facts中数据时,要求被引用主机进行了facts收集动作,或者有facts缓存。否则都没收集,当然无法引用其facts数据。也就是说,当被引用主机没有facts缓存时,ansible的控制节点中必须同时包含引用主机和被引用主机。除了引用其他主机的facts数据,还可以引用其他主机的主机变量和主机组变量,且不要求被引用主机有facts数据,因为主机变量和主机组变量是在ansible执行任务前加载的。例如,inventory中格式如下: 172.16.60.18 [dbserver] 172.16.60.21var1=1.1 172.16.60.22 172.16.60.23 [dbserver:vars] var2=2.2 playbook内容如下: --- -hosts:172.16.60.18 tasks: -debug:msg="{{hostvars['172.16.60.21'].var1}}&{{hostvars['172.16.60.23'].var2}}" 执行结果如下: TASK[debug]*************************************** ok:[172.16.60.18]=>{ "msg":"1.1&2.2" } 4)play_hosts 和 inventory_dirplay_hosts代表的是当前play所涉及inventory内的所有主机名列表。inventory_dir是所使用inventory所在的目录。例如,inventory内容为: 172.16.60.18 [webserver] 172.16.60.21 172.16.60.22 [dbserver] 172.16.60.24 172.16.60.25 那么,该inventory内的任意一或多台主机作为ansible或ansible-playbook的被控节点时,都会返回整个inventory内的所有主机名称。 5)ansible_version代表的是ansible软件的版本号。变量返回的内容如下: { "full":"2.3.1.0", "major":2, "minor":3, "revision":1, "string":"2.3.1.0" } ##### ansibl变量的优先级 #####1. extra vars变量(在命令行中使用 -e);优先级最高2. 在inventory中定义的连接变量(比如ansible_ssh_user);优先级第二3. 大多数的其他变量(命令行转换,play中的变量,include的变量,role的变量等);优先级第三4. 在inventory定义的其他变量;优先级第四5. 有系统发现的facts;优先级第五6. "role默认变量",这个是最默认的值,很容易丧失优先权。优先级最小。 ##### inventory清单列表里定义变量:单个主机定义的变量优先级高于主机组定义的变量 #####经过实验,ansible使用inventory定义变量的优先级顺序从高到低为:1. host_vars下定义变量2. inventory中单个主机定义变量3. group_vars下定义变量4. inventory中组定义变量 总之,ansible提供的变量定义方式真的是太丰富了,这些变量可以让ansible使用起来十分灵活,功能强大!

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册