Ansible 自动化运维工具笔记整理

2019/01/25 Linux

简介

最近两年我在工作中编写 shell 脚本的次数越来越少,过去那些一键安装某些程序的脚本也逐渐被像 ansible、saltstack 这种工具所替代。刚刚说的这两种工具多多少少的都用过,每个软件都有自己的优势,选择一个用着顺手的就好。最近几年 ansible 在 GitHub 上的贡献者明显上升了许多,而我也从 saltstack 又转回了 ansible 因为大多数中小公司使用 ansible 的比较多。

下面的内容是我在学习以及使用 ansible 的过程中整理出来的一些内容分享给各位。

43.1、安装ansible

1、配置EPEL源作为yum安装源

# Centos7
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

# Centos6
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-6.repo

2、安装

yum -y install ansible

43.2、ansible配置文件

1、主配置文件

/etc/ansible/ansible.cfg


[defaults]:段常用参数说明
inventory      = /etc/ansible/hosts # 定义主机清单的文件
library        = /usr/share/my_modules/ # 自定义lib库存放目录
remote_tmp     = ~/.ansible/tmp # 临时文件远程主机存放目录
local_tmp      = ~/.ansible/tmp # 临时文件本地存放目录
forks          = 5 # 默认开启的并发数
poll_interval  = 15 # 默认轮询时间间隔
sudo_user      = root # 默认sudo用户
#ask_sudo_pass = True # 是否需要sudo密码
#ask_pass      = True # 是否需要密码
roles_path - /etc/ansible/roles # 默认下载的roles存放的目录
log_path = /var/log/ansible.log # 执行日志存放目录

2、主机清单

/etc/ansible/hosts

# 定义格式
# "#" 开头的行表示为注释行

# 主机的定义可以直接为IP地址
192.168.1.142

# 主机定义也支持使用hostname的方式,后面跟冒号加数字表示端口号,默认22号端口
bj-1-142.enzhi.com:52113
bj-1-160.enzhi.com

# 中括号的内容表示一个分组开始,紧随其后的主机均属于该组成员,空行后的主机亦属于该组
[webservers]
192.168.1.142
web1.enzhi.com
web[10:20].enzhi.com # [10:20]表示10~20之间的所有数字(包括10,20),即表示web10.enzhi.com, web11.enzhi.com...web20.enzhi.com的所有主机

43.3、主程序

1、ansible

使用格式:

ansible <host-pattern> [-m module_name] [-a args] [options]
	<host-pattern>:指明对哪台主机或主机组执行操作
	-m module_name:指定执行使用的模块
	-a args:指定模块参数

2、ansible-playbook

ansible-playbook <filename.yml> ... [options]
[options]
--check:只检测可能会发生的改变,但不真正执行操作;
--list-hosts:列出运行任务的主机;

3、ansible-doc

ansible-doc [-M module_path] [-l] [-s] [module...]
-l:列出所有可用模块。
-s:显示模块用法

示例:

[root@Bj-1-141 ansible]# ansible-doc -s file
[root@Bj-1-141 ansible]# ansible-doc -s copy

43.4、ansible常用模块

43.4.1、command模块

功用:在远程主机上运行命令,不支持使用管道

[root@Bj-1-141 ansible]# ansible dbservers -m command -a "date"
192.168.1.160 | SUCCESS | rc=0 >>
2017年 04月 01日 星期六 10:38:29 CST

43.4.2、shell模块

功用:在远程主机,在shell进程下运行命令,支持shell特性,如管道等。

[root@Bj-1-141 ansible]# ansible dbservers -m shell -a "useradd mysql"
192.168.1.160 | SUCCESS | rc=0 >>

# 使用command模块为用户设置密码无法实现,会把echo后面的全部当作字符串输出。
[root@Bj-1-141 ansible]# ansible dbservers -m command -a "echo mypass | passwd --stdin mysql"
192.168.1.160 | SUCCESS | rc=0 >>
mypass | passwd --stdin mysql

# 使用shell模块可以做到
[root@Bj-1-141 ansible]# ansible dbservers -m shell -a "echo mypass | passwd --stdin mysql"
192.168.1.160 | SUCCESS | rc=0 >>
更改用户 mysql 的密码 。
passwd: 所有的身份验证令牌已经成功更新。

43.4.3、copy模块

功用:复制文件至远程主机某个位置;

用法:

