kubernetes学习笔记七(服务发现)

kubernetes学习笔记七(服务发现)

Scroll Down

Tips:

自己初学的时候,有时候会搞混 Deployment 、SVC 匹配标签的字段,这里标记一下

Deployment 匹配 pod 标签的字段是matchLabels
SVC 匹配 pod 标签的字段是selector

Service概念

Kubernetes以Pod作为应用部署的最小单位。kubernetes会根据Pod的声明对其进行调度,包括创建、销毁、迁移、水平伸缩等,因此Pod 的IP地址不是固定的,不方便直接采用Pod 的 IP 对服务进行访问。

为解决该问题,Kubernetes提供了Service资源,Service对提供同一个服务的多个Pod进行聚合。一个Service提供一个虚拟的Cluster IP,后端对应一个或者多个提供服务的Pod。在集群中访问该Service时,采用Cluster IP即可,Kube-proxy负责将发送到Cluster IP的请求转发到后端的Pod上。

Kube-proxy是一个运行在每个节点上的go应用程序,支持三种工作模式:userspace,iptables,ipvs

Kubernetes Service 定义了一个pod的逻辑分组,一种可以访问Pod组的策略--通常被称为微服务。Service通过指定的一组Label Selector而匹配到这组Pod

serviceexample.jpg

如图所示,Deployment定义了3个副本数目的一组应用程序Pod,这组Pod有着一组指定Label Selector。同时还存在一个Service通过特定的标签app=webapp、role=frontend所匹配到这组Frontend v1 Pod,所以匹配到的Pod的信息就会被写入到svc当中。当客户端访问的时候,就通过SVC以RR的调度方式( svc到pod的调度方式有且仅有一个RR算法 )来访问到这组Pod中的其中一个Pod。而且如果后期SVC匹配的Pod意外死掉了,Deployment根据副本数目创建出来的新的Pod就会被自动更新到SVC的负载策略中(同时也就是说只要被SVC的label Selector匹配的到的Pod,不管是意外退出、滚动更新、水平扩容,都会被更新到svc的负载策略中,并不会对上一层的服务造成影响)。

Service能够提供负载均衡的能力,但是在使用上有以下限制:

默认只提供4 层负载均衡能力,而没有 7 层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的。

( 后期可以通过 Ingress添加7层的负载均衡能力,后面详细讲 )

Service类型

ClusterIp:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP

NodePort( 常见的暴露k8s内部服务的方式 ):在 ClusterIP 的基础上为 Service 在每台机器节点上绑定一个端口,这样就可以通过: NodePort 来访问该服务。访问节点的 IP + 特定端口就会访问到SVC绑定的端口,进而以rr的调度算法访问到Pod的端口上 ,并且每一台机器节点上都是这样的流程,在集群多节点前加一个负载均衡器(nginx/LVS+keeplived等)的话就实现了一定程度的高可用,如下图

servicenodeport.png

LoadBalancer:在NodePort的基础上,借助云服务商(Cloud Provider) 创建一个外部负载均衡器,并将请求转发到 NodePort ( LoadBalancer 与 Nodeport( 有负载均衡器的情形下 )的区别就是,LoadBalancer 的负载均衡器是云服务商所提供的 )

serviceLoadBalancer.jpg

ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。在这种模式下,k8s集群内如果想访问外部的服务,只用去访问svc即可,外部服务后期如果更改了IP,只用更新svc的配置即可。只有 kubernetes 1.7 或更高版本的 kube-dns 才支持

serviceexternalname.jpg

SVC的实现原理:

svc 的实现需要这么几个组件的配合,首先ApiServer通过kube-proxy来负责监听所有的Service、Endpoint端点信息,而kube-proxy则监听Service 所指定Label Selector匹配到的Pod的信息,将其写入到 svc 中,而其实 SVC 就是通过 iptables 来实现的,也就是把kube-proxy监测到pod的信息写入到iptables 规则中。当客户端访问SVC的时候,其实访问的就是iptables规则,然后iptables规则就根据RR算法将流量调度到后端的Pod上。

整个过程其实就是:apiserver通过监控kube-proxy实现服务的更新、端点的维护,kube-proxy将规则写入到iptables,而iptables实现将客户端流量调度到具体的pod

servicetheory.jpg

VIP 和 Service 代理分类简介

