本文详细介绍ansible怎么批量初始化服务器,包括ansible批量初始化服务器详细配置和步骤,有需要的小伙伴们可以参考借鉴,希望对大家有所帮助。
[root@nginx ansible]# tail -3 /etc/ansible/hosts #要初始的主机如下
[node]
192.168.20.4
192.168.20.5
playbook文件内容如下:
[root@nginx ansible]# cat ssh.yaml
---
- name: configure ssh connection
hosts: node
gather_facts: false
connection: local
tasks:
- name: configure ssh connection
shell: |
ssh-keyscan {{inventory_hostname}} >>~/.ssh/known_hosts
sshpass -p '123.com' ssh-copy-id root@{{inventory_hostname}}
...
注:
配置主机名可以使用shell模块,但是对于不太专业,ansible提供了一个专用于配置主机名的模块:hostname模块。
当然,要使用ansible去设置多个主机名,要求目标主机和目标名称已经关联好,否则多个主机和多个主机名之间无法对应去设置。
例如:分别设置node组中的两个节点主机名为node01和node02,playbook内容如下:
[root@ansible ansible]# cat test.yaml
---
- name: set hostname
hosts: node
gather_facts: false
vars:
hostnames:
- host: 192.168.20.4
name: node01
- host: 192.168.20.5
name: node02
tasks:
- name: set hostname
hostname:
name: "{{item.name}}"
when: item.host == inventory_hostname
loop: "{{hostnames}}"
在上面的hostname模块中,需要详细介绍vars指令以及when、loop指令。
vars指令可用于设置变量,可以设置一个或多个变量。下面几种方式都是合理的:
# 设置单个变量
vars:
var1: value1
vars:
- var1: value1
# 设置多个变量
vars:
var1: value1
var2: value2
vars:
- var1: value1
- var2: value2
vars可以设置在play级别,也可以设置在task级别,设置在play级别,该play范围内的task可以访问这些变量,其他play范围内则无法访问;设置在task级别,只有该task能访问这些变量,其他task和其他play则无法访问。
例如:
[root@ansible ansible]# cat test.yaml
---
- name: play1
hosts: localhost
gather_facts: false
vars:
- var1: "value1"
tasks:
- name: access var1
debug:
msg: "var1's value: {{var1}}"
- name: play2
hosts: localhost
gather_facts: false
tasks:
- name: cat's access vars from play1
debug:
var: var1
- name: set and access var2 in this task
debug:
var: var2
vars:
var2: "value2"
- name: cat't accesss var2
debug:
var: var2
执行结果如下:
[root@ansible ansible]# ansible-playbook test.yaml
PLAY [play1] **************************************************************************
TASK [access var1] ********************************************************************
ok: [localhost] => {
"msg": "var1's value: value1"
}
PLAY [play2] **************************************************************************
TASK [cat's access vars from play1] ***************************************************
ok: [localhost] => {
"var1": "VARIABLE IS NOT DEFINED!"
}
TASK [set and access var2 in this task] ***********************************************
ok: [localhost] => {
"var2": "value2"
}
TASK [cat't accesss var2] *************************************************************
ok: [localhost] => {
"var2": "VARIABLE IS NOT DEFINED!"
}
PLAY RECAP ****************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
回到我们更改主机名的配置vars指令中:
vars:
hostnames:
- host: 192.168.20.4
name: node01
- host: 192.168.20.5
name: node02
上面只设置了一个变量hostnames,但这个变量的值是一个数组结构,数组的两个元素又都是对象(字典/hash)结构。
所以想要访问主机名node01和它的IP地址192.168.20.4,可以:
tasks:
- debug:
var: hostnames[0].name
- debug:
var: hostnames[0].host
在ansible中,提供的唯一一个通用的条件判断是when指令,当when指令的值为true时,则执行该任务,否则不执行该任务。
例如:
[root@ansible ansible]# cat test.yaml
---
- name: play1
hosts: localhost
gather_facts: false
vars:
- myname: "Ray"
tasks:
- name: task will skip
debug:
msg: "myname is : {{myname}}"
when: myname == "lv"
- name: task will execute
debug:
msg: "myname is : {{myname}}"
when: myname == "Ray"
在上面的myname值设置为Ray,第一个任务因为when的判断条件是myname==“lv”,所以判断结果为false,该任务不执行,同理,第二个任务因为when的值为true,所以执行了。
该playbook的执行结果:
PLAY [play1] **************************************************************************
TASK [task will skip] *****************************************************************
skipping: [localhost]
TASK [task will execute] **************************************************************
ok: [localhost] => {
"msg": "myname is : Ray"
}
PLAY RECAP ****************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
[root@ansible ansible]# cat add_dns.yaml
---
- name: play1
hosts: node
gather_facts: true
tasks:
- name: add DNS
lineinfile:
path: "/etc/hosts"
line: "{{item}} {{hostvars[item].ansible_hostname}}"
when: item != inventory_hostname
loop: "{{ play_hosts }}"
执行结果如下:
TASK [Gathering Facts] ****************************************************************
ok: [192.168.20.4]
ok: [192.168.20.5]
TASK [add DNS] ************************************************************************
skipping: [192.168.20.4] => (item=192.168.20.4)
changed: [192.168.20.4] => (item=192.168.20.5)
changed: [192.168.20.5] => (item=192.168.20.4)
skipping: [192.168.20.5] => (item=192.168.20.5)
需求如下:
playbook如下:
[root@ansible ansible]# cat config_yum.yaml
- name: config yum repo add install software
hosts: node
gather_facts: false
tasks:
- name: backup origin yum repos
shell:
cmd: "mkdir bak; mv *.repo bak"
chdir: /etc/yum.repos.d
creates: /etc/yum.repos.d/bak
- name: add os repo and epel repo
yum_repository:
name: "{{item.name}}"
description: "{{item.name}} repo"
baseurl: "{{item.baseurl}}"
file: "{{item.name}}"
enabled: 1
gpgcheck: 0
reposdir: /etc/yum.repos.d
loop:
- name: os
baseurl: "https://mirrors.tuna.tsinghua.edu.cn/centos/7/os/$basearch"
- name: epel
baseurl: "https://mirrors.tuna.tsinghua.edu.cn/epel/7/$basearch"
- name: install pkgs
yum:
name: lrzsz,vim,dos2unix,wget,curl
state: present
在上面的yaml文件中,第一个任务是将所有系统默认的repo文件备份到bak目录中,chdir参数表示在执行shell模块的命令前先切换到/etc/yum.repos.d目录下,creates参数表示bak目录存在时则不执行shell模块。
第二个任务是使用yum\_repository模块配置yum源,该模块可添加或移除yum源。
相关参数如下:
- name:指定repo的名称,对应于repo文件中的[name];
- description:repo的描述信息,对应repo文件中的name:xxx;
- baseurl:指定该repo的路径;
- file:指定repo的文件名,不需要加.repo后缀,会自动加上;
- reposdir:repo文件所在的目录,默认为/etc/yum.repos.d目录;
- enabled:是否启用该repo,对应于repo文件中的enabled;
- gpgcheck:该repo是否启用gpgcheck,对应于repo文件中的gpgcheck;
- state:present表示保证该repo存在,absent表示移除该repo。
在上面的配置中使用了一个loop循环来添加两个repo:os和epel。
第三个任务是使用yum模块安装一些rpm包,yum模块可以更新、安装、移除、下载包。
yum常用参数说明:
name:指定要操作的包名
- 可以带版本号;
- 可以是单个包名,也可以是包名列表,或者逗号分隔多个包名;
- 可以是url;
- 可以是本地rpm包
state:
- present和installed:保证包已安装,它们是等价的别名;
- latest:保证包已安装了最新版本,如果不是则更新;
- absent和removed:移除包,它们是等价的别名;
- download\_only:仅下载不安装包(ansible 2.7才支持)
- download\_dir:下载包存放在哪个目录下(ansible 2.8才支持)
yum模块是RHEL系列的包管理器,如果是ubuntu则无法使用,可以使用另一个更为通用的包管理器模块:package,它可以自动探测目标节点的包管理器类型并使用它们去管理软件。大多数时候使用package来代替yum或代替apt-install等不会有什么问题,但是有些包名在不同的操作系统上是不一样的,这是需要注意的。
保证时间同步可以避免很多玄学性的问题,特别是对集群中的节点。
通常会使用ntpd时间服务器来保证时间的同步,这里使用aliyun提供的时间服务器来保证时间同步,并将同步后的时间同步到硬件。
playbook文件如下:
---
- name: sync time
hosts: node
gather_facts: false
tasks:
- name: install and sync time
block:
- name: install ntpdate
yum:
name: ntpdate
state: present
- name: ntpdate to sync time
shell: |
ntpdate ntp1.aliyun.com
hwclock -w
上面使用了一个block指令来组织了两个有关联性的任务,将他们作为了一个整体。block更多的用于多个关联性任务之间的异常处理。
关闭selinux的playbook如下:
[root@ansible roles]# cat disable_selinux.yaml
---
- name: disable selinux
hosts: node
gather_facts: false
tasks:
- name: disable on the fly
shell: setenforce 0
ignore_errors: true #由于上条命令执行后的返回状态码不一定为0,所以为了防止非0报错并停止palsybook接下来的任务,所以使用ignore_errors忽略错误
- name: disable forever in config
lineinfile:
path: /etc/selinux/config
line: "SELINUX=disabled" #修改配置文件中的值,以便永久关闭
regexp: '^SELINUX=' #要修改的内容
注:ignore\_errors也经常结合block使用,因为在block级别上设置异常处理,可以处理block内部的所有错误。
playbook文件如下:
- name: Set Firewall
hosts: node
gather_facts: false
tasks:
- name: set iptables rule
shell: |
# 备份已有规则
iptables-save > /tmp/iptables.bak$(date +"%F-%T")
# 给它三板斧
iptables -X
iptables -F
iptables -Z
# 放行lo网卡和允许ping
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
# 放行关联和已建立连接的包,放行22、443、80端口
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
# 配置filter表的三链默认规则,INPUT链丢弃所有包
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
有时候为了服务器的安全,可能会去修改目标节点上sshd服务的默认配置,比如禁止root用户登录、禁止密码认证登录而只允许使用ssh密码认证等。
在修改服务的配置文件时,一般有几种方法:
相对来说,第三种方案是最统一、最易维护的方案。
此外,对于服务进程来说,修改了配置文件往往意味着要重启服务,使其加载新的配置文件,对于sshd也一样如此,但是sshd要比其他服务特殊一些,因为ansible默认基于ssh连接,重启sshd服务会使ansible连接断开,好在ansible默认会重试建立连接,无非是多等待几秒。但重建连接有可能会失败,比如修改了配置文件不允许重试、修改了sshd的监听端口等,这可能会使得ansible因连接失败而无法再继续执行后续任务。
所以,在修改sshd配置文件时,有如下建议:
- 将此任务作为初始化服务器的最后一个任务,即使连接失败也无所谓;
- 在playbook中加入连接失败的异常处理;
- 如果目标节点修改了sshd端口号,建议通过ansible自动或者我们手动去修改inventory文件中的ssh连接端口号。
这里为了简单,我准备使用lineinfile模块去修改配置文件,要修改的内容只有两项:
playbook内容如下:
[root@ansible roles]# cat sshd_config.yaml
---
- name: modify sshd_config
hosts: node
gather_facts: false
tasks:
# 1.备份/etc/ssh/sshd_config文件
- name: backup sshd config
shell:
/usr/bin/cp -f {{path}} {{path}}.bak
vars:
- path: /etc/ssh/sshd_config
# 2.设置PermitRootLogin no
- name: disable root login
lineinfile:
path: "/etc/ssh/sshd_config"
line: "PermitRootLogin no"
insertafter: "^#PermitRootLogin"
regexp: "^PermitRootLogin"
notify: "restart sshd"
# 3.设置PasswordAuthentication no
- name: disable password auth
lineinfile:
path: "/etc/ssh/sshd_config"
line: "PasswordAuthentication no"
regexp: "^PasswordAuthentication yes"
notify: "restart sshd"
handlers:
- name: "restart sshd"
service:
name: sshd
state: restarted
关于notify和handlers的作用如下:
ansible会监控playbook执行后的changed的状态,如果changed=1,则表示关注的状态发生了改变,即本次任务的执行不具备幂等性,如果changed=0,则表示本次任务要么没执行,要么执行了也没有影响,即本次任务具备幂等性。ansible提供了notify指令和handlers功能,如果在某个task中定义notify指令,当ansible在监控到该任务changed=1时,会触发该notify指令所定义的handler,然后去执行handler。所谓handler,其实就是task,无论是在写法上还是作用上它和task都没有什么区别,唯一的区别在于handler是被触发而被动执行的,不像普通task一样会按流程正常执行。
唯一需要注意的是,notify和handler中任务的名称必须一致。比如: notify: "restart sshd",那么handlers中必须得有一个任务设置了 name: "restart sshd"。
此外,在上面的playbook中,两个lineinfile任务都设置了相同的notify,但ansible不会多次去重启sshd,而是在最后重启一次。实际上,ansible在执行完某个任务之后,并不会立即去执行对应的handler,而是在当前play中所有普通任务都执行完成后再去执行handler,这样的好处是可以多次触发notify,但最后只执行一次对应的handler,从而避免多次重启。
这里将前面所有的playbook集合到单个playbook文件中去,这样就可以一次性执行所有任务。
整合后的playbook如下:
---
- name: Configure ssh Connection
hosts: node
gather_facts: false
connection: local
tasks:
- name: configure ssh connection
shell: |
ssh-keyscan {{inventory_hostname}} >>~/.ssh/known_hosts
sshpass -p'123.com' ssh-copy-id root@{{inventory_hostname}}
- name: Set Hostname
hosts: node
gather_facts: false
vars:
hostnames:
- host: 192.168.20.4
name: node01
- host: 192.168.20.5
name: node02
tasks:
- name: set hostname
hostname:
name: "{{item.name}}"
when: item.host == inventory_hostname
loop: "{{hostnames}}"
- name: Add DNS For Each
hosts: node
gather_facts: true
tasks:
- name: add DNS
lineinfile:
path: "/etc/hosts"
line: "{{item}} {{hostvars[item].ansible_hostname}}"
when: item != inventory_hostname
loop: "{{ play_hosts }}"
- name: Config Yum Repo And Install Software
hosts: node
gather_facts: false
tasks:
- name: backup origin yum repos
shell:
cmd: "mkdir bak; mv *.repo bak"
chdir: /etc/yum.repos.d
creates: /etc/yum.repos.d/bak
- name: add os repo and epel repo
yum_repository:
name: "{{item.name}}"
description: "{{item.name}} repo"
baseurl: "{{item.baseurl}}"
file: "{{item.name}}"
enabled: 1
gpgcheck: 0
reposdir: /etc/yum.repos.d
loop:
- name: os
baseurl: "https://mirrors.tuna.tsinghua.edu.cn/centos/7/os/$basearch"
- name: epel
baseurl: "https://mirrors.tuna.tsinghua.edu.cn/epel/7/$basearch"
- name: install pkgs
yum:
name: lrzsz,vim,dos2unix,wget,curl
state: present
- name: Sync Time
hosts: node
gather_facts: false
tasks:
- name: install and sync time
block:
- name: install ntpdate
yum:
name: ntpdate
state: present
- name: ntpdate to sync time
shell: |
ntpdate ntp1.aliyun.com
hwclock -w
- name: Disable Selinux
hosts: node
gather_facts: false
tasks:
- block:
- name: disable on the fly
shell: setenforce 0
- name: disable forever in config
lineinfile:
path: /etc/selinux/config
line: "SELINUX=disabled"
regexp: '^SELINUX='
ignore_errors: true
- name: Set Firewall
hosts: node
gather_facts: false
tasks:
- name: set iptables rule
shell: |
# 备份已有规则
iptables-save > /tmp/iptables.bak$(date +"%F-%T")
# 给它三板斧
iptables -X
iptables -F
iptables -Z
# 放行lo网卡和允许ping
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
# 放行关联和已建立连接的包,放行22、443、80端口
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
# 配置filter表的三链默认规则,INPUT链丢弃所有包
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
- name: Modify sshd_config
hosts: node
gather_facts: false
tasks:
- name: backup sshd config
shell:
/usr/bin/cp -f {{path}} {{path}}.bak
vars:
- path: /etc/ssh/sshd_config
- name: disable root login
lineinfile:
path: "/etc/ssh/sshd_config"
line: "PermitRootLogin no"
insertafter: "^#PermitRootLogin"
regexp: "^PermitRootLogin"
notify: "restart sshd"
- name: disable password auth
lineinfile:
path: "/etc/ssh/sshd_config"
line: "PasswordAuthentication no"
regexp: "^PasswordAuthentication yes"
notify: "restart sshd"
handlers:
- name: "restart sshd"
service:
name: sshd
state: restarted
按照以上步骤一步一步操作,你将轻松完成批量服务器的初始化。