Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kubernetes连载之10 Networking #17

Open
andy2046 opened this issue Apr 17, 2018 · 0 comments
Open

Kubernetes连载之10 Networking #17

andy2046 opened this issue Apr 17, 2018 · 0 comments

Comments

@andy2046
Copy link
Owner

Understanding the Kubernetes networking model

Kubernetes网络模型基于扁平的地址空间。集群中的所有pods可以直接看到对方。每个pod都有自己的IP地址。没有必要配置任何NAT。同一个pod中的容器共享它们的pod IP地址并且可以通过本地主机相互通信。这种模式非常有见地,一旦建立起来,它就可以为开发人员和管理员简化生活。这使得将传统网络应用程序迁移到Kubernetes变得非常简单。一个pod代表一个传统的节点,每个容器代表一个传统的进程。

Intra-pod communication (container to container)

正在运行的pod始终安排在一个(物理或虚拟)节点上。这意味着所有的容器都运行在同一个节点上,并且可以以各种方式相互通信,例如本地文件系统,任何IPC机制或使用本地主机和知名端口。不同Pod之间不会有端口冲突的危险,因为每个Pod都有自己的IP地址,并且当Pod中的容器使用本地主机时,它仅使用Pod的IP地址。因此,如果pod1中的容器1连接到容器2在pod1上侦听的端口1234,则它不会与pod2中运行在同一节点上的另一个容器发生冲突,该容器也侦听端口1234。唯一需要注意的是,将端口再次暴露给主机,那么你应该小心pod到节点的亲近关系。这可以使用几种机制来处理,例如DaemonSet和pod anti-affity。

Inter-pod communication (pod to pod)

Kubernetes中的Pod分配了一个网络可见的IP地址(不是专用于节点)。Pod无需借助网络地址转换,代理或任何其他混淆层即可直接进行通信。众所周知的端口号可以用于无配置的通信方案。该Pod的内部IP地址与其他Pod所看到的外部IP地址相同(在集群网络内,未暴露于外部世界)。这意味着标准的命名和发现机制(如DNS)可以直接使用。

Pod to service communication

pod可以使用IP地址和知名端口直接相互交谈,但这需要pods知道彼此的IP地址。在Kubernetes集群中,pod可以不断关闭和创建。服务提供了一个非常有用的间接层,因为即使响应请求的实际Pod的集合不断变化,服务也是稳定的。此外,你可以获得自动,高度可用的负载平衡,因为每个节点上的Kube-proxy负责将流量重定向到正确的pod。

External access

一些容器需要从外部访问。pod IP地址在外部不可见。这种情况下服务是正确的工具,但外部访问通常需要两个重定向。例如,云提供商负载均衡器不能将流量直接传输到特定服务,直接传输到运行可处理该请求的pod的节点。相反,公共负载平衡器只是将流量指向集群中的任何节点,并且如果当前节点未运行必需的pod,该节点上的Kube-proxy将再次重定向到适当的pod。

Kubernetes networking versus Docker networking

Docker网络遵循不同的模式,但随着时间的推移,它开始倾向于Kubernetes模型。在Docker网络中,每个容器都有自己的私有IP地址,这些私有IP地址来自限制在其自己节点上的172.xxx.xxx.xxx地址空间。它可以通过自己的172.xxx.xxx.xxx与同一节点上的不同的IP地址的其他容器通信。这对于Docker来说是有意义的,因为它没有带有多个交互容器的pod的概念,因此它将每个容器都模型化为具有自己网络标识的轻量级VM。请注意,对于Kubernetes,来自不同Pod的容器在同一节点上运行时无法通过localhost进行连接(除非公开host端口,这是不鼓励的)。总的来说,Kubernetes可以在任何地方杀死和创建pod,所以不同的pod通常不应该依赖于节点上可用的其他pod。DaemonSets是一个例外,但Kubernetes网络模型旨在适用于所有用例,并且不会为同一节点上不同Pod之间的直接通信添加特殊情况。

Docker容器如何跨节点进行通信,容器必须将端口发布到主机。这显然需要协调端口,因为如果两个容器尝试发布相同的主机端口,它们将会相互冲突。然后容器(或其他进程)连接到主机的映射到容器的端口。一个很大的缺点是容器不能自行注册外部服务,因为他们不知道主机的IP地址是什么。你可以通过在运行容器时将主机的IP地址作为环境变量来解决此问题,但这需要外部协调并使过程复杂化。

Lookup and discovery

