K8S基础

文章是基于“Kubernetes权威指南”和博客的学习笔记。初次接触K8S,通过笔记加深了解记忆。

K8S概述

Kubernetes[简称K8S]是一个开源的,用于在一组主机上运行和协同容器化应用程序的系统,提供应用部署、规划、更新维护的机制。K8S的目标是让部署容器化的应用简单高效,K8S是一种应用运行在 K8S 集群之上,实现服务的扩容、缩容,执行滚动更新以及在不同版本的应用程序之间调度流量以测试功能或回滚有问题的部署。K8S 实现管理服务的各项功能是通过定义各种类型的资源来实现的,如 deployment、pod、service、volume 等(下文中会解释这些概念)。
在K8S中,所有的容器均在Pod中运行,一个Pod可以承载一个或者多个相关的容器。Pod 是 K8S 系统的基础单元,是由用户创建或部署的最小组件,也是 kubernetes 系统上运行容器化应用的资源对象。同一个Pod中的容器会部署在同一个物理机器上并且能够共享资源。一个Pod也可以包含0个或者多个磁盘卷组(volumes),这些卷组将会以目录的形式提供给一个容器,或者被所有Pod中的容器共享。

K8S的架构概览

k8s功能架构

Docker Engine由许多专用的工具协同工作,从而可以创建和运行容器,例如 API、执行驱动、运行时、shim 进程等。主要由两个核心组件构成:LXC 和 Docker daemon。Docker daemon 是单一的二进制文件,包含诸如 Docker 客户端、Docker API、容器运行时、镜像构建等。LXC 提供了对诸如命名空间(Namespace)和控制组(CGroup)等基础工具的操作能力,它们是基于 Linux 内核的容器虚拟化技术。
Linux Namespace是Linux内核用来隔离资源的方式。每个Namespace下的资源对于其他Namespace都是不透明,不可见的。
容器就是进程,容器是与系统其他部分隔离开的进程。这个时候我们再看上图就更容易理解,容器是跑在宿主机OS(虚机容器的宿主机OS就是Guest OS)上的进程,容器间以及容器和宿主机间存在隔离性。

k8s相关组件介绍

  • api service:所有服务访问统一入口。对外暴露K8S的api接口,是外界进行资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制;
  • crontroller manager:负责维护集群的状态,比如故障检测、自动扩展、滚动更新等,它们是处理集群中常规任务的后台线程。
  • scheduler:负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上;就是监视新创建的 Pod,如果没有分配节点,就选择一个节点供他们运行,这就是pod的调度。
  • etcd:一个可信赖的分布式键值存储服务,能够为整个分布式集群存储一些关键数据,协助分布式集群运转。储存K8S集群所有重要信息(持久化)。v2版本是基于内存的存储,v3开始才是序列化到介质。新版本K8S(v1.11以上)已经改用v3版本etcd。
  • kubelet:运行在Node上的服务进程,默认监听 10250 端口,接收并执行 Master 发来的指令,管理 Pod 及 Pod 中的容器。每个 Kubelet 进程会在 API Server 上注册所在Node节点的信息,定期向 Master 节点汇报该节点的资源使用情况,并通过 cAdvisor 监控节点和容器的资源。
  • kube-proxy:运行在Node上的服务进程,它监听 API server 中 service 和 endpoint 的变化情况,并通过IPTABLES、IPVS 等来为服务配置负载均衡(仅支持 TCP 和 UDP)。

其中scheduler和controller-manager两个组件是有leader选举的,这个选举机制是k8s对于这两个组件的高可用保障。api server是可以水平扩展的。

其他重要插件:

  • coredns:可以为集群中的SVC创建一个域名IP的对应关系解析
  • dashboard:给 K8S 集群提供一个 B/S 结构访问体系
  • ingress controller:官方只能实现四层代理,INGRESS 可以实现七层代理
  • federation:提供一个可以跨集群中心多K8S统一管理功能
  • prometheus :提供K8S集群的监控能力
  • elk:提供 K8S 集群日志统一分析介入平台

K8S安装

学习还是需要跟实践结合的,这样理解更透彻。安装个单机版本K8S方便后面的学习整理

K8S安装分3种方式:

  • 单机版本,mac安装可以参考文档
  • 伪多机(通过vm),网上相关资源非常多,此处不赘述。
  • 真实多台机器,可以几台低配置的买阿里云安装。文档描述非常清晰

K8S中的资源

K8S中所有的内容都抽象为资源,资源实例化之后叫做对象。在K8S中,一般适用yaml格式的文件来创建符合我们预期的Pod,这样的yaml文件一般称为资源清单。K8S中的资源可以分为:名称空间级资源、集群级资源、元数据型资源。
名称空间级资源(通过kubectl api-resources --namespaced=true查看全部)
K8S是有空间概念的,以下资源是归属于某个空间的(没有指定就是默认空间)。名称空间为不同团队的用户(或项目)提供虚拟的集群空间,也可以用来区分开发环境/测试环境、准上线环境/生产环境。名称空间级资源又可以根据功能分为以下几种:

  • 工作负载型资源: Pod、 ReplicaSet(ReplicationController在v1.11版本废弃)、Deployment、StatefulSet、DaemonSet、Job、CronJob
  • 服务发现及负载均衡型资源:Service、Ingress等
  • 配置与存储型资源:Volume、CSI(容器存储接口,可以扩展各种各样的第三方存储卷)
  • 特殊类型的存储卷:ConfigMap(当配置中心来使用的资源类型)、Secret(保存敏感数据)、DownwardAPI(把外部环境中的信息输出给容器)

集群级资源(通过kubectl api-resources --namespaced=false查看全部)
Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding

元数据型资源
HPA、PodTemplate、LimitRange

资源清单中常用字段

yaml文件是K8S资源声明式表达的格式。各种资源通用的常用字段如下:

