K8S之Service详解
简介
在K8S中,Pod是容器运行的载体,我们可以访问Pod内部的容器来访问相对应的服务,但是Pod的IP地址不是固定的,因此这也就意味我们不方便直接采用IP地址对Pod进行访问管理。
在K8S中提供我们 service
服务来解决上述问题, service
对一组pod进行聚合,并且提供一个统一的外部接口地址,我们通过访问该接口地址就可以访问其所对应的pod服务。
本质上来说其实 service
只是一个概念,真正起到转发数据的是 kube-proxy
服务进程,每个节点之上都运行一个 kube-proxy
服务进程,当创建 service
的时候 API Service
会监听 service
然后向 etcd
写入 service
的信息, kube-proxy
会监听 etcd
中 service
的信息并且将 service
信息转发成对应的访问规则。
kube-proxy工作模式
userspace
在该模式下 kube-proxy
会为每一个 service
创建一个监听端口,发送给 Cluseter IP
请求会被 iptable
重定向给 kube-proxy
监听的端口上,其中 kube-proxy
会根据 LB
算法将请求转发到相应的pod之上。
该模式下,kube-proxy充当了一个四层负载均衡器的角色。由于kube-proxy运行在userspace中,在进行转发处理的时候会增加内核和用户空间之间的数据拷贝,虽然比较稳定,但是效率非常低下。
iptables
iptables模式下 kube-proxy
为每一个pod创建相对应的 iptables
规则,发送给 ClusterIP
的请求会被直接发送给后端pod之上
在该模式下 kube-proxy
不承担负载均衡器的角色,其只会负责创建相应的转发策略,该模式的优点在于较userspace模式效率更高,但是不能提供灵活的LB策略,当后端Pod不可用的时候无法进行重试。
ipvs模式
ipvs模式与iptable模式类型, kube-proxy
会根据pod的变化创建相应的 ipvs
转发规则,ipvs相对iptable来说转发效率更加高效,同时提供了大量的负责均衡算法。
使用ipvs模式必须安装ipvs内核模块,否则会自动降级为iptables
# 编辑配置文件 搜索43行mode将其修改为ipvs
[root@master k8s]# kubectl edit cm kube-proxy -n kube-system
# 删除原有的代理
[root@master k8s]# kubectl delete pod -l k8s-app=kube-proxy -n kube-system
# 查看
[root@master k8s]# ipvsadm -Ln
service类型
资源清单
apiVersion: v1 # 版本
kind: Service # 类型
metadata: # 元数据
name: # 资源名称
namespace: # 命名空间
spec:
selector: # 标签选择器,用于确定当前Service代理那些Pod
app: nginx
type: # Service的类型,指定Service的访问方式
clusterIP: # 虚拟服务的IP地址
sessionAffinity: # session亲和性,支持ClientIP、None两个选项,默认值为None
ports: # 端口信息
- port: 8080 # Service端口
protocol: TCP # 协议
targetPort : # Pod端口
nodePort: # 主机端口
- ClusterIP:默认类型,处于该模式下K8S会自动为service分配地址,其只能在集群内部访问。
- NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务。
- LoadBalancer:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境的支持。
- ExternalName:把集群外部的服务引入集群内部,直接使用。
service应用
Clusterip service
cat > service-clusterip.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: service-clusterip
namespace: dev
spec:
selector:
app: nginx-pod
type: ClusterIP
# service IP地址 如果不写默认会生成一个
clusterIP: 10.97.97.97
ports:
# service端口
- port: 80
# 目标pod端口
targetPort: 80
EOF
# 创建service
[root@master k8s]# kubectl create -f service-clusterip.yaml
# 查看service
[root@master k8s]# kubectl get -n dev service service-clusterip -o wide
创建pod
cat > deployment.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: pc-deployment
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
protocol: TCP
EOF
# 创建pod
[root@master k8s]# kubectl create -f deployment.yaml
# 查看pod
[root@master k8s]# kubectl get pod -n dev -o wide --show-labels
# 任意节点发起请求
[root@master k8s]# curl 10.244.2.64
# 由于pod节点都是nginx默认界面都是一样的为了方便测试修改默认界面
# 进入容器
[root@master k8s]# kubectl exec -it -n dev pc-deployment-7d7dd5499b-j5hnp /bin/sh
# 三个节点写入数据
echo "Current Request is 10.244.1.64" > /usr/share/nginx/html/index.html
echo "Current Request is 10.244.1.63" > /usr/share/nginx/html/index.html
echo "Current Request is 10.244.1.62" > /usr/share/nginx/html/index.html
# 发起请求
[root@master k8s]# curl 10.244.2.64
[root@master k8s]# curl 10.244.2.63
[root@master k8s]# curl 10.244.2.62
创建services
cat > service-clusterip.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: service-clusterip
namespace: dev
spec:
selector:
app: nginx-pod
type: ClusterIP
# service IP地址 如果不写默认会生成一个
clusterIP: 10.97.97.97
ports:
# service端口
- port: 80
# 目标pod端口
targetPort: 80
EOF
# 创建service
[root@master k8s]# kubectl create -f service-clusterip.yaml
# 查看service
[root@master k8s]# kubectl get -n dev service service-clusterip -o wide
# 查看详情
[root@master k8s]# kubectl describe svc service-clusterip -n dev
# 多次访问后端节点
[root@master k8s]# curl 10.97.97.97
Endpoint
Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有Pod的访问地址,它是根据service配置文件中的selector描述产生的。
一个service由一组Pod组成,这些Pod通过Endpoints暴露出来,Endpoints是实现实际服务的端点集合。换言之,service和Pod之间的联系是通过Endpoints实现的。
# 查看endpoints
[root@master k8s]# kubectl get endpoints -n dev
负载分发策略
对Service的访问被分发到了后端的Pod上去,目前kubernetes提供了两种负载分发策略:
- 如果不定义,默认使用kube-proxy的策略,比如随机、轮询等。
- 基于客户端地址的会话保持模式,即来自同一个客户端发起的所有请求都会转发到固定的一个Pod上,这对于传统基于Session的认证项目来说很友好,此模式可以在spec中添加
sessionAffinity: ClusterIP
选项。
# 修改分发策略
apiVersion: v1
kind: Service
metadata:
name: service-clusterip
namespace: dev
spec:
selector:
app: nginx-pod
clusterIP: 10.97.97.97 # service的IP地址,如果不写,默认会生成一个
type: ClusterIP
sessionAffinity: ClientIP # 修改分发策略为基于客户端地址的会话保持模式
ports:
- port: 80 # Service的端口
targetPort: 80 # Pod的端口
[root@master k8s]# kubectl create -f service-clusterip.yaml
# 循环测试
[root@master k8s]# while true;do curl 10.97.97.97:80; sleep 5; done;
HeadLiness services
在某些场景中,开发人员可能不想使用Service提供的负载均衡功能,而希望自己来控制负载均衡策略,针对这种情况,kubernetes提供了HeadLinesss Service,这类Service不会分配Cluster IP,如果想要访问Service,只能通过Service的域名进行查询。
创建service
cat > service-headliness.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: service-headliness
namespace: dev
spec:
selector:
app: nginx-pod
clusterIP: None # 将clusterIP设置为None,即可创建headliness Service
type: ClusterIP
ports:
- port: 80 # Service的端口
targetPort: 80 # Pod的端口
EOF
[root@master k8s]# kubectl create -f service-headliness.yaml
# 查看service
[root@master k8s]# kubectl get svc service-headliness -n dev -o wide
# 查看详情
[root@master k8s]# kubectl describe svc service-headliness -n dev
# 查看pod
[root@master k8s]# kubectl get pod -n dev
# 进入Pod
[root@master k8s]# kubectl exec -it -n dev pc-deployment-7d7dd5499b-jc84t /bin/bash
# 查看域名
root@pc-deployment-7d7dd5499b-jc84t:/# cat /etc/resolv.conf
# 通过Service的域名进行查询:
# 默认访问规则service名称.名称空间.svc.cluster.local
# 安装dig
[root@master k8s]# yum -y install bind-utils
# 访问
[root@master k8s]# dig @10.96.0.10 service-headliness.dev.svc.cluster.local
NodePort service
在上述案例之中 service 暴露的IP 只能供集群内部访问,但是我们创建资源即暴露给用户使用,因此 K8S 为我们提供了 NodePort service
其会将service 的端口与 node 的端口进行映射,当我们访问 node 的 IP + Port
即为访问 service 所对应的资源
创建 service
# 创建 service
cat > cat service-nodeport.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: service-nodeport
namespace: dev
spec:
selector:
app: nginx-pod
type: NodePort # Service类型为NodePort
ports:
- port: 80 # Service的端口
targetPort: 80 # Pod的端口
nodePort: 30002 # 指定绑定的node的端口(默认取值范围是30000~32767),如果不指定,会默认分配
EOF
# 创建pod
[root@master k8s]# kubectl create -f service-nodeport.yaml
# 查看详情
[root@master k8s]# kubectl get svc -n dev -o wide
# 访问测试 由于更换机器此时 master 的地址由 10.1.1.2 更改为 172.16.137.128
curl 172.16.137.128:30002
LoadBalancer service
通过上图可以看到使用 NodePort
模式中对负载均衡能力不是很友好,external 类型与 nodeport 类似都是对于外部暴露一个访问端口
区别在于使用该模式会在集群外部添加一个负载均衡设备,外部访问负载均衡设置,由负载均衡设备在根据一定的算法转发到后端服务节点
ExternalName service
ExternalName类型的Service用于引入集群外部的服务,它通过externalName属性指定一个服务的地址,然后在集群内部访问此Service就可以访问到外部的服务了。
创建 service
cat > service-externalname.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: service-externalname
namespace: dev
spec:
type: ExternalName # Service类型为ExternalName
externalName: www.baidu.com # 改成IP地址也可以
EOF
[root@master k8s]# kubectl create -f service-externalname.yaml
# 查看 service
[root@master k8s]# kubectl get svc service-externalname -n dev
# 安装域名解析
[root@master k8s]# yum install bind-utils
# 域名解析
[root@master k8s]# dig @10.96.0.10 service-externalname.dev.svc.cluster.local
Ingress
简介
在上述中 service 对外提供负载均衡主要有 nodeport 与 loadblancer 两种方式,但是这两种方式各自都有一定的缺点,在 nodeport 方式中 service 会与 node 节点进行映射,这样会占用大量的端口,当 service 过多的时候可能会导致节点端口不够,在 loadblancer 中每一个service 都需要一个 LB,并且需要外部的负载均衡设备进行支持
基于上述问题,在 K8S 中提出了 ingress
资源对象,该资源对象只需要一个 nodeport 或者一个 LB 就可以满足暴露多个 service 需求
实际上 Ingress 类似于一个七层的负载均衡器,是由 K8S 对反向代理的抽象,其工作原理类似于 Nginx 可以理解为Ingress里面建立了诸多映射规则,Ingress Controller通过监听这些配置规则并转化为Nginx的反向代理配置,然后对外提供服务。
-
Ingress:kubernetes中的一个对象,作用是定义请求如何转发到Service的规则。
-
Ingress Controller:具体实现反向代理及负载均衡的程序,对Ingress定义的规则进行解析,根据配置的规则来实现请求转发,实现的方式有很多,比如Nginx,Contour,Haproxy等。
-
其工作原理如下
- 用户编写 Ingress 规则说明那个域名对应那个 service
- Ingress Contoller 动态感知 ingress 编写的规则,然后生成对应的反向代理规则
- ingress 控制器会根据生成代理规则写入到代理服务中
- 客户端请求代理服务,由代理服务转发到后端 pod 节点
Ingress 使用
环境搭建
mkdir ingress-controller && cd ingress-controller
# 下载service 与控制器
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
# 更换网络源
sed -i 's#quay.io/kubernetes-ingress-controller/nginx-ingress-controller#registry.cn-qingdao.aliyuncs.com/kubernetes_xingej/nginx-ingress-controller#g' mandatory.yaml
# 使用配置文件
kubectl apply -f ./
# 查看pod
[root@master ~]# kubectl get -n ingress-nginx pod
# 查看svc
[root@master ~]# kubectl get -n ingress-nginx svc
准备Service和pod
cat > tomcat-nginx.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-deployment
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: tomcat-pod
template:
metadata:
labels:
app: tomcat-pod
spec:
containers:
- name: tomcat
image: tomcat:8.5-jre10-slim
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: dev
spec:
selector:
app: nginx-pod
clusterIP: None
type: ClusterIP
ports:
- port: 80
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
namespace: dev
spec:
selector:
app: tomcat-pod
clusterIP: None
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
EOF
# 创建pod
[root@master ~]# kubectl create -f tomcat-nginx.yaml
# 查看
[root@master ~]# kubectl get svc,pod -n dev
HTTP代理
cat > ingress-http.yaml << EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-http
namespace: dev
spec:
# 代理规则
rules:
# 绑定域名
- host: nginx.sr.com
http:
paths:
# 绑定路径
- path: /
backend:
serviceName: nginx-service
# service暴露端口
servicePort: 80
- host: tomcat.sr.com
http:
paths:
- path: /
backend:
serviceName: tomcat-service
servicePort: 8080
EOF
# 创建
[root@master ~]# kubectl create -f ingress-http.yaml
# 查看
[root@master ~]# kubectl get pod -n dev -o wide
# 查看详情
[root@master ~]# kubectl describe ingress ingress-http -n dev
配置本地host文件将master的IP地址与上述域名绑定
# 访问nginx
curl nginx.sr.com:30771
# 访问tomcat
curl tomcat.sr.com:30378
HTTPS 代理
# 生成证书
[root@master ~]# openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/C=CN/ST=BJ/L=BJ/O=nginx/CN=sr.com"
# 生成秘钥
[root@master ~]# kubectl create secret tls tls-secret --key tls.key --cert tls.crt
# 生成配置文件
cat > ingress-https.yaml << EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-https
namespace: dev
spec:
tls:
- hosts:
- nginx.sr.com
- tomcat.sr.com
secretName: tls-secret # 指定秘钥需要与上述生成的秘钥名称相同
rules:
- host: nginx.sr.com
http:
paths:
- path: /
backend:
serviceName: nginx-service
servicePort: 80
- host: tomcat.sr.com
http:
paths:
- path: /
backend:
serviceName: tomcat-service
servicePort: 8080
EOF
# 加载配置文件
[root@master k8s]# kubectl create -f ingress-https.yaml
# 查看配置文件
[root@master k8s]# kubectl get ingress ingress-https -n dev
# 查看详情
[root@master k8s]# kubectl describe -n dev ingress ingress-https
# 查看service
[root@master k8s]# kubectl get -n ingress-nginx service