一、k8s为什么要用Service四层代理

1.1 四层负载均衡Service: 概念、原理解读

1、pod ip 经常变化,service 是 pod 的代理,我们客户端访问,只需要访问 service,就会把请求代理到 Pod
2、pod ip 在 k8s 集群之外无法访问,所以需要创建 service,这个 service 可以在 k8s 集群外访问的。

1.2 Service概述

service 是一个固定接入层,客户端可以通过访问 service 的 ip 和端口访问到 service 关联的后端 pod,这个 service 工作依赖于在 kubernetes 集群之上部署的一个附件,就是 kubernetes 的 dns 服务 (不同 kubernetes 版本的 dns 默认使用的也是不一样的,1.11 之前的版本使用的是 kubeDNs,较新的版 本使用的是 coredns),service 的名称解析是依赖于 dns 附件的,因此在部署完 k8s 之后需要再部署 dns 附件,kubernetes 要想给客户端提供网络功能,需要依赖第三方的网络插件(flannel,calico 等)。每 个 K8s 节点上都有一个组件叫做 kube-proxy,kube-proxy 这个组件将始终监视着 apiserver 中有关 service 资源的变动信息,需要跟 master 之上的 apiserver 交互,随时连接到 apiserver 上获取任何一 个与 service 资源相关的资源变动状态,这种是通过 kubernetes 中固有的一种请求方法 watch(监视) 来实现的,一旦有 service 资源的内容发生变动(如创建,删除),kube-proxy 都会将它转化成当前节点 之上的能够实现 service 资源调度,把我们请求调度到后端特定的 pod 资源之上的规则,这个规则可能 是 iptables,也可能是 ipvs,取决于 service 的实现方式。

# service简写:svc
[root@master rs]# kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15d
nginx LoadBalancer 10.99.134.18 <pending> 80:30367/TCP 6d14h
tomcat NodePort 10.96.66.199 <none> 8080:30080/TCP 6d15h

# 这些IP是ping不通的,这些ip的规则是保存在iptables或ipvs上的,通过kube-proxy负责流量转发之类

1.3 Service工作原理

k8s 在创建 Service 时,会根据标签选择器 selector(lable selector)来查找 Pod,据此创建与 Service 同名的 endpoint 对象,当 Pod 地址发生变化时,endpoint 也会随之发生变化,service 接收前 端 client 请求的时候,就会通过 endpoint,找到转发到哪个 Pod 进行访问的地址。(至于转发到哪个节 点的 Pod,由负载均衡 kube-proxy 决定)

# 删除service命令,kubernetes不能删除
[root@master rs]# kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15d

1.4 kubernetes集群中有三类IP地址

1、Node Network(节点网络):物理节点或者虚拟节点的网络,如 ens33 接口上的网路地址

2、Pod network(pod 网络),创建的 Pod 具有的 IP 地址

[root@master rs]# kubectl get pods -n default -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-v1-c5549578-fdk8b 1/1 Running 0 16h 10.244.75.230 monitor <none> <none>
myapp-v1-c5549578-x2f9f 1/1 Running 0 16h 10.244.75.229 monitor <none> <none>

  Node Network 和 Pod network 这两种网络地址是我们实实在在配置的,其中节点网络地址是配置在 节点接口之上,而 pod 网络地址是配置在 pod 资源之上的,因此这些地址都是配置在某些设备之上的, 这些设备可能是硬件,也可能是软件模拟的

3、Cluster Network(集群地址,也称为 service network),这个地址是虚拟的地址(virtual ip),没有配置在某个接口上,只是出现在 service 的规则当中。

# 没有绑定到任何一个网卡上
[root@master rs]# kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15d

1.5 Service资源清单编写技巧

# 查看定义service资源需要的字段有哪些?
[root@master rs]# kubectl explain service
KIND: Service
VERSION: v1 FIELDS:
apiVersion <string> # service资源使用的api组
kind <string> # 创建的资源类型
metadata <Object> # 定义元数据
spec <Object>
status <Object> # 查看service.spec字段如何定义
[root@master rs]# kubectl explain service.spec
KIND: Service
VERSION: v1 RESOURCE: spec <Object> FIELDS:
allocateLoadBalancerNodePorts <boolean>
clusterIP <string> #动态分配的地址,也可以自己在创建的时候指定,创建之后就不能修改
clusterIPs <[]string>
externalIPs <[]string>
externalName <string>
externalTrafficPolicy <string>
healthCheckNodePort <integer>
ipFamilies <[]string>
ipFamilyPolicy <string>
loadBalancerIP <string>
loadBalancerSourceRanges <[]string>
ports <[]Object> #定义 service 端口,用来和后端 pod 建立联系
publishNotReadyAddresses <boolean>
selector <map[string]string> #通过标签选择器选择关联的 pod 有哪些
sessionAffinity <string>
sessionAffinityConfig <Object>
#service 在实现负载均衡的时候还支持 sessionAffinity,sessionAffinity
什么意思?会话联系,默认是 none,随机调度的(基于 iptables 规则调度的);如果我们定义sessionAffinity 的 client ip,那就表示把来自同一客户端的IP请求调度到同一个pod上