参数名 字段类型 说明
apiVersion String 这里是指K8S API的版本,目前基本是v1,可以通过kubectl api-versions命令查询
kind String 资源的类型和角色,比如:Pod、ReplicaSet等
metadata Object 元数据对象,固定值就写metadata
∟name String 元数据对象的名字,自定义编写
∟namespace String 元数据对象的命名空间,自定义编写
spec Object 详细定义对象,固定值就写Spec
∟containers list 这里是Spec对象的容器列表定义,是个列表
∟∟name String 定义容器的名字
∟∟image String 用到镜像名称
∟∟imagePullPolicy String 定义镜像拉取策略,有Always(每次都拉取最新镜像)、Never(仅使用本地镜像)、IfNotPresent(本地不存在才拉取在线镜像)三个值可选。默认是Always
∟∟command List 指定容器启动命令,因为是数组可以指定多个,不指定则使用镜像打包时使用的启动命令
∟∟args List 指定容器启动命令参数,因为是数据可以指定多个
∟∟workingDir String 指定容器的工作目录
∟∟volumeMounts List 指定容器内部的存储卷配置
∟∟∟name String 指定可以被挂载的存储卷的名称
∟∟∟mountPath String 指定可以被容器挂载的存储卷的路径
∟∟∟readOnly String 设置存储卷路径的读写模式,true或者false,默认是false
∟∟ports list 指定容器需要用到的端口列表
∟∟∟name String 指定端口名称
∟∟∟containerPort String 指定容器需要监听的端口号
∟∟∟hostPort String 指定容器所在主机需要监听的端口号,默认跟上面containerPort相同,注意设置了hostPort同一台主机无法启动该容器的相同副本(因为主机的端口号不能相同,这样会冲突)
∟∟∟protocol String 指定端口协议,支持TCP和UDP,默认是TCP
∟∟env list 指定容器运行前需设置的环境变量列表
∟∟∟name String 指定环境变量名称
∟∟∟value String 指定环境变量值
∟∟resources Object 指定资源限制和资源请求的值(这里开始就是设置容器的资源上限)
∟∟∟limits Object 指定设置容器运行时资源的运行上限
∟∟∟∟cpu String 指定CPU的限制,单位为core数,将用于docker run --cpu-shares参数
∟∟∟∟memory String 指定Memory内存的限制,单位为MiB、GiB
∟∟∟requests Object 指定容器启动和调度时的限制设置
∟∟∟∟cpu String CPU请求,单位为core数,容器启动时初始化可用数量
∟∟∟∟memory String 内存请求,单位为MiB、GiB,容器启动的初始化可用数量
∟restartPolicy String 定义Pod的重启策略,可选值为Always(Pod一旦终止运行,无论如何终止,kubectl服务都将重启它)、OnFailure(只有Pod以非零退出码终止时才会重启)、Never(Pod终止后,kubectl将退出码报告给Master,不会重启Pod),默认值为Always。

在上述的 .yaml 文件中,如下一级字段是必须填写的:

  • apiVersion 用来创建对象时所使用的Kubernetes API版本
  • kind 被创建对象的类型
  • metadata 用于唯一确定该对象的元数据:包括 namenamespace,如果 namespace 为空,则默认值为 default
  • spec 描述您对该对象的期望状态

不同类型的 Kubernetes, spec 对象的格式不同(含有不同的内嵌字段),通过 API 手册 (opens new window)可以查看 Kubernetes 对象的字段和描述。

工作负载

节点

Kubernetes中节点(node)指的是一个工作机器,曾经叫做 minion。不同的集群中,节点可能是虚拟机也可能是物理机。每个节点都由 master 组件管理,并包含了运行 Pod(容器组)所需的服务。这些服务包括:容器引擎、kubelet、kube-proxy。
执行kubectl get nodes -o wide命令可查看所有节点的列表。执行kubectl describe node <your-node-name>命令可查看节点状态以及节点的其他详细信息。输出的节点状态包含如下信息:

  • Addresses(包括内外部IP、hostname等信息)
  • Conditions(描述了节点的硬盘、内存、进程数等压力状态)
  • Capacity and Allocatable(描述节点上的可用资源如CPU、内存、该节点可调度的最大pod数等情况)
  • Info(描述了节点的基本信息,如Linux 内核版本、Kubernetes 版本、Docker 版本、操作系统名称等)

节点管理

与 Pod 和 Service 不一样,节点并不是由 Kubernetes 创建的,节点由云供应商创建。向 Kubernetes 中创建节点时,仅仅是创建了一个描述该节点的 API 对象。节点 API 对象创建成功后,Kubernetes将检查该节点是否有效。例如,假设创建如下节点信息:

kind: Node
apiVersion: v1
metadata:
  name: "10.240.79.157"
  labels:
    name: "my-first-k8s-node"

Kubernetes 在 APIServer 上创建一个节点 API 对象(节点的描述),并且基于 metadata.name 字段对节点进行健康检查。如果节点有效,则可以向该节点调度 Pod;否则,该节点 API 对象将被忽略,直到节点变为有效状态。
节点控制器(Node Controller)
节点控制器是一个负责管理节点的 Kubernetes master 组件。在节点的生命周期中,节点控制器起到了许多作用。

  • 首先,节点控制器在注册节点时为节点分配 CIDR 地址块
  • 第二,节点控制器通过云供应商(cloud-controller-manager)接口检查节点列表中每一个节点对象对应的虚拟机是否可用。在云环境中,只要节点状态异常,节点控制器检查其虚拟机在云供应商的状态,如果虚拟机不可用,自动将节点对象从 APIServer 中删除。
  • 第三,节点控制器监控节点的健康状况。当节点变得不可触达时(例如,由于节点已停机,节点控制器不再收到来自节点的心跳信号),节点控制器将节点API对象的 NodeStatus Condition 取值从 NodeReady 更新为 Unknown;然后在等待 pod-eviction-timeout 时间后,将节点上的所有 Pod 从节点驱逐。

节点自注册(Self-Registration)

