技術分享 | 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, } router.NotFoundHandler = http.HandlerFunc(http.NotFound) router.HandleFunc("/", s.handleCNIRequest).Methods("POST") return s } ……. // Start the CNIServer's local HTTP server on a root-owned Unix domain socket. // requestFunc will be called to handle pod setup/teardown operations on each // request to the CNIServer's HTTP server, and should return a PodResult // when the operation has completed. func (s *CNIServer) Start(requestFunc cniRequestFunc) error { if requestFunc == nil { return fmt.Errorf("no pod request handler") } s.requestFunc = requestFunc |
CNIServer收到Client的請求後會繼續呼叫後端的ovscontroller等檔案完成具體的網路實現。
南向介面
就像上面所說的,在Openshift的SDN中,南向協議是直接通過呼叫CLI命令來實現的,下面我們簡單看一下具體實現
#origin/pkg/util/ovs/ovs.go#line140
.... const ( OVS_OFCTL = "ovs-ofctl" OVS_VSCTL = "ovs-vsctl" ) .... func (ovsif *ovsExec) execWithStdin(cmd string, stdinArgs []string, args ...string) (string, error) { logLevel := glog.Level(4) switch cmd { case OVS_OFCTL: if args[0] == "dump-flows" { logLevel = glog.Level(5) } args = append([]string{"-O", "OpenFlow13"}, args...) case OVS_VSCTL: args = append([]string{"--timeout=30"}, args...) } kcmd := ovsif.execer.Command(cmd, args...) if stdinArgs != nil { stdinString := strings.Join(stdinArgs, "\n") stdin := bytes.NewBufferString(stdinString) kcmd.SetStdin(stdin) glog.V(logLevel).Infof("Executing: %s %s <<\n%s", cmd, strings.Join(args, " "), stdinString) } else { glog.V(logLevel).Infof("Executing: %s %s", cmd, strings.Join(args, " ")) } output, err := kcmd.CombinedOutput() if err != nil { glog.V(2).Infof("Error executing %s: %s", cmd, string(output)) return"", err } outStr := string(output) if outStr != "" { // If output is a single line, strip the trailing newline nl := strings.Index(outStr, "\n") if nl == len(outStr)-1 { outStr = outStr[:nl] } } return outStr, nil } |
這裡主要對OVS中兩個常用的命令ovs-vsctl、ovs-ifctl進行了函式化封裝,後續的增加ovs埠、轉發流表、QOS限速等,均可以通過呼叫該函式來實現對OVS配置,
#origin/pkg/network/node/iptables.go#line90
// syncIPTableRules syncs the cluster network cidr iptables rules. // Called from SyncLoop() or firewalld reload() func (n *NodeIPTables) syncIPTableRules() error { n.mu.Lock() defer n.mu.Unlock() start := time.Now() deferfunc() { glog.V(4).Infof("syncIPTableRules took %v", time.Since(start)) }() glog.V(3).Infof("Syncing openshift iptables rules") chains := n.getNodeIPTablesChains() fori := len(chains) - 1; i >= 0; i-- { chain := chains[i] // Create chain if it does not already exist chainExisted, err := n.ipt.EnsureChain(iptables.Table(chain.table), iptables.Chain(chain.name)) if err != nil { return fmt.Errorf("failed to ensure chain %s exists: %v", chain.name, err) } if chain.srcChain != "" { // Create the rule pointing to it from its parent chain. Note that since we // use iptables.Prepend each time, but process the chains in reverse order, // chains with the same table and srcChain (ie, OPENSHIFT-FIREWALL-FORWARD // and OPENSHIFT-ADMIN-OUTPUT-RULES) will run in the same order as they // appear in getNodeIPTablesChains(). _, err = n.ipt.EnsureRule(iptables.Prepend, iptables.Table(chain.table), iptables.Chain(chain.srcChain), append(chain.srcRule, "-j", chain.name)...) if err != nil { return fmt.Errorf("failed to ensure rule from %s to %s exists: %v", chain.srcChain, chain.name, err) } } // Add/sync the rules rulesExisted, err := n.addChainRules(chain) if err != nil { return err } if chainExisted && !rulesExisted { // Chain existed but not with the expected rules; this probably means // it contained rules referring to a *different* subnet; flush them // and try again. iferr = n.ipt.FlushChain(iptables.Table(chain.table), iptables.Chain(chain.name)); err != nil { return fmt.Errorf("failed to flush chain %s: %v", chain.name, err) } if_, err = n.addChainRules(chain); err != nil { return err } } } returnnil } |
這裡主要是對iptables相關命令的封裝,會在main函式中繼續迴圈監聽,同步SDN控制器下發的iptables rules並驗證是否配置成功,當用戶通過API建立一個Service服務時,ClusterIP相關的NAT對映、訪問控制、外部網路訪問均是通過呼叫iptables來實現。
初始化
當我們新建立一個叢集或者增加一個物理節點時,SDN的Controller會對節點進行一些初始化的配置,主要包括ovs、iptables兩部分
OVS
OVS的初始化主要包括網橋br0、port(tun0、vxlan)的建立、初始化流表的配置等。
我們這裡重點說一下流表的模型,Openshift的SDN模型中用到的流表主要包括一下幾個table:
// Table 0: initial dispatch based on in_port |
排程關係如下:
#origin/pkg/network/node/ovscontroller.go#line66
func (oc *ovsController) SetupOVS(clusterNetworkCIDR []string, serviceNetworkCIDR, localSubnetCIDR, localSubnetGateway string, mtu uint32) error { .... err = oc.ovs.AddBridge("fail-mode=secure", "protocols=OpenFlow13") if err != nil { return err .... _ = oc.ovs.DeletePort(Vxlan0) _, err = oc.ovs.AddPort(Vxlan0, 1, "type=vxlan", `options:remote_ip="flow"`, `options:key="flow"`) if err != nil { return err } _ = oc.ovs.DeletePort(Tun0) _, err = oc.ovs.AddPort(Tun0, 2, "type=internal", fmt.Sprintf("mtu_request=%d", mtu)) if err != nil { return err } otx := oc.ovs.NewTransaction() // Table 0: initial dispatch based on in_port if oc.useConnTrack { otx.AddFlow("table=0, priority=300, ip, ct_state=-trk, actions=ct(table=0)") } // vxlan0 for_, clusterCIDR := range clusterNetworkCIDR { otx.AddFlow("table=0, priority=200, in_port=1, arp, nw_src=%s, nw_dst=%s, actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10", clusterCIDR, localSubnetCIDR) otx.AddFlow("table=0, priority=200, in_port=1, ip, nw_src=%s, actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10", clusterCIDR) otx.AddFlow("table=0, priority=200, in_port=1, ip, nw_dst=%s, actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10", clusterCIDR) } otx.AddFlow("table=0, priority=150, in_port=1, actions=drop") // tun0 if oc.useConnTrack { otx.AddFlow("table=0, priority=400, in_port=2, ip, nw_src=%s, actions=goto_table:30", localSubnetGateway) for_, clusterCIDR := range clusterNetworkCIDR { otx.AddFlow("table=0, priority=300, in_port=2, ip, nw_src=%s, nw_dst=%s, actions=goto_table:25", localSubnetCIDR, clusterCIDR) } } otx.AddFlow("table=0, priority=250, in_port=2, ip, nw_dst=224.0.0.0/4, actions=drop") for_, clusterCIDR := range clusterNetworkCIDR { otx.AddFlow("table=0, priority=200, in_port=2, arp, nw_src=%s, nw_dst=%s, actions=goto_table:30", localSubnetGateway, clusterCIDR) } otx.AddFlow("table=0, priority=200, in_port=2, ip, actions=goto_table:30") otx.AddFlow("table=0, priority=150, in_port=2, actions=drop") // else, from a container otx.AddFlow("table=0, priority=100, arp, actions=goto_table:20") otx.AddFlow("table=0, priority=100, ip, actions=goto_table:20") otx.AddFlow("table=0, priority=0, actions=drop") // Table 10: VXLAN ingress filtering; filled in by AddHostSubnetRules() // eg, "table=10, priority=100, tun_src=${remote_node_ip}, actions=goto_table:30" otx.AddFlow("table=10, priority=0, actions=drop") // (${tenant_id} is always 0 for single-tenant) otx.AddFlow("table=20, priority=0, actions=drop") // Table 21: from OpenShift container; NetworkPolicy plugin uses this for connection tracking otx.AddFlow("table=21, priority=0, actions=goto_table:30") // Table 253: rule version note otx.AddFlow("table=%d, actions=note:%s", ruleVersionTable, oc.getVersionNote()) return otx.Commit() } |
IPtables
主要涉及Cluster Network、Service Network 網段相關的rule規則、NAT以及vxlan流量(udp.port 4789)的安全策略。
#origin/pkg/network/node/iptables.go#line146
func (n *NodeIPTables) getNodeIPTablesChains() []Chain { varchainArray []Chain chainArray = append(chainArray, Chain{ table: "filter", name: "OPENSHIFT-FIREWALL-ALLOW", srcChain: "INPUT", srcRule: []string{"-m", "comment", "--comment", "firewall overrides"}, rules: [][]string{ {"-p", "udp", "--dport", vxlanPort, "-m", "comment", "--comment", "VXLAN incoming", "-j", "ACCEPT"}, {"-i", Tun0, "-m", "comment", "--comment", "from SDN to localhost", "-j", "ACCEPT"}, {"-i", "docker0", "-m", "comment", "--comment", "from docker to localhost", "-j", "ACCEPT"}, }, }, Chain{ table: "filter", name: "OPENSHIFT-ADMIN-OUTPUT-RULES", srcChain: "FORWARD", srcRule: []string{"-i", Tun0, "!", "-o", Tun0, "-m", "comment", "--comment", "administrator overrides"}, rules: nil, }, ) varmasqRules [][]string varmasq2Rules [][]string varfilterRules [][]string for_, cidr := range n.clusterNetworkCIDR { if n.masqueradeServices { masqRules = append(masqRules, []string{"-s", cidr, "-m", "comment", "--comment", "masquerade pod-to-service and pod-to-external traffic", "-j", "MASQUERADE"}) } else { masqRules = append(masqRules, []string{"-s", cidr, "-m", "comment", "--comment", "masquerade pod-to-external traffic", "-j", "OPENSHIFT-MASQUERADE-2"}) masq2Rules = append(masq2Rules, []string{"-d", cidr, "-m", "comment", "--comment", "masquerade pod-to-external traffic", "-j", "RETURN"}) } filterRules = append(filterRules, []string{"-s", cidr, "-m", "comment", "--comment", "attempted resend after connection close", "-m", "conntrack", "--ctstate", "INVALID", "-j", "DROP"}) filterRules = append(filterRules, []string{"-d", cidr, "-m", "comment", "--comment", "forward traffic from SDN", "-j", "ACCEPT"}) filterRules = append(filterRules, []string{"-s", cidr, "-m", "comment", "--comment", "forward traffic to SDN", "-j", "ACCEPT"}) } chainArray = append(chainArray, Chain{ table: "nat", name: "OPENSHIFT-MASQUERADE", srcChain: "POSTROUTING", srcRule: []string{"-m", "comment", "--comment", "rules for masquerading OpenShift traffic"}, rules: masqRules, }, Chain{ table: "filter", name: "OPENSHIFT-FIREWALL-FORWARD", srcChain: "FORWARD", srcRule: []string{"-m", "comment", "--comment", "firewall overrides"}, rules: filterRules, }, ) if !n.masqueradeServices { masq2Rules = append(masq2Rules, []string{"-j", "MASQUERADE"}) chainArray = append(chainArray, Chain{ table: "nat", name: "OPENSHIFT-MASQUERADE-2", rules: masq2Rules, }, ) } return chainArray } |
舉個例子
POD Add
OVS
當我們新增加一個POD時,會呼叫底層的CRI介面例如docker來建立POD容器,Kubelet在建立pod時是先建立一個infra容器,配置好該容器的網路,然後建立真正工作的業務容器,最後再把業務容器的網路加到infra容器的網路名稱空間中,業務容器和infra容器共同組成一個pod。當kubelet建立好infra容器後,會去呼叫network-plugin,並將infra的namespace做為傳入引數開始網路建立流程,在這裡會去呼叫/opt/cni/bin/openshift-sdnSDN,由該二進位制檔案充當CNI的客戶端向CNI Server發起請求,之後控制器會下發命令完成OVS新增加Port、POD IP轉發相關的流標的配置。
#origin/pkg/network/node/ovscontroller.go#line266
func (oc *ovsController) SetUpPod(sandboxID, hostVeth string, podIP net.IP, vnid uint32) (int, error) { ofport, err := oc.ensureOvsPort(hostVeth, sandboxID, podIP.String()) if err != nil { return -1, err } return ofport, oc.setupPodFlows(ofport, podIP, vnid) } .... func (oc *ovsController) setupPodFlows(ofport int, podIP net.IP, vnid uint32) error { otx := oc.ovs.NewTransaction() ipstr := podIP.String() podIP = podIP.To4() ipmac := fmt.Sprintf("00:00:%02x:%02x:%02x:%02x/00:00:ff:ff:ff:ff", podIP[0], podIP[1], podIP[2], podIP[3]) // ARP/IP traffic from container otx.AddFlow("table=20, priority=100, in_port=%d, arp, nw_src=%s, arp_sha=%s, actions=load:%d->NXM_NX_REG0[], goto_table:21", ofport, ipstr, ipmac, vnid) otx.AddFlow("table=20, priority=100, in_port=%d, ip, nw_src=%s, actions=load:%d->NXM_NX_REG0[], goto_table:21", ofport, ipstr, vnid) if oc.useConnTrack { otx.AddFlow("table=25, priority=100, ip, nw_src=%s, actions=load:%d->NXM_NX_REG0[], goto_table:30", ipstr, vnid) } // ARP request/response to container (not isolated) otx.AddFlow("table=40, priority=100, arp, nw_dst=%s, actions=output:%d", ipstr, ofport) // IP traffic to container otx.AddFlow("table=70, priority=100, ip, nw_dst=%s, actions=load:%d->NXM_NX_REG1[], load:%d->NXM_NX_REG2[], goto_table:80", ipstr, vnid, ofport) return otx.Commit() } |
IPtables
#origin/pkg/network/node/iptables.go#line216
func (n *NodeIPTables) AddEgressIPRules(egressIP, mark string) error { for_, cidr := range n.clusterNetworkCIDR { _, err := n.ipt.EnsureRule(iptables.Prepend, iptables.TableNAT, iptables.Chain("OPENSHIFT-MASQUERADE"), "-s", cidr, "-m", "mark", "--mark", mark, "-j", "SNAT", "--to-source", egressIP) if err != nil { return err } } _, err := n.ipt.EnsureRule(iptables.Append, iptables.TableFilter, iptables.Chain("OPENSHIFT-FIREWALL-ALLOW"), "-d", egressIP, "-m", "conntrack", "--ctstate", "NEW", "-j", "REJECT") return err } |
POD delete
OVS
當一個POD別刪除時,相應的SDN 控制器會呼叫相關刪除函式,經對應的流標刪除
#origin/pkg/network/node/ovscontroller.go#line256
func (oc *ovsController) cleanupPodFlows(podIP net.IP) error { ipstr := podIP.String() otx := oc.ovs.NewTransaction() otx.DeleteFlows("ip, nw_dst=%s", ipstr) otx.DeleteFlows("ip, nw_src=%s", ipstr) otx.DeleteFlows("arp, nw_dst=%s", ipstr) otx.DeleteFlows("arp, nw_src=%s", ipstr) return otx.Commit() } func (oc *ovsController) DeleteServiceRules(service *kapi.Service) error { otx := oc.ovs.NewTransaction() otx.DeleteFlows(generateBaseServiceRule(service.Spec.ClusterIP)) return otx.Commit() } |
IPtables
#origin/pkg/network/node/iptables.go#line227
func (n *NodeIPTables) DeleteEgressIPRules(egressIP, mark string) error { for_, cidr := range n.clusterNetworkCIDR { err := n.ipt.DeleteRule(iptables.TableNAT, iptables.Chain("OPENSHIFT-MASQUERADE"), "-s", cidr, "-m", "mark", "--mark", mark, "-j", "SNAT", "--to-source", egressIP) if err != nil { return err } } return n.ipt.DeleteRule(iptables.TableFilter, iptables.Chain("OPENSHIFT-FIREWALL-ALLOW"), "-d", egressIP, "-m", "conntrack", "--ctstate", "NEW", "-j", "REJECT") } |
Project Add
#origin/pkg/network/node/vnids.go#line137
func (vmap *nodeVNIDMap) setVNID(name string, id uint32, mcEnabled bool) { vmap.lock.Lock() defer vmap.lock.Unlock() ifoldId, found := vmap.ids[name]; found { vmap.removeNamespaceFromSet(name, oldId) } vmap.ids[name] = id vmap.mcEnabled[name] = mcEnabled vmap.addNamespaceToSet(name, id) glog.Infof("Associate netid %d to namespace %q with mcEnabled %v", id, name, mcEnabled) } |
Project Delete
#origin/pkg/network/node/vnids.go#line137
func (vmap *nodeVNIDMap) unsetVNID(name string) (id uint32, err error) { vmap.lock.Lock() defer vmap.lock.Unlock() id, found := vmap.ids[name] if !found { return0, fmt.Errorf("failed to find netid for namespace: %s in vnid map", name) } vmap.removeNamespaceFromSet(name, id) delete(vmap.ids, name) delete(vmap.mcEnabled, name) glog.Infof("Dissociate netid %d from namespace %q", id, name) return id, nil } |
通過上面的原始碼我們可以看到當進行租戶專案級別的建立或者刪除時,主要對VNI進行了配置調整,在Openshift的多租戶網路模型中,控制器為每一個專案分配了一個VNID用來標識租戶專案並實現了不同租戶專案之間的隔離,當然也可以通過命令將不同專案join在一起實現兩個不同專案之間的互訪,底層主要通過table 80 來實現隔離或者互通。
這裡只是針對基本原理以及部分原始碼做了一些粗淺的分析,接下來的章節會針對具體例項做分析,歡迎大家批評指正!
【推薦閱讀】
優雲數智介紹
優雲數智(上海優銘雲端計算有限公司)是一家專注於提供企業級私有云產品與解決方案的雲端計算廠商,提供PaaS+IaaS的一站式解決方案。優雲數智的母公司是中國中立的公有云服務商UCloud。私有云技術來源於全球頂尖的OpenStack、Ceph、Kubernetes雲端計算開發團隊。
掃描關注,瞭解更多
相關推薦
技術分享 | OpenShift網路之SDN
在紅帽主導的容器平臺OpenShift中,預設使用了原生的SDN網路解決方案,這是專門為OpenShift開發的一套符合CNI標準的SDN Plugin,並採用了當下流行的OVS作為虛擬交換機。 Overview 一個基本的SDN系統一般包含管理面、控制面和資料(
理解OpenShift(3):網路之 SDN
理解OpenShift(1):網路之 Router 和 Route 理解OpenShift(2):網路之 DNS(域名服務) 理解OpenShift(3):網路之 SDN 1. 概況 OpenShift SDN 實現了符合Kubernetes CNI 要求的 OpenShift
Openshift的網路之二:SDN master程式碼分析
一、前言Openshift基於OVS SDN的CNI網路方案具有一個以etcd key-value store為中心master節點和若干worker節點,Openshift SDN master在etcd維護一個worker節點和SDN網路相關的資料庫。master主要的責
技術分享之如何完整備份sql server 2012數據庫?
數據庫 備份 sql server2012 ZBLOG 博客已經建了幾年了,但由於我是個外行,因此一開始就采用了zblog asp+access數據庫的方式進行搭建,主要還是因為技術方面的缺陷嘛。但是經過幾年的整理和寫作,發現這套系統越來越卡了,咨詢了很多博主,並且去ZBLOG論壇逛了很多次,
知乎技術分享:從單機到2000萬QPS並發的Redis高性能緩存實踐之路
周期性 聯網 .html twemproxy 級別 space oev container 基數 本文來自知乎官方技術團隊的“知乎技術專欄”,感謝原作者陳鵬的無私分享。 1、引言 知乎存儲平臺團隊基於開源Redis 組件打造的知乎 Redis 平臺,經過不斷的研發叠代,目前
阿里技術分享:阿里自研金融級資料庫OceanBase的艱辛成長之路
本文原始內容由作者“陽振坤”整理髮佈於OceanBase技術公眾號。 1、引言 OceanBase 是螞蟻金服自研的分散式資料庫,在其 9 年的發展歷程裡,從艱難上線到找不到業務場景瀕臨解散,最後在雙十一的流量考驗下浴火重生,成為螞蟻金服全部核心系統的承載資料庫。這一路走來的艱辛和故事,螞蟻
Java程式設計技術分享:Java併發之Fork-Join框架分析
1、什麼是Fork/Join框架 及產生背景 Fork/Join框架是Java7提供了的一個用於並行執行任務的框架, 是一個把大任務分割成若干個小任務,最終彙總每個小任務結果後得到大任務結果的框架。上邊是書上的定義。 我們用粗話說:Fork/Join是一個框架,來解決執行效率,手段是並行,但
理解OpenShift(1):網路之 Router 和 Route Neutron 理解 (7): Neutron 是如何實現負載均衡器虛擬化的
理解OpenShift(1):網路之Router 和 Route 1. OpenShift 為什麼需要 Router 和 Route? 顧名思義,Router 是路由器,Route 是路由器中配置的路由。OpenShift 中的這兩個概念是為了解決從叢集外部(就是從除了叢集節點
vue技術分享之你可能不知道的7個祕密
本文是vue原始碼貢獻值Chris Fritz在公共場合的一場分享,覺得分享裡面有不少東西值得借鑑,雖然有些內容我在工作中也是這麼做的,還是把大神的ppt在這裡翻譯一下,希望給朋友帶來一些幫助。 一、善用watch的immediate屬性 這一點我在專案中也是這麼寫的。例如有請求需要再也沒初
理解OpenShift(2):網路之 DNS(域名服務)
理解OpenShift(1):網路之 Router 和 Route 理解OpenShift(2):網路之 DNS(域名服務) OpenShift 叢集中,至少有三個地方需要用到 DNS: 一是Pod 中的應用通過域名訪問外網的時候,需要DNS來解析外網的域名 二是在叢集內部(p
web前端課程技術分享之關於rem佈局和vw佈局的理解
通過rem來實現響應式佈局: 首先來看,什麼是rem單位。rem是一個靈活的、可擴充套件的單位,由瀏覽器轉化畫素並顯示。與em單位不同,rem單位無論巢狀層級如何,都只相對於瀏覽器的根元素(HTML元素)的font-size。預設情況下,h
web前端課程技術分享之關於rem布局和vw布局的理解
phone 都是 vma 通過 改變 之前 控制 wid font 通過rem來實現響應式布局: 首先來看,什麽是rem單位。rem是一個靈活的、可擴展的單位,由瀏覽器轉化像素並顯示。與em單位不同,rem單位無論嵌套層級如何,都只相對於瀏覽器的根元素(HTML元素)的fo
知乎技術分享:從單機到2000萬QPS併發的Redis高效能快取實踐之路
本文來自知乎官方技術團隊的“知乎技術專欄”,感謝原作者陳鵬的無私分享。 1、引言 知乎儲存平臺團隊基於開源Redis 元件打造的知乎 Redis 平臺,經過不斷的研發迭代,目前已經形成了一整套完整自動化運維服務體系,提供很多強大的功能。本文作者陳鵬是該系統的負責人,本次文
技術分享之字串(es5和es6的對比)
2015年釋出了javascript的新規範es6,相比於之前的es5變動很大,這就需要我們花費一些時間去學習並使用。 字串作為一種基本資料型別,在講解它之前,我們來看一下資料型別方面es6相對於es5有哪些變化? 首先我們看一下es5的資料型別: es5包括兩種資料型別 1.基本資料型
vue技術分享之你可能不知道的7個秘密
med 動態 targe lod immediate 前端開發 class elements 傳遞 本文是vue源碼貢獻值Chris Fritz在公共場合的一場分享,覺得分享裏面有不少東西值得借鑒,雖然有些內容我在工作中也是這麽做的,還是把大神的ppt在這裏翻譯一下,希望給
技術分享丨從Hadoop到Spark,看大資料框架發展之路
談到大資料框架,不得不提Hadoop和 Spark,今天我們進行歷史溯源,幫助大家瞭解Hadoop和Spark的過去,感應未來。 在Hadoop出現前人們採用什麼計算模型呢?是典型的高效能HPC workflow,它有專門負責計算的compute cluster,cluster memory很小
理解OpenShift(1):網路之 Router 和 Route
1. OpenShift 為什麼需要 Router 和 Route? 顧名思義,Router 是路由器,Route 是路由器中配置的路由。OpenShift 中的這兩個概念是為了解決從叢集外部(就是從除了叢集節點以外的其它地方)訪問服務的需求。不曉得為什麼OpenShift 要將Kubernetes
技術人生:故事之十 網路是什麼?
故事之十 網路是什麼? 我做網路到第五個年頭的時候,發現問題了,使用者們並不清楚網路是什麼,我在報刊上詳細介紹了NT構造網路的方案,並用了一段被同事們常重複的話:網路,網路,網路到底是什麼。 說起來可笑,我也糊塗網路是什麼了,因為它太抽象又太具體。於是
【Android】開發乾貨-技術分享之AndResGuard資源混淆的使用
AndResGuard是微信團隊的開源專案,它的作用就是將apk中的資原始檔如layout等檔名進行混淆處理,增加逆向難度。 1.下載AndResGuard 2.下載完成後,修改配置檔案,此處因
阿里雲機器學習技術分享1——影象識別之TensorFlow實現方法【視訊+PPT】
阿里雲AI之影象識別技術是如何實現的!?視訊+PPT乾貨奉上 講師簡介:趙昆 阿里巴巴機器學習技術專家 歡迎加入阿里雲機器學習大家庭,**釘釘群:11768691** , QQ群:567810612 一、阿里雲機器學習之影象識別實踐-基礎篇: 觀看視訊:http://cl