topologyKeys <[]string>
type <string> #定义 service 的类型

1.5.1 Service的四种类型

[root@master rs]# kubectl explain service.spec.type
FIELD: type <string>
默认可选四种类型:Defaults to ClusterIP:ExternalName, ClusterIP, NodePort, LoadBalancer. 1.ExternalName
适用于 k8s 集群内部容器访问外部资源,它没有 selector,也没有定义任何的端口和 Endpoint。
以下 Service 定义的是将 prod 名称空间中的 my-service 服务映射到 my.database.example.com kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com 当查询主机 my-service.prod.svc.cluster.local 时,群集 DNS 将返回值为my.database.example.com 的 CNAME 记录。
service 的 FQDN 是: <service_name>.<namespace>.svc.cluster.local
my-service.prod. svc.cluster.local 2.ClusterIP
通过 k8s 集群内部 IP 暴露服务,选择该值,服务只能够在集群内部访问,这也是默认的ServiceType。
[root@master rs]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 15d
metrics-server ClusterIP 10.105.85.228 <none> 443/TCP 6d15h
springboot NodePort 10.105.225.164 <none> 8080:31180/TCP,8081:31181/TCP 2d6h 3.NodePort
通过每个 Node 节点上的 IP 和静态端口暴露 k8s 集群内部的服务。通过请求<NodeIP>:<NodePort>可以把请求代理到内部的 pod。
Client----->NodeIP:NodePort----->Service Ip:ServicePort----->PodIP:ContainerPort。 4.LoadBalancer
使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和ClusterIP 服务。

1.5.2 Service的端口

[root@master rs]# kubectl explain service.spec.ports
FIELDS:
appProtocol <string>
name <string> #定义端口的名字
nodePort <integer> # 宿主机上映射的端口,比如一个 Web 应用需要被 k8s 集群之外的其他用户访问,那么需要配置type=NodePort,若配置 nodePort=30001,那么其他机器就可以通过浏览器访问 scheme://k8s
集群中的任何一个节点 ip:30001即可访问到该服务,例如 http://192.168.199.131:30001。如果在 k8s 中部署MySQL 数据库,MySQL 可能不需要被外界访问,只需被内部服务访问,
那么就不需要设置 NodePort
port <integer> -required- #service 的端口,这个是 k8s 集群内部服务可访问的端口
protocol <string>
targetPort <string> # targetPort 是 pod 上的端口,从 port 和 nodePort 上来的流量,经过 kube-proxy 流入到后端 pod的 targetPort 上,最后进入容器。
与制作容器时暴露的端口一致(使用 DockerFile 中的 EXPOSE),例如:官方的 nginx 暴露 80 端口。

1.5.3 创建Service:type类型是ClusterIP

# 1.创建pod
# 将nginx镜像导入:docker load -i nginx.tar.gz
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
namespace: default
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
startupProbe:
periodSeconds: 5
initialDelaySeconds: 60
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /
livenessProbe:
periodSeconds: 5
initialDelaySeconds: 60
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /
readinessProbe:
periodSeconds: 5
initialDelaySeconds: 60
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /

pod_test.yaml


