18

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

 3 years ago
source link: https://www.ishells.cn/archives/kubernetes-study-7-service
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

service-example-2cb89ff64b344653a4506aea43a7fb46.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等)的话就实现了一定程度的高可用,如下图

service-nodeport-e46cfb02111d4995925d1705cfc98568.png

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

service-LoadBalancer-eb94eef8bc334244b953504d15062260.jpg

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

service-externalname-6a31dfc8665847298fdb1345cb568f3f.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

service-theory-e0bbc18816f04c30b8418e0297d65c60.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-619798d00c6b40f79b91e584a91fae8b.jpg

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

② iptables代理模式

service-iptables-2eda56ff9f154ebbb9a364f6a37d450f.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-86e270cd2ef94ac98b5d60135003b0ce.jpg

Cluster说明与演示

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

service-cluster-61b4a29a77b34d4d8d0b91c5fb769c9f.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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK