likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 40

Kubernetes 系统化学习之 基本概念篇(一) Kubernetes 系统化学习之 POD原理篇(二) Kubernetes 系统化学习之 资源清单篇(三) Kubernetes 系统化学习之 服务发现篇(四) Kubernetes 系统化学习之 持久存储篇(五) Kubernetes 系统化学习之 集群调度篇(六) 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 系统化学习之 服务发现篇(四)

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

代理模式

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

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

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 系统化学习之 服务发现篇(四)

使用 iptables 代理模式

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

使用 ipvs 代理模式

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

注意,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 系统化学习之 服务发现篇(四)

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

  • 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 系统化学习之 服务发现篇(四)

创建 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 系统化学习之 服务发现篇(四)

ExternalName

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

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

# 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 系统化学习之 服务发现篇(四)

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 系统化学习之 服务发现篇(四)

配置 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 系统化学习之 集群安全篇(七)

转载自:https://juejin.cn/post/7166423947968266247
评论
请登录