[root@master service]# kubectl apply -f nginx.yaml
deployment.apps/my-nginx created
[root@master service]# kubectl get deploy -n default
NAME READY UP-TO-DATE AVAILABLE AGE
my-nginx 0/2 2 0 16s
[root@master service]# kubectl get pods -n default -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-69f769d56f-kxjpl 1/1 Running 0 61s 10.244.75.232 monitor <none> <none>
my-nginx-69f769d56f-q7z62 1/1 Running 0 61s 10.244.75.231 monitor <none> <none> # node上请求pod的地址 curl 10.244.75.232和curl 10.244.75.231都能请求到nginx 官网
# 登录到其中一个pod,请求另外一个,也可以请求到
[root@master service]# kubectl exec -n default -it my-nginx-69f769d56f-kxjpl -- /bin/bash
root@my-nginx-69f769d56f-kxjpl:/# curl 10.244.75.231 需要注意的是,pod 虽然定义了容器端口,但是不会使用调度到该节点上的 80 端口,也不会使用任何特定的 NAT 规则去路由流量到 Pod 上。 这意味着可以在同一个节点上运行多个 Pod,使用相同的容器端口,
并且可以从集群中任何其他的 Pod 或节点上使用 IP 的方式访问到它们。 # 随机删除一个pod,也会生成
[root@master service]# kubectl delete pods -n default my-nginx-69f769d56f-kxjpl
pod "my-nginx-69f769d56f-kxjpl" deleted
[root@master service]# kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
my-nginx-69f769d56f-m7ngz 1/1 Running 0 25s
my-nginx-69f769d56f-q7z62 1/1 Running 0 10m # 查看pod具有的标签
[root@master service]# kubectl get pods -n default --show-labels
NAME READY STATUS RESTARTS AGE LABELS
my-nginx-69f769d56f-m7ngz 1/1 Running 0 9m10s pod-template-hash=69f769d56f,run=my-nginx
my-nginx-69f769d56f-q7z62 1/1 Running 0 19m pod-template-hash=69f769d56f,run=my-nginx
2.创建service
apiVersion: v1
kind: Service
metadata:
name: my-nginx
namespace: default
labels:
run: my-service
spec:
type: ClusterIP
ports:
- port: 80 # service的端口,暴露给k8s集群内部服务访问
protocol: TCP
targetPort: 80 # pod容器中定义的端口
selector:
run: my-nginx # 选择关联拥有run=my-nginx的pod 上述 yaml 文件将创建一个 Service,具有标签 run=my-nginx 的 Pod,目标 TCP 端口 80,并且在一个抽象的 Service 端口(targetPort:容器接收流量的端口;port:抽象的 Service 端口,
可以使任何其它 Pod 访问该 Service 的端口)上暴露。 [root@master service]# kubectl apply -f service_test.yaml
service/my-nginx created
[root@master service]# kubectl get pods -n default -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-69f769d56f-m7ngz 1/1 Running 0 33m 10.244.135.31 node3 <none> <none>
my-nginx-69f769d56f-q7z62 1/1 Running 0 44m 10.244.75.231 monitor <none> <none> [root@master service]# kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15d
my-nginx ClusterIP 10.99.173.55 <none> 80/TCP 13m [root@master service]# kubectl get endpoints -n default
NAME ENDPOINTS AGE
kubernetes 192.168.199.131:6443 15d
my-nginx 10.244.135.31:80,10.244.75.231:80 13m #在 k8s 控制节点访问 service 的 ip:端口就可以把请求代理到后端 pod
# 访问service,就会将端口转发到后面的my-nginx上
curl 10.99.173.55 #通过上面可以看到请求 service IP:port 跟直接访问 pod ip:port 看到的结果一样,这就说明service 可以把请求代理到它所关联的后端 pod
注意:上面的 10.99.173.55:80 地址只能是在 k8s 集群内部可以访问,在外部无法访问,比方说我们想要通过浏览器访问,那么是访问不通的,如果想要在 k8s 集群之外访问,
是需要把 service type 类型改成 nodePort 的 # 查看service详细信息
[root@master service]# kubectl describe svc -n default my-nginx
Name: my-nginx
Namespace: default
Labels: run=my-service
Annotations: <none>
Selector: run=my-nginx
Type: ClusterIP
IP Families: <none>
IP: 10.99.173.55
IPs: 10.99.173.55
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.135.31:80,10.244.75.231:80
Session Affinity: None
Events: <none> # endpoint简写ep
[root@master service]# kubectl get ep -n default my-nginx
NAME ENDPOINTS AGE
my-nginx 10.244.135.31:80,10.244.75.231:80 26m service 可以对外提供统一固定的 ip 地址,并将请求重定向至集群中的 pod。其中“将请求重定向至集群中的 pod”就是通过 endpoint 与 selector 协同工作实现。selector 是用于选择 pod,
由selector 选择出来的 pod 的 ip 地址和端口号,将会被记录在 endpoint 中。endpoint 便记录了所有 pod的 ip 地址和端口号。当一个请求访问到 service 的 ip 地址时,就会从 endpoint 中选择出一个 ip 地址
和端口号,然后将请求重定向至 pod 中。具体把请求代理到哪个 pod,需要的就是 kube-proxy 的轮询实现的。service 不会直接到 pod,service 是直接到 endpoint 资源,就是地址加端口,再由 endpoint 再关联到 pod。
service 只要创建完成,我们就可以直接解析它的服务名,每一个服务创建完成后都会在集群 dns 中动态添加一个资源记录,添加完成后我们就可以解析了,资源记录格式是: SVC_NAME.NS_NAME.DOMAIN.LTD.
服务名.命名空间.域名后缀
集群默认的域名后缀是 svc.cluster.local. 就像我们上面创建的 my-nginx 这个服务,它的完整名称解析就是
my-nginx.default.svc.cluster.local
[root@master service]# kubectl exec -n default -it my-nginx-69f769d56f-m7ngz -- /bin/sh
# curl my-nginx.default.svc.cluster.local
<!DOCTYPE html>
...
# cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

 1.5.4 创建Service:type 类型是NodePort

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-nodeport
namespace: default
spec:
selector:
matchLabels:
run: my-nginx-nodeport
replicas: 2
template:
metadata:
labels:
run: my-nginx-nodeport
spec:
containers:
- name: my-nginx-nodeport-container
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80 [root@master service]# kubectl apply -f pod_nodeport.yaml
deployment.apps/my-nginx-nodeport created
[root@master service]# kubectl get deploy -n default
NAME READY UP-TO-DATE AVAILABLE AGE
my-nginx-nodeport 2/2 2 2 38s [root@master ~]# kubectl get pods -n default --show-labels
NAME READY STATUS RESTARTS AGE LABELS
my-nginx-nodeport-649c945f85-f6w96 1/1 Running 0 108s pod-template-hash=649c945f85,run=my-nginx-nodeport
my-nginx-nodeport-649c945f85-wcn76 1/1 Running 0 108s pod-template-hash=649c945f85,run=my-nginx-nodeport [root@master service]# cat service_nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx-nodeport
labels:
run: my-nginx-nodeport
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 80
nodePort: 30380
selector:
run: my-nginx-nodeport [root@master service]# kubectl apply -f service_nodeport.yaml
service/my-nginx-nodeport created
[root@master service]# kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15d
my-nginx ClusterIP 10.99.173.55 <none> 80/TCP 3h59m
my-nginx-nodeport NodePort 10.110.86.112 <none> 80:30380/TCP 7s [root@master service]# kubectl describe svc -n default my-nginx-nodeport
Name: my-nginx-nodeport
Namespace: default
Labels: run=my-nginx-nodeport
Annotations: <none>
Selector: run=my-nginx-nodeport
Type: NodePort
IP Families: <none>
IP: 10.110.86.112
IPs: 10.110.86.112
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 30380/TCP
Endpoints: 10.244.135.32:80,10.244.75.233:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none> curl 10.110.86.112 访问正常
curl http://192.168.199.131:30380请求也可以,浏览器请求也行 服务请求走向:
Client-→node ip:30380->service ip:80-→pod ip:container port
Client ->192.168.199.131:30380->10.110.86.112:80->pod ip:80

