Kubernetes 系统化学习之服务发现篇(四)

Kubernetes 中为了实现服务实例间的负载均衡和不同服务间的服务发现,创造了 Service 对象,同时又为从集群外部访问集群创建了 Ingress 对象

1. Service

Pod 是有生命周期的,可以被创建且销毁之后不会再启动。而使用 Deployment 来运行您的应用程序,则它可以动态创建和销毁 Pod。就之前学习的知识,我们都是部署单独的服务,并没有应用实际的示例。比如,我们现在部署一个前后端分离的项目前端是一组 Pod,后端也是一组 Pod,那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用工作量的后端部分?

Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为 微服务。这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector 来实现的。Service 能够提供负载均衡的能力,但是在使用上有以下限制:只提供 四层负载均衡能力,而没有 七层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上 四层负载均衡是不支持的。

Service 的名称解析是依赖于 dns 附件的,因此在部署完 k8s 之后需要再部署 dns 附件。kubernetes 要想给客户端提供网络功能,需要依赖第三方的网络插件(flannel,calico等)。每个 K8s 节点上都有一个组件叫做 kube-proxy,kube-proxy 这个组件将始终监视着 apiserver 中有关 service 资源的变动信息,需要跟 master 之上的 apiserver 交互,随时连接到 apiserver 上获取任何一个与 service 资源相关的资源变动状态。

Kubernetes 系统化学习之服务发现篇(四)
1624.png

简单来讲,service 对象就是工作在节点上的一组 iptables 或 ipvs 规则,用于将到达 service 对象 ip 地址的流量调度转发至相应 endpoint 对象指向的 ip 地址和端口之上;工作于每个节点的 kube-proxy 组件通过 apiserver 持续监控着各 service 及其关联的 pod 对象,并将其创建或变动实时反映至当前工作节点上相应的 iptables 或 ipvs 规则;

代理模式

其实 service 和 pod 或其他资源的关联,本质上不是直接关联,它依靠一个中间组件 endpoint;endpoint 主要作用就是引用后端 pod 或其他资源(比如 k8s 外部的服务也可以被 endpoint 引用),所谓 endpoint 就是 ip 地址+端口;

Kubernetes 系统化学习之服务发现篇(四)
kubernetes-find-service-02.png

VIP(虚拟 IP 地址)和 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 中,添加了 ipvs 代理。
  • 在 Kubernetes 1.14 版本开始默认使用 ipvs 代理。在 Kubernetes v1.0 版本,Service 是 “4 层”(TCP/UDP over IP)概念。在 Kubernetes v1.1 版本,新增了 Ingress API(beta版),用来表示 “7 层”(HTTP)服务。

使用 userspace 代理模式

Kubernetes 系统化学习之服务发现篇(四)
kubernetes-find-service-11.png

使用 iptables 代理模式

Kubernetes 系统化学习之服务发现篇(四)
kubernetes-find-service-08.png

使用 ipvs 代理模式

Kubernetes 系统化学习之服务发现篇(四)
kubernetes-find-service-09.png

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

为什么不适用 Round-robin DNS 的形式进行负载均衡呢?

熟悉 DNS 的话,都知道 DNS 会在客户端进行缓存。当后端服务发生变动的话,我们是无法得到最新的地址的,从而无法达到负载均衡的作用了。

Service 的类型

在 Kubernetes 上 service 的类型有4种,第一种是 clusterIP,我们在创建 service 资源时,如果不指定其 type 类型,默认就是 clusterip;第二种是 NodePort 类型,第三种是 LoadBalancer,第四种是 ExternalName;不同类型的 service,其功能和作用也有所不同;

类型 用途介绍
ClusterIp 默认类型;自动分配一个仅 Cluster 内部可以访问的虚拟 IP 地址
NodePort 在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 :NodePort 来访问该服务
LoadBalancer 在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到 :NodePort 来访问该服务
ExternalName 把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 Kubernetes1.7 或更高版本的 kube-dns 才支持

ClusterIp

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

这种类型 service 不能被集群外部客户端所访问,仅能在集群节点上访问,这也是 默认的ServiceType;这种类型的service的ip地址一定是我们在初始化集群时,指定的service网络(10.96.0.0/12)中的地址。

Kubernetes 系统化学习之服务发现篇(四)
src=http___inotgo.com_imagesLocal_202105_24_20210524153258969B_6.png&refer=http___inotgo.webp

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

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

我们这里来使用一个例子,deployment 如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      demo: deployment
  template:
    metadata:
      labels:
        app: nginx
        demo: deployment
    spec:
      containers:
      - name: nginx
        image: nginx:1.16-alpine
        ports:
        - name: http
          containerPort: 80

创建 ClusterIP 类型的 Service:

apiVersion: v1
kind: Service
metadata:
  name: nginx-cluster-service
  labels:
    app: nginx
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx        

NodePort

NodePort 类型的 service,是建构在 ClusterIP 的基础上做的扩展,主要解决了集群外部客户端访问问题;通过每个 Node 节点上的 IP 和静态端口暴露 k8s 集群内部的服务。通过请求<NodeIP>:<NodePort>可以把请求代理到内部的 pod。

