当前位置:   article > 正文

k8s实践(12)--K8s service服务详解

k8s service

k8s实战系列icon-default.png?t=N7T8https://blog.csdn.net/hguisu/category_9999400.html

一、Service原理机制


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

       Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。 这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector实现的。Service 通过标签来选取服务后端,一般配合 Replication Controller 或者 Deployment 来保证后端容器的正常运行。这些匹配标签的 Pod IP 和端口列表组成 endpoints,由 kube-proxy 负责将服务 IP 负载均衡到这些 endpoints 上。

     

1、Service定义服务入口:

即k8s的Service定义了一个服务的访问入口地址,前端的应用通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,来自外部的访问请求被负载均衡到后端的各个容器应用上。

2、Service与pod:

     Service与其后端Pod副本集群之间则是通过Label Selector来实现对接的。而RC的作用相当于是保证Service的服务能力和服务质量始终处于预期的标准。

      通过分析、识别并建模系统中的所有服务为微服务-Kubernetes Service,最终我们的系统由多个提供不同业务能力而又彼此独立的微服务单元所组成,服务之间通过TCP/IP进行通信,从而形成了我们强大而又灵活的弹性网格,拥有了强大的分布式能力、弹性扩展能力、容错能力,与此同时,我们的程序架构也变得简单和直观许多,如图1.15所示。

   

3、Service的负载均衡器kube-proxy

Kubernetes也遵循了上述常规做法,运行在每个Node上的kube-proxy进程其实就是一个智能的软件负载均衡器,它负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。但Kubernetes发明了一种很巧妙又影响深远的设计: Service不是共用一个负载均衡器的IP地址,而是每个Service分配了一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP,这样一来,每个服务就变成了具备唯一IP地址的“通信节点”,服务调用就变成了最基础的TCP网络通信问题.

4、service、Endpoints、 pod关系

Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址,它是根据service配置文件中selector描述产生的。

一个Service由一组Pod组成,这些Pod通过Endpoints暴露出来,Endpoints是实现实际服务的端点集合。换句话说,service和pod之间的联系是通过endpoints实现的。

命名空间级资源,如果endpoints和service是同一个名字,那么就自动关联。

Endpoints表示一个Service对应的所有Pod副本的访问地址。

Node上的Kube-proxy进程获取每个Service的Endpoints,实现Service的负载均衡功能。

Endpoints Controller

Endpoints Controller就是负责生成和维护所有Endpoints对象的控制器,它负责监听Service和对应的Pod副本的变化。

如果检测到Service被删除,则删除和该Service同名的Endpoints对象。

如果检测到新的Service被创建或者修改则根据该Service信息获得相关的Pod列表,然后创建或者更新Service对应的Endpoints对象。
 kubectl get endpoints:

 

5、Cluster IP

我们知道, Pod的Endpoint地址会随着Pod的销毁和重新创建而发生改变,因为新Pod的IP地址与之前旧Pod的不同。而Service一旦创建, Kubernetes就会自动为它分配一个可用的Cluster IP,而且在Service的整个生命周期内,它的Cluster IP不会发生改变。于是,服务发现这个棘手的问题在Kubernetes的架构里也得以轻松解决:只要用Service的Name与Service的Cluster IP地址做一个DNS域名映射即可完美解决问题。现在想想,这真是一个很棒的设计。

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

Service的虚拟IP地址Cluster IP:外部网络无法ping通,只有kubernetes集群内部访问使用,但可以在各个node节点上直接通过ClusterIP:port访问。

kubernetes查询Cluster IP:  kubectl  get service

Cluster IP是一个虚拟的IP,但更像是一个伪造的IP网络,原因有以下几点

Cluster IP仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配P地址
Cluster IP无法被ping,他没有一个“实体网络对象”来响应.
Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备通信的基础,并且他们属于Kubernetes集群这样一个封闭的空间。
在不同Service下的pod节点在集群间相互访问可以通过Cluster IP