1.5.5 创建Service:type类型是ExternalName

应用场景:跨名称空间访问

需求:default 名称空间下的 client 服务想要访问 nginx-ns 名称空间下的 nginx-svc 服务

# 1. 节点导入busybox.tar.gz
# default名称空间下的测试节点
[root@master service]# cat client.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: client
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
containers:
- name: busybox
image: busybox
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 3600"] [root@master service]# kubectl apply -f client.yaml
deployment.apps/client created
[root@master service]# kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
client-5cdd445b4f-d6gnz 1/1 Running 0 31s
[root@master service]# kubectl get deploy -n default
NAME READY UP-TO-DATE AVAILABLE AGE
client 1/1 1 1 105s

# client的service服务
[root@master service]# cat client_svc.yaml
apiVersion: v1
kind: Service
metadata:
name: client-svc
namespace: default
spec:
type: ExternalName
externalName: nginx-svc.nginx-ns.svc.cluster.local
ports:
- name: http
port: 80
targetPort: 80
#该文件中指定了到 nginx-svc 的软链,让使用者感觉就好像调用自己命名空间的服务一样。 [root@master service]# kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
client-svc ExternalName <none> nginx-svc.nginx-ns.svc.cluster.local 80/TCP 9m14s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15d [root@master service]# kubectl describe svc -n default client-svc
Name: client-svc
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ExternalName
IP Families: <none>
IP:
IPs: <none>
External Name: nginx-svc.nginx-ns.svc.cluster.local
Port: http 80/TCP
TargetPort: 80/TCP
Endpoints: <none>
Session Affinity: None
Events: <none> # 创建nginx-ns名称空间
[root@master service]# kubectl create ns nginx-ns
namespace/nginx-ns created [root@master service]# kubectl apply -f server_nginx.yaml
deployment.apps/nginx created

[root@master service]# kubectl get pods -n nginx-ns -o wide
  NAME            READY STATUS RESTARTS AGE IP        NODE    NOMINATED NODE    READINESS GATES
  nginx-7cf7d6dbc8-p4w9g 1/1  Running 0     26m 10.244.75.235 monitor  <none>         <none>

