ansible

ansible是批量管理Linux机器的工具。让一批的机器,同时执行一些命令。比如查看网络、机器状态、批量安装、执行命令等等。提供了ansibleansible-playbook等命令。其中,后者的功能非常强大,有非常多的模块,而前者可以快速的,简单的调用后者的一些功能。

ansible应用场景,批量管理,当然也可以对单机操作。它抽象出了很多的管理,如机器组、任务等等。

它有很好的扩展性能编写模块来扩充它的功能。

它使用了比较流行的yaml格式来作为配置文件。但是在标准的格式上,又增加了一些其特有的自定义内容。

抽象会带来了复杂,但是为了更强大。比如:幂等性(即多次执行脚本,结果是一样的。)

在批量控制多台机器上,其实还有其他方案,如for+ssh,pssh等。pssh貌似比较接近ansible -m shell

资料

安装

两种方式,一种借助于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"'