二、Service定义和使用


      Service 定义可以基于 POST 方式,请求 apiserver 创建新的实例。一个 Service 在 Kubernetes 中是一个 REST 对象。本文对Service的使用进行详细说明,包括Service的负载均衡、外网访问、DNS服务的搭建、Ingress7层路由机制等。

2.1 yaml格式的Service定义文件的完整内容

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. spec:
  6. selector:
  7. app: MyApp
  8. ports:
  9. - protocol: TCP
  10. port: 80
  11. targetPort: 9376

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

需要注意的是, Service 能够将一个接收端口映射到任意的 targetPort。 默认情况下,targetPort 将被设置为与 port 字段相同的值。 可能更有趣的是,targetPort 可以是一个字符串,引用了 backend Pod 的一个端口的名称。 但是,实际指派给该端口名称的端口号,在每个 backend Pod 中可能并不相同。 对于部署和设计 Service ,这种方式会提供更大的灵活性。 例如,可以在 backend 软件下一个版本中,修改 Pod 暴露的端口,并不会中断客户端的调用。

Kubernetes Service 能够支持 TCP 和 UDP 协议,默认 TCP 协议。

我们看怎么使用:

1、我们定义一个提供web服务的RC:

由两个springboot容器副本组成,每个容器通过containerPort设置提供服务号为9081

apiVersion: v1
kind: ReplicationController
metadata:
  name: webapp
spec:
  replicas: 2
  template:
    metadata:
     name: webapp
     labels:
       app: webapp
    spec:  
      containers:
      - name: springboot-webapp
        image: registry.xxxx.com/springboot:latest
        ports:
        - containerPort: 9081
      imagePullSecrets:
      - name: registry-key-secret

创建该RC:
#kubectl create -f webapp-rc.yaml
获取Pod的IP地址:

直接通过这两个Pod的IP地址和端口号访问sringboot服务:

直接通过Pod的IP地址和端口号可以访问容器内的应用服务,但是Pod的IP地址是不可靠的,例如Pod所在的Node发生故障,Pod将被k8s重新调度到另一台Node。Pod的IP地址将发生变化,更重要的是,如果容器应用本身是分布式的部署方式,通过多个实例共同提供服务,就需要在这些实例的前端设置一个负载均衡器来实现请求的分发。kubernetes中的Service就是设计出来用于解决这些问题的核心组件。

(2)通过kubectl expose命令来创建service

为了让客户端应用能够访问到两个sprintbootPod 实例,需要创建一个Service来提供服务
k8s提供了一种快速的方法,即通过kubectl expose命令来创建:
#kubectl expose rc webapp
查看新创建的Service可以看到系统为它分配了一个虚拟的IP地址(clusterIP),而Service所需的端口号则从Pod中的containerPort复制而来:
[root@bogon ~]# kubectl expose rc webapp
service "webapp" exposed

#kubectl get svc

接下来,我们就可以通过Service的IP地址和Service的端口号访问该Service了:
# curl  192.168.14.242:9081
这里,对Service地址 curl  192.168.14.242:9081的访问被自动负载分发到了后端两个Pod之一。

3)配置文件定义Service

除了使用kubectl expose命令创建Service,我们也可以通过配置文件定义Service,再通过kubectl create命令进行创建。
例如前面的webapp就用,我们可以设置一个Service:
webapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: webapp2
spec:
  ports:
  - port: 9082
    targetPort: 9081
  selector:
    app: webapp
Service定义中的关键字段是ports和selector。
本例中ports定义部分指定了Service所需的虚拟端口号为9082,由于与Pod容器端口号9081不一样,所以需要在通过targetPort来指定后端Pod的端口。
selector定义部分设置的是后端Pod所拥有的label: app=webapp

 curl 192.168.22.2:9082

4)目前kubernetes提供了两种负载分发策略:RoundRobin和SessionAffinity

    RoundRobin:轮询模式,即轮询将请求转发到后端的各个Pod上
    SessionAffinity:基于客户端IP地址进行会话保持的模式,第一次客户端访问后端某个Pod,之后的请求都转发到这个Pod上

默认是RoundRobin模式。

2.2 对Service定义文件中各属性的说明表