[root@master service]# cat server_nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: nginx-ns
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent [root@master service]# cat nginx_svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: nginx-ns
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80 [root@master service]# kubectl apply -f nginx_svc.yaml
service/nginx-svc created
[root@master service]# kubectl get svc -n nginx-ns
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc ClusterIP 10.107.69.0 <none> 80/TCP 16s [root@master service]# kubectl describe svc nginx-svc -n nginx-ns
Name: nginx-svc
Namespace: nginx-ns
Labels: <none>
Annotations: <none>
Selector: app=nginx
Type: ClusterIP
IP Families: <none>
IP: 10.107.69.0
IPs: 10.107.69.0
Port: http 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.75.235:80
Session Affinity: None
Events: <none>
[root@master service]# kubectl get pods -o wide -n nginx-ns
\NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-7cf7d6dbc8-p4w9g 1/1 Running 0 10m 10.244.75.235 monitor <none> <none> [root@master service]# kubectl exec -n default -it client-5cdd445b4f-d6gnz -- /bin/sh
/ # wget -q -O - nginx-svc.nginx-ns.svc.cluster.local
可以访问nginx页面
/ # wget -q -O - client-svc.default.svc.cluster.local
可以访问到nginx页面

 1.5.6 k8s最佳实践:映射外部服务案例分享

场景 1:k8s 集群引用外部的 mysql 数据库
# 在node3节点安装数据库
[root@node3 ~]# yum install mariadb-server.x86_64 -y
[root@node3 ~]# systemctl start mariadb [root@master service]# cat mysql_service.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: default

spec:
type: ClusterIP
ports:
- port: 3306 [root@master service]# kubectl apply -f mysql_service.yaml
service/mysql created
[root@master service]# kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
client-svc ExternalName <none> nginx-svc.nginx-ns.svc.cluster.local 80/TCP 41m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15d
mysql ClusterIP 10.108.140.119 <none> 3306/TCP 8s [root@master service]# kubectl describe svc mysql -n default
Name: mysql
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP Families: <none>
IP: 10.108.140.119
IPs: 10.108.140.119
Port: <unset> 3306/TCP
TargetPort: 3306/TCP
Endpoints: <none>
Session Affinity: None
Events: <none> # 自动以Endpoint资源
[root@master service]# cat mysql_endpoint.yaml
apiVersion: v1
kind: Endpoints
metadata:
name: mysql # mysql_service名称一样,就会被它捕捉到
namespace: default
subsets:
- addresses:
- ip: 192.168.199.132
ports:
- port: 3306 [root@master service]# kubectl apply -f mysql_endpoint.yaml
endpoints/mysql created
[root@master service]# kubectl get ep -n default
NAME ENDPOINTS AGE
kubernetes 192.168.199.131:6443 15d
mysql 192.168.199.132:3306 11s [root@master service]# kubectl describe svc mysql -n default
Name: mysql
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP Families: <none>
IP: 10.108.140.119
IPs: 10.108.140.119
Port: <unset> 3306/TCP
TargetPort: 3306/TCP
Endpoints: 192.168.199.132:3306
Session Affinity: None
Events: <none> 上面配置就是将外部 IP 地址和服务引入到 k8s 集群内部,由 service 作为一个代理来达到能够访问外部服务的目的。

二、Service代理:kube-proxy组件详解

2.1 kube-proxy组件介绍

Kubernetes service 只是把应用对外提供服务的方式做了抽象,真正的应用跑在 Pod 中的 container 里,我们的请求转到 kubernetes nodes 对应的 nodePort 上,那么 nodePort 上的请求是如何 进一步转到提供后台服务的 Pod 的呢? 就是通过 kube-proxy 实现的:

kube-proxy 部署在 k8s 的每一个 Node 节点上,是 Kubernetes 的核心组件,我们创建一个 service 的时候,kube-proxy 会在 iptables 中追加一些规则,为我们实现路由与负载均衡的功能。在 k8s1.8 之 前,kube-proxy 默认使用的是 iptables 模式,通过各个 node 节点上的 iptables 规则来实现 service 的 负载均衡,但是随着 service 数量的增大,iptables 模式由于线性查找匹配、全量更新等特点,其性能 会显著下降。

从 k8s 的 1.8 版本开始,kube-proxy 引入了 IPVS 模式,IPVS 模式与 iptables 同样基于 Netfilter,但是采用的 hash 表,因此当 service 数量达到一定规模时,hash 查表的速度优势就会显现 出来,从而提高 service 的服务性能。

