ansible-galaxy

连接 https://galaxy.ansible.com 下载相应的roles,此网站是Ansible爱好者将日常使用较好的playbooks打包上传,其他人可以免费下载
到Ansible PlayBooks并立即投入使用。

ansible-galaxy 语法:

ansible-galaxy [delete|import|info|init|install|list|login|remove|search|setup] [--help] [options] 

 列出已安装的galaxy
#ansible-galaxy list geerlingguy.mysql
- geerlingguy.mysql, 2.8.1

 安装galaxy
ansible-galaxy install geerlingguy.redis

 删除galaxy
ansible-galaxy remove geerlingguy.redis

进入网站后找到这时标记的地方

image

把名字复制下来

image

就可以在你的ansible主机上进行安装剧本了。

#ansible-galaxy install geerlingguy.mysql
- downloading role 'mysql', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-mysql/archive/2.8.1.tar.gz
- extracting geerlingguy.mysql to /root/.ansible/roles/geerlingguy.mysql
- geerlingguy.mysql (2.8.1) was installed successfully

安装的剧本默认是存放在家目录的隐藏文件中。
/root/.ansible

#tree -L 2
.
└── geerlingguy.mysql
    ├── defaults
    ├── handlers
    ├── LICENSE
    ├── meta
    ├── README.md
    ├── tasks
    ├── templates
    ├── tests
    └── vars

当然,本机的galaxy命令也提供在线搜索剧本。

#ansible-galaxy search --author geerlingguy

Found 90 roles matching your search:

 Name                              Description
 ----                              -----------
 geerlingguy.raspberry-pi          Configures a Raspberry Pi.
 geerlingguy.php-redis             PhpRedis support for Linux
 geerlingguy.drupal-console        Drupal Console
 geerlingguy.adminer               Installs Adminer for Database management.
 geerlingguy.blackfire             Blackfire installation for Linux
 geerlingguy.tomcat6               Tomcat 6 for RHEL/CentOS and Debian/Ubuntu.
 geerlingguy.php-pear              PHP PEAR library installation.

Ansible-vault

功能:管理加密解密yml文件

ansible-vault [create|decrypt|edit|encrypt|rekey|view]
 ansible-vault encrypt hello.yml 加密
 ansible-vault decrypt hello.yml 解密
 ansible-vault view hello.yml 查看加密问题
 ansible-vault edit hello.yml 编辑加密文件
 ansible-vault rekey hello.yml 修改口令
 ansible-vault create new.yml 创建新文件

Ansible-console

可交互执行命令,支持tab补全。

#ansible-console
Vault password:默认是当前登录账号密码

root@all (5)[f:5]$
列出所有的内置命令: ?或help

执行用户@当前操作的主机组(all) (当前组的主机数量5)[f:并发数5]
设置并发数: forks n
root@all (5)[f:5]$ forks 10

切换组: cd 主机组
root@all (5)[f:10]$ cd db

列出当前组主机列表: list
root@all (5)[f:10]$ list
6-web-1.hunk.tech
7-web-0.hunk.tech
7-web-2.hunk.tech
6-dns-1.hunk.tech
7-db-3.hunk.tech

执行一行指令
root@web (3)[f:10]$ setup filter=ansible_distribution_version

Ansible-playbook

语法:

ansible-playbook <filename.yml> ... [options]
常见选项
--check 只检测可能会发生的改变,但不真正执行操作
--list-hosts 列出运行任务的主机
--list-tasks 列出此playbook中的所有任务
--list-tags 列出此playbook中的所有的tags
--limit 主机列表 只针对主机列表中的主机执行
--step 一步一步执行脚本
--flush-cache  清除fact缓存
-C 文件名     执行前先检查语法。
-D 显示出执行前后的变化内容
-v 显示过程 -vv -vvv 更详细

第一个playbook

---

- hosts: all
  remote_user: root

  tasks:
    - name: test yml
      command: /usr/bin/wall "hello world"

语法检查

image

真正执行

image

Playbook采用YAML语言编写

Playbook工作流程

image

YAML语法简介

这里只涉及到playbook相关的语法,更多请参考官网http://www.yaml.org

语法非常严格,请仔细仔细再仔细。

在单一档案中,可用连续三个连字号(---)区分多个档案。另外,还有选择性的连续三个点号( ... )用来表示档案结尾
 次行开始正常写Playbook的内容,一般建议写明该Playbook的功能
 使用#号注释代码
 缩进必须是统一的,不能空格和tab混用,一般缩进2个空格
 缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的
 YAML文件内容和Linux系统大小写判断方式保持一致,是区别大小写的,key/value的值均需大小写敏感
 key/value的值可以写在同一行,也可换行写。同一行使用 , 逗号分隔
 value可是个字符串,也可是另一个列表
 一个完整的代码块功能需最少元素需包括 name和task
 一个name只能包括一个task
 使用| 和 > 来分隔多行,实际上这只是一行。
        include_newlines: |
            exactly as you see
            will appear these three
            lines of poetry

        ignore_newlines: >
            this is really a
            single line of text
            despite appearances
 Yaml中不允许在双引号中出现转义符号,所以都是以单引号来避免转义符错误           
 YAML文件扩展名通常为yml或yaml

List:列表

其所有元素均使用"-"打头

- web
- dns
-空格web

Dictionary:字典(键值对)

通常由多个key与value构成

多行写法:
name: hunk
blog: "http://https://blog.51cto.com/191226139"
name:空格hunk   > 这个冒号后面必须是一个空格

同一行写法:
需要使用{ }
{name: hunk, blog: "http://https://blog.51cto.com/191226139"}  > 逗号后建议使用留一个空格

布尔值的表示法:
yes/no  true/false

create_key: yes
needs_agent: no
knows_oop: True
likes_emacs: TRUE
uses_cvs: false

一些特别要注意的语法

Ansible 使用 "{{ var }}" 来引用变量. 如果一个值以 "{"开头, YAML 将认为它是一个字典, 所以我们必须引用它, 像这样:

foo: "{{ variable }}"

使用引号来包裹任何包含冒号:的value值, 像这样:

foo: "somebody said I should put a colon here: so I did"

Playbook核心元素

hosts

hosts 行的内容是一个或多个组或主机的 patterns,以逗号为分隔符。通常是/etc/ansible/hosts定义的主机列表

remote_user 就是远程执行任务的账户名:

---
- hosts: web,dns
  remote_user: root

tasks

任务集

  tasks:
    - name: install httpd
      yum: name=httpd

    - name: start httpd
      service: name=httpd state=started

    - name: check http port
      shell: ss -ntl|grep 80 > /tmp/httpd.txt

    - name: fetch
      fetch: src=/tmp/httpd.txt dest=/app

执行过程

image

执行结果
#tree /app
/app
├── 6-web-1.hunk.tech
│   └── tmp
│       └── httpd.txt
├── 7-web-0.hunk.tech
│   └── tmp
│       └── httpd.txt
├── 7-web-2.hunk.tech
│   └── tmp
│       └── httpd.txt

#cat /app/6-web-1.hunk.tech/tmp/httpd.txt 
LISTEN     0      128                      :::80                      :::*    

一个yml文件里可以设计多个playbook,不过呢,为了更清晰的管理,建议应该独立存放不同任务需求,方便以后调用。

这里要注意双引号的情况

    - name: change httpd.conf
       shell: "/bin/sed -i.bakr '/^Listen 80/cListen 8080' /etc/httpd/conf/httpd.conf"

Handlers 和 notity

由特定条件触发的操作,满足条件方才执行,否则不执行。

Handlers也是task列表,这些task与前述的tasks并没有本质上的不同,用于当关注的资源发生变化时,才会采取一定的操作

还是拿上个例子的playbook修改下。

---
- hosts: ~^7.*web
  remote_user: root

  tasks:
    - name: install httpd
      yum: name=httpd

    - name: change httpd.conf
      copy: src=/app/httpd.conf dest=/etc/httpd/conf/ backup=yes
      notify: restart httpd             > 在 notify 中定义内容一定要和handlers中定义的 - name 内容一样,这样才能达到触发的效果,否则会不生效。
    - name: start httpd
      service: name=httpd state=started

    - name: wall http status
      shell: /usr/bin/wall `ss -nltp|grep httpd`

  handlers:
    - name: restart httpd           > 只有接收到通知才会执行这里的任务
      service: name=httpd state=restarted
