概念介绍

1.pod的生命周期

Kubernetes Pod 是有生命周期的,它们可以被创建,也可以被销毁,然而一旦被销毁生命就永远结束,通过ReplicaSets能够动态地创建和销毁Pod(例如,需要进行扩缩容,或者执行滚动升级),每个Pod都会获取它自己的IP地址,这些IP地址并不是一直处于稳定的状态,可能随时改变。这会导致一个问题:在Kubernetes集群中,如果一组Pod(称为backend)为其它 Pod (称为frontend)提供服务,那么那些frontend该如何发现,并连接到这组Pod 中的哪些 backend 呢?


2.什么是service

Kubernetes Service 定义了这样一种抽象:逻辑上的一组 Pod,一种可以访问它们的策略 —— 通常称为微服务。这一组Pod能够被Service访问到,通常是通过 Label Selector实现的。举个例子,考虑一个图片处理backend,它运行了3个副本。这些副本是可互换的 —— frontend 不需要关心它们调用了哪个 backend 副本。然而组成这一组 backend程序的Pod实际上可能会发生变化,frontend客户端不应该也没必要知道,而且也不需要跟踪这一组 backend 的状态。Service 定义的抽象能够解耦这种关联。


3.service代理pod

例如想要用nginx反向代理tomcat,那么tomcat如果是通过pod部署的,pod的ip可能会随时变化,那么我们就需要在所有这些部署tomcat的pod前面加上一个固定接入层,我们nginx反向代理只需要写service地址,就会代理到后端的pod,那么pod就算ip怎么变化,通过service都可以找到

对Kubernetes集群中的应用,Kubernetes提供了简单的Endpoints API,只要Service中的一组 Pod 发生变更,应用程序就会被更新。对非Kubernetes 集群中的应用,Kubernetes提供了基于VIP的网桥的方式访问Service,再由 Service重定向到 backend Pod。简单总结:

service是一个固定接入层,客户端可以通过访问service来访问到后端pod,kube-proxy为service生成一个iptables规则


案例演示

通过Service访问pod


1.创建pod

cat  pod_test.yaml

apiVersion: apps/v1kind: Deploymentmetadata:  name: my-nginxspec:  selector:    matchLabels:      run: my-nginx  replicas: 2  template:    metadata:      labels:        run: my-nginx    spec:      containers:      - name: my-nginx        image: nginx        ports:        - containerPort: 80

(2)执行yaml文件

kubectl apply -f pod_test.yaml

这使得可以从集群中任何一个节点来访问它。检查节点,通过如下命令查看Pod是否正在运行:


kubectl get pods -l run=my-nginx -o wide

看到的pod是running说明创建成功

(3)检查 Pod 的 IP 地址:


kubectl get pods -l run=my-nginx -o yaml | grep podIP

显示如下:

 podIP: 10.244.3.40 cni.projectcalico.org/podIP: 10.244.3.42/32 podIP: 10.244.3.42

应该能够通过ssh登录到集群中的任何一个节点上,使用curl也能调通所有IP地址。需要注意的是,容器不会使用该节点上的80端口,也不会使用任何特定的 NAT规则去路由流量到Pod上。这意味着可以在同一个节点上运行多个Pod,使用相同的容器端口,并且可以从集群中任何其他的 Pod 或节点上使用 IP 的方式访问到它们。像Docker 一样,端口能够被发布到主机节点的接口上,但是出于网络模型的原因应该从根本上减少这种用法。


2.创建service


我们所有Pod在一个扁平的、集群范围的地址空间中运行Nginx服务,可以直接连接到这些Pod,但如果某个节点死掉了会发生什么呢?Pod会终止,Deployment 将创建新的 Pod,且使用不同的 IP。这正是 Service 要解决的问题。Kubernetes Service从逻辑上定义了运行在集群中的一组 Pod,这些 Pod 提供了相同的功能。当每个Service 创建时,会被分配一个唯一的IP地址(也称为 clusterIP)。这个 IP 地址与一个Service 的生命周期绑定在一起,当Service存在的时候它也不会改变。可以配置 Pod 使它与 Service 进行通信,Pod 知道与Service通信将被自动地负载均衡到该Service中的某些 Pod 上。

(1)创建service

cat service.yaml

