K8s Service

作者: AlienPaul | 来源:发表于2020-01-15 17:00 被阅读0次

什么是service

pod不是永恒存在的,pod会随时被创建,同时pod也会被随时销毁(运行出错,超过资源限制等原因)。pod不可能被复活,因此pod被销毁和重建之后的IP地址是不同的。
其他依赖该pod的服务不可能去维护这些pod的IP地址信息。这里我们引入的service,service保存了我们如何访问一组pod的方式。这一组pod通过pod selector来确定。无论这些pod怎样创建和销毁,他们的label不变。service可以动态维护这些pod的真实IP(通过endpoint对象)。所以说,service解耦了pod和依赖这些pod的应用。

定义一个service

创建service的描述文件:

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

这里我们定义了一个service的描述文件。service的名字为my-service。service指向的pod需要具有app: MyApp标签。可以使用80端口访问每个pod的9376端口。这个service拥有一个cluster IP。这个IP只能在kubernetes集群内部访问。

具有selector的service在创建的时候会自动创建一个同名的endpoint。endpoint维护了具有这些标签的一组pod的真正的IP。如果这些pod的IP发生变化,endpoint中的IP也会随着变化。

没有pod selector的service

如下情况我们需要使用没有pod selector的service:

  • 使用集群外部的数据库
  • 要创建的service指向位于另一个namespace,或者是另一个kubernetes集群的service

编写描述文件:

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

只有这样是不够的,service需要有一个对应的endpoint对象。创建没有selector的service并不会随之创建一个endpoint。这时候我们需要手工创建endpoint。endpoint的描述文件如下:

apiVersion: v1
kind: Endpoints
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 192.0.2.42
    ports:
      - port: 9376

service proxy

service使用kube-proxy实现

不使用轮询DNS方式的原因

  • 不少DNS的实现不支持TTL(Time To Live)。无法在pod IP发生变化的时候及时改变缓存。
  • 一部分应用在DNS查询之后便永久保存,再也不会更新。
  • 即便是支持DNS再次解析,我们需要降低DNS的TTL。越低的TTL会导致DNS解析服务的频繁运行,增加系统的负载。

user space proxy 模式

kube-proxy 负责监视service和endpoint的创建和删除。对于每个service,在本地节点上随机开启一个端口(代理端口)。任何连接到该端口的连接会被代理到service后端一组pod中的一个。kube-proxy使用SessionAffinity配置项来决定使用哪一个后端pod。

user space模式使用iptables规则,把目标为service的cluster IP和端口的请求发送到代理端口。

iptables proxy 模式

iptables使用规则配置,将访问service的cluster IP和port的连接重定向到某一个后端pod。默认来说,kube-proxy随机选择后端pod提供服务。

iptables模式系统开销较小。

如果连接到的第一个被选定的pod没有响应,此连接会失败。这个和userspace模式不同。userspace模式中,如果连接到第一个pod失败,系统会自动重试连接另一个后端pod。

可以使用readness probe来确保pod是否正常工作。如果某个podreadness probe 失败,这个pod会从endpoint中移除,网络请求不会再转发到这个pod中,直到readness probe恢复到success状态。

IPVS proxy 模式

ipvs 模式kube-proxy使用netlink接口创建IPVS规则。IPVS基于钩子函数,使用内核空间的hash表作为底层数据结构。因此IPVS相比iptables具有更低的延迟,吞吐量更高。同时同步代理规则的时候也更快。

IPVS重定向网络通信具有如下策略:

  • rr: round-robin 轮询
  • lc: least connection (smallest number of open connections) 优先连接数最少
  • dh: destination hashing 目标hash
  • sh: source hashing 源hash
  • sed: shortest expected delay 延迟最小
  • nq: never queue 不排队

如果需要相同客户端的请求被重定向到相同的pod,需要配置service.spec.sessionAffinityClientIP(默认为None)。我们还可以设置pod和client的最大粘性时间service.spec.sessionAffinityConfig.clientIP.timeoutSeconds。默认为10800,即3小时。如果3小时内client没有访问pod,那么该client下次访问service的时候将重新指定新的pod,原先的session会失效。

多端口服务

service可以暴露多个port。如果使用多个port,必须为每一个port设置name。描述文件如下所示:

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

显式指定service的IP

可以设定spec.clusterIP配置项。但是IP必须位于API server的service-cluster-ip-range CIDR范围中。

service发现

环境变量方式

Kubernetes自动为每一个运行的pod中加入各个service环境变量。这些service的环境变量的格式为

  • {SVCNAME}_SERVICE_HOST
  • {SVCNAME}_SERVICE_PORT

以redis-master服务为例,对应的环境变量为:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