2.3 没有 selector 的 Service

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

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

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

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. spec:
  6. ports:
  7. - protocol: TCP
  8. port: 80
  9. targetPort: 9376

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

  1. kind: Endpoints
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. subsets:
  6. - addresses:
  7. - ip: 1.2.3.4
  8. ports:
  9. - port: 9376

注意: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:9376)。

ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。 相反地,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. namespace: prod
  6. spec:
  7. type: ExternalName
  8. externalName: my.database.example.com

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

2.4、service的类型

Kubernetes ServiceTypes 允许指定一个需要的类型的 Service,默认是 ClusterIP 类型。
Type 的取值以及行为如下:

1)ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
2)NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 <NodeIP>:<NodePort>,可以从集群的外部访问一个 NodePort 服务
3)LoadBalancer:使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。
4) ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

2)通过设置nodePort映射到物理机,同时设置Service的类型为NodePort:

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

使用nodePort的缺点:

  1. 每个端口只能是一种服务
  2. 端口范围只能是 30000-32767
  3. 和节点node的 IP 地址紧密耦合。

(2)通过设置LoadBalancer映射到云服务商提供的LoadBalancer地址。

这种用法仅用于在公有云服务提供商的云平台上设置Service的场景。在下面的例子中, status.loadBalancer.ingress.ip设置的146.148.47.155为云服务商提供的负载均衡器的IP地址。对该Service的访问请求将会通过LoadBalancer转发到后端Pod上,负载分发的实现方式则依赖于云服务商提供的LoadBalancer的实现机制。

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4.   name: my-service
  5. spec:
  6.   selector:
  7.     app: MyApp
  8.   ports:
  9.     - protocol: TCP
  10.       port: 80
  11.       targetPort: 9376
  12.      nodePort: 19376
  13.      clusterIP: 10.0.171.239
  14.      loadBalancerIP: 78.11.24.19
  15.      type: LoadBalancer
  16.   status:
  17.     LoadBalancer:
  18. ingress:
  19.     -ip: 146.148.47.155

k8s中有3种IP地址:

    Node IP: Node节点的IP地址,这是集群中每个节点的物理网卡的IP地址;
    Pod IP: Pod的IP地址,这是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络;
    Cluster IP:Service 的IP地址,这也是一个虚拟的IP,但它更像是一个“伪造”的IP地址,因为它没有一个实体网络对象,所以无法响应ping命令。它只能结合Service Port组成一个具体的通信服务端口,单独的Cluster IP不具备TCP/IP通信的基础。在k8s集群之内,Node IP网、Pod IP网与Cluster IP网之间的通信采用的是k8s自己设计的一种编程实现的特殊的路由规则,不同于常见的IP路由实现。

此模式会提供一个集群内部的虚拟IP(与Pod不在同一网段),以供集群内部的Pod之间通信使用。

headless service 需要将 spec.clusterIP 设置成 None。

因为没有ClusterIP,kube-proxy 并不处理此类服务,因为没有load balancing或 proxy 代理设置,在访问服务的时候回返回后端的全部的Pods IP地址,主要用于开发者自己根据pods进行负载均衡器的开发(设置了selector)。

三、Service 服务的VIP 和 Service 网络代理


1、Service 服务的VIP

在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程。运行在每个Node上的kube-proxy进程其实就是一个智能的软件负载均衡器,它会负责把对Service的请求转发到后端的某个Pod实例上并在内部实现服务的负载均衡与会话保持机制。

Service不是共用一个负载均衡器的IP,而是被分配了一个全局唯一的虚拟IP地址,称为Cluster IP。在Service的整个生命周期内,它的Cluster IP不会改变

kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。

在 Kubernetes v1.0 版本,代理完全在 userspace。
在 Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。
从 Kubernetes v1.2 起,默认就是 iptables 代理。
在 Kubernetes v1.0 版本,Service 是 “4层”(TCP/UDP over IP)概念。 在 Kubernetes v1.1 版本,新增了 Ingress API(beta 版),用来表示 “7层”(HTTP)服务。

