kubernetes权威指南阅读笔记——第三章核心原理

时间:Nov. 28, 2017 分类:

目录:

3. kubernetes核心原理

包含API Server访问,分析Master节点Controller Manager各个组件的功能实现,Scheduler预选算法和优选算法,Node节点上的kubelet和kube-proxy组件的运行机制,安全机制和网络原理。

3.1 kubernetes API Server原理分析

kubernetes API Server的核心功能提供了kubernetes各类资源对象的增删改查和watch等HTTP Rest接口,为集群各个功能模块之间数据交互和通信的中心枢纽,是整个系统的数据总线和数据中心。

功能特性

  1. 集群管理的API入口,kubectl也是调用的这个入口
  2. 资源配额控制的入口
  3. 提供完备的集群安全机制

3.1.1 kubernetes API Server概述

通过kube-apiserver进程提供服务,该服务运行在Master节点。默认为8080端口(对应参数--insecure-port)提供Rest服务,也可以同时启动HTTPS安全端口(--secure-port=6443)来启动安全机制。

curl命令进行API请求

curl 127.0.0.1:8080/api

得到API版本的JSON

curl 127.0.0.1:8080/api/v1

支持的资源对象

curl 127.0.0.1:8080/api/v1/pods
curl 127.0.0.1:8080/api/v1/services

集群中Pod列表,Service列表

只想对外暴露部分Rest服务,可以在集群节点启动内部代理

kubectl proxy --reject-paths="^/api/v1/replicationcontrollers" --port=8001 --v=2

可以通过curl localhost:8001/api/v1/replicationcontrollers验证

采用白名单来限制非法客户端访问

--accept-hosts="^localhost$,^127\\.0\\.0\\.1,^\\[::1\\]$"

?待验证是全局吗

通过编程调用kubernetes API Server

运行在Pod内的用户进程调用kubernetes API

Elasticsearch?

原理是Pod内部直接访问一个名叫kubernetes的Service,因为kubernetes API Server本身就是个Service,ClusterIP是地址池第一个IP地址,服务端口为HTTPS的443端口。

开发基于kubernetes的管理平台

通过图形界面完成对Pod,Service,RC等资源的创建和管理,可以调用kubernetes和开源社区提供的Client Lib。

3.1.2 独特的kubernetes Proxy API接口

kubernetes Proxy API接口是代理Rest请求,即Kubernetes API Server把收到的请求转发到某个Node上的kubelet守护进程的Rest端口,由kubelet进行响应?有什么作用

关于Node相关的接口

/api/v1/proxy/nodes/{name}/pods/    #列出指定节点所有Pod信息
/api/v1/proxy/nodes/{name}/stats/   #列出指定节点物理资源的统计信息
/api/v1/proxy/nodes/{name}/spec/    #列出指定节点概要信息

不过需要注意API获取的Pod数据来自Node,而非etcd数据库,所以两者时间点会有偏差。

在kebulet启动的时候,可以指定--enable-debugging-handlers = true,Proxy API接口还会增加以下接口。

/api/v1/proxy/nodes/{name}/run/     #在节点上运行某个容器
/api/v1/proxy/nodes/{name}/exec/    #在节点的某个容器中运行某条命令
等等信息

通过接口直接访问Pod里某些容器提供的服务

/api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*}   #访问Pod的某个接口服务
/api/v1/proxy/namespaces/{namespace}/pods/{name}            #访问Pod

但是如何知道访问的是那个端口呢,在Pod启动多个端口的时候?

不过对于以上API都是用于管理和排除问题使用。

3.1.3 集群功能模块之间的通信

kubernetes API Server作为集群的核心,各个功能模块也通过API Server将数据存储到etcd中,当需要获取数据的时候也是通过API server来取,进而实现了各个模块的信息交互。

示例常见的交互场景,kubelet进程与API Server交互,每个Node的kubelet进程每隔一个时间周期,就会调用API Server的REST接口报告自身状态,当API server接收到这些信息,就会将这些信息更新到etcd中。另外kubelet也通过API Server的Watch接口监听Pod信息,如果新的Pod副本被调度绑定到本节点,并执行Pod对应的容器的创建和启动逻辑;如果监听到Pod对象被删除,则删除本节点上对应的Pod容器;如果监听到修改Pod的信息,则kubectl监听到变化后,会相应的修改本节点的Pod容器。