如果 kubelet 的启动参数 --register-node为 true(默认为 true),kubelet 会尝试将自己注册到 API Server。kubelet自行注册时,将使用如下选项:

  • --kubeconfig:向 apiserver 进行认证时所用身份信息的路径
  • --cloud-provider:向云供应商读取节点自身元数据
  • --register-node:自动向 API Server 注册节点
  • --register-with-taints:注册节点时,为节点添加污点(逗号分隔,格式为 <key>=<value>:<effect>
  • --node-ip:节点的 IP 地址
  • --node-labels:注册节点时,为节点添加标签
  • --node-status-update-frequency:向 master 节点发送心跳信息的时间间隔

如果 Node authorization mode (opens new window)NodeRestriction admission plugin (opens new window)被启用,kubelet 只拥有创建/修改其自身所对应的节点 API 对象的权限。

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的整个流程,时序图如下:


pod创建流程
  • 用户提交创建Pod的请求,可以通过API Server的REST API ,也可用Kubectl命令行工具,支持Json和Yaml两种格式;
  • API Server 处理用户请求,存储Pod数据到Etcd;
  • Schedule通过和 API Server的watch机制,查看到新的pod,尝试为Pod绑定Node。调度器用一组规则过滤掉不符合要求的主机,比如Pod指定了所需要的资源,那么就要过滤掉资源不够的主机,对上一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略进行调度;选择打分最高的主机,进行binding操作,结果存储到Etcd中;
  • 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的启动情况。

Pod Template
用户应该始终使用控制器来创建 Pod,而不是直接创建 Pod,控制器可以提供如下特性:

  • 水平扩展(运行 Pod 的多个副本)
  • rollout(版本更新)
  • self-healing(故障恢复)

当一个节点出现故障,控制器可以自动地在另一个节点调度一个配置完全一样的 Pod,以替换故障节点上的 Pod。在 Kubernetes 中,广泛使用的控制器有:

  • Deployment
  • StatefulSet
  • DaemonSet

控制器通过其中配置的 Pod Template 信息来创建 Pod。Pod Template 是关于 Pod 的定义,但是被包含在其他的 Kubernetes 对象中(例如 Deployment、StatefulSet、DaemonSet 等控制器)。控制器通过 Pod Template 信息来创建 Pod
上面部署pod的yaml文件如果改为通过deployment来部署,对应的文件如下:

apiVersion: apps/v1 #与k8s集群版本有关,使用 kubectl api-versions 即可查看当前集群支持的版本
kind: Deployment    #该配置的类型,我们使用的是 Deployment
metadata:           #译名为元数据,即 Deployment 的一些基本属性和信息
  name: nginx-deployment    #Deployment 的名称
  labels:       #标签,可以灵活定位一个或多个资源,其中key和value均可自定义,可以定义多组,目前不需要理解
    app: nginx  #为该Deployment设置key为app,value为nginx的标签
spec:           #这是关于该Deployment的描述,可以理解为你期待该Deployment在k8s中如何使用
  replicas: 1   #使用该Deployment创建一个应用程序实例
  selector:     #标签选择器,与上面的标签共同作用,目前不需要理解
    matchLabels: #选择包含标签app:nginx的资源
      app: nginx
  template:     #这是选择或创建的Pod的模板
    metadata:   #Pod的元数据
      labels:   #Pod的标签,上面的selector即选择包含标签app:nginx的Pod
        app: nginx
    spec:       #期望Pod实现的功能(即在pod中部署)
      containers:   #生成container,与docker中的container是同一种
      - name: nginx #container的名称
        image: nginx:1.7.9  #使用镜像nginx:1.7.9创建container,该container默认80端口可访问

Pod的生命周期

pod生命周期

Pod能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的Init容器。Init容器与普通容器非常像,除了以下两点:

  • Init容器总是运行到成功完成为止。也就是Init容器会退出结束。
  • 每个Init容器都必须在下一个Init容器启动之前成功完成。

Init容器
可以通过kubectl explain pod.spec.initContainers查看init容器的属性配置。init容器属于 pod 的初始化容器列表。初始化容器是在容器启动之前按顺序执行。如果Pod的Init容器失败,Kubernetes会不断重启该Pod,直到Init容器成功为止(除非Pod对应的restartPolicy为Never)。
因为Init容器具有与应用程序容器分离的单独镜像,所以它们的启动相关代码具有如下优势:

  • 应用程序镜像可以分离出创建和部署的角色,而没必要联合它们构建一个单独的镜像。
  • Init容器使用Linux Namespace,所以相比应用程序容器来说具有不同的文件系统视图。因此,它们能够具有访问Secret的权限,而应用程序则不能。
  • 它们必须在应用容器启动之前运行完成,而应用程序容器是并行运行的,所以Init容器能够提供一种简单的阻塞或延迟应用容器的启动方法,直到满足一组先决条件。
  • 如果Pod重启,所有Init容器必须重新执行
  • 初始化容器不支持 就绪检查 readiness probe,因为初始化容器必须在 Pod ready 之前运行并结束

探测
探针是由kubelet对容器执行的定期诊断。要执行诊断,kubelet调用由容器实现的Handler。Handler有以下三种类型处理程序:

  • ExecAction: 在容器内执行指定命令,如果命令退出时返回码为0则认为诊断成功。
  • TCPSocketAction:对指定端口上的容器IP地址进行TCP检查。如果端口打开,则诊断成功。
  • HTTPGetAction: 对指定端口和路径上的容器IP地址执行Http Get请求。如果响应的状态码大于等于200且小于400,则诊断成功。

livenessProbe:指示容器是否在运行。如果存活探测失败,则kubelet会杀死容器,并且容器将受到其 重启策略 的影响。如果容器不提供存活探针,则默认状态为Success。

readinessProbe:指示容器是否准备好服务请求。如果就绪探测失败,端点控制器将从与Pod匹配的所有Service的端点中删除该Pod的IP地址。初始化延迟之前的就绪状态默认是Failure。如果容器不提供就绪探针,则默认状态为Success。

钩子
Pod hook(钩子)是由Kubernetes管理的kubelet发起的,当容器中的进程启动前或者容器中的进程终止之前运行。这是包含在容器的生命周期之中的,同时为Pod中的所有容器都配置hook。
Hook的类型包括两种:exec(执行一段命令)和http(发送http请求)

Pod的状态

状态 解释
Pending Pod已被K8S系统接受,但由一个或者多个容器镜像尚未创建。等待时间包括调度Pod的时间和通过网络下载镜像的时间
Running 该Pod已经绑定到一个节点上,Pod中所有容器已经被创建。至少有一个容器正在运行,或正处于启动/重启状态
Succeeded Pod中所有容器都已成功,并且不会再重启
Failed Pod中的所有容器都已终止了,并且至少有一个容器是因为失败终止。
Unknown 因为某些原因无法获得Pod的状态,通常是因为与Pod所在主机通信失败

控制器

Kubernetes 通过引入 Controller(控制器)的概念来管理 Pod 实例。在 Kubernetes 中,您应该始终通过创建 Controller 来创建 Pod,而不是直接创建 Pod。控制器可以提供如下特性:

  • 水平扩展(运行 Pod 的多个副本)
  • rollout(版本更新)
  • self-healing(故障恢复) 例如:当一个节点出现故障,控制器可以自动地在另一个节点调度一个配置完全一样的 Pod,以替换故障节点上的 Pod。

RC 和 RS

Replication Controller 用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的Pod来替代;同理异常多出来的容器也会自动回收。在新版本的Kubernetes中建议用ReplicaSet来替代ReplicationController。ReplicaSet跟ReplicationController没有本质的不同,只是名字不一样,并且ReplicaSet支持集合式的selector(基于标签圈选pod)。
虽然ReplicaSet可以独立使用,但一般还是建议使用Deployment来自动管理ReplicaSet,这样就无需担心跟其他机制的不兼容问题(比如ReplicaSet不支持rolling update但是Deployment支持)。
ReplicaSet的定义中,包含:

  • selector: 用于指定哪些 Pod 属于该 ReplicaSet 的管辖范围
  • replicas: 副本数,用于指定该 ReplicaSet 应该维持多少个 Pod 副本
  • template: Pod模板,在 ReplicaSet 使用 Pod 模板的定义创建新的 Pod

ReplicaSet 控制器将通过创建或删除 Pod,以使得当前 Pod 数量达到 replicas 指定的期望值。ReplicaSet 创建的 Pod 中,都有一个字段 metadata.ownerReferences 用于标识该 Pod 从属于哪一个 ReplicaSet。

ReplicaSet 通过 selector 字段的定义,识别哪些 Pod 应该由其管理。如果 Pod 没有 ownerReference 字段,或者 ownerReference 字段指向的对象不是一个控制器,且该 Pod 匹配了 ReplicaSet 的 selector,则该 Pod 的 ownerReference 将被修改为 该 ReplicaSet 的引用。
ReplicaSet的定义示例如下:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: nginx
        image: nginx

Deployment

Deployment 是最常用的用于部署无状态服务的方式,为Pod和ReplicaSet提供了一个声明式方法。Deployment 控制器使得您能够以声明的方式更新 Pod(容器组)和 ReplicaSet(副本集)。典型的应用场景包括:

  • 定义Deployment来创建Pod和ReplicaSet
  • 滚动升级和回滚应用
  • 扩容和缩容
  • 暂停和继续Deployment

创建deployment
上文通过deployment创建pod已经贴了对应的yaml文件,此处不再赘述。

更新deployment
例如将nginx镜像版本更新到1.9.1
kubectl --record deployment.apps/nginx-deployment set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1

回滚deployment
发布失败时需要回滚。
第一步:执行命令 kubectl rollout history deployment.v1.apps/nginx-deployment 检查 Deployment 的历史版本。
第二步:执行命令 kubectl rollout history deployment.v1.apps/nginx-deployment --revision=2,查看 revision(版本)的详细信息。
第三步:执行命令 kubectl rollout undo deployment.v1.apps/nginx-deployment 将当前版本回滚到前一个版本。也可以使用 --to-revision 选项回滚到前面的某一个指定版本。
第四步:执行命令 kubectl get deployment nginx-deployment,检查该回滚是否成功,Deployment 是否按预期的运行

StatefulSet

Deployments和ReplicaSets是为了无状态服务而设计的。业务场景中还有一种有状态服务的诉求。StatefulSet是为了解决有状态服务的问题而设计的,应用场景包括:

  • 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
  • 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有ClusterIP 的Service)来实现。
  • 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次进行(即从0到N-1,在下一个Pod运行之前所有的Pod必须都是Running和Ready状态),基于init containers来实现
  • 有序收缩,有序删除(即从N-1到0)

