Service
Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。通过 Label Selector 这一组 Pod 能够被 Service 访问到,
- Service 能够提供负载均衡的能力,但是在使用上有以下限制:只提供 4 层负载均衡能力,而没有 7 层功能。但是可以通过增加
Ingress
来添加一个 7 层的负载均衡能力
一、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
代理。
为什么使用服务代理而不使用 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。
1. userspace
2. iptables
3. ipvs
(常用)
这种模式,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
二、Service的类型
1. ClusterIp
默认类型,自动分配一个仅 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
中
ClusterIP 实例
-
创建一个 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
Headless Service(无头服务)
有时不需要或不想要负载均衡,以及单独的 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
2. NodePort
在 ClusterIP
基础上为 Service
在每台机器上绑定一个端口,这样就可以通过 NodeIp:NodePort
来访问该服务
a. 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 集群的节点都可以该端口):
b. 几个端口的说明
3. LoadBalancer
loadBalancer
和 nodePort
其实是同一种方式。区别在于 loadBalancer
在 nodePort
的基础上,借助 cloud provider
创建了 LB
来向节点导流(外部负载均衡器),并将请求转发到 NodeIp:NodePort
- LB 是供应商提供的,是收费的
- 服务器必须是云服务器
4. ExternalName
把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7
或更高版本的 kube-dns
才支持
这种类型的 Service 通过返回 CNAME
和它的值,可以将服务映射到 externalName
字段的内容(例如私有仓库:hub.zyx.com)。ExternalName Service
是 Service
的特例,它没有 selector
,也没有定义任何的端口和 Endpoint
。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务
ExternalName 实例
apiVersion: v1
kind: Service
metadata:
name: my-service-1
namespace: default
spec:
type: ExternalName
externalName: hub.zyx.com
当查询主机 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
Ingress-Nginx github 地址:https://github.com/kubernetes/ingress-nginx
Ingress-Nginx 官方网站:https://kubernetes.github.io/ingress-nginx/
1. 部署 Ingress-Nginx
官方网址:https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal
我现在的版本是 controller-v1.0.3
,去官网找一下最新版本,采用裸机的安装方式:
官方给的方法:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.3/deploy/static/provider/baremetal/deploy.yaml
我先下载 .yaml 文件,然后再创建:
# 先获取 yaml 文件,可以用这个文件来创建或者删除 Ingress
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.3/deploy/static/provider/baremetal/deploy.yaml
# 安装 Ingress
kubectl apply -f deploy.yaml
# 删除 Ingress
kubectl delete -f deploy.yaml
2. 部署过程中常见问题
1)yaml 文件版本报错
# 安装时发现 yaml 文件中 ValidatingWebhookConfiguration 版本报错,先获取版本
# 没有报错可以不用管这条命令
[root@k8s-master01 yaml]# kubectl explain ValidatingWebhookConfiguration
KIND: ValidatingWebhookConfiguration
VERSION: admissionregistration.k8s.io/v1beta1
# 修改下载 yaml 中 ValidatingWebhookConfiguration 对应的 pod 的版本,再重新安装
2)镜像下载不下来
查看 pod 日志,找到是哪个镜像下载不下来,然后到 DockerHub 上找到相应替代的镜像,修改 yaml
文件中对应的镜像,再重新启动
这是我找到的两个替代镜像,(注意版本对应):
- https://registry.hub.docker.com/r/liangjw/kube-webhook-certgen/tags
- https://registry.hub.docker.com/r/liangjw/ingress-nginx-controller/tags
image: k8s.gcr.io/ingress-nginx/controller:v1.0.3@sha256:4ade87838eb8256b094fbb5272d7dda9b6c7fa8b759e6af5383c1300996a7452
替换为:
image: liangjw/ingress-nginx-controller:v1.0.3@sha256:4ade87838eb8256b094fbb5272d7dda9b6c7fa8b759e6af5383c1300996a7452
image: k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.0@sha256:f3b6b39a6062328c095337b4cadcefd1612348fdd5190b1dcbcb9b9e90bd8068
替换为:
image: liangjw/kube-webhook-certgen:v1.0@sha256:f3b6b39a6062328c095337b4cadcefd1612348fdd5190b1dcbcb9b9e90bd8068
3. Ingress HTTP 代理访问示例
-
先创建两个
Pod
和ClusterIP Service
,提供 Nginx 内部访问# vim deployment-nginx.yaml apiVersion: apps/v1 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 annotations: kubernets.io/ingress.class: "nginx" spec: ports: - port: 80 targetPort: 80 protocol: TCP selector: name: nginx
-
再创建 Ingress 将服务暴露到外部
官方文档:https://kubernetes.io/docs/concepts/services-networking/ingress/apiVersion: extensions/v1beta1 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 ingress
Ingress 资源清单中的 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
-
域名访问
4. 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 创建 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,然后访问域名
5. Nginx 进行 BasicAuth
- 创建密钥文件
yum install -y httpd # 创建密钥文件 # -c 创建,创建文件为 auth,用户名为 foo htpasswd -c auth foo # 然后连输两次密码,这个密码为为之后认证使用 # 构建基础权限认证,根据文件 kubectl create secret generic basic-auth --from-file=auth
- 创建 Ingress
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-with-auth annotations: # 身份验证类型 nginx.ingress.kubernetes.io/auth-type: basic # secret 的名字(上面定义好了) nginx.ingress.kubernetes.io/auth-secret: basic-auth # 要显示的信息,说明为什么需要认证 nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo' spec: rules: - host: auth.zyx.com http: paths: - path: / backend: serviceName: nginx-svc servicePort: 80
- 添加 hosts 域名解析,然后访问就可以看到 BasicAuth 已经成功
6. Nginx 进行重写
名称 | 描述 | 值 |
---|---|---|
nginx.ingress.kubernetes.io/rewrite-target | 必须重定向流量的目标URI | 串 |
nginx.ingress.kubernetes.io/ssl-redirect | 指示位置部分是否仅可访问SSL(当Ingress包含证书时默认为True) | 布尔 |
nginx.ingress.kubernetes.io/force-ssl-redirect | 即使Ingress未启用TLS,也强制重定向到HTTPS | 布尔 |
nginx.ingress.kubernetes.io/app-root | 定义Controller必须重定向的应用程序根,如果它在’/'上下文中 | 串 |
nginx.ingress.kubernetes.io/use-regex | 指示Ingress上定义的路径是否使用正则表达式 | 布尔 |
重定向示例:
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