backup:在覆盖之前将原文件备份,备份文件包含时间信息。有两个选项:yes|no
content:用于替代"src",可以直接设定指定文件的值
dest:必选项。要将源文件复制到的远程主机的绝对路径,如果源文件是一个目录,那么该路径也必须是个目录
directory_mode:递归的设定目录的权限,默认为系统默认权限match
force:如果目标主机包含该文件,但内容不同,如果设置为yes,则强制覆盖,如果为no,则只有当目标主机的目标位置不存在该文件时,才复制。默认为yes
others:所有的file模块里的选项都可以在这里使用
src:要复制到远程主机的文件在本地的地址,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将递归复制。在这种情况下,如果路径使用"/"来结尾,则只复制目录里的内容,如果没有使用"/"来结尾,则包含目录在内的整个内容全部复制,类似于rsync。match
follow	yes/no	当拷贝的文件夹内有link存在的时候,那么拷贝过match去的也会有link
force	yes/no	默认为yes,会覆盖远程的内容不一样的文件(可能文件名一样)。如果是no,就不会拷贝文件,如果远程有这个文件
group	设定一个群组拥有拷贝到远程节点的文件权限
mode  等同于chmod,参数可以为“u+rwx or u=rw,g=r,o=r”
owner	设定一个用户拥有拷贝到远程节点的文件权限

示例:copy本地/etc/fstab文件到dbservers组内主机的/tmp目录下名为fstab,属主属组为mysql,权限为644

[root@Bj-1-141 ansible]# ansible dbservers -m copy -a "src=/etc/fstab dest=/tmp/fstab owner=mysql group=mysql mode=644"
192.168.1.160 | SUCCESS => {
    "changed": true,
    "checksum": "0cf45815fbdda9ff44910fe87e556984192c9414",
    "dest": "/tmp/fstab",
    "gid": 500,
    "group": "mysql",
    "md5sum": "3d18ffa0e04260589f1bbb43c4538c36",
    "mode": "0644",
    "owner": "mysql",
    "size": 501,
    "src": "/root/.ansible/tmp/ansible-tmp-1491015083.3-276514660679850/source",
    "state": "file",
    "uid": 500
}
# 查看结果
[root@Bj-1-141 ansible]# ansible dbservers -m shell -a "ls -l  /tmp/fstab"
192.168.1.160 | SUCCESS | rc=0 >>
-rw-r--r-- 1 mysql mysql 501 4月   1 10:51 /tmp/fstab

43.4.4、cron模块

功用:管理远程主机的crontab任务计划;

用法:

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=present 
name:该任务的描述,必须指明一个name
special_time:指定什么时候执行,参数:reboot,yearly,annually,monthly,weekly,daily,hourly 
state:present or absent,确认该任务计划是创建还是删除,默认是创建任务 presents
user:以哪个用户的身份执行

示例:对dbservers组内主机添加一条时间同步任务计划,每个5分钟同步一次时间

[root@Bj-1-141 ~]# ansible dbservers -m cron -a "minute=*/5 hour=* day=* month=* weekday=* job='/usr/sbin/ntpdate 0.asia.pool.ntp.org &> /dev/null' name=Synctime "
192.168.1.160 | SUCCESS => {
    "changed": true,
    "envs": [],
    "jobs": [
        "Synctime"
    ]
}
[root@Bj-1-141 ~]# ansible dbservers -m shell -a "crontab -l"
192.168.1.160 | SUCCESS | rc=0 >>
#Ansible: Synctime
*/5 * * * * /usr/sbin/ntpdate 0.asia.pool.ntp.org &> /dev/null

删除任务:

root@k8s02-ops-bjqw:~ # ansible all -m cron -a "state=absent name=Synctime"
10.100.4.182 | CHANGED => {
    "changed": true,
    "envs": [],
    "jobs": []
}
10.100.4.183 | CHANGED => {
    "changed": true,
    "envs": [],
    "jobs": []
}

43.4.5、file模块

功用:设定远程主机文件属性

用法:

force:需要在两种情况下强制创建软链接,一种是源文件不存在但之后会建立的情况下;另一种是目标软链接已存在,需要先取消之前的软链,然后创建新的软链,有两个选项:yes|no 
group:定义文件/目录的属组 
mode:定义文件/目录的权限
owner:定义文件/目录的属主
path:必选项,定义文件/目录的路径
recurse:递归的设置文件的属性,只对目录有效
src:要被链接的源文件的路径,只应用于state=link的情况
dest:被链接到的路径,只应用于state=link的情况 
state:  
	directory:如果目录不存在,创建目录
	file:即使文件不存在,也不会被创建
	link:创建软链接
	hard:创建硬链接
	touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间
	absent:删除目录、文件或者取消链接文件

示例1:创建软连接文件

[root@Bj-1-141 ~]# ansible dbservers -m file -a "src=/tmp/fstab path=/tmp/fstab.link state=link"
192.168.1.160 | SUCCESS => {
    "changed": true,
    "dest": "/tmp/fstab.link",
    "gid": 0,
    "group": "root",
    "mode": "0777",
    "owner": "root",
    "size": 10,
    "src": "/tmp/fstab",
    "state": "link",
    "uid": 0
}

示例2:修改文件属性