apiVersion: v1kind: Servicemetadata:  name: my-nginx  labels:    run: my-nginxspec:  ports:  - port: 80    protocol: TCP  selector:    run: my-nginx

(2)执行service的yaml文件

kubectl  apply -f service.yaml

上述yaml将创建一个Service,对应具有标签run: my-nginx的Pod,目标TCP 端口80,并且在一个抽象的Service端口(targetPort:容器接收流量的端口;port:抽象的Service 端口,可以使任何其它Pod访问该Service的端口)上暴露。查看 Service API 对象了解 Service 定义支持的字段列表。

kubectl get svc my-nginx

显示如下:


NAME       CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGEmy-nginx   10.104.137.147   <none>      80/TCP    10m


正如前面所提到的,一个Service由一组backend Pod组成。这些Pod通过 endpoints暴露出来。Service Selector将持续评估,结果被POST到一个名称为 my-nginx 的 Endpoint 对象上。当Pod 终止后,它会自动从Endpoint中移除,新的能够匹配上Service Selector 的 Pod 将自动地被添加到Endpoint中。检查该Endpoint,注意到IP地址与在第一步创建的 Pod 是相同的。

kubectl describe svc my-nginx

显示如下:

Name:              my-nginxNamespace:         defaultLabels:            run=my-nginxAnnotations:       kubectl.kubernetes.io/last-applied-configuration:                     {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"run":"my-nginx"},"name":"my-nginx","namespace":"default"},"spe...Selector:          run=my-nginxType:              ClusterIPIP:                10.104.137.147Port:              <unset>  80/TCPTargetPort:        80/TCPEndpoints:         10.244.3.40:80,10.244.3.42:80,10.244.3.43:80Session Affinity:  NoneEvents:            <none>

kubectl get ep my-nginx

显示如下:

NAME       ENDPOINTS                     AGEmy-nginx   10.244.2.5:80,10.244.3.4:80   1m

现在,能够从集群中任意节点上使用curl 命令请求Nginx Service <CLUSTER-IP>:<PORT>

curl 10.104.137.147:80  通过访问service ip:port可以路由到后端的pod

注:把上面的ClusterIP类型变成NodePort类型,供集群外部访问

在原有的基础上,增加一个字段type,如下所示

cat service.yaml

apiVersion: v1kind: Servicemetadata:  name: my-nginx  labels:    run: my-nginxspec:  ports:  - port: 80    protocol: TCP  type:    NodePort  selector:    run: my-nginx

kubectl  apply  -f service.yaml

kubectl  get  svc

显示如下:

上面截图可以发现my-nginx这个service 的TYPE类型变成了NodePort


粉丝福利
扫码注册,华为云产品可1折秒杀


service type类型介绍

1.没有selector的Service,type类型是ExternalName,k8s集群内到外的访问

Servcie抽象了该如何访问Kubernetes Pod,但也能够抽象其它类型的backend,例如:


希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。希望服务指向另一个Namespace中或其它集群中的服务。正在将工作负载转移到Kubernetes集群,和运行在Kubernetes集群之外的backend。

在任何这些场景中,都能够定义没有selector的Service :

kind: ServiceapiVersion: v1metadata:  name: my-servicespec:  ports:    - protocol: TCP      port: 80      targetPort: 330

由于这个Service没有selector,就不会创建相关的Endpoints对象。可以手动将 Service 映射到指定的 Endpoints

kind: EndpointsapiVersion: v1metadata:  name: my-servicesubsets:  - addresses:      - ip: 1.2.3.4  #这个地址可以是集群外部的地址,如mysql等    ports:      - port: 3306

注意:

Endpoint IP地址不能是loopback(127.0.0.0/8)、 link-local(169.254.0.0/16)、或者 link-local 多播(224.0.0.0/24)。

访问没有selector的 Service,与有 selector 的 Service 的原理相同。请求将被路由到用户定义的Endpoint(该示例中为 1.2.3.4:3306)ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和Endpoint。相反地,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。

kind: ServiceapiVersion: v1metadata:  name: my-service  namespace: prodspec:  type: ExternalName  externalName: my.database.example.com

