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