在 Kubernetes 集群中,每个worker Node 都会运行一个kube-proxy进程,kube-proxy负责为Service实现了一个VIP(虚拟 IP)的形式,而不是ExternalName( k8s内部访问集群外部服务的形式 )的形式。在 Kubernetes v1.0 版本,kube-proxy 代理只有 userspace 模式。在Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。从 Kubernetes v1.2 起,默认就是iptables 代理。在 Kubernetes v1.8.0-beta.0 中,添加了 ipvs 代理

在 Kubernetes 1.14 版本开始默认使用ipvs 代理

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

Service代理模式的分类说明

① userspace代理模式

service-userspace

userspace代理模式下,client pod如果想要访问Server Pod( 就好比LNMP中的php想要访问mysql ),首先需要访问到 Service IP,再由实际的 iptables 访问到kube-proxy,再由kube-proxy访问到具体的Server pod。这种代理模式下,kube-proxy的压力是比较大的。

② iptables代理模式

serviceiptables.jpg

在 iptables 的这种模式下,不需要kube-proxy去进行流量的调度了,client pod 的访问,直接就被 iptables 经过RR算法调度到某个server pod上

③ ipvs代理模式

什么是IPVS?

IPVS (IP Virtual Server,IP虚拟服务器)是基于Netfilter的、作为linux内核的一部分实现传输层负载均衡的技术,通常称为第4层LAN交换。

IPVS集成在LVS(Linux Virtual Server)中,它在主机中运行,并在真实服务器集群前充当负载均衡器。IPVS可以将对TCP/UDP服务的请求转发给后端的真实服务器,并使真实服务器的服务在单个IP地址上显示为虚拟服务。因此IPVS天然支持Kubernetes Service。

由于ipvs 无法提供包过滤、SNAT、masquared(伪装)等功能。因此在某些场景(如Node Port的实现)下还是要与iptables搭配使用,ipvs 将使用ipset来存储需要DROP或masquared的流量的源或目标地址,以确保 iptables 规则的数量是恒定的。假设要禁止上万个IP访问我们的服务器,则用iptables的话,就需要一条一条地添加规则,会在iptables中生成大量的规则;但是使用ipset的话,只需要将相关的IP地址(网段)加入到ipset集合中即可,这样只需要设置少量的iptables规则即可实现目标。

为什么选择IPVS

随着kubernetes使用量的增长,其资源的可扩展性变得越来越重要。特别是对于使用kubernetes运行大型工作负载的开发人员或者公司来说,service的可扩展性至关重要。

kube-proxy是为service构建路由规则的模块,之前依赖iptables来实现主要service类型的支持,比如(ClusterIP和NodePort)。但是iptables很难支持上万级的service,因为iptables纯粹是为防火墙而设计的,并且底层数据结构是内核规则的列表。

kubernetes早在1.6版本就已经有能力支持5000多节点,这样基于iptables的kube-proxy就成为集群扩容到5000节点的瓶颈。举例来说,如果在一个5000节点的集群,我们创建2000个service,并且每个service有10个pod,那么我们就会在每个节点上有至少20000条iptables规则,这会导致内核非常繁忙。

基于IPVS的集群内负载均衡就可以完美的解决这个问题。IPVS是专门为负载均衡设计的,并且底层使用哈希表这种非常高效的数据结构,几乎可以允许无限扩容

IPVS vs. Iptables区别

IPVS模式和IPTABLES模式之间的差异如下:

  • IPVS为大型集群提供了更好的可扩展性和性能。(规则的存储方式使用的数据结构更高效)
  • IPVS支持比iptables更复杂的负载平衡算法(最小负载,最少连接,位置,加权等)。
  • IPVS支持服务器健康检查和连接重试等。

此外,ipvs 为负载均衡算法提供了更多选项,例如:

✦ rr 轮询调度

✦ lc最小连接数

✦ dh目标哈希

✦ sh源哈希

✦ sed最短期望延迟

✦ nq不排队调度

注意:ipvs模式假定在运行kube-proxy之前已经在节点上都安装了IPVS内核模块,当kube-proxy以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了IPVS模块,如果未安装,则kube-proxy回退iptables代理模式

service-ipvs

Cluster说明与演示

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

servicecluster.png

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

apiserver 用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求后将数据存储到etcd中

kube-proxy kubernetes的每个woker节点中都有一个kube-porxy的进程,这个进程负责感知service,pod的变化,并将变化的信息写入本地的iptables规则中

iptables/ipvs 使用NAT等技术将virtualIP的流量转至endpoint中

