theboyaply

学,就硬学!

  • Home
  • Archives
  • Java
  • Maven
  • Docker
  • Kubernetes
  • JavaScript
  • ES6
  • Vue
  • 踩坑记录
  • noted

  • 搜索
element ui vue wsimport webservice npm mysql redis node nginx nfs ftp es6 开发工具 vscode 前端 javascript springboot 常见问题 tomcat oracle jenkins maven k8s Linux gitlab docker java

k8s Service介绍

发表于 2020-03-28 | 分类于 Kubernetes | 0 | 阅读次数 417
  • Service 介绍
  • Service 创建
    • 指定 Service 的 IP
  • VIP 和 Service 代理
  • 代理模式的分类
    • userspace 代理模式
    • iptables 代理模式
    • ipvs 代理模式
    • 为何不使用 round-robin DNS?
  • Service 类型 - 发布服务
    • ClusterIP 类型
    • NodePort 类型
    • LoadBalancer 类型
    • ExternalName 类型
  • Headless Service
    • 配置 Selector
    • 不配置 Selector

Service 介绍

k8s 中的 Pod 都会获取它自己的 IP 地址,即使这些 IP 地址不总是稳定可依赖的。 这会导致一个问题:在 k8s 集群中,如果一组 Pod(称为 backend)为其它 Pod (称为 frontend)提供服务,那么那些 frontend 该如何发现,并连接到这组 Pod 中的哪些 backend 呢?

k8s 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。 这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector 实现的。

举个例子,考虑一个图片处理 backend,它运行了3个副本。这些副本是可互换的 —— frontend 不需要关心它们调用了哪个 backend 副本。 然而组成这一组 backend 程序的 Pod 实际上可能会发生变化,frontend 客户端不应该也没必要知道,而且也不需要跟踪这一组 backend 的状态。 Service 定义的抽象能够解耦这种关联。

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

Service 创建

一个 Service 在 k8s 中是一个 Rest 对象,和 Pod 类似。 像所有的 Rest 对象一样, Service 定义可以基于 POST 方式,请求 apiserver 创建新的实例。 例如,假定有一组 Pod,它们对外暴露了 9376 端口,同时还被打上 "app=MyApp" 标签。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

上述配置将创建一个名称为 “my-service” 的 Service 对象,它会将请求代理到使用 TCP 端口 9376,并且具有标签 "app=MyApp" 的 Pod 上。 这个 Service 将被指派一个 IP 地址(默认为为 “Cluster IP”),它会被服务的代理使用。该 Service 的 selector 将会持续评估,处理结果将被 POST 到一个名称为 “my-service” 的 Endpoints 对象上。

指定 Service 的 IP

在 Service 创建的请求中,可以通过设置 spec.clusterIP 字段来指定自己的集群 IP 地址。 比如,希望替换一个已经已存在的 DNS 条目,或者遗留系统已经配置了一个固定的 IP 且很难重新配置。 用户选择的 IP 地址必须合法,并且这个 IP 地址在 service-cluster-ip-range CIDR 范围内,这对 API Server 来说是通过一个标识来指定的。 如果 IP 地址不合法,API Server 会返回 HTTP 状态码 422,表示值不合法。

VIP 和 Service 代理

在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程。kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。 在 Kubernetes v1.0 版本,代理完全在 userspace。在 Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。 从 Kubernetes v1.2 起,默认就是 iptables 代理。在 Kubernetes v1.8.0-beta.0 中,添加了 ipsv 代理。从 Kubernetes v1.14 版本开始默认使用 ipvs 代理。

在 Kubernetes v1.0 版本,Service 是 “4层”(TCP/UDP over IP)概念。 在 Kubernetes v1.1 版本,新增了 Ingress API(beta 版),用来表示 “7层”(HTTP)服务。

代理模式的分类

userspace 代理模式

这种模式,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会在本地 Node 上打开一个端口(随机选择)。 任何连接到“代理端口”的请求,都会被代理到 Service 的backend Pods 中的某个上面(如 Endpoints 所报告的一样)。 使用哪个 backend Pod,是基于 Service 的 SessionAffinity 来确定的。 最后,它安装 iptables 规则,捕获到达该 Service 的 clusterIP(是虚拟 IP)和 Port 的请求,并重定向到代理端口,代理端口再代理请求到 backend Pod。

网络返回的结果是,任何到达 Service 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service、或 Pod 的任何信息。

默认的策略是,通过 round-robin 算法来选择 backend Pod。 实现基于客户端 IP 的会话亲和性,可以通过设置 service.spec.sessionAffinity 的值为 "ClientIP" (默认值为 "None")。

