Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。通过 Label Selector 这一组 Pod 能够被 Service 访问到,
Service 能够提供负载均衡的能力,但是在使用上有以下限制:只提供 4 层负载均衡能力,而没有 7 层功能。但是可以通过增加 Ingress 来添加一个 7 层的负载均衡能力在 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 代理。
为什么使用服务代理而不使用 DNS 轮询?
DNS 实现的历史由来已久,它不遵守记录 TTL,并且在名称查找结果到期后对其进行缓存。有些应用程序仅执行一次 DNS 查找,并无限期地缓存结果。即使应用和库进行了适当的重新解析,DNS 记录上的 TTL 值低或为零也可能会给 DNS 带来高负载,从而使管理变得困难。 在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程apiserver 通过监控 kube-proxy 去进行对服务和端点的监控iptables 是 Service 代理方式的一种,其中保存地址映射及规则,通过 kube-proxy 写入的客户端访问节点时通过 iptables 来实现kube-proxy 通过 pod 的标签(lables)是否匹配去判断这个断点信息是否写入到 Endpoints(包含服务选择器(通过标签匹配)匹配到的所有 Pod 的引用) 里去。kube-proxy 通过不同的负载均衡策略,访问对应的 Pod。这种模式,kube-proxy 会监视 Kubernetes Service 对象和 Endpoints,调用 netlink 接口以相应地创建 ipvs 规则并定期与 Kubernetes Service 对象和 Endpoints 对象同步 ipvs 规则,以确保 ipvs 状态与期望一致。访问服务时,流量将被重定向到其中一个后端 Pod
与 iptables 类似,ipvs 于 netfilter 的 hook 功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着 ipvs 可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs 为负载均衡算法提供了更多选项,例如:
rr:轮询调度lc:最小连接数dh:目标哈希sh:源哈希sed:最短期望延迟nq:不排队调度注意:要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 之前确保 IPVS 内核模块已安装。当 kube-proxy 以 IPVS 代理模式启动时,它将验证节点上 IPVS 内核模块是否可用。 如果未检测到 IPVS 内核模块,则 kube-proxy 将退回到以 iptables 代理模式运行。
可以看到,集群使用的是 ipvs 的代理方式:
[root@k8s-master01 yaml]# ipvsadm -Ln Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.96.0.1:443 rr -> 192.168.66.10:6443 Masq 1 3 0 [root@k8s-master01 yaml]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 21d默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 ip,只能被集群内部的应用程序所访问
clusterIP 主要在每个 node 节点使用对应的代理模式(这里以 iptable 为例) ,将发向 clusterIP 对应端口的数据,转发到 kube-proxy 中。然后 kube-proxy 自己内部实现有负载均衡的方法,并可以查询到这个 service 下对应 pod 的地址和端口,进而把数据转发给对应的 pod 的地址和端口
为了实现图上的功能,主要需要以下几个组件的协同工作:
apiserver:用户通过 kubectl 命令向 apiserver 发送创建 service 的命令,apiserver 接收到请求后将数据存储到 etcd 中kube-proxy:kubernetes 的每个节点中都有一个叫做 kube-porxy 的进程,这个进程负责感知 service,pod 的变化,并将变化的信息写入本地的 iptable 规则中iptable:使用 NAT 等技术将 virtualIP 的流量转至 endpoint 中创建一个 Deployment(后面几种 Service 类型都使用的这个 Deployment),先写一个 svc-deployment.yaml 资源清单:
apiVersion: apps/v1 kind: Deployment metadata: name: myapp-deploy # Deployment 名称 namespace: default spec: replicas: 3 selector: matchLabels: app: myapp release: stable template: metadata: name: myapp # Pod 名 labels: app: myapp release: stable spec: containers: - name: myapp # 容器名 image: wangyanglinux/myapp:v2 # nginx imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80创建 Service 资源清单,来代理上面创建的三个 Pod。myapp-svc.yaml:
apiVersion: v1 kind: Service metadata: name: myapp-svc # Service名称 spec: type: ClusterIP # Service 类型,不写默认就是 ClusterIP selector: # 用于匹配后端的 Pod 资源对象,需和上面 Pod 的标签一致 app: myapp release: stable ports: - name: http port: 80 # Service端口号 targetPort: 80 # 后端 Pod 端口号 protocol: TCP # 使用的协议访问
[root@k8s-master01 yaml]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES myapp-deploy-6998f78dfc-nt28j 1/1 Running 0 11m 10.244.1.29 k8s-node01 <none> <none> myapp-deploy-6998f78dfc-p9bkc 1/1 Running 0 11m 10.244.1.30 k8s-node01 <none> <none> myapp-deploy-6998f78dfc-xqwbk 1/1 Running 0 11m 10.244.2.25 k8s-node02 <none> <none> # svc 是 service 的简写,使用 kubectl get service 也可 [root@k8s-master01 yaml]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE myapp-svc ClusterIP 10.101.140.64 <none> 80/TCP 6s # 可以看到负载均衡策略是轮询 [root@k8s-master01 yaml]# ipvsadm -Ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.101.140.64:80 rr -> 10.244.1.29:80 Masq 1 0 2 -> 10.244.1.30:80 Masq 1 0 3 -> 10.244.2.25:80 Masq 1 0 3 # 多访问几次也可以看到负载均衡策略是轮询 [root@k8s-master01 yaml]# curl 10.101.140.64/hostname.html myapp-deploy-6998f78dfc-nt28j有时不需要或不想要负载均衡,以及单独的 Service IP 。遇到这种情况,可以通过指定 ClusterIP(spec.clusterIP)的值为 “None” 来创建 Headless Service 。这类 Service 并不会分配 Cluster IP, kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。主要用来解决 Hostname 与 Podname 变化问题。在创建 StatefulSet 时,必须先创建一个 Headless Service。
使用场景:
第一种:自主选择权,有时候 client 想自己来决定使用哪个 Real Server,可以通过查询 DNS 来获取 Real Server 的信息。
第二种:Headless Services 还有一个用处(PS:也就是我们需要的那个特性)。Headless Service 的对应的每一个 Endpoints,即每一个 Pod,都会有对应的 DNS 域名。当删除 Pod 时,Pod 的 IP 会变,但是 Pod 的名字不会改变,这样各 Pod 之间就可以通过 Pod 名来互相访问。
创建一个 Headless Service,还是匹配上面创建的 Deployment(ClusterIP 实例) 下的 Pod
apiVersion: v1 kind: Service metadata: name: myapp-headless #service对象名 spec: clusterIP: None #将ClusterIP字段设置为None,即表示为headless类型的service资源对象 selector: app: myapp #匹配上面定义的pod资源 ports: - port: 80 #service端口 targetPort: 80 #后端pod端口 protocol: TCP #协议查看 svc
# 可以看到,Cluster-IP 对应位置的值为 None [root@k8s-master01 yaml]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE myapp-headless ClusterIP None <none> 80/TCP 8s在 DNS 中查询域名的 A 记录
# 查看 k8s coredns 的ip [root@k8s-master01 yaml]# kubectl get pod -n kube-system -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-5c98db65d4-5ztqn 1/1 Running 6 21d 10.244.0.11 k8s-master01 <none> <none> coredns-5c98db65d4-pc62t 1/1 Running 6 21d 10.244.0.10 k8s-master01 <none> <none> # 使用 dig 解析域名(没有 dig 要安装:yum -y install bind-utils):dig -t A 域名 @DNS服务器IP # DNS服务器IP:上面获取的两个 coredns ip 中选取一个 # 默认域名:SVC_NAME.NAMESPACE.svc.cluster.local [root@k8s-master01 yaml]# dig -t A myapp-headless.default.svc.cluster.local. @10.244.0.11 ;; ANSWER SECTION: myapp-headless.default.svc.cluster.local. 30 IN A 10.244.1.30 myapp-headless.default.svc.cluster.local. 30 IN A 10.244.1.29 myapp-headless.default.svc.cluster.local. 30 IN A 10.244.2.25 # 可以看到解析的结果和前面创建的 Pod 是对应的,因此可以通过域名访问这几个 Pod在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 NodeIp:NodePort 来访问该服务
创建一个 NodePort Service,匹配 ClusterIP 实例中创建的 Deployment
apiVersion: v1 kind: Service metadata: name: myapp #service对象名 spec: type: NodePort #这里指定使用ClusterIP,默认也是ClusterIP,这里可有可无 selector: app: myapp #匹配上面定义的pod资源 release: stable ports: - port: 80 #service端口 targetPort: 80 #后端pod端口 nodePort: 30001 #节点端口,物理机上暴露出来的端口 protocol: TCP #协议查看 svc
[root@k8s-master01 yaml]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE myapp NodePort 10.97.100.171 <none> 80:30001/TCP 7s可以从外部访问到 30001 端口(每个 k8s 集群的节点都可以该端口):
loadBalancer 和 nodePort 其实是同一种方式。区别在于 loadBalancer 在 nodePort 的基础上,借助 cloud provider 创建了 LB 来向节点导流(外部负载均衡器),并将请求转发到 NodeIp:NodePort
LB 是供应商提供的,是收费的服务器必须是云服务器把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持
这种类型的 Service 通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如私有仓库:hub.zyx.com)。ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务
当查询主机 my-service.defalut.svc.cluster.local ( SVC_NAME.NAMESPACE.svc.cluster.local )时,集群的 DNS 服务将返回一个值 hub.zyx.com 的 CNAME(别名) 记录。访问这个服务的工作方式和其他的相 同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发
[root@k8s-master01 yaml]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-service-1 ExternalName <none> hub.zyx.com <none> 13s [root@k8s-master01 yaml]# dig -t A my-service-1.default.svc.cluster.local. @10.244.0.11 ;; ANSWER SECTION: my-service-1.default.svc.cluster.local. 30 IN CNAME hub.zyx.com.Ingress-Nginx github 地址:https://github.com/kubernetes/ingress-nginx
Ingress-Nginx 官方网站:https://kubernetes.github.io/ingress-nginx/
官方网址:https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal
我这里安装的版本是 controller-v0.40.2,采用裸机的安装方式:
官方给的方法:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.40.2/deploy/static/provider/baremetal/deploy.yaml我先下载 .yaml 文件,然后再创建:
# 先获取 yaml 文件,可以用这个文件来创建或者删除 Ingress wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.40.2/deploy/static/provider/baremetal/deploy.yaml # 安装时发现 yaml 文件中 ValidatingWebhookConfiguration 版本报错,先获取版本 [root@k8s-master01 yaml]# kubectl explain ValidatingWebhookConfiguration KIND: ValidatingWebhookConfiguration VERSION: admissionregistration.k8s.io/v1beta1 # 修改 yaml 中 ValidatingWebhookConfiguration 对应的 pod 的版本 # 安装 Ingress kubectl apply -f deploy.yaml # 删除 Ingress kubectl delete -f deploy.yaml先创建两个 Pod 和 ClusterIP Service,提供 Nginx 内部访问
# vim deployment-nginx.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx-dm spec: replicas: 2 selector: matchLabels: name: nginx template: metadata: labels: name: nginx spec: containers: - name: nginx image: wangyanglinux/myapp:v1 ports: - name: http containerPort: 80 --- # 定义nginx 的 svc apiVersion: v1 kind: Service metadata: name: nginx-svc spec: ports: - port: 80 targetPort: 80 protocol: TCP selector: name: nginx再创建 Ingress 将服务暴露到外部
apiVersion: extensions/v1beta1 # kubectl explain ingress kind: Ingress # 类型 metadata: name: nginx-test # 名字 spec: rules: # 规则,List,可配置多个域名。 - host: www.zyx.com # 主机域名 http: paths: # 路径 - path: / # 域名的根路径 backend: serviceName: nginx-svc # 这里链接的是上面创建的 svc 的名称 servicePort: 80 # svc 的端口 # 查看 ingress kubectl get ingressIngress 资源清单中的 spec.rules 最终会转换为 nginx 的虚拟主机配置,进入到 ingress-nginx 容器中查看配置
kubectl exec ingress-nginx-controller-78fd88bd5-sbrz5 -n ingress-nginx -it -- /bin/bash cat nginx.conf修改 hosts 文件,设置上面的域名解析
192.168.66.10 www.zyx.com查看端口
[root@k8s-master01 ingress]# kubectl get svc -n ingress-nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller NodePort 10.96.189.184 <none> 80:31534/TCP,443:31345/TCP 10h域名访问
创建证书,以及 cert 存储方式
# 生成私钥和证书 openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc" # kubectl 创建 secret 资源,这个 secret 后面要用 kubectl create secret tls tls-secret --key tls.key --cert tls.crt # 查看kubectl 的 secret 资源 kubectl get secret tls-secret创建 Deployment 和 Service,这里仍使用上面创建的 Deployment
创建 Ingress
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: nginx-test-https spec: tls: - hosts: - www.zyx3.com # host 主机 secretName: tls-secret # 与上面创建的 secret 要对应上 rules: # 规则,List,可配置多个域名。 - host: www.zyx3.com # 主机域名 http: paths: # 路径 - path: / # 域名的根路径 backend: serviceName: nginx-svc # 这里链接的是上面创建的 svc 的名称 servicePort: 80 # svc 的端口获取 https 连接的端口
[root@k8s-master01 https]# kubectl get svc -n ingress-nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller NodePort 10.96.189.184 <none> 80:31534/TCP,443:31345/TCP 11h配置 hosts,然后访问域名
重定向示例:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: nginx-test annotations: nginx.ingress.kubernetes.io/rewrite-target: https://auth.zyx.com:31345 spec: rules: - host: re.zyx.com # 访问这个地址,就会被重定向到 https://auth.zyx.com:31345 http: # 这里配置不配置均可 paths: - path: / backend: serviceName: nginx-svc servicePort: 80