为了使pod和容器相互通信,他们需要找到彼此。容器可以通过几种方式找到其他容器或自行发布。还有一些架构模式允许容器间接进行交互。每种方法都有其优点和缺点。

Self-registration

当容器运行时,它知道其pod的IP地址。每个容器想要访问集群中的其他容器可以连接到某个注册服务并注册其IP地址和端口。其他容器可以向注册服务查询所有注册容器的IP地址和端口并连接到它们。当容器被正常地销毁时,它将取消自己的注册。如果一个容器不正常地死亡,那么需要建立一些机制来检测这个机制。例如,注册服务可以定期ping所有注册的容器,或者定期要求容器向注册服务发送一个keepalive消息。

自注册的好处是,一旦通用注册服务到位(不需要为不同目的而定制它),就不必担心跟踪容器。另一个巨大的好处是,容器可以采用复杂的政策,并根据自身情况决定暂时取消注册,例如,如果一个容器很忙,并且现在不想再收到任何请求。这种智能和分散式动态负载均衡在全局范围内实现起来非常困难。缺点是注册服务是容器需要知道的另一个非标准组件,以便找到其他容器。

Services and endpoints

Kubernetes服务可被视为注册服务。属于某一服务的pods将根据其标签自动注册。其他Pod可以查找端点来找到所有服务pods或利用服务本身,直接将消息发送到将被路由到其中一个后端pod的服务。

Loosely coupled connectivity with queues

如果容器可以在不知道IP地址和端口的情况下相互通话,会怎么样?如果大多数通信可以是异步和解耦的呢?在很多情况下,系统可以由松散耦合的组件组成,这些组件不仅不知道其他组件的身份,而且他们不知道其他组件也存在。队列促进了这种松散耦合的系统。组件(容器)监听队列中的消息,响应消息,执行其作业,并将消息(进度,完成状态和错误)发布到队列中。队列有很多好处:

  • 无需协调即可轻松添加处理能力,只需添加更多收听队列的容器即可
  • 易于通过队列深度跟踪整体负载
  • 通过对消息或主题进行版本控制,可以轻松地将多个版本的组件并行运行
  • 通过让多个消费者以不同模式处理请求,易于实现负载平衡以及冗余

队列的缺点如下:

  • 需要确保队列提供适当的高可用性,不会成为关键的SPOF(single point of failure)
  • 容器需要使用异步队列API(可以抽象出来)
  • 实现请求-响应需要在响应队列上繁琐的监听

总的来说,队列是大型系统的极好机制,它们可以用于大型Kubernetes集群以减轻协调难度。

Loosely coupled connectivity with data stores

另一个松耦合的方法是使用数据存储(例如,Redis)来存储消息,然后其他容器可以读取它们。但存储不是数据存储的设计目标,其结果往往繁琐,且不具备最佳性能。数据存储针对数据的存储而不是通信进行了优化。也就是说,数据存储可以与队列一起使用,在该队列中,组件将某些数据存储在数据存储中然后向队列发送消息,表明数据已准备好可以处理。多个组件监听消息,并且所有组件都开始并行处理数据。

Kubernetes ingress

Kubernetes提供了一个ingress资源和控制器,旨在向外界展示Kubernetes服务。你可以自己做,但是对于特定类型的ingress(例如Web应用程序,CDN或DDoS保护程序),定义ingress的许多任务在大多数应用程序中很常见。你也可以编写自己的入口对象。

ingress对象通常用于智能负载平衡和TLS终端。你不必配置和部署自己的Nginx服务器,而可以从内置ingress获益。

Kubernetes network plugins

Linux默认情况下具有单个共享网络空间。物理网络接口都可以在这个命名空间中访问。但物理名称空间可以分为多个逻辑名称空间,这与容器网络非常相关。

网络实体由其IP地址标识。服务器可以侦听多个端口上的传入连接。客户端可以连接(TCP)或发送数据(UDP)到网络内的服务器。

命名空间将一堆网络设备分组,以便它们可以在同一个命名空间中访问其他服务器,但不能与其他命名空间服务器连接,即使它们在物理上位于同一网络中。链接网络或网段可以通过网桥,交换机,网关和路由来完成。

虚拟以太网(veth)设备代表物理网络设备。当您创建链接到物理设备的veth时,你可以将该veth(扩展物理设备)分配到命名空间,其中来自其他命名空间的设备无法直接访问它,即使它们在物理上位于同一本地网络中。

网桥将多个网段连接到一个聚合网络,因此所有节点都可以相互通信。桥接在OSI网络模型的L1层(物理层)和L2层(数据链路层)完成。