service 是一组 pod 的服务抽象,相当于一组 pod 的 LB,负责将请求分发给对应的 pod。service 会 为这个 LB 提供一个 IP,一般称为 cluster IP。kube-proxy 的作用主要是负责 service 的实现,具体来 说,就是实现了内部从 pod 到 service 和外部的从 node port 向 service 的访问。

1、kube-proxy 其实就是管理 service 的访问入口,包括集群内 Pod 到 Service 的访问和集群外访问 service。

2、kube-proxy 管理 sevice 的 Endpoints,该 service 对外暴露一个 Virtual IP,也可以称为是 Cluster IP, 集群内通过访问这个 Cluster IP:Port 就能访问到集群内对应的 serivce 下的 Pod。

2.2 kube-proxy三种工作模式

1.Userspace模式(之后已经取消了)

Client Pod 要访问 Server Pod 时,它先将请求发给内核空间中的 service iptables 规则,由它再 将请求转给监听在指定套接字上的 kube-proxy 的端口,kube-proxy 处理完请求,并分发请求到指定Server Pod 后,再将请求转发给内核空间中的 service ip,由 service iptables 将请求转给各个节点中 的 Server Pod。

这个模式有很大的问题,客户端请求先进入内核空间的,又进去用户空间访问 kube-proxy,由 kube-proxy 封装完成后再进去内核空间的 iptables,再根据 iptables 的规则分发给各节点的用户空间 的 pod。由于其需要来回在用户空间和内核空间交互通信,因此效率很差。在 Kubernetes 1.1 版本之 前,userspace 是默认的代理模型。

2.iptables 方式

客户端 IP 请求时,直接请求本地内核 service ip,根据 iptables 的规则直接将请求转发到到各 pod 上,因为使用 iptable NAT 来完成转发,也存在不可忽视的性能损耗。另外,如果集群中存上万的 Service/Endpoint,那么 Node 上的 iptables rules 将会非常庞大,性能还会再打折 iptables 代理模式由 Kubernetes 1.1 版本引入,自 1.2 版本开始成为默认类型。

3.ipvs模式

Kubernetes 自 1.9-alpha 版本引入了 ipvs 代理模式,自 1.11 版本开始成为默认设置。客户端 请求时到达内核空间时,根据 ipvs 的规则直接分发到各 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:不排队调度

如果某个服务后端 pod 发生变化,标签选择器适应的 pod 又多一个,适应的信息会立即反映到 apiserver 上,而 kube-proxy 一定可以 watch 到 etc 中的信息变化,而将它立即转为 ipvs 或者 iptables 中的规则,这一切都是动态和实时的,删除一个 pod 也是同样的原理。如图:

注: 以上不论哪种,kube-proxy 都通过 watch 的方式监控着 apiserver 写入 etcd 中关于 Pod 的最新状 态信息,它一旦检查到一个 Pod 资源被删除了或新建了,它将立即将这些变化,反应在iptables 或 ipvs 规则中,以便 iptables 和 ipvs 在调度 Clinet Pod 请求到 Server Pod 时,不会出现 Server Pod不存在 的情况。自 k8s1.11 以后,service 默认使用 ipvs 规则,若 ipvs 没有被激活,则降级使用 iptables 规 则.

2.3 kube-proxy生成的iptables规则分析

2.3.1、service 的 type 类型是 ClusterIp,iptables 规则分析