这里准备了一个httpd2.4版本的配置文件
#grep ^Listen /app/httpd.conf
Listen 8080
这个配置文件不可用于httpd2.2版本,因此需要在执行时指定匹配的主机用正则判断一下。
---
#ansible-playbook installhttpd.yml --limit ~^7.*web > 支持正则表达式
PLAY [web]

TASK [Gathering Facts] 

ok: [7-web-0.hunk.tech]
ok: [7-web-2.hunk.tech]

tags

指定某条任务执行,用于选择运行playbook中的部分代码。 ansible具有幂等性,因此会自动跳过没有变化的部分,

即便如此,有些代码为测试其确实没有发生变化的时间依然会非常地长。此时,如果确信其没有变化,就可以通过

tags跳过此些代码片断。可以为每个tasks设置tags,这样方便调用。

    - name: change httpd.conf
      copy: src=/app/httpd.conf dest=/etc/httpd/conf/
      tags: copyconf    > tags:后除了一个空格外,不要有其他空格,中间可以使用_下划线
      notify: restart httpd

可以使用多个tags

#ansible-playbook –t copyconf useradd.yml
#ansible-playbook –t copyconf,start_httpd useradd.yml
如果命令或脚本的退出码不为零,可以使用如下方式替代
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand || /bin/true

 或者使用ignore_errors来忽略错误信息:
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True

变量

变量命名

变量名仅能由字母、数字和下划线组成,且只能以字母开头

变量定义方式

1. # ansible setup facts 远程主机的所有变量都可直接调用。setup模块就是用来获取远程主机的相关信息的。一般以ansible_开头的就是变量可以调用

2. 在/etc/ansible/hosts中定义

普通变量:主机组中主机单独定义,优先级高于公共变量
公共(组)变量:针对主机组中所有主机定义统一变量

[web]
6-web-1.hunk.tech hname=nginx http_prot=8080   > 主机普通变量
7-web-0.hunk.tech hname=httpd http_prot=8081   > 主机普通变量
7-web-2.hunk.tech hname=httpd24 http_prot=8082  > 主机普通变量

[web:vars]   注意:vars是关键字,针对web组内所有主机设置的变量
hname=web    > 主机公共(组)变量,

3. 通过命令行指定变量,优先级最高
    #ansible-playbook –e 变量名=变量值
        http_port=80

4. 在playbook中定义
vars:                 > 关键字
  - var1: value1
  - var2: value2

5. vars_files指定变量文件
    vars_files:
    - /app/vars.yml

6. 在role中定义

7. 通过{{ variable_name }} 调用变量,且变量名前后必须有空格,有时用"{{ variable_name }}"才生效

8. register  注册变量
   把某一条任务执行的结果保存下来,可以在接下的任务中调用或者做些判断。这里是定义了结果存到什么名字的变量里。常与ignore_errors配合设置成True
   整个变量可以使用在template中,动作行中,或者是when语句中
   使用-v参数来查看存储的内容或者使用debug: var=注册名
    register变量的命名不能用 - 中横线

各种变量优先级:
命令行 -e > vars_files指定变量文件 > 主机清单普通变量 > 主机清单公共(组)变量

一些例子:

通过playbook中使用vars: 定义调用


---
- hosts: web
  remote_user: root
  vars:                     > 关键字
    - username: hunk88      > 键值对
    - groupname: groupabc   > 键值对

  tasks:
    - name: add group
      group: name={{ groupname }}    > 变量调用
    - name: add user
      user: name={{ username }}      > 变量调用
...

通过playbook中使用setup中的变量调用

---
- hosts: web
  remote_user: root

  tasks:
    - name: create  file
      copy: dest=/app/ip.txt content="{{ ansible_all_ipv4_addresses }}"  > 调用了setup 模块收集的ansible_all_ipv4_addresses变量值
...

#cat /app/ip.txt 
["192.168.5.102", "172.18.103.79", "192.168.7.201"]

通过主机清单定义普通变量并调用

[web]
6-web-1.hunk.tech hname=nginx http_prot=8080
7-web-0.hunk.tech hname=httpd http_prot=8081
7-web-2.hunk.tech hname=httpd24 http_prot=8082