NodePort 类型 service 在创建时,它会每个节点上创建一条 DNAT 规则,外部客户端访问集群任意节点的指定端口,都会被 DNAT 到对应的 service 上,从而实现访问集群内部 Pod;对于集群内部客户端的访问它还是通过 ClusterIP 进行的。

Kubernetes 系统化学习之服务发现篇(四)
src=http___pic3.zhimg.com_v2-20e15b5e754dfd8945d5600b76c2a77d_1440w.jpg_source=172ae18b&refer=http___pic3.zhimg.webp

创建 NodeProt 类型的 Service:

apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport
  labels:
    app: nginx
spec:
  type: NodePort
  ports:
  - port: 81
    protocol: TCP
    targetPort: 80
 nodePort: 32280
  selector:
    app: nginx       

LoadBalancer

LoadBalancer 这种类型的 service 是在 NodePort 的基础上做的扩展,这种类型 service 只能在底层是云环境的 K8s 上创建,如果底层是非云环境,这种类型无法实现,只能手动搭建反向代理进行对 NodePort 类型的 service 进行反代;它主要解决 NodePort 类型 service 被集群外部访问时的端口映射以及负载;

Kubernetes 系统化学习之服务发现篇(四)
src=http___img-blog.csdnimg.cn_1a69b8d5b3ae4fb9ac1be8a19745eea3.png_x-oss-process=image_watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc3VycGFzc0xpYW5n,size_20,color_FFFFFF,t_70,g_se,x_16&refer=http__.webp

ExternalName

ExternalName 这种类型 service 主要用来解决对应 service 引用集群外部的服务;如果我们需要在集群中使用集群外部的服务,我们就可以创建 ExternalName 类型的 service,指定后端关联外部某个服务端 ip 地址或域名即可,它没有 selector,也没有定义任何的端口和 Endpoint。

Kubernetes 系统化学习之服务发现篇(四)
src=http___img2022.cnblogs.com_blog_1725357_202207_1725357-20220706010603397-231245063.jpg&refer=http___img2022.cnblogs.webp
# externalName 字段就是需要引用的服务名或者域名
kind: Service 
apiVersion: v1 
metadata: 
  name: my-service 
  namespace: prod 
spec: 
  type: ExternalName 
  externalName: my.database.example.com

2. Ingress

Service 对集群之外暴露服务的主要方式有两种:NotePort 和 LoadBalancer,但是这两种方式,都有一定的缺点:

  • NodePort 方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显;
  • LoadBalancer 方式的缺点是每个 service 需要一个 LoadBalancer,浪费、麻烦,并且需要 kubernetes 之外设备的支持。

基于这种现状,kubernetes 提供了 Ingress 资源对象,Ingress 只需要一个 NodePort 或者一个 LB 就可以满足暴露多个 Service 的需求。工作机制大致如下图表示:

Kubernetes 系统化学习之服务发现篇(四)
src=http___img2018.cnblogs.com_blog_1842344_201910_1842344-20191022010325392-212104489.png&refer=http___img2018.cnblogs.webp

Ingress 相当于一个七层的负载均衡器,是 kubernetes 对反向代理的一个抽象,它的工作原理类似于 Nginx,可以理解成在 Ingress 里建立诸多映射规则,Ingress Controller 通过监听这些配置规则并转化成 Nginx 的反向代理配置 , 然后对外部提供服务。

在这里有两个核心概念:

  • ingress:kubernetes 中的一个对象,作用是定义请求如何转发到 service 的规则;
  • ingress controller:具体实现反向代理及负载均衡的程序,对 ingress 定义的规则进行解析,根据配置的规则来实现请求转发,实现方式有很多,比如 Nginx, Contour, Haproxy 等等。

Ingress(以 Nginx 为例)的工作原理如下:

  1. 用户编写 Ingress 规则,说明哪个域名对应 kubernetes 集群中的哪个 Service;
  2. Ingress 控制器动态感知 Ingress 服务规则的变化,然后生成一段对应的 Nginx 反向代理配置;
  3. Ingress 控制器会将生成的 Nginx 配置写入到一个运行着的 Nginx 服务中,并动态更新;
  4. 到此为止,其实真正在工作的就是一个 Nginx 了,内部配置了用户定义的请求转发规则。
Kubernetes 系统化学习之服务发现篇(四)
d66608c411c9420b8069fbc796d35900.png

配置 HTTP 代理

下面我们就来看一个使用的例子(一个 MySQL服务/一个Redis 服务):

# Deployment控制3个mysql
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-deployment
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mysql-pod
  template:
    metadata:
      labels:
        app: mysql-pod
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        ports:
        - containerPort: 3306

---
# Deployment控制3个redis
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: redis-pod
  template:
    metadata:
      labels:
        app: redis-pod
    spec:
      containers:
      - name: redis
        image: redis:6.3
        ports:
        - containerPort: 6379

---
# mysql-service关联mysql
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
  namespace: dev