当查询主机my-service.prod.svc.CLUSTER时,集群的DNS服务将返回一个值为 my.database.example.com 的 CNAME 记录。访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在DNS层,而且不会进行代理或转发。如果后续决定要将数据库迁移到Kubernetes集群中,可以启动对应的 Pod,增加合适的Selector或Endpoint,修改Service的type。


2.Headless Service

有时不需要或不想要负载均衡,以及单独的Service IP。遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service。这个选项允许开发人员自由寻找他们自己的方式,从而降低与Kubernetes系统的耦合性。应用仍然可以使用一种自注册的模式和适配器,对其它需要发现机制的系统能够很容易地基于这个API来构建。对这类 Service 并不会分配Cluster IP,kube-proxy不会处理它们,而且平台也不会为它们进行负载均衡和路由。DNS 如何实现自动配置,依赖于Service是否定义了selector

(1)配置 Selector

对定义了selector的Headless Service,Endpoint控制器在API中创建了Endpoints记录,并且修改DNS配置返回A记录(地址),通过这个地址直接到达 Service 的后端 Pod 上

(2)不配置 Selector

对没有定义 selector 的Headless Service,Endpoint控制器不会创建 Endpoints记录。然而DNS系统会查找和配置,无论是:ExternalName 类型 Service 的CNAME记录,以及所有其它类型。

记录:与 Service 共享一个名称的任何 Endpoints


3.NodePort 类型

如果设置type的值为 "NodePort",Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个Node将从该端口(每个 Node 上的同一端口)代理到 Service。该端口将通过 Service的spec.ports[*].nodePort 字段被指定。如果需要指定的端口号,可以配置nodePort的值,系统将分配这个端口,否则调用API将会失败(比如,需要关心端口冲突的可能性)。这可以让开发人员自由地安装他们自己的负载均衡器,并配置 Kubernetes 不能完全支持的环境参数,或者直接暴露一个或多个 Node 的 IP 地址。需要注意的是,Service 将能够通过 <NodeIP>:spec.ports[*].nodePort 和 spec.clusterIp:spec.ports[*].port 而对外可见。


4.LoadBalancer 类型

使用支持外部负载均衡器的云提供商的服务,设置type的值为 "LoadBalancer",将为Service提供负载均衡器。负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过Service的status.loadBalancer 字段被发布出去。


kind: ServiceapiVersion: v1metadata:  name: my-servicespec:  selector:    app: MyApp  ports:    - protocol: TCP      port: 80      targetPort: 9376      nodePort: 30061  clusterIP: 10.0.171.239  loadBalancerIP: 78.11.24.19  type: LoadBalancerstatus:  loadBalancer:    ingress:      - ip: 146.148.47.155

来自外部负载均衡器的流量将直接打到backend Pod 上,不过实际它们是如何工作的,这要依赖于云提供商。在这些情况下,将根据用户设置的loadBalancerIP来创建负载均衡器。某些云提供商允许设置 loadBalancerIP。如果没有设置loadBalancerIP,将会给负载均衡器指派一个临时 IP。如果设置了loadBalancerIP,但云提供商并不支持这种特性,那么设置的 loadBalancerIP值将会被忽略掉。


5.AWS 内部负载均衡器

在混合云环境中,有时从虚拟私有云(VPC)环境中的服务路由流量是非常有必要的。可以通过在 Service 中增加 annotation 来实现,如下所示:


[...]metadata:     name: my-service    annotations:         service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0[...]


在水平分割的 DNS 环境中,需要两个 Service 来将外部和内部的流量路由到 Endpoint 上。


6.externalIP

如果外部的IP路由到集群中一个或多个 Node 上,Kubernetes Service 会被暴露给这些 externalIPs。通过外部 IP(作为目的 IP 地址)进入到集群,打到 Service的端口上的流量,将会被路由到 Service 的 Endpoint 上。externalIPs不会被Kubernetes管理,它属于集群管理员的职责范畴。

根据 Service 的规定,externalIPs 可以同任意的 ServiceType 来一起指定。在上面的例子中,my-service 可以在 80.11.12.10:80(外部 IP:端口)上被客户端访问。

kind: ServiceapiVersion: v1metadata:  name: my-servicespec:  selector:    app: MyApp  ports:    - name: http      protocol: TCP      port: 80      targetPort: 3306  externalIPs:     - 80.11.12.10