Day22_K8S

文章目录

快速记忆

apply、create、patch

# apply = create新建 or patch是更新
kubectl apply -f nginx-pod.yaml

# create新建
kubectl create -f nginx-pod.yaml

# patch是更新
kubectl patch -f nginx-pod.yaml

# 删除
kubectl delete -f nginxpod.yaml

Pod的状态有哪些:

  • 挂起(Pending):pod尚未被调度到node上面
  • 运行中(Running):pod已经被调度至某节点
  • 成功(Succeeded):pod中的所有容器都已经成功终止
  • 失败(Failed):pod中至少有一个容器终止失败
  • 未知(Unknown):apiserver无法正常获取到pod对象的状态信息,通常由网络通信失败所导致

Pod生命周期

pod对象从创建至终止,它主要包含下面的过程:

  • pod创建过程
  • 运行初始化容器(init container)过程
  • 运行主容器(main container)
    • 容器启动后钩子(post start)
    • 容器的存活性探测(liveness probe)、就绪性探测(readiness probe)
    • 容器终止前钩子(pre stop)
  • pod终止过程

初始化容器是在pod的主容器启动之前要运行的容器,主要是做一些主容器的前置工作,初始化容器必须按照定义的顺序执行,当且仅当前一个成功之后,后面的一个才能运行。

初始化容器时我们可以自定义一些代码。比如在运行nginx之前先要能够连接上mysql和redis所在服务器,也就是初始化容器时一直ping这两个地址,ping通才能创建nginx容器,nignx容器创建成功对应的pod才算真正的启动起来了。

spec:
  containers:
  - name: main-container
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
  initContainers:
  - name: test-mysql
    image: busybox:1.30
    command: ['sh', '-c', 'until ping 192.168.90.14 -c 1 ; do echo waiting for mysql...; sleep 2; done;']
  - name: test-redis
    image: busybox:1.30
    command: ['sh', '-c', 'until ping 192.168.90.15 -c 1 ; do echo waiting for reids...; sleep 2; done;']

我们定义pod的其中一个容器时,可以定义postStart(容器启动后做什么)、preStop(容器停止前做什么)。比如定义nginx容器启动后向首页写一些字符串,定义nginx容器终止前停止nginx服务

apiVersion: v1
kind: Pod
metadata:
  name: pod-hook-exec
  namespace: dev
spec:
  containers:
  - name: main-container
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    lifecycle:
      postStart: 
        exec: # 在容器启动的时候执行一个命令,修改掉nginx的默认首页内容
          command: ["/bin/sh", "-c", "echo postStart... > /usr/share/nginx/html/index.html"]
      preStop:
        exec: # 在容器停止之前停止nginx服务
          command: ["/usr/sbin/nginx","-s","quit"]

有存活性探针和就绪性探针:

我们定义pod的其中一个容器时,可以定义存活性探针为“执行一个查看文件的命令”,如果这个文件不存在相当于健康检查不通过,那么容器启动失败,pod会一直重启。当这个文件存在时,健康检查通过,容器启动成功,pod也就启动成功

我们定义pod的其中一个容器时,可以定义存活性探针为“访问某个ip地址”,如果这个地址访问不通相当于健康检查不通过,那么容器启动失败,pod会一直重启。当这个地址可以访问通时,健康检查通过,容器启动成功,pod也就启动成功

我们甚至可以定义容器启动后30s开始探测,探测时间要持续5s,间隔多少秒探测一次之类的参数

kubernetes提供了两种探针来实现容器探测,分别是:

  • liveness probes:存活性探针,用于检测应用实例当前是否处于正常运行状态,如果不是,k8s会重启容器
  • readiness probes:就绪性探针,用于检测应用实例当前是否可以接收请求,如果不能,k8s不会转发流量

livenessProbe 决定是否重启容器,readinessProbe 决定是否将请求转发给容器。
ready是准备好的,readiness是准备就绪

不管是postStart构子还是preStop构子,都可以使用 exec、httpGet、tcpSocket等3种方法来定义。

  • httpGet:通过容器的ip、端口、路径发送http 请求,返回200-400范围内的状态码表示成功。
  • exec:在容器内执行shell命令,根据命令退出状态码是否为0进行判断,0表示健康,非0表示不健康。
  • TCPSocket:与容器的ip、端口建立TCP Socket链接,能建立则说明探测成功,不能建立则说明探测失败。

运行命令的方式

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-exec
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      exec:
        command: ["/bin/cat","/tmp/hello.txt"] # 执行一个查看文件的命令

发送http请求的方式

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:  # 其实就是访问http://127.0.0.1:80/hello  
        scheme: HTTP #支持的协议,http或者https
        port: 80 #端口号
        path: /hello #URI地址

Pod调度

一言蔽之:

​ pod调度有四类:

​ ①自动调度,完全由Scheduler经过一系列的算法计算得出

​ ②我这个pod就是要运行在哪个node上(定向调度)

​ ③我这个pod倾向于运行在哪个node上(亲和性调度)

​ ④我这个pod不倾向于运行在哪个node上(污点(容忍)调度)

定向调度: 指的是在pod上声明nodeName或者nodeSelector,以此将Pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使要调度的目标Node不存在,也会向上面进行调度,只不过pod运行失败而已。

亲和性调度都是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否要调度到指定的Node上;

亲和性调度:

  • nodeAffinity(node亲和性):pod只能(或优先)调度到满足指定的规则的Node上
  • podAffinity(pod亲和性) :新Pod必须(或最好)与拥有标签nodeenv=pro的pod在同一Node上
  • podAntiAffinity(pod反亲和性):新Pod必须(或最好)与拥有标签nodeenv=pro的pod不在同一Node上

污点是站在Node的角度上,通过在Node上添加污点属性,来决定是否允许Pod调度过来。
容忍是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否容忍某个污点

  • PreferNoSchedule:kubernetes将尽量避免把Pod调度到具有该污点的Node上,除非没有其他节点可调度
  • NoSchedule:kubernetes将不会把Pod调度到具有该污点的Node上,但不会影响当前Node上已存在的Pod
  • NoExecute:kubernetes将不会把Pod调度到具有该污点的Node上,同时也会将Node上已存在的Pod驱离

pod的重启策略有哪些?

可以通过pod.spec.restartPolicy字段配置重启容器的策略,重启策略如下3种配置:

定义pod的重启策略(注意,不是定义容器的重启策略)

  • Always :容器失效时,自动重启该容器,这也是默认值。
  • OnFailure : 容器终止运行且退出码不为0时重启
  • Never : 不论状态为何,都不重启该容器

pod的镜像拉取策略有哪几种?

配置镜像拉取策略

  • Always:总是从远程仓库拉取镜像(一直远程下载)
  • IfNotPresent:本地有则使用本地镜像,本地没有则从远程仓库拉取镜像(本地有就本地 本地没远程下载)
  • Never:只使用本地镜像,从不去远程仓库拉取,本地没有就报错 (一直使用本地)

Pod控制器介绍

在kubernetes中,有很多类型的pod控制器,每种都有自己的适合的场景,常见的有下面这些:

  • ReplicaSet:支持ReplicaSet扩缩容、ReplicaSet镜像升级
  • Deployment:通过控制ReplicaSet来控制Pod,并支持Deployment扩缩容、Deployment镜像升级、重建更新与滚动更新、Deployment版本回退、Deployment金丝雀发布、
  • Horizontal Pod Autoscaler:在HPA里面可以配置最小pod数量为1最大pod数量为10。那么日常状态下只有一个pod,当你用postman压测时pod可以创建10个,后面会缩小为1
  • DaemonSet:比如你有一个contract服务,k8s集群里面有4个node,DaemonSet类型的控制器可以保证在集群中的每一台(或指定)node上都运行一个contract的pod。一般适用于日志收集、节点监控等场景。也就是说,如果每个节点都需要且只需要一个contract服务的pod时,那么这类Pod就适合使用DaemonSet类型的控制器创建。
  • Job:主要用于负责批量处理短暂的一次性任务
  • Cronjob:job是只执行一次的任务, CronJob就是可以周期性执行的任务。

deployment的更新升级策略有哪些?

Deployment镜像更新支持两种更新策略:

  • Recreate:重建更新,在创建出新的Pod之前会先杀掉所有已存在的Pod;
  • RollingUpdate:滚动更新,就是杀死一部分,就启动一部分,在更新过程中,存在两个版本Pod

Service介绍

service:在kubernetes中,我们可以通过pod的ip来访问应用程序,但是pod会频繁重启,pod的ip地址不是固定的,所以我们可以借助ervice,service的ip和端口不会改变,这样外部的客户端的请求到达service后,service将请求转发到后端的多个pod实例上。

endpoint:endpoint是service里面维护的资源列表,endpoint资源对象保存着service关联的pod的ip和端口。从表面上看,当pod消失,service会在endpoint列表中剔除pod,当有新的pod加入,service就会将pod ip加入endpoint列表;

kube-proxy:外部的客户端的请求到达service后,service将请求转发到后端的多个pod实例上,实现服务的负载均衡。kube-proxy就是实现负载均衡功能的核心,因为它负责创建并维护路由规则。

kube-proxy目前支持三种工作模式:

kube-proxy的类型:userspace、iptables、ipvs
巧记:proxy就是代理,代理就是对ip代理,所以看到proxy你就应该想到ip


service的类型:clusterIp、NodePort、LoadBalancer
巧记:k8s的章节里面先讲了pod、然后是node,然后是service;所以你看到service就应该想到node

userspace 模式

userspace模式下,请求先到kube-proxy,然后由kube-proxy挑选一个pod,效率比较低,已经舍弃

iptables 模式

iptables模式下,kube-proxy的作用就是生成iptables规则,请求过来后直接将请求发向Cluster IP然后重定向到一个Pod IP上面(不再经过kube-proxy了),iptables 模式效率较高但不能提供灵活的LB策略

这里是引用

ipvs 模式

ipvs 模式下,kube-proxy的作用就是生成ipvs规则,发向Cluster IP的请求经过轮询然后重定向到一个Pod IP上面(不经过kube-proxy),ipvs模式效率较高且能提供灵活的LB策略