每当我们在k8s cluster中创建一个service,k8s cluster就会在–service-cluster-ip-range的范围内为service分配一个cluster-ip,比如:

通过 kubectl get ep可以看到对应Endpoints信息,即代理的pod。

另外,也可以将已有的服务以 Service 的形式加入到 Kubernetes 集群中来,只需要在创建 Service 的时候不指定 Label selector,而是在 Service 创建好后手动为其添加 endpoint。

2、service网络代理模式:

拥有三种代理模式:userspace、iptables和ipvs。

现在默认使用iptables,在1.8版本之后增加了ipvs功能。

1)早期 userspace 代理模式

client先请求serviceip,经由iptables转发到kube-proxy上之后再转发到pod上去。这种方式效率比较低。

这种模式,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")。

2) 当前iptables 代理模式

client请求serviceip后会直接转发到pod上。这种模式性能会高很多。kube-proxy就会负责将pod地址生成在node节点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

3)、ipvs代理方式

这种方式是通过内核模块ipvs实现转发,这种效率更高。

3、多端口 Service

很多 Service 需要暴露多个端口。对于这种情况,Kubernetes 支持在 Service 对象中定义多个端口。 当使用多个端口时,必须给出所有的端口的名称,这样 Endpoint 就不会产生歧义,例如:

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. spec:
  6. selector:
  7. app: MyApp
  8. ports:
  9. - name: http
  10. protocol: TCP
  11. port: 80
  12. targetPort: 9376
  13. - name: https
  14. protocol: TCP
  15. port: 443
  16. targetPort: 9377

4、 选择自己的 IP 地址

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

service为何使用vip而不是不使用 round-robin DNS?

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

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

我们尽力阻止用户做那些对他们没有好处的事情,如果很多人都来问这个问题,我们可能会选择实现它。

四、服务发现和DNS


Kubernetes 支持2种基本的服务发现模式 —— 环境变量和 DNS。

1、环境变量

当 Pod 运行在 Node 上,kubelet 会为每个活跃的 Service 添加一组环境变量。 它同时支持 Docker links兼容 变量(查看 makeLinkVariables)、简单的 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT 变量,这里 Service 的名称需大写,横线被转换成下划线。

举个例子,一个名称为 "redis-master" 的 Service 暴露了 TCP 端口 6379,同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:

  1. REDIS_MASTER_SERVICE_HOST=10.0.0.11
  2. REDIS_MASTER_SERVICE_PORT=6379
  3. REDIS_MASTER_PORT=tcp://10.0.0.11:6379
  4. REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
  5. REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
  6. REDIS_MASTER_PORT_6379_TCP_PORT=6379
  7. REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

这意味着需要有顺序的要求 —— Pod 想要访问的任何 Service 必须在 Pod 自己之前被创建,否则这些环境变量就不会被赋值。DNS 并没有这个限制。

2、DNS

一个可选(尽管强烈推荐)集群插件 是 DNS 服务器。 DNS 服务器监视着创建新 Service 的 Kubernetes API,从而为每一个 Service 创建一组 DNS 记录。 如果整个集群的 DNS 一直被启用,那么所有的 Pod 应该能够自动对 Service 进行名称解析。

例如:

Service为:webapp,Namespace 为: "my-ns"。 它在 Kubernetes 集群为 "webapp.my-ns" 创建了一条 DNS 记录。

1)在同一个集群(名称为 "my-ns" 的 Namespace 中)内的 Pod 应该能够简单地通过名称查询找到 "webapp"。

2)在另一个 Namespace 中的 Pod 必须限定名称为 "webapp.my-ns"。 这些名称查询的结果是 Cluster IP。

Kubernetes 也支持对端口名称的 DNS SRV(Service)记录。 如果名称为 "webapp.my-ns" 的 Service 有一个名为 "http" 的 TCP 端口,可以对 "_http._tcp.webapp.my-ns" 执行 DNS SRV 查询,得到 "http" 的端口号。

Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName 类型的 Service 的方式。 更多信息可以查看https://guisu.blog.csdn.net/article/details/93501650