spec:
  selector:
    app: mysql-pod
  clusterIP: None
  type: ClusterIP
  ports:
  - port: 3306
    targetPort: 3306

---
# redis-service关联3306
apiVersion: v1
kind: Service
metadata:
  name: redis-service
  namespace: dev
spec:
  selector:
    app: redis-pod
  clusterIP: None
  type: ClusterIP
  ports:
  - port: 6379
    targetPort: 6379

创建 ingress-http.yaml:

注意我们在 Ingress 资源对象中添加了一个 annotations:kubernetes.io/ingress.class: “nginx”,这就是指定让这个 Ingress 通过 ingress-nginx 来处理,如果不添加则不会被 ingress 控制器所监控到

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-http
  namespace: dev
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
    - host: mysql.hupun.com # 主机,域名,ip
      http:
        paths:
          - path: / # 路径  url:host/path
            pathType: Prefix
            backend:
              service:
                name: mysql-service # 访问url会转到这个service的80端口上去
                port:
                  number: 3306
    - host: redis.hupun.com
      http:
        paths:
          - path: / # 路径  url:host/path
            pathType: Prefix
            backend:
              service:
                name: redis-service # 访问url会转到这个service的80端口上去
                port:
                  number: 6379

配置 HTTPS 代理

创建证书,以及 cert 存储方式:

# 生成自签名证书(365天有效期)  
# Key: tls.key  证书: tls.crt  
$ openssl req -x509 -sha256 -nodes   
    -days 365 -newkey rsa:2048   
    -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"  
Generating a 2048 bit RSA private key  
................+++  
................+++  
writing new private key to 'tls.key'  
  
# 在K8S中创建一个名称为tls-secret的secret格式的证书信息  
$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt  
secret "tls-secret" created  
Deployment/Service/Ingress

这里主要注意关注 “ secretName: tls-secret ”:

apiVersion: apps/v1  
kind: Deployment  
metadata:  
  name: erp-deploy
spec:  
  replicas: 2  
  template:  
    metadata:  
      labels:  
        name: erp
    spec:  
      containers:  
        - name: erp
          image: erp:1.0.1
          imagePullPolicy: IfNotPresent  
          ports:  
            - containerPort: 80  
  
---  
apiVersion: apps/v1  
kind: Service  
metadata:  
  name: svc-erp
spec:  
  selector:  
    name: erp
  ports:  
    - port: 80  
      targetPort: 80  
      protocol: TCP  
  
---  
apiVersion: apps/v1  
kind: Ingress  
metadata:  
  name: ingress-erp
spec:  
  tls:  
    - hosts:  
        - erp.hupun.com  
      secretName: tls-secret  
  rules:  
    - host: erp.hupun.com  
      http:  
        paths:  
          - path: /  
            backend:  
              serviceName: svc-erp
              servicePort: 80

几种常见的部署方式

  • Deployment + LoadBalancer 模式的 Service

如果要把 ingress 部署在公有云,那用这种方式比较合适。用 Deployment 部署 ingress-controller,创建一个 type 为 LoadBalancer 的 service 关联这组 pod。大部分公有云,都会为 LoadBalancer 的 service 自动创建一个负载均衡器,通常还绑定了公网地址。只要把域名解析指向该地址,就实现了集群服务的对外暴露。

  • Deployment + NodePort 模式的 Service

同样用 deployment 模式部署 ingress-controller,并创建对应的服务,但是 type 为 NodePort。这样, ingress 就会暴露在集群节点 ip 的特定端口上。由于 nodeport 暴露的端口是随机端口,一般会在前再搭建一套负载均衡器来转发请求。该方式一般用于宿主机是相对固定的环境 ip 地址不变的场景。NodePort 方式暴露 ingress 虽然简单方便,但是 NodePort 多了一层 NAT,在请求量级很大时可能对性能会有一定影响。

  • DaemonSet + HostNetwork + nodeSelector

用 DaemonSet 结合 nodeselector 来部署 ingress-controller 到特定的 node 上(也可结合 affinity 亲和性部署到多个节点),然后使用 HostNetwork 直接把该 pod 与宿主机 node 的网络打通,直接使用宿主机的 80/433 端口就能访问服务。这时,ingress-controller 所在的 node 机器就很类似传统架构的边缘节点,比如机房入口的 nginx 服务器。该方式整个请求链路最简单,性能相对 NodePort 模式更好。缺点是由于直接利用宿主机节点的网络和端口,一个 node 只能部署一个 ingress-controller pod。比较适合大并发的生产环境使用。

Kubernetes 系统化学习之 基本概念篇(一)
Kubernetes 系统化学习之 POD原理篇(二)
Kubernetes 系统化学习之 资源清单篇(三)
Kubernetes 系统化学习之 服务发现篇(四)
Kubernetes 系统化学习之 持久存储篇(五)
Kubernetes 系统化学习之 集群调度篇(六)
Kubernetes 系统化学习之 集群安全篇(七)


原文始发于微信公众号(白菜说技术):Kubernetes 系统化学习之服务发现篇(四)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/172449.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!