ipvs相对iptables转发效率更高,ipvs支持更多的LB算法。

Service类型

  • ClusterIP(重点):默认值,它是Kubernetes系统自动分配的虚拟IP,只能在集群内部访问,你使用浏览器什么的根本访问不了。
  • NodePort(重点):将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务,可以使用浏览器访问
  • LoadBalancer:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境支持

NodePort类型

在之前的样例中,创建的Service的ip地址只有集群内部才可以访问,如果希望将Service暴露给集群外部使用,那么就要使用到另外一种类型的Service,称为NodePort类型。NodePort的工作原理其实就是将service的端口映射到Node的一个端口上,然后就可以通过NodeIp:NodePort来访问service了。

这里是引用

LoadBalancer类型

LoadBalancer和NodePort很相似,目的都是向外部暴露一个端口,区别在于LoadBalancer会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境支持的,外部服务发送到这个设备上的请求,会被设备负载之后转发到集群中。

这里是引用

Ingress介绍

在前面课程中已经提到,Service对集群之外暴露服务的主要方式有两种:NotePort和LoadBalancer,但是这两种方式,都有一定的缺点:

  • NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显
  • LB方式的缺点是每个service需要一个外部的LB设备来支持,浪费又麻烦。

基于这种现状,kubernetes提供了Ingress资源对象,service都是ClusterIP类型的,通过ingress接入的外部流量。工作机制大致如下图表示:

这里是引用

实际上,Ingress相当于一个7层的负载均衡器,是kubernetes对反向代理的一个抽象,它的工作原理类似于Nginx,可以理解成在Ingress里建立诸多映射规则,Ingress Controller通过监听这些配置规则并转化成Nginx的反向代理配置 , 然后对外部提供服务

数据存储

EmptyDir是在Pod被分配到Node时创建的,并且无须指定宿主机上对应的目录文件,因为kubernetes会自动分配一个目录,当Pod销毁时EmptyDir中的数据也会被永久删除

EmptyDir用途如下:一个容器需要从另一个容器中获取数据的目录(多容器共享目录)
例:在一个Pod中准备两个容器nginx和busybox,然后声明一个Volume分别挂在到两个容器的目录中,然后nginx容器负责向Volume中写日志,busybox中通过命令将日志内容读到控制台。

这里是引用

2》HostPath

上节课提到,EmptyDir中数据不会被持久化,它会随着Pod的结束而销毁,如果想简单的将数据持久化到主机中,可以选择HostPath。

HostPath就是将Node主机中一个实际目录挂在到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据依据可以存在于Node主机上。

这里是引用

3》NFS

HostPath可以解决数据持久化的问题,但是一旦Node节点故障了,Pod如果转移到了别的节点,又会出现问题了,此时需要准备单独的网络存储系统,比较常用的用NFS、CIFS。

NFS是一个网络文件存储系统,可以搭建一台NFS服务器,然后将Pod中的存储直接连接到NFS系统上,这样的话,无论Pod在节点上怎么转移,只要Node跟NFS的对接没问题,数据就可以成功访问。

这里是引用

ConfigMap

宿主机里面创建一个ConfigMap,然后挂载到容器里面,这样当宿主机的ConfigMap变了,容器里面也会跟着变。这意味着可以在不停止应用程序的情况下修改配置,从而实现实时的配置更新。

Secret

在kubernetes中,还存在一种和ConfigMap非常类似的对象,称为Secret对象。它主要用于存储敏感信息,例如密码、秘钥、证书等等。

创建一个secret.yaml,然后在secret.yaml里面存一些加密的数据(用户名,密码等),然后创建Pod时使用这个Secret,这样在pod里面就可以直接读取里面的信息(无需解密)

k8s的组件有哪些,作用分别是什么?

k8s主要由master节点和node节点构成。master节点负责管理集群,node节点是容器应用真正运行的地方。

master节点包含的组件有:kube-api-server、kube-controller-manager、kube-scheduler、etcd
node节点包含的组件有:kubelet、kube-proxy、container-runtime

巧计:你使用kubectl命令创建一个Pod时,命令会传给kube-api-server验证授权,授权通过后kube-controller-manager自动创建新的Pod,kube-scheduler将待调度的pod通过一系列复杂的调度算法计算出最合适的node节点,pod、service等资源对象的信息保存到etcd里面

1》kube-api-server:提供了一套丰富的API,这些API支持对Kubernetes中各种资源对象(如Pod、Deployment、Service等)的增、删、改、查及监控等操作。当你使用kubectl命令创建一个Pod时,这个命令实际上是通过调用kube-api-server的API接口来完成的。
当你使用kubectl命令创建一个Pod时,kube-api-server首先接收这个创建Pod的请求。然后kube-api-server会对请求进行验证和授权检查。它确保请求者具有足够的权限来创建Pod。验证通过后,kube-api-server会将Pod对象的信息写入到etcd数据库中。调度器会定期检查etcd中的Pod状态,并根据调度算法选择一个合适的节点来运行Pod,kubelet会负责在该节点上创建和启动Pod中的容器。kubelet还会定期向kube-api-server报告容器的状态信息,包括容器的运行状态、健康状况和事件等。

2》kube-controller-manager:是k8s集群内部的管理控制中心,也是k8s自动化功能的核心;controller-manager内部包含多种控制器:replicaSet控制器、deployment控制器、daemonset控制器、job控制器、cronjob控制器等等各种资源对象的控制器。
假设你部署了一个需要3个副本的Web应用。kube-controller-manager中的副本控制器(或ReplicaSetController)会监控这个Web应用的Pod数量,如果发现Pod数量少于3个,它会自动创建新的Pod来补充,直到Pod数量达到预期的3个。

3》kube-scheduler:负责集群资源调度,其作用是将待调度的pod通过一系列复杂的调度算法计算出最合适的node节点,然后将pod绑定到目标节点上。

4》etcd:etcd是一个分布式的键值对存储数据库,主要是用于保存k8s集群状态数据,比如pod,service等资源对象的信息

5》kubelet:
①管理node。kubelet启动时会向api-server进行注册,然后会定时的向api-server汇报本节点信息状态、资源使用状态等,这样master知道了整个集群所有节点的资源情况。
②管理pod。kubelet负责维护node节点上pod的生命周期,比如要创建、更新、删除一个pod都用到了kubelet。
③容器健康检查。pod中可以定义启动探针、存活探针、就绪探针等,kubelet会定时执行pod中容器定义的探针

6》kube-proxy:在kubernetes中,我们可以通过pod的ip来访问应用程序,但是pod会频繁重启,pod的ip地址不是固定的,所以我们可以借助ervice,service的ip和端口不会改变,这样外部的客户端的请求到达service后,service将请求转发到后端的多个pod实例上。kube-proxy就是实现负载均衡功能的核心,因为它负责创建并维护路由规则。

7》container-runtime:容器运行时环境,目前k8s支持的容器运行时有很多,如docker、rkt、containerd或其他,比较受欢迎的是docker,但是新版的k8s已经宣布弃用docker。

一、以往笔记复习

1.资源管理方式

通过命令管理

直接使用命令去操作kubernetes资源

# 啥时候用命令式对象管理?当我们查询资源、分析资源时经常用命令方式去管理对象

kubectl top nodes

kubectl get pods -n dev

kubectl describe pod nginx -n dev
通过配置文件管理

①创建一个nginxpod.yaml,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: nginxpod
  namespace: dev
spec:
  containers:
  - name: nginx-containers
    image: nginx:latest

②通过命令配置去创建资源

# create是创建 
kubectl create -f nginx-pod.yaml

# patch是更新
kubectl patch -f nginx-pod.yaml

# 删除
kubectl delete -f nginxpod.yaml

③也可以通过apply命令

# apply就包含create创建和patch更新,如果没有就create,如果有就patch更新
kubectl apply -f nginx-pod.yaml


总结:
    其实声明式对象配置就是使用apply描述一个资源最终的状态(在yaml中定义状态)
    使用apply操作资源:
        如果资源不存在,就创建,相当于 kubectl create
        如果资源已存在,就更新,相当于 kubectl patch

2. 基本概念入门

2.1 Namespace

Namespace是kubernetes系统中的一种非常重要资源,它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离

一句话来讲:比如你弄两个namespace分别代表开发环境和测试环境,在不同namespace里面限制CPU使用量、内存使用量等等,然后将不同的namespace交给不同租户进行管理(限制不同租户的访问权限),那么就实现**多套环境的资源隔离**或者**多租户的资源隔离**。

这里是引用

2.2 Pod

在kubernetes的世界中,k8s并不直接处理容器,而是直接管理pod。所以Pod是kubernetes集群进行管理的最小单元

程序要运行必须部署在容器中(一般1个容器一般被设计成只运行1个进程),而容器必须存在于Pod中。Pod可以认为是容器的封装,一个Pod中可以存在一个或者多个容器

k8s会为每个pod分配一个集群内部唯一的ip地址,所以每个pod都拥有自己的ip地址、主机名、进程等;

这里是引用

2.3 Label

我们可以使用namespace来分组,但是不同的namespace下面的pod又无法直接通信,所以可以使用lable来实现分组,而且不同分组的pod还可以通信。

Label的特点:

  • 一个Label会以key/value键值对的形式附加到各种对象上,如Node、Pod、Service等等
  • 一个资源对象可以定义任意数量的Label ,同一个Label也可以被添加到任意数量的资源对象上去

一些常用的Label 示例如下:

  • 版本标签:“version”:“release”, “version”:“stable”…
  • 环境标签:“environment”:“dev”,“environment”:“test”,“environment”:“pro”
  • 架构标签:“tier”:“frontend”,“tier”:“backend”

标签定义完毕之后,还要考虑到标签的选择,这就要使用到Label Selector,即:

当前有两种Label Selector:

  • 基于等式的Label Selector

    name = slave: 选择所有包含Label中key="name"且value="slave"的对象

    env != production: 选择所有包括Label中的key="env"且value不等于"production"的对象

  • 基于集合的Label Selector

    name in (master, slave): 选择所有包含Label中的key="name"且value="master"或"slave"的对象

    name not in (frontend): 选择所有包含Label中的key="name"且value不等于"frontend"的对象

