[ English | Indonesia | русский ]

HAProxy 和 Keepalived 在 LXC 容器中

在某些情况下,您可能希望在 LXC 容器中运行 HAProxy 和 Keepalived。例如,在裸机上运行这些服务假定应为主机设置指向公共网络的默认路由。对于某些部署而言,这种方案可能不太理想,尤其是在您没有独立的负载平衡主机,而是将它们与其它基础设施服务共置的情况下。

库存覆盖

为了让 dynamic_inventory 生成用于 HAProxy 的容器集,您需要在 /etc/openstack_deploy/env.d/haproxy.yml 文件中创建以下内容

---
# This file contains an example to show how to set
# the cinder-volume service to run in a container.
#
# Important note:
# In most cases you need to ensure that default route inside of the
# container doesn't go through eth0, which is part of lxcbr0 and
# SRC nat-ed. You need to pass "public" VIP interface inside of the
# container and ensure "default" route presence on it.

container_skel:
  haproxy_container:
    properties:
      is_metal: false

定义主机网络

为了使公共网络可用,您需要确保在主机上有一个对应的桥接,HAProxy 容器将通过一个 veth 对的一侧连接到该桥接。该桥接还应包含一个提供“公共”连接性的 VLAN 接口。

您可以手动创建桥接,也可以利用我们的 systemd_networkd 角色,该角色能够配置主机上所需的网络。

对于下面的示例,让我们将我们的桥接命名为 br-public-api,公共 VLAN ID 为 40。在您的 user_variables.yml 文件中定义以下变量

_systemd_networkd_generic_devices:
  - NetDev:
      Name: bond0
      Kind: bond
    Bond:
      Mode: 802.3ad
      TransmitHashPolicy: layer3+4
      LACPTransmitRate: fast
      MIIMonitorSec: 100
    filename: 05-generic-bond0

_systemd_networkd_public_api_devices:
  - NetDev:
      Name: vlan-public-api
      Kind: vlan
    VLAN:
      Id: 40
    filename: 10-openstack-vlan-public-api
  - NetDev:
      Name: br-public-api
      Kind: bridge
    Bridge:
      ForwardDelaySec: 0
      HelloTimeSec: 2
      MaxAgeSec: 12
      STP: off
    filename: 11-openstack-br-public-api

openstack_hosts_systemd_networkd_devices: |-
  {% set devices = [] %}
  {% if is_metal %}
  {%   set _ = devices.extend(_systemd_networkd_generic_devices) %}
  {%   if inventory_hostname in groups['haproxy_hosts'] %}
  {%     set _ = devices.extend(_systemd_networkd_public_api_devices) %}
  {%   endif %}
  {% endif %}
  {{ devices }}

_systemd_networkd_bonded_networks:
  - interface: ens3
    filename: 05-generic-ens3
    bond: bond0
    link_config_overrides:
      Match:
        MACAddress: df:25:83:e1:77:c8
  - interface: ens6
    filename: 05-generic-ens6
    bond: bond0
    link_config_overrides:
      Match:
        MACAddress: df:25:83:e1:77:c9
  - interface: bond0
    filename: 05-general-bond0
    vlan:
      - vlan-public-api

_systemd_networkd_public_api_networks:
  - interface: "vlan-public-api"
    bridge: "br-public-api"
    filename: 10-openstack-vlan-public-api
  - interface: "br-public-api"
    filename: "11-openstack-br-public-api"

openstack_hosts_systemd_networkd_networks: |-
  {% set networks = [] %}
  {% if is_metal %}
  {%   set _ = networks.extend(_systemd_networkd_bonded_networks) %}
  {%   if inventory_hostname in groups['haproxy_hosts'] %}
  {%     set _ = networks.extend(_systemd_networkd_public_api_networks) %}
  {%   endif %}
  {% endif %}
  {{ networks }}

定义容器网络

在 LXC 中部署 HAProxy 时,您需要确保与公共网络的连接,并且 haproxy_bind_external_lb_vip_address 存在于容器内部,同时 external_lb_vip_address 可访问。

为此,我们需要在 openstack_user_config.yml 文件中进行以下一系列更改。

  1. cidr_networks 中,添加一个应作为访问 API 的“公共”网络的网络。例如,我们将使用 203.0.113.128/28

    cidr_networks:
      ...
      public_api: 203.0.113.128/28
    
  2. used_ips 中,您需要为您的网关和 haproxy_keepalived_external_vip_cidr/external_lb_vip_address 预留 IP 地址

    used_ips:
      ...
      - "203.0.113.129"
      - "203.0.113.140-203.0.113.142"
    
  3. provider_networks 中,您需要定义一个新的容器网络,并将其分配给 HAProxy 组。

    global_overrides:
      ...
      provider_networks:
        ...
        - network:
          group_binds:
            - haproxy
          type: "raw"
          container_bridge: "br-public-api"
          container_interface: "eth20"
          container_type: "veth"
          ip_from_q: public_api
          static_routes:
            - cidr: 0.0.0.0/0
              gateway: 203.0.113.129
    

虽然这些都是需要在 openstack_user_config.yml 中进行的更改,但还需要应用一个额外的覆盖。

如您可能已经发现,我们正在通过 eth20 为容器定义默认路由。但是,默认情况下,所有容器都通过 eth0 具有其默认路由,eth0 是一个本地 LXC 桥接,地址是通过 DHCP 接收的。为了避免冲突,您需要确保容器内部不会为 eth0 设置默认路由。为此,在 /etc/openstack_deploy/group_vars/haproxy 中创建一个文件,内容如下

---
lxc_container_networks:
  lxcbr0_address:
    bridge: "{{ lxc_net_bridge | default('lxcbr0') }}"
    bridge_type: "{{ lxc_net_bridge_type | default('linuxbridge') }}"
    interface: eth0
    type: veth
    dhcp_use_routes: False

配置容器内的 HAProxy 绑定

由于容器内的 IP 分配相当随机,因此将 HAProxy 绑定到特定 IP 地址可能并不总是方便的。如果这样,您可以将 HAProxy 绑定到接口,因为我们始终知道容器内的接口名称。这样,Keepalived 公共/内部 VIP 应该添加到 used_ips 中,因此您仍然可以自由定义它们。

下面的示例显示了 user_variables.yml 中的可能内容

haproxy_bind_external_lb_vip_interface: eth20
haproxy_bind_internal_lb_vip_interface: eth1
haproxy_bind_external_lb_vip_address: "*"
haproxy_bind_internal_lb_vip_address: "*"
haproxy_keepalived_external_vip_cidr: 203.0.113.140/32
haproxy_keepalived_internal_vip_cidr: 172.29.236.9/32
haproxy_keepalived_external_interface: "{{ haproxy_bind_external_lb_vip_interface }}"
haproxy_keepalived_internal_interface: "{{ haproxy_bind_internal_lb_vip_interface }}"

或者,您可以检测容器内使用的 IP 地址来配置 HAProxy 绑定。可以通过引用 container_networks 映射来完成此操作

haproxy_bind_external_lb_vip_address: "{{ container_networks['public_api_address']['address'] }}"
haproxy_bind_internal_lb_vip_address: "{{ container_networks['management_address']['address'] }}"

创建容器

完成以上所有步骤后,就可以创建新的 HAProxy 容器了。为此,运行以下命令

# openstack-ansible playbooks/lxc-containers-create.yml --limit haproxy,lxc_hosts