技术分享 | OpenShift网络之SDN

在红帽主导的容器平台OpenShift中,默认使用了原生的SDN网络解决方案,这是专门为OpenShift开发的一套符合CNI标准的SDN Plugin,并采用了当下流行的OVS作为虚拟交换机。

 

Overview

 

一个基本的SDN系统一般包含管理面、控制面和数据(转发)面三部分,通俗来说,管理面的对外表现就是北向接口,拿Openshift中的SDN来说,它的北向接口就是CNI了,kubelet收到用户创建POD的请求后,会调用CRI接口去实现POD创建相关的工作,另一块就是调用CNI去完成网络相关的工作,当然这里的CNI主要分成两块:

 

一部分是二进制文件,也就是kubelet直接执行该二进制文件并向其传递pod id、namespace等参数;

另一部分就是CNI Server,接收CNI Client的请求,并调用SDN Controller去完成OVS Bridge端口、流表相关的配置,为POD打通网络。

 

控制面一个主要载体就是南向接口了,当然管理面的一些具体实现也有可能通过南向接口实现(这里不做重点分析),对于一个DC领域SDN控制器而言,其南向协议占了很大一部分的,说道南向协议很多人可能想到是Openflow、netconf、XMPP、P4 runtime……,不过可能要让大家失望了,这里并没采用上面说的那些高大上的协议,有的只是CLI、ovs-vsctl、ovs-ofctl、iptables等我们以前常用的命令而已,通过对这些常用命令的函数化封装、调用就组成了今天要用到的SDN南向协议。

 

可能很多人要在心里面犯嘀咕了,仅仅通过几条命令的调用能不能胜任这份重任呢?答案当然是肯定的,没有一点问题,大名鼎鼎的OpenStack中的Neutron也是这么干的,Neutron中的默认方式也是直接调用的命令来实现对OVS的控制,所以放心用吧。

 

我们这里重点介绍一下该SDN方案的数据面实现模型,当SDN Controller收到北向CNI Server的网络请求后,是如何控制OVS进行流表的增删改查、以及iptables相关的操作。

 

数据面就是指各种转发设备了,包括传统的硬件厂家个各种路由器、交换机、防火墙、负载均衡等,也包括各种纯软件实现的是Vswitch、Vrouter,其中OVS作为其中的典型代表,借着OpenStack等云计算技术的发展着实火了一把,在Openshift的SDN方案中OVS作为数据面的转发载体,起着至关重要的作用。

 

北向接口

 

在一个基本的SDN系统中北向接口作为直接面向用户或者应用部分,一个主要的功能就是先理解用户的“语言”,这里的语言就是是CNI了,CNI也是容器平台一个重要的网络接口,基本动作包含:添加网络、删除网络、添加网络列表、删除网络列表。

 

在Openshift的SDN中,首先实现了一个CNI的client,编译后会产生一个二进制可执行文件,安装过程中一般会将该文件放置在/opt/cni/bin目录下,供kubelet调用。

在pkg/network/sdn-cni-plugin/openshift-sdn.go#line57文件中

 

// Send a CNI request to  the CNI server via JSON + HTTP over a root-owned unix socket,

// and return the result

func (p *cniPlugin) doCNI(url string, req  *cniserver.CNIRequest) ([]byte, error) {

    data, err := json.Marshal(req)

    if err != nil {

        returnnil, fmt.Errorf("failed to marshal  CNI request %v: %v", req, err)

    }

 

    client := &http.Client{

        Transport: &http.Transport{

            Dial: func(proto, addr string) (net.Conn, error) {

                return net.Dial("unix", p.socketPath)

            },

        },

    }

 

    varresp *http.Response

    err = p.hostNS.Do(func(ns.NetNS) error {

        resp, err = client.Post(url, "application/json", bytes.NewReader(data))

        return err

    })

    if err != nil {

        returnnil, fmt.Errorf("failed to send CNI  request: %v", err)

    }

    defer resp.Body.Close()

 

    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {

        returnnil, fmt.Errorf("failed to read CNI  result: %v", err)

    }

 

    if resp.StatusCode != 200 {

        returnnil, fmt.Errorf("CNI request failed  with status %v: '%s'", resp.StatusCode, string(body))

    }

 

    return body, nil

}

 

// Send the ADD command  environment and config to the CNI server, returning

// the IPAM result to  the caller

func (p *cniPlugin) doCNIServerAdd(req *cniserver.CNIRequest,  hostVeth string) (types.Result, error) {

    req.HostVeth = hostVeth

    body, err := p.doCNI("http://dummy/", req)

    if err != nil {

        returnnil, err

    }

 

    // We currently expect CNI version 0.2.0  results, because that's the

    // CNIVersion we pass in our config JSON

    result, err := types020.NewResult(body)

    if err != nil {

        returnnil, fmt.Errorf("failed to  unmarshal response '%s': %v", string(body), err)

    }

 

    return result, nil

}

.... 

 

func (p *cniPlugin) CmdDel(args *skel.CmdArgs) error {

    _, err := p.doCNI("http://dummy/", newCNIRequest(args))

    return err

}

 

主要实现了CmdAdd、CmdDel两种类型的方法供kubelet调用,以完成POD创建或者删除时相关网络配置的变更。

 

其次就是实现了一个CNI的Server用来响应处理CNI客户端的请求,Server 跟Client之间通过一个unix类型的Socket以HTTP + JSON 的方式进行通信。

在Openshift中每个NODE启动时都会顺带启动一个CNI Server,在文件origin/pkg/network/node/pod.go#line170中

 

// Start the CNI server  and start processing requests from it

func (m *podManager) Start(rundir string, localSubnetCIDR string, clusterNetworks  []common.ClusterNetwork, serviceNetworkCIDR string) error {

    if m.enableHostports {

        iptInterface := utiliptables.New(utilexec.New(), utildbus.New(),  utiliptables.ProtocolIpv4)

        m.hostportSyncer = kubehostport.NewHostportSyncer(iptInterface)

    }

 

    varerrerror

    ifm.ipamConfig, err = getIPAMConfig(clusterNetworks, localSubnetCIDR); err != nil {

        return err

    }

 

    go m.processCNIRequests()

 

    m.cniServer = cniserver.NewCNIServer(rundir,  &cniserver.Config{MTU: m.mtu, ServiceNetworkCIDR: serviceNetworkCIDR})

    return m.cniServer.Start(m.handleCNIRequest)

}

 

其中的line184的cniserver.NewCNIServer具体实现在文件origin/pkg/network/node/cniserver/cniserver.go#line120中

 

//  Create and return a new CNIServer object which will listen on a socket in the  given path

funcNewCNIServer(rundir string, config *Config)  *CNIServer {

    router := mux.NewRouter()

 

    s := &CNIServer{

        Server: http.Server{

            Handler: router,

        },

        rundir: rundir,

        config: config,

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值