使用ad-hoc命令可以执行一些简单的任务,但是有时一个任务过于复杂,需要大量的操作时候,执行的ad-hoc命令是不适合的,这时最好使用playbook。Ansible的任务配置文件被称为playbook,我们可以称之为“剧本”,也可以理解为批处理任务。
YAML语言
Playbook采用YAML语法编写,该语言被开发的时候,它的意思是:yet another markup language (仍是一种标记语言)。详见:YAML语言教程
YAML语言的优点:
具有很好的可读性,易于实现
表达能力强,扩展性好
和脚本语言的交互性好
有一个一致的信息模型
可以基于流程来处理
Playbooks核心元素 1 2 3 4 5 Explainhosts:主机组; tasks:任务列表; variables:变量,设置方式有四种; templates:包含了模板语法的文本文件; handlers:由特定条件触发的任务
hosts 对于playbook中的每一个play,使用hosts选项可以定义inventory中要执行这些任务的主机或主机组,比如:
1 2 3 4 5 Explain--- - hosts: centos6,centos7,192.168.100.59 # hosts处可以使用","分隔主机或主机组 tasks: XXXXXX - name: run a command shell: /bin/date
其他指定主机和主机组的方式:
all
或*
:表示inventory中的所有主机。
:
:取并集。例如host1:host2:group1
表示2台主机加一个主机组。
:&
:取交集。例如group1:&group2
表示两个主机组中都有的主机。
:!
:排除。例如group1:!host1
表示group1中排除host1主机的剩余主机。
通配符
:例如web*.baidu.com
。
数字范围
:例如web[0-5].baidu.com
。
字母范围
:例如web[a-d].baidu.com
。
正则表达式
remote_user
使用remote_user
指定在远程主机上执行任务的用户,默认remote_user
是ssh连接到被控主机上的用户。
此外,还可以在某个task上单独定义执行该task的身份,这将覆盖全局的定义。比如:
1 2 3 4 5 6 7 8 9 Explain--- - hosts: centos6,centos7,192.168.100.59 remote_user: root tasks: - name: run a command shell: /bin/date - name: copy a file to /tmp copy: src=/etc/fstab dest=/tmp remote_user: myuser
remote_user
实际上并不是执行任务的绝对身份,它只是ssh连接过去的身份,如果ssh连接的是普通用户,可以通过become
来指定root
,比如:
1 2 3 4 5 6 7 8 9 Explain--- - hosts: centos6,centos7,192.168.100.59 remote_user: yourname tasks: - name: copy a file to /tmp copy: src=/etc/fstab dest=/tmp become: yes become_method: sudo become_user: root # 此项默认值就是为root,可省略
tasks tasks下定义的是一系列task任务列表,比如调用各个模块。这些task按顺序一次执行一个,直到所有被筛选出来的主机都执行了这个task之后才会移动到下一个task上进行同样的操作。
任务执行时,hosts选项所指定的那些主机都会收到相同的task指令,临时任务文件会通过sftp发送到所有的被控主机上,所有主机收到指令后,ansible主控端会筛选某些主机(如果进行了筛选),并通过ssh在远程执行任务。对于某主机来说,某任务执行失败,后续的所有任务都不会再去执行。当然,这不会影响其他的主机执行任务(除非主机的任务之间有依赖关系)。
最重要的是,ansible中的task是幂等性的,多次执行不会影响那些成功执行过的任务。另外幂等性还表现在执行失败后如果修正了playbook再次执行,将不会影响那些原本已经执行成功的任务,即使是不同主机也不会影响。仅这方面而言,ansible对于排错来说是极其友好的。当然,某些特殊的模块或者特殊定义的task并不一定总是幂等的,例如最简单的,执行一个command或者shell模块的命令,它会重复执行。但也有办法使其变得幂等,以command和shell模块为例,它们有两个选项:creates和removes,它们分别表示被控主机上指定的文件存在(不存在)时就不执行命令。
task中有其要执行的一个或多个任务,大部分情况下 action 是作为一个列表来提供的。在playbook中,每调用的一个模块都称为一个action,其本质是加载并执行ansible对应的模块。
name
可以为每个task加上name项,也可以不加,比如:
1 2 3 4 5 Explain# 这个task有两个action,第一个action定义了name进行描述,调用了file模块;第二个action没有定义name,调用了shell模块 tasks: - name: do something to initialize mariadb file: path=/mydata/data state=directory owner=mysql group=mysql mode=0755 - shell: /usr/bin/mysql_install_db --datadir=/mydata/data --user=mysql creates=/mydata/data/ibdata1
实际上name只是一种描述性语句,它可以定义在任何地方。
action
action不需要显式地定义出来,这只是一个理解性地概念,表示调用了一个模块的动作,有以下三种方法可以传递模块参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Explain# 下面的task是要启动ssh服务 # 方法一: 定义为key=value,直接传递参数给模块 tasks: - service: name=sshd state=started # 方法二: 定义为key: value方式 tasks: - service: name: sshd state: started # 方法三: 使用args内置关键字,然后定义为key: value方式 tasks: - service: args: name: sshd state: started
ping
模块、command
和shell
模块是不需要key=value
格式的。
条件判断
在task中可以使用when
语句进行条件判断,是否执行这条action。when
语句符合Jinja语法。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Explain# 配合facts变量根据centos版本号分发不同的配置文件 tasks: - name: install conf file to centos7 template: src=files/nginx.conf.c7.j2 when: ansible_distribution_major_version == "7" - name: install conf file to centos6 template: src=files/nginx.conf.c6.j2 when: ansible_distribution_major_version == "6" # 配合register变量根据上一步执行结果判断下一步是否执行 tasks: - name: Get the list of installed packages shell: rpm -qa register: installed_packages - name: Install a package if it's not already installed yum: name: mypackage state: present when: "'mypackage' not in installed_packages.stdout"
循环
对于需要重复执行的action,可以使用循环来实现。
对循环项的引用,固定变量名为item
,而后,要在task中使用with_items
给定要循环的元素列表。比如:
1 2 3 4 5 6 7 8 Explain# 批量安装软件 tasks: - name: unstall web packages yum: name={{ item }} state=absent with_items: - httpd - php - php-mysql
如果要指定的内容较多,with_items
中的列表项也支持字典的形式。比如:
1 2 3 4 5 6 7 Explaintasks: - name: add some users user: name={{ item.name }} group={{ item.group }} state=present with_items: - { name: 'user11', group: 'group11' } - { name: 'user12', group: 'group12' } - { name: 'user13', group: 'group13' }
tag
可以为playbook中的每个action都打上标签,标签的主要作用是可以在ansible-playbook中设置只执行哪些被打上tag的任务或忽略被打上tag的任务。
1 2 3 4 5 6 7 8 Explain# 给启动apache的任务打上了apache标签,给启动mysql的任务打上了mysql标签 tasks: - name: make sure apache is running service: name=httpd state=started tags: apache - name: make sure mysql is running service: name=mysqld state=started tags: mysql
执行剧本时可以根据tag来选择执行的action,比如:
1 ansible-playbook -i hosts -t mysql test.yml # 只执行启动mysql的任务
其他tag相关的选项
1 2 3 --list-tags # 显示剧本中所有已定义的标签 -t TAGS, --tags=TAGS # 仅执行指定的标签 --skip-tags=SKIP_TAGS # 跳过这个标签,执行其他所有任务
variables 变量的设置方式有四种
在剧本中书写变量
在主机清单中定义
直接调用facts变量
1 2 3 4 5 6 7 8 Explain#常用必备的facts ansible_default_ipv4.address # 默认的网卡ip eth0 ansible_distribution # 系统发行版本名字 CentOS Ubuntu Debian ... ansible_memtotal_mb # 内存大小 ansible_processor_vcpus # cpu数量 ansible_processor_cores # cpu 核心数 ansible_date_time.date # 时间
通过roles传递变量
本文后续详细讲解
templates Templates模板是一个嵌套有脚本的文本文件,使用Jinja2
模板编程语言编写,常用于含有脚本或变量的配置文件的编写,以.j2
结尾。 Jinja2是python的一种模板语言,以Django的模板语言为原本。详见 Jinja语言教程
handlers Handler可以理解为触发器,需要notify来触发。Handler主要用于重启服务或者触发系统重启,除此之外很少使用Handler。
ansible中几乎所有的模块都具有幂等性,这意味着被控主机的状态是否发生改变是能被捕捉的,即每个action的changed=true或changed=false。ansible在捕捉到changed=true时,可以触发notify组件(如果定义了该组件)。
需要在Handler中定义任务,然后可以在剧本中使用notify来触发handler中定义的action。notify是一个组件,其主要目的是调用handler。
1 2 3 4 5 6 7 8 9 10 11 12 Explain# 这表示当执行template模块的任务时,如果捕捉到changed=true,即nginx配置文件发生了变化,那么就会触发notify,执行handler中定义的start nginx 和 test web page两个action tasks: - name: copy template file to remote host template: src=/etc/ansible/nginx.conf.j2 dest=/etc/nginx/nginx.conf notify: - restart nginx - test web page handlers: # handlers与tasks平级。定义了两个action:start nginx 和 test web page - name: restart nginx # action一定要带有name描述 service: name=nginx state=restarted - name: test web page # action一定要带有name描述 shell: curl -I http://192.168.100.10/index.html | grep 200 || /bin/false
当一个任务通过 notify
触发了一个handler,handler并不会立即执行,而是会在本次playbook的所有任务执行完成后,按照定义的顺序执行。这意味着当触发了多个handler时,它们会在当前剧本的所有任务执行完成后按照其在剧本中的定义顺序依次执行。
这种行为确保了在一个剧本中所有任务执行完成后再执行一些共同的操作,从而允许对多个任务的结果进行整合或者基于这些任务的结果执行其他操作。
Ansible Roles role是一种ansible剧本目录的编排方式,目的是分割庞大的playbook,以及复用某些细化的play甚至是task。
介绍role之前,需要先了解include
include 如果将所有的play都写在一个playbook中,很容易导致这个playbook文件变得臃肿庞大,且不易读。因此,可以将多个不同任务分别写在不同的playbook中,然后使用include
将其包含进去即可。
include可以在一个playbook中导入另一个playbook文件,也可以更细分地导入task文件。
导入playbook
在此处,include
的动作是在playbook中加载一个或多个playbook,所以写在顶级列表的层次
1 2 3 4 5 6 7 8 9 Explain- name: this is a play at the top level of a file hosts: all tasks: - name: say hi shell: echo "hi..." - include: load_balancers.yml sayhi="hello world" - include: webservers.yml - include: dbservers.yml
导入task
先编写一个task文件a.yml
,其实就是多个action的列表。
1 2 3 4 Explain- name: restart nginx service: name=nginx state=restarted - name: test web page shell: curl -I http://192.168.100.10/index.html | grep 200 || /bin/false
playbook中,在tasks指令的子选项处使用include包含。
1 2 3 4 5 Explain- hosts: centos7 tasks: - include: a.yml # 在同目录中才可以这样写相对路径 vars: # 如果a.yml中含有变量,可以在此处添加,也可以直接在include行中添加`var=xxx` sayhi: "hi"
handlers中也可以使用include包含
1 2 handlers: - include: a.yml # 在同目录中才可以这样写相对路径
role 通过include
可以对臃肿的playbook实现分割,但是还很难实现灵活地调用。
roles 用于层次性、结构化地组织playbook。roles 能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单来讲,roles就是通过分别将变量(vars)、文件(file)、任务(tasks)、模板(templates)及触发器(handlers)放置于单独的目录中,并可以便捷地include它们的一种机制。
roles要素
default/:此目录中至少应该有一个名为main.yml的文件,用于设定默认变量;
files/:存储由copy或script等模块调用的文件;
tasks/:此目录中至少应该有一个名为main.yml的文件,用于定义各task;其它的文件需要由main.yml进行“包含”调用;
handlers/:此目录中至少应该有一个名为main.yml的文件,用于定义各handler;其它的文件需要由main.yml进行“包含”调用;
vars/:此目录中至少应该有一个名为main.yml的文件,用于定义各variable;其它的文件需要由main.yml进行“包含”调用;
templates/:存储由template模块调用的模板文本;
meta/:此目录中至少应该有一个名为main.yml的文件,定义当前角色的特殊设定及其依赖关系;其它的文件需要由main.yml进行“包含”调用;
目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Explain[root@server yaml]# tree . ├── site.yml # 入口文件,ansible执行的剧本文件 ├── roles # roles目录,所有的role都放在这个目录中 ├── httpd # 部署apache服务的role │ ├── default │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ └── vars ├── mysql # 部署mysql服务的role │ ├── default │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ └── vars └── nginx # 部署nginx服务的role ├── default ├── files ├── handlers ├── meta ├── tasks ├── templates └── vars
Ansible有一个网站专门存放了一大堆的playbook,可以下载下来稍作修改就能使用:Ansible官方playbook仓库
自动部署案例 nfs部署案例 在目标主机上部署nfs服务,并且挂载到本机目录中
1 vim /scripts/playbook/nfs.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - hosts: backup tasks: - name: install nfs rpcbind yum: name=nfs-utils state=installed - name: configure nfs exports file copy: dest=/etc/exports content="/playbook-backup/ 172.16 .1 .0 /24(rw,sync,all_squash)" - name: mkdir chown file: path=/playbook-backup state=directory owner=nfsnobody group=nfsnobody - name: start && enable rpc systemd: name=rpcbind enabled=yes state=started - name: start && enable nfs systemd: name=nfs enabled=yes state=started - name: mount test mount: fstype=nfs src=172.16.1.41:/playbook-backup path=/mnt state=mounted
lnp部署案例 1 vim /scripts/playbook/lnp.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 - hosts: backup tasks: - name: add nginx repo yum_repository: file: nginx name: nginx description: "ngx repo" baseurl: "http://nginx.org/packages/centos/$releasever/$basearch/" enabled: yes gpgcheck: no state: present - name: add php repo yum_repository: file: php name: php description: "php repo" baseurl: "http://us-east.repo.webtatic.com/yum/el7/x86_64/" enabled: no gpgcheck: no state: present - name: install nginx && php pack yum: name: nginx,php71w,php71w-cli,php71w-common,php71w-devel,php71w-embedded,php71w-gd,php71w-mcrypt,php71w-mbstring,php71w-pdo,php71w-xml,php71w-fpm,php71w-mysqlnd,php71w-opcache,php71w-pecl-memcached,php71w-pecl-redis,php71w-pecl-mongodb enablerepo: php state: installed - name: copy nginx conf copy: src: nginx-php-www.conf dest: /etc/nginx/conf.d/www.conf backup: yes - name: copy php conf copy: src: php-www.conf dest: /etc/php-fpm.d/www.conf backup: yes - name: create code dir file: path: /data/www-play/ owner: nginx group: nginx state: directory - name: copy code copy: src: web-index.php dest: /data/www-play/index.php owner: nginx group: nginx backup: yes - name: start nginx systemd: name: nginx enabled: yes state: restarted - name: start php systemd: name: php-fpm enabled: yes state: restarted
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 server { listen 80 default_server; server_name www.etiantian.org; root /data/www-play; location / { index index.php index.html; } location ~* \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name ; include fastcgi_params; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [www] user = nginx group = nginx listen = 127.0.0.1:9000 listen.allowed_clients = 127.0.0.1 pm = dynamic pm.max_children = 50 pm.start_servers = 5 pm.min_spare_servers = 5 pm.max_spare_servers = 35 slowlog = /var/log/php-fpm/www-slow.log php_admin_value[error_log] = /var/log/php-fpm/www-error.log php_admin_flag[log_errors] = on php_value[soap.wsdl_cache_dir] = /var/lib/php/wsdlcache