五、集群外部访问服务


k8s集群外如何访问集群内的服务,主要方式有:hostPort或hostNetwork、NodePort、Ingress

1、hostPort或hostNetwork

 hostPort和hostNetwork 放在首位是因为大家很容易忽略它们,它们也可让集群外访问集群内应用,

hostNetwork 用法:

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. labels:
  5. app: nginx
  6. name: nginx-deployment
  7. spec:
  8. replicas: 1
  9. selector:
  10. matchLabels:
  11. app: nginx
  12. template:
  13. metadata:
  14. labels:
  15. app: nginx
  16. spec:
  17. nodeSelector: # node节点选择器
  18. role: master # node节点标签(Label)
  19. hostNetwork: true # 使用node节点网络
  20. containers:
  21. - image: nginx
  22. imagePullPolicy: IfNotPresent
  23. name: nginx
  24. ports:
  25. - containerPort: 8080

重点在和containers平级的hostNetwork: true,表示pod使用宿主机网络,配合nodeSelector,把pod实例化在固定节点,如上,我给mater节点加上标签role: master,通过nodeSelector,nginx就会实例化在master节点,这样就可以通过master节点的ip和8080端口访问这个nginx了。

hostPort用法:

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. labels:
  5. app: nginx
  6. name: nginx-deployment
  7. spec:
  8. replicas: 1
  9. selector:
  10. matchLabels:
  11. app: nginx
  12. template:
  13. metadata:
  14. labels:
  15. app: nginx
  16. spec:
  17. nodeSelector: # node节点选择器
  18. role: master # node节点标签(Label)
  19. containers:
  20. - image: nginx
  21. imagePullPolicy: IfNotPresent
  22. name: nginx
  23. ports:
  24. - containerPort: 8080
  25. hostPort: 80 #重点

hostNetwork相比多了映射能力,可以把容器端口映射为node节点不同端口,hostPort,当然也需要nodeSelector来固定节点,不然每次创建,节点不同,ip也会改变

访问方式:nodeSelector所选节点ip:hostPort, 如上:role=Master标签节点Ip:80

2、NodePort

NodePort是最常见的提供集群外访问的方式之一,该方式使用Service提供集群外访问:

通过设置nodePort映射到物理机,同时设置Service的类型为NodePort:

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

访问方式:集群内任意节点ip加nodePort所配端口号,如上:集群内任一节点ip:30376,即可访问服务了。

使用nodePort的缺点:

  1. 每个端口只能是一种服务
  2. 端口范围只能是 30000-32767
  3. 和节点node的 IP 地址紧密耦合。

在本机可以访问nodePort, 其他服务器无法问题,例如node 172.16.1.23上可以访问curl 172.16.1.23:30018,但是在服务器172.16.1.21上无法访问:


解决的办法是:
cat > /etc/sysctl.d//etc/sysctl.conf <<EOF
net.ipv4.ip_forward=1
EOF
sysctl -p

具体原因:vendor libnetwork @1861587 by sanimej · Pull Request #28257 · moby/moby · GitHub

如果net.ipv4.ip_forward=1参数是由Docker所设置,则iptables的FORWARD会被设置为DORP策略。如果是由用户设置net.ipv4.ip_forward=1参数,则用户可能会进行某些意图需要FORWARD为ACCEPT策略,这时候Docker就不会去修改FORWARD策略。

3、LoadBalancer

通过设置LoadBalancer映射到云服务商提供的LoadBalancer地址。

这种用法仅用于在公有云服务提供商的云平台上设置Service的场景。在下面的例子中, status.loadBalancer.ingress.ip设置的146.148.47.155为云服务商提供的负载均衡器的IP地址。对该Service的访问请求将会通过LoadBalancer转发到后端Pod上,负载分发的实现方式则依赖于云服务商提供的LoadBalancer的实现机制。

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4.   name: my-service
  5. spec:
  6.   selector:
  7.     app: MyApp
  8.   ports:
  9.     - protocol: TCP
  10.       port: 80
  11.       targetPort: 9376
  12.      nodePort: 19376
  13.      clusterIP: 10.0.171.239
  14.      loadBalancerIP: 78.11.24.19
  15.      type: LoadBalancer
  16.   status:
  17.     LoadBalancer:
  18. ingress:
  19.     -ip: 146.148.47.155