在deployment定义标签选择器,这样就通过标签选择器来选择哪些pod是受其控制的;service也是通过标签选择器来关联哪些pod最后其服务后端pod。

2.4 Deployment

在kubernetes中,Pod是最小的控制单元,但是kubernetes很少直接控制Pod,一般都是通过Pod控制器来完成的。Pod控制器用于pod的管理,确保pod资源符合预期的状态,当pod的资源出现故障时,会尝试进行重启或重建pod。(这就是为什么你之前删除pod是删不掉的,你必须删掉控制器才行)

在kubernetes中Pod控制器的种类有很多,本章节只介绍一种:Deployment。

这里是引用

2.5 Service

service:在kubernetes中,我们可以通过pod的ip来访问应用程序,但是pod会频繁重启,pod的ip地址不是固定的,所以我们可以借助ervice,service的ip和端口不会改变,这样外部的客户端的请求到达service后,service将请求转发到后端的多个pod实例上。

这里是引用

3. Pod详解

3.1 Pod相关配置

1》配置容器

一言蔽之:

​ 在pod里面自定义两个程序(naginx和busybox),启动pod后发现nginx容器启动成功,但是busybox容器启动失败

创建pod-base.yaml文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-base
  namespace: dev
  labels:
    user: heima
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  - name: busybox
    image: busybox:1.30
# 创建Pod
[root@k8s-master01 pod]# kubectl apply -f pod-base.yaml
pod/pod-base created

# 查看Pod状况
# READY 1/2 : 表示当前Pod中有2个容器,其中1个准备就绪,1个未就绪
# RESTARTS  : 重启次数,因为有1个容器故障了,Pod一直在重启试图恢复它
[root@k8s-master01 pod]# kubectl get pod -n dev
NAME       READY   STATUS    RESTARTS   AGE
pod-base   1/2     Running   4          95s

2》配置镜像拉取策略

一言蔽之:

  • Always:总是从远程仓库拉取镜像(一直远程下载)
  • IfNotPresent:本地有则使用本地镜像,本地没有则从远程仓库拉取镜像(本地有就本地 本地没远程下载)
  • Never:只使用本地镜像,从不去远程仓库拉取,本地没有就报错 (一直使用本地)

创建pod-imagepullpolicy.yaml文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-imagepullpolicy
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    imagePullPolicy: Never # 用于设置镜像拉取策略
  - name: busybox
    image: busybox:1.30

默认值说明:

如果镜像tag为具体版本号, 默认策略是:IfNotPresent

如果镜像tag为:latest(最终版本) ,默认策略是always

3》配置容器启动时执行的命令

一言蔽之:

​ 一个pod里面有好多个容器,有的容器启动后需要执行一些命令,那可以使用command来定义程序启动后执行什么命令。

创建pod-command.yaml文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-command
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done;"]

4》配置环境变量

一个pod里面有好多个容器,每一个容器里面都可以定义环境变量

创建pod-env.yaml文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-env
  namespace: dev
spec:
  containers:
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","while true;do /bin/echo $(date +%T);sleep 60; done;"]
    env: # 设置环境变量列表
    - name: "username"
      value: "admin"
    - name: "password"
      value: "123456"

5》配置端口

你可以给pod定义端口,访问容器中的程序使用Podip:containerPort(10.244.1.26:80)

apiVersion: v1
kind: Pod
metadata:
  name: pod-ports
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports: # 设置容器暴露的端口列表
    - name: nginx-port
      containerPort: 80
      protocol: TCP

6》配置资源配额

如果不对某个容器的资源做限制,那么它就可能吃掉大量资源,导致其它容器无法运行。

  • limits:用于限制运行时容器的最大占用资源,当容器占用资源超过limits时会被终止,并进行重启。
  • requests :用于设置容器需要的最小资源,如果环境资源不够,容器将无法启动

可以通过上面两个选项设置资源的上下限。limits是容器的上限,requests 是下限(规定启动容器的下限)

apiVersion: v1
kind: Pod
metadata:
  name: pod-resources
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    resources: # 资源配额
      limits:  # 限制资源(上限)
        cpu: "2" # CPU限制,单位是core数
        memory: "10Gi" # 内存限制
      requests: # 请求资源(下限)
        cpu: "1"  # CPU限制,单位是core数
        memory: "10Mi"  # 内存限制

3.2 Pod生命周期