[root@Bj-1-141 ~]# ansible dbservers -m file -a "path=/tmp/fstab owner=nobody group=nobody mode=755"
192.168.1.160 | SUCCESS => {
    "changed": true,
    "gid": 99,
    "group": "nobody",
    "mode": "0755",
    "owner": "nobody",
    "path": "/tmp/fstab",
    "size": 501,
    "state": "file",
    "uid": 99
}

示例3:创建目录

[root@Bj-1-141 ~]# ansible dbservers -m file -a "path=/tmp/testdir state=directory"
192.168.1.160 | SUCCESS => {
    "changed": true,
    "gid": 0,
    "group": "root",
    "mode": "0755",
    "owner": "root",
    "path": "/tmp/testdir",
    "size": 4096,
    "state": "directory",
    "uid": 0
}

43.4.6、hostname模块

功用:设定主机名

使用格式:

name=                  # Name of the host

示例:修改主机名

[root@Bj-1-141 ~]# ansible dbservers -m hostname -a "name=webdb-1-160.enzhi.com"
192.168.1.160 | SUCCESS => {
    "ansible_facts": {
        "ansible_domain": "enzhi.com",
        "ansible_fqdn": "webdb-1-160.enzhi.com",
        "ansible_hostname": "webdb-1-160",
        "ansible_nodename": "webdb-1-160.enzhi.com"
    },
    "changed": true,
    "name": "webdb-1-160.enzhi.com"
}
[root@Bj-1-141 ~]# ansible dbservers -m shell -a "hostname"
192.168.1.160 | SUCCESS | rc=0 >>
webdb-1-160.enzhi.com

43.4.7、yum模块

功用:管理程序包

用法:

config_file:yum的配置文件 
disable_gpg_check:关闭gpg_check 
disablerepo:不启用某个源 
enablerepo:启用某个源
name:要进行操作的软件包的名字,可以带版本号,也可以传递一个url或者一个本地的rpm包的路径 
state:状态,安装(present,latest)卸载包(absent)

示例:

[root@Bj-1-141 ~]# ansible dbservers -m yum -a "name=htop state=present"

43.4.8、service模块

功用:管理服务启动或关闭

用法:

arguments:给命令行提供一些选项 

enabled:是否开机自动启动 yes|no
name:必选项,服务名称 
pattern:定义一个模式,如果通过status指令来查看服务的状态时,没有响应,就会通过ps指令在进程中根据该模式进行查找,如果匹配到,则认为该服务依然在运行
runlevel:运行级别
sleep:如果执行了restarted,在则stop和start之间沉睡几秒钟
state:对当前服务执行启动,停止、重启、重新加载等操作(started,stopped,restarted,reloaded)

示例:对远程主机安装nginx启动并设置为3级别开机自动启动

[root@Bj-1-141 ~]# ansible dbservers -m yum -a "name=nginx state=present"
[root@Bj-1-141 ~]# ansible dbservers -m shell -a "rpm -q nginx"
192.168.1.160 | SUCCESS | rc=0 >>
nginx-1.10.2-1.el6.x86_64

# 启动服务
[root@Bj-1-141 ~]# ansible dbservers -m service -a "name=nginx runlevel=3 enabled=yes state=started"
192.168.1.160 | SUCCESS => {
    "changed": true,
    "enabled": true,
    "name": "nginx",
    "state": "started"
}

43.4.9、user模块

功用:管理用户账户

用法:

home:指定用户的家目录,需要与 createhome 配合使用
groups:指定用户的属组
uid:指定用户的 uid
password:指定用户的密码
name:指定用户名
createhome:是否创建家目录 yes|no
system:是否为系统用户 yes|no
remove:当 state=absent 时,remove=yes 则表示连同家目录一起删除,等价于 userdel -r
state:是创建还是删除
shell:指定用户的shell环境

示例:

[root@Bj-1-141 ~]# ansible dbservers -m user -a "name=enzhi.wang"

43.4.10、setup模块

功用:获取主机的信息;

用法:

filter="":用于过滤出指定的变量的值;

示例1:查看dbservers组内所有主机的信息

[root@Bj-1-141 ~]# ansible dbservers -m setup

示例2:查看dbservers组内所有主机的内存总量信息;

[root@Bj-1-141 ~]# ansible dbservers -m setup -a "filter=ansible_memtotal_mb"
192.168.1.160 | SUCCESS => {
    "ansible_facts": {
        "ansible_memtotal_mb": 980 # 获取的内存总量信息;
    },
    "changed": false
}

43.4.11、template模块

功用:基于模版方式生成一个文件复制到远程主机

用法:

backup	yes/no	 建立个包括timestamp在内的文件备份,以备不时之需.
dest			远程节点上的绝对路径,用于放置template文件。
group			设置远程节点上的的template文件的所属用户组
mode			设置远程节点上的template文件权限。类似Linux中chmod的用法
owner			设置远程节点上的template文件所属用户
src	  			本地Jinjia2模版的template文件位置。