4、Ingress

可以简单理解为部署了一个nginx服务,该服务使用hostNetwork或hostPort方式提供集群外访问,再根据配置的路由规则,路由的集群内部各个service。

根据前面对Service的使用说明,我们知道Service的表现形式为IP:Port,即工作在TCP/IP层,而对于基于HTTP的服务来说,不同的URL地址经常对应到不同的后端服务或者虚拟服务器,这些应用层的转发机制仅通过kubernetes的Service机制是无法实现的。kubernetes V1.1版本中新增的Ingress将不同URL的访问请求转发到后端不同的Service,实现HTTP层的业务路由机制。在kubernetes集群中,Ingress的实现需要通过Ingress的定义与Ingress Controller的定义结合起来,才能形成完整的HTTP负载分发功能。

1)、创建Ingress Controller

      使用Nginx来实现一个Ingress Controller,需要实现的基本逻辑如下:
     a)监听apiserver,获取全部ingress的定义
    b)基于ingress的定义,生成Nginx所需的配置文件/etc/nginx/nginx.conf
    c)执行nginx -s relaod命令,重新加载nginx.conf文件,写个脚本。

通过直接下载谷歌提供的nginx-ingress镜像来创建Ingress Controller:
文件nginx-ingress-rc.yaml
apiVersion: v1
kind: ReplicationController
matadata:
  name: nginx-ingress
  labels:
    app: nginx-ingress
spec:
  replicas: 1
  selector:
    app: nginx-ingress
  template:
    metadata:
      labels:
        app: nginx-ingress
    spec:
      containers:
      - image: gcr.io/google_containers/nginx-ingress:0.1
        name: nginx
        ports:
        - containerPort: 80
          hostPort: 80
这里,Nginx应用配置设置了hostPort,即它将容器应用监听的80端口号映射到物理机,以使得客户端应用可以通过URL地址“http://物理机IP:80”来访问该Ingress Controller
#kubectl create -f nginx-ingress-rc.yaml
#kubectl get pods

2)、定义Ingress

为mywebsite.com定义Ingress,设置到后端Service的转发规则:
apiVersion: extensions/vlbeta1
kind: Ingress
metadata:
  name: mywebsite-ingress
spec:
  rules:
  - host: mywebsite.com
    http:
      paths:
      - path: /web
        backend:
          serviceName: webapp
          servicePort: 80

这个Ingress的定义说明对目标http://mywebsite.com/web的访问将被转发到kubernetes的一个Service上 webapp:80

创建该Ingress
#kubectl create -f Ingress.yaml

#kubectl get ingress
NAME |Hosts |Address |Ports |Age
mywebsite-ingress |mywebsite.com |80 |17s

创建后登陆nginx-ingress Pod,查看自动生成的nginx.conf内容

3)访问http://mywebsite.com/web
我们可以通过其他的物理机对其进行访问。通过curl --resolve进行指定
#curl --resolve mywebsite.com:80:192.169.18.3 mywebsite.com/web

3)、使用Ingress 场景

Ingress 可能是暴露服务的最强大方式,但同时也是最复杂的。Ingress 控制器有各种类型,包括 Google Cloud Load BalancerNginxContourIstio,等等。它还有各种插件,比如 cert-manager,它可以为你的服务自动提供 SSL 证书。
如果你想要使用同一个 IP 暴露多个服务,这些服务都是使用相同的七层协议(典型如 HTTP),那么Ingress 就是最有用的。如果你使用本地的 GCP 集成,你只需要为一个负载均衡器付费,且由于 Ingress是“智能”的,你还可以获取各种开箱即用的特性(比如 SSL,认证,路由,等等)。

5、总结各方式利弊

hostPorthostNetwork直接使用节点网络,部署时节点需固定,访问ip也固定(也可以用host),端口为正常端口

