为OpenStack和K8s集群提供无缝虚拟网络



  • 在数据中心里,同时拥有OpenStack和Kubernetes集群的情况正变得越来越普遍。

    一边是由OpenStack管理的虚拟机,另一边是由K8s控制的容器化工作负载。

    现实情况下,可能会发生虚拟机和容器需要相互“交谈”的情况。如果是这样,我们需要以某种方式实现两个独立集群之间的通信。
    51990e6a-23c8-4247-adb1-5d45a227a2e2-image.png
    作为两个不同的集群,每个集群都有自己的网络平面。OpenStack可能会依赖OVS,而Kubernetes则会部署某个可用的CNI。

    由于每个集群都是独立于其它集群的,因此必须在第三个“元素”上启用通信。这第三个元素可以是IP Fabric。

    虚拟机将通过提供商网络退出计算,并在VLAN内的IP Fabric上现身。容器也会做类似的事情。因此,我们可能最终会有两个VLAN,通过配置IP Fabric叶子节点(或spine节点)作为L2/L3 gw来允许它们相互对话。另外,L3 gw的角色可能会被委托给上层设备,比如SDN gw(在这种情况下,IP Fabric叶子节点/spine节点只会是L2 gw)。

    这里的一个关键要点是,虚拟/容器化的工作负载成为underlay的一部分,使得服务平面(工作负载)和网络平面(IP Fabric)相互交错。

    提供服务也需要在IP Fabric underlay进行配置。我们在探索数据中心使用Tungsten Fabric的优势时,谈到过这个概念,请见下面链接:

    https://iosonounrouter.wordpress.com/2020/04/28/which-sdn-solution-is-better-what-i-learned/

    同样的考虑在这里也适用,另外我们不仅需要连接虚拟机,还需要连接容器。

    我们假设需要部署一个应用程序,其构件既有虚拟机又有容器。这意味着我们需要在OpenStack里部署一些对象,在Kubernetes里部署另外一些对象。从网络的角度来看,我们需要对OpenStack、K8s和IP Fabric进行操作。如果能简化这一点就好了。Tungsten Fabric来了!

    TF可以与OpenStack和K8s一起工作(作为CNI)。当然,代码的某些部分是“编排器专用”的。我的意思是,TF一方面需要与Neutron/Nova进行交互,另一方面需要与K8s API和其它K8s组件进行交互。

    总之,除了这些,解决方案的核心还是一样的!虚拟网络也还是VRF。工作负载也还是接口!不管我们的接口后面是虚拟机还是容器,对于Tungsten Fabric来说,它只是放到VRF中的一个接口。对于计算节点之间和通向SDN网关的overlay(MPLSoUDP、MPLSoGRE、VXLAN)也是一样的。

    因此,让虚拟机与容器进行通信,只是在同一个VRF(虚拟网络)中放置接口的问题。这可以通过让一个实体来“控制”OpenStack和Kubernetes集群的虚拟网络来实现。这个实体就是我们最爱的TF Controller。

    我们的数据中心将会是这样的:
    ccc0ee40-e16e-4c6c-9a9f-0c7ff27c3fa8-image.png
    如你所见,从配置的角度来看,集群还是有区别的。Kubernetes master控制容器,而OpenStack controller负责Nova虚拟机。

    这里的关键变化在于Tungsten Fabric控制器(controller)的存在,它将与OpenStack计算节点和Kubernetes worker节点进行交互(仍然使用XMPP)。这样我们就为集群提供了一个单一的网络平面。

    由于TF在overlay层工作,我们不再需要IP Fabric来“看到”虚拟机和容器。工作负载的流量被隐藏在overlay隧道中(无论是在计算节点之间,还是通向SDN网关……就像任何一个TF集群一样)。

    拥有一个单一的网络平面还会带来其它优势。例如,TF将负责IP地址分配。假设我们有一个地址为192.168.1.0/24的虚拟网络。如果我们在该VN上创建一个虚拟机,TF将分配地址192.168.1.3。之后,如果我们在Kubernetes中创建一个容器,并将其连接到同一个VN,TF将分配地址192.168.1.4,因为它知道.3已经在使用。如果有两个不同的集群,实现这个功能需要额外的“工具”(例如配置静态分配池或让更高级别的编排器充当IPAM管理器角色)。Tungsten Fabric则简化了这种网络操作。

    现在,理论已经说得够多了。让我们来看一个实际的例子!

    我在一个虚拟实验室中构建了上述拓扑。为了部署Tungsten Fabric,我使用了Ansible部署器,它允许我们同时配置OpenStack和Kubernetes集群。我不会去详细介绍如何使用Ansible部署器安装TF(这里是一个部署K8s集群的例子);我假设对这个工具有预先的了解。

    大家知道,Ansible部署器的核心是instances.yaml文件。下面是我使用的一个文件:

    ###FILE USED TO DESCRIBE THE CONTRAIL CLOUD THAT ANSIBLE DEPLOYER HAS TO BUILD
    global_configuration:
      CONTAINER_REGISTRY: hub.juniper.net/contrail
      CONTAINER_REGISTRY_USERNAME: xxx
      CONTAINER_REGISTRY_PASSWORD: yyy
    provider_config:
      bms:
        ssh_user: root
        ssh_pwd: Embe1mpls
        ntpserver: 10.102.255.254
        domainsuffix: multiorch.contrail
    instances:
      cnt-control:
        provider: bms
        ip: 10.102.240.183
        roles:
          config:
          config_database:
          control:
          webui:
          analytics:
          analytics_database:
          analytics_alarm:
      os-control:
        provider: bms
        ip: 10.102.240.178
        roles:
          openstack:
      os-compute:
        provider: bms
        ip: 10.102.240.171
        roles:
          vrouter:
            VROUTER_GATEWAY: 192.168.200.1
          openstack_compute:
      k8s-master:
       provider: bms
       roles:
          k8s_master:
          kubemanager:
       ip: 10.102.240.174
      k8s-worker:
       provider: bms
       roles:
         vrouter:
           VROUTER_GATEWAY: 192.168.200.1
         k8s_node:
       ip: 10.102.240.172
    contrail_configuration:
      CLOUD_ORCHESTRATOR: openstack
      CONTRAIL_CONTAINER_TAG: 2008.121
      CONTRAIL_VERSION: 2008.121
      OPENSTACK_VERSION: queens
      ENCAP_PRIORITY: "VXLAN,MPLSoUDP,MPLSoGRE"
      BGP_ASN: 65100
      CONFIG_NODEMGR__DEFAULTS__minimum_diskGB: 2
      DATABASE_NODEMGR__DEFAULTS__minimum_diskGB: 2
      CONFIG_DATABASE_NODEMGR__DEFAULTS__minimum_diskGB: 2
      KUBERNETES_CLUSTER_PROJECT: {}
      KUBERNETES_API_NODES: 192.168.200.12
      KUBERNETES_API_SERVER: 192.168.200.12
      KUBEMANAGER_NODES: 192.168.200.12
      RABBITMQ_NODE_PORT: 5673
      KEYSTONE_AUTH_URL_VERSION: /v3
      VROUTER_GATEWAY: 192.168.200.1
      CONTROLLER_NODES: 10.102.240.183
      CONTROL_NODES: 192.168.200.10
      JVM_EXTRA_OPTS: "-Xms1g -Xmx2g"
      PHYSICAL_INTERFACE: "ens3f1"
    kolla_config:
      kolla_globals:
        enable_haproxy: no
        enable_ironic: no
        enable_swift: no
        enable_barbican: no
      kolla_passwords:
        keystone_admin_password: contrail123
    metadata_secret: meta123
    

    实例部分包括5台服务器:

    • openstack controller
    • kubernetes master
    • contrail controller
    • openstack compute
    • kubernetes worker

    如果你去看“contrail_configuration”部分,就会注意到我们配置TF是为了与openstack controller(KEYSTONE_AUTH_URL_VERSION)和kubernetes master(KUBERNETES_API_NODES)交互。
    一旦所有节点都做好安装准备,从“deployer”节点(可以是contrail controller本身)运行以下命令:

    ansible-playbook -e orchestrator=openstack -i inventory/ playbooks/configure_instances.yml
    ansible-playbook -e orchestrator=openstack -i inventory/ playbooks/install_openstack.yml
    ansible-playbook -e orchestrator=openstack -i inventory/ playbooks/install_k8s.yml
    ansible-playbook -e orchestrator=openstack -i inventory/ playbooks/install_contrail.yml
    

    如果一切顺利的话,我们的集群应该可以运行了。

    让我们连接到Tungsten Fabric GUI:
    f93f1b03-6345-4c45-8705-98245960a45e-image.png

    我们看到两个虚拟路由器:os-compute和k8s-worker!

    我们看一下控制节点:
    c434da04-a779-4028-9c62-4265ec56af9e-image.png

    只有一个控制器!我们“单一网络平面”的关键概念变成了现实。

    接下来,我创建了一个虚拟网络:

    • FQ名称:default-domain:k8s-contrail:seamless
    • CIDR:192.168.1.0/24

    启动一个连接到该VN的虚拟机:

    nova boot --image cirros2 --flavor cirros --nic net-id= vm
    
    (kolla-toolbox)[ansible@os-control /]$ nova list
    +--------------------------------------+------+--------+------------+-------------+------------------------+
    | ID                                   | Name | Status | Task State | Power State | Networks               |
    +--------------------------------------+------+--------+------------+-------------+------------------------+
    | 3cf82185-5261-4b35-87bf-4eaa9de3caaf | vm   | ACTIVE | -          | Running     | seamless=192.168.100.3 |
    +--------------------------------------+------+--------+------------+-------------+------------------------+
    

    接下来,创建一个连接到该VN的容器:

    [root@k8s-master ~]# cat cn.yaml
    ---
    kind: Namespace
    apiVersion: v1
    metadata:
      name: seamless
      annotations:
        'opencontrail.org/network' : '{"domain":"default-domain", "project": "k8s-contrail", "name":"seamless"}'
      labels:
        name: seamless
    ---
    apiVersion: v1
    kind: Pod
    metadata:
      name: cont
      namespace: seamless
    spec:
      containers:
      - name: cont
        image: alpine
        command: ["tail"]
        args: ["-f", "/dev/null"]
    
    kubectl apply -f vn.yaml
    
    [root@k8s-master ~]# kubectl get pod -n seamless -o wide
    NAME   READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES
    cont   1/1     Running   0          74s   192.168.100.4   k8s-worker              
    
    [root@k8s-master ~]# kubectl get -f cn.yaml -o wide
    NAME                 STATUS   AGE
    namespace/seamless   Active   31m
    
    NAME       READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES
    pod/cont   1/1     Running   0          31m   192.168.100.4   k8s-worker              
    

    如你所见,由于192.168.100.3已经被VM占用了,pod被分配了192.168.100.4的IP地址。这也是多个集群使用单一网络平面的优势之一。
    baf8bb04-32e2-49a1-8d2f-5b9f631a0305-image.png
    b5add53a-601e-4d02-94a3-8f6b00f6687d-image.png

    让我们从GUI中查看一下路由表:
    37e5b67e-6f59-4308-b949-51b7905e31c0-image.png
    两个IP都有!我们刚刚把一个虚拟机连接到一个工作负载上……而这对underlay是完全透明的!

    让我们再到worker节点,查看一下vRouter代理:

    (vrouter-agent)[root@k8s-worker /]$ vif --get 3
    Vrouter Interface Table
    
    vif0/3      OS: tapeth0-11ccbe NH: 51
                Type:Virtual HWaddr:00:00:5e:00:01:00 IPaddr:192.168.100.4
                Vrf:4 Mcast Vrf:4 Flags:PL3L2DEr QOS:-1 Ref:6
                RX packets:67  bytes:2814 errors:0
                TX packets:87  bytes:3654 errors:0
                Drops:67
    
    (vrouter-agent)[root@k8s-worker /]$ rt --get 192.168.100.3/32 --vrf 4
    Match 192.168.100.3/32 in vRouter inet4 table 0/4/unicast
    
    Flags: L=Label Valid, P=Proxy ARP, T=Trap ARP, F=Flood ARP
    vRouter inet4 routing table 0/4/unicast
    Destination           PPL        Flags        Label         Nexthop    Stitched MAC(Index)
    192.168.100.3/32        0           LP         25             39        2:85:bf:53:6b:67(96024)
    (vrouter-agent)[root@k8s-worker /]$ nh --get 39
    Id:39         Type:Tunnel         Fmly: AF_INET  Rid:0  Ref_cnt:8          Vrf:0
                  Flags:Valid, MPLSoUDP, Etree Root,
                  Oif:0 Len:14 Data:56 68 a6 6f 05 ff 56 68 a6 6f 06 f7 08 00
                  Sip:192.168.200.14 Dip:192.168.200.13
    

    所有的路由信息都在那里!容器可以通过连接Kubernetes worker节点和OpenStack计算节点的MPLSoUPD隧道到达虚拟机。

    这样就可以进行通信了吗?还不行!别忘了安全组还在那里!

    虚拟机属于一个OpenStack项目(这里是管理项目),而容器属于另一个项目(映射到Kubernetes命名空间)。每个项目都有自己的安全组。默认情况下,安全组只允许来自属于同一安全组的入口流量。由于两个工作负载的接口被分配到不同的安全组,因此它们之间不允许对话!

    为了解决这个问题,我们需要对安全组采取行动。

    简单的方法是在两个安全组上都允许来自0.0.0.0/0的入口流量(这是容器安全组,但同样也可以在虚拟机安全组上进行):
    145bb302-b9b3-48ce-a570-888829fd93ff-image.png
    另外,我们也可以在入口方向允许特定的安全组。

    例如,在k8s-seamless-default-sg(容器所属命名空间/项目的安全组)上,我们允许默认安全组(虚拟机所属OpenStack项目的安全组)。
    c629ed30-4733-46ec-9d1d-530fdbf728ae-image.png
    同样地,也可以在分配给虚拟机接口的安全组上进行:
    22418c56-9360-4903-ba85-f3e7bc5a8612-image.png
    现在,让我们访问容器,并且ping一下虚拟机:

    [root@k8s-worker ~]# docker ps | grep seaml
    18c3898a09ac        alpine                                                         "tail -f /dev/null"      9 seconds ago       Up 8 seconds                            k8s_cont_cont_seamless_e4a7ed6d-38e9-11eb-b8fe-5668a66f06f8_0
    6bb1c3b40300        k8s.gcr.io/pause:3.1                                           "/pause"                 17 seconds ago      Up 15 seconds                           k8s_POD_cont_seamless_e4a7ed6d-38e9-11eb-b8fe-5668a66f06f8_0
    [root@k8s-worker ~]# docker exec -it 18c38 sh
    / # ip add
    1: lo:  mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
    24: eth0@if25:  mtu 1500 qdisc noqueue state UP
        link/ether 02:e4:e5:52:ce:38 brd ff:ff:ff:ff:ff:ff
        inet 192.168.100.4/24 scope global eth0
           valid_lft forever preferred_lft forever
    / # ping 192.168.100.3
    PING 192.168.100.3 (192.168.100.3): 56 data bytes
    64 bytes from 192.168.100.3: seq=0 ttl=64 time=3.813 ms
    64 bytes from 192.168.100.3: seq=1 ttl=64 time=1.802 ms
    64 bytes from 192.168.100.3: seq=2 ttl=64 time=2.260 ms
    64 bytes from 192.168.100.3: seq=3 ttl=64 time=1.945 ms
    ^C
    --- 192.168.100.3 ping statistics ---
    4 packets transmitted, 4 packets received, 0% packet loss
    round-trip min/avg/max = 1.802/2.455/3.813 ms
    / #
    

    就是这样!虚拟机和容器可以互相对话了!

    无论你的工作负载是虚拟机还是容器……至少从网络的角度来看,一切都是在Tungsten Fabric的机制下进行的。


    作者:Umberto Manferdini 译者:TF编译组
    原文链接:https://iosonounrouter.wordpress.com/2020/12/14/contrail-providing-seamless-virtual-networking-to-openstack-and-kubernetes-clusters/



Log in to reply