在容器化时代,虽然Docker已经很强大了,但是在实际使用上还是有诸多不便,比如集群管理、资源调度、文件管理等等。
不过目前也涌现了很多解决方案,比如 Mesos、Swarm、Kubernetes 等等,其中谷歌开源的 Kubernetes就是其中的王者!
它是基于容器的集群编排引擎,具备扩展集群、滚动升级回滚、弹性伸缩、自动治愈、服务发现等多种特性能力。
学习K8s,需要了解K8s常见的应用场景和核心概念。内容丰富,建议收藏!!
一、K8s集群架构及核心概念
1.1 架构图

1.2 K8s组件
api service
所有服务访问统一入口。对外暴露K8s的api接口,是外界进行资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制。
controller manager
负责维护集群的状态,比如故障检测、自动扩展、滚动更新等,他们是处理集群中常规任务的后台线程。
scheduler
负责资源的调度,按照预定的调度策略将Pod调度到响应的机器上;就是监视新创建的Pod,如果没有分配节点,就选择一个节点供他们运行,这就是Pod的调度。
etcd
一个可信赖的分布式键值存储服务,能够为整个分布式集群存储一些关键数据,协助分布式集群运转。储存K8s集群所有重要信息(持久化)。
v2版本是基于内存的存储,v3开始才是序列化到介质。
新版K8s(v1.11以上)已经改用v3版本的etcd。
kubelet
直接跟容器引擎交互实现容器的生命周期管理。
kube-proxy
负责写入规则至iptables、ipvs实现服务映射访问。
其中scheduler和controller-manager两个组件是有leader选举的,这个选举机制是K8s对于这两个组件的高可用保障。Api server是可以水平扩展的。
其他重要插件:
CoreDNS
可以为集群中的SVC创建一个域名IP的对应解析关系。
Dashboard
给K8s集群提供一个B/S结构访问体系。
Ingress Controller
官方只能实现四层代理,ingress可以实现七层代理。
Federation
提供一个可以跨集群中心多K8s统一管理功能。
Prometheus
提供K8s集群的监控能力。
ELK
提供K8s集群日志统一分析介入平台。
1.3 K8s资源
K8S中的所有内容都抽象为资源,资源实例化后叫做对象。
在K8S中,一般使用yaml格式的文件来创建符合我们预期的Pod,这种yaml文件一般被称为资源清单。
K8s中的资源可分为三种:
#1 名称空间级资源
通过:
kubectl api-resources --namespaced=true查看全部。
K8S是有空间概念的,以下资源是归属于某个空间的(没有指定就是默认空间default)。
同时也可以根据功能分为以下集中:
工作负载型
Pod、ReplicaSet(Replication
Controller在v1.11版本废弃)、Deployment、StatefulSet、DaemonSet、Job、CronJob
服务发现及负载均衡型
Service、Ingress等
配置与存储型
Volume、CSI(容器存储接口,可以扩展各种各样的第三方存储卷)
特殊类型的存储卷
ConfigMap(当做配置中心来使用的资源类型)、Secret(保存敏感数据)、DownwardAPI(把外部环境中的信息输出给容器)
#2 集群级资源
通过:、
kubectl api-resources --namespaced=false 查看全部。
Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding
#3 元数据型资源
HPA、PodTemplate、LimitRange
资源清单常用字段:
yaml文件是K8s资源声明表达的格式。各种资源通用的常用字段如下:


1.4 工作负载
Node
K8s中节点(node)指的是一个工作机器,曾经叫做minion。
不同的集群中,节点可能是虚拟机,也可能是物理机。每个节点都由master组件管理,并包含了运行Pod(容器组)所需的服务。
这些服务包括:容器引擎、kubelet、kube-proxy。
kubectl get nodes -o wide 查看所有节点的列表。
kubectl describe node 查看节点状态以及节点的其他详细信息。
输出的节点状态包括以下信息:
Addersses
包括内外部IP、hostname等信息。
Conditions
描述了节点的硬盘、内存、进程数等压力状态。
Capacity and Allocatable
描述节点上的可用资源如CPU、内存、该节点可调度的最大pod数等情况。
Info
描述了节点的基本信息,如Linux内核版本、Kubernetes版本、Docker版本、操作系统名称等。
1.5 节点管理
与Pod和Service不一样,节点并不是由Kubernetes创建的,节点由云供应商创建。
向Kubernetes中创建节点时,仅仅是创建了一个描述该节点的API对象。
节点API对象创建成功后,Kubernetes将检查该节点是否有效。
例如,假设创建如下节点信息:

