K8S基础

2021-12-22  本文已影响0人  彦帧

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

K8S概述

Kubernetes是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单高效,Kubernetes提供了应用部署、规划、更新、维护的一种机制。
在Kubenetes中,所有的容器均在Pod中运行,一个Pod可以承载一个或者多个相关的容器。同一个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相关组件介绍

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

其他重要插件:

K8S安装

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

K8S中的资源

K8S中所有的内容都抽象为资源,资源实例化之后叫做对象。在K8S中,一般适用yaml格式的文件来创建符合我们预期的Pod,这样的yaml文件一般称为资源清单。K8S中的资源可以分为:名称空间级资源、集群级资源、元数据型资源。
名称空间级资源(通过kubectl api-resources --namespaced=true查看全部)
K8S是有空间概念的,以下资源是归属于某个空间的(没有指定就是默认空间)。名称空间级资源又可以根据功能分为以下几种:

集群级资源(通过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。

工作负载

Node

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

节点管理

与 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 组件。在节点的生命周期中,节点控制器起到了许多作用。

节点自注册(Self-Registration)

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

如果 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命名空间的联合:

同一个Pod中的应用可以共享磁盘,磁盘是Pod级的,应用可以通过文件系统调用。

Pod创建流程

创建Pod的整个流程,时序图如下:


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,以替换故障节点上的 Pod。在 Kubernetes 中,广泛使用的控制器有:

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

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

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。控制器可以提供如下特性:

RC 和 RS

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

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
上文通过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是为了解决有状态服务的问题而设计的,应用场景包括:

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的一些定型用法:

下面是 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:
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 集群中的每个节点都运行了一个 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 代理模式

如下图所示:


user space模式

Iptables 代理模式--默认模式

如下图所示:


iptable模式

iptables proxy mode 的优点:

与 user space mode 的差异:

您可以配置 Pod 就绪检查(readiness probe)确保后端 Pod 正常工作,此时,在 iptables 模式下 kube-proxy 将只使用健康的后端 Pod,从而避免了 kube-proxy 将请求转发到已经存在问题的 Pod 上。

IPVS 代理模式

IPVS模式

IPVS proxy mode 基于 netfilter 的 hook 功能,与 iptables 代理模式相似,但是 IPVS 代理模式使用 hash table 作为底层的数据结构,并在 kernel space 运作。这就意味着:

IPVS 提供更多的负载均衡选项:

在所有的代理模式中,发送到 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,可选值有:

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默认情况下可以为容器配置以下网络:

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至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可以执行什么样的网络通信,规则通常由如下三类信息组合而成:

一个 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),以实现如下两个目的:

数据包的源地址是一个 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 的数据包传递非常相似。核心差别是:

存储

数据卷

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

数据卷

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

挂载

挂载是指将定义在 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 : 静态、动态

ConfigMap

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

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文件名进行挂载到容器
上一篇下一篇

猜你喜欢

热点阅读