nodeport方式部署时不要求固定节点,可通过集群内任一ip进行访问,就是端口为30000以上,很多时候由于公司安全策略导致不能访问。

LoadBalancer依赖于云服务商提供的LoadBalancer的实现机制。

ingress需要额外安装ingress模块,配置路由规则,且仅能通过所配置域名访问,配置好域名后,可以直接对外提供服务,和传统的nginx作用类似

六、Headless Service


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

2、和普通Service相比:

     对这类Headless Service并不会分配Cluster IP,kube-proxy不会处理它们,而且平台也不会为它们进行负载均衡和路由。

     它会给一个集群内部的每个成员提供一个唯一的DNS域名来作为每个成员的网络标识,集群内部成员之间使用域名通信。

3、无头服务管理的域名是如下的格式:$(service_name).$(k8s_namespace).svc.cluster.local。其中的"cluster.local"是集群的域名,除非做了配置,否则集群域名默认就是cluster.local

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

     因为没有load balancing或 proxy 代理设置,在访问服务的时候回返回后端的全部的Pods IP地址,主要用于开发者自己根据pods进行负载均衡器的开发(设置了selector)。

     DNS如何实现自动配置,依赖于Service时候定义了selector。
 

(1)编写headless service配置清单
# vim myapp-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-headless
  namespace: default
spec:
  selector:
    app: myapp
    release: canary
  clusterIP: "None" #headless的clusterIP值为None
  ports:
  - port: 80
    targetPort: 80

(2)创建headless service
# kubectl apply -f myapp-svc-headless.yaml
[root@k8s-master mainfests]# kubectl get svc
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.96.0.1        <none>        443/TCP        36d
myapp            NodePort    10.101.245.119   <none>        80:30080/TCP   1h
myapp-headless   ClusterIP   None             <none>        80/TCP         5s
redis            ClusterIP   10.107.238.182   <none>        6379/TCP       2h

(3)使用coredns进行解析验证
[root@k8s-master mainfests]# dig -t A myapp-headless.default.svc.cluster.local. @10.96.0.10

; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> -t A myapp-headless.default.svc.cluster.local. @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62028
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp-headless.default.svc.cluster.local. IN A

;; ANSWER SECTION:
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.1.18
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.1.19
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.15
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.16
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.17

;; Query time: 4 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Thu Sep 27 04:27:15 EDT 2018
;; MSG SIZE  rcvd: 349

[root@k8s-master mainfests]# kubectl get svc -n kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP   36d

[root@k8s-master mainfests]# kubectl get pods -o wide -l app=myapp
NAME                            READY     STATUS    RESTARTS   AGE       IP            NODE
myapp-deploy-69b47bc96d-4hxxw   1/1       Running   0          1h        10.244.1.18   k8s-node01
myapp-deploy-69b47bc96d-95bc4   1/1       Running   0          1h        10.244.2.16   k8s-node02
myapp-deploy-69b47bc96d-hwbzt   1/1       Running   0          1h        10.244.1.19   k8s-node01
myapp-deploy-69b47bc96d-pjv74   1/1       Running   0          1h        10.244.2.15   k8s-node02
myapp-deploy-69b47bc96d-rf7bs   1/1       Running   0          1h        10.244.2.17   k8s-node02

(4)对比含有ClusterIP的service解析
[root@k8s-master mainfests]# dig -t A myapp.default.svc.cluster.local. @10.96.0.10

; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> -t A myapp.default.svc.cluster.local. @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50445
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp.default.svc.cluster.local. IN    A

;; ANSWER SECTION:
myapp.default.svc.cluster.local. 5 IN    A    10.101.245.119

;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Thu Sep 27 04:31:16 EDT 2018
;; MSG SIZE  rcvd: 107

# kubectl get svc
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.96.0.1        <none>        443/TCP        36d
myapp            NodePort    10.101.245.119   <none>        80:30080/TCP   1h
myapp-headless   ClusterIP   None             <none>        80/TCP         11m
redis            ClusterIP   10.107.238.182   <none>        6379/TCP       2h