示例常见的交互场景,kube-controller-manager进程与API Server的交互,kube-controller-manager中的Node Controller模块通过API Service 提供的Watch接口,实时监控Node的信息,并做响应的处理。

示例常见的交互场景,kube-scheduler进程与API Server交互,当Scheduler通过API Server的Watch接口监听到新建Pod副本的信息后,它会检索所有符合该Pod要求的Node列表,开始执行Pod调度逻辑,调度成功后将Pod绑定到目标Node上。

我的理解是,执行Pod调度就是将Pod的信息重新返回给API Server,然后才会对应的Node的kubelet通过Watch监控到执行Pod创建。

为了缓解集群各个组件对API Server的访问压力,各个功能模块都采用缓存的机制来缓存数据,各个功能模块定时从API Server获取指定的资源对象信息,然后将这些数据缓存的到本地,对于某些情况就可以直接从本地缓存获取数据了。

那么那些是走缓存的?

3.2 Controller Manager原理分析

Controller Manager为集群内部管理中心,负责集群的Node,Pod,Endpoint,Namespace,Service,ResourceQueta等管理,当Node发生宕机,Controller Manager会发现故障并执行自动化处理流程,确保集群始终处于预期工作状态。

?这个返现Node宕机是如何实现的,主动获取Node信息还是通过kubelet上报给API Server然后Controller Manager通过Watch获取还是等通知。

Controller Manager包含很多管理,内部又有Replation Controller,Node Controller等,各负责一个控制流程。

每个Controller通过API提供的接口实时监控集群每个资源对象的状态,当系统状态发生变化的时候会将系统状态从现有状态修正到期望状态。

ServiceAccount Controller与Token Controller是负责安全相关的两个控制器。

3.2.1 Replication Controller

Replication Controller用于保证集群中一个RC所有关联的Pod副本数量保持在预设值,不过需要在Pod的重启策略为Always的时候(RestartPolicy=Always)。

对于Pod

  • 对于Pod创建后就不会改变,无论模板发生什么变化,都不会受到影响。
  • Pod可以通过修改标签脱离RC
  • 删除一个RC不会对创建的Pod有影响

对于RC

  • 确保当前集群Pod副本数量
  • 通过调整replicas来扩容和缩容
  • 改变Pod镜像版本进行滚动升级

3.2.2 Node Controller

kubectl进程在启动的时候会通过API Server注册自身节点信息,并定时会报,这些信息存储在etcd中,包括节点健康状态,节点资源,节点名称,节点地址信息,操作系统版本,Docker版本,kubelet版本等。节点状态包括True(就绪),False(未就绪),Unknown(未知)。

3.2.3 ResourceQuota Controller

ResourceQuota负责资源配额管理,确保指定资源在任何时候都不会超量占用系统物理资源,避免由于某些业务问题导致系统运行紊乱或者宕机。

支持三个层次的资源配额管理

  • 容器级别
  • Pod级别
  • Namespace级别,包括Pod数量,RC数量,Service数量,ResourceQuota数量,Secret数量,PV数量

配额通过Adminsession Crontrol(准入规则)来控制,分别是用于限制Pod和Container的LimitRanger和限制Namespace的ResourceQuota

ResourceQuota Controller负责定期统计Namespace下的各类资源使用数量和Container所使用的资源量,写入的etcd的resourceQuotas/status目录,这些统计信息会被被Adminsession Crontrol来使用,来确保Namespace中资源不会超过限制。

3.2.4 Namespace Controller

Namespace没啥原理

3.2.5 Service Controller与Endpoint Controller

Endpoint表示了一个Service对应所有副本的访问地址,而Endpoint Controller负责生成和维护所有Endpoint对象。

  • 负责监听Service和对应副本的变化,如果检测到Service被删除则删除同名Endpoint
  • 如果检测到Service被创建和修改,根据Service创建和更新Endpoint对象
  • 如果检测到Pod事件,更新对应Service的Endpoint。

Endpoint被每个Node的kube-proxy进程使用,实现Service的负载功能,每个kube-proxy都拥有代理到集群所有Service的能力

Service Controller的作用是kubernetes集群与外部之间的一个接口,如果是LoadBalancer类型的Service(externalLoadBalancer=true),确保外部的云平台对Service对应的LoadBalancer实例进行创建删除以及更新路由表(根据Endpoint条目)?

