Docker学习第九天——K8S之pod、service
1.1 Service的作用• 在kubernetes中,Pod是有生命周期的,如果Pod重启它的IP很有可能会发生变化。如果我们的服务都是将Pod的IP地址写死,Pod挂掉或者重启,和刚才重启的pod相关联的其他服务将会找不到它所关联的Pod。• 为了解决这个问题,在kubernetes中定义了service资源对象,Service 定义了一个服务访问的入口。
目录
一、pod
1、pod标签
1.1 标签的定义
• 标签其实就一对 key/value ,被关联到对象上(如Pod),可用来划分特定的对象(比如版本,服务类型等);
• 标签可以在创建一个对象的时候直接定义,也可以在后期随时修改;
• 每一个对象可以拥有多个标签,但是,key值必须是唯一的;
• 创建标签之后也可以方便我们对资源进行分组管理;
• 如果对pod打标签,之后就可以使用标签来查看、删除指定的pod;
• 在k8s中,大部分资源都可以打标签。
1.2 给pod资源打标签
#对已经存在的pod打标签 [root@hd1.com~]# kubectl label pods pod-first release=v1 #查看标签是否打成功: [root@hd1.com~]# kubectl get pods pod-first --show-labels NAME READY STATUS RESTARTS AGE LABELS pod-first 1/1 Running 1 21h release=v1, app=tomcat-pod-first
1.3 查看资源标签相关命令
• 查看所有pod资源的标签:
kubectl get pods --show-labels
• 查看指定pod具有的所有标签:
kubectl get pods pod名 --show-labels
• 列出标签key是release的pod,不显示标签:
kubectl get pods -l release
• 列出标签key是release、值是v1的pod,不显示标签:
kubectl get pods -l release=v1
• 列出标签key是release的所有pod,并打印对应的标签值:
kubectl get pods -L release
• 查看所有名称空间下的所有pod的标签:
kubectl get pods --all-namespaces --show-labels
• 查看key-value为release=v1的pod,且release独立显示一列
kubectl get pods -l release=v1 -L release
NAME READY STATUS RESTARTS AGE RELEASE
pod-first 1/1 Running 0 13h v1
2、node节点选择器
2.1 基本介绍
在创建pod资源的时候,pod会根据scheduler进行调度,那么默认会调度到随机的一个工作节点,若想pod调度到指定节点或者调度到一些具有相同特点的node节点,可以使用pod中的nodeName或者nodeSelector字段指定要调度到的node节点。
2.2 nodeName
指定pod节点运行在哪个具体node上。
在spec字段内,写法:nodeName: node节点名。
[root@hd1.com ~]# cat pod-node.yaml apiVersion: v1 kind: Pod metadata: name: demo-pod1 namespace: default labels: app: myapp env: dev spec: nodeName: hd2.com #指定node节点 containers: - name: tomcat-pod-java ports: - containerPort: 8080 image: tomcat:8.5-jre8-alpine imagePullPolicy: IfNotPresent - name: busybox image: busybox:1.28 command: - "/bin/sh" - "-c" - "sleep 3600" [root@hd1.com ~]# kubectl apply -f pod-node.yaml #查看pod调度到哪个节点 [root@hd1.com ~]# kubectl get pods -o wide NAME READY STATUS RESTARTS demo-pod 1/1 Running 0 hd2.com
3、nodeSelector
指定pod调度到具有哪些标签的node节点上。
#给node节点打标签,打个具有disk=ceph的标签 [root@hd1.com ~]# kubectl label nodes hd3.com disk=ceph node/hd3.com labeled #查看节点的详细信息 [root@hd1 node]# kubectl describe nodes hd3.com Name: hd3.com Roles: worker Labels: beta.kubernetes.io/arch=amd64 beta.kubernetes.io/os=linux disk=ceph #定义pod的时候指定要调度到具有disk=ceph标签的node上 [root@hd1.com ~]# vim pod-1.yaml apiVersion: v1 kind: Pod metadata: name: demo-pod-1 namespace: default labels: app: myapp env: dev spec: nodeSelector: #指定node标签 disk: ceph containers: - name: tomcat-pod-java ports: - containerPort: 8080 image: tomcat:8.5-jre8-alpine imagePullPolicy: IfNotPresent [root@hd1.com ~]# kubectl apply -f pod-1.yaml #查看pod调度到哪个节点 [root@hd1.com ~]# kubectl get pods -o wide NAME READY STATUS RESTARTS demo-pod-1 1/1 Running 0 hd3.com
3、节点亲和性
3.1 亲和性定义
• 第一个pod随机选则一个节点,做为评判后续的pod能否到达这个pod所在的节点上的运行方式,这就称为pod亲和性;
• 以节点名称为标准,这个节点名称相同的表示是同一个位置,节点名称不相同的表示不是一个位置。
3.2 亲和性调度的表现形式
• podAffinity:把相近的pod结合到相近的位置,如同一区域,同一机架,这样的话pod和pod之间更好通信,比方说有两个机房,这两个机房部署的集群有1000台主机,那么我们希望把nginx和tomcat都部署同一个地方的node节点上,可以提高通信效率;
• podAntiAffinity:pod和pod更倾向不处于相近的位置,如果部署两套程序,那么这两套程序更倾向于反亲和性,这样相互之间不会有影响;
• 同样从node出发,也可以分成亲和性和反亲和性,分别对应nodeAffinity和nodeAntiAffinity。
3.3 键值运算关系
• In:label 的值在某个列表中
• NotIn:label 的值不在某个列表中
• Gt:label 的值大于某个值
• Lt:label 的值小于某个值
• Exists:某个 label 存在
• DoesNotExist:某个 label 不存在
3.4 硬亲和与软亲和
• preferredDuringSchedulingIgnoredDuringExecution 软亲和
软策略举例:结合上面的 “operator: NotIn”,意思就是尽量不要将 pod 调度到匹配到的节点,但是如果只有匹配的节点的话,也可以调度到匹配到的节点。
• requiredDuringSchedulingIgnoredDuringExecution 硬亲和
硬策略举例:结合上面的 “operator: In”,意思就是必须调度到满足条件的节点上,否则就等着 Pending
3.5 pod节点亲和性举例
• 例1:定义两个pod,第一个pod做为基准,第二个pod跟着它走。
[root@hd1.com]# kubectl delete pods pod-first [root@hd1.com ~]# vim pod-required-affinity-demo.yaml apiVersion: v1 kind: Pod metadata: name: pod-first labels: app2: myapp2 tier: frontend spec: containers: - name: myapp image: ikubernetes/myapp:v1 imagePullPolicy: IfNotPresent --- apiVersion: v1 kind: Pod metadata: name: pod-second labels: app: backend tier: db spec: containers: - name: busybox image: busybox:1.28 imagePullPolicy: IfNotPresent command: ["sh","-c","sleep 3600"] affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - {key: app2, operator: In, values: ["myapp2"]} topologyKey: kubernetes.io/hostname [root@hd1 node]# kubectl get nodes --show-labels NAME STATUS ROLES AGE VERSION LABELS kubernetes.io/hostname=hd1.com,kubernetes.io/os=linux,node - #上面表示创建的pod必须与拥有app=myapp2标签的pod在一个节点上 [root@hd1.com ~]# kubectl apply -f pod-required-affinity-demo.yaml kubectl get pods -o wide pod-first running hd2.com pod-second running hd2.com
上面说明第一个pod调度到哪,第二个pod也调度到哪,这就是pod节点亲和性。
• pod节点反亲和性:定义两个pod,第一个pod做为基准,第二个pod跟它调度节点相反。
[root@hd1.com ~]# vim pod-required-anti-affinity-demo.yaml apiVersion: v1 kind: Pod metadata: name: pod-first labels: app1: myapp1 tier: frontend spec: containers: - name: myapp image: ikubernetes/myapp:v1 --- apiVersion: v1 kind: Pod metadata: name: pod-second labels: app: backend tier: db spec: containers: - name: busybox image: busybox:1.28 imagePullPolicy: IfNotPresent command: ["sh","-c","sleep 3600"] affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - {key: app1, operator: In, values: ["myapp1"]} topologyKey: kubernetes.io/hostname [root@hd1.com ~]# kubectl apply -f pod-required-anti-affinity-demo.yaml [root@hd1.com ~]# kubectl get pods -o wide pod-first running hd2.com pod-second running hd3.com [root@hd1.com ~]# kubectl delete -f pod-required-anti-affinity-demo.yaml
上述显示两个pod不在一个node节点上,这就是pod节点反亲和性。
4、Pod常见的状态和重启策略
4.1 常见Pod状态
• 挂起(Pending):我们在请求创建pod时,条件不满足,调度没有完成,没有任何一个节点能满足调度条件,已经创建了pod但是没有适合它运行的节点叫做挂起,调度没有完成,处于pending的状态会持续一段时间:包括调度Pod的时间和通过网络下载镜像的时间。
• 运行中(Running):Pod已经绑定到了一个节点上,Pod 中所有的容器都已被创建。至少有一个容器正在运行。
• 成功(Succeeded):Pod 中的所有容器都被成功终止,并且不会再重启。
• 失败(Failed):Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。
• 未知(Unknown):未知状态,所谓pod是什么状态是apiserver和运行在pod节点的kubelet进行通信获取状态信息的,如果节点之上的kubelet本身出故障,那么apiserver就连不上kubelet,得不到信息了,就会看Unknown
4.2 Pod重启策略
• Pod的重启策略(RestartPolicy)应用于Pod内的所有容器,并且仅在Pod所处的Node上由kubelet进行判断和重启操作。当某个容器异常退出或者健康检查失败时,kubelet将根据 RestartPolicy 的设置来进行相应的操作。
• Pod的重启策略包括 Always、OnFailure和Never,默认值为Always。
◇ Always:当容器失败时,由kubelet自动重启该容器。
◇ OnFailure:当容器终止运行且退出码不为0时,由kubelet自动重启该容器。
◇ Never:不论容器运行状态如何,kubelet都不会重启该容器。
• yaml举例:
[root@hd1.com ~]# vim pod.yaml apiVersion: v1 kind: Pod metadata: name: demo-pod namespace: default labels: app: myapp spec: restartPolicy: Always containers: - name: tomcat-pod-java ports: - containerPort: 8080 image: tomcat:8.5-jre8-alpine imagePullPolicy: IfNotPresent
4.3 存活性探测和就绪性探测
1) livenessProbe:存活性探测
Liveness探测器是让Kubernetes知道你的应用是否活着。如果你的应用还活着,那么Kubernetes就让它继续存在。如果你的应用程序已经死了,Kubernetes将移除Pod并重新启动一个来替换它。
2) readinessProbe:就绪性探测
就绪探针旨在让Kubernetes知道你的应用是否准备好为请求提供服务。Kubernetes只有在就绪探针通过才会把流量转发到Pod。如果就绪探针检测失败,Kubernetes将停止向该容器发送流量,直到它通过。
3) 目前LivenessProbe和ReadinessProbe两种探针都支持下面三种探测方法:
◇ ExecAction:在容器中执行指定的命令,如果执行成功,退出码为 0 则探测成功。
◇ TCPSocketAction:通过容器的IP 地址和端口号执行TCP 检查,如果能够建立 TCP 连接,则表明容器健康。
◇ HTTPGetAction:通过容器的IP地址、端口号及路径调用 HTTP Get方法,如果响应的状态码大于等于200且小于400,则认为容器健康
4) 探针探测结果有以下值:
◇ Success:表示通过检测。
◇ Failure:表示未通过检测。
◇ Unknown:表示检测没有正常进行。
5) Pod探针相关的属性:
◇ initialDelaySeconds: Pod启动后首次进行检查的等待时间,单位“秒”。
◇ periodSeconds: 检查的间隔时间,默认为10s,单位“秒”。
◇ timeoutSeconds: 探针执行检测请求后,等待响应的超时时间,默认为1s,单位“秒”。
◇ successThreshold:连续探测几次成功,才认为探测成功,默认为 1,在 Liveness 探针中必须为1,最小值为1。
◇ failureThreshold: 探测失败的重试次数,重试一定次数后将认为失败,在 readiness 探针中,Pod会被标记为未就绪,默认为 3,最小值为 1
6) 两种探针区别:
ReadinessProbe 和 livenessProbe 可以使用相同探测方式,只是对 Pod 的处置方式不同:
◇ readinessProbe 当检测失败后,将Pod的IP:Port从对应的EndPoint 列表中删除。
◇ livenessProbe 当检测失败后,将杀死容器并根据Pod的重启策略来决定作出对应的措施。
7) Pod探针使用示例:
◇ LivenessProbe 探针使用示例
(1)通过exec方式做健康探测
vim liveness-http.yaml apiVersion: v1 kind: Pod metadata: name: liveness-http labels: test: liveness spec: containers: - name: liveness image: mydlqclub/springboot-helloworld:0.0.1 livenessProbe: initialDelaySeconds: 20 #延迟加载时间 periodSeconds: 5 #重试时间间隔 timeoutSeconds: 10 #超时时间设置 httpGet: scheme: HTTP port: 8081 path: /actuator/health
- 上面 Pod 中启动的容器是一个 SpringBoot 应用,其中引用了 Actuator 组件,提供了 /actuator/health 健康检查地址,存活探针可以使用 HTTPGet 方式向服务发起请求,请求 8081 端口的 /actuator/health 路径来进行存活判断:
- 任何大于或等于200且小于400的代码表示探测成功。
- 任何其他代码表示失败。
- 如果探测失败,则会杀死 Pod 进行重启操作。
▪ httpGet探测方式有如下可选的控制字段:
- scheme: 用于连接host的协议,默认为HTTP。
- host:要连接的主机名,默认为Pod IP,可以在http request head中设置host头部。
- port:容器上要访问端口号或名称。
- path:http服务器上的访问URI。
- httpHeaders:自定义HTTP请求headers,HTTP允许重复headers。
(3)通过TCP方式做健康探测
vim liveness-tcp.yaml apiVersion: v1 kind: Pod metadata: name: liveness-tcp labels: app: liveness spec: containers: - name: liveness image: nginx livenessProbe: initialDelaySeconds: 15 periodSeconds: 20 tcpSocket: port: 80
- TCP 检查方式和 HTTP 检查方式非常相似,在容器启动 initialDelaySeconds 参数设定的时间后,kubelet 将发送第一个 livenessProbe 探针,尝试连接容器的 80 端口,如果连接失败则将杀死 Pod 重启容器。
◇ ReadinessProbe 探针使用示例
▪ Pod 的ReadinessProbe 探针使用方式和 LivenessProbe 探针探测方法一样,也是支持三种,只是一个是用于探测应用的存活,一个是判断是否对外提供流量的条件。
▪ 这里用一个 Springboot 项目,设置 ReadinessProbe 探测 SpringBoot 项目的 8081 端口下的 /actuator/health 接口,如果探测成功则代表内部程序以及启动,就开放对外提供接口访问,否则内部应用没有成功启动,暂不对外提供访问,直到就绪探针探测成功。
vim readiness-exec.yaml apiVersion: v1 kind: Service metadata: name: springboot labels: app: springboot spec: type: NodePort ports: - name: server port: 8080 targetPort: 8080 nodePort: 31180 - name: management port: 8081 targetPort: 8081 nodePort: 31181 selector: app: springboot --- apiVersion: v1 kind: Pod metadata: name: springboot labels: app: springboot spec: containers: - name: springboot image: mydlqclub/springboot-helloworld:0.0.1 ports: - name: server containerPort: 8080 - name: management containerPort: 8081 readinessProbe: initialDelaySeconds: 20 periodSeconds: 5 timeoutSeconds: 10 httpGet: scheme: HTTP port: 8081 path: /actuator/health
二、Service
1、Service简介
1.1 Service的作用
• 在kubernetes中,Pod是有生命周期的,如果Pod重启它的IP很有可能会发生变化。如果我们的服务都是将Pod的IP地址写死,Pod挂掉或者重启,和刚才重启的pod相关联的其他服务将会找不到它所关联的Pod。
• 为了解决这个问题,在kubernetes中定义了service资源对象,Service 定义了一个服务访问的入口,客户端通过这个入口即可访问服务背后的应用集群实例,service是一组Pod的逻辑集合,这一组Pod能够被Service访问到,通常通过Label Selector实现。
1.2 Service概述
• service是一个固定接入层,客户端可以通过访问service的ip和端口访问到service关联的后端pod,这个service工作依赖于在kubernetes集群之上部署的一个附件,就是kubernetes的dns服务(不同kubernetes版本的dns默认使用的也是不一样的,1.11之前的版本使用的是kubeDNs,较新的版本使用的是coredns),service的名称解析是依赖于dns附件的,因此在部署完k8s之后需要再部署dns附件。
• kubernetes要想给客户端提供网络功能,需要依赖第三方的网络插件(flannel,calico等)。
• 每个K8s节点上都有一个组件叫做kube-proxy,kube-proxy这个组件将始终监视着apiserver中有关service资源的变动信息,需要跟master之上的apiserver交互,随时连接到apiserver上获取任何一个与service资源相关的资源变动状态,这种是通过kubernetes中固有的一种请求方法watch(监视)来实现的,一旦有service资源的内容发生变动(如创建,删除),kube-proxy都会将它转化成当前节点之上的能够实现service资源调度,把我们请求调度到后端特定的pod资源之上的规则,这个规则可能是iptables,也可能是ipvs,取决于service的实现方式。
1.3 Service工作原理
• k8s在创建Service时,会根据标签选择器selector(lable selector)来查找Pod,据此创建与Service同名的endpoint对象,当Pod 地址发生变化时,endpoint也会随之发生变化,service接收前端client请求的时候,就会通过endpoint,找到转发到哪个Pod进行访问的地址。(至于转发到哪个节点的Pod,由负载均衡kube-proxy决定)
1.4 k8s集群中的三类IP地址
• Node Network(节点网络):物理节点或者虚拟节点的网络,如ens33接口上的网路地址
• Pod network(pod 网络),创建的Pod具有的IP地址
• Cluster Network(集群地址,也称为service network):这个地址是虚拟的地址(virtual ip),没有配置在某个接口上,只是出现在service的规则当中。
• Node Network和Pod network这两种网络地址是我们实实在在配置的,其中节点网络地址是配置在节点接口之上,而pod网络地址是配置在pod资源之上的,因此这些地址都是配置在某些设备之上的,这些设备可能是硬件,也可能是软件模拟的。
2、创建Service资源
2.1 查看定义Service资源所用字段
[root@hd1.com ~]# kubectl explain service FIELDS: apiVersion <string> #service资源使用的api组 kind<string> #创建的资源类型 metadata<Object> #定义元数据 spec<Object>
2.2 Service的四种类型
1) ExternalName:适用于k8s集群内部容器访问外部资源,它没有指定selector去选择pod,也没有定义任何的端口和Endpoint。
2) ClusterIP:通过k8s集群内部IP暴露服务,选择该值,服务只能够在集群内部访问,这也是默认的ServiceType。
3) NodePort:通过每个Node节点上的IP和静态端口暴露k8s集群内部的服务。通过请求<NodeIP>:<NodePort>可以把请求代理到内部的pod。Client----->NodeIP:NodePort----->Service Ip:ServicePort----->PodIP:ContainerPort。
4) LoadBalancer:使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到NodePort服务和ClusterIP服务。
2.3 Service端口
[root@hd1.com ~]# kubectl explain service.spec.ports FIELDS: appProtocol <string> name<string> #定义端口的名字 nodePort<integer> #宿主机上映射的端口,比如一个Web应用需要被k8s集群之外的其他用户访问, #那么需要配置type=NodePort,若配置nodePort=30001,那么其他机器就 #可以通过浏览器访问scheme://k8s集群中的任何一个节点ip:30001即可访问 #到该服务,例如http://192.168.1.12:30001。如果在k8s中部署MySQL数据库, #MySQL可能不需要被外界访问,只需被内部服务访问,那么就不需要设置NodePort port<integer> -required- #service的端口,这个是k8s集群内部服务可访问的端口 protocol<string> targetPort <string> # targetPort是pod上的端口,从port和nodePort上来的流量, #经过kube-proxy流入到后端pod的targetPort上,最后进入容器。 #与制作容器时暴露的端口一致(使用DockerFile中的EXPOSE), #例如官方的nginx暴露80端口。
2.4 创建Service:type类型为ClusterIP
1)创建Pod
#把nginx.tar.gz上传到hd3.com和hd2.com,手动解压 [root@hd2.com ~]# docker load -i nginx.tar.gz [root@hd3.com ~]# docker load -i nginx.tar.gz [root@hd1.com ~]# cat pod_test.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx imagePullPolicy: IfNotPresent ports: - containerPort: 80 #pod中的容器需要暴露的端口 #更新资源清单文件 [root@hd1.com ~]# kubectl apply -f pod_test.yaml #查看刚才创建的Pod ip地址 [root@hd1.com ~]# kubectl get pods -l run=my-nginx -o wide NAME STATUS IP NODE my-nginx-5b56ccd65f-26vcz Running 10.244.187.101 hd3.com my-nginx-5b56ccd65f-95n7p Running 10.244.209.149 hd2.com #请求pod ip地址,查看结果 [root@hd1.com ~]# curl 10.244.187.101 <!DOCTYPE html> <html> <h1>Welcome to nginx!</h1> </body> </html> [root@hd1.com ~]# curl 10.244.209.149 <!DOCTYPE html> <html> <h1>Welcome to nginx!</h1> </body> </html> [root@hd1.com ~]# kubectl exec -it my-nginx-5b56ccd65f-26vcz -- /bin/bash root@my-nginx-5b56ccd65f-26vcz:/# curl 10.244.209.149 <!DOCTYPE html> <html> <h1>Welcome to nginx!</h1> </html> root@my-nginx-5b56ccd65f-26vcz:/#exit
2)创建Service
[root@hd1.com service]# cat service_test.yaml apiVersion: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: type: ClusterIP #只能在集群内部访问 ports: - port: 80 #service的端口,暴露给k8s集群内部服务访问 protocol: TCP targetPort: 80 #pod容器中定义的端口 selector: run: my-nginx #选择拥有run=my-nginx标签的pod [root@hd1.com service]# kubectl apply -f service_test.yaml service/my-nginx created [root@hd1.com ~]# kubectl get svc -l run=my-nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-nginx ClusterIP 10.99.198.177 <none> 80/TCP 143m #在k8s控制节点访问service的ip:端口就可以把请求代理到后端pod [root@hd1.com ~]# curl 10.99.198.177:80 <!DOCTYPE html> <html> <h1>Welcome to nginx!</h1> </html> #通过上面可以看到请求service IP:port跟直接访问pod ip:port看到的结果一样, #这就说明service可以把请求代理到它所关联的后端pod #注意:上面的10.99.198.177:80地址只能是在k8s集群内部可以访问,在外部无法访问 #若想在k8s集群之外访问,需要把service type类型改成NodePort #查看service详细信息 [root@hd1.com ~]# kubectl describe svc my-nginx Name: my-nginx Namespace: default Labels: run=my-nginx Annotations: <none> Selector: run=my-nginx Type: ClusterIP IP Families: <none> IP: 10.99.198.177 IPs: 10.99.198.177 Port: <unset> 80/TCP TargetPort: 80/TCP Endpoints: 10.244.187.102:80,10.244.209.149:80 Session Affinity: None Events: <none> [root@hd1.com ~]# kubectl get ep my-nginx NAME ENDPOINTS AGE my-nginx 10.244.187.102:80,10.244.209.149:80 142m
◇ service可以对外提供统一固定的ip地址,并将请求重定向至集群中的pod。其中“将请求重定向至集群中的pod”就是通过endpoint与selector协同工作实现。selector是用于选择pod,由selector选择出来的pod的ip地址和端口号,将会被记录在endpoint中。
◇ endpoint便记录了所有pod的ip地址和端口号。当一个请求访问到service的ip地址时,就会从endpoint中选择出一个ip地址和端口号,然后将请求重定向至pod中。具体把请求代理到哪个pod,需要的就是kube-proxy的轮询实现的。
◇ service不会直接到pod,service是直接到endpoint资源,就是地址加端口,再由endpoint再关联到pod。
更多推荐
所有评论(0)