ECS 上使用 vip 的解决方案

背景介绍

在阿里云部署 Kubernetes,需要为 API-Server 做 HA,第一个想到的是使用 keepalived + haproxy,但是众所周知,公有云不支持组播。当然,如果想省心,直接使用阿里云的托管服务。

方法
  • 使用公有云的 SLB
  • 脚本调用阿里云的 API,直接使用单播
ENI 介绍

弹性网卡(Elastic Network Interface,简称ENI)是一种可以附加到专有网络VPC类型ECS实例上的虚拟网卡。通过弹性网卡,您可以在任何阿里云地域下实现高可用集群搭建、低成本故障转移和精细化的网络管理。

创建 ENI
授权 ESC 权限
安装 Python 模块
1
pip3 install aliyun-python-sdk-core aliyun-python-sdk-ecs
利用 Python 控制 ENI
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
#!/usr/bin/env python3
#coding=utf-8
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.acs_exception.exceptions import ClientException
from aliyunsdkcore.acs_exception.exceptions import ServerException
from aliyunsdkecs.request.v20140526.AttachNetworkInterfaceRequest import AttachNetworkInterfaceRequest
from aliyunsdkecs.request.v20140526.DetachNetworkInterfaceRequest import DetachNetworkInterfaceRequest
import sys
from time import sleep

AccessKeyId = 'AccessKeyId'
AccessKeySecret = 'AccessKeySecret'
Endpoint = 'cn-qingdao'
remove_ecs_id = sys.argv[1] #传递移除网卡的ecs_id
ENI_ID = 'xxxx' #弹性辅助网卡ENI的id
ECS_ID_LIST = ['xxxx','xxxxx'] #ENI网卡绑定的ecs集群列表
try:
ECS_ID_LIST.remove(remove_ecs_id)
except Exception as e:
print(e)
exit(1)

add_ecs_id = ECS_ID_LIST[0] #选一个可以用的ecs的id,做为网卡绑定的服务器


def remove_vip(remove_ecs_id,ENI_ID):
'''解绑函数'''
client = AcsClient(AccessKeyId,AccessKeySecret,Endpoint) #建议不要使用变量,直接输入字符串
request = DetachNetworkInterfaceRequest()
request.set_accept_format('json')
request.set_InstanceId(remove_ecs_id)
request.set_NetworkInterfaceId(ENI_ID)
try:
response = client.do_action_with_exception(request)
except Exception as e:
print(e)
exit(2)
print(str(response, encoding='utf-8'))
return True

def add_vip(add_ecs_id,ENI_ID):
'''绑定函数'''
client = AcsClient(AccessKeyId,AccessKeySecret,Endpoint) #建议不要使用变量,直接输入字符串
request = AttachNetworkInterfaceRequest()
request.set_accept_format('json')
request.set_NetworkInterfaceId(ENI_ID)
request.set_InstanceId(add_ecs_id)
try:
response = client.do_action_with_exception(request)
except Exception as e:
print(e)
exit(3)
print(str(response, encoding='utf-8'))

if __name__ == '__main__':
ret = remove_vip(remove_ecs_id,ENI_ID)
if ret:
add_vip(add_ecs_id,ENI_ID)
配置 keepalived 单播模式

主配置

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
! Configuration File for keepalived

global_defs {
notification_email {
[email protected] #发送邮箱
}
notification_email_from [email protected] #邮箱地址
smtp_server 127.0.0.1 #邮件服务器地址
smtp_connect_timeout 30
router_id LVS_DEVEL-01 #主机名,每个节点不同即可
}

vrrp_instance VI_1 {
state MASTER #节点主从,在另一个节点上为BACKUP
interface eth0 #IP地址漂移到的网卡
virtual_router_id 51 #多个节点必须相同
priority 100 #优先级,备用节点的值必须低于主节点的值
advert_int 1 #通告间隔1秒
authentication {
auth_type PASS #预共享密钥认证
auth_pass 1111 #密钥
}

unicast_src_ip 172.31.0.204 #本机地址
unicast_peer {
172.31.0.205 #目标地址
}

virtual_ipaddress {
192.168.1.100/24 dev eth0 #VIP地址
}
}

从配置

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
! Configuration File for keepalived

global_defs {
notification_email {
[email protected]
}
notification_email_from [email protected]
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id LVS_DEVEL-02
}

vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}

unicast_src_ip 172.31.0.205
unicast_peer {
172.31.0.204
}

virtual_ipaddress {
192.168.1.100/24 dev eth0
}
}

检查脚本

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
#!/bin/bash

source /etc/init.d/functions

vip=192.168.1.100 #虚拟vip
local_ip=172.31.0.204 #本机ip
rsync_module_ip=172.31.0.205 #rsync目标的ip
ecs_id_file=/tmp/ecs_id.txt #保存弹性网卡所在的ecs的id名称(ecsID)

declare -A arr
arr["172.31.0.204"]="ecs的id" #内网ip作为key,ecs的id作为value
arr["172.31.0.205"]="ecs的id"

if [ `ip a s eth0|egrep -o ${vip}|wc -l` -eq 1 ];then #判断是否拥有vip
if [ `cat ${ecs_id_file}|wc -l` -eq 1 ];then #判断文件是否有内容
if `cat ${ecs_id_file}` != ${arr[${local_ip}]};then #判断ecs_id_file文件中记录的ecsID和本机的ecsID是否相同
python3 ENI.py ${arr[${rsync_module_ip}]} #执行脚本(python控制ENI的脚本)
echo ${arr[${local_ip}]} > ${ecs_id_file}
rsync -avz ${ecs_id_file} [email protected]${rsync_module_ip}::aliyun_eni_module --password-file=/etc/rsync.password
else
rsync -avz ${ecs_id_file} [email protected]${rsync_module_ip}::aliyun_eni_module --password-file=/etc/rsync.password

fi
else
# unset arr[${local_ip}] #删除arr中的本机的变量内容
python3 ENI.py ${arr[${rsync_module_ip}]} #执行脚本(python控制ENI的脚本)
if [ $? -eq 0 ];then #判断执行是否失败
#脚本执行成功,覆盖文件内容
echo ${arr[${local_ip}]} > ${ecs_id_file}
#rsync覆盖给另一台服务器的ecs_id_file文件(建议使用rsync的demon)
rsync -avz ${ecs_id_file} [email protected]${rsync_module_ip}::aliyun_eni_module --password-file=/etc/rsync.password
else
#执行失败了操作
action "python3 scripts carried out Fail ${rsync_module_ip}" /bin/false


fi
fi
fi

-------------本文结束感谢您的阅读-------------