43.4.12 systemd 模块

功能:CentOS7 版本以后改用 systemd 风格启动脚本,有些服务使用 service 模块无法启动。

enabled:是否加入开机自启动;
name:程序名称;
state:started(启动服务),stopped(停止服务),restarted(重启), reloaded(平滑重启)

43.4.13 stat 模块

stat 模块文档地址:https://docs.ansible.com/ansible/latest/modules/stat_module.html?highlight=stat#return-values

  • 功能:检索文件或文件系统状态,有点类似于 Linux 系统中的 stat 命令。
  • 应用场景:一般用来判断文件是否存在,是否为链接文件,目录是否存在。

常用参数:

path:必须给的参数,指定一个文件或对象的绝对路径

示例:直接在命令行执行查看返回结果

$ ansible webservers -m stat -a "path=/etc/fstab"

返回如下图:

-w650

应用案例:我在使用 ansible 部署 JDK 时用来判断 /usr/local/jdk 是否存在,如果存在就不执行安装操作,否则就安装 JDK。

- name: 检查 JDK 是否已经安装
  stat: path=/usr/local/jdk
  register: jdk_result

- name: 安装JDK
  shell: cd /usr/local/src/ && tar xf  -C /usr/local/ && ln -sv /usr/local/ /usr/local/jdk
  when: jdk_result.stat.exists == false

stat 模块的执行结果返回的是一个字典,常见的返回值,可以查看其模块文档内有详细说明。

43.4.14 debug 模块

功能:调试模块,用于在调试中输出信息。

常用参数:

msg:调试输出的消息。
var:将某个任务执行的输出作为变量传递给debug模块,debug会直接将其打印输出。
verbosity:debug的级别(默认是0级,全部显示)。

示例:

- hosts: webservers
  remote_user: root
  tasks:
  - name: show date time
    shell: date
    register: result
  - name: Show debug info
    debug: var=result verbosity=0

执行结果如下:

-w973

43.5、Playbook的用法

1、playbook的核心元素

Hosts:
Tasks:
Variables
Templates:包含了模版语法的文本文件;
Handlers:由特定条件触发的任务;

43.5.1、playbook的基础组件

hosts:运行指定任务的目标主机;
remote_user:在远程主机上执行任务的用户;

tasks:任务列表
	模块,模块参数:
	定义格式:
		(1) action:module arguments
		(2) module:arguments
	# 注意:shell和command模块后面直接跟命令,而非key=value类的参数列表;
(1)某任务的状态在运行后为changed时,可通过"notify"通知给相应的handlers;
(2)任务可以通过"tags"打标签,而后可在ansible-playbook命令上使用-t指定进行调用;
假如已经安装了httpd服务,而后我们修改了配置文件,在此执行playbook时会把所有任务全都执行一遍,如果希望仅执行指定的任务就需要给指定任务打标签;

示例:

# vim webservice.yml
- hosts: webservers # 指明在webservers组内所有主机上执行任务
  remote_user: root # 指明远程执行任务的用户为root
  tasks: # 任务列表
  - name: install httpd package	# name:可以写多个,声明此任务的作用
    yum: name=httpd state=present # 使用yum模块安装httpd
  - name: install config file  # 声明这个任务是提供配置文件
    copy: src=files/httpd.conf dest=/etc/httpd/conf/ # 使用copy模块将配置文件复制到远程主机
  - name: start httpd service # 声明启动httpd服务
    service: name=httpd state=started

⚠️注意:copy模块src=files/这里使用的相对路径,是相对于此playbook文件所在的目录;

43.5.2、playbook的运行方式

使用格式:

# 测试
ansible-playbook --check <filename.yml>  
	只检测可能会发生的改变,但不真正执行操作;
ansible-playbook --list-hosts <filename.yml>
	列出运行任务的主机;
	
# 运行
ansible-playbook <filename.yml>
ansible-playbook [-t tags_name] <filename.yml>

示例:运行上面示例创建的webservice.yml文件

[root@Bj-1-141 working]# ansible-playbook webservice.yml

43.5.3、notify与handlers

notify用来指明触发事件后执行哪个任务;handlers接收到其它任务的通知时执行操作;

⚠️注意:notify指定的名称必须要与handlers中定义的name名称完全一样;

格式:

- hosts: webservers
  remote_user: root
  tasks:
  - name: install httpd package
    yum: name=httpd state=present
  - name: install config file
    copy: src=files/httpd.conf dest=/etc/httpd/conf/
    notify: restart httpd # 指明当src指明的文件发生变化时就触发restart httpd这个任务
  - name: start httpd service
    service: name=httpd state=started
  handlers:  
    - name: restart httpd  # 定义restart httpd具体的操作
      service: name=httpd state=restarted

43.5.4、tags