注意:如果pod需要使用环境变量的方式访问service,一定要在创建pod之前创建service。否则pod中将没有这个service的环境变量。如果使用DNS方式的话不需要担心这个创建顺序问题。

DNS方式

集群的DNS服务,例如CoreDNS会监听service,创建service对应的DNS入口。

如果在命名空间my-ns定义了一个服务my-service,自动创建的DNS入口为my-service.my-ns。集群内任何pod可以通过此DNS访问这个服务。如果是和该服务位于同一个命名空间的pod,可以省略DNS中的命名空间部分,使用my-service这个DNS入口访问my-service服务。

DNS支持命名端口的方式。如果一个端口命名为http,使用的是TCP协议,可以通过_http._tcp.my-service.my-ns这条DNS解析到它的IP和端口号。

Headless Service

特点:

  1. 禁用掉了service的负载均衡功能。
  2. 没有指定service的clusterIP。
  3. service不使用kube-proxy代理。

适用场景:

  1. 需要不经过代理直接访问pod。
  2. 使用服务发现功能,例如部署zookeeper等。

配置方式:
设置spec.clusterIPNone

DNS自动配置的方式和是否配置了pod selector有关。

  • 配置了pod selector:endpoints会被创建,DNS直接指向后台pod的IP
  • 没有配置pod selector:endpoints不会被创建。DNS服务会查找: 1. CNAME记录(ExternalName-type Services)2. 和service同名的endpoints

公开service(允许service在集群外访问)

通过设置服务类型(ServiceType)来实现。
service type有如下类型:

  • clusterIP:一个只有在集群内部可访问的虚拟的IP。这个是Service Type的默认值。
  • NodePort:使用每个节点的一个静态端口来访问service。集群外可使用<NodeIP>:<NodePort>形式访问。
  • LoadBalancer:负载均衡器,使用云提供商的负载均衡器访问服务。
  • ExternalName:映射为一个自定义DNS名称,适用于访问外部服务。

除此之外还可以使用Ingress方式,但是Ingress不是ServiceType。

NodePort

NodePort范围从--service-node-port-rangeflag中随机选择(默认为30000-32767)。分配的端口号在.spec.ports[*].nodePort字段中。

可以使用kube-proxy的--nodeport-addresses配置指定nodePort IP的范围。

可以通过指定nodePort字段的内容,来使得NodePort使用一个固定的IP。

NodePort的使用例子:

apiVersion: v1
kind: Service
metadata:
    name: kubia-nodeport
spec:
    type: NodePort
    ports:
        - port: 80
          targetPort: 8080
          nodePort: 30123
    selector:
        app: kubia

LoadBalancer

自己部署的集群中无LoadBalancer,此部分省略。

ExternalName

映射service到一个DNS名称。

apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

External IPs

使用外部IP访问服务。要求可以通过外部IP访问到集群节点。

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

Service 使用Session Affinity

session affinity,同一个client发出的请求会发送给同一个pod处理。配置方法如下:

apiVersion: v1
kind: Service
spec:
    sessionAffinity: ClientIP
    ...

使用命名端口

可以在Pod中为端口命令,在service中使用。

步骤如下所示:

  1. 在pod中给端口命名
kind: Pod
spec:
    containers:
    - name: kubia
      ports:
      - name: http
        containerPort: 8080
      - name: https
        containerPort: 8443
  1. 创建service时使用命名端口(targetPort中使用)
apiVersion: v1
kind: Service
spec:
    ports:
    - name: http
      port: 80
      targetPort: http
    - name: https
      port: 443
      targetPort: https

Service关联k8s集群外部的服务

把集群外部的某一服务的入口做成k8s的endpoint。创建service的时候如果不指定label selector,endpoints就不会自动创建。

下面举一个例子。我们需要在k8s集群中访问集群外部11.11.11.11和22.22.22.22的80端口这个服务。

这里我们需要手工创建service和endpoint:

apiVersion: v1
kind: Service
metadata:
    name: external-service
spec:
    ports:
    - port: 80
apiVersion: v1
kind: Endpoints
metadata:
    name: external-service
subsets:
    - addresses:
        - ip: 11.11.11.11
        - ip: 22.22.22.22
    ports:
        - port: 80

完成之后,集群内部可以通过访问external-service来实现访问集群外部11.11.11.1122.22.22.22

减少网络连接的hop数量

可以通过配置service,只把连接重定向到接收这个连接的node上运行的pod。

方法如下:

spec:
    externalTrafficPolicy: Local
    ...

缺点:如果这个node上没有运行该service对应的pod,连接会挂起。

相关文章

网友评论

    本文标题:K8s Service

    本文链接:https://www.haomeiwen.com/subject/gxngactx.html