概念介绍
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/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 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: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: 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) AGE my-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-nginx Namespace: default Labels: run=my-nginx Annotations: 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-nginx Type: ClusterIP IP: 10.104.137.147 Port: <unset> 80/TCP TargetPort: 80/TCP Endpoints: 10.244.3.40:80,10.244.3.42:80,10.244.3.43:80 Session Affinity: None Events: <none>
kubectl get ep my-nginx
显示如下:
NAME ENDPOINTS AGE my-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: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: ports: - port: 80 protocol: TCP type: NodePort selector: run: my-nginx
kubectl apply -f service.yaml
kubectl get svc
显示如下:
上面截图可以发现my-nginx这个service 的TYPE类型变成了NodePort
service type类型介绍
1.没有selector的Service,type类型是ExternalName,k8s集群内到外的访问
Servcie抽象了该如何访问Kubernetes Pod,但也能够抽象其它类型的backend,例如:
希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。 希望服务指向另一个Namespace中或其它集群中的服务。 正在将工作负载转移到Kubernetes集群,和运行在Kubernetes集群之外的backend。
在任何这些场景中,都能够定义没有selector的Service :
kind: Service apiVersion: v1 metadata: name: my-service spec: ports: - protocol: TCP port: 80 targetPort: 330
由于这个Service没有selector,就不会创建相关的Endpoints对象。可以手动将 Service 映射到指定的 Endpoints:
kind: Endpoints apiVersion: v1 metadata: name: my-service subsets: - 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: Service apiVersion: v1 metadata: name: my-service namespace: prod spec: 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: Service apiVersion: v1 metadata: name: my-service spec: selector: app: MyApp ports: - protocol: TCP port: 80 targetPort: 9376 nodePort: 30061 clusterIP: 10.0.171.239 loadBalancerIP: 78.11.24.19 type: LoadBalancer status: 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: Service apiVersion: v1 metadata: name: my-service spec: selector: app: MyApp ports: - name: http protocol: TCP port: 80 targetPort: 3306 externalIPs: - 80.11.12.10