默认情况下每次执行playbook文件都会从头开始全部执行一遍任务,当需要指明从某个具体的任务开始向下执行时就需要使用tags来指明从此任务开始向下执行。

假如已经安装了httpd服务,而后我们修改了配置文件,在此执行playbook时会把所有任务全都执行一遍,如果希望从提供配置文件任务开始执行就需要给指定任务打标签;

示例:

- hosts: webservers
  remote_user: root
  tasks:
  - name: install httpd package
    yum: name=httpd state=present
  - name: install config file
    copy: src=files/httpd.conf dest=/etc/httpd/conf/
    tags: instconf # 给install configure file任务打一个标签叫instconf名称自定义;
    notify: restart httpd
  - name: start httpd service
    service: name=httpd state=started
  handlers:
    - name: restart httpd
      service: name=httpd state=restarted

注意:定义 tags 名称时不要加空格,同一个 tasks 下的任务可以为每个任务打一个标签,并且标签的名字可以相同。

使用 ansible-playbook -t <tag> 选项来指定从哪个 tag 开始向下执行。

运行:

[root@Bj-1-141 working]# ansible-playbook -t instconf --check webservice.yml

PLAY [webservers] **************************************************************

TASK [setup] *******************************************************************
ok: [192.168.1.142]
ok: [192.168.1.143]

TASK [install config file] *****************************************************
changed: [192.168.1.142]
changed: [192.168.1.143]

RUNNING HANDLER [restart httpd] ************************************************
changed: [192.168.1.143]
changed: [192.168.1.142]

PLAY RECAP *********************************************************************
192.168.1.142              : ok=3    changed=2    unreachable=0    failed=0
192.168.1.143              : ok=3    changed=2    unreachable=0    failed=0

观察上面运行测试结果可以看到并没有运行install httpd package任务

43.5.5 获取命令的输出 Register

https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html?highlight=register#register-variables

在刚开始使用 ansible-playbook 做应用程序部署的时候,因为在部署的过程中有使用到 command 或 shell 模块执行一些自定义的脚本,而且这些脚本都会有输出,用来表示是否执行正常或失败。如果像之前自己写脚本做应用程序部署的,这很好实现。但现在是用 Ansible 做,那么要怎么样做可以获取到 ansible playbook 中 command 模块的输出呢? Ansible 也提供的解决办法,这时我们就可以通过使用 register 关键字来实现,register 关键字可以存储指定命令的输出结果到一个自定义的变量中,我们通过访问这个自定义变量就可以获取到命令的输出结果。Register 的使用很方便,只需要在 task 声明 register 关键字,并自定义一个变量名就可以。如下:

- name: 检查 JAVA_HOME 是否存在
  shell: cat /root/.bashrc
  register: bashrc_result

- name: 引用环境变量
  shell: source /etc/profile 
  when: bashrc_result.stdout.find('JAVA_HOME') == -1

这里第 1 个 task 是执行了一个 cat 命令,register 关键字将 cat 命令的输出存储到 bashrc_result 变量名。第 2 个 task 对输出进行分析,并使用 when 对关键字对分析后的进行判断,如果 barshrc_result 变量中找不到 ‘JAVA_HOME’ 这个字符串返回 -1,则执行这个 task,不匹配就不执行。这里要重点说下的,因为 register 获取到的输出内容都是字符串,而 ansible 又是 python 写的,你可以使用 python 字符串的方法对其做处理,比如本文中使用的 find,还可以使用 split 方法。个人觉得,真是非常灵活方便。

特意使用 Python 测试了一下字符串的方法如下图:

-w973

加上 JAVA_HOME 字符串在使用 find 方法:

-w892

43.6、variables变量

43.6.1、变量的定义

1、通过setup模块结果直接调用其内部变量

2、ansible-playbook命令行中的自定义变量

-e VARS, --extra-vars=VARS

# 示例
root@k8s05-ops-bjqw:~/working # cat forth.yml
- hosts: webservers
  remote_user: root
  tasks:
  - name: install pkg
    yum: name= state=present # name定义变量引用
    
# 命令行传递一个变量 pkname=memcached
root@k8s05-ops-bjqw:~/working # ansible-playbook -e pkname=memcached  forth.yml

3、通过 roles 传递变量

4、Host Inventory 中定义变量

(1) 向不同的主机传递不同的变量
	IP/HOSTNAME variables=value var2=value2
	示例:
	[webservers]
	192.168.1.142 http_port=80
	
(2) 向组中的主机传递相同的变量
      [groupname:vars]
      variable=value
	示例:
      [webservers]
      192.168.1.142
      
      [webservers:vars] # 向webservers组内传递变量
      http_port=80

注意:inventory 参数:

用于定义ansible远程连接目标主机时使用的参数,而非传递给playbook的变量;
ansible_ssh_host
ansible_ssh_port
ansible_ssh_user
ansible_ssh_pass
ansible_sudo_pass

