12月技术周 | Rocky Octavia 的实现与分析(二):LoadBalancer 创建流程


前文列表

《OpenStack Rocky Octavia 的实现与分析(一)架构简介》

基础概念回顾

12月技术周 | Rocky Octavia 的实现与分析(二):LoadBalancer 创建流程

图1. Octavia 网络架构图

12月技术周 | Rocky Octavia 的实现与分析(二):LoadBalancer 创建流程

图2. Octavia 软件架构图

LoadBalancer:负载均衡服务的根对象,用户对负载均衡的定义、配置和操作都基于此。 

VIP:与 LoadBalancer 关联的虚拟 IP 地址,每个 LoadBalancer 最起码有一个 VIP 作为外部对后端业务集群访问的标准入口。

Amphora(e):实体为云主机,作为负载均衡器软件(HAProxy)和高可用支撑(Keepalived)的运行载体,通过 Agent 与 Octavia Services 通信,也是 Octavia 的 Default Loadbalancer Provider。

Create LoadBalancer 的实现与分析

紧接前文,Create LoadBalancer 是 Octavia 的典型实现,作为主干贯穿了 Octavia 的核心技术堆栈,非常适合作为掌握 Octavia 代码实现规范的切入点。

从指令行看起

  1. $ openstack loadbalancer create help

  2. usage: openstack loadbalancer create [-h] [-f {json,shell,table,value,yaml}]

  3.                                     [-c COLUMN] [--max-width <integer>]

  4.                                     [--fit-width] [--print-empty]

  5.                                     [--noindent] [--prefix PREFIX]

  6.                                     [--name <name>]

  7.                                     [--description <description>]

  8.                                     [--vip-address <vip_address>]

  9.                                     [--vip-port-id <vip_port_id>]

  10.                                     [--vip-subnet-id <vip_subnet_id>]

  11.                                     [--vip-network-id <vip_network_id>]

  12.                                     [--vip-qos-policy-id <vip_qos_policy_id>]

  13.                                     [--project <project>]

  14.                                     [--enable | --disable]

  • –vip-network-id/–vip-subnet-id/–vip-port-id:VIP 的地址源

  • –vip-address:具体的 VIP 地址

  • –vip-qos-policy-id:VIP 的 Qos Policy

  • –name:友好的 loadbalancer 名称

CLI Example

  1. $ openstack loadbalancer create --vip-subnet-id lb-vip-subnet --name lb1

Request Example

  1. POST /v2.0/lbaas/loadbalancers


  2. {

  3.  "loadbalancer": {

  4.    "vip_subnet_id": "c55e7725-894c-400e-bd00-57a04ae1e676",

  5.    "name": "lb1",

  6.    "admin_state_up": true

  7.  }

  8. }

Octavia API

12月技术周 | Rocky Octavia 的实现与分析(二):LoadBalancer 创建流程

图3. Create LB 在 Octavia API 的 UML 图

Octavia API 接收请求后执行下列流程

  1. 请求鉴权,判断用户是否有权限执行 loadbalancer 的创建。

  2. 验证 VIP 及其相关对象(e.g. port, subnet, network)是否可用,这里可以通过config secition [networking]来配置 Allow/disallow specific network object types when creating VIPs.

  3. 检查用户 Project 的 LB Quota,可以通过 config section [quotas] 来配置默认 Quota(e.g. 指定 Project1 只能创建 3 个 loadbalancer)。

  4. 准备 loadbalancer 的数据对象

  5. 创建 table load_balancer 和 vip 的数据库记录。

  6. 创建 VIP 对应的 port,并将 Port、VIP、LB 三者的数据库记录关联起来。

  7. 以 Graph flow(图流)的方式创建 loadbalancer 下属的 Listeners 和 Pools。

  8. 准备用于传递到 create loadbalancer flow 的 stores 数据。

  9. 异步调用 octavia-worker service 执行 create loadbalancer flow。

NOTE

  • loadbalancer 相关的 quota 依旧是通过指令 openstack quota set 设定。

  • 如果 VIP 的 Port 不存在,则首先会调用 neutronclient 在 vip-net 创建名为octavia-lb-<loadbalancer_uuid> 的 Port。

  • 支持 Creating a Fully Populated Load Balancer,即一次性创建包括 Listenr、Pool 和 Members 在内的所有对象。

Octavia Controller Worker