在 k8s 创建的 service,虽然有 ip 地址,但是 service 的 ip 是虚拟的,不存在物理机上的,是在iptables 或者 ipvs 规则里的。
[root@master service]# kubectl apply -f nginx_test.yaml
deployment.apps/my-nginx created
[root@master service]# kubectl apply -f service_test.yaml
service/my-nginx created [root@master service]# kubectl get svc -n default -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
client-svc ExternalName <none> nginx-svc.nginx-ns.svc.cluster.local 80/TCP 91m <none>
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15d <none>
my-nginx ClusterIP 10.107.231.10 <none> 80/TCP 61s run=my-nginx
mysql ClusterIP 10.108.140.119 <none> 3306/TCP 49m <none>
[root@master service]# kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
client-5cdd445b4f-d6gnz 1/1 Running 1 122m
my-nginx-69f769d56f-pmsgv 1/1 Running 0 17s
my-nginx-69f769d56f-pzntv 1/1 Running 0 17s [root@master service]# kubectl get svc -n default -l run=my-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.107.231.10 <none> 80/TCP 2m15s
[root@master service]# kubectl get pods -n default -l run=my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-69f769d56f-pmsgv 1/1 Running 0 110s 10.244.135.33 node3 <none> <none>
my-nginx-69f769d56f-pzntv 1/1 Running 0 110s 10.244.75.236 monitor <none> <none> [root@master service]# iptables -t nat -L | grep "10.107.231.10" KUBE-MARK-MASQ tcp -- !10.244.0.0/16 10.107.231.10 /* default/my-nginx cluster IP */ tcp dpt:http
KUBE-SVC-L65ENXXZWWSAPRCR tcp -- anywhere 10.107.231.10 /* default/my-nginx cluster IP */ tcp dpt:http [root@master service]# iptables -t nat -L | grep KUBE-SVC-L65ENXXZWWSAPRCR
KUBE-SVC-L65ENXXZWWSAPRCR tcp -- anywhere 10.107.231.10 /* default/my-nginx cluster IP */ tcp dpt:http
Chain KUBE-SVC-L65ENXXZWWSAPRCR (1 references) [root@master service]# iptables -t nat -L | grep 10.244.135.33
KUBE-MARK-MASQ all -- 10.244.135.33 anywhere /* default/my-nginx */
DNAT tcp -- anywhere anywhere /* default/my-nginx */ tcp to:10.244.135.33:80 # type类型是ClusterIP,流量来了后,先经过KUBE-MARK-MASQ做标记,再由KUBE-SVC-L65ENXXZWWSAPRCR接收请求向后做DNAT转发到my-nginx #通过上面可以看到之前创建的 service,会通过 kube-proxy 在 iptables 中生成一个规则,来实现流量路由,有一系列目标为 KUBE-SVC-xxx 链的规则,每条规则都会匹配某个目标 ip 与端口。
也就是说访问某个 ip:port 的请求会由 KUBE-SVC-xxx 链来处理。这个目标 IP 其实就是 service ip。

2.3.2 service 的 type 类型是 nodePort,iptables 规则分析


[root@master service]# kubectl get pods -n default -l run=my-nginx-nodeport -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-nodeport-649c945f85-hc8pb 1/1 Running 0 7m4s 10.244.135.35 node3 <none> <none>
my-nginx-nodeport-649c945f85-lvcd9 1/1 Running 0 7m4s 10.244.135.34 node3 <none> <none>

[root@master service]# kubectl get svc -n default -l run=my-nginx-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx-nodeport NodePort 10.111.23.182 <none> 80:30380/TCP 46s [root@master ~]# iptables -t nat -S | grep 30380
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-SVC-J5QV2XWG4FEBPH3Q [root@master ~]# iptables -t nat -S | grep KUBE-SVC-J5QV2XWG4FEBPH3Q
-N KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-SERVICES -d 10.111.23.182/32 -p tcp -m comment --comment "default/my-nginx-nodeport cluster IP" -m tcp --dport 80 -j KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-R65QDLMGRR2R54VA
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -j KUBE-SEP-55EMS7DNNB2C4GL4 [root@master ~]# iptables -t nat -S |grep KUBE-SEP-R65QDLMGRR2R54VA
-N KUBE-SEP-R65QDLMGRR2R54VA
-A KUBE-SEP-R65QDLMGRR2R54VA -s 10.244.135.34/32 -m comment --comment "default/my-nginx-nodeport" -j KUBE-MARK-MASQ
-A KUBE-SEP-R65QDLMGRR2R54VA -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp -j DNAT --to-destination 10.244.135.34:80
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-R65QDLMGRR2R54VA

2.4 Service服务发现:coredns组件详解

CoreDNS?

CoreDNS 其实就是一个 DNS 服务,而 DNS 作为一种常见的服务发现手段,所以很多开源项目以及 工程师都会使用 CoreDNS 为集群提供服务发现的功能,Kubernetes 就在集群中使用 CoreDNS 解决服务 发现的问题。 作为一个加入 CNCF(Cloud Native Computing Foundation)的服务, CoreDNS 的实现 非常简单。

验证coredns:

[root@master service]# cat dig.yaml
apiVersion: v1
kind: Pod
metadata:
name: dig
namespace: default
spec:
containers:
- name: dig
image: xianchao/dig:latest
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always [root@master service]# kubectl apply -f dig.yaml
pod/dig created
[root@master service]# kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
client-5cdd445b4f-d6gnz 1/1 Running 2 169m
dig 1/1 Running 0 11s
[root@master service]# kubectl exec -n default -it dig -- /bin/sh
/ # nslookup kubernetes
Server: 10.96.0.10
Address: 10.96.0.10#53 Name: kubernetes.default.svc.cluster.local
Address: 10.96.0.1 / # cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