5、在playbook文件中定义变量

定义方式:

vars:
- var1: value1
- var2: value2

示例:

- hosts: webservers
  remote_user: root
  vars: # 声明变量定义
  - http_port: 80 # http_port的值为80
  tasks:
  - name: install httpd package
    yum: name=httpd state=present
  - name: install config file
    template: src=files/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
    tags: instconf
    notify: restart httpd
  - name: start httpd service
    service: name=httpd state=started
  handlers:
    - name: restart httpd
      service: name=httpd state=restarted

43.6.2、调用变量

调用方式:在模版文件中使用来调用指定变量;



# 例如,nginx 进程数设置为远程主机的CPU核心数
user nginx;
worker_processes ;

# 变量中还可进行算数运算等操作例如,CPU 的核心数加1
worker_processes ;

⚠️注意:备调用的模版文件后缀必须以.j2结尾

示例:httpd.conf配置文件中Listen 调用变量http_port

# 修改httpd.conf文件
Listen 

# playbook文件
- hosts: webservers
  remote_user: root
  vars: # 声明变量定义
  - http_port: 80 # http_port的值为80
  tasks:
  - name: install httpd package
    yum: name=httpd state=present
  - name: install config file
    template: src=files/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
    tags: instconf
    notify: restart httpd
  - name: start httpd service
    service: name=httpd state=started
  handlers:
    - name: restart httpd
      service: name=httpd state=restarted

43.7、条件测试

when语句:在 task 中使用,jinja2 的语法格式;

示例:

task:
- name: install conf file to centos7
	template: stc=files/nginx.conf.c7.j2
	when: ansible_distribution_major_version == "7" # 当远程主机版本为centos7提供centos7的nginx配置文件
- name: install conf file to centos6
	template: stc=files/nginx.conf.c6.j2
	when: ansible_distribution_major_version == "6" # 当远程主机版本为centos6提供centos6的nginx配置文件

when 语句中也可以使用 jinja2 中的语法,例如 and、not 等

- hosts: webservers
  remote_user: root
  tasks:
  - name: install nginx
    yum: name=nginx state=latest
  - name: inst nginx config centos7
    template: src=files/nginx.conf.c7.j2 dest=/etc/nginx/nginx.conf
    # 两个条件必须同时满足
    when: ansible_distribution == "CentOS" and ansible_distribution_major_version == "7"
    tags: instconf
    notify: restart nginx
  - name: inst nginx config centos6
    template: src=files/nginx.conf.c6.j2 dest=/etc/nginx/nginx.conf
    when: ansible_distribution == "CentOS" and ansible_distribution_major_version == "6"
    tags: instconf
  - name: start nginx
    systemd: name=nginx state=started enabled=yes
  handlers:
  - name: restart nginx
    systemd: name=nginx state=reloaded

43.8、循环

功用:迭代需要重复的任务;

格式:

# 对迭代项引用,固定变量名为"item";而后要在task中使用 `loop` 给定要迭代的元素列表;在低版本的 ansible 中使用 with_items;

列表方法:
	字符串
	字典

示例:

- name: install some packages
  yum: name=  state=present
  loop: # 一次迭代一个元素,第一次item为nginx,第二次item为memcached,第三次item为php-fpm
  - nginx
  - memcached
  - php-fpm
- name: add some groups # 循环创建三个组
  group: name= state=present
  loop:
  - group11
  - group12
  - group13
- name: add some users # 循环创建三个用户,并且同时给组引用变量;
  user: name= group= state=present
  loop:
  - { name: 'user1',group: 'group11' }
  - { name: 'user2',group: 'group12' }
  - { name: 'user3',group: 'group13' }

完整示例:

[root@Bj-1-141 working]# cat users.yml
- hosts: webservers
  remote_user: root
  tasks:
  - name: add some groups
    group: name= state=present
    loop:
    - group11
    - group12
    - group13
  - name: add some users
    user: name= group= state=present
    loop:
    - { name: 'user1',group: 'group11' }
    - { name: 'user2',group: 'group12' }
    - { name: 'user3',group: 'group13' }

43.9、角色(roles)

使用 roles 可以让我们按照功能角色为基准来划分。

默认 ansible 角色(roles)的存放位置在 /etc/ansible/roles 目录内,可以通过修改 ansible 配置文件进行修改:

# additional paths to search for roles in, colon separated
#roles_path    = /etc/ansible/roles

以下是一个参考示例,我们就是按照功能进行划分,每个应用在roles下面都是一个独立的子目录;

roles/
	mysql/
	nginx/
	httpd/
	tomcat/
	memcached/

每个角色,以特定的层级目录结构进行组织;例如:

mysql/
	files/:存放由copy或script模块等调用的文件;
	templates/:template模块查找所需的模版文件目录;
	tasks/:至少应该包含一个名为main.yml的文件;其它的文件需要在此文件中通过include进行包含;
	handlers/:至少应该包含一个名为main.yml的文件;其它的文件需要在此文件中通过include进行包含;
	vars/:至少应该包含一个名为main.yml的文件;其它的文件需要在此文件中通过include进行包含;
	meta/:至少应该包含一个名为main.yml的文件;定义当前角色的特殊设定及其依赖关系;其它的文件需要在此文件中通过include进行包含;
	default/:设定默认变量时使用此目录中的main.yml文件;

上面这些目录并不是必须创建,用到哪个就创建哪个就可以。

43.9.1、角色调用方法

在playbook中调用角色的方法1:

- hosts: webservers
  remote_user: root
  roles:
  - mysql
  - memcached
  - nginx

在playbook中调用角色的方法2:传递变量给角色

- hosts: webservers
  remote_user: root
  roles:
  - { role: nginx,username: nginx }
  键role用于指定角色名称;后续的username: nginx用于传递变量给角色;
  
  还可以基于条件测试实现角色调用:
  roles:
  - { role: nginx, when: ansible_distribution_major_version == '7' }
  当系统版本为centos7时才执行nginx这个角色

43.10、源码方式部署 Nginx Web 服务

要求: 1、使用 ansible roles 实现 2、Nginx 软件包的版本通过变量获取; 2、nginx 配置文件中 worker_processes 参数使用变量动态获取 CPU 核数; 3、设置以 daemon 用户运行 nginx 配置文件中 user 指令通过变量获取运行 nginx 的用户;

43.10.1 环境说明

角色 IP地址 硬件配置
Ansible 10.100.4.176 1vCPU 2GB内存
Nginx Web Server1 10.100.4.177 1vCPU 2GB内存
Nginx Web Server2 10.100.4.178 1vCPU 2GB内存

ansible-nginx

43.10.2 创建 nginx roles

在 ansible 主机上创建

root@k8s05-ops-bjqw:~ # mkdir /etc/ansible/roles/nginx
root@k8s05-ops-bjqw:~ # cd /etc/ansible/roles/nginx

创建 files 目录用于存放 nginx 源代码包:

root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # mkdir files
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # cd files/
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx/files # wget http://nginx.org/download/nginx-1.12.2.tar.gz

创建 templates 目录存放 nginx 模板配置文件:

root@k8s05-ops-bjqw:/etc/ansible/roles/nginx/files # cd ../
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # mkdir templates
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # cd templates/
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx/templates # cat nginx.conf.c7.j2
# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

# 通过 username 变量获取运行 Nginx 的用户
user ;
# 通过 ansible_processor_count 获取远程主机的 CPU数量, setup 模块
worker_processes ;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 10240;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

创建 vars 目录用于存放安装 Nginx 时所设定的变量:

root@k8s05-ops-bjqw:/etc/ansible/roles/nginx/templates # cd ../
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # mkdir vars
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # cd vars/
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx/vars # cat main.yml
username: daemon
version: 1.12.2

# username 运行nginx的用户
# version copy模块获取 Nginx 版本的变量

注意:在定义变量的格式 val: value

创建用于执行任务的 tasks 目录:

root@k8s05-ops-bjqw:/etc/ansible/roles/nginx/vars # cd ../
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # mkdir tasks
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # vim tasks/main.yml
- name: Install Nginx Depend on the package
  yum: name= state=present
  loop:
  - openssl
  - openssl-devel
  - pcre
  - pcre-devel
  - gcc
  - gcc-c++
  - automake
  - zlib
  - zlib-devel

- name: Install Nginx package
  copy: src=nginx-.tar.gz dest=/usr/local/src/

- name: Install Nginx
  shell: cd /usr/local/src/ && tar xf nginx-.tar.gz && cd nginx- && ./configure --prefix=/usr/local/nginx --conf-path=/etc/nginx/nginx.conf --user=nginx --group=nginx --error-log-path=/var/log/nginx/error_log --http-log-path=/var/log/nginx/access_log --pid-path=/var/run/nginx/nginx.pid --lock-path=/var/lock/nginx.lock --with-http_ssl_module --with-http_gzip_static_module --with-debug --with-http_stub_status_module && make && make install

- name: Install Nginx env
  copy: src=nginx.sh dest=/etc/profile.d/

- name: Install Nginx config
  template: src=nginx.conf.c7.j2 dest=/etc/nginx/nginx.conf
  tags: instconf
  notify: restart nginx

- name: Start Nginx
  shell: /usr/local/nginx/sbin/nginx

由于上面 tasks 中 Install Nginx config 任务中设置了 notify 我们还需要创建 handlers 目录来定义触发条件所执行的任务:

root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # mkdir handlers
root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # vim handlers/main.yml
- name: restart nginx
  shell: /usr/local/nginx/sbin/nginx -s reload
  