statefulSet的spec中必须包含volumeClaimTemplates属性,对应的子属性如下示例:

  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

稳定的存储
Kubernetes 为每一个 VolumeClaimTemplate 创建一份 PersistentVolume(存储卷)。在上面的例子中,每一个 Pod 都将由 StorageClass(存储类)my-storage-class 为其创建一个 1Gib 大小的 PersistentVolume(存储卷)。当 Pod 被调度(或重新调度)到一个节点上,其挂载点将挂载该存储卷声明(关联到该 PersistentVolume)。

DaemonSet

DaemonSet确保全部(或者一些)Node上运行一个Pod的副本。当有Node加入集群时,也会为他们新增一个Pod。当有Node从集群移除时,这些Pod也会被回收。删除DaemonSet将会删除它创建的所有Pod。使用DaemonSet的一些定型用法:

  • 运行进群存储daemon,例如在每个Node上运行glusterd、ceph。
  • 在每个Node上运行日志收集daemon,例如fluentd、logstash
  • 在每个Node上运行监控daemon,例如prometheus

下面是 DaemonSet 的 YAML 文件示例 daemonset.yaml。该例子中的 DaemonSet 运行了一个 fluentd-elasticsearch 的 docker 镜像:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: fluent/fluentd-kubernetes-daemonset:v1.7.1-debian-syslog-1.0
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