Pod会出现的5种状态

  • 挂起(Pending):apiserver已经创建了pod资源对象,但它尚未被调度完成或者仍处于下载镜像的过程中
  • 运行中(Running):pod已经被调度至某节点,并且所有容器都已经被kubelet创建完成
  • 成功(Succeeded):pod中的所有容器都已经成功终止并且不会被重启
  • 失败(Failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态
  • 未知(Unknown):apiserver无法正常获取到pod对象的状态信息,通常由网络通信失败所导致

pod对象从创建至终止,它主要包含下面的过程:

  • pod创建过程
  • 运行初始化容器(init container)过程
  • 运行主容器(main container)
    • 容器启动后钩子(post start)
    • 容器的存活性探测(liveness probe)、就绪性探测(readiness probe)
    • 容器终止前钩子(pre stop)
  • pod终止过程
3.2.1 初始化容器

一言蔽之:

​ 初始化容器时我们可以自定义一些代码。比如在运行nginx之前先要能够连接上mysql和redis所在服务器,也就是初始化容器时一直ping这两个地址,ping通才能创建nginx容器,nignx容器创建成功对应的pod才算真正的启动起来了。

初始化容器是在pod的主容器启动之前要运行的容器,主要是做一些主容器的前置工作,初始化容器必须按照定义的顺序执行,当且仅当前一个成功之后,后面的一个才能运行。

假设要以主容器来运行nginx,但是要求在运行nginx之前先要能够连接上mysql和redis所在服务器

为了简化测试,事先规定好mysql`(192.168.90.14)`和redis`(192.168.90.15)`服务器的地址
apiVersion: v1
kind: Pod
metadata:
  name: pod-initcontainer
  namespace: dev
spec:
  containers:
  - name: main-container
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
  initContainers:
  - name: test-mysql
    image: busybox:1.30
    command: ['sh', '-c', 'until ping 192.168.90.14 -c 1 ; do echo waiting for mysql...; sleep 2; done;']
  - name: test-redis
    image: busybox:1.30
    command: ['sh', '-c', 'until ping 192.168.90.15 -c 1 ; do echo waiting for reids...; sleep 2; done;']
3.2.2 钩子函数

我们定义pod的其中一个容器时,可以定义postStart(容器启动后做什么)、preStop(容器停止前做什么)。比如定义nginx容器启动后向首页写一些字符串,定义nginx容器终止前停止nginx服务

apiVersion: v1
kind: Pod
metadata:
  name: pod-hook-exec
  namespace: dev
spec:
  containers:
  - name: main-container
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    lifecycle:
      postStart: 
        exec: # 在容器启动的时候执行一个命令,修改掉nginx的默认首页内容
          command: ["/bin/sh", "-c", "echo postStart... > /usr/share/nginx/html/index.html"]
      preStop:
        exec: # 在容器停止之前停止nginx服务
          command: ["/usr/sbin/nginx","-s","quit"]
3.2.3 容器探测

一言蔽之:

​ 有存活性探针和就绪性探针两类。

​ 我们定义pod的其中一个容器时,可以定义存活性探针为“执行一个查看文件的命令”,如果这个文件不存在相当于健康检查不通过,那么容器启动失败,pod会一直重启。当这个文件存在时,健康检查通过,容器启动成功,pod也就启动成功

​ 我们定义pod的其中一个容器时,可以定义存活性探针为“访问某个ip地址”,如果这个地址访问不通相当于健康检查不通过,那么容器启动失败,pod会一直重启。当这个地址可以访问通时,健康检查通过,容器启动成功,pod也就启动成功

​ 我们甚至可以定义容器启动后30s开始探测,探测时间要持续5s,间隔多少秒探测一次之类的参数

kubernetes提供了两种探针来实现容器探测,分别是:

  • liveness probes:存活性探针,用于检测应用实例当前是否处于正常运行状态,如果不是,k8s会重启容器
  • readiness probes:就绪性探针,用于检测应用实例当前是否可以接收请求,如果不能,k8s不会转发流量

livenessProbe 决定是否重启容器,readinessProbe 决定是否将请求转发给容器。
ready是准备好的,readiness是准备就绪

不管是postStart构子还是preStop构子,都可以使用 exec、httpGet、tcpSocket等3种方法来定义。

  • httpGet:通过容器的ip、端口、路径发送http 请求,返回200-400范围内的状态码表示成功。
  • exec:在容器内执行shell命令,根据命令退出状态码是否为0进行判断,0表示健康,非0表示不健康。
  • TCPSocket:与容器的ip、端口建立TCP Socket链接,能建立则说明探测成功,不能建立则说明探测失败。

运行命令的方式

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-exec
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      exec:
        command: ["/bin/cat","/tmp/hello.txt"] # 执行一个查看文件的命令

发送http请求的方式

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:  # 其实就是访问http://127.0.0.1:80/hello  
        scheme: HTTP #支持的协议,http或者https
        port: 80 #端口号
        path: /hello #URI地址
3.2.4 重启策略

定义pod的重启策略(注意,不是定义容器的重启策略),一旦容器探测出现了问题,kubernetes就会对容器所在的Pod进行重启

  • Always :容器失效时,自动重启该容器,这也是默认值。
  • OnFailure : 容器终止运行且退出码不为0时重启
  • Never : 不论状态为何,都不重启该容器

创建pod-restartpolicy.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: pod-restartpolicy
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:
        scheme: HTTP
        port: 80
        path: /hello
  restartPolicy: Never # 设置重启策略为Never,那么pod启动不起来的话不会重启

3.3 Pod调度

一言蔽之:

​ pod调度有四类:

​ ①自动调度,完全由Scheduler经过一系列的算法计算得出

​ ②我这个pod就是要运行在哪个node上(定向调度)

​ ③我这个pod倾向于运行在哪个node上(亲和性调度)

​ ④我这个pod不倾向于运行在哪个node上(污点(容忍)调度)

3.3.1 定向调度

一言蔽之:

​ 定向调度,指的是在pod上声明nodeName或者nodeSelector,以此将Pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使要调度的目标Node不存在,也会向上面进行调度,只不过pod运行失败而已。

  • 你可以在pod上面添加NodeName,这样该pod只会运行在该node上面
  • 你可以先给node指定一个标签,然后在pod上面指定调度到具有nodeenv=pro标签的节点上

两种定向调度的方式,有一定的问题,那就是如果没有满足条件的Node,那么Pod将不会被运行,即使在集群中还有可用Node列表也不行

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: node1 # 指定调度到node1节点上
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeselector
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeSelector: 
    nodeenv: pro # 指定调度到具有nodeenv=pro标签的节点上
3.3.2 亲和性调度

亲和性调度都是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否要调度到指定的Node上;

亲和性调度:

  • nodeAffinity(node亲和性):pod只能(或优先)调度到满足指定的规则的Node上
  • podAffinity(pod亲和性) :新Pod必须(或最好)与拥有标签nodeenv=pro的pod在同一Node上
  • podAntiAffinity(pod反亲和性):新Pod必须(或最好)与拥有标签nodeenv=pro的pod不在同一Node上

nodeAffinity(node亲和性):

  • 硬限制:pod只能调度到满足指定的规则的Node上,没有这样的node那这个pod就启动不起来。
  • 软限制:优先调度到满足指定的规则的Node,没有这样的node也没关系。
    场景:
    ①给pod设置一个硬限制,让pod只能调度到有标签env且env的值在[“xxx”,“yyy”]中的node上面,由于没有这样的node所以pod启动不起来;当有了这样的node后pod就启动起来了
    ①给pod设置一个软限制,让pod只能调度到有标签env且env的值在[“xxx”,“yyy”]中的node上面,虽然没有这样的node但是pod还是启动起来了

podAffinity(pod亲和性) :

  • 硬限制:新Pod必须要与拥有标签nodeenv=pro的pod在同一Node上,没有这样的pod那这个pod就启动不起来。
  • 软限制:新Pod最好与拥有标签nodeenv=pro的pod在同一Node上,没有这样的pod也没关系。
    场景:
    ①创建一个拥有标签podenv=pro的pod然后运行在node1上面,然后创建一个新Pod必须要与拥有标签nodeenv=xxx或者nodeenv=yyy的pod在同一Node上,显然现在没有这样pod,所以新的pod启动不起来;
    然后修改新Pod让其必须要与拥有标签nodeenv=pro或者nodeenv=yyy的pod在同一Node上,因为有这样的pod,所以启动成功。

podAntiAffinity(pod反亲和性):

  • 硬限制:新Pod必须要与拥有标签nodeenv=pro的pod不在同一Node上,没有这样的node那这个pod就启动不起来。
  • 软限制:新Pod最好与拥有标签nodeenv=pro的pod不在同一Node上,没有这样的node也没关系。
    场景:
3.3.3 污点和容忍

污点是站在Node的角度上,通过在Node上添加污点属性,来决定是否允许Pod调度过来。
容忍是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否容忍某个污点

亲和性调度:

  • nodeAffinity(node亲和性):pod只能(或优先)调度到满足指定的规则的Node上
  • podAffinity(pod亲和性) :新Pod必须(或最好)与拥有标签nodeenv=pro的pod在同一Node上
  • podAntiAffinity(pod反亲和性):新Pod必须(或最好)与拥有标签nodeenv=pro的pod不在同一Node上

污点:

亲和性调度都是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否要调度到指定的Node上,其实我们也可以站在Node的角度上,通过在Node上添加污点属性,来决定是否允许Pod调度过来。

  • PreferNoSchedule:kubernetes将尽量避免把Pod调度到具有该污点的Node上,除非没有其他节点可调度
  • NoSchedule:kubernetes将不会把Pod调度到具有该污点的Node上,但不会影响当前Node上已存在的Pod
  • NoExecute:kubernetes将不会把Pod调度到具有该污点的Node上,同时也会将Node上已存在的Pod驱离

场景:

①准备节点node1为node1节点设置一个污点: tag=heima:PreferNoSchedule;然后创建pod1( pod1 可以 )

②修改为node1节点设置一个污点: tag=heima:NoSchedule;然后创建pod2( pod1 正常 pod2 失败 )

③修改为node1节点设置一个污点: tag=heima:NoExecute;然后创建pod3 ( 3个pod都失败 )

污点
这里是引用

容忍(Toleration)

上面介绍了污点的作用,我们可以在node上添加污点用于拒绝pod调度上来,但是如果就是想将一个pod调度到一个有污点的node上去,这时候应该怎么做呢?这就要使用到容忍

这里是引用

污点就是拒绝,容忍就是忽略,Node通过污点拒绝pod调度上去,Pod通过容忍忽略拒绝

4. Pod控制器详解

4.1 Pod控制器介绍

什么是Pod控制器

Pod控制器是管理pod的中间层,使用Pod控制器之后,只需要告诉Pod控制器,想要多少个什么样的Pod就可以了,它会创建出满足条件的Pod并确保每一个Pod资源处于用户期望的目标状态。如果Pod资源在运行中出现故障,它会基于指定策略重新编排Pod。

在kubernetes中,有很多类型的pod控制器,每种都有自己的适合的场景,常见的有下面这些:

  • ReplicaSet:保证副本数量一直维持在期望值,并支持pod数量扩缩容镜像版本升级
  • Deployment:通过控制ReplicaSet来控制Pod,并支持滚动升级、回退版本
  • Horizontal Pod Autoscaler:可以根据集群负载自动水平调整Pod的数量,实现削峰填谷
  • DaemonSet:可以保证在集群中的每一台(或指定)node上都运行某个服务的pod
  • Job:主要用于负责批量处理短暂的一次性任务
  • Cronjob:job是只执行一次的任务, CronJob就是可以周期性执行的任务。

4.2 ReplicaSet(RS)

  • ①ReplicaSet扩缩容:创建ReplicatSet时一开始指定副本是3个,那么就会创建三个pod;修改副本数量为6此时pod会扩容为6个;然后又指定副本数量为2此时pod就会缩容为2
  • ②ReplicaSet镜像升级:更改ReplicatSet的配置文件里面的镜像版本,那么它管理的那些pod就都会更新成对应的版本
  • ③删除ReplicaSet: 删除RS时会删除此RS以及它管理的所有Pod(你删除pod这个pod会被删掉但是立刻会重新启动一个新的pod,但是你删除控制器RS那么这个控制器下面的pod就真的被删掉了)

4.3 Deployment(Deploy)

Deployment扩缩容:创建Deployment时一开始指定副本是3个,那么就会创建三个pod;修改副本数量为6此时pod会扩容为6个;然后又指定副本数量为2此时pod就会缩容为2

Deployment镜像更新:支持两种更新策略。

  • Recreate:重建更新,在创建出新的Pod之前会先杀掉所有已存在的Pod;
  • RollingUpdate:滚动更新,就是杀死一部分,就启动一部分,在更新过程中,存在两个版本Pod

Deployment版本回退:其实deployment之所以可是实现版本的回滚,就是通过记录下历史ReplicaSet来实现的,一旦想回滚到哪个版本,只需要将当前ReplicaSet版本的pod数量降为0,然后将回滚ReplicaSet版本的pod提升为目标数量就可以了

Deployment金丝雀发布:Deployment控制器支持在更新pod过程中使用“暂停(pause)”或“继续(resume)”等操作。比如有一批新的Pod资源创建完成后立即暂停更新过程,此时,仅存在一部分新版本的应用,主体部分还是旧的版本。然后,再筛选一小部分的用户请求路由到新版本的Pod应用,继续观察能否稳定地按期望的方式运行。确定没问题之后再继续完成余下的Pod资源滚动更新,否则立即回滚更新操作。这就是所谓的金丝雀发布。

Deployment删除:删除deployment,其下的rs和pod也将被删除

Deployment控制器并不直接管理pod,而是通过管理ReplicaSet来简介管理Pod,即:Deployment管理ReplicaSet,ReplicaSet管理Pod。所以Deployment比ReplicaSet功能更加强大。所以Deployment支持ReplicaSet的所有功能,还支持回滚、金丝雀发布等

这里是引用

4.4 HPA

在HPA里面可以配置最小pod数量为1最大pod数量为10还有cpu使用指标为3%,那么日常状态下只有一个pod,当你用postman压测时pod的cpu会超过3%那我HPA就会创建新的pod,最多可以创建10个,当请求量变小时我pod的数量也会缩小直到缩小为1。这就是HPA。

Kubernetes期望可以实现通过监测Pod的使用情况,实现pod数量的自动调整,于是就产生了Horizontal Pod Autoscaler(HPA)这种控制器。

这里是引用

4.5 DaemonSet(DS)

比如你有一个contract服务,k8s集群里面有4个node,DaemonSet类型的控制器可以保证在集群中的每一台(或指定)node上都运行一个contract的pod。一般适用于日志收集、节点监控等场景。也就是说,如果每个节点都需要且只需要一个contract服务的pod时,那么这类Pod就适合使用DaemonSet类型的控制器创建。

这里是引用

4.6 Job

Job,主要用于负责批量处理短暂的一次性任务。Job特点如下:

  • 当Job创建的pod执行成功结束时,Job将记录成功结束的pod数量
  • 当成功结束的pod达到指定的数量时,Job将完成执行

这里是引用

4.7 CronJob(CJ)

job是只执行一次的任务, CronJob就是可以周期性执行的任务。

你定时任务是每分钟执行一次,但是每个任务执行1.5分钟,第一个任务还没有执行完第二个任务就来了,到底是执行完第一个再执行第二个呢还是同时执行呢?你可以通过concurrencyPolicy来配置

CronJob控制器是通过控制 Job控制器来管理pod资源对象的。

这里是引用

5. Service详解

这里是引用

5.1 Service介绍

kube-proxy的类型:userspace、iptables、ipvs
巧记:proxy就是代理,代理就是对ip代理,所以看到proxy你就应该想到ip


service的类型:clusterIp、NodePort、LoadBalancer
巧记:k8s的章节里面先讲了pod、然后是node,然后是service;所以你看到service就应该想到node

service:在kubernetes中,我们可以通过pod的ip来访问应用程序,但是pod会频繁重启,pod的ip地址不是固定的,所以我们可以借助ervice,service的ip和端口不会改变,这样外部的客户端的请求到达service后,service将请求转发到后端的多个pod实例上。

这里是引用

Service在很多情况下只是一个概念,真正起作用的其实是kube-proxy服务进程

service:在kubernetes中,我们可以通过pod的ip来访问应用程序,但是pod会频繁重启,pod的ip地址不是固定的,所以我们可以借助ervice,service的ip和端口不会改变,这样外部的客户端的请求到达service后,service将请求转发到后端的多个pod实例上。

endpoint:endpoint是service里面维护的资源列表,endpoint资源对象保存着service关联的pod的ip和端口。从表面上看,当pod消失,service会在endpoint列表中剔除pod,当有新的pod加入,service就会将pod ip加入endpoint列表;

kube-proxy:外部的客户端的请求到达service后,service将请求转发到后端的多个pod实例上,实现服务的负载均衡。kube-proxy就是实现负载均衡功能的核心,因为它负责创建并维护路由规则。

这里是引用

# ipvsadm -Ln是查看ipvs规则的命令
# 10.97.97.97:80 是service提供的访问入口
# 当访问这个入口的时候,可以发现后面有三个pod的服务在等待调用,
# kube-proxy会基于rr(轮询)的策略,将请求分发到其中一个pod上去
# 这个规则会同时在集群内的所有节点上都生成,所以在任何一个节点,访问都可以。
# 可以看到你访问service地址,后台是三个pod在提供服务(请求轮询转发到三个pod上面)
[root@node1 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.97.97.97:80 rr
  -> 10.244.1.39:80               Masq    1      0          0
  -> 10.244.1.40:80               Masq    1      0          0
  -> 10.244.2.33:80               Masq    1      0          0

这里是引用

kube-proxy目前支持三种工作模式:

5.1.1 userspace 模式

userspace模式下,请求先到kube-proxy,然后由kube-proxy挑选一个pod,效率比较低,已经舍弃

5.1.2 iptables 模式

iptables模式下,kube-proxy的作用就是生成iptables规则,请求过来后直接将请求发向Cluster IP然后重定向到一个Pod IP上面(不再经过kube-proxy了),iptables 模式效率较高但不能提供灵活的LB策略

这里是引用

5.1.3 ipvs 模式

ipvs 模式下,kube-proxy的作用就是生成ipvs规则,发向Cluster IP的请求经过轮询然后重定向到一个Pod IP上面(不经过kube-proxy),ipvs模式效率较高且能提供灵活的LB策略

ipvs相对iptables转发效率更高,ipvs支持更多的LB算法。

这里是引用

5.2 Service类型

Service类型:

  • ClusterIP(重点):默认值,它是Kubernetes系统自动分配的虚拟IP,只能在集群内部访问,你使用浏览器什么的根本访问不了。
  • NodePort(重点):将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务,可以使用浏览器访问
  • LoadBalancer:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境支持

NodePort类型

在之前的样例中,创建的Service的ip地址只有集群内部才可以访问,如果希望将Service暴露给集群外部使用,那么就要使用到另外一种类型的Service,称为NodePort类型。NodePort的工作原理其实就是将service的端口映射到Node的一个端口上,然后就可以通过NodeIp:NodePort来访问service了。

这里是引用

LoadBalancer类型

LoadBalancer和NodePort很相似,目的都是向外部暴露一个端口,区别在于LoadBalancer会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境支持的,外部服务发送到这个设备上的请求,会被设备负载之后转发到集群中。

这里是引用

5.3 Ingress介绍

在前面课程中已经提到,Service对集群之外暴露服务的主要方式有两种:NotePort和LoadBalancer,但是这两种方式,都有一定的缺点:

  • NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显
  • LB方式的缺点是每个service需要一个外部的LB设备来支持,浪费又麻烦。

基于这种现状,kubernetes提供了Ingress资源对象,service都是ClusterIP类型的,通过ingress接入的外部流量。工作机制大致如下图表示:

这里是引用

实际上,Ingress相当于一个7层的负载均衡器,是kubernetes对反向代理的一个抽象,它的工作原理类似于Nginx,可以理解成在Ingress里建立诸多映射规则,Ingress Controller通过监听这些配置规则并转化成Nginx的反向代理配置 , 然后对外部提供服务

6. 数据存储

在前面已经提到,容器的生命周期可能很短,会被频繁地创建和销毁。那么容器在销毁时,保存在容器中的数据也会被清除。这种结果对用户来说,在某些情况下是不乐意看到的。为了持久化保存容器的数据,kubernetes引入了Volume的概念。Volume的生命容器不与Pod中单个容器的生命周期相关,当容器终止或者重启时,Volume中的数据也不会丢失。

kubernetes的Volume支持多种类型,比较常见的有下面几个:

  • 简单存储:EmptyDir、HostPath、NFS
  • 高级存储:PV、PVC
  • 配置存储:ConfigMap、Secret

6.1 基本存储

1》EmptyDir

EmptyDir是最基础的Volume类型,一个EmptyDir就是Host上的一个空目录。

EmptyDir是在Pod被分配到Node时创建的,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为kubernetes会自动分配一个目录,当Pod销毁时EmptyDir中的数据也会被永久删除。 EmptyDir用途如下:

  • 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留
  • 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)

接下来,通过一个容器之间文件共享的案例来使用一下EmptyDir。

在一个Pod中准备两个容器nginx和busybox,然后声明一个Volume分别挂在到两个容器的目录中,然后nginx容器负责向Volume中写日志,busybox中通过命令将日志内容读到控制台。

这里是引用

2》HostPath

上节课提到,EmptyDir中数据不会被持久化,它会随着Pod的结束而销毁,如果想简单的将数据持久化到主机中,可以选择HostPath。

HostPath就是将Node主机中一个实际目录挂在到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据依据可以存在于Node主机上。

这里是引用

3》NFS

HostPath可以解决数据持久化的问题,但是一旦Node节点故障了,Pod如果转移到了别的节点,又会出现问题了,此时需要准备单独的网络存储系统,比较常用的用NFS、CIFS。

NFS是一个网络文件存储系统,可以搭建一台NFS服务器,然后将Pod中的存储直接连接到NFS系统上,这样的话,无论Pod在节点上怎么转移,只要Node跟NFS的对接没问题,数据就可以成功访问。

这里是引用

6.2 高级存储—PV和PVC

略,能喊出名字就行

6.3 配置存储

6.3.1 ConfigMap

宿主机里面创建一个ConfigMap,然后挂载到容器里面,这样当宿主机的ConfigMap变了,容器里面也会跟着变。这意味着可以在不停止应用程序的情况下修改配置,从而实现实时的配置更新。

6.3.2 Secret

在kubernetes中,还存在一种和ConfigMap非常类似的对象,称为Secret对象。它主要用于存储敏感信息,例如密码、秘钥、证书等等。

创建一个secret.yaml,然后在secret.yaml里面存一些加密的数据(用户名,密码等),然后创建Pod时使用这个Secret,这样在pod里面就可以直接读取里面的信息(无需解密)

二、K8S全局问题

什么是k8s?说出你的理解

k8s就是一个编排容器的系统,一个可以管理容器应用全生命周期的工具,从创建—部署—提供服务—应用更新,都非常的方便,而且还可以做到故障自愈,所以,k8s是一个非常强大的容器编排系统。

k8s的组件有哪些,作用分别是什么?

k8s主要由master节点和node节点构成。master节点负责管理集群,node节点是容器应用真正运行的地方。

master节点包含的组件有:kube-api-server、kube-controller-manager、kube-scheduler、etcd
node节点包含的组件有:kubelet、kube-proxy、container-runtime

巧计:你使用kubectl命令创建一个Pod时,命令会传给kube-api-server验证授权,授权通过后kube-controller-manager自动创建新的Pod,kube-scheduler将待调度的pod通过一系列复杂的调度算法计算出最合适的node节点,pod、service等资源对象的信息保存到etcd里面

1》kube-api-server:提供了一套丰富的API,这些API支持对Kubernetes中各种资源对象(如Pod、Deployment、Service等)的增、删、改、查及监控等操作。当你使用kubectl命令创建一个Pod时,这个命令实际上是通过调用kube-api-server的API接口来完成的。
当你使用kubectl命令创建一个Pod时,kube-api-server首先接收这个创建Pod的请求。然后kube-api-server会对请求进行验证和授权检查。它确保请求者具有足够的权限来创建Pod。验证通过后,kube-api-server会将Pod对象的信息写入到etcd数据库中。调度器会定期检查etcd中的Pod状态,并根据调度算法选择一个合适的节点来运行Pod,kubelet会负责在该节点上创建和启动Pod中的容器。kubelet还会定期向kube-api-server报告容器的状态信息,包括容器的运行状态、健康状况和事件等。

2》kube-controller-manager:是k8s集群内部的管理控制中心,也是k8s自动化功能的核心;controller-manager内部包含多种控制器:replicaSet控制器、deployment控制器、daemonset控制器、job控制器、cronjob控制器等等各种资源对象的控制器。
假设你部署了一个需要3个副本的Web应用。kube-controller-manager中的副本控制器(或ReplicaSetController)会监控这个Web应用的Pod数量,如果发现Pod数量少于3个,它会自动创建新的Pod来补充,直到Pod数量达到预期的3个。

这里是引用

3》kube-scheduler:负责集群资源调度,其作用是将待调度的pod通过一系列复杂的调度算法计算出最合适的node节点,然后将pod绑定到目标节点上。

4》etcd:etcd是一个分布式的键值对存储数据库,主要是用于保存k8s集群状态数据,比如pod,service等资源对象的信息

5》kubelet:
①管理node。kubelet启动时会向api-server进行注册,然后会定时的向api-server汇报本节点信息状态、资源使用状态等,这样master知道了整个集群所有节点的资源情况。
②管理pod。kubelet负责维护node节点上pod的生命周期,比如要创建、更新、删除一个pod都用到了kubelet。
③容器健康检查。pod中可以定义启动探针、存活探针、就绪探针等,kubelet会定时执行pod中容器定义的探针

6》kube-proxy:在kubernetes中,我们可以通过pod的ip来访问应用程序,但是pod会频繁重启,pod的ip地址不是固定的,所以我们可以借助ervice,service的ip和端口不会改变,这样外部的客户端的请求到达service后,service将请求转发到后端的多个pod实例上。kube-proxy就是实现负载均衡功能的核心,因为它负责创建并维护路由规则。

7》container-runtime:容器运行时环境,目前k8s支持的容器运行时有很多,如docker、rkt、containerd或其他,比较受欢迎的是docker,但是新版的k8s已经宣布弃用docker。

简单讲一下 pod创建过程(经常问,必须牢记)

如果面试官说的是使用deployment来创建pod,则可以这样回答:

1、首先,用户使用kubectl create命令或者kubectl apply命令提交了要创建一个deployment资源请求;
2、api-server收到创建资源的请求后,会对客户端操作进行身份认证、进行鉴权,当api-server确定客户端的请求合法后,就会接受本次操作,并把相关的信息保存到etcd中
3、api-server把相关的信息保存到etcd中,其他组件使用watch机制跟踪api-server上的变动。
4、controller-manager组件会监听api-server的信息,controller-manager是有多个类型的,比如Deployment Controller, 它的作用就是负责监听Deployment,此时Deployment Controller发现有新的deployment要创建,那么它就会去创建一个ReplicaSet,一个ReplicaSet的产生,又被另一个叫做ReplicaSet Controller监听到了,紧接着它就会依照ReplicaSet的template去创建pod, 它一看这个pod并不存在,那么就新建此pod,当pod刚被创建时,它的nodeName属性值为空,代表着此pod未被调度。
5、接着调度器Scheduler组件发现有未调度的pod,则根据节点资源情况,pod定义的亲和性反亲和性等等,调度器会综合的选出一批候选节点,在候选节点中选择一个最优的节点,然后将pod绑定到该节点,将信息反馈给api-server。
6、某个node通过watch机制监听到有一个pod应该要被调度到自身上来,kubelet首先判断本地是否在此pod,如果不存在,则会进入创建pod流程。kubelet创建完pod,将信息反馈给api-server,api-servier将pod信息写入etcd。
7、pod建立成功后,ReplicaSet Controller会对其持续进行关注,如果pod因意外或被我们手动退出,ReplicaSet Controller会知道,并创建新的pod,以保持replicas数量期望值。

描述一下pod的终止流程(记住,经常问)

以下为容器在 Kubernetes 环境中的pod终止流程:
1、用户向api-server发送删除pod对象的命令,此时 Pod 状态置为 Terminating,启动pod关闭过程
2、kube-proxy 更新转发规则,将 Pod 从 service 的 endpoint 列表中摘除掉,新的流量不再转发到该 Pod。
3、启动pod关闭过程:①如果 Pod 配置了 preStop Hook ,将会执行。②通知pod里面的容器进程开始优雅停止。
4、所有容器进程终止,清理 Pod 资源。

kubelet的功能、作用是什么?(重点,经常会问)

答:kubelet部署在每个node节点上的,它主要有4个功能:
1、管理node。kubelet启动时会向api-server进行注册,然后会定时的向api-server汇报本节点信息状态、资源使用状态等,这样master知道了整个集群所有节点的资源情况。
2、管理pod。kubelet负责维护node节点上pod的生命周期,比如要创建、更新、删除一个pod都用到了kubelet。
3、容器健康检查。pod中可以定义启动探针、存活探针、就绪探针等,kubelet会定时执行pod中容器定义的探针

kube-api-server的端口是多少?各个pod是如何访问kube-api-server的?

kube-api-server的端口是8080和6443,前者是http的端口,后者是https的端口

以我本机使用kubeadm安装的k8s为例:
在命名空间的kube-system命名空间里,有一个名称为kube-api-master的pod,这个pod就是运行着kube-api-server进程,它绑定了master主机的ip地址和6443端口,但是在default命名空间下,存在一个叫kubernetes的服务,该服务对外暴露端口为443,目标端口6443,这个服务的ip地址是ClusterIP地址池里面的第一个地址,同时这个服务的yaml定义里面并没有指定标签选择器,也就是说这个kubernetes服务所对应的endpoint是手动创建的,该endpoint也是名称叫做kubernetes,该endpoint的yaml定义里面代理到master节点的6443端口,也就是kube-api-server的ip和端口。这样一来,其他pod访问kube-api-server的整个流程就是:pod创建后嵌入了环境变量,pod获取到了kubernetes这个服务的ip和443端口,请求到kubernetes这个服务其实就是转发到了master节点上的6443端口的kube-api-server这个pod里面。

k8s中命名空间的作用是什么?

Namespace主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离

一句话来讲:比如你弄两个namespace分别代表开发环境和测试环境,在不同namespace里面限制CPU使用量、内存使用量等等,然后将不同的namespace交给不同租户进行管理(限制不同租户的访问权限),那么就实现**多套环境的资源隔离**或者**多租户的资源隔离**。

这里是引用

etcd集群节点可以设置为偶数个吗,为什么要设置为奇数个呢?

不能,也不建议这么设置。
etcd采用了Raft一致性算法,集群一般包含2n+1个节点,所以进行Leader选举和数据复制时,节点数必须是奇数个。
奇数个节点与配对的偶数个节点(如3个节点和4个节点)相比,容错能力相同,但可以少一个节点;其次,偶数个节点的集群在选举过程中由于等额选票的存在,有较大概率触发下一轮选举,从而增加了不可用的风险。etcd官方推荐3、5、7个节点

你们生产环境etcd节点一般是几个节点?

我们使用的3节点的etcd集群,3节点etcd集群允许存在1台机器宕机,如果此时两台etcd节点宕机,那此时剩余的1台节点由于无法进行选举,所以整个etcd集群服务就不可用了,同理5个节点则可以最大容忍2个节点不可用,7节点可以容忍3个节点不可用。
目前etcd官方推荐etcd集群节点为3节点、5节点、7个节点,3节点可以支撑小规模的k8s集群,5到7节点可以支持中大型规模的k8s集群。

etcd节点是越多越好吗?

不是,etcd 集群的极限有两部分,一是单机的容量限制,内存和磁盘;二是网络开销,每次 Raft 操作需要所有节点参与,每一次写操作需要集群中大多数节点将日志落盘成功后,Leader 节点才能修改内部状态机,并将结果返回给客户端。因此节点越多性能越低。

而且节点越多并且出错的概率会直线上升,并且是呈现线性的性能下降,所以扩展很多 etcd 节点是没有意义的,其次,如果etcd集群超过7个达到十几个几十个,那么,对运维来说也是一个不小的压力了,并且集群的配置什么的也会更加的复杂,而不是简单易用了。因此,etcd集群的数量一般是 3、5、7, 3 个是最低标准,7个已经是最高了。

怎么使一个node脱离集群调度,比如要停机维护但又不能影响业务应用

要使一个节点脱离集群调度可以使用kubectl cordon <node-name> 命令使节点不可调度,该命令其实背后原理就是给节点打上污点,这样新的pod如果没有容忍将不会调度到该节点,但是已经存在于该节点的pod仍然可以继续在该节点上运行不受影响。

如果需要恢复节点重新调度,可以使用kubectl uncordon <node-name> 命令恢复节点可调度。

如果节点是要停机维护,则可以对节点上的pod 进行驱逐:使用kubectl drain <node-name>命令用于将节点上的pod驱逐出去,以便对节点进行维护。kubectl drain 命令背后原理其实还是首先将指定的节点标记为不可调,从而阻止新 pod 分配到节点上,然后删除旧pod。

综上所述,要停机维护:

1、kubectl cordon node01								#设置节点不可调度
2、kubectl drain node01 --ignore-daemonsets  --force	#驱逐pod
3、kubectl uncordon node01  							#恢复节点调度

三、Pod相关

pod是什么?

在kubernetes的世界中,k8s并不直接处理容器,而是直接管理pod。所以Pod是kubernetes集群进行管理的最小单元

程序要运行必须部署在容器中(一般1个容器一般被设计成只运行1个进程),而容器必须存在于Pod中。Pod可以认为是容器的封装,一个Pod中可以存在一个或者多个容器

k8s会为每个pod分配一个集群内部唯一的ip地址,所以每个pod都拥有自己的ip地址、主机名、进程等;

这里是引用

pod有什么特点?

在kubernetes的世界中,k8s并不直接处理容器,而是直接管理pod。所以Pod是kubernetes集群进行管理的最小单元

程序要运行必须部署在容器中(一般1个容器一般被设计成只运行1个进程),而容器必须存在于Pod中。Pod可以认为是容器的封装,一个Pod中可以存在一个或者多个容器

k8s会为每个pod分配一个集群内部唯一的ip地址,所以每个pod都拥有自己的ip地址、主机名、进程等;

pause容器作用是什么?(经常问)

每个Pod中都可以包含一个或者多个容器,这些容器可以分为两类:

  • 业务容器:用户程序所在的容器,数量可多可少

  • Pause容器,这是每个Pod都会有的一个根容器,它的作用有两个:

    • 可以以它为依据,评估整个Pod的健康状态
    • 可以在根容器上设置Ip地址,其它容器都此Ip(Pod IP),以实现Pod内部的网路通信

pod的重启策略有哪些?(经常问)

可以通过pod.spec.restartPolicy字段配置重启容器的策略,重启策略如下3种配置:

定义pod的重启策略(注意,不是定义容器的重启策略)

  • Always :容器失效时,自动重启该容器,这也是默认值。
  • OnFailure : 容器终止运行且退出码不为0时重启
  • Never : 不论状态为何,都不重启该容器

pod的镜像拉取策略有哪几种?(经常问)

配置镜像拉取策略

  • Always:总是从远程仓库拉取镜像(一直远程下载)
  • IfNotPresent:本地有则使用本地镜像,本地没有则从远程仓库拉取镜像(本地有就本地 本地没远程下载)
  • Never:只使用本地镜像,从不去远程仓库拉取,本地没有就报错 (一直使用本地)

pod的存活探针有哪几种?(必须记住3种探测方式,重点,经常问)

kubernetes可以通过存活探针检查容器是否还在运行,kubelet将定期执行探针,如果探测失败,将杀死容器,并根据restartPolicy策略来决定是否重启容器,kubernetes提供了3种探测容器的存活探针,如下:

httpGet:通过容器的ip、端口、路径发送http 请求,返回200-400范围内的状态码表示成功。
exec:在容器内执行shell命令,根据命令退出状态码是否为0进行判断,0表示健康,非0表示不健康。
TCPSocket:与容器的ip、端口建立TCP Socket链接,能建立则说明探测成功,不能建立则说明探测失败。

一言蔽之:

​ 有存活性探针和就绪性探针两类。

​ 我们定义pod的其中一个容器时,可以定义存活性探针为运行某个命令的方式,比如“执行一个查看文件的命令”,如果这个文件不存在相当于健康检查不通过,那么容器启动失败,pod会一直重启。当这个文件存在时,健康检查通过,容器启动成功,pod也就启动成功

​ 我们定义pod的其中一个容器时,可以定义存活性探针为“访问某个ip地址”,如果这个地址访问不通相当于健康检查不通过,那么容器启动失败,pod会一直重启。当这个地址可以访问通时,健康检查通过,容器启动成功,pod也就启动成功

​ 我们甚至可以定义容器启动后30s开始探测,间隔多少秒探测一次,探测时间要持续5s之类的参数

存活探针的属性参数有哪几个?

存活探针的附加属性参数有以下几个:

可以定义容器启动后30s开始探测,间隔多少秒探测一次,探测时间要持续5s之类的参数

pod的就绪探针有哪几种?(必须记住3种探测方式,重点,经常问)

我们知道,当一个pod启动后,就会开始接收到客户端的链接请求,假若此时pod中的容器的业务进程还没有初始化完毕,那么这些客户端链接请求就会失败,为了解决这个问题,kubernetes提供了就绪探针来解决这个问题的。
在pod中的容器定义一个就绪探针,就绪探针周期性检查容器,如果就绪探针检查失败了,说明该pod还未准备就绪,不能接受客户端链接,则该pod将从endpoint列表中移除。

httpGet:通过容器的ip、容器的端口以及路径来发送http get请求,返回200-400范围内的状态码表示请求成功。
exec:在容器内执行shell命令,它根据shell命令退出状态码是否为0进行判断,0表示健康,非0表示不健康。
TCPSocket:通过容器的ip、端口建立TCP Socket链接,能正常建立链接,则说明探针成功,不能正常建立链接,则探针失败。

就绪探针的属性参数有哪些

就绪探针的附加属性参数有以下几个:

可以定义容器启动后30s开始探测,间隔多少秒探测一次,探测时间要持续5s之类的参数

就绪探针与存活探针区别是什么?

两者作用不一样。
存活探针,是检测容器是否存活,如果检测失败,将检查失败的容器杀死,创建新的启动容器来保持pod正常工作;
就绪探针,是检测容器是否可以正常接收流量,当就绪探针检查失败,并不重启容器,而是将pod移出endpoint列表,就绪探针确保了service中的pod都是可用的,确保客户端只与正常的pod交互并且客户端永远不会知道系统存在问题。

pod的生命周期有哪几种?(记住,经常问)

pod生命周期有的5种状态(也称5种相位),如下:

Pending(挂起):该pod还有一个或多个容器的镜像没有创建(可能正在下载镜像);
Running(运行中):pod内所有的容器已经创建,且至少有一个容器处于运行状态、正在启动括正在重启状态;
Succeed(成功):Pod中的所有容器都已经成功终止,并且不会被重启(表示Pod已经完成了其指定的任务);
Failed(失败):Pod中的所有容器都已经终止,但至少有一个容器终止失败
Unknown(未知):某于某种原因api-server无法获取该pod的状态,可能由于网络通行问题导致;

pod状态一般有哪些?

pod的状态一般会有以下这些:

Pending(挂起):该pod还有一个或多个容器的镜像没有创建(可能正在下载镜像);
Running(运行中):pod内所有的容器已经创建,且至少有一个容器处于运行状态、正在启动括正在重启状态;
Succeed(成功):Pod中的所有容器都已经成功终止,并且不会被重启(表示Pod已经完成了其指定的任务);
Failed(失败):Pod中的所有容器都已经终止,但至少有一个容器终止失败
Unknown(未知):某于某种原因api-server无法获取该pod的状态,可能由于网络通行问题导致;

ErrImagePull(镜像拉取异常): 这个错误表示Kubernetes无法从指定的镜像仓库拉取镜像。可能的原因有很多,比如网络问题、镜像名称或标签错误、或者没有权限访问这个镜像仓库等。

ImagePullBackOff(镜像拉取异常): 这个错误表示Kubernetes尝试拉取镜像,但是失败了,然后它回滚了之前的操作。这通常是因为镜像仓库的问题,比如网络问题、镜像不存在、或者没有权限访问这个镜像仓库等。

pod一直处于pending状态一般有哪些情况,怎么排查?(重点,持续更新)

(这个问题被问到的概率非常大)
①一个pod一开始创建的时候,它本身就是会处于pending状态,这时可能是正在拉取镜像,正在创建容器的过程。

②如果等了一会发现pod还一直处于pending状态,那么我们可以使用kubectl describe命令查看一下pod的详细信息。

一般可能会有这么几种情况导致pod一直处于pending状态:
Scheduer调度器无法为pod分配一个合适的node节点:

  • pod有资源要求但node的资源都不足以满足;
  • node节点上有污点而pod没有定义容忍;
  • pod中定义了亲和性或反亲和性而没有节点满足这些亲和性或反亲和性;

pod的钩子函数有哪几种,作用是什么?

钩子函数能够在相应的时刻到来时运行用户指定的程序代码。

pod的钩子函数有PostStart和PreStop两种:

  • postStart(容器创建后做什么): 比如定义nginx容器启动后向首页写一些字符串
  • preStop(容器停止前做什么): 主要用于优雅关闭应用程序、或者完成一些清理工作等等(如在容器关闭前优雅地停止一个服务,如Nginx或MySQL服务)

不管是postStart构子还是preStop构子,都可以使用 exec、httpGet、tcpSocket等3种方法来定义。

如何优雅的终止pod或者说如何保证pod不丢失流量(重点,经常问)

在一些重点领域,比如涉及金钱交易的系统,保证pod终止时不丢失流量是很重要的,这就涉及到如何保证pod优雅的退出的问题。

1、在开发层面,程序务必在业务代码里处理 SIGTERM 信号。主要逻辑是不接受新的流量进入,继续处理剩余流量,保证所有连接全部断开程序才退出。
2、在k8s层面,Pod里面可以设置preStop构子,preStop构子的作用是在容器终止之前立即被调用,主要用于优雅关闭应用程序、或者完成一些清理工作等等(如在容器关闭前优雅地停止一个服务,如Nginx或MySQL服务)

pod的初始化容器是干什么的?

一言蔽之:

​ 初始化容器时我们可以自定义一些代码。比如在运行nginx之前先要能够连接上mysql和redis所在服务器,也就是初始化容器时一直ping这两个地址,ping通才能创建nginx容器,nignx容器创建成功对应的pod才算真正的启动起来了。

初始化容器是在pod的主容器启动之前要运行的容器,主要是做一些主容器的前置工作,初始化容器必须按照定义的顺序执行,当且仅当前一个成功之后,后面的一个才能运行。

pod的资源请求、限制如何定义?

如果不对某个容器的资源做限制,那么它就可能吃掉大量资源,导致其它容器无法运行。

  • limits:用于限制运行时容器的最大占用资源,当容器占用资源超过limits时会被终止,并进行重启。
  • requests :用于设置容器需要的最小资源,如果环境资源不够,容器将无法启动

可以通过上面两个选项设置资源的上下限。limits是容器的上限,requests 是下限(规定启动容器的下限)

apiVersion: v1
kind: Pod
metadata:
  name: pod-resources
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    resources: # 资源配额
      limits:  # 限制资源(上限)
        cpu: "2" # CPU限制,单位是core数
        memory: "10Gi" # 内存限制
      requests: # 请求资源(下限)
        cpu: "1"  # CPU限制,单位是core数
        memory: "10Mi"  # 内存限制

pod的定义中有个command和args参数,这两个参数不会和docker镜像的entrypoint冲突吗?

不会。
在pod中定义的command参数用于指定容器的启动命令列表,如果不指定,则默认使用Dockerfile打包时的启动命令;
args参数用于容器的启动命令需要的参数列表;

特别说明:
kubernetes中的command、args其实是实现覆盖dockerfile中的ENTRYPOINT的功能的。当:

1、如果command和args均没有写,那么使用Dockerfile的配置;
2、如果command写了但args没写,那么Dockerfile默认的配置会被忽略,执行pod容器指定的command;
3、如果command没写但args写了,那么Dockerfile中的ENTRYPOINT的会被执行,使用当前args的参数;
4、如果command和args都写了,那么Dockerfile会被忽略,执行pod当前定义的command和args。

四、Service相关

标签及标签选择器是什么,如何使用?

我们可以使用namespace来分组,但是不同的namespace下面的pod又无法直接通信,所以可以使用lable来实现分组,而且不同分组的pod还可以通信。

Label的特点:

  • 一个Label会以key/value键值对的形式附加到各种对象上,如Node、Pod、Service等等
  • 一个资源对象可以定义任意数量的Label ,同一个Label也可以被添加到任意数量的资源对象上去

一些常用的Label 示例如下:

  • 版本标签:“version”:“release”, “version”:“stable”…
  • 环境标签:“environment”:“dev”,“environment”:“test”,“environment”:“pro”
  • 架构标签:“tier”:“frontend”,“tier”:“backend”

标签定义完毕之后,还要考虑到标签的选择,这就要使用到Label Selector,即:

当前有两种Label Selector:

  • 基于等式的Label Selector

    name = slave: 选择所有包含Label中key="name"且value="slave"的对象

    env != production: 选择所有包括Label中的key="env"且value不等于"production"的对象

  • 基于集合的Label Selector

    name in (master, slave): 选择所有包含Label中的key="name"且value="master"或"slave"的对象

    name not in (frontend): 选择所有包含Label中的key="name"且value不等于"frontend"的对象

在deployment定义标签选择器,这样就通过标签选择器来选择哪些pod是受其控制的;service也是通过标签选择器来关联哪些pod最后其服务后端pod。

service是如何与pod关联的?

答案:通过标签选择器。在deployment定义标签选择器,service也是通过标签选择器来关联哪些pod最后其服务后端pod。

service的类型有哪几种

service的类型一般有4种,分别是:

  • ClusterIP:表示service仅供集群内部使用,默认值就是ClusterIP类型
  • NodePort:在之前的ClusterIP中,创建的Service的ip地址只有集群内部才可以访问,如果希望将Service暴露给集群外部使用可以使用NodePort,这会在每个节点上暴露一个端口,这样外部浏览器通过NodeIp:NodePort来访问service了
  • LoadBalancer:LoadBalancer和NodePort很相似,目的都是向外部暴露一个端口,区别在于LoadBalancer会在集群的外部再来做一个负载均衡设备

一般情况下,service都是ClusterIP类型的,通过ingress接入的外部流量。

一个应用pod是如何发现service的,或者说,pod里面的容器是如何连接service的?

答:有两种方式,一种是通过环境变量,另一种是通过service的dns域名方式。
1、环境变量:当pod被创建之后,k8s系统会自动为容器注入集群内有效的service名称和端口号等信息为环境变量的形式,这样容器应用直接通过取环境变量值就能访问service了,如,每个pod都会自动注入了api-server的svc:curl http://${KUBERNETES_SERVICE_HOST}:{KUBERNETES_SERVICE_PORT}
2、DNS方式:使用dns域名解析的前提是k8s集群内有DNS域名解析服务器,默认k8s中会有一个CoreDNS作为k8s集群的默认DNS服务器提供域名解析服务器;service的DNS域名表示格式为<servicename>.<namespace>.svc.<clusterdomain>,servicename是service的名称,namespace是service所处的命名空间,clusterdomain是k8s集群设置的域名后缀,一般默认为 cluster.local ,这样容器应用直接通过service域名就能访问service了,如wget http://nginx-svc.default.svc.cluster.local:80,另外,service的port端口如果定义了名称,那么port也可以通过DNS进行解析,格式为:_<portname>._<protocol>.<servicename>.<namespace>.svc.<clusterdomain>

如何创建一个service代理外部的服务,或者换句话来说,在k8s集群内的应用如何访问外部的服务,如数据库服务,缓存服务等?

答:可以通过创建一个没有标签选择器的service来代理集群外部的服务。
1、创建service时不指定selector标签选择器,但需要指定service的port端口、端口的name、端口协议等,这样创建出来的service因为没有指定标签选择器就不会自动创建endpoint;
2、手动创建一个与service同名的endpoint,endpoint中定义外部服务的ip和端口,endpoint的名称一定要与service的名称一样,端口协议也要一样,端口的name也要与service的端口的name一样,不然endpoint不能与service进行关联。
完成以上两步,k8s会自动将service和同名的endpoint进行关联,这样,k8s集群内的应用服务直接访问这个service就可以相当于访问外部的服务了。

service、endpoint、kube-proxy三种的关系是什么?

service:在kubernetes中,我们可以通过pod的ip来访问应用程序,但是pod会频繁重启,pod的ip地址不是固定的,所以我们可以借助ervice,service的ip和端口不会改变,这样外部的客户端的请求到达service后,service将请求转发到后端的多个pod实例上。

endpoint:endpoint是service里面维护的资源列表,endpoint资源对象保存着service关联的pod的ip和端口。从表面上看,当pod消失,service会在endpoint列表中剔除pod,当有新的pod加入,service就会将pod ip加入endpoint列表;

kube-proxy:外部的客户端的请求到达service后,service将请求转发到后端的多个pod实例上,实现服务的负载均衡。kube-proxy就是实现负载均衡功能的核心,因为它负责创建并维护路由规则。

kube-proxy有哪几种模式?

kube-proxy有3种模式,分别是user namespace 、iptables、ipvs。

其中user namespace已经被废弃了,目前用的最多的就是iptables和ipvs这两种模式。

你们生产环境kube-proxy使用哪种模式,为什么?

生产环境中使用ipvs。因为ipvs性能比iptables高。

五、deployment

deployment怎么扩容或缩容?

答:直接修改pod副本数即可,可以通过下面的方式来修改pod副本数:

  • 直接修改yaml文件的replicas字段数值,然后kubectl apply -f xxx.yaml来实现更新;
  • 使用kubectl edit deployment xxx 实现在线更新;
  • 也可以通过命令的方式更新(略)

deployment的更新升级策略有哪些?

Deployment镜像更新支持两种更新策略:

  • Recreate:重建更新,在创建出新的Pod之前会先杀掉所有已存在的Pod;
  • RollingUpdate:滚动更新,就是杀死一部分,就启动一部分,在更新过程中,存在两个版本Pod

deployment的滚动更新策略有两个特别主要的参数,解释一下它们是什么意思?

rollingUpdate 策略,主要有两个参数:

  • maxUnavailable:最大不可用数,用来指定在升级过程中不可用Pod的最大数量,默认为25%。
  • maxSurge:最大激增数,用来指定在升级过程中可以超过期望的Pod的最大数量,默认为25%。

deployment更新的命令有哪些?

1、直接修改yaml文件的镜像版本,然后kubectl apply -f xxx.yaml来实现更新;
2、使用kubectl edit deployment xxx 实现在线更新;
3、也可以通过命令的方式更新(略)

简述一下deployment的更新过程 (经常问)

deployment是通过控制replicaset来实现,由replicaset真正创建pod副本,每更新一次deployment,都会创建新的replicaset。
1、用户通过`kubectl edit deployment 命令直接修改镜像后,api-server对当前用户操作进行鉴权,鉴权成功后接收操作请求
2、Deployment控制器检测到配置变化后,开始创建一个新的replicaset,然后默认采用滚动更新(RollingUpdate)策略。

1、deployment创建一个新的replaceset,先新增1个新版本pod,此时pod总数为4个(旧=3,新=1,总=4);
2、减少一个旧版本的pod,此时pod总数为3个(旧=2,新=1,总=3)
3、再新增一个新版本的pod,此时pod总数为4个(旧=2,新=2,总=4)
4、减少一个旧版本的pod,此时pod总数为3个(旧=1,新=2,总=3)
5、再新增一个新版本的pod,此时pod总数为4个(旧=1,新=3,总=4)
6、减少一个旧版本的pod,此时pod总数为3个,更新完成(旧=0,新=3,总=3)

一次性一定是减少一个旧的pod新增一个新的pod吗?不一定,取决于(maxUnavailable:最大不可用数)和(maxSurge:最大激增数)的配置

3、在每个新pod被创建之后,kubelet会根据定义的livenessProbe和readinessProbe来检查新pod是否已经启动并准备好接收流量。当新pod通过存活探针并标记为"就绪"时,pod的ip和端口将会被endpoint检查并加入端点列表,此时Service会逐渐将流量从旧pod转移到新pod。
4、一旦所有旧pod都被新pod替换并且新版本的所有pod都已准备就绪,则升级过程结束。

deployment的回滚使用什么命令

kubectl rollout: 版本升级相关功能,支持下面的选项:

  • history 显示 升级历史记录
  • undo 回滚到上一级版本(可以使用–to-revision回滚到指定版本)
# 查看升级历史记录
[root@k8s-master01 ~]# kubectl rollout history deploy pc-deployment -n dev
deployment.apps/pc-deployment
REVISION  CHANGE-CAUSE
1         kubectl create --filename=pc-deployment.yaml --record=true
2         kubectl create --filename=pc-deployment.yaml --record=true
3         kubectl create --filename=pc-deployment.yaml --record=true
# 可以发现有三次版本记录,说明完成过两次升级


# 版本回滚,使用--to-revision=1回滚到了1版本, 如果省略这个选项,就是回退到上个版本,就是2版本
[root@k8s-master01 ~]# kubectl rollout undo deployment pc-deployment --to-revision=1 -n dev
deployment.apps/pc-deployment rolled back

六、存储卷

讲一下都有哪些存储卷,作用分别是什么?

1》EmptyDir

EmptyDir是最基础的Volume类型,一个EmptyDir就是Host上的一个空目录。

EmptyDir是在Pod被分配到Node时创建的,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为kubernetes会自动分配一个目录,当Pod销毁时EmptyDir中的数据也会被永久删除。 EmptyDir用途如下:

  • 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留
  • 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)

接下来,通过一个容器之间文件共享的案例来使用一下EmptyDir。

在一个Pod中准备两个容器nginx和busybox,然后声明一个Volume分别挂在到两个容器的目录中,然后nginx容器负责向Volume中写日志,busybox中通过命令将日志内容读到控制台。

这里是引用

2》HostPath

上节课提到,EmptyDir中数据不会被持久化,它会随着Pod的结束而销毁,如果想简单的将数据持久化到主机中,可以选择HostPath。

HostPath就是将Node主机中一个实际目录挂在到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据依据可以存在于Node主机上。

这里是引用

3》NFS

HostPath可以解决数据持久化的问题,但是一旦Node节点故障了,Pod如果转移到了别的节点,又会出现问题了,此时需要准备单独的网络存储系统,比较常用的用NFS、CIFS。

NFS是一个网络文件存储系统,可以搭建一台NFS服务器,然后将Pod中的存储直接连接到NFS系统上,这样的话,无论Pod在节点上怎么转移,只要Node跟NFS的对接没问题,数据就可以成功访问。

这里是引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BlackTurn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值