ansible是在运维过程中必不可少的一个自动化工具了,它的实用性、重要性不必多说,几乎每一位运维人都要去掌握它,下面是我们在使用过程中的一些经验与分享。

首先说一下几个应用场景

场景一

设置hosts解析,当我们部署一个集群时,我们可能需要设置hosts文件,保证每一台机器通过hostname就可以互相连通,那我们如何结合ansible-playbook来完成呢?

首先我们不想去做重复的操作,我们在清单文件中设置好组名,填好IP地址,设置好每一个IP地址对应的hostname之后,就希望每一个主机都自动设置好/etc/hosts文件,有人可能会说,那还不简单,我这样设置就好了,啪啪啪,一顿操作如下:

先写一个最简单的playbook,目录结构如下:

# tree 
.
├── host
│   └── hosts
├── roles
│   └── test-server
│       ├── tasks
│       │   └── main.yaml
│       ├── files
│       │   ├── hosts
└── setup.yaml

来看一下这些文件都是如何写的

hosts清单文件

# cat host/hosts
[all-server]
192.168.1.221192.168.1.222 
[all:vars]
ansible_ssh_port=22
ansible_ssh_pass=root
ansible_ssh_user=root

tasks文件

# cat roles/test-server/tasks/main.yaml 
- name: Copy file
  file:
    src: hosts
    dest: /etc/hosts

templates文件

# cat roles/test-server/files/hosts
192.168.1.221 test01
192.168.1.222 test02

setup文件

# cat setup.yaml 
- hosts: all-server
  tags: nfs
  roles:
    - role: test-server

到这里,看起来思路异常的清晰,提前设置好hosts文件,把所有用到的IP和hostname都设置好,嗯,非常不错!但是我们是不是做了一些重复的工作呢?设置完清单文件,还有再手动设置hosts文件,啊啊啊啊,真的好繁琐啊,也low!还有没有更好的方法?当然有!

这里我们修改下清单文件的内容

# cat host/hosts
[all-server]
192.168.1.221 host_name=test01
192.168.1.222 host_name=test02
[all:vars]
ansible_ssh_port=22
ansible_ssh_pass=root
ansible_ssh_user=root

然后,我们使用ansible的模板功能,创建一个目录 templates,如下目录结构

# tree -d
.
├── host
└── roles
    └── test-server
        ├── tasks
        ├── templates

然后我们就可以写我们的模板文件了,不要眨眼!

# cat roles/test-server/templates/hosts.j2 
{% for host in groups['all-server'] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }} {{ hostvars[host]['host_name'] }}
{% endfor %}

OK!写完了,首先遍历"all-server"组里的所有主机,然后获取每一个主机的IP地址和每一个主机对应的host_name这个变量,分别对应的是

{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }} 和 {{ hostvars[host]['host_name'] }}

接下来修改下tasks那个文件
# cat roles/test-server/tasks/main.yaml 
- name: Copy file
  template:
    src: hosts.j2
    dest: /etc/hosts

欸,这样不管你设置了多少主机,他都会遍历整个组,是不是很方便!

场景二

前端时间我在写安装k8s集群的部署脚本时,遇到了一个问题,我们由于某些原因吧,使用的nodePort的方式,通过nginx做的代理来进行服务的访问,即nginx代理k8s集群的nodePort,nodeIP + nodePort这种形式,在进行设置nginx的upstream配置时,由于每个集群的数量规模是不一样的,所以在代理时需要把所有node节点的IP地址列一遍,类似于下面这种样子

upstream test {
    server 192.168.1.1:32001;
    server 192.168.1.2:32001;
    server 192.168.1.3:32001;
}

看起来就很麻烦,这样如果每次一个新的k8s集群部署时,都要再设置一遍这个就非常的麻烦,我之前的做法也是比较麻烦,后来有一天无意发现了遍历组里的主机和变量之后,就又做了修改,如下:

# cat roles/test-server/templates/upstream.j2 
{% for server in server_groups %}
upstream {{ server.name }} {
    {% if k8s_deploy_type == 'cluster' %}
    {% for host in groups['k8s-server'] %}
    {% if hostvars[host]['k8s_ha_role'] == 'node' %}
    server {{ hostvars[host]['inventory_hostname'] }}:{{ server.port }} weight=1  max_fails=3  fail_timeout=30s;
    {% endif %}
    {% endfor %}
    {% elif k8s_deploy_type == 'simple' %}
    {% for host in groups['k8s-single-server'] %}
    {% if hostvars[host]['k8s_role'] == 'node' %}
    server {{ hostvars[host]['inventory_hostname'] }}:{{ server.port }} weight=1  max_fails=3  fail_timeout=30s;
    {% endif %}
    {% endfor %}
    {% endif %}
}
{% endfor %}

不要觉着很麻烦,其实逻辑非常简单,因为k8s集群,根据业务场景,有时候需要部署单机版的,有时候需要部署高可用版的,所以我这里做了一层判断,所以清单文件和setup.yaml文件修改如下:

# cat host/hosts 
[all-server]
192.168.1.221 host_name=test01
192.168.1.222 host_name=test02

[k8s-server]
192.168.1.1 k8s_ha_role=master1
192.168.1.2 k8s_ha_role=master2
192.168.1.3 k8s_ha_role=master3
192.168.1.4 k8s_ha_role=node
192.168.1.5 k8s_ha_role=node
192.168.1.6 k8s_ha_role=node

[k8s-single-server]
192.168.1.1 k8s_role=master
192.168.1.2 k8s_role=node
192.168.1.3 k8s_role=node

[all:vars]
ansible_ssh_port=22
ansible_ssh_pass=root
ansible_ssh_user=root

setup.yaml文件参考

# cat setup.yaml 
- hosts: all-server
  tags: nfs
  roles:
    - role: test-server
      vars:
        k8s_deploy_type: cluster

到这里你可能还有一点疑惑,那就是{{ server.port }}这个是哪里来的?server_groups又是个什么鬼?别急,你是不是有很多的疑问?我们解决了node节点的IP问题,但是端口呢?我们的k8s集群,不可能只有一个nodePort吧,我们肯定有一堆的端口,所以不光要解决IP的问题,还要解决nodePort的问题,那么该如何解决呢?

由于nodePort我们根据具体的服务设置具体的端口,所以是可以提前写死的,那么根据这个情形,我们是不是可以设置一个专门存放upstream名和端口的组呢?

# cat roles/test-server/vars/main.yaml 
server_groups:
  - name: cloudweb
    port: 32028
  - name: cloudwebapi
    port: 32009
  - name: cloudupload
    port: 32037
  - name: rms-resource
    port: 32018
  - name: usermanage
    port: 32022
  - name: mbusermanage
    port: 32752
  - name: userapi
    port: 32021
  - name: mbuserapi
    port: 32750

走着,这里就是模板文件中的{% for server in server_groups %} ,先去迭代这个组,然后再去迭代清单文件里的组,然后得到nodeIP:nodePort这样的一个endports