Octavia Controller Worker 接收到 Octavia API 丢过来的消息后首先进入一层 TaskFlow 封装:

12月技术周 | Rocky Octavia 的实现与分析(二):LoadBalancer 创建流程

图4. Create LB 在 Octavia Controller Worker 的 UML 图

  1. 获取待创建的 loadbalancer 数据库记录

  2. 准备 flow store data

  3. 获取 create loadbalancer flow

  4. 生成执行 flow 的 engine

  5. 执行 flow

不妨先插入一下 TaskFlow 的介绍。TaskFlow 脱胎于 OpenStack,为了解决 OpenStack 中的长业务流程的执行可靠性和数据一致性问题。典型的应用场景就是 Cinder create volume。

TaskFlow is a Python library that helps to make task execution easy, consistent and reliable. A library to do [jobs, tasks, flows] in a highly available, easy to understand and declarative manner (and more!) to be used with OpenStack and other projects.

简而言之,将多个 Tasks 以一种特定的 Flow(线性流、并行流、图流)执行起来,就是 Task-Flow。

从 Octavia 的软件架构可以看出,它需要统筹 Nova、Neutron、Barbican 等多个项目的资源对象状态机,TaskFlow 非常契合其自身的需求,所以 Octavia 的主干流程基本上全是 TaskFlow 实现。

12月技术周 | Rocky Octavia 的实现与分析(二):LoadBalancer 创建流程

图5. Get create loadbalancer flow 的 UML 图

上图可见 Create loadbalancer flow 的关键是 Step 3, 4

  • create loadbalancer topology

  • create networking for amphora(e)

翻译过来就是要解决两个问题

  1. 如何为 loadbalancer 准备 amphorae?

  2. 如何将 amphorae 接入 vip-net?

12月技术周 | Rocky Octavia 的实现与分析(二):LoadBalancer 创建流程

图6. Create amphora topology 的 UML 图

先解释第一个问题,create loadbalancer flow 会根据 loadbalancer topology 来准备 amphorae。所谓 loadbalancer topology 指的是 amphorae 的高可用拓扑,现支持以下两种类型:

  • SINGLE:单节点的 amphora,不具备高可用性,不建议在生产环境中使用;

  • ACTIVE_STANDBY:双节点 amphorae,实现了 Keepalived Master/Backend 主从模式

PS:本文不讨论 SINGLE topology。

需要注意的是并非每次 create loadbalancer 都需要即时新建 amphora,Octavia 支持 直接从 space amphora pool 获取 amphorae 避免浪费时间。space amphora pool 由 Housekeeping Manager 维护,根据配置 [house_keeping]spare_amphora_pool_size 设定。当没有 space amphora 时就会进入 create amphora for lb subflow,新建 amphora 虚拟机,在图5中亦有描述,这里不再多说。

NOTE

  • 如果 loadbalancer topology 为 ACTIVE_STANDBY,同时还可以配置 [nova]enable_anti_affinity=True 应用 Nova 的反亲和性来进一步提升高可用性。

  • 图 5 中的 amphora for loadbalancer flow 是一个 graph flow(图流),其特点是 flow 的流向可定义,正如 Set the flow direction judgment condition of graph flow 中的 amp_for_lb_flow.link

再解释第二个问题。之前我们提到过 amphorae 最初只挂靠在 lb-mgmt-net,当 amphorae 被分配到 loadbalancer 并且 lb-mgmt-net 与 vip-net 不是同一个网络时,还需要将 amphorae 挂靠到 vip-net 上。在 octavia-api service 创建的 octavia-lb-<loadbalancer_uuid> Port 此时被用上了。而且如果采用了 ACTIVESTANDBY topology,则还会在 vip-net 上创建两个 octavia-lb-vrrp-<amphora_uuid> VRRP ports 分别挂载到两个 amphorae 作为 Keepalived VIP 漂移的 Interfaces。

12月技术周 | Rocky Octavia 的实现与分析(二):LoadBalancer 创建流程

图7. Create networking for amphorae 的 UML 图

罗列 create networking for amphorae 的几个关键任务

  • network_tasks.AllocateVIP

  • network_tasks.PlugVIP

  • amphora_driver_tasks.AmphoraePostVIPPlug

  • amphora_driver_tasks.AmphoraVRRPUpdate

  • amphora_driver_tasks.AmphoraVRRPStart

AllocateVIP