3.3 Scheduler原理分析

  1. Scheduler负责接收Controller Manager创建新的Pod,按照特定的算法和调度策略为其安排目标Node,并将相关信息写入的etcd。
  2. 目标Node的kebulet通过API server监听到Scheduler的Pod的绑定事件,然后获取到Pod清单,下载Image镜像,并启动容器,完成安装启动步骤后,目标Nodekubelet进程接管Pod。

默认调度流程

  1. 预调度过程,遍历所有Node,选择符合要求的候选节点
  2. 确定最优节点,通过优选策略计算每个节点的积分,调度到积分高的节点

而调度流程是通过插件方式加载调度算法提供者AlgorithmProvider实现的, 是个包含一组预选策略与一组优先选择策略的结构体

预选算法

  1. NoDiskConflict

判断备选Pod的GCEPerstentDisk或AWSElasitcBlockStore和备选节点中已存在的Pod是否存在冲突

  1. PodFitsResources

判断备选节点的资源是否满足备选Pod的需求

  1. PodSelectorMatches

判断备选节点是否包含备选Pod的标签选择器指定的标签

  1. PodFitsHost

判断备选Pod的spec.nodeName域所指定的节点名称和备选节点的名称是否一致 不太理解?

  1. CheckNodeLabelPresence

需要配置指定

  1. CheckServiceAffinity

需要配置指定

  1. PodFitsPorts

判断Pod所用端口是否在备选节点中被占用

优选策略

  1. LeastRequestedPriority

从备选节点中选出资源(cpu和内存)消耗最小的节点

  1. CalculateNodeLabelPriority

需要配置指定

  1. BalancedResourceAllocation

从备选节点中选出资源使用率均衡的节点

3.4 kubelet运行机制分析

Kubernetes集群中,每个Node节点都会启动一个kubelet进程,用于处理Master发送到本节点的任务,管理Pod和Pod中的容器,在API Server上注册自身信息,定期向Master会报根节点资源的使用情况,并通过cAdvisor监控容器和节点资源。

3.4.1 节点管理

kubelet启动的时候指定--register-node=true,kubelet向API Server注册自己,另外还有以下启动参数

--api-servers:API Server地址 --kubeconfig:访问API Server的证书 --cloud-provider:从云服务获取相关元数据信息

kubelet可以启动的时候使用不注册的模式,当集群资源不够的时候自动去配置Node的资源信息并告知API server地址,当然还是推荐直接加入到集群,通过auto scaling的方式扩容Node。

3.4.2 Pod管理

kubelet通过以下几种方式获取Node上运行的Pod清单

  1. 文件: 通过--config指定配置文件目录,通过--file-check-frequency设置检查时间间隔,默认为20s
  2. HTTP端点:通过--manifest-url参数设置,通过--http-check-frequency设置检查时间间隔,默认为20s
  3. API Server:通过监听API Server监听etcd,同步Pod列表

非API Server方式创建的为静态Pod,kubelet将静态Pod的状态汇报给API Server,API Server为静态Pod创建一个Mirror Pod与之匹配,通过Mirror Pod反应静态Pod状态。

当kubelet监听到Pod信息,会做以下处理

  1. 为Pod创建数据目录
  2. 从API Server获取Pod清单
  3. 为Pod挂载外部卷
  4. 下载Pod用到的Secret
  5. 检查已运行在节点中的Pod,如果该Pod没有容器或Pause容器没有启动,则先停止Pod中容器的进程,如果在Pod中没有删除的容器,则删除这些容器
  6. 用Pause镜像为Pod创建一个容器,接管Pod中所有其他容器的网络
  7. 为Pod中,调用docker client下载容器镜像,并运行容器,每个容器计算hash值,并对于容器的hash值,如果不一致则进行停止,如果指定重启策略则进行重启。

3.4.3 容器健康检查

健康检查有两种探针,Live

3.4.4 cAdvisor资源健康

Heapster是kubernetes提供的基本监控平台,是集群级别的监控和事件数据生成器。Heapster作为Pod运行在kubernetes集群,通过kubelet发现所有运行在集群中的节点,并查看节点资源使用情况,kubelet通过cAdvisor获取其所在节点及容器数据,Heapster通过带着关联标签的Pod分组信息,将这些数据推送到可配置后端,例如InfluxDB。