K8s应用---Service代理和kube-proxy转发(9)的更多相关文章

  1. 使用Nginx反向代理和proxy_cache缓存搭建CDN服务器加快Web访问速度

    碰到问题:移动用户访问web服务器www.osyunwei.com很慢解决办法:1.在移动机房放置一台nginx反向代理服务器2.通过域名DNS智能解析,所有移动用户访问www.osyunwei.co ...

  2. SpringAOP-JDK 动态代理和 CGLIB 代理

    在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类. 1.JDK 动态代理 那么接 ...

  3. 通过一个工具类更深入理解动态代理和Threadlocal

    动态代理和Threadlocal 一个代理类返回指定的接口,将方法调用指定的调用处理程序的代理类的实例.返回的是一个代理类,由指定的类装载器的定义和实现指定接口指定代理实例调用处理程序最近用到一个工具 ...

  4. 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。

    基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...

  5. Spring 静态代理+JDK动态代理和CGLIB动态代理

    代理分为两种:静态代理 动态代理 静态代理:本质上会在硬盘上创建一个真正的物理类 动态代理:本质上是在内存中构建出一个类. 如果多个类需要进行方法增强,静态代理则需要创建多个物理类,占用磁盘空间.而动 ...

  6. Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。

    借鉴:http://jinnianshilongnian.iteye.com/blog/1508018 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional ...

  7. Spring <tx:annotation-driven>注解 JDK动态代理和CGLIB动态代理 区别。

    基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...

  8. java的静态代理、jdk动态代理和cglib动态代理

    Java的代理就是客户端不再直接和委托类打交道,而是通过一个中间层来访问,这个中间层就是代理.使用代理有两个好处,一是可以隐藏委托类的实现:二是可以实现客户与委托类之间的解耦,在不修改委托类代码的情况 ...

  9. JAVA高级架构师基础功:Spring中AOP的两种代理方式:动态代理和CGLIB详解

    在spring框架中使用了两种代理方式: 1.JDK自带的动态代理. 2.Spring框架自己提供的CGLIB的方式. 这两种也是Spring框架核心AOP的基础. 在详细讲解上述提到的动态代理和CG ...

  10. 容器编排系统k8s之Service资源

    前文我们了解了k8s上的DemonSet.Job和CronJob控制器的相关话题,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14157306.html:今 ...

随机推荐

  1. 记录-vue项目中使用PWA

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前言: 梳理了一下项目中的PWA的相关用法,下面我会正对vue2和vue3的用法进行一些教程示例,引入离线缓存机制,即使你断网,也能访问页 ...

  2. 记录--这样封装列表 hooks,一天可以开发 20 个页面

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 这样封装列表 hooks,一天可以开发 20 个页面 前言 在做移动端的需求时,我们经常会开发一些列表页,这些列表页大多数有着相似的功能: ...

  3. FFmpeg开发笔记(四)FFmpeg的动态链接库介绍

    FFmpeg不仅提供了ffmpeg.ffplay和ffprobe三个可执行程序,还提供了八个工具库,使得开发者能够调用库里面的函数,从而实现更精准的定制化开发需求.这八个库的名字是avcodec.av ...

  4. AntvG6-graph图谱工具

    1 快速上手 1.1 在项目中使用 npm 包引入 Step 1: 使用命令行在项目目录下执行以下命令 npm install --save @antv/g6 Step 2: 在需要用的 G6 的 J ...

  5. 学习笔记-Kafka消息队列

    官网地址:https://kafka.apache.org/ 一.认识kafka 1.认识kafka Apache Kafka是Apache软件基金会的开源的流处理平台,该平台提供了消息的订阅与发布的 ...

  6. 英语文档阅读学习系列之ZYNQ-7000 All Programmable SOC Packaging and Pinout

    UG865-Zynq-7000-pkg-pinout 1.Table 一个overview和其他部分的构成一个整体. 2.overview This section describes the pin ...

  7. pymysql连接、关闭、查询,python如何操作mysql数据库

    1 def get_conn(): 2 """ 3 :return: 连接,游标 4 """ 5 # 创建连接 6 conn = pymys ...

  8. yml和properties打印SQL日志信息

    1.配置文件里面配置 第一种是properties类型如下 logging.level.com.datayes.mdi.dao.rdb.mommp.**=debug其中 com.datayes.mdi ...

  9. #整体二分,树状数组#洛谷 3332 [ZJOI2013]K大数查询

    题目 分析 虽然树套树也可以做,这里考虑整体二分, 对于二分的答案\(mid\),1操作实际上就是如果\(c>mid\)就给区间整体加1, 2操作即询问区间和是否超过\(k\),如果超过\(k\ ...

  10. 使用OHOS SDK构建mimalloc

    参照OHOS IDE和SDK的安装方法配置好开发环境. 从github下载源码. 执行如下命令: git clone https://github.com/microsoft/mimalloc.git ...