AllocateVIP 负责分配 VIP 和确保 VIP Port 存在,返回一个关联了 Port、VIP 和 LB 三者的 data object,之后再由任务 UpdateAmphoraVIPData 将 data object 落库持久化。

PlugVIP

PlugVIP 负责将 VIP 插入到 amphorae。

12月技术周 | Rocky Octavia 的实现与分析(二):LoadBalancer 创建流程

图8. PlugVIP 的 UML 图

PlugVIP 主要做了两件事情

  1. Update VIP Port 的 security group rules。因为 Listener 是依附于 VIP 的,所以 VIP 的安全组规则也应该是动态更新的。例如:为 loadbalancer 添加了一个 HTTP:8080 的 Listener,则会在相应的 VIP 上 Upload ingress HTTP:8080 规则。

  2. 轮询检查 amphorae 是否具备当前所需要的 Ports,如果没有则调用 neutronclient 创建出来,再调用 novaclient 挂载。

在经过 AllocateVIP 和 PlugVIP 后就完成了外部网络资源的准备,接下来的任务会进入到 amphorae 的内部实现。

AmphoraePostVIPPlug

AmphoraePostVIPPlug 负责将 VIP Port 的 interface infos 注入到 amphorae 的 network namespace 中。实际上,该任务通过 Amphora Driver 封装的 AmphoraAPIClient 发送 PUT plug/vip/{vip} 请求到 amphora-agent REST API,执行更新网卡配置文件和添加路由规则,具体实现为 Plug:plug_vip method。

初始状态下的 amphora 只有一个用于与 lb-mgmt-net 通信的 interface:

  1. root@amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77:~# ifconfig

  2. ens3      Link encap:Ethernet  HWaddr fa:16:3e:b6:8f:a5

  3.          inet addr:192.168.0.9  Bcast:192.168.0.255  Mask:255.255.255.0

  4.          inet6 addr: fe80::f816:3eff:feb6:8fa5/64 Scope:Link

  5.          UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1

  6.          RX packets:19462 errors:14099 dropped:0 overruns:0 frame:14099

  7.          TX packets:70317 errors:0 dropped:0 overruns:0 carrier:0

  8.          collisions:0 txqueuelen:1000

  9.          RX bytes:1350041 (1.3 MB)  TX bytes:15533572 (15.5 MB)


  10. lo        Link encap:Local Loopback

  11.          inet addr:127.0.0.1  Mask:255.0.0.0

  12.          inet6 addr: ::1/128 Scope:Host

  13.          UP LOOPBACK RUNNING  MTU:65536  Metric:1

  14.          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

  15.          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

  16.          collisions:0 txqueuelen:1

  17.          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

被分配到 loadbalancer 后会创建 network namespace amphora-haproxy,并添加一个 vrrp_port interface 充当 Keepalived 的虚拟路由,一般是 eth1。

  1. root@amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77:~# ip netns exec amphora-haproxy ifconfig

  2. eth1      Link encap:Ethernet  HWaddr fa:16:3e:f4:69:4b

  3.          inet addr:172.16.1.3  Bcast:172.16.1.255  Mask:255.255.255.0

  4.          inet6 addr: fe80::f816:3eff:fef4:694b/64 Scope:Link

  5.          UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1

  6.          RX packets:12705 errors:0 dropped:0 overruns:0 frame:0

  7.          TX packets:613211 errors:0 dropped:0 overruns:0 carrier:0

  8.          collisions:0 txqueuelen:1000

  9.          RX bytes:762300 (762.3 KB)  TX bytes:36792968 (36.7 MB)


  10. eth1:0    Link encap:Ethernet  HWaddr fa:16:3e:f4:69:4b

  11.          inet addr:172.16.1.10  Bcast:172.16.1.255  Mask:255.255.255.0

  12.          UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1

