ansible 深入实践
[toc]
RHCA447 ansible的版本: 2.8
什么是ansible
开源的自动化管理工具
用于管理中间件(应用)
- 必备条件
- 主控端需要知道所有被它管理的节点的ip
- inventory 主控清单
- 它需要有权限连接到所有的节点并能执行相应的任务
—> 能免连上 - ssh
—> 有权限执行相应的任务 - 授权 - 主控端需要能够选择特定的主机执行任务
- 批量执行的任务是由管理员编写并下发给主控端的
- 主控端需要知道所有被它管理的节点的ip
当前主流的应用自动化管理工具
- ansible
- 主法简单
- 轻量级
- python 开发
- 无客户端 ,基于ssh连接被控端
- serverless,即无服务端,在主控端上,只有一条ansible指令,不需要启动任何服务
- 同时管理500+节点的时候会出现性能问题
- saltstack
- 语法相对复杂
- 重理级
- python开发
- 有客户端,但也支持无客户端的ssh连接
- 有服务端,被称之为salt-master, 需要去考虑master的高可用及负载均衡能力,当master挂了时,随之所有客端的地址都改。
- 号称可以管理上万台的节点
- puppet
- Fabric
- 可以整合python语句实现更匹配公司业务的自动化工具编写
- 无客户端无服务端,基于ssh进行近程控制
- ansible
ansible 对被控端的要求
- 得有ssh
- 所有被控端得有一个统一的用户,可用于主控制端
- 被控端的这个用户,得有相应用的权限
- 在所有的被控端上得有python的环境
实验hosts清单
127.0.0.1 workstation
172.25.250.9 workstation
172.25.250.10 servera servera.lab.example.com
172.25.250.11 serverb serverb.lab.example.com
172.25.250.12 serverc serverc.lab.example.com
172.25.250.13 serverd serverd.lab.example.com
172.25.250.14 servere servere.lab.example.com
ansible 安装与初始化
- 安装ansible
# yum -y install ansible # 这里以ansible 2.8的版本为例
配置ssh免密
配置主机清单
# vim /etc/ansible/hosts # 这里是ansible.cfg inventory 默认指定的主机清单
servera
serverb
serverc
serverd
servere
- 测试连通性
# ansible all -m ping
- 命令行参数
- 选择主机: 从主机清单当中选择特定的主机
- 执行相应的任务模块
- -m ping
- 配置要执行的模块的参数,使用-a 来指定
主机清单
主机选择
- all 选择所有主机
- “*” 选择所有主机
- groupname 选择主机组
ansible -i inventory webservers,dbservers,servere --list-hosts
可以用逗号分隔来匹配多个组或者多个主机,主机名,或者组名会在主机清单中自动实别- 使用通配符来匹配主机
# ansible -i inventory "*.example.com" --list-hosts # ansible -i inventory "172.25.*" --list-hosts # asnbile -i inventory "*.example.com,!*.lab.example.com" --list-hosts # 匹配所有.example.com 结尾的主机,但排除所有.lab.example.com结尾的主机
- 使用正则表达式来匹配,在开头的地方使用
~
,用来表示这是一个正则表达式# ansible -i inventory '~(s|db).*example\.com' --list-hosts
- 通过—limit 在选定的组中明确指定主机
ansible -i inventory ungrouped --limit srv1.example.com --list-hosts
,表示选定组后,只执行组中的指定节点 - 通过—limit 参数,还可以指定一个文件,该文件中定义指定的主机列表
# ansible ungrouped --limit @retry_hosts.txt --list-hosts # 指定组中,限制hosts.txt中的主机列表执行
主机清单定义
ansible 当前主控端用户是谁,那么默认用当前登录用户来连接被控端主机,即被控端也需要有当前主机的用户
ansible2.8之前定义主机组
[webservers]
servera
serverb
[dbservers]
serverd
servere
[webdbservers:children]
webservers
dbservers
注意配置清单中,不确定的节点要放在清单配置文件的最上边,若放到最下边,它会认为是最后一个组中的主机
继以上原因,ansible2.8之后定义主机组
ansible.cfg配置文件中写了主机清单配置的类型,yaml形式的配置清单默认是开启的# enable_plugins = host_list, virtualbox, yaml, constructed
而host_list格式的文件,可以用ansible-inventory -y -i ./inventory_old --list --output ./inventory
转换为yaml格式的主机配置清单
ungrouped: # 这样定义的就是未定义的主机组
hosts:
serverx1:
serverx2:
serverx3:
webservers:
hosts:
servera:
serverb:
dbservers:
hosts:
serverd:
servere:
webdbservers:
children:
dbservers:
webservers:
ansible -i inventory all --list-hosts
列出所有的主机ansible -i inventory webservers --list-hosts
列出webservers组所有的主机
主机清单定义的示例
all:
children:
webservers:
vars:
ansible_ssh_user: root
ansible_ssh_pass: redhatweb
hosts:
servera:
ansible_host: 192.168.0.41
serverb:
ansible_host: 192.168.0.241
dbservers:
vars:
ansible_ssh_user: root
ansible_ssh_pass: redhatdb
hosts:
serverc:
serverd:
分发密钥
- ansible需要连接上主机
- 主机清单
- ansible需要通过认证
- ansible_ssh_user # 变量
- ansible_ssh_pass # 变量
在所有被端创建一个可以被ansible远程连接的用户
ansible all -m group 'name=devops gid=10000'
ansible all -m user 'name=devops shell=/bin/bash uid=10000 group=devops'
ansible all -m authorized_key -a 'user=devops state=persent' key="ssha-rsa AAXX#注:这里是要分发公钥的内容"'
也可以用以下两步来取代以上这一步
# ansible all -m file -a 'path=/home/devops/.ssh state=directory owner=devops group=devops mode=0700' # ansible all -m copy -a '/root/.ssh/id.rsa.pub dest=/home/devops/.ssh/authorized_keys owner=devops group=devops mode=0600'
# vim /etc/sudoers # 这个在workstation节点上执行,然后把配置同步到其它节点 devops ALL=(ALL) NOPASSWD:ALL
ansible all -m copy -a "src=/etc/sudoers dest=/etc/sudoers owner=root group=root mode=0440"
- ansible需要连接上主机
ansible.cfg中的host_key_checking = False
首次登录时不检测密钥,即ssh登录时自动回答yes —> ssh的known_hosts
ansible配置文件
优先级最高1:ANSIBLE_CONFIG=/tmp/ansible.cfg
优先级2:(常用)执行ansible指令所在的当前目录ansible.cfg
优先级3:~/.ansible.cfg
当前用户家目录下的一隐藏文件,如果当前目录下不存在ansible.cfg
配置文件,则会查找用户目录下的该隐藏文件
优先级4:/etc/ansible/ansible.cfg
默认配置文件,如果上面两个路径下的ansible.cfg
都不存在,则使用该文件
补充 grep -Ev "\s*#|^$" ansible.cfg
查看所有生效的配置
- 配置项
- inventory: 用于定义默认主机清单的路径,默认值为/etc/ansible/hosts
- remote_tmp: 被控端临时脚本所存放的路径
- local_tmp: 主控端临时脚本所存放的路径
- remote_port: ansible主控端连接被控端的ssh的默认的端口号,默认是22
- remote_user: ansible主控端连接被控端所使用的用户
private_key_file = /root/.ssh/id_rsa
主控端私钥在本地存放的位置- host_key_checking: 定义是否关闭远程hosts_key的检查
- 配置remote_user的提权
- privilege_escalation
become=True
开启提权become_method=sudo
提权的方式使用sudobecome_user=root
提权到root用户become_ask_pass=False
提权到指定用户是否需要密码
- privilege_escalation
ansible ad-hoc,模块
在ansible 当中,向主控端下发控制指令的两种方式:
- ansible ad-hoc
- ansible playbook
基础常用模块
ping
- 用于检测ansible的主控端是否能够正常的控制被控端,如果能够连接,但是连接所使用的用户权限不足时,ping检测不出来
copy
- 用于复制本地的文件或者目录到补控端的指定目录
- src: 原文件或者目录的路径
- dest: 复制到被控端后的路径
- owner: 文件被复制到被控端后,在被控端上的属主
- group: 属组
- mode: 在被控端上的权限
- content: 此参数与src参数不能共存,其用于指定被控端上的文件的内容
- file
- path: 远程主机上的文件或目录的路径
- state: 定义被管理的文件的状态
- absent: 删除
- directory: 创建为目录
- file: 如果文件存在,返回文件的当前状态,如果文件不存在,则报错
- hard: 创建为硬链接
- link: 创建为软链接
- touch: 如果存在则更新时间
- group
- owner
- mode
- owner:
- group:
- mode: 644
- src: 如果文件的状态为link或者hard时,需要指定原文件的路径
- shell
- 用于在远程主机上执行shell指令
- 与shell功能类似的模块
- command: 功能比shell稍弱,不支持管道,重定向、&& 这种特殊的操作符
- raw
- script: 将主控端上的脚本自动复制到远程主机上执行
- cmd: 要执行的shell指令
- chdir: 执行指令之前要切换到的目录
- creates: 用于判断当前指令是否应该执行
- removes: 与creates功能一样,用法相反
# asnible all -m shell -a 'chdir=/tmp ls # 执行结果会返回到终端上面 # ansible all -m shell -a 'chdir=/tmp cmd=ls' # 执行结果不返回到终端上面 # ansible servera -m shell -a "creates=/tmp/555.txt touch /tmp/666.txt" #存在不执行touch # ansible serverb -m shell -a "removes=/tmp/555.txt touch /tmp/666.txt" #存在执行touch
- shell 有一个缺陷,就是不能保证幂等的原则
- authorized_key
- 将主控端的公钥复制到远程主机指定用户.ssh目录下的authorized_keys文件当中
- 常用参数
- user 要把公钥放到哪个主机的哪个用户下
- key 被推送的公钥的内容
- path 被推送的这个公钥放到远程主机的哪个路径下,默认是.ssh/authorized_keys中
- state 是推送公钥,还是从远程主机上删除指定的公钥
- present
- absent
copy模块的作用: 从主控端复制文件和目录到被控端# asnbile all -m copy -a "src=/etc/fstab dest=/tmp/fstab"
模块帮助
ansible-doc -l
可以列出所有支持模块ansible-doc <nodule_name>
可以打印当前模块的详细用法-s <module_name>
打印当前模块的所有参以及参数的说明
yum
- 用于安装软件包
- 常用参数:
- name
- state
- present
- absent
- latest
# ansible workstation -m yum -a 'name=httpd state=present'
- 常用参数:
fetch
- 将远程主机上的指定文件复制到控制节点
- 参数
- src: 指的是远程主机上的文件和路径
- dest: 指的是主控端上的文件和路径
- owner
- group
- mode
# ansible workstation -m fetch -a 'src=/etc/httpd/conf/httpd.conf dest=/tmp' # 将远程主机上的httpd.conf 拷到控制节点的/tmp目录下面
service
- 管理远程主机上的服务的启停
参数
功能与service 一样,只不过它无法管理红帽6版本中的服务
- 参数
- 定时任务计划
- backup: 对远程主机上的原任务计划内容修改之前做备份
- cron_file: 如果指定该参数,则用该文件替换远程主机上的cron.d目录下的用户的任务计划
- day: 日(1-31,,/2,…)
- hour: 小时(0-23,,/2,…)
- minute: 分钟(0-59, ,/2,…)
- month: 月(1-12,,/2,…)
- weekday: 周(0-7,*,…)
- job: 要执行的任务,依赖于state=persent
- name: 该任务的描述
- special_time: 指定什么时候执行,参数
- reboot,yearly,annually,monthly,weekly,daily,hourly
- state: 确认任该计划是创建还是删除
- user: 以哪个用户的身份执行
# ansible workstation -m cron -a 'name="test cron module of ansible" job="echo hello" minute="30" hour="00" user=root' # 创建
# ansible workstation -m cron -a 'name="test cron moudle of ansible" job="echo hello" minute="30" hour="1" weekday="*/2"' # 基于上条做定时任务的修改
group
- 创建用户组
- 创建用户
- 参数
- name: 指定创建用户的名称
- home: 指定用户的家目录,需要与createhome配合使用
- groups: 指定用户的附加组
- group: 指定用户的属组
- uid: 指定用户的uid
- password: 指定用户的密码
- createhome: 是否创建家目录 yes| no
- system: 是否为系统用户
- comment: 定义用户描述信息
- remove: 当state=absent时, remove=yes 同表示边同家目录一起删除,等价与userdel -r
- state: 是创建还是删除
- shell: 指定用户的shell环境
需要说明的是,在指定password参数时,不能使用明文密码,因为后面这一串密码会被直接传送到被管理主机的/etc/shadow文件中,所以需要先将密码字符串进行加密处理。然后将得到的字符串放到password中即可# ansible workstation -m user -a "name=user1 group=user1 uid=10002 createhome=yes shell=/bin/bash password={{'redat'| password_hash('sha256')}}"
get_url
- 从互联网上下载文件到目标服务器
- 解压压缩文件,将指定sre当中的文件直接解压至远程主机
- 解压压缩文件
- 用于获取变量
这个打印的是# ansible all -m setup
ansible_facts
包含的所有的变量信息,打印的信息是一个打的字典
练习
要求
- 在webservers组的所有主机上安装nginx
- 修改nginx的配置文件,使用之监听在8081端口,并且nginx的用户使用www用户
- 修改nginx的首页文件,内容为”hello ansible”
- 启动nginx,并设置为开机自启
当nginx的配置文件变更时,自动更新reload nginx
# 1. 安装 # ansible webservers -m yum -a "name=nginx state=present" # 创建用户 # ansible webservers -m group -a 'name=www gid=10003 system=yes' # ansible webservers -m user -a 'name=www group=www uid=10003 system=yes createhome=no' # 2. 修改配置文件 # ansible servera -m fetch -a "src=/etc/nginx/nginx.conf dest=/tmp/" # 3. 修改配置文件 # vim files/nginx.conf user www www; Listen 8081 # 4. 修改首页文件 # ansible webservers -m copy -a "src=files/nginx.conf dest=/etc/nginx/nginx.conf mode=0644" # ansible webservers -m copy -a 'content="hello ansible" dest=/usr/share/nginx/html/index.html' # 5. 启动nginx并设置为开机自启 # ansible webservers -m systemd -a "name=nginx state=started daemon_reload=yes enabled=yes"
在447的调优当中
- 在大规模文件复制场景当中,不要使用copy,而使用synchronize
- 在文件修改的场景,尽可能使用template,而不要使用lineinfile
- 在软件包安装的场景,尽可能在yum的name当中指定多个软件包,而不要使用循环
上题使用yaml 来实现
```yaml
- hosts: webservers
tasks:- name: install nginx
yum:
name: nginx
state: present - name: create group for nginx
group:
name: www
state: present
gid: 10003
system: yes - name: create user for nginx
user:
name: www
state: present
system: yes
shell: /sbin/nologin
createhome: no
group: www
gid: 10003 - name: copy nginx.conf to dest
copy:
src: file/nginx.conf
dest: /etc/nginx/nginx.conf
mode: 0644
notify: restart nginx - name: generate index.html on dest
copy:
content: “hello ansible”
dest: /usr/share/nginx/html/index.html
notiofy: restart nginx - name start nginx
systemd:
name: nginx
state: started
daemon_reload: yes
enabled: yes
handlers: - name: restart nginx
systemd:
name: nginx
state: restarted
daemon_reload: yes
```
- name: install nginx
- 在上面的示例中,一个playbook被分为了三个部分
- target: 选取的目标
- vars: 定义亦量
- tasks: 执行的任务
- handler: 触发器
yaml 语法
- yaml语法是通过严格的缩进控制层级的,上层对下层是包含关系;缩进只能使用空格,不可以使用tab,一般是两个空格
- yaml语法是对编程友好的,在这里我们就直接以程序语法结构来对照;
执行剧本时,自上而下执行,顺序是不可以颠倒的;
- hosts: webservers
- hosts: dbservers
- hosts: all
- hosts: webservers,dbservers
- hosts: 's*'
- hosts: '*.example.com,!*.lab.example.com'
tasks
- tasks中定义具体要执行的任务,可以有多个,默认情况下,自上而依次执行;如果其间一个任务执行失败,则后惯任不再执行,整个play会自动退出
- name: install nginx
yum:
name: nginx
state: present
- name: install package
yum:
name:
- httpd
- vim
- socat
- "@Developmen Tools"
state: preset
- name: copy nginx.conf to remote hosts
copy:
src: file/nginx.conf
dest: /etc/nginx/nginx.conf
mode: 0644
owner: root
group: root
handlers
- 要想使用handlers,需要在需要使用handlers的task上配置notify;当这个task被执行并且返回的状态为change时,handler即会被调用;
- handler会在所有的task执行完毕后,才会执行;
- 如果有多个任务调用了相同的handler,而这个handler只会在最后执行一次
示例
tasks: - name: template configuration file template: src: template.j2 dest: /etc/foo.conf notify: - restart memecached - restart apache - name: start memcached service: name: memcached state: started - name: start apache service: name: httpd state: started handlers: - name: restart memcached service: name: memcached state: restarted - name: restart apache service: name: httpd state: restarted - name: check server check: ## 通知器检查 name: httpd state: restarted
listen 示例
当tasks任务重多,而调用handlers,这样会频繁的在剧本中写,在2.8之后提供了listen
以下示例中同时定义了多个listen 以供调用
- hosts: webservers
tasks:
- name: install memcache and apache
yum:
name:
- httpd
- memcached
state: present
- name: copy configuration file
copy:
src: files/nginx.conf
dest: /etc/foo.conf
notify: restart service
- name: test
copy:
src: files/nginx.conf
dest: /tmp/xxx.conf
notify: test
- name: restart memcached
service
name: memcached
state: started
- name: start apache
service:
name: httpd
state: started
handlers:
- name: restart memcached
service:
name: memcached
state: restarted
- name: restart apache
service:
name: httpd
state: restarted
listen:
- restart service
- test
变量
- 变量定义的几个位置
- 在ansible-playbook的命令执行当中通过-e参数传递
- inventory 当中
- playbook 当中的vars当中
- playbook 当中的vars_files当中
- 可以定义在任务当中
- ansible-playbook的命令行传参
- 在
yaml
执行的当前目录host_vars
当中为主机定义的变量,host_vars
的子目录为inventory_hostname
即主机名,再下级目录定义变量文件 - 在
yaml
执行的当前目录group_vars
当中为主机组定义的变量 ,group_vars
的子目录为组名,再下级目录定义亦是文件 - 通过host_vars 和group_vars目录来定义
- 通过register关键字实现变量的注册
- 通过ansible的facts来定义以及获取变量
- 在ansible的roles当中的defaults或者vars目录当中定义
在ansible当中,调用变量,使用"{{ varname }}"
内置变量
内置变量一般定义的inventory中,或者playbook的vars和vars_files中
- 一般连接
ansible_host
# 用于指定被管理的主机的真实IPansible_port
# 用于指定连接到管理主机的ssh端口号,默认为22ansible_user
# 用于指定连接时默认使用的用户名 - 特定ssh连接
ansible_connection
# SSH连接的类:local,ssh,paramiko
,在ansible 1.2之前默认是paramiko,后来智能选择,优先使用基于ControlPersist的 ssh(如果支持的话)ansible_ssh_pass
# ssh 连接时的密码asnible_ssh_private_key_file
# 密钥文件路径,如果不想使用ssh-agent管理密钥时可以使用此选项 - 特权升级
ansible_become
# 相当于ansible_sudo或者ansible_su,允许强制特权升级ansible_become_user
# 通过特权升级到的用户,相当于asnible_sudo_user
或者ansible_su_user
ansible_become_pass
# 提供特权时,如需要密码的话,可以通过该变量指定,相当于ansible_sudo_pass
或者ansible_su_pass
远程主机环境参数
ansible_shell_executable
# 设置目标主机上使用的shell,默认为/bin/sh
ansible_python_interpreter
# 用来指定python解释器的路径,默认为/usr/bin/python
同样可以指定ruby、perl的路径ansible_*_interpreter
# 其他解释器路径,用法与ansible_python_interpreter
类似,这里”*”可以是ruby或者perl等其他语言inventory
中定义的主机清单的中的主机名也称之为inventory_hostname
示例
- hosts: all tasks: - name: set hostname for remote_hsots hostname: name: "{{ inventory_hostname }}"
这样就可以依据inventory中的主机名变量来批量配置主机名
在默认情况下,ansible配置文件当中的所有配置项,在其前面加上
ansible_
则可将其申明为ansible的内置变量进行调用
在ansible.cfg
中没有配置提权的情况下
- hosts: serverb
tasks:
- name: task1
debug: "task1"
- name: task2
debug: "task2"
- name: task3
vars:
ansible_become: True
ansible_become_user: root
ansible_become_method: sudo
ansible_become_ask_pass: False
yum:
name: redis
state: persent
自定义变量
注意
- 在playbook当中定义的变量,只对当前的playbook有效,其的playbook无法使用此变量;
- 在task当中定义的变量,只对当前task有效,其他的task无法使用此变量;
- 在inventory当中为主机组定义的变量,只对当前主机组有效;
- 在host_vars目录当中定义的变量,只对指定的主机有效;
- 在group_vars当中定义的变量,只对指定的主机有效;
# mkdir host_vars/server{a,b,d,e}
# vim host_vars/servera/comman.yaml
hobby: aa
# vim host_vars/serverb/comman.yaml
hobb: bb
# vim host_vars/serverc/comman.yaml
hobb: cc
# vim host_vars/serverd/comman.yaml
hobb: dd
# mkdir group_vars/webservers
# vim group_vars/webservers/comman.yaml
aa: webserversaa
# vim group_vars/dbservers/comman.yaml
aa: dbserversaa
# mkdir group_vars/dbservers
- hosts: all
tasks:
- name: print hobby for remote hosts
debug:
msg: "{{ inventory_hostname }}'s hobby is {{ hobby }}"
- name: print aa for remote host groups
debug:
msg: "{{ inventory_hostname }}'s aa is {{ aa }}"
注册变量
- 将一个任执行之后的输出结果,注册为变量,以便在后续的任务当中使用
- hosts: webservers
tasks:
- name: print whoami
shell:
cmd: whoami
register: user
- name: print user
debug:
var: user
执行时命令传递变量ansible-playbook 9-vars.yaml -e '{"user":"natasha","xx":"yy"}'
ansible中的变量类型
- 在ansible当中,常用的变量类型:
- string: 字符串
- number: 包含整型和浮点型
- list: 列表类型
- dict: 字典类型
- null: 未被定义的变量
447考试的核心是过滤器与变量占比70%
ansbile_facts
- hosts: workstation
gether_facts: # 收变量,默认不写也会进行收集
# gether_facacts: no # 不收集变量
tasks:
- name: fetch ipv4 address
debug:
msg: "{{ ansible_all_ipv4_address }}" # ansible_facts是可以直接省略的
变量定义实例
users:
- name: natasha
id: 666
shell: /bin/bash
system: no
createhome: yes
home: /home/natasha
hobby:
- Music
- Tea
- name: bob
id: 6666
shell: /sbin/nologin
system: yes
createhome: no
hobby:
- Film
- Basketball
- name: tom
id : 5555
shell: /bin/bash
system: no
createhome: yes
home: /home/tom
hobby:
- Music
- Film
users1:
natasha:
id: 666
shell: /bin/bash
system: no
createhome: yes
home: /home/natasha
hobby:
- Music
- Tea
bob:
id: 6666
shell: /sbin/nologin
system: yes
createhome: no
home: /home/bob
hobby:
- Film
- Basketball
tom:
id: 5555
shell: /bin/bash
system: no
createhome: yes
home: /home/tom
hobby:
- Music
- Film
引用
- hosts: workstation
vars_files:
- users.yaml
tasks
- name: get tom's home
debug:
msg: "users_tome: {{ users[2]['home'] }} users1_tom: {{ users1['tom']['home'] }}"
- name: get bob's hobby
debug:
msg: "users_bob_hobby: {{ users[1]['hobby'][1]}} users1_bob_hobby: {{ users1['bob']['hobby']}}"
- hosts: workstation
gether_facts: yes
tasks:
- debug:
msg: "{{ ansible_interfaces }}"
- name: fetch ipv4 address
debug:
msg: "{{ ansible_eth0['ipv4']['address'] }}"
- name: fetch ansible_distribution
debug:
msg: "{{ ansible_distribution }} -- {{ ansible_distribution_major_version }}"
- 自定义fact
# vim files/custom-fact.ini
[general]
package = httpd
service = httpd
state = started
- hosts: all
tasks:
- name: ensure directory /etc/ansible/facts.d is exists
file:
path: /etc/ansible/facts.d
state: directory
- name: copy custom-fact.ini to dest
copy:
src: files/custom-fact.ini
dest: /etc/ansible/facts.d/custom.fact
"ansible_local":{
"custom":{
"general":{
"package": "httpd"
"service": "httpd"
"state": "started"
}
}
}
- hosts: workstation
gether_facts: yes
tasks:
- name: fetch custom fact
debug:
msg: "{{ ansible_local['custom']['general']['state'] }}"
- set_fact 用于设置变量
- hosts: workstation
gather_facts: yes
tasks:
- name: set fact example
set_fact:
AA: bb
- name: print AA
debug:
msg: "{{ AA }}"
- fact 缓存
将fact 缓存写在数据库当中
[defaults]
gathering = smart
fact_caching_timeout = 86400
fact_caching = redis
fact_caching_connection = 127.0.0.1:6379:0
缓存到文件中去
gethering = smart
# 缓存时间,单位为秒
fact_caching_timeout = 86400
fact_caching = jsonfile
# 指定ansible包含fact的json文件位置,如果目录不存在,会自动创建
fact_cachinga_connection = /tmp/ansible_fact_cache
魔法变量
- hosts: webservers
tasks:
- name: set hostname for remote hosts
hostname:
name: "{{ inventory_hostname }}"
hostvars
作用: 通过hostvars可以在当前执行任务的主机上获取其他主机的fact
inventory
loadbalancers:
hosts:
workstation:
webservers:
hosts:
servera:
serverb:
要求: 在loadbalancers的组中主机部署nginx反向代理,将自身的80端口请求代理至webservers组的所有主机的80端口
要实现以上需求:
- loadbalancers 组的主机在执行部署的时候,必须要拿到webservers组里的所有的主机列表
grups 魔法变量 - loadbalancers 组的主机在执行部署的时候,还要要拿到webservers组里的所有的主机的ip地址
hostvars 魔法变量
groups
作用: groups自动获取inventory当中定义的所有组
# 可以这样两种方式来写 groups.webservers --> groups['webservers']
groups:
webservers:
- servera
- serverb
lbservers:
- workstation
nginx 模板配置
upstream web {
{% for host in gorups['webservers'] %}
server {{ hostvars[host]['ansible_eth0']['ipv4']['address']}};
{% endfor %}
}
server {
listen 80 default_server;
server_name _;
location / {
proxy_pass http://web;
}
}
- hosts: webservers
tasks:
- name: install httpd
yum:
name: httpd
state: present
- name: copy index.html to dest
copy:
content: "This is a test for {{ ansible_eth0.ipv4.address }}"
dest: /var/www/html/index.html
notify: restart httpd
- name: start httpd
systemd:
name: httpd
state: started
handlers:
- name: restart httpd
systemd:
name: httpd
state: restarted
daemon_reload: yes
- hosts: lbservers
tasks:
- name: install nginx
yum:
name: nginx
state: present
- name: copy config to dest
template: files/nginx.conf.j2
dest: /etc/nginx/nginx.conf
- name: start nginx
systemd:
name: nginx
state: started
notify: restart nginx
handlers:
- name: restart nginx
systemd: nginx
state: restarted
daemon_reload: yes
paly_hosts
- 当前playbook会在哪些hosts上运行
inventory_dir
- 主机清单所在目录
iventory_file
- 主机清单文件
group_names
- 对于当前执行节点,属于哪个组
- 例: servera 属于 lbservers webservers 这两个组,servrerb属于 webservers这一个组
变量优先级
- extra vars(命令中-e)最优先
- inventory 主机清单中连接变量(ansible_ssh_user等)
- play 中vars、vars_file等
- host_vars 和group_vars定义的变量
- 剩余的在inventory中定义的变量
- 系统的facts变量
- 角色定义的默认变量(roles/rolesname/defaults/main.yaml)
lookup 与query
lookup: 负责调用插件从特定的存储当中读取数据充作变量
query: 和lookup 功能是一样的,但是它返回的是一个列表
- file
- 将一个文件当中的的内容读出来存储为一个变量
- 文件总是来自主控端
{{ lookup('file', '/root/.ssh/id_rsa.pub ')}}
- hosts: workstation
tasks:
- name: lookup file
set_fact:
public_key: "{{ lookup('file', '/root/.ssh/id_rsa.pub')}}"
- name: print public_key
debug:
msg: "public_key's value is : {{ public_key }}"
可以通过如下命令查询ansible的 lookup支持所有的插件
ansbile-doc -t lookup
- pipe
pipe: 将一个命令的执行结果保存为一个变量,它和register不一样的是,不需要一个专门的任务来执行这个命令lookup的变量是可以直接定义到template当中去的,而register注册的变量不可以
- hosts: workstation
tasks:
- name: Flamingo | Get release version
set_fact:
flamingo_release_version: "{{ lookup('pipe','date +%Y%m%d%H%M%Z') }}"
- name: print flamingo release version
debug:
msg: "{{ flamingo_release_version }}"
- name: hostname
set_fact:
hostname: "{{ query('pipe','hostname')}}"
- name: print hostname
debug:
msg: "{{ hostname }}"
- url 获取页面内容提交给主控端
- hosts: webservers
tasks:
- name: url
set_fact:
text: "{{ query('url','http://139.9.234.235')}}"
- name:
debug:
msg: "{{ text }}"
- redis_kv
redis_kv lookup可以直接从redis存储中来获取一个key的value,key必须是一个字符串,如同Redis GET指令一样,需要注意的是,要使用redis_kv lookup,需要在主控端安装python 的redis客户端,在CentOS上,软件包为python-redis。
下面是一个在playbook中调用redis lookup的task, 从本地的redis中取一个key 为weather的值
- name: lookup value in redis
debug: msg="{{ lookup('redis_kv','redis://localhost:6379,weather' )}}"
- etcd
etcd 是一个分布式的key-value存储,通常被用于保存配置信息或者用于实现服务发现,可以使用etc lookup来从etcd中获取指定key的valuecurl -L http://127.0.0.1:2379/v2/keys/weather -XPUT -d value=sunny
- 定义一个调用etcd插件的task
- name: look up value in etcd
debug: msg="{{ lookup('etcd','http://127.0.0.1:2379',weather)}}"
默认情况下,etcd lookup会在http://127.0.0.1:2379
上查找etcd服务器。但我们在执行playbook之前可以通过设置ANSIBLE_ETCD_URL环境变量来修改这个设置
- password
password lookup会随机生成一个密码,并将这个密码写入到参数指定的文件中。如下示例。创建一个名为bob的mysql用户,并随机生成该用户的密码,将密码写入到主控端的bob-password.txt中
- name: create deploy mysql user
mysql_user:
name=bob
password={{ lookup('password','bob-password.txt')}}
priv=*.*:ALL
state=present
- dnstxt
dnstxt lookup用于获取指定域名的TXT记录。需要在主控端安装python-dns
使用方法如下
- name: lookup TXT record
debug: msg="{{ lookup('dnstxt','aliyun.com')}}"
如果某一个主机有多个相关的TXT记录,那么模块会把他们连在一起,并且每次调用时连接顺序可能不同
template jinjia2模板
set_fact: redismem="{{ (ansible_memtotal_mb /2) | int}}M"
jinjia2 条件语句
- 判断
{% if ansible_bond0 is defined %}
bind {{ ansible_bond0.ipv4.address }} 127.0.0.1
{% elif ansible_eth0 is defined %}
bind {{ ansible_eth0.ipv4.address }} 127.0.0.1
{% else %}
bind 0.0.0.0
{% endif %}
## 主从配置
{% if 'redis-slave' in group_name %}
slaveof {{ hostvars[groups['redis-master'][0]]['ansible_eth0']['ipv4']['address'] }} {{ port | default(6379) }}
{% if requirepass is defined %}
masterauth {{ requirepass }}
{% endif %}
{% endif %}
## 以上为嵌套if
{% if requirepass is defined %}
requirepass {{ requirepass }}
{% endif %}
redis:
children:
redis-master:
hosts:
servera:
ansible_ssh_host: 192.168.0.41
redis-slave:
hosts:
serverb:
ansible_ssh_host: 192.168.0.241
- for 循环
for循环只能用于循环列表,不可以循环字典,如果是字典,必须将其转换为列表才能循环
upstream web{
{% for host in groups['webservers'] %}
{% if hostvars[host]['ansible_bond0']['ipv4']['address'] is defined %}
server {{ hostvars[host]['ansible_bond0']['ipv4']['address']}};
{% elif hostvars[host]['ansible_eth0']['ipv4']['address'] is defined %}
server {{ hostvars[host]['ansible_eth0']['ipv4']['address']}};
{% endif %}
}
- 在template 文件中定义变量
{% if 'authorztivenames' in group_names %}
{% set zone_type = 'master' %}
{% set zone_dir = 'data' %}
{% else %}
{% set zone_type = 'slave' %}
{% set zone_dir = 'slaves' %}
{% endif %}
zone "internal.example.com" IN {
type {{ zone_type }};
file "{{ zone_dir }}/internal.example.com";
{% if 'authorativename' not in group_names %}
master {192.168.2.2;};
{% endif %}
};
过滤器
- default
作用于对变量的进一步的处理,当变量不存在时,用于设定默认值
"Host:" "{{ db_host | default('localhost')}}"
过滤器
dict2items
items2dict
示例,基于以下变量,实现一个playbook 创建以上用户
users:
natasha:
shell:/bin/bash
createhome: yes
id: 666
home: /tmp/natasha
bob:
shell: /bin/bash
id: 66666
createhome: no
tom:
shell: /bin/bash
createhome: yes
id: 11111
参考
- name: create users
group:
name: {{ item.key }}
gid: {{ item.value.id }}
loop: {{ users | dict2items }}
mode: {{ item.mode | default(omit) }}
##这句的话意思是item.mode
不存在,则不进配置
- mandatory
判断一个变量是否被定义,如果未定义,则主动失败,主动报错为变量没有定义
- hosts: localhost
gether_facts: no
tasks:
- name: print my_value
debug:
msg: "{{ my_value | mandatory}}"
- upper
用于将所有字符串转换成为大写{{ teststr | upper }}
- lower
将所有的字符串转换成为小写{{ teststr | lower}}
- capitalize
将所有的字符串首字母大写,其他字母小写{{ teststr | capitalize }}
- reverse
将字符串倒序排列{{ teststr | reverse }}
- first
返回字符串的第一个字符{{ teststr | first }}
- last
返回字符串的最后一个字符{{ teststr | last }}
- trim
将字符串开头和结尾的空格去掉{{ teststr | trim }}
- center
将字符串放在中间按30个字符的长度,将字符串居中,两边以空格填弃
{{ teststr2 | center(30)}}
length
返回字符串的长度,与count 等价{{ teststr2 | length }}
list
返回字符串换为列表{{ teststr3 | list }}
- shuffle
list将字符串转换为列表,但是顺序排列,shuffle会将字符串转换为列表,但是会随机打乱字符串顺序{{ teststr3 | shuffle }}
- split
将字符串分割为列表{{ teststr4.split() }}
- json
将列表按指定的值连接为字符串{{ teststr4.split() | json('.') }}
hash
将给定的字符串做hash算法password_hash(‘sha256’)
一般用于新建系统用户{{ 'redat' | password_hash('sha256') }}
replace
修改字符串regex_replace
regex_search
正则匹配与搜索,搜索到第一个退出
示例
- name: regex_search
debug:
msg: "{{ 'marvin, arthur, arthur'|regex_search('ar\\S*r') }}"
- name: regex_replace
debug:
msg: "{{ 'marvin, arthur, arthur' | regex_repliace('ar(\\S*r)', '\\1mb') }}"
- b64encode
base64加密与echo redhat | base64
一样 - b64decode
与base64 -d
一样
数字相关的过滤器
- 两个number相加
- 两个number相减
/ 浮点数除法运算
// 整数的除法运算
% 整数的除法运算
- 两个numbers相乘
** 乘方
示例
{{ (ansible_facts['ansible_date_time']['hour'] | int) + 1 }}
操作数字相关的过滤器
- int
- 将对应的值转换为整数
"{{ 8+('8'| int)}}"
默认情况下,如果无法完成数字转换则返回0;这里指定如果无法完成数字转换则返回6
- 将对应的值转换为整数
"{{ 'a' | int(default=6) }}"
- float
- 将对应的值转换为浮点数
"{{ '8' | float }}"
"{{ 'a' | float(8.88)}}"
- 将对应的值转换为浮点数
abs
- 获取绝对值
"{{ '-8'| abs }}"
- 获取绝对值
round
- 小数点四舍五入
"{{ 3.1415926 | round(5) }}"
- random
- 从一个给定的范围中获取随机值
"{{ 100 | random }}" ## 从0到100中随机获取一个随机数
"{{ 10 | random(start=5)}}" ## 从5到10中随机返回一个数字
- 从一个给定的范围中获取随机值
- 生产中常用的过滤器的场景
- 提取特定的字符串,将其转换为列表
- 提取字典当中的特定的值,其其转换为列表
80%
的操作都是为循环
列表操作相关的过滤器
- length: 返回列表长度
- first: 返回列表的第一个值
- last: 返回列表的最后一个值
- main: 返回列表中最小的值
- max: 返回列表中最大的值
- sort: 重新排列列表,默认为升序排列, sort(reverse=true)为降序
- sum: 返回纯属数字嵌套列表中所有数字的和
- flatten: 如果列表中包含列表,则flatten可拉平嵌套 的列表,levels参数可用于指定被拉平的层级
- join: 将列表中的元素合并为一个字符串
- random: 从列表中随机返回一个元素
- shuffle: 将列表中的元素随机重排
- upper: 将列表中的字符串元素转换为大写
- lower: 将列表中的字符串元素转换为小写
- union: 将两个列表合并,如果元素有重复,则只留下一个
- intersect: 获取两个列表的交集
- difference: 获取存在于第一个列表中,但不存在于第二个列表中的元素
- symmetric_difference: 取出两个列表中各自独立元素,如果重复只留一个
应用于字典变量的过滤器
- combine: 将两个字典合成一个
- dict2items: 将字典转换成列表
- items2dict: 将列表转换为字典
- josn_query: 将变量转抱成json,并查询子元素
to_json/to_yaml/to_nice_json/to_nice_yaml
- from_json
ipaddr 过滤器
- 想要使用ipaddr过滤器,需要在主控端安装
python-netaddr
包
yum install -y python-netaddr
- host: localhost
vars:
my_ipaddr: ["192.168.0.1/24"]
tasks:
- name: ipaddr
debug:
msg: "{{ my_ip_addr | ipaddr }}"
- name: ipaddr(network)
debug:
msg: "{{ my_ip_addr | ipaddr('network') }}"
- name: ipaddr(host)
debug:
msg: "{{ my_ip_addr | ipaddr('host') }}"
- name: ipaddr(netmask)
debug:
msg: "{{ my_ip_addr | ipaddr('netmask') }}"
- name: ipaddr(size)
debug:
msg: "{{ my_ip_addr | ipaddr('size') }}"
- name: ipaddr(subnet)
debug:
msg: "{{ my_ip_addr | ipaddr('subnet') }}"
- name: ipaddr(ipv4)
debug:
msg: "{{ my_ip_addr | ipaddr('ipv4') }}"
- name: ipaddr(broadcast)
debug:
msg: "{{ my_ip_addr | ipaddr('broadcast') }}"
url分割过滤器
- hosts: localhost
gather_facts: no
vars:
url: 'http://user:password@www.example.com:8080/xxx/index.html?query=132'
tasks:
- name: "get url hostname"
debug:
msg: "{{ url | urlsplit('hostname') }}" # ==> "msg": "www.example.com"
- name: "get url netloc"
debug:
msg: "{{ url | urlsplit('netloc') }}" # ==> "user:password@www.example.com:8080"
- name: "get url path"
debug:
msg: "{{ url | urlsplit('path') }}" # "msg": "/xxx/index.html"
- name: "get url port"
debug:
msg: "{{ url | urlsplit('port') }}" # ==> "msg": "8080"
- name: "get url scheme"
debug:
msg: "{{ url | urlsplit('scheme') }}" # ==> "msg": "http"
- name: "get url query"
debug:
msg: "{{ url | urlsplit('query' )}}" # ==> "msg": "query=132"
应用于文件路径的过滤器
- basename
- 返回文件路径中的文件名部分
- dirname
- 返回文件路径中的目录部分
- expanduer
- 将文件路径中的
~
替换为用户目录
- 将文件路径中的
- realpath
- 处理符号链接后的文件实际路径
- name: test hostname
hosts: test
vars:
homepage: /usr/share/nginx/html/index.html
tasks:
- name: copy homepage
copy:
src: files/index.html
dest: {{ homepage }}
可以通过basename改写成如下方式
- name: test basename
hosts: test
vars:
homepage: /usr/share/nginx/html/index.html
tasks:
- name: copy homepage
copy:
src: files/{{ homepage | basename }}
dest: {{ homepage }}
loop循环
在编写playbook的时候,不可避免的要执行一些重复性操作,比如安装软件包,批量创建用户,操作某个目录 下的所有文件等,
ansible作为一门简单的自动化语言,流程控制,循环语句这些编程语言的基本元素它同样都具备
在ansible2.5以前,playbook 通过不同的循环语句来实现循环,这些语句使用with_作用,在2.5以后,推荐使用loop关键字来实现循环
示例
tasks:
- name: postfix and httpd are running
service:
name: "{{ item }}"
state: present
loop:
- postfix
- httpd
users:
natasha:
shell:/bin/bash
createhome: yes
id: 666
home: /tmp/natasha
bob:
shell: /bin/bash
id: 66666
createhome: no
tom:
shell: /bin/bash
createhome: yes
id: 11111
- hosts: workstation
vars_files:
- vars/users.yaml
tasks:
- name: create group
group:
name: "{{ item['key'] }}"
gid "{{ item['value']['id'] }}"
loop: "{{ users | dict2items }}"
- name: create user
user:
name: "{{ item.key }}"
group: "{{ item.key }}"
uid: "{{ item['value']['id'] }}"
createhome: "{{ item['value']['createhome'] }}"
home: "{{ item.value.home | default(omit) }}"
shell: "{{ item.value.shell }}"
loop: "{{ users | dict2items }}"
loop_control
loop_control用于循环时,获取列表的索引
- hosts: workstation
gether_facts: no
vars:
testlist:
- a
- [b,c,[e,f]]
- d
tasks:
- debug:
msg: "{{ index }}: {{ item }}"
loop: "{{ testlist | flatten(levels=1)}}"
loop_control:
index_var: index
zip_longest
- 使用zip_longest过滤器将两个列表中的元素对齐合并,与python语法中的zip函数类似
- hosts: localhost
gather_fact: no
vars:
testlist1: [a,b]
testlist2: [1,2,3]
testlist3: [A,B,C,D]
tasks:
- debug:
msg: "['{{ item.0}}','{{item.1}}','{{item.2}}']"
loop: "{{ testlist1 | zip_longest(list1,list2,list3)| list}}"
product
- 使用product过滤器实现嵌套循环
- hosts: localhost
gether_fact: no
vars:
testlist1:
- alice
- bob
testlist2:
- a
- b
- c
tasks:
- name: debug loops
debug: msg="name is {{ item[0] }} values is {{ item[1] }}" ## alice a alice b alice c bob a bob b bob c
loop: "{{ testlist1 | product(testlist2) | list }}"
看一个示例
mkdir /data/{a,b}/{1,2,3}
- hosts: localhost
gether_facts: false
vars:
dir1:
- a
- b
dir2:
- 1
- 2
- 3
tasks:
- file:
state: directory
path: "/data/{{item[0]}}/{{item[1]}}"
loop: "{{ dir1 | product(dir1) | list }}"
range
- 通过range过滤器实现对数字序列的循环
- hosts: test
gather_fact: no
tasks:
- debug:
msg: "{{ item }}"
loop: "{{ range(0,6,2) | list }}"
subelements
- 使用subelements 过滤器实现循环复杂变量的元素
需要说明的是,loop只能循环列表,所以这个元素需要是一个列表,或者可以被转换成为列表
- hosts: workstation
gether_facts: no
vars:
users:
- name: bobo
gender: mable
hobby:
- skateboard
- videogame
- name: alice
gender: female
hobby:
- music
- skateboard
tasks:
- debug:
msg: "{{ item.0.name }}'s hobby is {{ item.1 }}"
with_subelements
- "{{ users }}"
- hobby
- debug:
msg: "{{ item.0.name }}'s hobby is {{ item.1 }}"
loop: "{{ users | subelements('hobby')}}"
补充 运维组件的统计
https://landscape.cncf.io/?zoom=200
ansible 条件语句
基于上一个任务的执行结果,来判断一条任务要不要执行
在不同的操作系统上使用不同的安装工具来安装
- hosts: workstation
tasks:
- name: yum install httpd
yum:
name: httpd
state: present
when: ansible_describution == "CentOS"
- name: apt install httpd
yum:
name: apache
state: present
when: ansible_describution == "Ubuntu"
- 条件判断的关键词是when
在执行playbook 时,有时play的结果依赖于变量,fact 或者是前一个任务的执行结果,或者基于上一个task执行返回的结果而决定如何执行后续的task。此时就需要用到条件判断。
条件语句在Ansible中的使用场景
- 在目标主机上定了义一个硬限制,比如目标主机的最小内存必须达到多少,才能执行该task
- 捕获一个命令的输出。根据命令输出结果的不同以触发不同的task
- 根据不同目标主机的facts,以定义不同的task
- 根据目标机的cpu的大小,以调优相关应用情能
- 用于判断某个服务的配置文件是否发生变更,以确定是否需要重启服务
- name: install vim
host: all
tasks:
- name: Install VIM via in yum
yum:
name: vim-enhanced
state: installed
when: ansible_os_family == "RedHat"
- name: Install VIM via in opt
apt:
name: vim
state: installed
when: ansible_os_family == "Debian"
- name: Unexpected OS family
debug:
msg: "OS Family {{ ansible_os_family }} is not supported"
when: not (ansible_os_family == "RedHat" or ansible_os_family == "Debian")
比较运算符
在上面的示例当中,我们使用了 ‘==’的比较运算符,在ansible中,还支持如下比较运算符
== 比较两个对象是否相等,相等,则返回真,用于比较字符串和数字
!= 比较两个对象是否不等,不等则为真
> 比较两个对象的大小,左边的值大于右边的值,则为真
< 比较两个对象的大小,右边的值大于右边的值,则为真
>= 比较两个对象的大小,左边的值大于等于右边的值,则为真
\<= 比较两个对象的大小,左边的值小于等于右边的值,则为真
when: ansible_mechine == 'x86_64'
when: max_memory <= 512
逻辑运算符
在Ansible当中,除了比较运算符,还有逻辑运算符
and 逻辑与,当左边和右边两个表达式相同时为真,则返回真
or 逻辑或,当左边和右边两个表达式任意一个为真,则返回真
not 逻辑否,对表达式取反
() 当一组表达式组合在一起,形成一个更大的表达式,组合内的表达式都是逻辑与关系
## 逻辑或
when: ansible_distribution == "RedHat" or ansible_distribution == "Fedora" or ansible_destribution == "CentOS"
## 逻辑与
when: ansible_destribution_version == "7.5" and ansible_kernel == "3.10.0-327.el7.x86_64" and ansible_distribution == "CentOS"
### 逻辑与第二种形式
when:
- ansible_distribution_version == "7.5"
- ansible_kernel == "3.10.0-327.el7.x86_64"
### 组合 => 这里指的是换行
when: =>
(ansible_distribution == "RedHat" and ansible_distribution_major_version == "7")
or
(ansible_distribution == "Fedora" and ansible_distribution_major_version == 28")
- 判断register 注册变量的返回结果
- name: restart httpd if postfix is running
hosts: workstation
tasks:
- name: get postfix server status
command: /usr/bin/systemctl is-active postfix
ignore_errors: yes
register: result
- name: restart apache httpd based on postfix status
service:
name: httpd
state: restarted
when: result.rc == 0
条件判断与tests
在shell当中,我们可使用test命令来进行一些常用的判断操作,如下
- 判断/test文件是否存在
test -e /test
- 判断/testdir 是否为一个目录
test -d /testdir
事实上,在ansible中也有类似的用法,只不过ansible 没有使用linux的test命令
- hosts: test
vars:
testpath: /testdir
tasks:
- debug:
msg: "file exist"
when: testpath is exists ## 这里判断不存在时可以 when: not testpath is exists
判断变量
- defined: 判断变量是否已定义,已定义则返回真
- undefined: 判断变量是否未定义,未定义则返回真
- none: 判断变量的值是否为空,如果变量已定义且值为空,则返回真
- hosts: test
gether_facts: no
vars:
testvar: "test"
testvar1:
tasks:
- debug:
msg: "testvar is defined"
when: testvar is defined
- debug:
msg: "testvar2 is undefined"
when: testvar2 is undefined
- debug:
msg: "testvar1 is none"
when: testvar1 is none
判断执行的结果
- success 或者 succeeded: 通过任务执行结果返回的信息判断任务的执行状态,任务执行成功则返回true
- failure 或者 failed: 任务执行失败则返回true
- changed 或者 change: 任务执行状态为changed 则返回 true
- skip 或者skipped: 任务被跳过则返回 true
- hosts: test
gether_facts: no
vars:
doshell: true
tasks:
- shell: 'cat/testdir/aaa'
when: doshell
ignore_errors: true
- debug:
msg: "success"
when: result is success
- debug:
msg: "failed"
when: result is failure
- debug:
msg: "changed"
when: result is changed
- debug:
msg: "skip"
when: result is skip
判断路径
- file: 判断是否是一个文件
- directory: 判断是否是一个目录
- link: 判断指定路径是否为一个软链接
- mount: 判断指定路径是否为一个挂载点
- exists: 判断指定的路径是否存在
判断字符串
- lower: 判断字符串中的所有字母是否都是小写字母
- upper: 判断字符串中的所有字母是否都是大写字母
block 任务分块
条件相同的所有任务可以放到一个block块中
我们在前面使用when做条件判断时,如果条件成立则执行对应的任务,但这就面监一个问题,当我们一个条件判断执行多个任务的时候,就意味着我们要在某一个任务下面都写一个when语句,而且判断条件完全一样,这种方式不仅麻烦而且显得很low。ansible提供了一种更好的方式来解决这个问题,即block
在ansible中,使用block将多个任务进行组合,当作一个整体。我们可以对这个整体做条件判断,当条件成立时,则执行块中所有的任务
- hosts: workstation
tasks:
- debug:
msg: "tasks1 not in block"
- block:
- debug:
msg: "tasks2 in block1"
- debug:
msg: "task3 in block1"
when: 2 > 1
随之有 rescue 求援,block 执行错误时,则执行rescue中的内容
always 不管block resuce 执行成功与否, always 中的内容一定会执行
任务委托
- name: enable alerts for web servers
hosts: webservers
tasks:
- name: task1
debug:
msg: "task1"
- name: task2
debug:
msg: "task2"
- name: enable alerts
shell:
cmd: "cat /root/ansible/ansible.cfg"
register: result
delegate_to: workstation
- name: print result.stdout
debug:
msg: "{{ result.stdout }}"
when: result.rc == 0
任务暂停
有些情况下,一些任务的运行需要等待一些状态的恢复,比如某一台主机或者应用刚刚重启,我们需要等待它上面的某个端口开启,此时就需要将正在运行的任务暂停,直到其状态满足要求
ansilbe提供了wait_for
模块以实现任务暂停的需求
- wait_for模块常用参数
- connect_timeout: 在下一个任务执行之前等待的连接超时时间
- delay: 等待一个端口或者文件或者连接到指定的状态时,默认超时时间300秒,在这等待的300s的时间里,wait_for模块会一直轮询指定的对象是否到达指定的状态,delay即为多长时间轮询一次状态
- host: wait_for模块等待主机的地址,默认为127.0.0.1
- port: wait_for模块等待的主机的端口
- path: 文件路径,只有当这个文件存在时,下一个任务才开始执行,即等待该文件创建完成
- state: 等待的状态,即等待的文件或者连接状态达到指定的状态时,下一个文件任务开始执行。当等的对象为端口时,状态有started, stoped,即端口已经监听或者端口已经关闭;当等待的对象为文件时,状态有persent 或者started,absent,文件已创建或者删除;当等待的对象为一个连接时,状态有drained即连接已建立。默认为started
- timeout: wait_for 的等待的超时时间,默认为300秒
wait_for:
port: 8080
state: started
connect_timeout: 1
timeout: 300
delay: 10
滚动执行
ansible.cfg
中的forks: 20
当配置为20时,是指 ,比如有100台主机,那么20台主机为一组并发先把第一个任务执行完, 再执行下一个任务,在剧本中配置serial: 1
是指,一台主机把所有的任务执行完,再把下一台主机所有的任务执行完
生产中的平滑滚动更新
- 需要执行
delegate_to: lbservers
摘除要更新的web节点 - 通过
serial: 1
控制更新当前节点 - 通过任务暂停等待tomcat启动完成
- 业务探活
- 更新完成,
- 通过
delegate_to: lbservers
将该节点重新加回去 max_fail_percetage: 25
即最大的失败比例
以上五步会带来一个新的问题如果任务暂停等待tomcat启动时,发现tomcat启动失败了ansible会怎么处理 ? 如果ansible不去处理它,那么滚动更新完之后,所有节点都失败了 因此,可以使用第六步来实现,即最大的失败比例来进行一个控制
- hosts: all
serial: 1
ma_fail_percentage: 25
tasks:
- name: debug1
debug:
msg: "tasks1"
- name: debug2
debug:
msg: "task2"
只执行一次
- run_once
- hosts: all
tasks:
- name: run the task only once
debug:
msg: "run this task only once"
run_once: yes
比如在初始化webserver 的时候,webserver上会初始化数据库,而数据库只需要初始化一次,当webserver有多个节点的时候,我们就要用到run_once
只在一个节点上执行。
设置环境变量
tasks:
- name: set environment
shell:
cmd: "/data/bin/foo --version"
environment:
PATH="$PATH:/data/bin"
- hosts: all
remote_user: root
vars:
proxy_env:
http_proxy: http://proxy.example.com:8080
https_proxy: https://proxy.bos.example.com:8080
tasks:
- apt:
name: cobbler
state: installed
environment: proxy_env
任务标签
- name: install nginx
yum:
name: nginx
state: present
tags:
- always # 内置标签,不管指不指定,都要执行
- name: copy nginx.conf to dest
template:
src: templates/nginx.conf
dest: /etc/nginx/nginx.conf
mode: 0644
tags:
- update-conf
- update-code-or-conf
notify: restart nginx
- name: generate index.html on dest
copy:
content: "hello ansible"
dest: /usr/share/nginx/html/index.html
tags:
- update-code
- update-code-or-conf
# ansible-playbook nginx.yaml --tags update-code # 指定标签
# ansible-playbook nginx.yaml --tags update-conf
# ansible-palybook nginx.yaml --tags update-conf,update-code # 多个标签
# ansible-palybook nginx.yaml --tags update-code-or-conf # 标签组
# ansible-playbook nginx.yaml --skip-tags update-code-or-conf # 标签取反
# ansible-palybook nginx.yaml --tags tagged # 执行所有标签
roles 使用
# ansible-galaxy role init nginx # 可以初始化一个role 的规范化目录结构
nginx/
├── defaults # 一般定义可变更的变量
│ └── main.yml
├── files # 传输的文件存放位置
├── handlers # 触发器的存放位置
│ └── main.yml
├── meta # 依赖相关的roles 的调用关系
│ └── main.yml
├── README.md
├── tasks # tasks 任务
│ └── main.yml
├── templates # 配置文件模板定义的位置
├── tests # 测试流程
│ ├── inventory
│ └── test.yml
└── vars # 一般存放固定不变的变量
└── main.yml
tasks文件调用,生产中不建议直接在man.yaml 中写tasks
# cat main.yaml
- name: manage nginx
include_tasks:
file: nginx.yaml
折分成多个,放在tasks 目录下的nginx目录中
- name: manage nginx
include_tasks:
file: nginx/install.yaml
- include_tasks: nginx/mange_users.yaml
- include_tasks:
file: nginx/nginx.yaml
vars 目录中的main.yaml
是无法拆分的
而实际引用变量文件是在tasks中
- hosts: all
vars_files:
- vars.yaml
tasks:
- name: include_vars
include_vars:
- vars.yaml
引入handlers 要使用import_tasks
注意事项
- include_tasks 和 import_tasks 都可以引入task,但尽可能使用 include_tasks
- include_tasks 无法引入handlers,但import_tasks 可以
- 在role当中变量无法使用include方式引入
使用role
- hosts: webservers
roles:
- comman
- webserver
- hosts: webservers
roles:
- role: nginx
- role: mysql
可以在使用role的时候指定变量,优先级要比role vars 目录下定义的变量的优先级要高
- hosts: webservers
roles:
- common
- { role: foo_app_instance, dir:'/opt/a', port: 5000 }
- { role: foo_app_instance, dir:'/opt/b' port: 5001 }
下面也是一个带入变量的示例
- hosts: webservers
roles:
- roles: database
vars:
database_name: "{{ db_name }}"
database_user: "{{ db_pass }}"
- roles: webserver
vars:
live_hostname: web1
domains:
- example.com
- www.example.com
role的执行优先级默认永远高于tasks的优先级
pre_tasks —> role —> tasks —> post_tasks
- hosts: webservers
post_tasks:
- name: post_tasks
debug:
msg: "post_tasks"
tasks:
- name: debug
debug:
msg: "hellow tasks"
pre_tasks:
- name: pre_tasks
debug:
msg: "pre_tasks"
roles:
- role: nginx
使用pre_task 和post_tasks 控制任务的执行顺序
playbook里任务执行顺序总结
- 优先执行使用pre_tasks定义的任务
- 然后执行role中的任务
- 再执行tasks中的任务
- 最后执行post_tasks中定义的任务
定义role的依赖
- hosts: webservers
roles:
- systeminit
- nginx
- 在meta目录中定义一个role 依赖另一个role
dependencies:
- role: systeminit
galaxy
ansible 提供专用的roles 模板的网站
在早期的时候,galaxy社区用于分享roles
从ansible2.8开始,ansible社区推出了一个新的概念,叫collection,它被称为之为集合
- 模块
- role
- plugin: inventory支持init和yaml两种模式
- inventory
ansible.cfg
namespace
- commuity
# ansible-galaxy role install acandid.mysql -p ./
# ansible-galaxy collection install alikins.collection_inspect
性能调优
打开监控
ansible.cfg
callback_whitelist = timer, mail, profile_tasks, profile_roles, cgroup_perf_recap
# timer 会去监测每一个task
# cgroup_perf_recap # 监测任性能的消耗
[callback_cgroup_perf_recap]
control_group = ansible_profile ## 执行的时候会自动创建cgroup
# yum -y install libcgroup-tools
# cgcreate -a devops:devops -t devops:devops -g cpuacct,memory,pids:ansible_profile # 这里要对应配置文件中的名字
``
对比两个包的安装方式的性能
```yaml
- hosts: webservers
tasks:
- name: install package no use loop
yum:
name:
- httpd
- mod_wsgi
- mod_ssl
- targetcli
state: latest
when: ansible_fqdn == "servera"
- name: install package use loop
yum:
name: "{{ item }}"
state: latest
loop:
- httpd
- mod_wsgi
- mod_ssl
- targetcli
when: ansible_fqdn == "severb"
监控执行过程所消耗的时间,可以明显的对比出,使用loop循环占消耗的时间更高
- hosts: webservers
tasks:
- name: copy files to target
copy:
src: test_files
dest: /mnt
when: ansible_fqdn == "servera"
- name: copy files to target
synchronize:
src: test_files
dest: /mnt
when: ansible_fqdn == "serverb"
监控以上执行过程所消耗的时间,可以明显对比出,拷贝一个目录下的所有文件,copy 消耗的时间要高出许多
命令行调试
- 交互式执行每一步
# ansible-playbook nginx.yaml --step
- 从指定哪一步开始执行
# ansible-playbook playbook.yaml --start-at-tasks="install packages"
- 打印详细信息
# ansible-playbook xxx.yaml -v
# ansible-playbook xxx.yaml -vv
# ansible-playbook xxx.yaml -vvv
- 检查相比上次发现了哪些改变
# ansible-playbook foo.yml --check --diff --limit foo.example.com
异步任务
- 异步单个任务,即把不强依赖的较慢的任务,放到后台去执行
- name: copy files to target
copy:
src: test_files
dest:/mnt
async: 30 # 30秒超时
poll:2 # 每两秒轮询一次
配置调优
ssh_connection
优化ssh连接
- 默认情况下,ansible其于ssh连按被控端,当ansible在运行playbook的时候,会建立很多的ssh连接来执行相应的task,而每个task都会创建一个新的ssh连接,
- ssh连接的建立无疑问需要额外tcp建立连接的开销。
- openssh支持一个优化,叫做ssh nultiplexing, 也被称作ControiPersist。 当启用了该特性,则多个连按到相同主机的ssh会五格会共享相同的tcp连接。这样就只有第一次连接的时候两要进行TCP三次握手。
- 当启用Multiplexing后:
- 第一次ssh连按到主机的时候,openssh会创建一个主连接
- 紧接着openssh会创建一个控制套接字,通过去连接与远程去机关联
- 当有新的ssh尝试连接到主机时,openssh格使用控制套接字与远程主机通信,井不会创建新的tcp连接
开启该配置项:# cat ansible.cfg [ssh_connection] ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s
- 配罝项说明:
- ControlMaster:启用ssh multiplexing,允许多个同时与远程主机连接的ssh会话使用单一网络连接。auto用于告诉ssh在主连按和控制套接字不存在的情况下,自动创建它们,第一个ssh会话建立,则与同一主机连接的其他会话重复利用此连接,从而绕过较慢的初始过程,ssh在最后一个会话关闭后,立即销毁共享的连接
- ControiPersist:使连接在后台保持打开,而不是在上一次会话后销毁连接。其配置空闲的连接保持打开的时间长度,每个新会话将重置此空闲计时器。
[ssh_connection]
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s
- pipelining
- 在说明pipelinling之前,需要先了解一下ansible是如何执行一个tasks的
- 基于playbook中的task生成一个python脚本
- 将生成的python脚本复制到被控主机上
- 在被控主机上运行这个脚本
上面三个步骤中,后面两个步骤会产生两个ssh会话,而在pipline模式下,ansible执行python脚本时并不会复制它,而是通过管道传递给ssh会话,这样一来就会让原本需要的两个ssh会话减少成为一个,以降低开销,节省时间[defaults] pipelining = True
# vim /etc/sudoers.d/develop
develop ALL=(ALL) NOPASSWD:ALL
Defaults:develop !requiretty
- git
- git基本使用
- git的版本管理及回退
- git的分支
- git的tag
- git的远程仓库
- tower—> awx
- 安装—> 使用docker安装
- 在awx的最新版本当中,已经推荐直接通过awx-operator将awx安装进kubernetes
- awx的使用
- 用户和组
- 凭据管理
- 仓库管理
- inventory管理
- template管理
git 管理
版本控制系统 SCM
SVN 版本控制系统依赖中央存储仓库,而git本地和中央仓库上都会有版本存放
安装使用
# yum -y install git
# mkdir studygit
# git init
# git status
# git add mysql # 将目录提交到暂存区
# git commit -m "first commit"
# 当文件再次发生变更时可以使用# git status # 看到modified 哪些文件发生了变更
WSL2 vagrant ohmyzsh vscode packer工具 类似linux git 环境的工具
- 要让代码被git接管
- 初始化代码仓库
- 编写代码文件
- 提交到代码仓库
# git status
# git checkout -- mysql/tasks/main.yml # 回退单个文件
- 未被git接管的文件
- 暂存区
- 仓库
版本回退
- 回退
- 从workdir目录回退,此时代码未提交到暂存区
git checkout -- file
或者git checkout
- 从暂从区回退,此时代码被提交到了暂存区,但还未被提交至仓库
git reset HEAD file
再执行git checkout -- file
进行版本回退 - 从仓库回退,此时代码已经被提交至仓库
git log
查看要回退版本的commit号git reset --hard commitid
- 当回退错了,再次回退时
git reflog git reset --hard commitid
- 查看所有的历史版本,可以使用
git log --onelie --graph
- 当回退错了,再次回退时
- 从workdir目录回退,此时代码未提交到暂存区
HEAD^ 代表的是上一个版本
HEAD^^ 代表的是前两个版本
HEAD^^^ 代表的是前三个版本依此类推
分支管理
- 但看当前所有的分支
# git branch
# git branch -a
- 创建分支
# git checkout -b br1 # 创建分支 # git branch br1 # 创建并合并分支
- 删除分支
# git branch -d br1 # 删除分支
- 切换分支
# git checkout master # git checkout br1
- 合并分支
# 在master上合并br1分支 # git checkout master # git merage br1
版本标签(tag)
- 创建 tag
# git tag v1.0
# git tag v0.9 f52633 # 打标签到指定的commit上面
# git commit -am "add tag v1"
- 列出tag
# git tag
查看tag详情
# git show v1.0
删除tag
# git tag -d v1.0
远端操作
- 获取远端代码,从无到有的获取
# git clone
- 推送代码至远端
# git push origin <branch>
- 从远端列新代码到本地
# git pull
- 查看关联的远端库
# git remote
- 从远端获取代码,跟git pull 不同的是,git fetch会更新所有分支的代码到本地,而git pull只更新当前分支的代码
# git fetch
awx
组织—> 团队 —> 成员
组织 —> 团队 —> 成员
创建
- 删除
- 信息变更
- 管理团队
- 添加团队
- 删除团队
- 变更团队的信息
- 管理用户
- 添加用户
- 删除用户
- 变更用户的信息
角色变更
- 角色一般可以分配给团队或者用户
- 一个用户在组织当中可以有多个角色
- mormal user —普通用户
- system auditor - 系统审核,只读,可以读取这个组织当中的所有资源
- 用户
- 团队
- 主机清单
- 主机
- 主机组
- 项目
- 任务模板
- system admin
角色
- 用户组可以操作哪些权限
用户属于组织
- 用户在组织当中有三种角色
- system auditor
- system administrator
- normal user
- 用户在组织当中有三种角色
- 用户可以属于一个团队,也可以不属于任何团队
用户可以管理团队,无论这个用户是否属于这个团队
用户管理团,有三种角色
- admin 这个用户可以不属于这个团队,但是对这个团队及其所有资源拥有所有权限
- read 这个用户可以不属于这个团队,但是这个团队及其所有有相关资源有只读权限
- member 用户属于这个团队,属于这个团队的用户,可以继承这个团队当中所有的权限