本文要实现的初始化配置目标以下:node
- ansible配置ssh免密登陆;
- ansible远程配置主机名;
- ansible控制远程主机互相添加DNS解析记录;
- ansible配置远程主机上的yum镜像源以及安装一些软件;
- ansible配置远程主机上的时间同步;
- ansible关闭远程主机上的selinux;
- ansible配置远程主机上的防火墙;
- ansible远程修改sshd配置文件并重启sshd,使其更安全;
[root@nginx ansible]# tail -3 /etc/ansible/hosts #要初始的主机以下 [node] 192.168.20.4 192.168.20.5
playbook文件内容以下:linux
[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}} ...
注:nginx
配置主机名可使用shell模块,可是对于不太专业,ansible提供了一个专用于配置主机名的模块:hostname模块。shell
固然,要使用ansible去设置多个主机名,要求目标主机和目标名称已经关联好,不然多个主机和多个主机名之间没法对应去设置。ubuntu
例如:分别设置node组中的两个节点主机名为node01和node02,playbook内容以下:vim
[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指令。centos
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 # 放行关联和已创建链接的包,放行2二、44三、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密码认证等。
在修改服务的配置文件时,通常有几种方法:
相对来讲,第三种方案是最统1、最易维护的方案。
此外,对于服务进程来讲,修改了配置文件每每意味着要重启服务,使其加载新的配置文件,对于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 # 放行关联和已创建链接的包,放行2二、44三、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