eth1 和 eth1:0 分别对应 vip-network 的 octavia-lb-vrrp-<amphora_uuid>octavia-lb-<loadbalancer_uuid> ports。网卡配置内容如下:

  1. root@amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77:~# ip netns exec amphora-haproxy cat /etc/network/interfaces.d/eth1

  2. auto eth1

  3. iface eth1 inet dhcp

  4. root@amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77:~# ip netns exec amphora-haproxy cat /etc/network/interfaces.d/eth1.cfg


  5. # Generated by Octavia agent

  6. auto eth1 eth1:0

  7. iface eth1 inet static

  8. address 172.16.1.3

  9. broadcast 172.16.1.255

  10. netmask 255.255.255.0

  11. gateway 172.16.1.1

  12. mtu 1450


  13. iface eth1:0 inet static

  14. address 172.16.1.10

  15. broadcast 172.16.1.255

  16. netmask 255.255.255.0

  17. # Add a source routing table to allow members to access the VIP

  18. post-up /sbin/ip route add 172.16.1.0/24 dev eth1 src 172.16.1.10 scope link table 1

  19. post-up /sbin/ip route add default via 172.16.1.1 dev eth1 onlink table 1

  20. post-down /sbin/ip route del default via 172.16.1.1 dev eth1 onlink table 1

  21. post-down /sbin/ip route del 172.16.1.0/24 dev eth1 src 172.16.1.10 scope link table 1

  22. post-up /sbin/ip rule add from 172.16.1.10/32 table 1 priority 100

  23. post-down /sbin/ip rule del from 172.16.1.10/32 table 1 priority 100

  24. post-up /sbin/iptables -t nat -A POSTROUTING -p udp -o eth1 -j MASQUERADE

  25. post-down /sbin/iptables -t nat -D POSTROUTING -p udp -o eth1 -j MASQUERADE

AmphoraVRRPUpdate

AmphoraVRRPUpdate 负责生成、更新 Keepalived 的配置文件。将上述 Tasks 准备的 VIP port、VRRP_ports 网络信息渲染到 keepalived.conf Jinja 模板,然后调用 AmphoraAPIClient 发送 PUT vrrp/upload 请求到 amphora-agent 更新 Keepalived 配置文件。

NOTE:只有当 loadbalancer_topology=ACTIVE_STANDBY 时才会启用 Keepalived 提供高可用服务。

配置文件的内容:

  1. # file: /var/lib/octavia/vrrp/octavia-keepalived.conf


  2. vrrp_script check_script {

  3.  script /var/lib/octavia/vrrp/check_script.sh   # VRRP check

  4.  interval 5

  5.  fall 2

  6.  rise 2

  7. }


  8. vrrp_instance 01197be798d5440da846cd70f52dc503 { # VRRP instance name is loadbalancer UUID

  9.  state MASTER                                   # Master router

  10.  interface eth1                                 # VRRP IP device

  11.  virtual_router_id 1                            # VRID

  12.  priority 100

  13.  nopreempt

  14.  garp_master_refresh 5

  15.  garp_master_refresh_repeat 2

  16.  advert_int 1

  17.  authentication {

  18.    auth_type PASS

  19.    auth_pass b76d77e

  20.  }


  21.  unicast_src_ip 172.16.1.3                      # VRRP IP

  22.  unicast_peer {

  23.    172.16.1.7                                   # Backup router VRRP IP

  24.  }


  25.  virtual_ipaddress {

  26.    172.16.1.10                                  # VIP address

  27.  }

  28.  track_script {

  29.    check_script

  30.  }

  31. }

可见 keepalived 使用了 eth1 作为 VRRP IP 和 VIP 的 interface,其中脚本 check_script.sh 用于检查运行在 amphorae 中的 HAProxy 服务健康状况,以此作为 VIP 漂移的判断依据,下述的脚本 haproxy-vrrp-check 依旧由 Octavia 提供。

  1. root@amphora-caa6ba0f-1a68-4f22-9be9-8521695ac4f4:~# cat /var/lib/octavia/vrrp/check_scripts/haproxy_check_script.sh

  2. haproxy-vrrp-check /var/lib/octavia/d367b5ec-24dd-44b3-b947-e0ff72c75e66.sock; exit $?

AmphoraVRRPStart