cAdvisor因容器的产生而产生,在kubernetes中被集成到代码中,自动查找其所在节点上的容器,自动采集CPU,内存,文件系统和网络信息。

UI暴露端口为4194,待测试?

3.5 kube-proxy运行机分析

Service是一组Pod的抽象,根据负载均衡策略来访问这组服务。Kubernetes在创建服务的时候会提供虚拟IP,Client可以通过该虚拟IP进行访问服务,而Service背后就kube-proxy进程。

每个Node都会启动一个kube-proxy进程,对于每一个TCP类型的Service,kube-proxy都会在本地建立一个SocketServer来接收请求,然后均匀发送到后端Pod的端口。由于每台都有kube-proxy进程,所以任意Node都可以发起对Server的请求。

ClusterIP与NodePort都是kube-proxy通过Iptables的NAT转换实现,对请求的流量进行重定向。

kube-proxy都会在本地建立一个SocketServer来接收请求,然后均匀发送到后端Pod的端口。由于每台都有kube-proxy进程,所以任意Node都可以发起对Server的请求。

kube-proxy默认的Round Robin算法支持Session保持,如果Service定义Session保持,在kube-proxy接收到请求的时候会先从本地内存查找IP对应得affintityState。这个默认算法如何配置?

实现细节

  • kube-proxy通过查询和监听API Server中的Service与Endpoints的变化,为每一个Service都建立一个服务代理,并自动同步。代理对象对于kube-proxy是一种数据结构,包括一个同于监听服务请求的SocketServer,而端口选择是随机一个本地空壁端口。
  • kube-proxy在内部创建一个负载均衡器LoadBalancer,LoadBalancer上保存了Service到对应后端的动态路由表,而具体路由由Round Robin负载均衡算法和SessionAffinity特性。

处理流程(针对发生变化的Server列表)

  1. 如果Service没有设置ClusterIP,则不会做任何处理,否则获取该Service的所有端口定义列表
  2. 逐个读取服务端定义的列表中的端口信息,判断本地时候是否有对应代理对象,如果不存在就创建,如果存在且Service端口被修改过,则先删除iptable中相关规则,管理服务代理对象,重新创建。
  3. 更新负载均衡器组件中Service的转发列表,对于新建的Service确定转发时的会话保持策略
  4. 对于已经删除的Service进行清理

Endpoint变化,kube-proxy会自动更新负载均衡器中对应Service的转发地址列表。

kube-proxy对于iptables的操作

在kube-proxy监听到Endpoint或Service发生变化的时候,会在本机的NAT表中添加4条链路规则。

  1. KUBE-PORTALS-CONTAINER:从容器通过Service Cluster IP和端口号访问Service的请求
  2. KUBE-PORTALS-HOST:从主机通过Service Cluster IP和端口号访问Service的请求
  3. KUBE-NODEPORT-CONTAINER:从容器通过Service的NodePort端口号访问Service的请求
  4. KUBE-NODEPORT-HOST:从主机通过NodePort端口号访问Service的请求

3.6 深入分析集群安全机制

Kubernetes通过一系列安全机制来实现集群的安全控制,包括API Server的认证授权,准入控制机制及保护敏感信息的Secret机制等。暂时只考虑功能问题,这里略过?

3.6.1 API Server认证

3.6.2 API Server授权

3.6.3 Admission Control准入控制

3.6.4 Service Account

3.6.5 Secret私密凭证

3.7 网络原理

3.7.1 Kubernetes网络模型

每个Pod有独立的IP,如果Pod所有IP可以连通,Pod不论是否在一个Node,都可以直接通过IP访问。

对于kubernetes,IP是以Pod为单位,一个Pod中的所有容器共享一个网络堆栈,抽象出一个IP-per-Pod模型,一个Pod内部获取到本机的IP和端口与集群其他Pod看到是一样的,都是docker0网卡分配的IP。对于这种设计省略的NAT的转换,也可以利用各种域名解析和发现机制。为什么NAT不能呢?

对于Pod使用相同的网络协议栈,Pod中所有容器都可以通过localhost来连接其他容器的端口,就类似一个VM中的进程一样,不过这样资源的隔离性降低了,但是共享资源的效率提高了,对于需要隔离的可以单独创建Pod,而对于每个Pod都可以看做一个虚拟主机。