iptables 代理模式

这种模式,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会安装 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面。 对于每个 Endpoints 对象,它也会安装 iptables 规则,这个规则会选择一个 backend Pod。

默认的策略是,随机选择一个 backend。 实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity 的值设置为 "ClientIP" (默认值为 "None")。

和 userspace 代理类似,网络返回的结果是,任何到达 Service 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service、或 Pod 的任何信息。 这应该比 userspace 代理更快、更可靠。然而,不像 userspace 代理,如果初始选择的 Pod 没有响应,iptables 代理能够自动地重试另一个 Pod,所以它需要依赖 readiness probes。

ipvs 代理模式

这种模式,kube-proxy 会监视 Kubernetes Service 对象和 Endpoints,调用 netlink 接口以相应地创建 ipvs 规则并定期与 Kubernetes Service 对象和 Endpoints 对象同步 ipvs 规则,以确保 ipvs 状态与期望一致。访问服务时,流量将被重定向到其中一个后端 Pod。

与 iptables 类似,ipvs 于 netfilter 的 hook 功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着 ipvs 可以更快地 重定向流量,丙炔在同步代理规则时具有更好的性能。此外, ipvs 为负载均衡算法提供了更多选项,例如:

  • rr:轮询调度。
  • lc:最小连接数。
  • dh:目标哈希。
  • sh:源哈希。
  • sed:最短期望延迟。
  • nq:不排队调度。

为何不使用 round-robin DNS?

一个不时出现的问题是,为什么我们都使用 VIP 的方式,而不使用标准的 round-robin DNS,有如下几个原因:

  • 长久以来,DNS 库都没能认真对待 DNS TTL、缓存域名查询结果
  • 很多应用只查询一次 DNS 并缓存了结果
    • 就算应用和库能够正确查询解析,每个客户端反复重解析造成的负载也是非常难以管理的

Service 类型 - 发布服务

对一些应用(如 Frontend)的某些部分,可能希望通过外部(k8s 集群外部)IP 地址暴露 Service。

k8s ServiceTypes 允许指定一个需要的类型的 Service,默认是 ClusterIP 类型。

Type 的取值以及行为如下:

  • ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。

  • NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 : ,可以从集群的外部访问一个 NodePort 服务。

    通俗来讲就是将 Service 的端口绑定到宿主机的某个端口,访问宿主机 IP + 端口即可访问到该 Service。

  • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。

  • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

    通俗来讲就是,定义一个 Service,让其代理一个集群外的服务,在集群中访问这个 Service,通过 Service 去访问到外部服务。

ClusterIP 类型

clusterIP 主要在每个 node 节点使用 iptables 或 ipvs,将发向 clusterIP 对应端口的数据,转发到 kube-proxy 中。然后 kube-proxy 自己内部实现有负载均衡的方法,并可以查询到这个 service 下对应 pod 的地址和端口,进而把数据转发给对应的 pod 的地址和端口。

为了实现围上的功能,主要需要以下几个组件的协同工作:

  • apiserver:用户通过 kubectl 命令向 apiserver 发送创建 service 的命令,apiserver 接收到请求后将数据存储 到 etcd 中。
  • kube-proxy:k8s 的每个节点中都有一个叫做 kube-porxy 的进程,这个进程负责感知 service,pod 的变化,并将变化的信息写入本地的 iptables 或 ipvs 规则中。
  • iptables 或 ipvs:使用 NAT 等技术将 virtual IP 的流量转至 endpoints 中。

示例:

创建 Deployment(cluster-deployment.yaml):

apiVersion: apps/v1
kind: Deployment
metadata:
    name: nginx-deployment
    namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-app
  template:
    metadata:
      labels:
        app: nginx-app
    spec:
      containers:
      - name: nginx-container
        image: hub.xixihaha.com/library/mynginx:v1
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 80

创建一个 Service(cluster-svc.yaml):

apiVersion: v1
kind: Service
metadata:
  name: cluster-svc
  namespace: default
spec:
  type: ClusterIP
  selector:
    app: nginx-app
  ports:
  - name: http
    port: 80
    targetPort: 80

创建 Deployment:

# 创建 deployment
[root@k8s-master01 ~]# kubectl apply -f cluster-deployment.yaml 
deployment.apps/nginx-demployment created

# 查看 deployment
[root@k8s-master01 ~]# kubectl get deployment
NAME                READY   UP-TO-DATE   AVAILABLE   AGE
nginx-demployment   3/3     3            3           10s

