赞
踩
本文是《云计算网络极速入门》三部曲:
《云计算网络极速入门-容器网络》
《云计算网络极速入门-混合云网络》
的第二篇。
前一篇文章介绍的虚拟机网络主要通过虚拟化来实现,而容器网络的实现机制不同,主要通过资源的隔离机制来实现。容器技术的兴起主要是由于虚拟机存在两大无法回避的问题:
Hypervisor自身资源消耗较大,同时磁盘IO性能低下。
一个虚拟机本身仍是一个独立的操作系统,这对诸如微服务或者函数式应用来说仍显得太重了。
图1是容器技术的架构实现原理图,为了便于读者理解,每一个架构层的典型技术或者代表产品也在图上用红字标注。
图1-容器技术整体架构
其中运行引擎和资源管理都是单机程序。运行引擎类似虚拟化中的KVM和Xen,运行在服务器和操作系统之上,接受上层集群管理系统的管理,运行引擎最典型的代表就是Docker,它运行在Linux上,但不仅仅Linux有容器技术,Windows系统也有自己的容器化技术,叫Hyper-V。
在容器资源管理上,Docker最早使用LXC(Linux Container),后来开发了自己的LibContainer;而CoreOS则开发了rkt;微软则于2016年发布了基于Windows的容器技术Hyper-V Container,原理与Linux的容器技术类似。
容器是很轻量化的技术,相对于物理机和虚拟机而言,在等量资源的基础上能创建出更多的容器实例。一旦面对着分布在多台主机上且拥有数百个容器的大规模应用时,传统的或单机的容器管理解决方案就会变得力不从心。另一方面,由于为微服务提供了越来越完善的原生支持,在一个容器集群中的容器粒度越来越小、数量越来越多,这种情况下,容器或微服务都需要接受管理并有序接入外部环境,从而实现调度、负载均衡等任务。这就是容器编排。简单而高效地管理快速增长的容器实例,是容器编排系统的主要任务,而K8S(kubernetes)则是此方面的当红选手,下面我们重点介绍K8S。
K8S是主从模式的分布式集群架构,其整体架构如图2所示,共包含Master和Node两种节点:
Master:控制节点,调度管理整个系统;
Node:运行节点,运行业务容器。每个Node上运行多个Pod,每个Pod中可以运行多个容器(通常一个Pod中只部署一个容器,也可以将一些高度耦合的容器部署在一起);然而Pod无法直接对来自K8S集群外部的访问提供服务。
图2-Kubernetes架构
K8S的Master节点包含如下4大组件:
etcd:K8S用于存储各个资源状态的分布式数据库,采用Raft协议作为一致性算法。
API Server(kube-apiserver):主要通过REST提供认证和授权、管理API版本等功能,任何对资源(Pod、Deployment、Service等)进行增、删、改、查等操作都要交给API Server处理后再提交给etcd。
Scheduler(kube-scheduler):负责调度Pod到合适的Node上,根据集群的资源和状态选择合适的节点创建Pod,Scheduler的输入是Pod和多个Node组合的列表,输出是Pod和一个Node的绑定,即将Pod部署到Node上。K8S提供了调度算法的实现接口,用户可以根据自己的需求定义自己的调度算法。
Controller Manager:K8S的后台,每个资源一般都对应一个控制器,Controller Manager就是负责管理这些控制器的,Pod创建之后,保证Pod的状态始终和预期一样的重任就由Controller Manager去完成了。
“容器集”Pod是K8S的基本操作单元,也是应用运行的载体。Pod为容器分组增加了一个抽象层,有助于调整工作负载(均衡),并为这些容器提供所需的联网和存储等服务。一个Pod表示一组容器所形成的集合,以及这些容器所共享的一些资源,这些资源包括:
共享的存储,如卷(Volume)
网络
运行这些容器所需的相关信息,如容器镜像版本、使用的特定端口等。
同一个Pod下的多个容器共用一个IP,这就要求这些容器中不能出现同样的端口号,比如一个Pod下运行两个Apache服务器,就会有一个容器异常。同一个Pod下的多个容器可以使用localhost加端口号访问对方的端口。
Node可以是虚拟机,也可以是物理机,但不管是那种,都统一由Master来管理。Node上可以有1~N个Pod,Master会根据集群中每个Node上的可用资源情况自动地调度Pod的部署。Node包含如下3大组件:
kubelet:是Master在每个Node节点上的agent,主要负责容器管理,包括Master和Node之间的通信、管理Pod和容器。
kube-proxy:实现了K8S中的服务发现和反向代理功能(面向应用),K8S通过它实现虚拟IP路由及转发,每个Node上都需要部署Proxy组件,从而实现K8S层级的虚拟转发网络。
反向代理:支持TCP和UDP连接转发,默认基于Round Robin算法将客户端流量转发到与Service对应的一组后端Pod。
服务发现:使用etcd的监听机制,监控集群中Service和Endpoint对象数据的动态变化,并且维护一个Service到Endpoint的映射关系,从而保证了后端Pod的IP变化不会对访问者造成影响。此外,kube-proxy还支持session affinity(会话关联)。
一个容器:负责从Registry拉取容器镜像、解压缩容器及运行应用程序等工作。
K8S中Pod的迁移,实际指的是在新Node上重建Pod。当某个Pod失败时,首先会被K8S清理掉,之后Replication Controller会在其他机器(或本机)上重建Pod,重建之后Pod的ID发生了变化,与原有的Pod将拥有不同的IP地址,因而将会是一个新的Pod。
node上包含两大核心概念:
Replication Controller(RC):是K8S的一个核心概念,它会确保任何时间K8S中都有指定数量的Pod在运行。同时提供弹性伸缩(根据负载的高低动态调整Pod的副本数量)、滚动升级(逐步替代策略,同步Pod的不同版本来实现)等能力。RC与Pod的关联是通过Label(标签)来实现的,Label不具有唯一性,为了准确标识Pod,应该为Pod设置多个维度的Label。此外,新一代的Service命名为Replica Set,引入了对基于子集的selector查询条件,而RC仅支持基于值相等的selector查询条件。
Service:K8S的另一个核心概念,是服务的抽象,定义了一个Pod的逻辑分组,和访问这些Pod的策略,执行相同任务的Pod可以组成一个Service,并以Service的IP(VIP,不是一个真实的IP地址,在外部无法寻址)提供服务。Service的目标是提供一种桥梁,它会为访问者提供一个固定的访问地址。用于在访问时重定向到相应的后端。Service同RC一样,也是通过Label来关联Pod的。一组Pod能够被Service访问到,通常是通过Label selector实现的。Service负责将外部的请求发送到Kubernetes内部的Pod,同时也将内部Pod的请求发送到外部,从而实现服务请求的转发。Service可以作为Pod的访问入口,起到代理服务器的作用。K8S为每个Service分配一个唯一的ClusterIP,所以当使用<ClusterIP:Port>的组合访问一个Service的时候,不管Port是什么,这个组合是一定不会发生重复的。
此外,K8S还有Deployment和Namespace两大概念:
Deployment:K8S提供了一种简单的更新RC和Pod的机制,叫做Deployment,它使用了RS(Replica Set)。通过在Deployment中描述所期望的集群状态,Deployment Controller会将现在的集群状态在一个可控的速度下逐步更新成期望的集群状态。Deployment的主要职责同样是为了保证Pod的数量和健康,继承了上面所述的Replication Controller全部功能(90%的功能与RC完全一样),可以看出是新一代的RC。但它又具有新的特性:
时间和状态查看:可以查看Deployment的升级详细进度和状态。
回滚
版本记录:每次Deployment操作都能够记录下来
暂停和启动
Namespace:不同于Linux的Namespace。而是对于同一个物理集群,K8S可以虚拟出多个虚拟集群,这些虚拟集群即称为Namespace。可以通过Namespace将一个集群的资源分配给多个用户(IaaS)。
K8S的默认容器实现是Docker,Docker容器基本上成为了容器引擎的事实标准。我们本篇文章就以Docker为例来介绍容器网络。
Docker最初只支持三种网络驱动(bridge、none、host),在引入Libnetwork之后,又增加了overlay、remote driver、macvlan等驱动类型。Docker在Libnetwork中使用它主导的CNM(Container Network Model)虚拟化网络模型,提供了可以用于开发多种网络驱动的标准化接口和组件。用户能以驱动/插件的形式,使用其它特定类型的网络插件实体,例如overlay。CNM中主要有沙盒(sandbox)、端点(endpoint)和网络(network)3种组件:
沙盒:一个沙盒包含了一个容器网络栈的信息。沙盒可以对容器的接口(interface)、路由和DNS设置等进行管理。沙盒的实现可以是Linux network namespace、FreeBSD Jail或类似的机制。一个沙盒可以有多个端点和网络。
端点:一个端点可以加入一个沙盒和一个网络。端点的实现可以是veth pair、Open vSwitch内部端口或类似的设备。一个端点可以属于一个网络并且只属于一个沙盒。
网络:一个网络是一组可以直接互相联通的端点。网络的实现可以是Linux Bridge、VLAN等。一个网络可以包含多个端点。
目前通过不同的驱动,Docker可以实现不同类型的网络,以下是Dock常用的几种网络驱动:
Bridge驱动:默认的网络驱动,创建的网络为桥接网络。桥接网络适用于在同一个主机上运行的容器直接的通信。
host驱动:使用host作为驱动程序,容器直接使用宿主机网络,并去除容器与宿主机之间的网络隔离。
overlay网络驱动:可以连接多个宿主机,从而使Swarm服务之间能够互相通信。
macvlan网络驱动:为容器分配mac地址,从而使其显示为网络上的物理设备。
network plugin:可以从Docker Store或第三方供应商处获得网络插件。
none:使用none驱动的容器,被禁止使用网络。
Docker默认设置下使用Linux bridge驱动来实现Bridge网络。此时Docker会自动生成一个虚拟网桥(docker0),并且从RFC1918定义的私有地址块中为其分配一个子网。Docker为每一个生成的容器分配一个已连接至网桥的虚拟以太网设备veth,而veth则通过Linux Namespace在容器中映射显示为eth0,容器内部的eth0会在网桥的地址段内被分配一个IP地址。
Bridge网络下,仅当多个Docker容器处于同一个物理主机内时,才能互相通信。为了使得跨主机的Docker容器间能够实现互相通信,必须要在主机自身的IP地址上为它们分配端口并将这些端口转发或代理至容器处,如图3所示。
图3-Docker的网络实现
显然,同一主机上的容器之间必须相互协调,小心使用分配的端口,或是动态分配这些端口,以防止端口冲突。
K8S作为容器的编排框架,在网络层面,并没有进入更底层的具体容器互通互联的网络解决方案的设计中,而是将网络功能一分为二,主要关注Kubernetes服务在网络中的暴露以及Pod自身网络的配置,至于Pod具体需要配置的网络参数以及Service、Pod之间的互联互通,则交给CNI(容器网络接口规范)来解决。这样K8S本身不会具有太复杂的网络功能,从而能够将更多的精力放在K8S服务和容器的管理上,最终能够对外呈现一个易于管理、可用性高的应用服务集群。
我们前面介绍了Docker的CNM容器模型,而CNI则是由Google、CoreOS、K8S主导的容器网络模型,它们之间是直接竞争关系,但不管是CNM还是CNI只是容器网络的规范,并不是网络实现,从开发者的角度看,它们就是一堆接口。CNI的接口主要包含4大方法:
添加网络
删除网络
添加网络列表
删除网络列表
CNI插件必须是实现为一个可执行文件,这个可执行文件再被容器编排管理系统(例如K8S)所调用。CNI插件负责将网络接口插入容器网络命名空间(例如veth pair的一端),并在主机上进行任何必要的改变(例如将veth的另一端连接到网桥),然后将IP分配给接口,并通过调用适当的IPAM(IP Address Management :IP地址管理)插件来设置路由。一些常见的CNI插件如下:
Flannel:CoreOS开源的网络方案,负责为K8S集群中的多个Node间提供Layer3的IPV4网络,它的底层通信可以选择UDP、VxLAN、AWS VPC等不同协议,默认为UDP协议,部署和管理相对简单。
Calico:一个基于BGP的纯三层的数据中心网络方案。它主要通过Linux内核实现一个高效的vRouter来负责数据转发,基于iptables创建相应的路由规则。
Weave Net:会创建一个虚拟网络,实现跨主机的Docker容器互联,并提供了自动发现机制。
Contiv:思科开源方案,兼容CNI和CNM模型,它的特点是用户可以根据容器实例IP地址直接进行访问。
CNI-Genie:支持为一个Pod分配多个IP地址,每个IP地址由不同的CNI插件提供。
Cilium:主要运行在网络Layer3层及Layer4层,以一种不干涉运行在容器中的应用程序的方式,提供了安全的网络连接和负载均衡。
Contrail:基于OpenContrail,是一个真正开放的多云网络虚拟化和策略管理平台。Contrail和OpenContrail已经与各种编排系统集成,如K8S、OpenShift、OpenStack和Mesos,并为容器或Pod、虚拟机和裸机的工作负载提供了不同的隔离模式。
Multus:可以解决K8S的Pod默认不支持多网卡配置的问题。Multus自己不会进行任何网络配置,而是调用其它CNI插件来实现多个网络接口配置。
有了CNI的支持,就可以在K8S中很容易的构建各种网络,接下来,我们就来看看,容器网络下的各类应用场景是如何实现的。
K8S为每一个Pod分配一个IP地址,且同一个Pod内的容器共享Pod的网络命名空间。K8S在创建Pod时,会首先在Node节点上创建一个运行在Docker Bridge网络上的“Pod容器”,并为这个Pod容器创建虚拟网卡eth0及分配IP地址。而Pod里的容器(称为APP容器),只需要在创建时使用--net=container:<id>加入该网络命名空间,这样所有的Docker容器就可以都运行在同一个网络命名空间中。
K8S这种“IP-per-pod”网络模型,能让Pod里的容器之间通过localhost网络访问,但这同时也意味着Pod里的容器必须有效协调使用端口,在端口分配上不能发生冲突。由于不同的Pod之间拥有不同的IP,Pod里的容器根本不用担心和其它Pod里的容器发生端口冲突。
由于每一个Pod都有一个真实的全局IP地址,因此同一个Node内的不同Pod之间可以直接采用对方的IP地址通信,而且不需要使用DNS等其它发现机制。
图4-同一Node内的Pod通信架构
如图4所示,在Pod1、Pod2的Linux协议栈上,默认路由都是Docker0的地址,也就是说所有非本地地址的网络数据,都会被默认发送到Docker0网桥上,由Docker0网桥直接中转。由于Pod1和Pod2都关联在同一个Docker0网桥上,位于同一个网段,它们之间是能直接通信的。
要想支持不同Node上的Pod间通信,要满足如下两个条件:
在整个K8S集群中对Pod的IP地址分配进行全局合理规划,不能有冲突。我们前面介绍了,Pod的调度是通过kube-scheduler来实现的,这个条件可以通过在kube-scheduler中进行规划的时候用诸如ETCD这样的统一存储服务保存IP来实现。
找到一种办法,将Pod的IP地址和所在Node的IP地址关联起来,让Pod之间可以互相访问。
因此,如图5所示,在实际环境中,除了部署K8S和Docker外,还需要额外的网络配置,甚至通过一些软件或插件来实现K8S对网络管控的要求。在此之上,Pod之间才能无差别地进行通信。
图5-不同Node的Pod之间通信架构
前面介绍了,K8S中的Service是一个抽象的实体,Kubernetes在创建Service实体时,为其分配了一个虚拟的IP,这个IP地址是相对固定的,此外,K8S还通过Service实现了负载均衡、服务发现和DNS等功能。当需要访问Pod里的容器所提供的功能时,不直接使用Pod的IP地址和端口,而是访问Service的这个虚拟IP和端口,再由Service把请求转发给它背后的Pod。
如图6所示,K8S在创建Service时,根据Service的标签选择器(Label Selector)来查找Pod,据此创建与Service同名的EndPoints对象,Service的targetPort和Pod的IP地址都记录在与Service同名的Endpoints里。当Pod的地址发生变化时,EndPoints也随之变化。当Service接收到请求时,就能通过EndPoints、找到请求需要转发的目标地址。
图6-K8S中的Service请求分发原理
Service只是一个虚拟概念,是一个抽象实体,为其分配的IP地址也只是一个虚拟的IP地址,这背后真正负责转发请求的是运行在Node上的kube-proxy。目前,kube-proxy共有三种请求转发模式,分别为userspace、iptables和ipvs:
userspace:kube-proxy会监控Master对Service和Endpoints对象的添加和删除操作。创建Service时,Node节点上的kube-proxy会为其随机开放一个端口(简称代理端口),然后建立一个iptables规则,iptables会完成<服务虚拟IP,端口>与代理端口的流量转发,再从Endpoints里选择一个Pod,把代理端口的流量转给该Pod。
iptables:这种模式下,创建Service时,Node节点上的kube-proxy会建立两个iptables规则,一个为Service服务,用于将<服务虚拟IP,端口>的流量转给后端,另一个为Endpoints创建,用于选择Pod。
ipvs:调用netlink接口以创建相应的ipvs规则,并定期与Service和Endpoint同步ipvs规则,从而确保ipvs状态与期望一致。ipvs模式需要在Node上预装ipvs内核模块。
根据应用场景的不同,K8S提供了4种类型的Service:
ClusterIP:默认类型。在集群内部的IP地址上提供服务,并且该类型的Service只能从集群内部访问。
NodePort:通过每个Node IP上的静态端口(NodePort)来对外提供服务,集群外部可以通过访问<NodeIP>:<NodeProt>来访问对应的端口。在使用该模式时,会自动创建ClusterIP,访问NodePort的请求会最终路由到ClusterIP。
LoadBalancer:通过使用云服务提供商的负载均衡器对集群外部提供服务。使用该模式时,会自动创建NodePort和ClusterIP,集群外部的负载均衡器最终会将请求路由给NodePort和ClusterIP。
ExternalName:将服务映射到集群外部的某个资源,例如foo.bar.example.com。
在生产环境中,为了保证安全和性能,不同功能的网络进行隔离是一个必须要的措施,一般常用的可以分为:
管理网络
控制网络
数据网络
这三种类型网络的隔离。这种隔离对于物理机和虚拟机来说很容易实现,但是在使用Kubernetes容器的Pod时,则会面临一些限制,尤其是现在K8S的Pod默认还不支持多网卡设置。
可以通过Multus来解决这个缺陷,Multus能够支持几乎所有的CNI插件,包括Flannel、DHCP和Macvlan等。Multus使用“delegates”的概念将多个CNI插件组合起来,并指定一个master plugin作为Pod的主网络并且被K8S感知。
Multus自己不会进行任何网络设置,而是调用其它插件(如Flannel、Calico)来完成真正的网络配置。它重用了Flannel中调用代理的方式,通过将多个插件分组,再按照CNI配置文件的顺序来调用它们。默认的网络接口名称为“eth0”,而Pod中的网络接口名称为net0、net1、...netX。通过这种方式,就可以让同一个Pod同时支持多个互相隔离的网络。
《Linux开源网络》
《Docker 容器与容器云》
《软件定义网络 SDN与OpenFlow解析》
《混合云架构》
《深度探索Linux系统虚拟化》
最后——
我把十几年的经验总结起来,出了本书,叫做《微服务治理:体系、架构及实践》,大家如果感兴趣可以关注一下。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。