---
- hosts: web
  remote_user: root

  tasks:
    - name: set hostname
      hostname: name={{ hname }}-{{ http_prot }}

结果:
#ansible web -m shell -a 'echo $HOSTNAME'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
nginx-8080

7-web-0.hunk.tech | SUCCESS | rc=0 >>
httpd-8081

7-web-2.hunk.tech | SUCCESS | rc=0 >>
httpd24-8082

通过主机清单定义公共(组)变量并调用

[web]
6-web-1.hunk.tech http_prot=8080
7-web-0.hunk.tech http_prot=8081
7-web-2.hunk.tech http_prot=8082

[web:vars]
hname=web
make="-"

tasks:
    - name: change hostname
      hostname: name={{ hname }}{{ make }}{{ http_prot }}

结果:
#ansible web -m shell -a 'echo $HOSTNAME'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
web-8080

7-web-0.hunk.tech | SUCCESS | rc=0 >>
web-8081

7-web-2.hunk.tech | SUCCESS | rc=0 >>
web-8082

通过独立的变量文件调用

#vim /app/vars.yml
hname: nginx    > 注意键值对的格式
prot: 9527

playbook:

- hosts: web
  remote_user: root
  vars_files:
    - /app/vars.yml

  tasks:
    - name: set hostname
      hostname: name={{ hname }}-{{ prot }}

结果:

#ansible web -m shell -a 'echo $HOSTNAME'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
nginx-9527

7-web-2.hunk.tech | SUCCESS | rc=0 >>
nginx-9527

7-web-0.hunk.tech | SUCCESS | rc=0 >>
nginx-9527

通过register注册变量并调用

- hosts: web
  tasks:
  - shell: /bin/cat /etc/centos-release
    register: release               > 将上一条结果保存到release变量
  - name: show release
    debug: var=release

以下是用debug模块查看输出结果

ok: [7-web-0.hunk.tech] => {
    "release": {
        "changed": true,                       > 显示是否已更改
        "cmd": "/bin/cat /etc/centos-release", > 执行的命令
        "delta": "0:00:00.006148", 
        "end": "2018-02-03 22:47:49.010194",   > 执行结束的时间
        "failed": false,
        "rc": 0,                               > 命令的返回码
        "start": "2018-02-03 22:47:49.004046", > 执行开始的时间
        "stderr": "",                          > 如果有错误,则输出错误的信息
        "stderr_lines": [],                    > 逐行输出
        "stdout": "CentOS Linux release 7.4.1708 (Core) ", > 命令的输出
        "stdout_lines": [
            "CentOS Linux release 7.4.1708 (Core) "
        ]
    }
}

调用:
- hosts: web
  tasks:
  - shell: /bin/cat /etc/centos-release
    ignore_errors: true
    register: release

  - name: show Centos 6
    command: /usr/bin/wall "Centos 6"
    when: release.stdout | match("CentOS release 6.9 (Final)")

  - name: show Centos 7
    command: /usr/bin/wall "Centos 7"
    when: release.stdout | search("release 7")  > 模糊匹配

判断文件是否存在的实例:

- hosts: all
  tasks:
    - name: check file
      shell: ls /app/ip.txt
      ignore_errors: true
      register: release

    - name: show file not found
      command: /usr/bin/wall "file not found"
      when: release.rc != 0

    - name: show file found
      command: /usr/bin/wall "file found"
      when: release.rc == 0

when条件判断

与shell编程中的条件判断类似

在task后添加when子句即可使用条件测试;when语句支持Jinja2表达式语法

前面的案例使用了正则来判断主机的系统版本号,这里直接使用when调用系统变量来判断

match:精确匹配
search:模糊匹配
支持正则表达式

when: release.stdout | match('.*release 6.9.*$')
when: release.stdout | match('CentOS release 6.9 \(Final\)') > 精确匹配
when: release.stdout | search("release 7")  > 模糊匹配
when: ansible_distribution_major_version == "7"

判断变量是否定义

is defined: 变量已经定义
is undefined: 变量未定义

tasks:
    - shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
      when: foo is defined

    - fail: msg="Bailing out. this play requires 'bar'"
      when: bar is undefined

with_items

当有需要重复性执行的任务时,可以使用迭代机制。类似于循环

 对迭代项的引用,固定变量名为 item"
 要在task中使用with_items给定要迭代的元素列表

