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 资源相关的资源变动状态。

简单来讲,service 对象就是工作在节点上的一组 iptables 或 ipvs 规则,用于将到达 service 对象 ip 地址的流量调度转发至相应 endpoint 对象指向的 ip 地址和端口之上;工作于每个节点的 kube-proxy 组件通过 apiserver 持续监控着各 service 及其关联的 pod 对象,并将其创建或变动实时反映至当前工作节点上相应的 iptables 或 ipvs 规则;
代理模式
其实 service 和 pod 或其他资源的关联,本质上不是直接关联,它依靠一个中间组件 endpoint;endpoint 主要作用就是引用后端 pod 或其他资源(比如 k8s 外部的服务也可以被 endpoint 引用),所谓 endpoint 就是 ip 地址+端口;

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 代理模式

使用 iptables 代理模式

使用 ipvs 代理模式

注意,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)中的地址。

了实现图上的功能,主要需要以下几个组件的协同工作:
-
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 进行的。

创建 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 被集群外部访问时的端口映射以及负载;

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

# 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 的需求。工作机制大致如下图表示:

Ingress 相当于一个七层的负载均衡器,是 kubernetes 对反向代理的一个抽象,它的工作原理类似于 Nginx,可以理解成在 Ingress 里建立诸多映射规则,Ingress Controller 通过监听这些配置规则并转化成 Nginx 的反向代理配置 , 然后对外部提供服务。
在这里有两个核心概念:
-
ingress:kubernetes 中的一个对象,作用是定义请求如何转发到 service 的规则; -
ingress controller:具体实现反向代理及负载均衡的程序,对 ingress 定义的规则进行解析,根据配置的规则来实现请求转发,实现方式有很多,比如 Nginx, Contour, Haproxy 等等。
Ingress(以 Nginx 为例)的工作原理如下:
-
用户编写 Ingress 规则,说明哪个域名对应 kubernetes 集群中的哪个 Service; -
Ingress 控制器动态感知 Ingress 服务规则的变化,然后生成一段对应的 Nginx 反向代理配置; -
Ingress 控制器会将生成的 Nginx 配置写入到一个运行着的 Nginx 服务中,并动态更新; -
到此为止,其实真正在工作的就是一个 Nginx 了,内部配置了用户定义的请求转发规则。

配置 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