kubernetes网络要求:

  1. 所有容器都可以在不使用NAT的方式与其他容器进行通信;
  2. 所有节点都可以在不使用NAT的方式同所有容器通信,反之亦然;
  3. 容器地址和外部看到地址一致。

什么是NAT

  1. 静态转换,将内部私有地址转换为公有地址,IP地址是一对一的
  2. 动态转换,将内部私有地址转换为公有地址,IP地址是不确定的,所有被授权访问上网的私有地址IP都能随机转换成一个合法IP地址
  3. 多路端口复用,改变外出数据包的源端口并进行转换,内部网络所有主机都共享一个合法的外部地址,减少IP资源,隐藏内部主机

3.7.2 Docker网络基础

Docker本身依赖Linux内核虚拟化,网络部分主要依赖Network Namespace,Veth设备,iptables,网桥,路由。

Network Namespace(网络命名空间)

Linux网络栈通过网络命名空间支持网络协议栈的多个实例,这些独立的协议栈被隔离到不同的命名空间,不同命名空间的网络协议栈是完全隔离,彼此无法通信,这样宿主机可以虚拟出多个网络环境,docker通过这个实现网络隔离。命名空间可以有独立的iptable来提供包转发,NAT和IP包过滤功能。

而隔离出独立的协议栈,纳入命名空间的有进程,套接字,网络设备。

  • 进程创建的套接字必须属于命名空间
  • 套接字的操作也必须在命名空间内进行
  • 网络设备属于公共资源,通过修改属性在命名空间间移动实现
网络命名空间的实现

为了支持独立协议栈,全局变量都变为协议栈私有。解决的方式在协议栈的调用函数中加入一个Namespace参数,内核部分隐性的使用了命名空间的变量,网络空间对应用程序是透明的。(注意以上讲的并不是虚拟网卡,是命名空间,而网卡可以放入到命名空间)

当某个进程关联到某个网络命名空间,所有网络栈变量都放入命名空间的数据结构,这个网络命名空间就属于这个进程组私有,与其他进程组并不冲突。

新创建的命名空间只有回环lo设备(停止状态),docker容器的网络栈设备都是docker daemon在容器启动时创建和配置的。

通常的物理设备都只能管理到root的命名空间,虚拟网络设备或者虚拟网口对可以关联到给定命名空间,并且可以在这些命名空间中移动。这个移动怎么理解呢?

网络命名空间代表一个独立的协议栈,是相互隔离无法进行通信的。如果想要进行协议栈间通信或者与外部网络进行通信就要依赖虚拟网口对。

网络命名空间操作

创建命名空间

ip netns add <name>
网络命令空间的一些技巧

网络设备可以在不同的网络命名空间之间转移设备,因为一个网络设备只能属于一个命名空间。网络设备转移命名空间需要设备的属性NETIF_F_ETNS_LOCAL不为on。

[root@why 16:44:11 ~]$ethtool -k vethe4aae38 | grep netns-local
netns-local: off [fixed]
[root@why 16:44:32 ~]$ethtool -k docker0 | grep netns-local
netns-local: on [fixed]

设备转移到命名空间,需要iproute2版本以上,默认CentOS6版本是没有的。

ip link set br0 netns ns1

Veth设备对

Veth设备对是为了不同网络命名空间进行通信,可以将两个命名空间进行连接,并且是成对出现,像一个以太网的网卡。

源码位于drivers/net/veth.c

设备对操作

创建Veth设备对

ip link add veth0 type veth peer name veth1

查看所有网络接口

ip link show

这时查看有veth0和veth1两个设备对

然后将veth0放入到另一个命名空间,就查看不到了veth0

通过

ip netns exec netns1 ip link show 

就可以看到veth0设备了。

对于docker的实现,直接将Veth放入到容器,并且将名字修改为eth0.

目前还不能通信,需要分配IP地址并启动。

ip netns exec netns1 ip addr add 10.1.1.1/24 dev veth1
ip addr add 10.1.1.2/24 dev veth0

ip netns exec netns1 ip link set dev veth1 up
ip link set dev veth0 up
Veth设备对如何查看对端
ip netns exec netns1 ethtool -S veth1