Kubernetes在APIServer上创建一个节点API对象(节点的描述),并且基于metadata.name字段对节点进行健康检查。
如果节点有效,则可以向该节点调度Pod;否则,该节点API对象将被忽略,直到节点变为有效状态。
1.6 节点控制器(Node Controller)
这是一个负责管理节点的Kubernetes master组件。在节点的生命周期中,节点控制器起到了许多作用:
在注册节点时为节点分配CIDR地址块。
通过云供应商(cloud-controller-manager)接口,检查节点列表中每一个节点对象对应的虚拟机是否可用。在云环境中,只要节点状态异常,节点控制器检查其虚拟机在云供应商的状态,如果虚拟机不可用,自动将节点对象从APIServer中删除。
监控节点的健康状况。当节点变得不可触达时(例如,由于节点已停机,节点控制器不再收到来自节点的心跳新号),节点控制器将节点API对象的NodeStatusCondition取值从NodeReady更新为Unknown;然后在等待pod-eviction-timeout时间后,将节点上的所有Pod从节点驱逐。
节点自注册(Self-Registration
如果Kubelet的启动参数–register-node为true(默认为true),kubelet会尝试将自己注册到APIServer。
Kubelet自行注册时,将使用以下选项:
–kubeconfig:向apiserver进行认证时所用身份信息的路径
–cloud-provider:向云供应商读取节点自身元数据
–register-node:自动向API Server祖册节点
–register-with-taints:注册节点时,为节点添加污点(逗号分隔,格式为=:)
–node-ip:节点的IP地址
–node-labels:注册节点时,为节点添加标签
–node-status-update-frequency:向master节点发送心跳信息的时间间隔
如果 Node authorization mode (opens new window)和 NodeRestriction admission plugin (opens new window)被启用,kubelet 只拥有创建/修改其自身所对应的节点 API 对象的权限。
1.7 Pod
Pod(容器组)是 Kubernetes 中最小的可部署单元。
一个 Pod(容器组)包含了一个应用程序容器(某些情况下是多个容器)、存储资源、一个唯一的网络 IP 地址、以及一些确定容器该如何运行的选项。
Pod 容器组代表了 Kubernetes 中一个独立的应用程序运行实例,该实例可能由单个容器或者几个紧耦合在一起的容器组成。
一个Pod是容器环境下的“逻辑主机”,它可能包含一个或者多个紧密相连的应用,这些应用可能是在同一个物理主机或虚拟机上。
Pod 的context可以理解成多个linux命名空间的联合:
PID 命名空间
同一个Pod中应用可以看到其它进程。
网络 命名空间
同一个Pod的中的应用对相同的IP地址和端口有权限。
IPC 命名空间
同一个Pod中的应用可以通过VPC或者POSIX进行通信。
UTS 命名空间
同一个Pod中的应用共享一个主机名称。
注:同一个Pod中的应用可以共享磁盘,磁盘是Pod级的,应用可以通过文件系统调用。
Pod创建流程
创建Pod的整个流程,时序图如下:

注:
1、用户提交创建Pod的请求,可以通过API Server的REST API ,也可用Kubectl命令行工具,支持Json和Yaml两种格式。
2、API Server 处理用户请求,存储Pod数据到Etcd。
3、Schedule通过和 API Server的watch机制,查看到新的pod,尝试为Pod绑定Node。调度器用一组规则过滤掉不符合要求的主机,比如Pod指定了所需要的资源,那么就要过滤掉资源不够的主机,对上一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略进行调度;选择打分最高的主机,进行binding操作,结果存储到Etcd中。
4、kubelet根据调度结果执行Pod创建操作。绑定成功后,会启动container。scheduler会调用API Server的API在etcd中创建一个bound pod对象,描述在一个工作节点上绑定运行的所有pod信息。运行在每个工作节点上的kubelet也会定期与etcd同步bound pod信息。
Pod的定义
可以通过kubectl explain pod 查看pod yaml定义的顶级属性,然后通过类似 kubectl explain pod.{顶级属性}查看二级属性的细节定义。
下面给出一个pod资源定义的样例,对pod的描述有一个初步了解。
apiVersion: v1 #与k8s集群版本有关,使用 kubectl api-versions 即可查看当前集群支持的版本
kind: Pod #该配置的类型,我们使用的是Pod
metadata: #译名为元数据,即 Pod 的一些基本属性和信息
name: nginx-pod #Pod 的名称
labels: #标签,可以灵活定位一个或多个资源,其中key和value均可自定义,可以定义多组,目前不需要理解
app: nginx #为该Deployment设置key为app,value为nginx的标签
spec: #期望Pod实现的功能(即在pod中部署)
containers: #生成container,与docker中的container是同一种
- name: nginx #container的名称
image: nginx:1.7.9 #使用镜像nginx:1.7.9创建container,该container默认80端口可访问
保存为nginx-pod.yaml文件,通过kubectl apply -f nginx-pod.yaml启动pod。通过kubectl get pods -o wide查看pod的启动情况。

二、服务发现、负载均衡、网络
Kubernetes 中 Service 是一个 API 对象,通过 kubectl + YAML 定义一个 Service,可以将符合 Service 指定条件的 Pod ,作为可通过网络访问的服务提供给服务调用者。
Service 是 Kubernetes 中的一种服务发现机制:
Pod 有自己的 IP 地址
Service 被赋予一个唯一的 dns name
Service 通过 label selector 选定一组 Pod
Service 实现负载均衡,可将请求均衡分发到选定这一组 Pod 中
例如,假设您有一组 Pod:
每个 Pod 都监听 9376 TCP 端口
每个 Pod 都有标签 app=MyApp
下面文件可用来创建一个 Service(名字为 my-service):
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
Service 从自己的 IP 地址和 port 端口接收请求,并将请求映射到符合条件的 Pod 的 targetPort。
为了方便,默认 targetPort 的取值 与 port 字段相同
Kubernetes 将为该 Service 分配一个 IP 地址(ClusterIP 或 集群内 IP),供 Service Proxy 使用
Kubernetes 将不断扫描符合该 selector 的 Pod,并将最新的结果更新到与 Service 同名 my-service 的 Endpoint 对象中
2.1 服务代理
Kubernetes 集群中的每个节点都运行了一个 kube-proxy。
负责为 Service(ExternalName 类型的除外)提供虚拟 IP 访问。
Kubernetes 支持三种 proxy mode(代理模式),他们的版本兼容性如下:

User space 代理模式
kube-proxy 监听 kubernetes master以获得添加和移除 Service / Endpoint 的事件
kube-proxy 在其所在的节点(每个节点都有 kube-proxy)上为每一个 Service 打开一个随机端口
kube-proxy 安装 iptables 规则,将发送到该 Service 的 ClusterIP(虚拟 IP)/ Port 的请求重定向到该随机端口
任何发送到该随机端口的请求将被代理转发到该 Service 的后端 Pod 上(kube-proxy 从 Endpoint 信息中获得可用 Pod)
kube-proxy 在决定将请求转发到后端哪一个 Pod 时,默认使用 round-robin(轮询)算法,并会考虑到 Service 中的 SessionAffinity 的设定
如下图所示:

Iptables 代理模式–默认模式
kube-proxy 监听 kubernetes master 以获得添加和移除 Service / Endpoint 的事件
kube-proxy 在其所在的节点(每个节点都有 kube-proxy)上为每一个 Service 安装 iptable 规则
iptables 将发送到 Service 的 ClusterIP / Port 的请求重定向到 Service 的后端 Pod 上
对于 Service 中的每一个 Endpoint,kube-proxy 安装一个 iptable 规则。默认情况下,kube-proxy 随机选择一个 Service 的后端 Pod
如下图所示:

iptables proxy mode 的优点:
更低的系统开销
在 linux netfilter 处理请求,无需在 userspace 和 kernel space 之间切换
更稳定
与 user space mode 的差异:
使用 iptables mode 时
如果第一个 Pod 没有响应,则创建连接失败
使用 user space mode 时
如果第一个 Pod 没有响应,kube-proxy 会自动尝试连接另外一个后端 Pod
您可以配置 Pod 就绪检查(readiness probe)确保后端 Pod 正常工作。
此时,在 iptables 模式下 kube-proxy 将只使用健康的后端 Pod,从而避免了 kube-proxy 将请求转发到已经存在问题的 Pod 上。
IPVS 代理模式
kube-proxy 监听 kubernetes master 以获得添加和移除 Service / Endpoint 的事件
kube-proxy 根据监听到的事件,调用 netlink 接口,创建 IPVS 规则;并且将 Service/Endpoint 的变化同步到 IPVS 规则中
当访问一个 Service 时,IPVS 将请求重定向到后端 Pod
如下图所示:

IPVS proxy mode 基于 netfilter 的 hook 功能,与 iptables 代理模式相似,但是 IPVS 代理模式使用 hash table 作为底层的数据结构,并在 kernel space 运作。
这就意味着:
IPVS 代理模式可以比 iptables 代理模式有更低的网络延迟,在同步代理规则时,也有更高的效率
与 user space 代理模式 / iptables 代理模式相比,IPVS 模式可以支持更大的网络流量
IPVS 提供更多的负载均衡选项:
rr: round-robin
lc: least connection (最小打开的连接数)
dh: destination hashing
sh: source hashing
sed: shortest expected delay
nq: never queue
在所有的代理模式中,发送到 Service 的 IP:Port 的请求,将被转发到一个合适的后端 Pod,而无需调用者知道任何关于 Kubernetes/Service/Pods 的细节。
2.2 服务发现–DNS
安装了K8s插件-- DNS 服务,Core DNS (opens new window)。
Kubernetes 集群中就会运行一组 DNS Pod,配置了对应的 Service,并由 kubelete 将 DNS Service 的 IP 地址配置到节点上的容器中以便解析 DNS names。
CoreDNS 监听 Kubernetes API 上创建和删除 Service 的事件,并为每一个 Service 创建一条 DNS 记录。
集群中所有的 Pod 都可以使用 DNS Name 解析到 Service 的 IP 地址。
例如:
名称空间 my-ns 中的 Service my-service,将对应一条 DNS 记录 my-service.my-ns。
名称空间 my-ns 中的Pod可以直接 nslookup my-service (my-service.my-ns 也可以)。
其他名称空间的 Pod 必须使用 my-service.my-ns。my-service 和 my-service.my-ns 都将被解析到 Service 的 Cluster IP。
Service 类型
Kubernetes 中可以通过不同方式发布 Service,通过 ServiceType 字段指定,该字段的默认值是 ClusterIP。
可选值有:
ClusterIP: 默认值。
通过集群内部的一个Cluster IP 地址暴露 Service,只在集群内部可以访问。
NodePort
通过每一个节点上的的静态端口(NodePort)暴露 Service,同时自动创建 ClusterIP 类型的访问方式。
在集群内部通过(ClusterIP):(Port) 访问,
在集群外部通过 (NodeIP):(NodePort) 访问。
LoadBalancer
通过云服务供应商(AWS、Azure、GCE 等)的负载均衡器在集群外部暴露 Service,同时自动创建 NodePort 和 ClusterIP 类型的访问方式。LoadBalancer是将 .spec.type 字段设置为 LoadBalancer,Kubernetes 将为该Service 自动创建一个负载均衡器。
负载均衡器的创建操作异步完成,您可能要稍等片刻才能真正完成创建,负载均衡器的信息将被回写到 Service 的.status.loadBalancer 字段。
在集群内部通过 (ClusterIP):(Port) 访问
在集群外部通过 (NodeIP):(NodePort) 访问
在集群外部通过 (LoadBalancerIP):(Port) 访问。
ExternalName
将 Service 映射到 externalName 指定的地址(例如:http://foo.bar.example.com),返回值是一个 CNAME 记录。不使用任何代理机制。ExternalName 类型的 Service 映射到一个外部的 DNS name,而不是一个 pod label selector。可通过 spec.externalName 字段指定外部 DNS name。
External IP
如果有外部 IP 路由到 Kubernetes 集群的一个或多个节点,Kubernetes Service 可以通过这些 externalIPs 进行访问。externalIP 需要由集群管理员在 Kubernetes 之外配置。
在 Service 的定义中, externalIPs 可以和任何类型的 .spec.type 一通使用。
Ingress
Ingress 是 Kubernetes 的一种 API 对象,将集群内部的 Service 通过 HTTP/HTTPS 方式暴露到集群外部,并通过规则定义 HTTP/HTTPS 的路由。
Ingress 具备如下特性:集群外部可访问的 URL、负载均衡、SSL Termination、按域名路由(name-based virtual hosting)。
Ingress 的例子如下所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress-for-nginx # Ingress 的名字,仅用于标识
spec:
rules: # Ingress 中定义 L7 路由规则
- host: demo.my-company.com # 根据 virtual hostname 进行路由(请使用您自己的域名)
http:
paths: # 按路径进行路由- path: /
backend:
serviceName: nginx-service # 指定后端的 Service 为之前创建的 nginx-service
servicePort: 80
- path: /
Ingress Controller 通常是部署在集群中的一个 Deployment,并且通过 NodePort Service 暴露自己的端口,使得用户可以在集群外通过其 NodePort 访问到 Ingress Controller。
假设该端口为 32351,并且 http://demo.my-company.com 这个域名被解析到集群中某一个节点的 IP(或者被配置到浏览器所在机器的 hosts 文件),则当用户在浏览器中输入地址 http://demo.my-company.com:32351 时:
请求被转发到集群某节点的 32351 节点端口。
根据 Service 的定义,请求被转发到 Ingress Controller 的 Web 端口。
Ingress Controller 根据请求域名 http://demo.my-company.com 以及请求路径,匹配到 Ingress 定义中该请求应该被路由到 nginx-service 的 80 端口。
Ingress Controller 执行 L7 路由转发,将请求发送到 nginx-service 的 80 端口。
2.3 K8s网络
容器网络
容器网络是容器选择连接到其他容器、主机和外部网络(如Internet)的机制。
CNI意为容器网络接口,它是一种标准的设计,为了让用户在容器创建或销毁时都能够更容易地配置容器网络。
目前最流行的CNI插件是Flannel。Flannel插件既可以确保满足Kubernetes的网络要求,又能为Kubernetes集群管理员提供他们所需的某些特定的网络功能。
容器的Runtime提供了各种网络模式,每种模式都会产生不同的体验。
例如,Docker默认情况下可以为容器配置以下网络:
none
将容器添加到一个容器专门的网络堆栈中,没有对外连接。
host
将容器添加到主机的网络堆栈中,没有隔离。
default bridge
默认网络模式。每个容器可以通过IP地址相互连接。
自定义网桥
用户定义的网桥,具有更多的灵活性、隔离性和其他便利功能。
Docker还可以让用户通过其他驱动程序和插件,来配置更高级的网络(包括多主机覆盖网络)。
Flannel插件
Flannel是CoreOs团队针对kubernetes设计的一个网络规划服务。
简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。
而且它还能在这些IP之间建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递到目标容器内。
Flannel监控ETCD中每个Pod的实际地址,并在内存中建立维护Pod节点路由表。

Kubernetes的网络模型假设了所有Pod都在一个可以直接连通的扁平的网络空间中,kubernetes假定这个网络已经存在。
Pod之间的通信存在以下三种情况,对应的通信方式如下:
同一个Pod内的多个容器之间。这个是通过lo回路在机器内寻址
当两个Pod在同一个节点主机。由网桥直接转发至对应Pod,不用经过Flannel。
当两个Pod在不同的节点主机,不同Node之间的通信智能通过宿主机的物理网卡进行。
Pod至Service的网络,目前基于性能考虑,采用的是LVS方式维护和转发。
Pod到外网:Pod向外网发送请求,查找路由表,转发数据包到宿主机的网卡,宿主网卡完成路由选择后,iptable执行Masquerade,把源IP更改为宿主机网卡的IP,然后向外网服务器发送请求。
外网访问Pod:是通过Service找到对应的Pod,通过网络转换为对应的pod网络Ip再定位到具体的Pod。