系统城装机大师 - 固镇县祥瑞电脑科技销售部宣传站!

当前位置:首页 > 系统教程 > 其它教程 > 详细页面

ansible批量初始化服务器

时间:2020-03-05来源:电脑系统城作者:电脑系统城

本文要实现的初始化配置目标如下:

  • ansible配置ssh免密登录;
  • ansible远程配置主机名;
  • ansible控制远程主机互相添加DNS解析记录;
  • ansible配置远程主机上的yum镜像源以及安装一些软件;
  • ansible配置远程主机上的时间同步;
  • ansible关闭远程主机上的selinux;
  • ansible配置远程主机上的防火墙;
  • ansible远程修改sshd配置文件并重启sshd,使其更安全;

1、ansible要初始化的主机

1
2
3
4
[root@nginx ansible]# tail -3 /etc/ansible/hosts   #要初始的主机如下
[node]
192.168.20.4
192.168.20.5

2、配置ssh免密登录

playbook文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
[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}}
...

注:

  • gather_facts:如果值为false,则表示不收集目标主机上的节点信息,默认为true,为收集节点信息,如果收集节点信息,则会慢很多,如果在接下来的操作中,不需要节点上的信息,可设置为false。
  • connection:local表示在ansible端本地执行任务,hosts:localhost和connection:local容易搞混,虽然两者的效果都是在本地执行任务,但是hosts:localhost是从inventory中筛选出了目标节点localhost来执行任务,而connection:local则筛选出来执行任务的目标主机是node组中的节点,但因为指定了local连接类型,使得node组中有多少个节点,就会在ansible本地执行几次该play。

3、配置主机名

配置主机名可以使用shell模块,但是对于不太专业,ansible提供了一个专用于配置主机名的模块:hostname模块。

当然,要使用ansible去设置多个主机名,要求目标主机和目标名称已经关联好,否则多个主机和多个主机名之间无法对应去设置。

例如:分别设置node组中的两个节点主机名为node01和node02,playbook内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[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指令。

1)vars设置变量

vars指令可用于设置变量,可以设置一个或多个变量。下面几种方式都是合理的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 设置单个变量
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则无法访问。

例如:

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
[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

执行结果如下:

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
[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指令中:

1
2
3
4
5
6
vars:
  hostnames:
    - host: 192.168.20.4
      name: node01
    - host: 192.168.20.5
      name: node02

上面只设置了一个变量hostnames,但这个变量的值是一个数组结构,数组的两个元素又都是对象(字典/hash)结构。

所以想要访问主机名node01和它的IP地址192.168.20.4,可以:

1
2
3
4
5
tasks:
  - debug:
      var: hostnames[0].name
  - debug:
      var: hostnames[0].host

2)when条件判断

在ansible中,提供的唯一一个通用的条件判断是when指令,当when指令的值为true时,则执行该任务,否则不执行该任务。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[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的执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

4、互相添加DNS解析记录

1
2
3
4
5
6
7
8
9
10
11
12
[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 }}"

执行结果如下:

1
2
3
4
5
6
7
8
9
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)

5、配置yum镜像源并安装软件

需求如下:

  • 备份原有yum镜像源文件,并配置清华大学的yum镜像源:os源和epel源
  • 安装常用软件,包括lrzsz、dos2unix、wget、curl、vim等;

playbook如下:

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
[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等不会有什么问题,但是有些包名在不同的操作系统上是不一样的,这是需要注意的。

6、时间同步

保证时间同步可以避免很多玄学性的问题,特别是对集群中的节点。

通常会使用ntpd时间服务器来保证时间的同步,这里使用aliyun提供的时间服务器来保证时间同步,并将同步后的时间同步到硬件。

playbook文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
- 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更多的用于多个关联性任务之间的异常处理。

7、关闭selinux

关闭selinux的playbook如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[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内部的所有错误。

8、配置iptables规则

playbook文件如下:

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
- 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

9、远程修改sshd配置文件并重启

有时候为了服务器的安全,可能会去修改目标节点上sshd服务的默认配置,比如禁止root用户登录、禁止密码认证登录而只允许使用ssh密码认证等。

在修改服务的配置文件时,一般有几种方法:

  • 通过远程执行sed等命令进行修改配置文件;
  • 通过lineinfile模块去修改配置文件;
  • 在ansible本地段写好配置文件,然后使用copy模块或者template模块传输到目标节点上。

相对来说,第三种方案是最统一、最易维护的方案。

此外,对于服务进程来说,修改了配置文件往往意味着要重启服务,使其加载新的配置文件,对于sshd也一样如此,但是sshd要比其他服务特殊一些,因为ansible默认基于ssh连接,重启sshd服务会使ansible连接断开,好在ansible默认会重试建立连接,无非是多等待几秒。但重建连接有可能会失败,比如修改了配置文件不允许重试、修改了sshd的监听端口等,这可能会使得ansible因连接失败而无法再继续执行后续任务。

所以,在修改sshd配置文件时,有如下建议:

  • 将此任务作为初始化服务器的最后一个任务,即使连接失败也无所谓;
  • 在playbook中加入连接失败的异常处理;
  • 如果目标节点修改了sshd端口号,建议通过ansible自动或者我们手动去修改inventory文件中的ssh连接端口号。

这里为了简单,我准备使用lineinfile模块去修改配置文件,要修改的内容只有两项:

  • 将PermitRootLogin指令设置为no,禁止root用户直接登录;
  • 将PasswordAuthentication指令设置为no,不允许使用密码认证的方式登录

playbook内容如下:

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
[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,从而避免多次重启。

10、整合所有任务到单个playbook中

这里将前面所有的playbook集合到单个playbook文件中去,这样就可以一次性执行所有任务。

整合后的playbook如下:

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
---
- 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

-------------本文结束感谢您的阅读-------------
请作者喝杯咖啡。
分享到:

相关信息

  • ThinkPad蓝牙鼠标如何配对

    ThinkPad蓝牙鼠标如何配对解答步骤41U5008鼠标驱动官网地址: https://support.lenovo.com/en_US/downloads/detail.page?&LegacyDocID=MIGR-67201 第一种方式是比较传统的:使...

    2024-04-11

  • USB接口无法识别设备的解决方法

    故障现象: USB设备U盘、移动硬盘等插入后提示无法识别的设备,确认设备本身正常,设备可加电,或插入设备后加电但无任何反应,无法使用。新型号机器多表现为黄色USB接口存在此问题,...

    2024-04-11

系统教程栏目

栏目热门教程

人气教程排行

站长推荐

热门系统下载