Pod Template
.spec.template 是必填字段,定义了 Pod 的模板,与定义 Pod 的 yaml 格式完全相同(除了内嵌在 DaemonSet 中以外,没有 kind、APIVersion 字段以外)。
在 DaemonSet 中,您必须指定 .spec.template.metadata.labels 字段和 .spec.tempalte.spec 字段。
DaemonSet 的 .spec.template.spec.restartPolicy 字段必须为 Always,或者不填(默认值为 Always)

Pod Selector
.spec.selector 字段定义了 DaemonSet 的 pod selector,DaemonSet 认为符合该选择器的 Pod 由其管理。
自 Kubernets v1.8 以后,.spec.selector 是必填字段,且您指定该字段时,必须与 .spec.template.metata.labels 字段匹配(不匹配的情况下创建 DaemonSet 将失败)。DaemonSet 创建以后,.spec.selector 字段就不可再修改。如果修改,可能导致不可预见的结果。

job 和 cronjob

Job负责批处理任务,即仅执行一次的任务,它确保批处理任务的一个或多个Pod成功结束。Cronjob管理基于时间的Job,即在给定的时间运行一次或周期性运行任务。
在下面这个 Job 的例子中,Pod 执行了一个跟 π 相关的计算,并打印出最终结果,该计算大约需要 10 秒钟执行结束。

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

对应的crobjob声明:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: pi
            image: perl
            command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
          restartPolicy: Never

服务发现、负载均衡、网络

Service

Kubernetes 中 Service 是一个 API 对象,通过 kubectl + YAML 定义一个 Service,可以将符合 Service 指定条件的 Pod 作为可通过网络访问的服务提供给服务调用者。Service 是 Kubernetes 中的一种服务发现机制:

  • Pod 有自己的 IP 地址
  • Service 被赋予一个唯一的 dns name
  • Service 通过 label selector 选定一组 Pod
  • Service 实现负载均衡,可将请求均衡分发到选定这一组 Pod 中

例如,假设您有一组 Pod:
1)每个 Pod 都监听 9376 TCP 端口
2)每个 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 对象中。

服务代理

Kubernetes 集群中的每个节点都运行了一个 kube-proxy,负责为 Service(ExternalName 类型的除外)提供虚拟 IP 访问。
Kubernetes 支持三种 proxy mode(代理模式),他们的版本兼容性如下:

代理模式 Kubernetes 版本 是否默认
User space proxy mode v1.0 +
Iptables proxy mode v1.1 + 默认
Ipvs proxy mode v1.8 +

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 的设定

如下图所示:


user space模式

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

如下图所示:


iptable模式

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模式

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 的细节。