列表格式:
字符串
字典

例子:

- hosts: web
  remote_user: root
  vars:               > 定义了2个变量
    - user1: hunk4    > 键值对
    - user2: hunk5

  tasks:
    - name: create user
      user: name={{ item }}    > 调用with_items的值,这是固定的关键字,不能换成其他的。
      with_items:              > with_items:列表开始    
        - "{{ user1 }}"        > 调用vars中定义的2个变量,注意需要用双引号引起来。
        - "{{ user2 }}"

结果:
#ansible web -a 'tail -n3 /etc/passwd'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
user2:x:503:503::/home/user2:/bin/bash
hunk4:x:504:504::/home/hunk4:/bin/bash
hunk5:x:505:505::/home/hunk5:/bin/bash

7-web-0.hunk.tech | SUCCESS | rc=0 >>
user2:x:1003:1003::/home/user2:/bin/bash
hunk4:x:1004:1004::/home/hunk4:/bin/bash
hunk5:x:1005:1005::/home/hunk5:/bin/bash

7-web-2.hunk.tech | SUCCESS | rc=0 >>
user2:x:1003:1003::/home/user2:/bin/bash
hunk4:x:1004:1004::/home/hunk4:/bin/bash
hunk5:x:1005:1005::/home/hunk5:/bin/bash

当然,也可以不像上面那样,直接在with_items下直接给到列表内容。

另外,每个- name 下面的with_items可以同时存在,不相互冲突。如下面的:
- name: X1
  with_items
    - a
    - b

- name: X2
  with_items
    - c
    - d

等价的写法:

方法1,多行:
  tasks:
    - name: with_items
      command: wall "{{ item }}"
      with_items:
        - 0
        - 2
        - 4
        - 6
        - 8
        - 10
      when: item > 5

方法2,单行:
  tasks:
    - name: with_items
      command: wall "{{ item }}"
      with_items: [ 0, 2, 4, 6, 8, 10 ]    > 使用[ ] 来存放列表
      when: item > 5

批量循环创建文件夹

本着为了以后更好的维护变量的设计,这次把变量独立到到个文件中
#vim /app/vars.yml
all_data:                
  - A
  - B 
  - C 
  - D

playbook:
- hosts: dns
  remote_user: root
  vars_files:
    - /app/vars.yml

  tasks:
    - name: create dir
      file: "path=/tmp/{{ item }} state=directory"
      with_items:
        - "{{ all_data }}"

#tree /tmp
/tmp
├── A
├── B
├── C
└── D

with_items嵌套子变量

这次来了个综合一点的例子,本着为了以后更好的维护变量的设计,这次把变量独立到到个文件中

#cat /app/vars.yml 

user1: hunk3       > 键值对
user2: hunk4
group1: A
group2: B

playbook

- hosts: web
  remote_user: root
  vars_files:              > 导入变量文件
    - /app/vars.yml

  tasks:
    - name: create groups
      group: name={{ item }}   > 使用with_items创建组
      with_items:
        - "{{ group1 }}"       > with_items列表的值来自变量文件中的变量名
        - "{{ group2 }}"

    - name: create users
      user: name={{ item.username }} group={{ item.groupname }}   > 嵌套子变量,格式为:item.with_items的列表中的键名
      with_items:
       - { username: "{{ user1 }}", groupname: "{{ group1 }}" }   > 别晕,最外层的{} 就是键值对的语法,里面有2个键值对,用逗号,分隔。{{ }}是引入的变量键名,注意要用双引号。
       - { username: "{{ user2 }}", groupname: "{{ group2 }}" }

结果:

image

#ansible web -m shell -a 'id hunk3;id hunk4'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
uid=502(hunk3) gid=502(A) groups=502(A)
uid=503(hunk4) gid=503(B) groups=503(B)

7-web-0.hunk.tech | SUCCESS | rc=0 >>
uid=1002(hunk3) gid=1002(A) groups=1002(A)
uid=1003(hunk4) gid=1003(B) groups=1003(B)

7-web-2.hunk.tech | SUCCESS | rc=0 >>
uid=1002(hunk3) gid=1002(A) groups=1002(A)
uid=1003(hunk4) gid=1003(B) groups=1003(B)