# name 定义的名字必须和 notify 一致

创建 Install Nginx env 任务中所复制的环境变量文件:

root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # vim files/nginx.sh
export PATH=/usr/local/nginx/sbin:$PATH

创建 playbook 调用 nginx role:

# 创建一个目录专门用于存放 playbook 文件
root@k8s05-ops-bjqw:~ # mkdir -p /data/ansible
root@k8s05-ops-bjqw:~ # vim /data/ansible/nginx.yml
- hosts: webservers
  remote_user: root
  roles:
  - nginx

43.10.3 运行 playbook

运行 ansile-playbook –check 也就是 dryrun 模式检查一遍:

root@k8s05-ops-bjqw:~ # ansible-playbook --check /data/ansible/nginx.yml

检查没问题如下图:

-w945

接下来我们就可以去掉 –check 直接跑一遍测试一下:

root@k8s05-ops-bjqw:~ # ansible-playbook /data/ansible/nginx.yml

-w1069

在 ansible 主机上使用远程模块验证一下 Nginx 服务是否安装成功并启动:

# 获取 Nginx 版本号看看是否正确
root@k8s05-ops-bjqw:~ # ansible webservers -m shell -a "nginx -v"
10.100.4.178 | CHANGED | rc=0 >>
nginx version: nginx/1.12.2

10.100.4.177 | CHANGED | rc=0 >>
nginx version: nginx/1.12.2

获取配置文件中的运行用户和 worker_process 是否配置正确:

-w1261

验证 Nginx 是否运行:

-w1243

接下来我们修改 nginx 监听的端口号验证能否触发 handler:

root@k8s05-ops-bjqw:/etc/ansible/roles/nginx # vim templates/nginx.conf.c7.j2

# 将 80 端口修改为 8080
listen       8080 default_server;

在使用 dryrun 模式检查一遍看看,在 Install Nginx config 中设定了 tags 所以我们指定从这个tag 所在的任务开始向下执行就可以不用每次都检查一遍安装包是否安装:

root@k8s05-ops-bjqw:~ # ansible-playbook -t instconf --check /data/ansible/nginx.yml

-w1440

上图显示 changed 只有一个是因为配置文件还没有真正复制过去,所以 HANDLER 被跳过了,去掉 –check 直接执行:

root@k8s05-ops-bjqw:~ # ansible-playbook -t instconf  /data/ansible/nginx.yml

-w1438

检查 Nginx 端口是否更改:

-w1340

-w1088

43.10.4 如何安装不同版本的 Nginx

示例中安装的是 nginx-1.12.2 版本,如果你需要安装其它版本的 Nginx 需要做以下几点改动:

  1. 修改 vars/main.ymlversion 的值修改为你要安装的 nginx 版本号例如 1.8.1
  2. 将 Nginx 软件包下载后放置到 files 目录中;

Ansible template 模块遇到特殊字符处理问题

注意:如果推送的配置文件里含有特殊字符,如:”;” “#”等,是不能用Template模块直接推送的,因为这些不能被解析会报错,如下:

-w1439

解决方法

使用jinja2的Escaping,把这些特殊字符转义,语法:“ … ”,参考:http://jinja.pocoo.org/docs/2.9/templates/#escaping

sed -i '/^;/s/^;/;/g' www.conf.j2
sed -i '/^{% raw %}/s/$/\n/g' www.conf.j2

Jinja2转义符:

#被raw包含起来的部分被转义,不会被解析

;aaa
#bbb

-w1285

执行成功后配置文件内容不变如下

-w924

Jinja2 模板文件中如何使用 for 循环

hosts 文件中定义的 [zookeeper] 用来循环 [zookeeper] 组内的主机在 zoo.cfg.cluster.j2 模板文件中循环生成配置

示例:

三台 Zookeeper 节点配置成集群,Server id 不同,IP 地址不同,配置文件如下,如何实现?

server.1=10.100.4.177:2888:3888
server.2=10.100.4.178:2888:3888
server.3=10.100.4.179:2888:3888

就是使用 jinja2 语法中的 for 循环,从变量中获取 ansible 主机清单 hosts 文件中定义的组内主机 IP。

hosts 主机清单文件如下:

[zookeeper]
10.100.4.177 
10.100.4.178
10.100.4.179

定义vars 目录下的 main.yml 文件

version: 3.4.10
dataDir: /data/zookeeper
dataLogDir: /data/logs/zookeeper
clientPort: 2181
leader_port: 2888
vote_port: 3888
zk_hosts: zookeeper # 这里就是引用的 hosts 配置文件中的[zookeeper] 

定义 Zookeeper配置文件模板

$ cat zoo.cfg.j2


关于 loop.index 是 for 循环中特殊的变量,相关解释如下:

-w781

Search

    Table of Contents