路由连接单独的网络,通常基于路由表,指示网络设备如何将数据包转发到目的地。路由通过各种网络设备完成,例如路由器,网桥,网关,交换机和防火墙。

最大传输单元(MTU)决定了数据包的大小。例如,在以太网上,MTU为1,500字节。MTU越大,有效载荷和头部之间的比率越好,这是一件好事。但缺点是最小延迟会降低,因为你必须等待整个数据包到达,而且如果发生故障,你必须重新发送整个大数据包。

Kubenet

Kubenet是一个网络插件。它只是创建一个名为cbr0的Linux桥接器,并为每个pod创建一个veth。云提供商通常使用它来为节点之间的通信或单节点环境中的通信设置路由规则。veth对用主机IP地址范围的IP地址将每个pod连接到其主机节点。Kubenet插件具有以下要求:

  • 必须为节点分配一个子网,以便为其Pod分配IP地址
  • 在0.2.0或更高版本中需要标准的CNI bridge lo和host-local插件
  • Kubelet必须使用--network-plugin = kubenet参数运行
  • Kubelet必须使用--non-masquerade-cidr = 参数运行

MTU对网络性能至关重要。像Kubenet这样的Kubernetes网络插件尽最大努力推导出最佳的MTU,但有时他们需要帮助。例如,如果现有网络接口(例如Docker docker0网桥)设置了一个小的MTU,那么Kubenet将重新使用它。另一个例子是IPSEC,由于IPSEC封装的额外开销,需要降低MTU,但Kubenet网络插件没有考虑到这一点。解决方案是避免依赖MTU的自动计算,只需通过提供给所有网络插件的--network-plugin-mtu命令行开关告诉Kubelet网络插件应使用什么MTU。尽管目前只有Kubenet网络插件负责该命令行切换。

Container networking interface

容器网络接口(CNI)是一个规范,也是一组用于编写网络插件以在Linux容器(不仅仅是Docker)中配置网络接口的库。该规范实际上是从rkt网络提案演变而来的。

CNI为网络应用程序容器定义了一个插件规范,但插件必须插入提供某些服务的容器运行时。在CNI的情况下,应用程序容器是可网络寻址的实体(具有自己的IP地址)。对于Docker,每个容器都有自己的IP地址。对于Kubernetes,每个pod都有自己的IP地址,pod是CNI容器,而不是pod内的容器。

CNI插件的工作是将网络接口添加到容器网络名称空间中,并通过veth对 将容器桥接到主机。然后它应该通过IPAM(IP地址管理)插件和设置路由分配一个IP地址。

容器运行时(rkt或Docker)将CNI插件作为可执行文件调用。该插件需要支持以下操作:

  • 将一个容器添加到网络
  • 从网络中移除容器
  • 报告版本

该插件使用简单的命令行界面,标准输入/输出和环境变量。JSON格式的网络配置通过标准输入传递给插件。其他参数被定义为环境变量:

  • CNI_COMMAND:表示所需的操作; ADD,DEL或VERSION。
  • CNI_CONTAINERID:容器ID。
  • CNI_NETNS:网络命名空间文件的路径。
  • CNI_IFNAME:要设置的接口名称;插件必须遵守此接口名称或返回错误。
  • CNI_ARGS:用户在调用时传入的额外参数。以分号分隔的字母数字键值对,例如FOO = BAR; ABC = 123。
  • CNI_PATH:搜索CNI插件可执行文件的路径列表。路径由操作系统特定的列表分隔符分隔,例如在Linux上是:在Windows上是;。

如果命令成功,插件将返回零退出代码,并将生成的接口(在ADD命令的情况下)作为JSON流式传输到标准输出。这种低技术的界面很智能,因为它不需要任何特定的编程语言或组件技术或二进制API。CNI插件作者也可以使用他们最喜欢的编程语言。

Using network policies effectively

Kubernetes网络策略是关于管理到选定的Pod和命名空间的网络流量。在微服务的世界中,Kubernetes经常进行数百种服务的部署和管理,管理pods网络和可连接性至关重要。重要的是要明白,它不是一种主要的安全机制。如果攻击者可以访问内部网络,他们可能会创建符合现有的网络策略的自己的Pod并与其他Pod自由通信。

Understanding the Kubernetes network policy design

网络策略是关于如何选择Pod与其他网络端点进行通信的一个说明。除了给定命名空间的隔离策略允许的内容以外,NetworkPolicy资源还使用标签来选择pod并定义白名单规则,以允许通往选定pods的流量。

Network policies and CNI plugins