从以上的演示可以看到对比普通的service和headless service,headless service做dns解析是直接解析到pod的,而servcie是解析到ClusterIP的,那么headless有什么用呢?这将在statefulset中应用到,这里暂时仅仅做了解什么是headless service和创建方法。
 

七、VIP注意事项


对很多想使用 Service 的人来说,前面的信息应该足够了。 然而,有很多内部原理性的内容,还是值去理解的。

避免冲突

Kubernetes 最主要的哲学之一,是用户不应该暴露那些能够导致他们操作失败、但又不是他们的过错的场景。 这种场景下,让我们来看一下网络端口 —— 用户不应该必须选择一个端口号,而且该端口还有可能与其他用户的冲突。 这就是说,在彼此隔离状态下仍然会出现失败。

为了使用户能够为他们的 Service 选择一个端口号,我们必须确保不能有2个 Service 发生冲突。 我们可以通过为每个 Service 分配它们自己的 IP 地址来实现。

为了保证每个 Service 被分配到一个唯一的 IP,需要一个内部的分配器能够原子地更新 etcd 中的一个全局分配映射表,这个更新操作要先于创建每一个 Service。 为了使 Service能够获取到 IP,这个映射表对象必须在注册中心存在,否则创建 Service 将会失败,指示一个 IP 不能被分配。 一个后台 Controller 的职责是创建映射表(从 Kubernetes 的旧版本迁移过来,旧版本中是通过在内存中加锁的方式实现),并检查由于管理员干预和清除任意 IP 造成的不合理分配,这些 IP 被分配了但当前没有 Service 使用它们。

IP 和 VIP

不像 Pod 的 IP 地址,它实际路由到一个固定的目的地,Service 的 IP 实际上不能通过单个主机来进行应答。 相反,我们使用 iptables(Linux 中的数据包处理逻辑)来定义一个虚拟IP地址(VIP),它可以根据需要透明地进行重定向。 当客户端连接到 VIP 时,它们的流量会自动地传输到一个合适的 Endpoint。 环境变量和 DNS,实际上会根据 Service 的 VIP 和端口来进行填充。

Userspace

作为一个例子,考虑前面提到的图片处理应用程序。 当创建 backend Service 时,Kubernetes master 会给它指派一个虚拟 IP 地址,比如 10.0.0.1。 假设 Service 的端口是 1234,该 Service 会被集群中所有的 kube-proxy 实例观察到。 当代理看到一个新的 Service, 它会打开一个新的端口,建立一个从该 VIP 重定向到新端口的 iptables,并开始接收请求连接。

当一个客户端连接到一个 VIP,iptables 规则开始起作用,它会重定向该数据包到 Service代理 的端口。 Service代理 选择一个 backend,并将客户端的流量代理到 backend 上。

这意味着 Service 的所有者能够选择任何他们想使用的端口,而不存在冲突的风险。 客户端可以简单地连接到一个 IP 和端口,而不需要知道实际访问了哪些 Pod。

Iptables

再次考虑前面提到的图片处理应用程序。 当创建 backend Service 时,Kubernetes master 会给它指派一个虚拟 IP 地址,比如 10.0.0.1。 假设 Service 的端口是 1234,该 Service会被集群中所有的 kube-proxy 实例观察到。 当代理看到一个新的 Service, 它会安装一系列的 iptables 规则,从 VIP 重定向到 per-Service 规则。 该 per-Service 规则连接到 per-Endpoint 规则,该 per-Endpoint 规则会重定向(目标 NAT)到 backend。

当一个客户端连接到一个 VIP,iptables 规则开始起作用。一个 backend 会被选择(或者根据会话亲和性,或者随机),数据包被重定向到这个 backend。 不像 userspace 代理,数据包从来不拷贝到用户空间,kube-proxy 不是必须为该 VIP 工作而运行,并且客户端 IP 是不可更改的。 当流量打到 Node 的端口上,或通过负载均衡器,会执行相同的基本流程,但是在那些案例中客户端 IP 是可以更改的。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/128325
推荐阅读
相关标签
  

闽ICP备14008679号