ansible
ansible是批量管理Linux机器的工具。让一批的机器,同时执行一些命令。比如查看网络、机器状态、批量安装、执行命令等等。提供了ansible、ansible-playbook等命令。其中,后者的功能非常强大,有非常多的模块,而前者可以快速的,简单的调用后者的一些功能。
ansible应用场景,批量管理,当然也可以对单机操作。它抽象出了很多的管理,如机器组、任务等等。
它有很好的扩展性能编写模块来扩充它的功能。
它使用了比较流行的yaml格式来作为配置文件。但是在标准的格式上,又增加了一些其特有的自定义内容。
抽象会带来了复杂,但是为了更强大。比如:幂等性(即多次执行脚本,结果是一样的。)
在批量控制多台机器上,其实还有其他方案,如for+ssh,pssh等。pssh貌似比较接近ansible -m shell。
资料
- 官方文档
- https://zhuanlan.zhihu.com/p/405394094
- 常见的12种模块
- 中文文档
- https://www.cnblogs.com/keerya/p/7987886.html
安装
两种方式,一种借助于pip工具,另外一种,使用yum或apt来安装。
pip install ansible
yum install -y ansible
apt install -y ansile
两种方式比较,使用python来安装,能安装到较新的版本,而使用yum等工具,安装速度快,但是可能版本比较旧。
一般推荐使用yum来安装。其相关依赖的配置文件,会默认创建。而pip貌似没有。
ansible-doc
查看相关模块帮助文档。这个应该是快速掌握各个模块的利器。
ansible-doc yum
ansible-doc shell
# 模块列表
ansible-doc -l|grep copy
模块多达3387个。
| 格式 | 说明 |
|---|---|
| - 参数 | 可选参数 |
| = 参数 | 必填参数 |
ansible
配置文件
已配置免密登录。(默认22端口可以省略)
# vim /etc/ansible/hosts
[group1]
10.0.15.60:2222
10.0.15.66
[group2]
10.0.15.61:2222
10.0.15.65
[group:children]
group1
group2
group包含了group1、group2。
配置2
[defaults]
inventory = ~/ansible/hosts
roles_path = ~/ansible/roles
remote_user = alice
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False
ssh配置
[group1]
10.0.15.60:2222 ansible_ssh_port=220 ansible_ssh_user=alice ansible_ssh_pass=密码
10.0.15.60:2222 ansible_ssh_private_key_file=密钥文件
每行都可以写该主机的变量。
组变量
[pre]
10.172.48.8:22022
[pre:vars]
msg=helloworld
配置文件优先级
当前目录> home目录> etc。
ansible.cfg
hosts
检查联通性
ansible group1 -m ping
执行
m指定的是模块。
ansible all -m shell -a "df -hP|awk 'NR>1 && int(\$5) '"
ansible all -m shell -a "ls"
参数:
u 执行的用户
k 提示输入ssh密码,即使免密也需要输入
m 模块,默认是command ,不支持管道、分号等命令,只能执行最简单的命令。
shell模块,支持更多的符号。
a 模块的参数。
1、command
三兄弟模块。
默认的模块,只能识别单个的命令,而且不支持管道符号、分号、且、或等符号。
command和shell模块的区别
command模块的命令不启动shell,直接通过ssh执行命令
command不支持bash的特性,如管道和重定向等功能
所有需要调用shell的功能都无法使用
2、shell
支持shell命令、甚至非常复杂的组合。但不可以使用shell模块执行交互命令,如vim、top等。
3、script
将本地的可执行脚本发到远程执行,可以是任意执行的脚本。
setup
好像能收集很多的系统变量。
ansible hostname -m setup|less
# ansible myhosts -m setup -a 'filter=ansible_nodename'
ansible-playbook
这个功能非常强大。配置文件为yaml格式的文件。格式遵循一定的规律。
playbook基础组件
- hosts:运行执行任务(task)的目标主机
- remote_user:在远程主机上执行任务的用户
- tasks:任务,由模板定义的操作列表
- handlers:任务,与tasks不同的是只有在接受到通知(notify)时才会被触发
- templates:模板,使用模板语言的文本文件,使用jinja2语法。
- variables:变量,变量替换
- roles:角色
第一个playbook
demo.yaml
- hosts: pre
gather_facts: no
tasks:
- name: test playbook
debug: msg="play book demo"
说明:输出测试消息。gather_facts是收集配置,貌似很耗时。
ansible-playbook命令
# 语法检测
ansible-playbook --syntax-check *.yaml
# 检测 不光语法检测,相当于模拟运行
ansible-playbook --check *.yaml
# 传入变量
ansible-playbook -e version=2 -e "msg=wahaha" vars.yaml
# 执行某些tags
--list-tags
--skip-tags # 跳过的标签,用英文逗号分隔
-t # 要执行的tag
# 列出有那些任务
--list-tasks
–list-hosts 列出运行任务的主机
–limit 主机列表 只针对主机列表中的主机执行
-v 显示过程 -vv -vvv 更详细
变量
vars.yaml
- hosts: pre
gather_facts: no
vars:
- version: 0.1
- msg: hello world
tasks:
- name: test playbook
debug: msg="version {{version}},msg={{msg}}"
备注:花括号内为变量。变量如果未定义,会报错。
The task includes an option with an undefined variable.
执行
ansible-playbook vars.yaml
# 注入变量执行 问题,haha貌似没有显示出来
ansible-playbook -e version=2 -e "msg=wa haha" vars.yaml
变量在定义时可含有:数字、字母、下换线,但数字下划线不能开头;
其他形式:
备注下面的形式,可能更符合理解。
vars:
version: 0.1
msg: hello world
嵌套定义:
vars:
msg:
pre: hello
引用:
{{msg.pre}}
{{msg['pre']}}
变量的注入
[pre]
10.172.48.7
10.172.48.8:22022
[pre:vars]
msg=scc
命令行参数
ansible-playbook -e version=2 -e "msg=wa haha" vars.yaml
系统变量
比如获取ip地址。
- hosts: all
remote_user: root
vars:
collect_info: "/data/test/{{ansible_default_ipv4['address']}}/"
tasks:
- name: create IP directory
file:
name: "{{collect_info}}"
state: directory
tasks
playbook是由若干的任务task构成。每个task,都应该是个对象。其中,name,是任务的名称,(不是必要的)。往往可以将主要的命令参数,写在模块后面。行如:copy: dest=/path/to
一般构成:
- name: mydemo #随便起个名字,在执行的过程中会输出
copy: demo.txt dest=/root/demo.txt #主要的模块,模块的参数可以写在这,也可以写在该命令的子级后面。
ignore_errors: yes # 忽略错误继续
tags: t1,t2
一个命令一行。
使用ansible可以直接执行上面的任务。
ansible hostname -m 模块名 -a "a=1 b=2 c=3 ..."
command
- name: this command prints FAILED when it fails
command: /usr/bin/example-command -x -y -z
register: command_result
failed_when: "'FAILED' in command_result.stderr"
shell
shell-demo.yaml
- hosts: pre
gather_facts: no
vars:
- version: 0.1
- msg: hello world
tasks:
- name: shell command
shell: cd /home/ && ls
输出shell执行结果
输出执行的结果:
- hosts: pre
gather_facts: no
tasks:
- name: shell command
shell: cd /root/ && ls
register: lsout
#- debug: msg={{lsout.stdout}}
- debug: var=lsout
- debug: var=lsout.stdout_lines
备注:先注册到变量,然后在debug模式下输出。lsout是注册的变量名。主要两种形式。
其他形式:
tasks:
- local_action: command echo item
with_items: ps.stdout_lines
添加免密登录
直接使用shell
ansible all -i hosts -m shell -a "cat >> /root/.ssh/authorized_keys <<EOL
> ssh-rsa xxxxxxxxxxx
> EOL
> "
但是,实际上,使用现成的模块更好
- hosts: dev2
gather_facts: no
tasks:
- name: line in file
lineinfile:
path: ~/.ssh/authorized_keys
line: ssh-rsa xxxxxxxxxxx
script
ansible 192.168.20.22 -m script -a 'removes=/etc/passwd /root/file1.sh'
# 或者-m script -a "/PATH/TO/SCRIPT_FILE";
参数:
creates:如果其后跟的文件存在,则不执行脚本;(创建成功,则执行?)
removes:如果其后跟的文件存在,则执行脚本; (删除成功,则执行?)
作用:
将本地的脚本,推送到远程来执行。
备注:
可以使用相对路径,或者绝对路径。但是,要执行的文件,要有可执行权限。ansible更建议,使用编写好的ansible模块,而不是推送脚本的方式。
yum
- 方式1:
- name: "安装需要的环境"
yum: name=gcc,gcc-c++,make,php,php-gd,php-mysql,php-fpm,pcre-devel,zlib-devel,mariadb,mariadb-server state=latest
- 方式2:
- name: install package
hosts: all
tasks:
- name: install php and mariaDB
yum:
name:
- php
- mariadb
state: present
when: ansible_hostname in groups['dev']
- 方式3
- yum: name="" state=installed
with_items:
- tmux
- lrzsz
卸载
- 方式1:
# 备注 -y 不能少,否则会卡住。卸载的日志会在屏幕上输出
ansible pre -m shell -a "yum remove -y socat"
# 检查是否卸载
ansible pre -m shell -a "which socat"
- 方式2:
即更改状态为state=absent或者 removed。
yum: name={{package}} state=absent
copy
- name: "分发配置文件"
copy: src=nginx.conf dest=/usr/local/nginx/conf/nginx.conf
从本地拷贝文件到服务器。
创建文件
- copy:
content: "Hello world"
dest: /root/mydemo.txt
设置权限、用户组等
name: Copy file with owner and permissions
copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
group: foo
mode: '0644'
等价shell
ansible all -m copy -a "src=/root/test.txt dest=/root force=yes backup=yes
fetch
另见slurp。
- name: Specifying a path directly
fetch:
src: /tmp/somefile
dest: /tmp/prefix-{{ inventory_hostname }}
flat: yes
如:
- name: gather ssh private key
fetch:
src: /root/.ssh/id_rsa
dest: /tmp/ssh-{{ inventory_hostname }}
结果发现,文件目录非常的深。
/tmp/ssh-10.171.48.15/10.171.48.15/root/.ssh
影响拷贝行为的参数flat:
- no 即保证跟源文件相同的目录结构。(默认)
- yes 将源文件写到dest指定名字的文件中。
解压
- name: "导入nginx包"
unarchive: src=nginx-1.16.1.tar.gz dest=/root
默认好像也是从本地,复制到远程机器上。要求dest的目录必须存在。
如sc.tar.gz是本地的文件,会自动拷贝到远程解压,效果,跟本地解压一样。
file
- name: touch file
file: path=/root/mydemo.txt state=touch
删除,state=absent即可。
lineinfile
如果不存在,则插入
ansible test70 -m lineinfile -a 'path=/testdir/test line="test text"'
可以使用正则
ansible test70 -m lineinfile -a 'path=/testdir/test regexp="^line" line="test text" '
替换
ansible test70 -m lineinfile -a 'path=/testdir/test regexp="^line" line="test text" backrefs=yes '
删除
ansible test70 -m lineinfile -a 'path=/testdir/test line="lineinfile -" state=absent'
blockinline
https://blog.csdn.net/qq_34556414/article/details/107726215
https://blog.csdn.net/dylloveyou/article/details/80602152
service
未验证
- name: start docker service
service:
name: docker
state: started
enable: true
firewalld
未验证
- name: the firewall port for docker
firewalld:
service: docker
permanent: started
immediate: true
state: enabled
get_url
下载文件。
- name: Download file with check (md5)
get_url:
url: http://example.com/path/file.conf
dest: /etc/foo.conf
checksum: md5:66dffb5228a211e61d6d7ef4a86f5758
cron
定时任务模块。
slurp
从远端获取文件内容,并可以注册为变量(base64格式)
- name: Find out what the remote machine's mounts are
slurp:
src: /proc/mounts
register: mounts
- debug:
msg: "{{ mounts['content'] | b64decode }}"
template
使用jinjia模板来动态生成配置文件等。
local_action
需要在 Ansible 本机执行操作,可以有如下方式:
- name: add host record to center server
local_action: shell 'echo "192.168.1.100 test.xyz.com " >> /etc/hosts'
- name: add host record to center server
shell: 'echo "192.168.1.100 test.xyz.com " >> /etc/hosts'
connection: local
标签tag
标签存在的意义,就是方便选择特殊的task,他们可以被跳过、可以被挑选单独执行。一个任务,可以有一至多个标签。
- hosts: pre
gather_facts: no
tasks:
- name: install socat
yum: name=socat state=latest
tags: install
- name: debug test
shell: ls
tags: debug
其他形式:
tags:
- t1
tags: ["t1","t2"]
tags: t1,t2
执行:
# 查看有哪些tag
ansible-playbook --list-tags yum-tag.yaml
# 只执行标记的tag 多个英文逗号分隔
ansible-playbook -t debug yum-tag.yaml
# 或者跳过
ansible-playbook --skip-tags debug yum-tag.yaml
特殊的tag:
always
never(2.5版本中新加入的特殊tag)
tagged
untagged
all
条件判断when
参见shell
- name: when用法举例
hosts: all
tasks:
- name: 修改文件权限
file: src=/root/test.txt.j2 dest=/opt/test.txt mode=0644
when: '$USER=root'
循环
include
- name: check if extra_tasks.yml is present
stat: path=extras/extra-tasks.yml # 判断文件是否存在
register: extra_tasks_file
connection: local
- include: tasks/extra-tasks.yml
when: xtra_tasks_file.stat.exists
import
block
block搭配另外一个关键字rescue,字面意思为”救援”,表示当block中的任务执行失败时,会执行rescue中的任务进行补救,当然,在rescue中定义什么任务,是由你决定的。
- 合并判断
- name: block的用法
hosts: node
tasks:
- debug:
msg: "task1 not in block"
- block:
- debug:
msg: "task2 in block1"
- debug:
msg: "task3 in block1"
when: 2 > 1
- try-catch-finally
---
- hosts: testuser
remote_user: root
tasks:
- block:
- debug:
msg: 'I execute normally'
- command: /bin/false
- debug:
msg: 'I never execute, due to the above task failing'
rescue:
- debug:
msg: 'I caught an error'
- command: /bin/false
- debug:
msg: 'I also never execute'
always:
- debug:
msg: "This always executes"
when: 2>1
使用jinja2模板
handlers
执行某操作时,发触发什么事件。当有变化的时候,才会触发handlers响应。
tasks:
- name: Installs httpd server
yum: name=httpd state=installed update_cache=true
notify:
- start httpd
handlers:
- name: start httpd
service: name=httpd state=started // 启动httpd服务
角色
参见https://www.cnblogs.com/hackerlin/p/12553219.html
配置
配置文件:/etc/ansible/ansible.cfg
而默认的配置:
sed '/^#/d;/^$/d' /etc/ansible/ansible.cfg
得到如下:
[defaults]
[inventory]
[privilege_escalation]
[paramiko_connection]
[ssh_connection]
[persistent_connection]
[accelerate]
[selinux]
[colors]
[diff]
取消验证主机:
host_key_checking = False
一些目录,可以直接搜索path即可。
ansible-vault
可以将本地保存的敏感文件进行加密、解密、查看等操作。(本地加密、传送到远端或者执行的时候,要提前解密,否则会提示报错??? 原来并不是解决远端敏感数据,而是解决控制机器的敏感数据造泄漏问题)
# 加密文件
ansible-vault encrypt data.txt
# 查看文件
ansible-vault view data.txt
# 解密文件 data.txt已变成明文
ansible-vault decrypt data.txt
# 更换密码 输入旧密码、两次新密码。
ansible-vault rekey data.txt
# 编辑 输入密码、然后用view编辑,保存后,仍然是加密状态
ansible-vault edit data.txt
create /encrypt_string暂时还未用过。
指定密码文件
# ansible-vault --vault-id=pass.txt view 居然是错的
ansible-vault view --vault-id=pass.txt data.txt
# ansible ansible-playbook 均可用。
--ask-vault-pass
# 指定文件
--vault-id
roles
roles就是一种规范的目录结构。
·在实际生产环境中,为了实现不同的功能,我们会编写大量的playbook文件
·而且,每个playbook还可能会调用其他文件(如变量文件)·对于海量的、无规律的文件,管理起来非常痛苦!
Ansible从1.2版本开始支持Roles
Roles是管理ansible文件的一种规范(目录结构)
Roles会按照标准的规范,自动到特定的目录和文件中读取数据
defualts/main.yml:定义变量的缺省值,优先级较低files目录:存储静态文件的目录
handlerslmain.yml:定义handlers
metalmain.yml:写作者、版本等描述信息README.md:整个角色(role)的描述信息taskslmain.yml:定义任务的地方
templates目录:存放动态数据文件的地方(模板文件)vars/main.yml:定义变量,优先级高
ansible-galaxy
入门使用
配置文件
roles_path = /root/ans/playbook/role
创建
用来创建标准化的roles。
# 创建
ansible-galaxy init myjob
结构如下
myjob/
|-- README.md
|-- defaults
| `-- main.yml # 存放默认变量
|-- files # 静态文件
|-- handlers
| `-- main.yml
|-- meta
| `-- main.yml
|-- tasks # 主要的目录,存放task,但是不需要task字段
| `-- main.yml
|-- templates # 模板文件
|-- tests
| |-- inventory
| `-- test.yml
`-- vars
`-- main.yml # 自定义变量
添加任务
# cat role/myjob/tasks/main.yml
---
# tasks file for myjob
- name: test
shell: cd /home/ && ls
register: ps
- name: debug test
debug: var=ps.stdout_lines
添加启动剧本
# cat start-myjob-roles.yaml
---
- hosts: pre
gather_facts: no
roles:
- myjob # 可以有多个
启动
因为配置了默认的role的位置,故,系统可以正常的找到剧本的位置。
ansible-playbook start-myjob-roles.yaml
总结
如果不想改变系统的配置,可以在执行的时候,指定配置。
ansible-playbook -i ansible.cfg start-myjob-roles.yaml
命令行使用
官方的仓库 https://galaxy.ansible.com/
ansible-galaxy search "httpd" # 查找应用
ansible-galaxy info 'httpd'
# 安装到指定的目录下
ansible-galaxy install 'httpd' -p ~/anasi
# 列出本地有哪些roles
ansible-galaxy list -p roles/
requirements
下载Roles的方法:使用ansible-galaxy install或者编写requirements.yml文件
# cat ~lansible/roles/requirements.yml
#格式一:直接从Ansible Galaxy官网下载
- src: acandid.httpd
#格式二:从某个git服务器下载
- src: http://gitlab.com/xxx/xxx.git
scm: git
version: 56e00a54
name: nginx-acme
#格式三:下载tar包,支持http、https、file
- src: http://example.com/myrole.tar
name: myrole
安装
ansible-galaxy install -r roles/requirements.yml -p roles
示例
这个代码仓库很好演示:
https://github.com/zhanglianghhh/ansible-example-lnmp
实践
alias
ansible = an
s=shell
alias ans='f(){ hostname=$1;shift;cmdline=$1;shift;ansible $hostname -m shell -a "$cmdline" $*;};f'
alias anp='ansible-playbook'
lnmp
https://www.cnblogs.com/gaiting/p/12075043.html
---
- hosts: lnmp
tasks:
- name: "安装需要的环境"
yum: name=gcc,gcc-c++,make,php,php-gd,php-mysql,php-fpm,pcre-devel,zlib-devel,mariadb,mariadb-server state=latest
- name: "导入nginx包"
unarchive: src=nginx-1.16.1.tar.gz dest=/root
- name: "编译安装"
shell: cd /root/nginx-1.16.1 && ./configure && make && make install
- name: "分发配置文件"
copy: src=nginx.conf dest=/usr/local/nginx/conf/nginx.conf
- name: "分发主界面"
copy: src=info.php dest=/var/www/html/info.php
- name: 启动nginx
shell: /usr/local/nginx/sbin/nginx
- name: "启动mysql"
shell: systemctl restart mariadb
- name: "启动php-fpm"
shell: systemctl restart php-fpm
- name : "关闭防火墙"
shell: systemctl stop firewalld
- name: "关闭selinux"
shell: setenforce 0
进阶–模快
ansible有很好的扩展性,自己可以根据需求,从网上安装模块,或者编写、扩充新模块。
查看示例模块
以slurp模块为例,通过ansible-doc slurp文档,查看提示的文件路径,打开内容如下:
import base64
import os
from ansible.module_utils.basic import AnsibleModule
def main():
module = AnsibleModule(
argument_spec=dict(
src=dict(type='path', required=True, aliases=['path']),
),
supports_check_mode=True,
)
source = module.params['src']
if not os.path.exists(source):
module.fail_json(msg="file not found: %s" % source)
if not os.access(source, os.R_OK):
module.fail_json(msg="file is not readable: %s" % source)
with open(source, 'rb') as source_fh:
source_content = source_fh.read()
data = base64.b64encode(source_content)
module.exit_json(content=data, source=source, encoding='base64')
if __name__ == '__main__':
main()
我们发现,slurp本身的内容还是非常的简单的,以此为模板,我们也能编写我们的模块功能。
上面的python代码,是要在远程的机器上执行的。这说明,远程机器上,也要有可执行的python环境。
fetch模块,居然啥内容也没有。
安装新模块
编写新模块
Ansible模块开发 这篇文章讲得很不错
增加模块的位置定义。
[defaults]
library = /root/ans/playbook/library/
无参
cat /root/ans/playbook/library/info.py
from ansible.module_utils.basic import *
module = AnsibleModule(
argument_spec = dict(),
)
output="hello word!scc"
result = dict(module='info',stdout=output,changed=False,rc=0)
module.exit_json(**result)
有参
cat /root/ans/playbook/library/info2.py
from ansible.module_utils.basic import *
module = AnsibleModule(
argument_spec = dict(
msg=dict(required=True),
),
)
msg = module.params['msg']
result = dict(module='info2',stdout=msg,changed=False,rc=0)
module.exit_json(**result)
测试运行
ansible pre2 -m info
ansible pre2 -m info2 -a 'msg="123"'