获取到的是peer_ifindex,然后在其他网卡空间查找接口设备序号为5的设备

ip netns exec netns2 ip link | grep 5 

对于网络空间很多,就需要一些脚本来实现这个功能了。

网桥

Linux正常情况下多端口是进行通信的,如果需要实现类似交换机的多对多通信,就要通过网桥来实现。

网桥是一个二层网络,可以解析收到的报文,读取MAC地址的信息,和自己的MAC表结合,进行决策报文的转发端口。网桥可以学习源MAC地址,在转发的时候直接向特定网络接口转发,如果遇到一个未学习的地址,将报文进行广播到所有网络设备。对于实际网络环境,网络拓扑(网络设备)一旦进行修改,网桥是无法感知这个变化,如果再次向源端口发送数据就会丢失,所以在MAC学习的时候有超时时间(默认为5分钟),超时就会重新发送广播。

在Linux主机,对于一个网桥,网络报文的地址可能是本机,除了被转发和丢弃,也可以被送到网络协议栈的上层网络层被主机的协议栈处理,可以理解为一个二层或三层的设备

Linux网桥的实现

Linux内核通过虚拟网桥设备实现桥接,这个设备可以绑定多个以太网接口设备

网桥的常用操作命令

创建网桥

brctl addbr <name>

增加端口,实际是一个网卡

brctl addif <name> eth0

网桥由于在链路层,网卡就不需要IP地址了

ifconfig eth0 0.0.0.0

配置网桥ip

ifconfig <name> xxx.xxx.xxx.xxx

iptables/Netfilter

Linux网络协议栈有一组回调函数挂接点,通过这些钩子函数可以在Linux网络协议栈处理数据包的过程进行一些操作,例如过滤,修改,丢弃。iptables和Netfilter就是实现挂节点的。

  • Netfilter负责内核中执行挂载的规则,运行在内核态
  • iptables是在用户态下运行的进程,负责维护内核中的Netfilter表
  • 两者配合实现Linxu网络包的处理

Netfilter

规则表table

挂载点能挂载的规则也分不同类型的规则表,支持四种规则表,优先级从前到后分别为RAW,MANGLE,NAT和FILTER

不过对于实际各个表的用途,例如在INPUT上不需要FILTER表的过滤规则。

当Linux协议栈的数据处理运行到挂载点,会依次执行挂载点上所有钩子函数,知道数据包的处理结果是明确的接收或者拒绝

处理规则

规则特征

  • 表类型
  • 什么挂载点(什么时候起作用)
  • 匹配的参数
  • 匹配后的动作

常见的匹配参数有流入流出网络接口,来源目的地址,协议类型,来源目的端口

Iptables命令

路由

路由功能是当IP层在处理数据发送和转发的时候,使用路由表来决定发送到哪里,如果主机直接与目的主机相连就直接发送IP报文到目的主机,如果没有直接相连就会通过路由表来决定。

当网络层接收到数据报文,IP层会检查报文的IP地址是否与主机自身的地址相同,如果相同,则直接将报文发送到传输层相应的协议,如果不是本机,如果有路由,就按照路由功能进行转发,否则报文丢弃。

一个路由表包含以下项

  • 目的IP地址,可以是一个IP地址,也可以是一个网络地址
  • 下一个路由器的IP地址
  • 标志,可以标识下一个路由器是一个IP地址还是网络地址
  • 网络接口规范

如果路由没有匹配到,就会被转发到默认路由,如果没有默认路由就会ICMP主机不可达或ICMP网络不可达的错误,并将此错误返回给应用程序。

路由表的创建

Linux的路由表至少包含Local和MAIN两个表。

  • Local表包含本地所有的设备,在配置网络地址的时候自动创建的,用于Linux协议栈识别本地地址
  • MAIN表用于各类网络IP地址的转发,既可以通过静态配置生成,也可以由动态路由生成
路由表的查看

有很多方式ip route listnetstat -rn,对于结果U代表可达路由,G代表连接的是网关

3.7.3 Docker网络实现

docker支持四种网络模式

  • host模式
  • container模式
  • none模式
  • bridge

对于kubernetes使用bridge模式。