# 查看 pod 及所在 ip
[root@k8s-master01 ~]# kubectl get pod -o wide
NAME                                 READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
nginx-demployment-6d6dbd6df6-4v7mh   1/1     Running   0          15s   10.244.1.16   k8s-node01   <none>           <none>
nginx-demployment-6d6dbd6df6-f87gr   1/1     Running   0          15s   10.244.2.60   k8s-node02   <none>           <none>
nginx-demployment-6d6dbd6df6-ng5w4   1/1     Running   0          15s   10.244.2.61   k8s-node02   <none>           <none>

创建 svc :

# 创建 svc
[root@k8s-master01 ~]# kubectl apply -f cluster-svc.yaml 
service/cluster-svc created

# 查看 svc
[root@k8s-master01 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1     <none>        443/TCP   10d
cluster-svc  ClusterIP   10.108.249.98 <none>        80/TCP    13s

# 查看 ipvs规则。可以看见 svc 所在的 ip 地址代理的是上面的三个 pod 的 ip
[root@k8s-master01 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
......      
TCP  10.108.249.98 rr
  -> 10.244.1.16:80               Masq    1      0          0         
  -> 10.244.2.60:80               Masq    1      0          0         
  -> 10.244.2.61:80               Masq    1      0          0         
......

NodePort 类型

如果设置 type 的值为 "NodePort",Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个 Node 将从该端口(每个 Node 上的同一端口)代理到 Service。该端口将通过 Service 的 spec.ports[*].nodePort 字段被指定。

如果需要指定的端口号,可以配置 nodePort 的值,系统将分配这个端口,否则调用 API 将会失败(比如,需要关心端口冲突的可能性)。

这可以让开发人员自由地安装他们自己的负载均衡器,并配置 Kubernetes 不能完全支持的环境参数,或者直接暴露一个或多个 Node 的 IP 地址。

需要注意的是,Service 将能够通过 :spec.ports[ * ].nodePort 和 spec.clusterIp:spec.ports[ * ].port 而对外可见。

NodePort 资源清单的编写与 ClusterIP 最主要的区别在于 spec.type: NodePort:

apiVersion: v1
kind: Service
metadata:
  name: nodeport-svc
  namespace: default
spec:
  type: NodePort
  selector:
    app: nginx-app
  ports:
  - name: http
    port: 80
    targetPort: 80
    # 绑定到宿主机的31234端口,如果不指定,将随机分配30000-32767
    nodePort: 31234

执行以下操作:

[root@k8s-master01 ~]# kubectl apply -f nodeport-svc.yaml 
service/nodeport-svc created

[root@k8s-master01 ~]# kubectl get svc
NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
cluster-svc               ClusterIP   10.108.249.98   <none>        80/TCP         2m49s
kubernetes                ClusterIP   10.96.0.1       <none>        443/TCP        10d
nodeport-svc              NodePort    10.110.251.86   <none>        80:31234/TCP   17s

# 查看 ipvs 规则
[root@k8s-master01 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.19.150:31234 rr
  -> 10.244.1.16:80               Masq    1      0          0         
  -> 10.244.2.60:80               Masq    1      0          0         
  -> 10.244.2.61:80               Masq    1      0          0         
TCP  10.110.251.86:80 rr
  -> 10.244.1.16:80               Masq    1      0          0         
  -> 10.244.2.60:80               Masq    1      0          0         
  -> 10.244.2.61:80               Masq    1      0          0         
TCP  10.244.0.0:31234 rr
  -> 10.244.1.16:80               Masq    1      0          0         
  -> 10.244.2.60:80               Masq    1      0          0         
  -> 10.244.2.61:80               Masq    1      0          0         
TCP  10.244.0.1:31234 rr
  -> 10.244.1.16:80               Masq    1      0          0         
  -> 10.244.2.60:80               Masq    1      0          0         
  -> 10.244.2.61:80               Masq    1      0          0         
TCP  127.0.0.1:31234 rr
  -> 10.244.1.16:80               Masq    1      0          0         
  -> 10.244.2.60:80               Masq    1      0          0         
  -> 10.244.2.61:80               Masq    1      0          0         
......

接下来就可以在集群外使用任一节点的 :31234 访问到这个服务。

LoadBalancer 类型

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

示例:

kind: Service
apiVersion: v1
metadata:
  name: loadbalancer-svc
spec:
  selector:
    app: nginx-App
  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 值将会被忽略掉。

ExternalName 类型

这种类型的 Service 通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容,例如 www.xixihaha.com。ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。

示例:

apiVersion: v1
kind: Service
metadata:
  name: externalname-svc
  namespace: default
spec:
  type: ExternalName
  externalName: www.xixihaha.com

当查询主机 externalname-svc.defalut.svc.cluster.local(svc-name.namespace.svc.cluster.local)时,集群的 DNS 服务将返回一个值 www.xixihaha.com 的 CNAME 记录。访问这个服务的工作方式和其他的相 同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发。

执行以下操作:

[root@k8s-master01 ~]# kubectl apply -f externelname-svc.yaml 
service/externalname-svc created

[root@k8s-master01 ~]# kubectl get svc
NAME               TYPE           CLUSTER-IP    EXTERNAL-IP        PORT(S)      AGE
externalname-svc   ExternalName   <none>        www.xixihaha.com   <none>       4s
kubernetes         ClusterIP      10.96.0.1     <none>             443/TCP      10d

[root@k8s-master01 ~]# dig -t A externalname-svc.default.svc.cluster.local. @10.244.0.14
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-9.P2.el7 <<>> -t A externalname-svc.default.svc.cluster.local. @10.244.0.14
......
;; ANSWER SECTION:
externalname-svc.default.svc.cluster.local. 30 IN CNAME	www.xixihaha.com.
......

Headless Service

一种特殊的 ClusterIP SVC 类型。

有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP 的值为 "None" 来创建 Headless Service(spec.clusterIP: "None")。

这个选项允许开发人员自由寻找他们自己的方式,从而降低与 Kubernetes 系统的耦合性。 应用仍然可以使用一种自注册的模式和适配器,对其它需要发现机制的系统能够很容易地基于这个 API 来构建。

对这类 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了 selector。

配置 Selector

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

示例:

apiVersion: v1
kind: Service
metadata:
  name: headless-svc
  namespace: default
spec:
  selector:
    app: nginx-app
  clusterIP: "None"
  ports:
  - port: 80
    targetPort: 80

执行以下操作:

[root@k8s-master01 ~]# kubectl apply -f headless-svc.yaml 
service/headless-svc created

# 查看 svc。可以看见 headless-svc 的 IP 是空的
[root@k8s-master01 ~]# kubectl get svc
NAME           TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
headless-svc   ClusterIP   None          <none>        80/TCP    5s
kubernetes     ClusterIP   10.96.0.1     <none>        443/TCP   10d

我们使用 k8s 中的 coredns 组件查看 headless-svc 被解析到的 IP 地址:

# 查看 k8s 组件所在 IP 地址
[root@k8s-master01 ~]# kubectl get pod -n kube-system -o wide
NAME                                   READY   STATUS    RESTARTS   AGE   IP               NODE           NOMINATED NODE   READINESS GATES
coredns-5c98db65d4-8srwz               1/1     Running   8          10d   10.244.0.15      k8s-master01   <none>           <none>
coredns-5c98db65d4-m7tvx               1/1     Running   7          10d   10.244.0.14      k8s-master01   <none>           <none>
......

# 使用 dig 命令查看 svc 解析到的ip地址。yum install -y bind-utils 安装 dig 命令
[root@k8s-master01 ~]# dig -t A headless-svc.default.svc.cluster.local. @10.244.0.14

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-9.P2.el7 <<>> -t A headless-svc.default.svc.cluster.local. @10.244.0.14
......

;; ANSWER SECTION:
headless-svc.default.svc.cluster.local.	30 IN A	10.244.1.16
headless-svc.default.svc.cluster.local.	30 IN A	10.244.2.61
headless-svc.default.svc.cluster.local.	30 IN A	10.244.2.60
......

dig -t A [svc-name].[namespace].svc.cluster.local. @[codedns-ip]

使用 dig 命令从 codedns 组件查看到了 headless-svc 所解析到的 IP 地址。有三个 10.244.1.16、10.244.2.61 和 10.244.2.60。

如果你现在创建一个不使用 spec.clusterIP: "None" 的 svc,使用 codedns 查看,会发现只解析到了一个 IP 上。

不配置 Selector

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

  • ExternalName 类型 Service 的 CNAME 记录
    • 记录:与 Service 共享一个名称的任何 Endpoints,以及所有其它类型

-- end --

# k8s
k8s 控制器介绍
wget无法建立 SSL 连接
  • 文章目录
  • 站点概览
theboyaply

theboyaply

好记性不如烂笔头

184 日志
13 分类
27 标签
Github E-mail
Creative Commons
0%
© 2019 — 2023 theboyaply
由 Halo 强力驱动
|
主题 - NexT.Gemini
湘ICP备19009291号

湘公网安备 43312402001034号