k8s中Pod的理解基本概念 Pod是Kubernetes集群中能够被创建和管理的最小部署单元,它是虚拟存在的。Pod是一组容器的集合,并且部署在同一个Pod里面的容器是亲密性很强的一组容器,Pod里面的容器,共享网络和存储空间,Pod是短暂的。 k8s中的Pod有下面两种使用方式 1、一个Pod中运行一个容器,这是最常见的用法。一个Pod封装一个容器,k8s直接对Pod管理即可; 2、一个Pod中同时运行多个容器,通常是紧耦合的我们才会放到一起。同一个Pod中的多个容器可以使用localhost通信,他们共享网络和存储卷。不过这种用法不常见,只有在特定的场景中才会使用。k8s为什么使用Pod作为最小的管理单元 k8s中为什么不直接操作容器而是使用Pod作为最小的部署单元呢? 为了管理容器,k8s需要更多的信息,比如重启策略,它定义了容器终止后要采取的策略;或者是一个可用性探针,从应用程序的角度去探测是否一个进程还存活着。基于这些原因,k8s架构师决定使用一个新的实体,也就是Pod,而不是重载容器的信息添加更多属性,用来在逻辑上包装一个或者多个容器的管理所需要的信息。如何使用Pod1、自主式Pod 我们可以简单的快速的创建一个Pod类似下面:catpod。yamlapiVersion:v1kind:Podmetadata:name:nginxspec:containers:name:nginximage:nginx:1。14。2ports:containerPort:80 创建kubectlapplyfpod。yamlnstudyk8skubectlgetpodnstudyk8sNAMEREADYSTATUSRESTARTSAGEnginx11Running02m 自主创建的Pod,因为没有加入控制器来管理,这样创建的Pod,被删除,或者因为意外退出了,不会重启自愈,直接就会被删除了。所以,业务中,我们在创建Pod的时候都会加入控制器。2、控制器管理的Pod 因为我们的业务长场景的需要,我们需要Pod有滚动升级,副本管理,集群级别的自愈能力,这时候我们就不能单独的创建Pod,我们需要通过相应的控制器来创建Pod,来实现Pod滚动升级,自愈等的能力。 对于Pod使用,我们最常使用的就是通过Deployment来管理。 Deployment提供了一种对Pod和ReplicaSet的管理方式。Deployment可以用来创建一个新的服务,更新一个新的服务,也可以用来滚动升级一个服务。借助于ReplicaSet也可以实现Pod的副本管理功能。 滚动升级一个服务,实际是创建一个新的RS,然后逐渐将新RS中副本数增加到理想状态,将旧RS中的副本数减小到0的复合操作;这样一个复合操作用一个RS是不太好描述的,所以用一个更通用的Deployment来描述。videployment。yamlapiVersion:appsv1kind:Deploymentmetadata:name:nginxdeploymentlabels:app:nginxspec:replicas:3selector:matchLabels:app:nginxtemplate:metadata:labels:app:nginxspec:containers:name:nginximage:nginx:1。14。2ports:containerPort:80 运行kubectlapplyfdeployment。yamlnstudyk8skubectlgetpodsnstudyk8sNAMEREADYSTATUSRESTARTSAGEnginx11Running067mnginxdeployment66b6c48dd524sbd11Running059snginxdeployment66b6c48dd5wxkln11Running059snginxdeployment66b6c48dd5xgzgh11Running059s 因为上面定义了replicas:3也就是副本数为3,当我们删除一个Pod的时候,马上就会有一个新的Pod被创建。 同样经常使用的到的控制器还有DaemonSet和StatefulSet DaemonSet:DaemonSet确保全部(或者某些)节点上运行一个Pod的副本。当有节点加入集群时,也会为他们新增一个Pod。当有节点从集群移除时,这些Pod也会被回收。删除DaemonSet将会删除它创建的所有Pod。 StatefulSet:用来管理有状态应用的工作负载,和Deployment类似,StatefulSet管理基于相同容器规约的一组Pod。但和Deployment不同的是,StatefulSet为它们的每个Pod维护了一个有粘性的ID。这些Pod是基于相同的规约来创建的,但是不能相互替换:无论怎么调度,每个Pod都有一个永久不变的ID。静态Pod 静态Pod是由kubelet进行管理的仅存在与特定node上的Pod,他们不能通过apiserver进行管理,无法与rc,deployment,ds进行关联,并且kubelet无法对他们进行健康检查。 静态Pod始终绑定在某一个kubelet,并且始终运行在同一个节点上。kubelet会自动为每一个静态Pod在Kubernetes的apiserver上创建一个镜像Pod(MirrorPod),因此我们可以在apiserver中查询到该Pod,但是不能通过apiserver进行控制(例如不能删除)。 为什么需要静态Pod? 主要是用来对集群中的组件进行容器化操作,例如etcdkubeapiserverkubecontrollermanagerkubescheduler这些都是静态Pod资源。 因为这些Pod不受apiserver的控制,就不会不小心被删掉的情况,同时kubeapiserver也不能自己去控制自己。静态Pod的存在将集群中的容器化操作提供了可能。 静态Pod的创建有两种方式,配置文件和HTTP两种方式,具体参见。静态Pod的创建Pod的生命周期 Pod在运行的过程中会被定义为各种状态,了解一些状态能帮助我们了解Pod的调度策略。当Pod被创建之后,就会进入健康检查状态,当Kubernetes确定当前Pod已经能够接受外部的请求时,才会将流量打到新的Pod上并继续对外提供服务,在这期间如果发生了错误就可能会触发重启机制。 不过Pod本身不具有自愈能力,如果Pod因为Node故障,或者是调度器本身故障,这个Pod就会被删除。所以Pod中一般使用控制器来管理Pod,来实现Pod的自愈能力和滚动更新的能力。 Pod的重启策略包括Always只要失败,就会重启;OnFile当容器终止运行,且退出码不是0,就会重启;Never从来不会重启。 重启的时间,是以2n来算。比如(10s、20s、40s、。。。),其最长延迟为5分钟。一旦某容器执行了10分钟并且没有出现问题,kubelet对该容器的重启回退计时器执行重置操作。 管理Pod的重启策略是靠控制器来完成的。 Pod的几种状态 使用的过程中,会经常遇到下面几种Pod的状态。 Pending:Pod创建已经提交给k8s,但有一个或者多个容器尚未创建亦未运行,此阶段包括等待Pod被调度的时间和通过网络下载镜像的时间。这个状态可能就是在下载镜像; Running:Pod已经绑定到一个节点上了,并且已经创建了所有容器。至少有一个容器仍在运行,或者正处于启动或重启状态; Secceeded:Pod中的所有容器都已经成功终止,并且不会再重启; Failed:Pod中所有的容器均已经终止,并且至少有一个容器已经在故障中终止; Unkown:因为某些原因无法取得Pod的状态。这种情况通常是因为与Pod所在主机通信失败。 当pod一直处于Pending状态,可通过kubectldescribepodsnnamespace来获取出错的信息Pod如何直接暴露服务 Pod一般不直接对外暴露服务,一个Pod只是一个运行服务的实例,随时可能在节点上停止,然后再新的节点上用一个新的IP启动一个新的Pod,因此不能使用确定的IP和端口号提供服务。这对于业务来说,就不能根据Pod的IP作为业务调度。kubernetes就引入了Service的概念,它为Pod提供一个入口,主要通过Labels标签来选择后端Pod,这时候不论后端Pod的IP地址如何变更,只要Pod的Labels标签没变,那么业务通过service调度就不会存在问题。 不过使用hostNetwork和hostPort,可以直接暴露node的ip地址。hostNetwork 这是一种直接定义Pod网络的方式,使用hostNetwork配置网络,Pod中的所有容器就直接暴露在宿主机的网络环境中,这时候,Pod的PodIP就是其所在Node的IP。从原理上来说,当设定Pod的网络为Host时,是设定了Pod中podinfrastructure(或pause)容器的网络为Host,Pod内部其他容器的网络指向该容器。catEOF。podhost。yamlapiVersion:appsv1kind:Deploymentmetadata:name:nginxdeploymentlabels:app:nginxspec:replicas:1selector:matchLabels:app:nginxtemplate:metadata:labels:app:nginxspec:containers:name:nginximage:nginx:1。14。2ports:containerPort:80hostNetwork:trueEOF 运行kubectlapplyfpodhost。yamlnstudyk8skubectlgetpodsnstudyk8sowideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESnginxdeployment6d47cff9fd5bzjq11Running06m25s192。168。56。111kubeserver8。zsnonenonecurlhttp:192。168。56。111!DOCTYPEhtmlhtmlheadtitleWelcometonginx!titlestylebody{width:35margin:0fontfamily:Tahoma,Verdana,Arial,}styleheadbodyh1Welcometonginx!h1pIfyouseethispage,thenginxwebserverissuccessfullyinstalledandworking。Furtherconfigurationisrequired。pForonlinedocumentationandsupportpleaserefertonginx。org。 Commercialsupportisavailableatnginx。com。pemThankyouforusingnginx。embodyhtml 一般情况下除非知道需要某个特定应用占用特定宿主机上的特定端口时才使用hostNetwork:true的方式。hostPort 这是一种直接定义Pod网络的方式。 hostPort是直接将容器的端口与所调度的节点上的端口路由,这样用户就可以通过宿主机的IP加上端口来访问Pod。catEOF。podhostPort。yamlapiVersion:appsv1kind:Deploymentmetadata:name:nginxdeploymentlabels:app:nginxspec:replicas:1selector:matchLabels:app:nginxtemplate:metadata:labels:app:nginxspec:containers:name:nginximage:nginx:1。14。2ports:containerPort:80hostPort:8000EOF 运行kubectlapplyfpodhostPort。yamlnstudyk8skubectlgetpodsnstudyk8sowideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESnginxdeployment6d47cff9fd5bzjq11Running03m25s192。168。56。111kubeserver8。zsnonenonecurlhttp:192。168。56。111:8000!DOCTYPEhtmlhtmlheadtitleWelcometonginx!titlestylebody{width:35margin:0fontfamily:Tahoma,Verdana,Arial,}styleheadbodyh1Welcometonginx!h1pIfyouseethispage,thenginxwebserverissuccessfullyinstalledandworking。Furtherconfigurationisrequired。pForonlinedocumentationandsupportpleaserefertonginx。org。 Commercialsupportisavailableatnginx。com。pemThankyouforusingnginx。embodyhtml 这种网络方式可以用来做nginx〔Ingresscontroller〕。外部流量都需要通过kubenretesnode节点的80和443端口。hostNetwork和hostPort的对比 相同点 hostNetwork和hostPort的本质都是暴露Pod所在的节点IP给终端用户。因为Pod的生命周期并不固定,随时都有被重构的可能。可以使用DaemonSet或者非亲缘性策略,来保证每个node节点只有一个Pod被部署。 不同点 使用hostNetwork,pod实际上用的是pod宿主机的网络地址空间; 使用hostPort,podIP并非宿主机IP,而是cni分配的podIP,跟其他普通的pod使用一样的ip分配方式,端口并非宿主机网络监听端口,只是使用了DNAT机制将hostPort指定的端口映射到了容器的端口之上。Label Label是kubernetes系统中的一个重要概念。它的作用就是在资源上添加标识,用来对它们进行区分和选择。 Label可以添加到各种资源对象上,如Node、Pod、Service、RC等。Label通常在资源对象定义时确定,也可以在对象创建后动态添加或删除。当我们给一个对象打了标签之后,随后就可以通过LabelSelector(标签选择器)查询和筛选拥有某些Label的资源对象。通过使用Label就能实现多维度的资源分组管理功能。 LabelSelector在Kubernetes中的重要使用场景如下: 1、Kubecontroller进程通过资源对象RC上定义的LabelSelector来筛选要监控的Pod副本的数量,从而实现Pod副本的数量始终符合预期设定的全自动控制流程; 2、Kubeproxy进程通过Service的LabelSelector来选择对应的Pod,自动建立起每个Service到对应Pod的请求转发路由表,从而实现Service的智能负载均衡机制; 3、通过对某些Node定义特定的Label,并且在Pod定义文件中使用NodeSelector这种标签调度策略,kubescheduler进程可以实现Pod定向调度的特性。 同时借助于Label,k8s中可以实现亲和(affinity)与反亲和(antiaffinity)调度。亲和性调度什么是亲和(affinity)与反亲和(antiaffinity)调度 Kubernetes支持节点和Pod两个层级的亲和与反亲和。通过配置亲和与反亲和规则,可以允许您指定硬性限制或者偏好,例如将前台Pod和后台Pod部署在一起、某类应用部署到某些特定的节点、不同应用部署到不同的节点等等。 在学习亲和性调度之前,首先来看下如何进行打标签的操作 查看节点及其标签kubectlgetnodesshowlabels 给节点添加标签kubectllabelnodesyournodenamenodeNamenode9 删除节点的标签kubectllabelnodesyournodenamenodeName 查看某个标签的的分布情况kubectlgetnodeLnodeNameNAMESTATUSROLESAGEVERSIONNODENAMEkubeserver7。zsReadynone485dv1。19。9node7kubeserver8。zsReadynone485dv1。19。9node8kubeserver9。zsReadymaster485dv1。19。9node9Node亲和性调度策略catEOF。podaffinity。yamlapiVersion:appsv1kind:Deploymentmetadata:name:nginxdeploymentlabels:app:nginxspec:replicas:5selector:matchLabels:app:nginxtemplate:metadata:labels:app:nginxspec:containers:name:nginximage:nginx:1。14。2ports:containerPort:80affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:matchExpressions:key:nodeNameoperator:Invalues:node7node8EOF 重点看下下面的affinity:表示亲和nodeAffinity:表示节点亲和requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:matchExpressions:key:nodeNameoperator:Invalues:node7node8 分析下具体的规则 requiredDuringSchedulingIgnoredDuringExecution非常长,不过可以将这个分作两段来看: 前半段requiredDuringScheduling表示下面定义的规则必须强制满足(require)才会调度Pod到节点上; 后半段IgnoredDuringExecution表示已经在节点上运行的Pod不需要满足下面定义的规则,即去除节点上的某个标签,那些需要节点包含该标签的Pod不会被重新调度。 operator有下面几种取值:In:标签的值在某个列表中;NotIn:标签的值不在某个列表中;Exists:某个标签存在;DoesNotExist:某个标签不存在;Gt:标签的值大于某个值(字符串比较);Lt:标签的值小于某个值(字符串比较)。 需要说明的是并没有nodeAntiAffinity(节点反亲和),通过NotIn和DoesNotExist即可实现反亲和性地调度。 requiredDuringSchedulingIgnoredDuringExecution是一种强制选择的规则。 preferredDuringSchedulingIgnoredDuringExecution是优先选择规则,表示根据规则优先选择那些节点。 使用preferredDuringSchedulingIgnoredDuringExecution规则的时候,我们可以给label添加权重,这样Pod就能按照设计的规则调度到不同的节点中了。catEOF。podaffinityweight。yamlapiVersion:appsv1kind:Deploymentmetadata:name:nginxdeploymentlabels:app:nginxspec:replicas:5selector:matchLabels:app:nginxtemplate:metadata:labels:app:nginxspec:containers:name:nginximage:nginx:1。14。2ports:containerPort:80affinity:nodeAffinity:preferredDuringSchedulingIgnoredDuringExecution:weight:80preference:matchExpressions:key:nodeNameoperator:Invalues:node7node9weight:20preference:matchExpressions:key:nodeNameoperator:Invalues:node8EOF 上面的栗子可以看到,可以给label添加weight权重,在preferredDuringSchedulingIgnoredDuringExecution的规则下,就能按照我们设计的权重,部署到label对应的节点中。Pod亲和性调度 除了支持Node的亲和性调度,k8s中还支持Pod和Pod之间的亲和。 栗如:将应用的前端和后端部署在同一个节点中,从而减少访问延迟。 Pod亲和同样有requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution两种规则。 模拟后端的Pod部署catEOF。podaffinitybackend。yamlapiVersion:appsv1kind:Deploymentmetadata:name:backendlabels:app:backendspec:replicas:1selector:matchLabels:app:backendtemplate:metadata:labels:app:backendspec:containers:name:nginximage:nginx:1。14。2ports:containerPort:80EOF 模拟前端的Pod部署,使得前端对应的业务使用Pod亲和性调度和后端Pod部署到一起catEOF。podaffinityfrontend。yamlapiVersion:appsv1kind:Deploymentmetadata:name:frontendlabels:app:frontendspec:replicas:5selector:matchLabels:app:frontendtemplate:metadata:labels:app:frontendspec:containers:name:nginximage:nginx:1。14。2ports:containerPort:80affinity:podAffinity:requiredDuringSchedulingIgnoredDuringExecution:topologyKey:kubernetes。iohostnamelabelSelector:matchExpressions:key:appoperator:Invalues:backendEOF 这里把backend的Pod数量设置成1,然后frontend的Pod数量设置成5。这样来演示frontend向backend的亲和调度。kubectlgetpodsnstudyk8sowideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESbackend5f489d5d4fxcv4d11Running022s10。233。67。179kubeserver8。zsnonenonefrontend64846f7fbf6nsmd11Running033s10。233。67。181kubeserver8。zsnonenonefrontend64846f7fbf7pfq711Running033s10。233。67。182kubeserver8。zsnonenonefrontend64846f7fbfdg7wx11Running033s10。233。67。178kubeserver8。zsnonenonefrontend64846f7fbfq7jd511Running033s10。233。67。177kubeserver8。zsnonenonefrontend64846f7fbfv4hf911Running033s10。233。67。180kubeserver8。zsnonenone 这里有两个点需要注意下 1、topologyKey表示的指定的范围,指定的也是一个label,通过指定这个label来确定安装的范围; 2、matchExpressions指定亲和的Pod,例如上面的栗子就是appfrontend。 不过这里有个先后顺序,首先匹配topologyKey,然后匹配下面的matchExpressions规则。 Pod的反亲和性调度 有时候我们希望Pod部署到一起,那么我们就会也有希望Pod不部署在一起的场景,这时候就需要用到反亲和性调度。catEOF。podaffinityfrontend。yamlapiVersion:appsv1kind:Deploymentmetadata:name:frontendlabels:app:frontendspec:replicas:5selector:matchLabels:app:frontendtemplate:metadata:labels:app:frontendspec:containers:name:nginximage:nginx:1。14。2ports:containerPort:80affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:topologyKey:kubernetes。iohostnamelabelSelector:matchExpressions:key:appoperator:Invalues:backendEOF 使用podAntiAffinity即可定义反亲和性调度的策略kubectlgetpodsnstudyk8sowideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESbackend5f489d5d4fxcv4d11Running0108m10。233。67。179kubeserver8。zsnonenonefrontend567ddb4c456mf8711Running023s10。233。111。125kubeserver7。zsnonenonefrontend567ddb4c45fbj6j11Running023s10。233。111。124kubeserver7。zsnonenonefrontend567ddb4c45j5qpb11Running021s10。233。72。25kubeserver9。zsnonenonefrontend567ddb4c45qsc8m11Running021s10。233。111。126kubeserver7。zsnonenonefrontend567ddb4c45sfwjn11Running023s10。233。72。24kubeserver9。zsnonenoneNodeSelector定向调度 kubernetes中kubescheduler负责实现Pod的调度,内部系统通过一系列算法最终计算出最佳的目标节点。如果需要将Pod调度到指定Node上,则可以通过Node的标签(Label)和Pod的nodeSelector属性相匹配来达到目的。 栗如:DaemonSet中使用到了NodeSelector定向调度。 DaemonSet(守护进程),会在每一个节点中运行一个Pod,同时也能保证只有一个Pod运行。 如果有新节点加入集群,Daemonset会自动的在该节点上运行我们需要部署的Pod副本,如果有节点退出集群,Daemonset也会移除掉部署在旧节点的Pod副本。 DaemonSet适合一些系统层面的应用,例如日志收集,资源监控等,等这类需要每个节点都运行,且不需要太多的实例。catEOF。poddaemonset。yamlapiVersion:appsv1kind:Deploymentmetadata:name:nginxdaemonsetlabels:app:nginxdaemonsetspec:selector:matchLabels:app:nginxdaemonsettemplate:metadata:labels:app:nginxdaemonsetspec:nodeSelector:节点选择,当节点拥有nodeNamenode7时才在节点上创建PodnodeName:node7containers:name:nginximage:nginx:1。14。2ports:containerPort:80EOF 可以看到DaemonSet也是通过label来选择部署的目标节点nodeSelector:节点选择,当节点拥有nodeNamenode7时才在节点上创建PodnodeName:node7 如果不添加目标节点,那么就是在所有节点中进行部署了。资源限制 每个Pod都可以对其能使用的服务器上的计算资源设置限额,当前可以设置限额的计算资源有CPU和Memory两种,其中CPU的资源单位为CPU(Core)的数量,是一个绝对值。 对于容器来说一个CPU的配额已经是相当大的资源配额了,所以在Kubernetes里,通常以千分之一的CPU配额为最小单位,用m来表示。通常一个容器的CPU配额被定义为100300m,即占用0。10。3个CPU。与CPU配额类似,Memory配额也是一个绝对值,它的单位是内存字节数。 对计算资源进行配额限定需要设定以下两个参数:Requests:该资源的最小申请量,系统必须满足要求。Limits:该资源最大允许使用的量,不能超过这个使用限制,当容器试图使用超过这个量的资源时,可能会被KubernetesKill并重启。 通常我们应该把Requests设置为一个比较小的数值,满足容器平时的工作负载情况下的资源需求,而把Limits设置为峰值负载情况下资源占用的最大量。下面是一个资源配额的简单定义:spec:containers:name:dbimage:mysqlresources:requests:memory:64Micpu:250mlimits:memory:128Micpu:500m 最小0。25个CPU及64MB内存,最大0。5个CPU及128MB内存。 当然CPU的指定也可以不使用m。cpu:2,就表示2个cpu。 添加资源限制的目的 在业务流量请求较低的时候,释放多余的资源。当有一些突发性的活动,就能根据资源占用情况,申请合理的资源。Pod的持久化存储 volume是kubernetesPod中多个容器访问的共享目录。volume被定义在pod上,被这个pod的多个容器挂载到相同或不同的路径下。volume的生命周期与pod的生命周期相同,pod内的容器停止和重启时一般不会影响volume中的数据。所以一般volume被用于持久化pod产生的数据。Kubernetes提供了众多的volume类型,包括emptyDir、hostPath、nfs、glusterfs、cephfs、cephrbd等。1、emptyDir emptyDir类型的volume在pod分配到node上时被创建,kubernetes会在node上自动分配一个目录,因此无需指定宿主机node上对应的目录文件。这个目录的初始内容为空,当Pod从node上移除时,emptyDir中的数据会被永久删除。emptyDirVolume主要用于某些应用程序无需永久保存的临时目录,多个容器的共享目录等。 容器的crashing事件并不会导致emptyDir中的数据被删除。 配置示例catEOF。podemptyDir。yamlapiVersion:v1kind:Podmetadata:name:testpdspec:containers:image:liz2019testwebservername:testcontainervolumeMounts:mountPath:cachename:cachevolumevolumes:name:cachevolumeemptyDir:{}EOF2、hostPath hostPathVolume为pod挂载宿主机上的目录或文件,使得容器可以使用宿主机的高速文件系统进行存储。缺点是,在k8s中,pod都是动态在各node节点上调度。当一个pod在当前node节点上启动并通过hostPath存储了文件到本地以后,下次调度到另一个节点上启动时,就无法使用在之前节点上存储的文件。 配置示例catEOF。podhostPath。yamlapiVersion:v1kind:Podmetadata:name:testpdspec:containers:image:liz2019testwebservername:testcontainervolumeMounts:mountPath:cachename:testvolumevolumes:name:testvolumehostPath:directorylocationonhostpath:datatestEOF3、Pod持久化存储 1、pod直接挂载nfsserver 我们能将NFS(网络文件系统)挂载到Pod中,不像emptyDir那样会在删除Pod的同时也会被删除,nfs卷的内容在删除Pod时会被保存,卷只是被卸载。这意味着nfs卷可以被预先填充数据,并且这些数据可以在Pod之间共享,NFS卷可以由多个pod同时挂载。catEOF。podnfs。yamlapiVersion:v1kind:Podmetadata:name:testpdspec:containers:image:liz2019testwebservername:testcontainervolumeMounts:mountPath:cachename:testnfsvolumes:name:testnfsnfs:path:datatestserver:192。268。56。111EOF 2、pv和pvc 具体什么是pv和pvc,可参见pv和pvc 创建pvcatEOF。pvdemp。yamlapiVersion:v1kind:PersistentVolumemetadata:name:pv001labels:name:pv001spec:nfs:path:datavolumesv1server:nfsaccessModes:〔ReadWriteMany,ReadWriteOnce〕capacity:storage:2GiapiVersion:v1kind:PersistentVolumemetadata:name:pv002labels:name:pv002spec:nfs:path:datavolumesv2server:nfsaccessModes:〔ReadWriteOnce〕capacity:storage:1GiEOF 创建pvccatEOF。pvcdemp。yamlapiVersion:v1kind:PersistentVolumeClaimmetadata:name:testpvcnamespace:defaultspec:accessModes:〔ReadWriteMany〕resources:requests:storage:1GiEOF 业务绑定pvccatEOF。podpvccache。yamlapiVersion:v1kind:Podmetadata:name:testpdspec:containers:image:liz2019testwebservername:testcontainervolumeMounts:mountPath:cachename:testpvccachevolumes:name:testpvccachepersistentVolumeClaim:claimName:testpvcEOFPod的探针 k8s中当Pod的运行出现异常的时候,需要能够检测到Pod的这种状态,将流量路由到其他正常的Pod中,同时去恢复损坏的组件。 默认情况下,Kubernetes会观察Pod生命周期,并在容器从挂起(pending)状态转移到成功(succeeded)状态时,将流量路由到Pod。Kubelet会监控崩溃的应用程序,并重新启动Pod进行恢复。 但是Pod的健康并不代表,Pod中所有的容器都已经专准备好了,例如,这时候的应用可能正在启动,处于连接数据库的状态中,处于编译的状态中,这时候打进来的流量,得到的返回肯定就是异常的了。 k8s中引入了活跃(Liveness)、就绪(Readiness)和启动(Startup)探针来解决上面的问题。 存活探针:主要来检测容器中的应用程序是否正常运行,如果检测失败,就会使用Pod中设置的restartPolicy策略来判断,Pod是否要进行重启,例如,存活探针可以探测到应用死锁。 就绪探针:可以判断容器已经启动就绪能够接收流量请求了,当一个Pod中所有的容器都就绪的时候,才认为该Pod已经就绪了。就绪探针就能够用来避免上文中提到的,应用程序未完全就绪,然后就有流量打进来的情况发生。 启动探针:kubelet使用启动探针来了解应用容器何时启动。启动探针主要是用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。 如果提供了启动探针,则所有其他探针都会被禁用,直到此探针成功为止。 如果启动探测失败,kubelet将杀死容器,而容器依其重启策略进行重启。 在配置探针之前先来看下几个主要的配置 initialDelaySeconds:容器启动后要等待多少秒后才启动存活和就绪探针,默认是0秒,最小值是0; periodSeconds:执行探测的时间间隔(单位是秒)。默认是10秒。最小值是1; timeoutSeconds:探测的超时后等待多少秒。默认值是1秒。最小值是1; successThreshold:探针在失败后,被视为成功的最小连续成功数。默认值是1。存活和启动探测的这个值必须是1。最小值是1; failureThreshold:将探针标记为失败之前的重试次数。对于liveness探针,这将导致Pod重新启动。对于readiness探针,将标记Pod为未就绪(unready)。 目前ReadinessProbe和LivenessProbe都支持下面三种探测方法 ExecAction:在容器中执行指定的命令,如果能成功执行,则探测成功; HTTPGetAction:通过容器的IP地址、端口号及路径调用HTTPGet方法,如果响应的状态码200400,则认为容器探测成功; TCPSocketAction:通过容器IP地址和端口号执行TCP检查,如果能建立TCP连接,则探测成功; gRPC活跃探针:在Kubernetesv1。24〔beta〕中引入了,gRPC活跃探针,如果应用实现了gRPC健康检查协议,kubelet可以配置为使用该协议来执行应用活跃性检查。不过使用的时候需要先开启。 这里来看下使用HTTPGetAction的方式,来看下存活探针和就绪探针的配置,其他的可参考配置存活、就绪和启动探针catEOF。podtestgoliveness。yamlapiVersion:appsv1kind:Deploymentmetadata:labels:app:testgolivenessname:testgolivenessnamespace:studyk8sspec:replicas:5selector:matchLabels:app:gowebstrategy:{}template:metadata:creationTimestamp:nulllabels:app:gowebspec:containers:image:liz2019testgolivenessname:testgolivenesslivenessProbe:活跃探针httpGet:path:healthzport:8001initialDelaySeconds:15periodSeconds:10timeoutSeconds:5readinessProbe:存活探针httpGet:path:healthzport:8001initialDelaySeconds:5容器启动后多少秒启动探针periodSeconds:10执行探针检测的时间建个间隔timeoutSeconds:5探测的超时后等待多少秒status:{}EOF 可以看到上面同时设置了存活探针和就绪探针; 对于镜像中的代码设置了超过十秒即返回错误,这样上面的示例就能模拟探针检测失败,然后重启的效果了funchealthz(whttp。ResponseWriter,rhttp。Request){duration:time。Now()。Sub(started)w。Header()。Set(ContentType,charsetutf8)ifduration。Seconds()10{w。WriteHeader(500)w。Write(〔〕byte(fmt。Sprintf(error:v,duration。Seconds())))}else{w。WriteHeader(200)w。Write(〔〕byte(ok))}} 查看Pod的重启情况kubectlgetpodsnstudyk8sNAMEREADYSTATUSRESTARTSAGEtestgoliveness66755487cdck4mz01CrashLoopBackOff611mtestgoliveness66755487cdf54lz01CrashLoopBackOff611mtestgoliveness66755487cdhts8k01CrashLoopBackOff711mtestgoliveness66755487cdjzsmb01Running711mtestgoliveness66755487cdk9hdk11Running711m 下面在简单看下启动探针的配置ports:name:livenessportcontainerPort:8080hostPort:8080livenessProbe:httpGet:path:healthzport:livenessportfailureThreshold:1periodSeconds:10startupProbe:httpGet:path:healthzport:livenessportfailureThreshold:30将探针标记为失败之前的重试次数periodSeconds:10执行探测的时间间隔(单位是秒) 上面配置了启动探针,那么程序就会有3010300s来完成启动过程。一旦启动探测成功一次,存活探测任务就会接管对容器的探测,对容器死锁作出快速响应。如果启动探测一直没有成功,容器会在300秒后被杀死,并且根据restartPolicy来执行进一步处置。使用HPA实现Pod的自动扩容 我们通过手动更改RS副本集的大小就能实现Pod的扩容,但是这是我们手动操作的,人工不可能24小时不间断的检测业务的复杂变化。所以需要自动扩容的机制,能够根据业务的变化,自动的实现Pod的扩容和缩容。 Kubernetes提供HorizontalPodAutoscaling(Pod水平自动伸缩),简称HPA。通过使用HPA可以自动缩放在ReplicationController,Deployment或者ReplicaSet中的Pod。HPA将自动缩放正在运行的Pod的数量,以实现最高效率。 HPA中影响Pod数量的因素包括:用户定义的允许运行的Pod的最小和最大数量;资源指标中报告的观察到的CPU或内存使用情况;第三方指标应用程序(例如Prometheus,Datadog等)提供的自定义指标。HPA是如何工作的 HPA是用来控制Pod水平伸缩的控制器,基于设定的扩缩容规则,实时采集监控指标数据,根据用户设定的指标阈值计算副本数,进而调整目标资源的副本数量,完成扩缩容操作。 算法的细节 Pod水平自动扩缩控制器根据当前指标和期望指标来计算扩缩比例期望副本数ceil〔当前副本数(当前指标期望指标)〕 例如,如果当前指标值为200m,而期望值为100m,则副本数将加倍,因为200。0100。02。0如果当前值为50m,则副本数将减半,因为50。0100。00。5。如果比率足够接近1。0(在全局可配置的容差范围内,默认为0。1),则控制平面会跳过扩缩操作。 如果HorizontalPodAutoscaler指定的是targetAverageValue或targetAverageUtilization,那么将会把指定Pod度量值的平均值做为当前指标。 HPA控制器执行扩缩操作并不是马上完成的,会有一个扩缩操作的时间窗口。首先每次的扩缩操作建议会被记录,在扩缩操作的时间窗口内,会根据规则选出一个缩容的策略。这样就能让系统更平滑的进行扩缩操作,消除短时间内指标波动带来的影响。时间窗口的值可通过kubecontrollermanager服务的启动参数horizontalpodautoscalerdownscalestabilization进行配置,默认值为5分钟。 来个栗子实践下catEOF。podhpa。yamlapiVersion:appsv1kind:Deploymentmetadata:creationTimestamp:nulllabels:app:gowebname:gowebnamespace:studyk8sspec:replicas:5selector:matchLabels:app:gowebstrategy:{}template:metadata:creationTimestamp:nulllabels:app:gowebspec:containers:image:liz2019testdockergohubname:goappcontainerresources:requests:cpu:0。1memory:50Milimits:cpu:0。2memory:100Mistatus:{}apiVersion:v1kind:Servicemetadata:name:gowebsvclabels:run:gowebsvcspec:selector:app:gowebtype:NodePort服务类型ports:protocol:TCPport:8000targetPort:8000nodePort:30001对外暴露的端口name:gowebhttpEOF 配置HPA策略catEOF。podhpaconfig。yamlapiVersion:autoscalingv2beta2kind:HorizontalPodAutoscalermetadata:name:gowebhpanamespace:studyk8sspec:scaleTargetRef:apiVersion:appsv1kind:Deploymentname:gowebminReplicas:1maxReplicas:10metrics:type:Resourceresource:name:cputarget:type:UtilizationaverageUtilization:50EOF 关于HPA的版本支持 autoscalingv1只支持CPU一个指标的弹性伸缩; autoscalingv2beta1支持自定义指标; autoscalingv2beta2支持外部指标;总结 1、Pod是Kubernetes集群中能够被创建和管理的最小部署单元; 2、通常使用控制起来管理Pod,来实现Pod的滚动升级,副本管理,集群级别的自愈能力; 3、Deployment可以用来创建一个新的服务,更新一个新的服务,也可以用来滚动升级一个服务。借助于ReplicaSet也可以实现Pod的副本管理功能; 4、静态Pod是由kubelet进行管理的仅存在与特定node上的Pod,他们不能通过apiserver进行管理,无法与rc,deployment,ds进行关联,并且kubelet无法对他们进行健康检查; 5、静态Pod主要是用来对集群中的组件进行容器化操作,例如etcdkubeapiserverkubecontrollermanagerkubescheduler这些都是静态Pod资源; 6、Pod一般不直接对外暴露服务,一个Pod只是一个运行服务的实例,随时可能在节点上停止,然后再新的节点上用一个新的IP启动一个新的Pod,因此不能使用确定的IP和端口号提供服务; 7、Pod中可以使用hostNetwork和hostPort,可以直接暴露node的ip地址; 8、Label是kubernetes系统中的一个重要概念。它的作用就是在资源上添加标识,用来对它们进行区分和选择; 9、Kubernetes支持节点和Pod两个层级的亲和与反亲和。通过配置亲和与反亲和规则,可以允许您指定硬性限制或者偏好,例如将前台Pod和后台Pod部署在一起、某类应用部署到某些特定的节点、不同应用部署到不同的节点等等; 10、为了合理的利用k8s中的资源,一般会对Pod添加资源限制; 11、k8s中当Pod的运行出现异常的时候,需要能够检测到Pod的这种状态,将流量路由到其他正常的Pod中,同时去恢复损坏的组件,这种情况可以通过K8s中的探针去解决; 12、面对业务情况的变化,使用HPA实现Pod的自动扩容;