AmphoraVRRPStart 负责启动 Keepalived 服务进程。通过调用 AmphoraAPIClient 发送 PUT vrrp/start 请求执行 amphora-agent 的视图函数 manage_service_vrrp(action=start)

  1. # file: /opt/rocky/octavia/octavia/amphorae/backends/agent/api_server/keepalived.py


  2.    def manager_keepalived_service(self, action):

  3.        action = action.lower()

  4.        if action not in [consts.AMP_ACTION_START,

  5.                          consts.AMP_ACTION_STOP,

  6.                          consts.AMP_ACTION_RELOAD]:

  7.            return webob.Response(json=dict(

  8.                message='Invalid Request',

  9.                details="Unknown action: {0}".format(action)), status=400)


  10.        if action == consts.AMP_ACTION_START:

  11.            keepalived_pid_path = util.keepalived_pid_path()

  12.            try:

  13.                # Is there a pid file for keepalived?

  14.                with open(keepalived_pid_path, 'r') as pid_file:

  15.                    pid = int(pid_file.readline())

  16.                os.kill(pid, 0)


  17.                # If we got here, it means the keepalived process is running.

  18.                # We should reload it instead of trying to start it again.

  19.                action = consts.AMP_ACTION_RELOAD

  20.            except (IOError, OSError):

  21.                pass


  22.        cmd = ("/usr/sbin/service octavia-keepalived {action}".format(

  23.            action=action))


  24.        try:

  25.            subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)

  26.        except subprocess.CalledProcessError as e:

  27.            LOG.debug('Failed to %s octavia-keepalived service: %s %s',

  28.                      action, e, e.output)

  29.            return webob.Response(json=dict(

  30.                message="Failed to {0} octavia-keepalived service".format(

  31.                    action), details=e.output), status=500)


  32.        return webob.Response(

  33.            json=dict(message='OK',

  34.                      details='keepalived {action}ed'.format(action=action)),

  35.            status=202)

可见,amphora-agent 是通过执行指令 /usr/sbin/service octavia-keepalived start 来启动 keepalived 服务的,继续看 octavia-keepalived.service 配置内容:

  1. # file: /usr/lib/systemd/system/octavia-keepalived.service


  2. [Unit]

  3. Description=Keepalive Daemon (LVS and VRRP)

  4. After=network-online.target .service

  5. Wants=network-online.target

  6. Requires=.service


  7. [Service]

  8. # Force context as we start keepalived under "ip netns exec"

  9. SELinuxContext=system_u:system_r:keepalived_t:s0

  10. Type=forking

  11. KillMode=process


  12. ExecStart=/sbin/ip netns exec amphora-haproxy /usr/sbin/keepalived  -D -d -f /var/lib/octavia/vrrp/octavia-keepalived.conf -p /var/lib/octavia/vrrp/octavia-keepalived.pid


  13. ExecReload=/bin/kill -HUP $MAINPID

  14. PIDFile=/var/lib/octavia/vrrp/octavia-keepalived.pid


  15. [Install]

  16. WantedBy=multi-user.target

从配置可知:

  1. 真正的 keepalived 服务进程同样启动在 namespace amphora-haproxy。

  2. keepalived 的配置文件路劲为 /var/lib/octavia/vrrp/octavia-keepalived.conf

最后

最后小结一下 Create LoadBalancer 的流程:

  1. 根据 loadbalancer topology 为 loadbalancer 分配 amphorae。

  2. 为 loadbalancer 在 vip-net 上准备 VIP Port,并根据 loadbalancer topology 准备 VRRP Ports。

  3. 将 VIP Port 和 VRRP Ports 挂载到 amphorae。

  4. 将这些 Ports 的网络信息注入到 amphorae 的 namespace 中。

  5. 根据 loadbalancer topology 和这些网络信息生成 keepalived 的配置文件

  6. 启动 keepalived 服务进程

掌握 Create LoadBalancer 最关键莫过于要结合 Octavia 的网络架构,理解 lb-mgmt-net、vip-net 及 amphorae 之间的关系;同时要结合 Octavia 的软件结构,理解底层 Drivers 和上层功能模块之间的调用与封装。

下一篇《OpenStack Rocky Octavia 的实现与分析(三)Listener 创建流程》即将更新,敬请期待!


关于九州云99Cloud

九州云成立于2012年,是中国早期从事OpenStack和相关开源服务的专业公司。公司成立六年,秉承“开源 · 赋能变革”的理念,不断夯实自身实力,已实现业务从核心云到边缘云再到垂直行业应用的拓展和积累,先后为政府、金融、运营商、能源、制造业、商业、交通、物流、教育、医疗等各大行业的企业级客户提供高质量的云计算服务。目前拥有国家电网、中国人民银行、中国银联、中国移动、中国电信、中国联通、中国资源卫星、eBay、国际陆港集团、中国人寿、万达信息、东风汽车、诺基亚等众多重量级客户,被用户认可为开源界的领军企业。

12月技术周 | Rocky Octavia 的实现与分析(二):LoadBalancer 创建流程