服务发现--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-servicemy-service.my-ns 也可以)。其他名称空间的 Pod 必须使用 my-service.my-nsmy-servicemy-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 指定的地址(例如: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

Ingress Controller 通常是部署在集群中的一个 Deployment,并且通过 NodePort Service 暴露自己的端口,使得用户可以在集群外通过其 NodePort 访问到 Ingress Controller,假设该端口为 32351,并且 demo.my-company.com 这个域名被解析到集群中某一个节点的 IP(或者被配置到浏览器所在机器的 hosts 文件),则当用户在浏览器中输入地址 http://demo.my-company.com:32351 时:

  1. 请求被转发到集群某节点的 32351 节点端口;
  2. 根据 Service 的定义,请求被转发到 Ingress Controller 的 Web 端口;
  3. Ingress Controller 根据请求域名 demo.my-company.com 以及请求路径,匹配到 Ingress 定义中该请求应该被路由到 nginx-service80 端口;
  4. Ingress Controller 执行 L7 路由转发,将请求发送到 nginx-service80 端口。

配置Pod的 /etc/hosts

某些情况下,DNS 或者其他的域名解析方法可能不太适用,您需要配置 /etc/hosts 文件,在Linux下是比较容易做到的,在 Kubernetes 中,可以通过 Pod 定义中的 hostAliases 字段向 Pod 的 /etc/hosts 添加条目。
使用hostAliases添加额外的条目
通过 Pod 定义中的 .spec.hostAliases 字段,我们可以向 Pod 的 /etc/hosts 文件中添加额外的条目,用来解析 foo.local、bar.local 到 127.0.0.1 和 foo.remote、bar.remote 到 10.1.2.3,如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: hostaliases-pod
spec:
  restartPolicy: Never
  hostAliases:
  - ip: "127.0.0.1"
    hostnames:
    - "foo.local"
    - "bar.local"
  - ip: "10.1.2.3"
    hostnames:
    - "foo.remote"
    - "bar.remote"
  containers:
  - name: cat-hosts
    image: busybox
    command:
    - cat
    args:
    - "/etc/hosts"

K8S网络

容器网络

容器网络是容器选择连接到其他容器、主机和外部网络(如Internet)的机制。CNI意为容器网络接口,它是一种标准的设计,为了让用户在容器创建或销毁时都能够更容易地配置容器网络。目前最流行的CNI插件是Flannel。Flannel插件既可以确保满足Kubernetes的网络要求,又能为Kubernetes集群管理员提供他们所需的某些特定的网络功能。
容器的Runtime提供了各种网络模式,每种模式都会产生不同的体验。例如,Docker默认情况下可以为容器配置以下网络:

  • none:将容器添加到一个容器专门的网络堆栈中,没有对外连接。
  • host:将容器添加到主机的网络堆栈中,没有隔离。
  • default bridge:默认网络模式。每个容器可以通过IP地址相互连接。
  • 自定义网桥:用户定义的网桥,具有更多的灵活性、隔离性和其他便利功能。

Docker还可以让用户通过其他驱动程序和插件,来配置更高级的网络(包括多主机覆盖网络)。

CNI的初衷是创建一个框架,用于在配置或销毁容器时动态配置适当的网络配置和资源。CNI规范概括了用于配制网络的插件接口,这个接口可以让容器运行时与插件进行协调。
插件负责为接口配置和管理IP地址,并且通常提供与IP管理、每个容器的IP分配、以及多主机连接相关的功能。容器运行时会调用网络插件,从而在容器启动时分配IP地址并配置网络,并在删除容器时再次调用它以清理这些资源。

运行时或协调器决定了容器应该加入哪个网络以及它需要调用哪个插件。然后,插件会将接口添加到容器网络命名空间中,作为一个veth对的一侧。接着,它会在主机上进行更改,包括将veth的其他部分连接到网桥。再之后,它会通过调用单独的IPAM(IP地址管理)插件来分配IP地址并设置路由。

Flannel插件
Flannel是CoreOs团队针对kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。而且它还能在这些IP之间建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递到目标容器内。Flannel监控ETCD中每个Pod的实际地址,并在内存中建立维护Pod节点路由表。

k8s的3层网络

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。

网络策略

Kubernetes 中,Network Policy(网络策略)定义了一组 Pod 是否允许相互通信,或者与网络中的其他端点 endpoint 通信。Network Policy 由网络插件实现,因此,您使用的网络插件必须能够支持 NetworkPolicy 才可以使用此特性。
NetworkPolicy 对象使用标签选择Pod,并定义规则指定选中的Pod可以执行什么样的网络通信,规则通常由如下三类信息组合而成:

  • 允许访问的其他容器组(容器组不能阻止其访问自己的端口)
  • 允许访问的名称空间
  • 允许访问的 IP 段(例外:从容器组所在的节点访问容器组,或者从容器组访问其所在的节点都是始终被允许的)

一个 NetworkPolicy 的 Example 如下所示:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector: //同名称空间中,符合此标签选择器 .spec.podSelector 的 Pod 都将应用这个 NetworkPolicy。
    matchLabels:
      role: db //podSelector 选择了 role=db 的 Pod。如果该字段为空,则将对名称空间中所有的 Pod 应用这个 NetworkPolicy
  policyTypes: //是一个数组类型的字段,该数组中可以包含 Ingress、Egress 中的一个,也可能两个都包含。
  - Ingress
  - Egress
  ingress: //是一个数组,代表入方向的白名单规则。每一条规则都将允许与 from 和 ports 匹配的入方向的网络流量发生。
  - from: //求方可以是如下三种来源当中的任意一种
    - ipBlock: 
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels: //namespaceSelector 标签选择器,匹配标签为 project=myproject
          project: myproject 
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress: //是一个数组,代表出方向的白名单规则。每一条规则都将允许与 to 和 ports 匹配的出方向的网络流量发生。
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978

NetworkPolicy 的 .spec.ingress.from 和 .spec.egress.to 字段中,可以指定 4 种类型的标签选择器:

podSelector 选择与 NetworkPolicy 同名称空间中的 Pod 作为入方向访问控制规则的源或者出方向访问控制规则的目标

namespaceSelector 选择某个名称空间(其中所有的Pod)作为入方向访问控制规则的源或者出方向访问控制规则的目标

namespaceSelector 和 podSelector 在一个 to / from 条目中同时包含 namespaceSelector 和 podSelector 将选中指定名称空间中的指定 Pod。

网络模型

Kubernetes 为每一个 Pod 都创建了一个 network namespace。Pod 是一组 docker 容器的集合,这一组 docker 容器将共享一个 network namespace。Pod 中所有的容器都使用该 network namespace 提供的同一个 IP 地址以及同一个端口空间,可以通过 localhost 直接与同一个 Pod 中的另一个容器通信。
Pod-to-Pod的网络
从 Pod 的视角来看,Pod 是在其自身所在的 network namespace 与同节点上另外一个 network namespace 进程通信。在Linux上,不同的 network namespace 可以通过 Virtual Ethernet Device (opens new window)veth pair (两块跨多个名称空间的虚拟网卡)进行通信。
连接 pod 的 network namespace,可以将 veth pair 的一段指定到 root network namespace,另一端指定到 Pod 的 network namespace。每一组 veth pair 类似于一条网线,连接两端,并可以使流量通过。节点上有多少个 Pod,就会设置多少组 veth pair。下图展示了 veth pair 连接 Pod 到 root namespace 的情况:

pod2pod通信

Linux 将所有的进程都分配到 root network namespace,以使得进程可以访问外部网络,也即通过eth0进行外部通信。 容器内的eth0是虚拟网卡。
为了让 Pod 可以互相通过 root network namespace 通信,我们将使用 network bridge(网桥)。
Linux Ethernet bridge 是一个虚拟的 Layer 2 网络设备,可用来连接两个或多个网段(network segment)。网桥的工作原理是,在源于目标之间维护一个转发表(forwarding table),通过检查通过网桥的数据包的目标地址(destination)和该转发表来决定是否将数据包转发到与网桥相连的另一个网段。桥接代码通过网络中具备唯一性的网卡MAC地址来判断是否桥接或丢弃数据。

网桥实现了 ARP (opens new window)协议,以发现链路层与 IP 地址绑定的 MAC 地址。当网桥收到数据帧时,网桥将该数据帧广播到所有连接的设备上(除了发送者以外),对该数据帧做出相应的设备被记录到一个查找表中(lookup table)。后续网桥再收到发向同一个 IP 地址的流量时,将使用查找表(lookup table)来找到对应的 MAC 地址,并转发数据包。
如果目的地址不是当前IP,则属于跨节点通信,数据包到达 root namespace 中的网桥 cbr0。网桥上执行 ARP 将会失败,因为与网桥连接的所有设备中,没有与该数据包匹配的 MAC 地址。一旦 ARP 失败,网桥会将数据包发送到默认路由(root namespace 中的 eth0 设备)。此时,数据包离开节点进入网络。网络可以根据各节点的CIDR网段,将数据包路由到正确的节点。如下图所示:

pod-to-pod

Pod-to-Service的网络
一个 Kubernetes Service 管理了一组 Pod 的状态,可以追踪一组 Pod 的 IP 地址的动态变化过程。一个 Service 拥有一个 IP 地址,并且充当了一组 Pod 的 IP 地址的“虚拟 IP 地址”。任何发送到 Service 的 IP 地址的数据包将被负载均衡到该 Service 对应的 Pod 上。在此情况下,Service 关联的 Pod 可以随时间动态变化,客户端只需要知道 Service 的 IP 地址即可(该地址不会发生变化)。
在 Kubernetes 中,kube-proxy 控制器监听 apiserver 中的变化,并配置 iptables 规则。当 Service 或 Pod 发生变化时(例如 Service 被分配了 IP 地址,或者新的 Pod 被关联到 Service),kube-proxy 控制器将更新 iptables 规则,以便将发送到 Service 的数据包正确地路由到其后端 Pod 上。iptables 规则将监听所有发向 Service 的虚拟 IP 的数据包,并将这些数据包转发到该Service 对应的一个随机的可用 Pod 的 IP 地址,同时 iptables 规则将修改数据包的目标 IP 地址(从 Service 的 IP 地址修改为选中的 Pod 的 IP 地址)。当 Pod 被创建或者被终止时,iptables 的规则也被对应的修改。换句话说,iptables 承担了从 Service IP 地址到实际 Pod IP 地址的负载均衡的工作。
在返回数据包的路径上,数据包从目标 Pod 发出,此时,iptables 规则又将数据包的 IP 头从 Pod 的 IP 地址替换为 Service 的 IP 地址。从请求的发起方来看,就好像始终只是在和 Service 的 IP 地址通信一样。

pod-to-service

Internet-to-Service的网络
出方向
Kubernetes 集群在 VPC 内运行,在此处,每一个节点都被分配了一个内网地址(private IP address)可以从 Kubernetes 集群内部访问。
为了使访问外部网络,通常会在 VPC 中添加互联网网关(Internet Gateway),以实现如下两个目的:

  • 作为 VPC 路由表中访问外网的目标地址
  • 提供网络地址转换(NAT Network Address Translation),将节点的内网地址映射到一个外网地址,以使外网可以访问内网上的节点

数据包的源地址是一个 Pod,因为其 NAT 只能识别与节点(虚拟机)相连的 IP 地址。因此,需要 iptables 执行源地址转换(source NAT),这样子,对互联网网关来说,该数据包就是从节点(虚拟机)发出的,而不是从 Pod 发出的。互联网网关再次执行源地址转换(source NAT),将数据包的源地址从节点(虚拟机)的内网地址修改为网关的外网地址,最终数据包被发送到互联网。图示如下:


pod-to-internet

入方向
当创建 Kubernetes Service 时,可以指定其类型为LoadBalancer和Ingress。
Layer 4--LoadBalancer:用户将请求发送到负载均衡器来访问 Kubernetes 中的 Service。负载均衡器可以将网络流量分发到其目标服务器组(即 Kubernetes 集群中的所有节点)。一旦数据包到达节点,Service 的 iptables 规则将确保其被转发到 Service 的一个后端 Pod。
Layer 7--Ingress:要实现 Layer 7 网络入方向访问,首先需要将 Service 指定为 NodtePort 类型,此时 Kubernetes master 将会为该 Service 分配一个节点,每一个节点上的 iptables 都会将此端口上的请求转发到 Service 的后端 Pod 上。

Ingress-to-Service 的数据包传递与 LoadBalancer-to-Service 的数据包传递非常相似。核心差别是:

  • Ingress 能够解析 URL 路径(可基于路径进行路由)
  • Ingress 连接到 Service 的 NodePort

存储

数据卷

Volume(数据卷)是一个可被容器组中的容器访问的文件目录(也许其中包含一些数据文件)。这个目录是怎么来的,取决于该数据卷的类型(不同类型的数据卷使用不同的存储介质)。
下图来理解 容器组、容器、挂载点、数据卷、存储介质(nfs、PVC、ConfigMap)等几个概念之间的关系:

  • 一个容器组可以包含多个数据卷、多个容器
  • 一个容器通过挂载点(volumnMount)决定某一个数据卷(Volumn)被挂载到容器中的什么路径
    不同类型的数据卷对应不同的存储介质(图中仅列出了 nfs、PVC、ConfigMap 三种存储介质)
数据卷

常见的数据卷的类型
Kubernetes 目前支持多达 28 种数据卷类型(其中大部分特定于具体的云环境如 GCE/AWS/Azure 等),如需查阅所有的数据卷类型,请查阅 Kubernetes 官方文档 Volumes(opens new window)

  • emptyDir:emptyDir类型的数据卷在容器组被创建时分配给该容器组,并且直到容器组被移除,该数据卷才被释放。该数据卷初始分配时,始终是一个空目录。同一容器组中的不同容器都可以对该目录执行读写操作,并且共享其中的数据,(尽管不同的容器可能将该数据卷挂载到容器中的不同路径)。当容器组被移除时,emptyDir数据卷中的数据将被永久删除。
  • NFS: NFS(Network File System)类型的数据卷可以加载到容器组/容器。容器组被移除时,将仅仅 umount(卸载)NFS 数据卷,NFS 中的数据仍将被保留。可以在加载 NFS 数据卷前就在其中准备好数据;可以在不同容器组之间共享数据;可以被多个容器组加载并同时读写。
  • cephfs:cephfs 数据卷可以挂载一个外部 CephFS 卷到您的容器组中。对于 kubernetes 而言,cephfs 与 nfs 的管理方式和行为完全相似,适用场景也相同。不同的仅仅是背后的存储介质。
  • hostPath:hostPath 类型的数据卷将 Pod(容器组)所在节点的文件系统上某一个文件或文件夹挂载进容器组(容器)。除了为 hostPath 指定 path 字段以外,您还可以为其指定 type 字段。
  • ConfigMap:提供了一种向容器组注入配置信息的途径。ConfigMap 中的数据可以被 Pod(容器组)中的容器作为一个数据卷挂载。在数据卷中引用 ConfigMap 可以直接引用整个 ConfigMap 到数据卷,此时 ConfigMap 中的每一个 key 对应一个文件名,value 对应该文件的内容;也可以只引用 ConfigMap 中的某一个名值对,此时可以将 key 映射成一个新的文件名。
  • secret:数据卷可以用来注入敏感信息(例如密码)到容器组。您可以将敏感信息存入 kubernetes secret 对象,并通过 Volume(数据卷)以文件的形式挂载到容器组(或容器)。secret 数据卷使用 tmpfs(基于 RAM 的文件系统)挂载。比如将 HTTPS 证书存入 kubernets secret,并挂载到 /etc/nginx/conf.d/myhost.crt、/etc/nginx/conf.d/myhost.pem 路径,用来配置 nginx 的 HTTPS 证书。
  • persistentVolumeClaim :用来挂载 PersistentVolume 存储卷。PersistentVolume 存储卷为用户提供了一种在无需关心具体所在云环境的情况下”声明“ 所需持久化存储的方式。

挂载

挂载是指将定义在 Pod 中的数据卷关联到容器,同一个 Pod 中的同一个数据卷可以被挂载到该 Pod 中的多个容器上。

数据卷内子路径
有时候我们需要在同一个 Pod 的不同容器间共享数据卷。使用 volumeMounts.subPath 属性,可以使容器在挂载数据卷时指向数据卷内部的一个子路径,而不是直接指向数据卷的根路径。

容器内路径
mountPath 数据卷被挂载到容器的路径,不能包含 :

权限
容器对挂载的数据卷是否具备读写权限,如果 readOnlytrue,则只读,否则可以读写(为 false 或者不指定)。默认为 false

存储卷PersistentVolume

与管理计算资源相比,管理存储资源是一个完全不同的问题。为了更好的管理存储,Kubernetes 引入了 PersistentVolume 和 PersistentVolumeClaim 两个概念,将存储管理抽象成如何提供存储以及如何使用存储两个关注点。
PersistentVolume(PV 存储卷)是集群中的一块存储空间,由集群管理员管理、或者由 Storage Class(存储类)自动管理。PV(存储卷)和 node(节点)一样,是集群中的资源(kubernetes 集群由存储资源和计算资源组成)。PersistentVolumeClaim(存储卷声明)是一种类型的 Volume(数据卷),PersistentVolumeClaim(存储卷声明)引用的 PersistentVolume(存储卷)有自己的生命周期,该生命周期独立于任何使用它的容器组。PersistentVolume(存储卷)描述了如何提供存储的细节信息(NFS、cephfs等存储的具体参数)。

PersistentVolumeClaim(PVC 存储卷声明)代表用户使用存储的请求。Pod 容器组消耗 node 计算资源,PVC 存储卷声明消耗 PersistentVolume 存储资源。Pod 容器组可以请求特定数量的计算资源(CPU / 内存);PersistentVolumeClaim 可以请求特定大小/特定访问模式(只能被单节点读写/可被多节点只读/可被多节点读写)的存储资源。
根据应用程序的特点不同,其所需要的存储资源也存在不同的要求,例如读写性能等。集群管理员必须能够提供关于 PersistentVolume(存储卷)的更多选择,无需用户关心存储卷背后的实现细节。为了解决这个问题,Kubernetes 引入了 StorageClass(存储类)的概念。StorageClass 为管理员提供了一种通过服务质量级别、备份策略或集群管理员制定存储卷 的方式确定何时执行 存储卷与存储卷声明的绑定、何时执行动态存储卷提供(动态创建存储卷)。

总结一下:PVC有点像接口,PV是具体的实现,StorageClass是工厂类。


PVC-PV

有两种方式为 PersistentVolumeClaim 提供 PersistentVolume : 静态、动态

  • 静态:集群管理员实现创建好一系列 PersistentVolume,它们包含了可供集群中应用程序
  • 动态:在配置有合适的 StorageClass(存储类)且 PersistentVolumeClaim 关联了该 StorageClass 的情况下,kubernetes 集群可以为应用程序动态创建 PersistentVolume。

ConfigMap

ConfigMap以一个或多个kv形式保存在K8S系统中供应用使用,既可以用于表示一个变量的值,也可以用于表示一个完整配置文件的内容(例如xml应用配置文件)。
可以通过yaml配置文件或者直接用kubectl create configmap命令行的方式创建ConfigMap。

  • 可以通过--from-file参数从文件中创建,可以指定key的名称,语法为kubectl create configmap {name} --from-file={source}
  • 通过--from-file参数从目录中创建,该目录下每个文件名都被设置为key,文件内容被设置为value。语法为kubectl create configmap {name} --from-file={config-files-dir}
  • --from-literal 从文本中进行创建,直接将指定的key=value创建为ConfigMao的内容,语法为kubectl create configmap {name} --from-literal=key1=value1 --from-literal=key2=value2

Pod中使用ConfigMap
1)通过环境变量方式使用ConfigMap。示例如下:

spec: # 仅展示相关属性配置
    containers:
    - name: cn-test
      env:
      -name : APPLOGLEVEL   # 定义环境变量名称
       valueFrom:                       # APPLOGLEVEL对应的值
          configMapKeyRef:       
             name : cn-appvars     # 环境变量的值取自 cn-appvars这个configMap
             key: apploglevel         # key为“apploglevel”

2)通过valumeMount使用ConfigMap。示例如下:

spec: # 仅展示相关属性配置
    containers:
    - name: cn-test
      volumnMounts:
      -name : serverxml   # 引用volume名
       mountPath: /configfiles #挂载到容器内的目录
    volumes:                       # APPLOGLEVEL对应的值
     - name: serverxml # 定义volume名
       configMap:       
          name : cn-appconfigfiles     # 使用configmap名“cn-appconfigfiles”
          items:
          - key: key-serverxml          # key为“key-serverxml”
            path:server.xml              # value 将server.xml文件名进行挂载到容器
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容