杨梅冲
每天在想什么呢?

一、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 作为一个代理来达到能够访问外部服务的目的。
测试结果,不管busybox属于哪个域名空间,都可以telnet通mysql.default.svc.cluster.local的3306端口,也可以远程登录,数据库显示的连接地址是内网IP地址,不会是k8s的地址
MariaDB [(none)]> select * from information_schema.processlist;
+----+----------------------+---------------------+------+---------+------+----------------------+----------------------------------------------+---------+-------+-----------+----------+
| ID | USER                 | HOST                | DB   | COMMAND | TIME | STATE                | INFO                                         | TIME_MS | STAGE | MAX_STAGE | PROGRESS |
+----+----------------------+---------------------+------+---------+------+----------------------+----------------------------------------------+---------+-------+-----------+----------+
|  3 | unauthenticated user | 192.168.10.11:42274 | NULL | Connect |    0 | login                | NULL                                         |   0.000 |     0 |         0 |    0.000 |
|  2 | root                 | localhost           | NULL | Query   |    0 | Filling schema table | select * from information_schema.processlist |   0.374 |     0 |         0 |    0.000 |
+----+----------------------+---------------------+------+---------+------+----------------------+----------------------------------------------+---------+-------+-----------+----------+


[root@master service]# kubectl get pod -n nginx-ns -o wide
NAME         READY   STATUS    RESTARTS   AGE   IP              NODE    NOMINATED NODE   READINESS GATES
mysql-test   1/1     Running   0          16m   10.244.104.19   node2   <none>           <none>

/ # telnet mysql.nginx-ns.svc.cluster.local 3306
R
5.5.68-MariaDB~tMb%1e CRP{>^?_Cp+$mysql_native_password

 二、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

 

posted on 2022-07-29 16:24  杨梅冲  阅读(2521)  评论(0编辑  收藏  举报