bridge模式下docker daemon在第一次启动的时候会创建一个虚拟网桥,默认为docker0,按照RPC1918的模型,在私有网络空间给这个网桥分配一个子网,每个docker容器都会为其创建一个虚拟的Veth对,一端关联到网桥,另一端使用Linux的网络命名空间映射到容器内的eth0设备,然后从网桥地址段内给eth0分配一个IP地址,而容器的MAC地址也会根据这个IP地址,在02:42:ac:11:00:00和02:42:ac:11:ff:ff之间。

同一主机的容器之间能互相通信,但是不同主机之间不行,如果想要跨主机通信,就需要将端口路由代理到容器。

查看docker启动后的系统情况

  • 创建docker0网桥,添加了iptables的NAT和FILTER规则,docker0和iptables规则都处于root命名空间。
  • 打开Linux的ip_forward功能

查看容器启动后的情况(容器无端口映射)

  • 宿主机上Netfilter和路由表都是没有变化的
  • 宿主机上的Veth对已经建立,并且连接到容器
  • 容器内部路由表包含一条到docker0的子网路由和一条到docker0的默认路由

查看容器启动后的情况(容器无端口映射)

  • 宿主机添加了对宿主机或宿主机本地协议栈到容器内端口的转发
  • 宿主机上的Veth对已经建立,并且连接到容器
  • 容器内部路由表包含一条到docker0的子网路由和一条到docker0的默认路由

Docker网络的局限

docker最开始并没有考虑多主机互联的问题,导致网络需要依赖外部手段支持。

3.7.4 Kubernetes网络实现

kubernetes网络设置主要是为了实现以下场景

  • 容器与容器之间的直接通信
  • Pod之间通信
  • 同一个Node内的Pod之间的通信
  • 不同Node上的Pod之间的通信

容器与容器之间的直接通信

在一个Pod中的容器共享一个网络命名空间,共享一个协议栈,对于网络的操作就和在一台主机上一样,可以通过localhost进行连接。

Pod之间通信

每个Pod有一个全局真实的IP地址。

同一个Node中的Pod之间通信

同一个Node内不同Pod之间可以通过对方Pod的IP地址通信。

Pod1和Pod2都通过Veth连接到同一个docker0网桥,IP地址从docker0网段动态获取,所以和网桥为相同网段。 Pod1和Pod2的Linux协议栈上默认网关都指向了docker0的地址,非本地数据都会发送到docker0网桥,有docker0网桥转发。

不同Node上的Pod之间的通信

Pod地址是和docker0在一个网段,docker0和宿主机的网卡是完全不同的IP段,不同Node之间通信必须通过宿主机网卡实现Node上Pod通信,这需要通过主机地址进行寻址和通信。

Pod动态分配的IP地址会被kubernetes记录在etcd,作为Service的Endpoint。

3.7.5 开源的网络组建

Flannel

原理

  1. Flannel可以协助kubernetes给每个Node上的docker容器分配互不冲突的IP地址
  2. 在这个IP之间建立一个覆盖网络

实现方式

  1. Flannel创建了flannel0的网桥,一端连接docker0网桥,另一端连接flannel服务进程。
  2. Flannel进程连接etcd,利用etcd获取IP地址段,监控Pod的实际地址,在内存中建立Pod的路由表,然后下联docker0和物理网络,使用内存中的Pod路由表将docker发送的数据包进行包装,利用物理网络将数据包投递到目标Flannel上,完成Pod和Pod之间的通信。

底层协议支持UDP,VxLan,AWS VPC等多种方式,不过一般常用都是UDP,只要Flannel能到对端的Flannel即可,源Flannel进行加包,目标Flannel进行解包,最后docker0看到的还是原始数据。

Flannel通过修改docker启动参数将分配给它的地址段传递进去。

缺点是

  1. 数据在Flannel传输的时候会引入一些网络的时延
  2. UDP本身非可靠,在高并发和高流量的时候是否有问题

Open vSwitch

Open vSwitch是一个开源的交换机软件,可以建立多种隧道

直接路由

直接让Node知道对端docker的地址,通过部署MultiLayer Switch实现,在MLS中配置每个docker子网到Node地址的路由项。

这样在每新增Node都需要添加路由项,会带来巨大的工作量,另外不好维护。重启的话会造成docker0网络发生变化,也需要修改所有Node的配置,需要动态路由等帮助。

3.7.6 网络实践

这边我准备粗略看一下,平时坐地铁的时候。