1、apiserver 先将信息写入到 etcd数据库中, kube-proxy检测 etcd数据库中信息的变化,得到变化的信息后会将信息写入到本地(每一个woker节点都会有自己的kube-proxy进程,所以说是本地)的 iptables/ipvs/userspace中

clusterIP 演示:

apiVersion: apps/v1
kind: Deployment
metadata:  
  name: myapp-deploy
  namespace: default
spec:  
  replicas: 3 
  selector: 
    matchLabels: 
      app: myapp   
      release: stabel 
  template:   
    metadata:   
      labels:      
        app: myapp   
        release: stabel   
        env: test  
    spec:    
      containers:    
      - name: myapp
        image: wangyanglinux/myapp:v2 
        imagePullPolicy: IfNotPresent   
        ports:    
        - name: http      
          containerPort: 80

# 创建完deployment之后,是可以直接通过pod的ip进行集群内访问的
# 但是这样的方式太过繁琐,且如果pod意外停止,新创建的Pod的IP的也是会发生改变的,所以接下来创建一个svc
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  type: ClusterIP
  selector:
    app: myapp
    release: stabel
  ports:
  - name: http
    port: 80
    targetPort: 80
# 创建完service之后,可以结合本地的环境,查看是否有相应的转发规则
# ipvsadm -Ln / iptables -t nat -nvL 
# 如果创建的 service 通过 selector 设定的标签与后端的pod关联起来了,就可以通过访问svc直接访问后端的pod了

Headless Service:

有时不需要或不想要负载均衡以及单独的 Service IP ,遇到这种情况,可以通过指定 ClusterIP(spec.clusterIP) 的值为 “None” 来创建 Headless Service 。这类 Service 并不会分配 Cluster IP, kube-proxy 不会处理它们,而且平台也不会为它们指定负载均衡和路由

apiVersion: v1
kind: Service
metadata:  
  name: myapp-headless
  namespace: default
spec:  
  selector: 
    app: myapp  
  clusterIP: "None" 
  ports: 
  - port: 80 
    targetPort: 80
# svc创建之后会被写入到coreDNS中,写入的格式就是  " SVC名称.名称空间.域名. "
# 域名默认是 " svc.cluster.local. "
# 然后我们就可以通过下面的方式来访问Headless SVC
# dig -t a SVC名称.名称空间.域名. @dns

一个Pod可以对应多个svc,而一个svc也可以对应多个pod,也就是说它们之间的对应关系是n对m的

NodePort:

nodePort 的原理在于在 node 上开了一个端口,将向该端口的流量导入到 kube-proxy,然后由 kube-proxy 进一步到给对应的 pod

apiVersion: v1
kind: Service
metadata:  
  name: myapp  
  namespace: default
spec:  
  type: NodePort  
  selector:    
    app: myapp    
    release: stabel  
  ports:  
  - name: http
    port: 80 
    targetPort: 80

查询流程:

# iptables -t nat -nvL

LoadBanlance:

客户端只用访问到云供应商的负载均衡器,流量从该负载均衡器指向k8s集群的NodePort(worker node),由NodePort指向后端的Pod

ExternalName:

externalName SVC是比较特殊的不使用Label Seclector的一个SVC

    这个模式是为了将外部的服务引入k8s集群内部,比如将外部读写分离的mysql集群引入集群内部。首先需要知道的是,每创建一个svc就会在k8s的coreDNS中添加一条解析记录,该值格式为svc_name.namespace_name.svc.cluster.local.,而Externalname SVC的工作流程是,当客户端访问到 svc_name.namespace_name.svc.cluster.local.这条解析的时候,流量会被导向yaml资源清单中定义的externalName上,从而将流量指向集群外对应的服务至上。

externalName其实就相当于在coreDns中加了一个Cname解析

问:

集群外的服务如何以externalName指定的字段向k8s集群提供服务的?

传统svc仅支持四层代理,并不支持七层代理,如果要用到七层代理,那就需要用到ingress

Ingress负载网络:(个人这里掌握不是特别好,后期需要特别看一下)

    在通常情况下,service和pod的IP仅可在集群内部访问。集群外部的请求需要通过负载均衡转发到service在节点暴露的NodePort上,然后再由kube-proxy将其转发给相关的Pod。而Ingress就是为进入集群的请求提供路由规则的集合。

    Ingress可以给service提供集群外部访问的URL、负载均衡、SSL终止、HTTP路由等。为了配置这些Ingress规则,集群管理员需要部署一个Ingresscontroller,它监昕Ingress和service的变化,并根据规则配置负载均衡并提供访问入口。

    为什么需要Ingress,因为在对外访问的时候,NodePort类型需要在外部搭建额外的负载均衡,而LoadBalancer要求kubernetes必须跑在特定的云服务提供商上面。

    Ingress的实现方案有很多,例如Ingress-nginx、Ingress-Haproxy等

    定义一个Ingress如下:

apiVersion: extensions/vlbetal 
kind: Ingress
metadata: 
  name: test-ingress
spec: 
  rules: 
  - http: 
      paths: 
      - path: /testpath
        backend: 
          serviceName: test
          servicePort: 80 

每个Ingress都需要配置rules,目前Kubernetes仅支持http规则。上面的示例表示请求/testpath时转发到服务test的80端口中。根据IngressSpec配置的不同,Ingress可以分为以下几种类型.

1、单服务Ingress

单服务Ingress即该Ingress仅指定一个没有任何规则的后端服务

apiVersion: extensions/vibetal
kind: Ingress
metadata: 
  name: test-ingress 
spec: 
  backend:
    serviceName: testsvc 
    servicePort: 80

单服务还可以通过设置Service.Type=NodePort或者Service.Type=LoadBalancer来对外暴露

2、多服务Ingress

路由到多服务的Ingress即根据请求路径的不同转发到不同的后端服务上,比如可以通过下面的Ingress来定义:

apiVersion: extensions/vlbetal
kind: Ingress 
metadata: 
  name: test 
spec: 
  rules:
  - host: foo.bar.com 
    http: 
      paths: 
      - path: /foo 
        backend: 
          serviceName: sl 
          servicePort: 80 
      - path: /bar
        backend: 
          serviceName: s2 
          servicePort: 80

上面例子中,如果访问的是/foo,则路由转发到sl服务,如果是/bar则转发到s2服务

3、虚拟主机Ingress

虚拟主机Ingress即根据名字的不同转发到不同的后端服务上,而它们共用同一个IP地址,个基于Hostheader路由请求的Ingress如下:

aiVersion: extensions/vlbetal 
kind: Ingress 
metadata: 
  name: test
spec: 
  rules:
  - host: foo.bar.com
    http: 
      paths: 
      - backend:
          serviceName: sl
          servicePort: 80
  - host: bar.foe.com 
    http: 
      paths: 
      - backend: 
          serviceName: s2
          servicePort: 8

根据不同的域名路由到不同的后端服务

4、更新Ingress

可以通过kubectled工ting name的方法来更新Ingress

# kubectl edit ing Ingress名称

这会使用编辑器打开一个已有Ingress的yam!定义文件,修改并保存就会将其更新到KubemetesAPI server,进而触发IngressController重新配置负载均衡

spec: 
  rules:
  - host: foe.bar.com
    http:
      paths:
      - backend:
          serviceName: sl 
          servicePort: 80
        path: /foo
  - host: bar.baz.com
    http:
      paths: 
      - backend:
          serviceName: s2
          servicePort: 80
        path: /foo

当然,也可以使用kubectlreplace - f new ingress.yaml来更新

Ingress HTTP代理访问
deployment、Service、Ingress Yaml文件

apiVersion: extensions/v1beta1
kind: Deployment
metadata:  
  name: nginx-dm
spec:  
  replicas: 2
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
        - name: nginx
          image: wangyanglinux/myapp:v1
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  selector:
    name: nginx
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-test
spec:
  rules:
    - host: www1.test.com
      http:
        paths:
        - path: /
          backend:
            serviceName: nginx-svc
            servicePort: 80

Ingress HTTPS代理访问
创建证书、以及cert存储方式

# openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj"/CN=nginxsvc/O=nginxsvc"
# kubectl create secret tls tls-secret --key tls.key --cert tls.crt

deployment、Service、Ingress Yaml文件:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-test
spec:
  tls:
    - hosts:
      - foo.bar.com
      secretName: tls-secret
  rules:
    - host: foo.bar.com
      http:
        paths:
        - path: /
          backend:
            serviceName: nginx-svc
            servicePort: 80

Nginx进行BasicAuth

# yum -y install httpd
# htpasswd -c auth foo
# kubectl create secret generic basic-auth --from-file=auth
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-with-auth
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'
spec:
  rules:
  - host: foo2.bar.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-svc
          servicePort: 80