网络策略和CNI插件之间有着复杂的关系。一些CNI插件实现了网络连接和网络策略,而其他一些插件只实现了一个方面,但它们可以与另一个实现其他方面的插件(例如Calico和Flannel)协作。

Configuring network policies

apiVersion: extensions/v1beta1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
  matchLabels:
    role: db
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          project: awesome-project
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: tcp
      port: 6379

Implementing network policies

虽然网络策略API本身是通用的,并且是Kubernetes API的一部分,但实施与网络解决方案紧密结合。这意味着在每个节点上都有一个特殊的代理或gatekeeper来执行以下操作:

  • 拦截进入节点的所有流量
  • 验证它是否遵守网络策略
  • 转发或拒绝每个请求

Kubernetes提供了通过API定义和存储网络策略的工具。执行网络策略由网络解决方案或与特定网络解决方案紧密集成的专用网络策略解决方案决定。

Load balancing options

负载均衡是Kubernetes集群等动态系统的关键功能。节点,虚拟机和pod来来去去,但客户端无法跟踪哪些个体可以为他们的请求提供服务,即使他们可以,它也需要一个机制管理集群动态映射的复杂性,并且处理断开连接,无响应或缓慢的节点。负载平衡是一种经过战斗考验且充分理解的机制,它增加了一层间接方法,可以隐藏集群外部客户或消费者的内部动荡。有外部和内部负载均衡器的选项。你也可以混合搭配使用。混合方法有其自身的优点和缺点,如性能与灵活性。

External load balancer

外部负载均衡器是在Kubernetes集群外部运行的负载均衡器,但必须有一个外部负载均衡器提供程序,Kubernetes才能与其进行交互,以运行状况检查,防火墙规则配置,并获取负载平衡器的外部IP地址。

Configuring an external load balancer

{
  "kind": "Service",
  "apiVersion": "v1",
  "metadata": {
    "name": "example-service"
  },
  "spec": {
    "ports": [{
      "port": 8765,
      "targetPort": 9376
    }],
    "selector": {
      "app": "example"
    },
    "type": "LoadBalancer"
  }
}

Finding the load balancer IP addresses

负载均衡器将有两个IP地址。内部IP地址可以在集群内用于访问服务。外部IP地址是集群外部的一个客户端将使用的地址。为外部IP地址创建DNS条目是一种很好的做法。要获取这两个地址,请使用kubectl describe命令。IP表示内部IP地址。LoadBalancer ingress表示外部IP地址:

> kubectl describe services example-service
    Name:  example-service
    Selector:   app=example
    Type:     LoadBalancer
    IP:     10.67.252.103
    LoadBalancer Ingress: 123.45.678.9
    Port:     <unnamed> 80/TCP
    NodePort:   <unnamed> 32445/TCP
    Endpoints:    10.64.0.4:80,10.64.1.5:80,10.64.2.4:80
    Session Affinity: None
    No events.

Understanding potential in even external load balancing

外部负载均衡器在节点级别运行,虽然它们将流量引导至特定的pod,但负载分配是在节点级完成的。这意味着如果你的服务有四个pods,其中三个pods在节点A上,最后一个在节点B上,那么外部负载平衡器很可能在节点A和节点B之间平均分配负载.这样节点A上的三个pods处理一半的负载(每个1/6),而节点B上的单个pod单独处理另一半的负载。未来可能会增加weight来解决这个问题。

Service load balancer

服务负载平衡旨在汇集Kubernetes集群内的内部流量,而不是用于外部负载平衡。这是通过使用clusterIP的服务类型完成的。通过使用服务类型的NodePort,可以直接通过预分配的端口公开服务负载平衡器,并将其用作外部负载平衡器,但它不是为该用例设计的。例如,SSL协商和HTTP缓存等令人满意的功能将不可用。

Ingress

Kubernetes中的Ingress的核心是一组允许入站连接到达集群服务的规则。另外,一些入口控制器支持以下内容:

  • 连接算法
  • 请求限制
  • URL重写和重定向
  • TCP/UDP负载平衡
  • 访问控制和授权

Ingress是使用ingress资源指定的,并由ingress控制器提供服务。以下是管理两项服务的流量的ingress资源示例。规则将外部可见的http://foo.bar.com/foo映射到s1服务,将http://foo.bar.com/bar映射到s2服务:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: s1
          servicePort: 80
      - path: /bar
        backend:
          serviceName: s2
          servicePort: 80
@andy2046 andy2046 changed the title Kubernetes连载之10 Kubernetes连载之10 Networking May 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant