------> 课程视频同步分享在今日头条B站

Service、Endpoint、Endpoint Slices-00:45:33

K8s是运维人员的救星,为什么这么说呢,因为它里面的运行机制能确保你需要运行的服务,一直保持所期望的状态,还是以上面nginx服务举例,我们不能确保其pod运行的node节点什么时候会当掉,同时在pod环节也说过,pod的IP每次重启都会发生改变,所以我们不应该期望K8s的pod是健壮的,而是要按最坏的打算来假设服务pod中的容器会因为代码有bug、所以node节点不稳定等等因素发生故障而挂掉,这时候如果我们用的Deployment,那么它的controller会通过动态创建新pod到可用的node上,同时删除旧的pod来保证应用整体的健壮性;并且流量入口这块用一个能固定IP的service来充当抽象的内部负载均衡器,提供pod的访问,所以这里等于就是K8s成为了一个7 x 24小时在线处理服务pod故障的运维机器人。

nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: new-nginx
  labels:
    app: new-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: new-nginx
  template:
    metadata:
      labels:
        app: new-nginx
    spec:
      containers:
#--------------------------------------------------
      - name: new-nginx
        image: nginx:1.21.6
#        image: nginx:1.25.1
        env:
          - name: TZ
            value: Asia/Shanghai
        ports:
        - containerPort: 80
        volumeMounts:
          - name: html-files
            mountPath: "/usr/share/nginx/html"
#--------------------------------------------------
      - name: busybox
        image: registry.cn-shanghai.aliyuncs.com/acs/busybox:v1.29.2
#        image: nicolaka/netshoot
        args:
        - /bin/sh
        - -c
        - >
           while :; do
             if [ -f /html/index.html ];then
               echo "[$(date +%F\ %T)] ${MY_POD_NAMESPACE}-${MY_POD_NAME}-${MY_POD_IP}" > /html/index.html
               sleep 1
             else
               touch /html/index.html
             fi
           done
        env:
          - name: TZ
            value: Asia/Shanghai
          - name: MY_POD_NAME
            valueFrom:
              fieldRef:
                apiVersion: v1
                fieldPath: metadata.name
          - name: MY_POD_NAMESPACE
            valueFrom:
              fieldRef:
                apiVersion: v1
                fieldPath: metadata.namespace
          - name: MY_POD_IP
            valueFrom:
              fieldRef:
                apiVersion: v1
                fieldPath: status.podIP
        volumeMounts:
          - name: html-files
            mountPath: "/html"
          - mountPath: /etc/localtime
            name: tz-config

#--------------------------------------------------
      volumes:
        - name: html-files
          emptyDir:
            medium: Memory
            sizeLimit: 10Mi
        - name: tz-config
          hostPath:
            path: /usr/share/zoneinfo/Asia/Shanghai

创建一个service服务来提供固定IP轮巡访问上面创建的nginx服务的2个pod(nodeport)

# 给这个nginx的deployment生成一个service(简称svc)
# 同时也可以用生成yaml配置的形式来创建 kubectl expose deployment new-nginx --port=80 --target-port=80 --dry-run=client -o yaml
# 我们可以先把上面的yaml配置导出为svc.yaml提供后面,这里就直接用命令行创建了
# kubectl expose deployment new-nginx --port=80 --target-port=80
service/new-nginx exposed

# kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.68.0.1      <none>        443/TCP   4d23h
new-nginx        ClusterIP   10.68.18.121   <none>        80/TCP    5s

# 看下自动关联生成的endpoint
# kubectl  get endpoints nginx 
NAME    ENDPOINTS                       AGE
nginx   172.20.139.72:80,172.20.217.72:80   27s

# 接下来测试下svc的负载均衡效果吧
root@node-1:~# curl 10.68.211.139
[2023-11-07 13:31:35] default-new-nginx-854cf59647-j5fph-172.20.217.98
root@node-1:~# curl 10.68.211.139
[2023-11-07 13:31:37] default-new-nginx-854cf59647-7njcp-172.20.139.90



# 修改svc的类型来提供外部访问
# kubectl patch svc nginx -p '{"spec":{"type":"NodePort"}}'
service/nginx patched

# kubectl  get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.68.0.1     <none>        443/TCP        3d21h
nginx        NodePort    10.68.86.85   <none>        80:33184/TCP   30m

[root@node-2 ~]# curl 10.0.1.201:20651
nginx-f89759699-bzwd2
[root@node-2 ~]# curl 10.0.1.201:20651
nginx-f89759699-qlc8q

我们这里也来分析下这个svc的yaml配置

cat svc.yaml

apiVersion: v1       # <<<<<<  v1 是 Service 的 apiVersion
kind: Service        # <<<<<<  指明当前资源的类型为 Service
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx       # <<<<<<  Service 的名字为 nginx
spec:
  ports:
  - port: 80        # <<<<<<  将 Service 的 80 端口映射到 Pod 的 80 端口,使用 TCP 协议
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx     # <<<<<<  selector 指明挑选那些 label 为 run: nginx 的 Pod 作为 Service 的后端
status:
  loadBalancer: {}

我们来看下这个nginx的svc描述

# kubectl describe svc nginx 
Name:                     nginx
Namespace:                default
Labels:                   app=nginx
Annotations:              <none>
Selector:                 app=nginx
Type:                     NodePort
IP:                       10.68.18.121
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  20651/TCP
Endpoints:                172.20.139.72:80,172.20.217.72:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

我们可以看到在Endpoints列出了2个pod的IP和端口,pod的ip是在容器中配置的,那么这里Service cluster IP又是在哪里配置的呢?cluster ip又是自律映射到pod ip上的呢?

# 首先看下kube-proxy的配置
# cat /etc/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Kube-Proxy Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
WorkingDirectory=/var/lib/kube-proxy
ExecStart=/opt/kube/bin/kube-proxy \
  --config=/var/lib/kube-proxy/kube-proxy-config.yaml
Restart=always
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target


# cat /var/lib/kube-proxy/kube-proxy-config.yaml
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
bindAddress: 0.0.0.0
clientConnection:
  kubeconfig: "/etc/kubernetes/kube-proxy.kubeconfig"
# 根据clusterCIDR 判断集群内部和外部流量,配置clusterCIDR选项后,kube-proxy 会对访问 Service IP 的请求做 SNAT
clusterCIDR: "172.20.0.0/16"
conntrack:
  maxPerCore: 32768
  min: 131072
  tcpCloseWaitTimeout: 1h0m0s
  tcpEstablishedTimeout: 24h0m0s
healthzBindAddress: 0.0.0.0:10256
# hostnameOverride 值必须与 kubelet 的对应一致,否则 kube-proxy 启动后会找不到该 Node,从而不会创建任何 iptables 规则
hostnameOverride: "10.0.1.201"
metricsBindAddress: 0.0.0.0:10249
mode: "ipvs"        #<-------  我们在最开始部署kube-proxy的时候就设定它的转发模式为ipvs,因为默认的iptables在存在大量svc的情况下性能很低
ipvs:
  excludeCIDRs: null
  minSyncPeriod: 0s
  scheduler: ""
  strictARP: False
  syncPeriod: 30s
  tcpFinTimeout: 0s
  tcpTimeout: 0s
  udpTimeout: 0s






# 看下本地网卡,会有一个ipvs的虚拟网卡
# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:20:b8:39 brd fe:fe:fe:fe:fe:ff
    inet 10.0.1.202/24 brd 10.0.1.255 scope global noprefixroute ens32
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29fe:fe20:b839/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:91:ac:ce:13 brd fe:fe:fe:fe:fe:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
4: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 22:50:98:a6:f9:e4 brd fe:fe:fe:fe:fe:ff
5: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default 
    link/ether 96:6b:f0:25:1a:26 brd fe:fe:fe:fe:fe:ff
    inet 10.68.0.2/32 brd 10.68.0.2 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.68.0.1/32 brd 10.68.0.1 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.68.120.201/32 brd 10.68.120.201 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.68.50.42/32 brd 10.68.50.42 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.68.18.121/32 brd 10.68.18.121 scope global kube-ipvs0    # <-------- SVC的IP配置在这里
       valid_lft forever preferred_lft forever
6: caliaeb0378f7a4@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether ee:ee:ee:ee:ee:ee brd fe:fe:fe:fe:fe:ff link-netnsid 0
    inet6 fe80::ecee:eefe:feee:eeee/64 scope link 
       valid_lft forever preferred_lft forever
7: tunl0@NONe: <NOARP,UP,LOWER_UP> mtu 1440 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 172.20.247.0/32 brd 172.20.247.0 scope global tunl0
       valid_lft forever preferred_lft forever
       
       
       


# 来看下lvs的虚拟服务器列表
# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  172.17.0.1:20651 rr
  -> 172.20.139.72:80             Masq    1      0          0         
  -> 172.20.217.72:80             Masq    1      0          0         
TCP  172.20.247.0:20651 rr
  -> 172.20.139.72:80             Masq    1      0          0         
  -> 172.20.217.72:80             Masq    1      0          0         
TCP  10.0.1.202:20651 rr
  -> 172.20.139.72:80             Masq    1      0          0         
  -> 172.20.217.72:80             Masq    1      0          0         
TCP  10.68.0.1:443 rr
  -> 10.0.1.201:6443              Masq    1      0          0         
  -> 10.0.1.202:6443              Masq    1      3          0         
TCP  10.68.0.2:53 rr
  -> 172.20.247.2:53              Masq    1      0          0         
TCP  10.68.0.2:9153 rr
  -> 172.20.247.2:9153            Masq    1      0          0         
TCP  10.68.18.121:80 rr                                        #<-----------   SVC转发Pod的明细在这里
  -> 172.20.139.72:80             Masq    1      0          0         
  -> 172.20.217.72:80             Masq    1      0          0         
TCP  10.68.50.42:443 rr
  -> 172.20.217.71:4443           Masq    1      0          0         
TCP  10.68.120.201:80 rr
  -> 10.0.1.201:80                Masq    1      0          0         
  -> 10.0.1.202:80                Masq    1      0          0         
TCP  10.68.120.201:443 rr
  -> 10.0.1.201:443               Masq    1      0          0         
  -> 10.0.1.202:443               Masq    1      0          0         
TCP  10.68.120.201:10254 rr
  -> 10.0.1.201:10254             Masq    1      0          0         
  -> 10.0.1.202:10254             Masq    1      0          0         
TCP  127.0.0.1:20651 rr
  -> 172.20.139.72:80             Masq    1      0          0         
  -> 172.20.217.72:80             Masq    1      0          0         
UDP  10.68.0.2:53 rr
  -> 172.20.247.2:53              Masq    1      0          0 

除了直接用cluster ip,以及上面说到的NodePort模式来访问Service,我们还可以用K8s的DNS来访问

# 我们前面装好的CoreDNS,来提供K8s集群的内部DNS访问
# kubectl -n kube-system get deployment,pod|grep dns
deployment.apps/coredns                   1/1     1            1           5d2h
pod/coredns-d9b6857b5-tt7j2                    1/1     Running   1          27h

# coredns是一个DNS服务器,每当有新的Service被创建的时候,coredns就会添加该Service的DNS记录,然后我们通过serviceName.namespaceName就可以来访问到对应的pod了,下面来演示下:

# kubectl run -it --rm busybox --image=registry.cn-shanghai.aliyuncs.com/acs/busybox:v1.29.2 -- sh    # --rm代表等我退出这个pod后,它会被自动删除,当作一个临时pod在用
If you don't see a command prompt, try pressing enter.
/ # ping nginx.default
PING nginx.default (10.68.18.121): 56 data bytes
64 bytes from 10.68.18.121: seq=0 ttl=64 time=0.096 ms
64 bytes from 10.68.18.121: seq=1 ttl=64 time=0.067 ms
64 bytes from 10.68.18.121: seq=2 ttl=64 time=0.065 ms
^C
--- nginx.default ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.065/0.076/0.096 ms
/ # wget nginx.default
Connecting to nginx.default (10.68.18.121:80)
saving to 'index.html'
index.html           100% |********************************************************************|    22  0:00:00 ETA
'index.html' saved
/ # cat index.html 
nginx-f89759699-bzwd2

Endpoint Slices

在 Kubernetes 网络系统中,Endpoint 和 Endpoint Slices 都是非常重要的资源。Endpoint 和 Endpoint Slices 都负责追踪网络端点,但 Endpoint Slices 是 Kubernetes 1.16 之后引入的,用于替代 Endpoint 的新资源。

Endpoint Slices 是 Kubernetes 中的 API 对象,用于表示集群中的网络端点。与其前身 Endpoint 相比,Endpoint Slices 提供了一个更加可扩展和可扩展的方式来追踪网络端点。

Endpoint Slices 的主要优势包括:

  • 可扩展性:Endpoint Slices 比 Endpoints 更具可扩展性。在大型集群中,使用 Endpoint 可能会导致网络性能问题,因为每次服务的后端更改时,都会更新所有的 Endpoints。但是,Endpoint Slices 分散了这些信息,使得网络流量可以在多个 Endpoint Slices 中分布,从而提高了可扩展性。
  • 内置拓扑信息:Endpoint Slices 支持内置的拓扑信息,如 nodeName、zone、region 等,这些信息可以用于实现更复杂的网络路由,以优化网络流量。
  • 多地址类型:Endpoint Slices 支持多种地址类型,包括 IPv4、IPv6 和 FQDN(完全限定域名)。

Endpoint Slices 的主要构成部分包括:

  • 地址:这是 Endpoint Slices 中的主要字段,用于存储网络端点的地址。
  • 端口:Endpoint Slices 可以包含多个端口,每个端点可以关联一个或多个端口。
  • 条件:Endpoint Slices 包含关于端点的一些条件信息,例如端点是否就绪。
  • 拓扑:这些字段用于表示端点的拓扑信息,例如 nodeName、zone、region 等。

在 Kubernetes 中,Endpoint Slices 控制器默认会为每个服务创建和管理 Endpoint Slices。当创建新的 Kubernetes 服务时,相应的 Endpoint Slices 也会被创建,当服务的后端发生更改时,Endpoint Slices 也会相应地更新。

yaml

Copy

apiVersion: discovery.k8s.io/v1beta1
kind: EndpointSlice
metadata:
  name: example-abc
  labels:
    kubernetes.io/service-name: example
addressType: IPv4
ports:
  - name: http
    protocol: TCP
    port: 80
endpoints:
  - addresses:
      - "10.1.2.3"
    conditions:
      ready: true
    nodeName: node-1
    zone: us-central1-a

以上是一个 EndpointSlices 的简单例子。在此 YAML 文件中,我们定义了一个名为 “example-abc” 的 EndpointSlice,它追踪名为 “example” 的服务的网络端点。这个 EndpointSlice 包含一个端点,地址为 “10.1.2.3”,端口为 80,位于节点 “node-1” 和区域 “us-central1-a”。

总的来说,Endpoint Slices 是 Kubernetes 网络系统的重要组成部分,它提供了一种高效且可扩展的方式来追踪网络端点,使得 Kubernetes 网络可以更好地扩展和优化。

service生产小技巧 通过svc来访问非K8s上的服务

上面我们提到了创建service后,会自动创建对应的endpoint,这里面的关键在于 selector: app: nginx 基于lables标签选择了一组存在这个标签的pod,然而在我们创建svc时,如果没有定义这个selector,那么系统是不会自动创建endpoint的,我们可不可以手动来创建这个endpoint呢?答案是可以的,在生产中,我们可以通过创建不带selector的Service,然后创建同样名称的endpoint,来关联K8s集群以外的服务,这个具体能带给我们运维人员什么好处呢,就是我们可以直接复用K8s上的ingress(这个后面会讲到,现在我们就当它是一个nginx代理),来访问K8s集群以外的服务,省去了自己搭建前面Nginx代理服务器的麻烦

开始实践测试

这里我们挑选node-2节点,用python运行一个简易web服务器

[root@node-2 mnt]# python2 -m SimpleHTTPServer 9999
Serving HTTP on 0.0.0.0 port 9999 ...

然后我们用之前学会的方法,来生成svc和endpoint的yaml配置,并修改成如下内容,并保存为mysvc.yaml

注意Service和Endpoints的名称必须一致

# 注意我这里把两个资源的yaml写在一个文件内,在实际生产中,我们经常会这么做,方便对一个服务的所有资源进行统一管理,不同资源之间用"---"来分隔
apiVersion: v1
kind: Service
metadata:
  name: mysvc
  namespace: default
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP

---

apiVersion: v1
kind: Endpoints
metadata:
  name: mysvc
  namespace: default
subsets:
- addresses:
  - ip: 10.0.1.202
    nodeName: 10.0.1.202
  ports:
  - port: 9999
    protocol: TCP

开始创建并测试

# kubectl  apply -f mysvc.yaml 
service/mysvc created
endpoints/mysvc created

# kubectl get svc,endpoints |grep mysvc
service/mysvc        ClusterIP   10.68.71.166   <none>        80/TCP         14s
endpoints/mysvc        10.0.1.202:9999                     14s

# curl 10.68.71.166
mysvc

# 我们回到node-2节点上,可以看到有一条刚才的访问日志打印出来了
10.0.1.201 - - [25/Nov/2020 14:42:45] "GET / HTTP/1.1" 200 -

外部网络如何访问到Service呢?

在上面其实已经给大家演示过了将Service的类型改为NodePort,然后就可以用node节点的IP加端口就能访问到Service了,我们这里来详细分析下原理,以便加深印象

# 我们看下先创建的nginx service的yaml配置
# kubectl get svc nginx -o yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2020-11-25T03:55:05Z"
  labels:
    app: nginx
  managedFields:       # 在新版的K8s运行的资源配置里面,会输出这么多的配置信息,这里我们可以不用管它,实际我们在创建时,这些都是忽略的
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      e:metadata:
        e:labels:
          .: {}
          e:app: {}
      e:spec:
        e:externalTrafficPolicy: {}
        e:ports:
          .: {}
          k:{"port":80,"protocol":"TCP"}:
            .: {}
            e:port: {}
            e:protocol: {}
            e:targetPort: {}
        e:selector:
          .: {}
          e:app: {}
        e:sessionAffinity: {}
        e:type: {}
    manager: kubectl
    operation: Update
    time: "2020-11-25T04:00:28Z"
  name: nginx
  namespace: default
  resourceVersion: "591029"
  selfLink: /api/v1/namespaces/default/services/nginx
  uid: 84fea557-e19d-486d-b879-13743c603091
spec:
  clusterIP: 10.68.18.121
  externalTrafficPolicy: Cluster
  ports:
  - nodePort: 20651     # 我们看下这里,它定义的一个nodePort配置,并分配了20651端口,因为我们先前创建时并没有指定这个配置,所以它是随机生成的
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: NodePort
status:
  loadBalancer: {}
  
  
# 我们看下apiserver的配置
# cat /etc/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
ExecStart=/opt/kube/bin/kube-apiserver \
  --allow-privileged=true \
  --anonymous-auth=false \
  --api-audiences=api,istio-ca \
  --authorization-mode=Node,RBAC \
  --bind-address=10.0.1.201 \
  --client-ca-file=/etc/kubernetes/ssl/ca.pem \
  --endpoint-reconciler-type=lease \
  --etcd-cafile=/etc/kubernetes/ssl/ca.pem \
  --etcd-certfile=/etc/kubernetes/ssl/kubernetes.pem \
  --etcd-keyfile=/etc/kubernetes/ssl/kubernetes-key.pem \
  --etcd-servers=https://10.0.1.201:2379,https://10.0.1.202:2379,https://10.0.1.203:2379 \
  --kubelet-certificate-authority=/etc/kubernetes/ssl/ca.pem \
  --kubelet-client-certificate=/etc/kubernetes/ssl/kubernetes.pem \
  --kubelet-client-key=/etc/kubernetes/ssl/kubernetes-key.pem \
  --secure-port=6443 \
  --service-account-issuer=https://kubernetes.default.svc \
  --service-account-signing-key-file=/etc/kubernetes/ssl/ca-key.pem \
  --service-account-key-file=/etc/kubernetes/ssl/ca.pem \
  --service-cluster-ip-range=10.68.0.0/16 \
  --service-node-port-range=30000-32767 \      # 这就是NodePor随机生成端口的范围,这个在我们部署时就指定了
  --tls-cert-file=/etc/kubernetes/ssl/kubernetes.pem \
  --tls-private-key-file=/etc/kubernetes/ssl/kubernetes-key.pem \
  --requestheader-client-ca-file=/etc/kubernetes/ssl/ca.pem \
  --requestheader-allowed-names= \
  --requestheader-extra-headers-prefix=X-Remote-Extra- \
  --requestheader-group-headers=X-Remote-Group \
  --requestheader-username-headers=X-Remote-User \
  --proxy-client-cert-file=/etc/kubernetes/ssl/aggregator-proxy.pem \
  --proxy-client-key-file=/etc/kubernetes/ssl/aggregator-proxy-key.pem \
  --enable-aggregator-routing=true \
  --v=2
Restart=always
RestartSec=5
Type=notify
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target




# NodePort端口会在所在K8s的node节点上都生成一个同样的端口,这就使我们无论所以哪个node的ip接端口都能方便的访问到Service了,但在实际生产中,这个NodePort不建议经常使用,因为它会造成node上端口管理混乱,等用到了ingress后,你就不会想使用NodePort模式了,这个接下来会讲到

[root@node-1 ~]# ipvsadm -ln|grep -C6 20651
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
......      
TCP  10.0.1.201:20651 rr      # 这里
  -> 172.20.139.72:80             Masq    1      0          0         
  -> 172.20.217.72:80             Masq    1      0          0    


[root@node-2 mnt]# ipvsadm -ln|grep -C6 20651
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
......       
TCP  10.0.1.202:20651 rr      # 这里
  -> 172.20.139.72:80             Masq    1      0          0         
  -> 172.20.217.72:80             Masq    1      0          0  

生产中Service的调优

# 先把nginx的pod数量调整为1,方便呆会观察
# kubectl scale deployment nginx --replicas=1
deployment.apps/nginx scaled

# 看下这个nginx的pod运行情况,-o wide显示更详细的信息,这里可以看到这个pod运行在node 203上面
# kubectl  get pod -o wide                     
NAME                    READY   STATUS    RESTARTS   AGE     IP              NODE         NOMINATED NODE   READINESS GATES
nginx-f89759699-qlc8q   1/1     Running   0          3h27m   172.20.139.72   10.0.1.203   <none>           <none>

# 我们先直接通过pod运行的node的IP来访问测试
[root@node-1 ~]# curl 10.0.1.203:20651
nginx-f89759699-qlc8q

# 可以看到日志显示这条请求的来源IP是203,而不是node-1的IP 10.0.1.201
# 注: kubectl logs --tail=1 代表查看这个pod的日志,并只显示倒数第一条
[root@node-1 ~]# kubectl logs --tail=1 nginx-f89759699-qlc8q 
10.0.1.203 - - [25/Nov/2020:07:22:54 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/7.29.0" "-"

# 再来通过201来访问
[root@node-1 ~]# curl 10.0.1.201:20651          
nginx-f89759699-qlc8q

# 可以看到显示的来源IP非node节点的
[root@node-1 ~]# kubectl logs --tail=1 nginx-f89759699-qlc8q 
172.20.84.128 - - [25/Nov/2020:07:23:18 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/7.29.0" "-"

# 这就是一个虚拟网卡转发的
[root@node-1 ~]# ip a|grep -wC2 172.20.84.128 
9: tunl0@NONe: <NOARP,UP,LOWER_UP> mtu 1440 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 172.20.84.128/32 brd 172.20.84.128 scope global tunl0
       valid_lft forever preferred_lft forever

# 可以看下lvs的虚拟服务器列表,正好是转到我们要访问的pod上的
[root@node-1 ~]# ipvsadm -ln|grep -A1 172.20.84.128 
TCP  172.20.84.128:20651 rr
  -> 172.20.139.72:80             Masq    1      0          0      
  

详细处理流程如下:

* 客户端发送数据包 10.0.1.201:20651
* 10.0.1.201 用自己的IP地址替换数据包中的源IP地址(SNAT)
* 10.0.1.201 使用 pod IP 替换数据包上的目标 IP
* 数据包路由到 10.0.1.203 ,然后路由到 endpoint 
* pod的回复被路由回 10.0.1.201 
* pod的回复被发送回客户端

          client
             \ ^
              \ \
               v \
10.0.1.203 <--- 10.0.1.201
    | ^   SNAT
    | |   --->
    v |
 endpoint   

为避免这种情况, Kubernetes 具有保留客户端IP 的功能。设置 service.spec.externalTrafficPolicy 为 Local 会将请求代理到本地端点,不将流量转发到其他节点,从而保留原始IP地址。如果没有本地端点,则丢弃发送到节点的数据包,因此您可以在任何数据包处理规则中依赖正确的客户端IP。

# 设置 service.spec.externalTrafficPolicy 字段如下:
# kubectl patch svc nginx -p '{"spec":{"externalTrafficPolicy":"Local"}}'
service/nginx patched

# 现在通过非pod所在node节点的IP来访问是不通了
[root@node-1 ~]# curl 10.0.1.201:20651                       
curl: (7) Failed connect to 10.0.1.201:20651; Connection refused

# 通过所在node的IP发起请求正常
[root@node-1 ~]# curl 10.0.1.203:20651
nginx-f89759699-qlc8q

# 可以看到日志显示的来源IP就是201,这才是我们想要的结果
[root@node-1 ~]# kubectl logs --tail=1 nginx-f89759699-qlc8q          
10.0.1.201 - - [25/Nov/2020:07:33:42 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/7.29.0" "-"

# 去掉这个优化配置也很简单
# kubectl patch svc nginx -p '{"spec":{"externalTrafficPolicy":""}}' 

注意:这样会带来个什么问题呢,如是一旦pod发生重启飘移到了另一台node节点上,而你用的IP还是203的话就会访问不到服务了,这个利弊需要自己权衡,解决方法可以用nodeSelector来将服务的pod固定在哪几台node上运行,这样ip还是在我们控制的范围了,我们现在就来试试nodeSelector的效果吧

# 修改好yaml配置
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
      nodeSelector:           # <--- 这里
        apps/nginx: "true"    # <--- 基于这个label来选择
status: {}

然后我们这里在需要指定运行的node上打上label

注意:这里提醒下,对于在K8s中运行的资源,大部分都是基于label标签来作服务关系的选择,这里仅仅是以node这个资源来作的演示,其它如Service、Deployment等等,都是可以用类似命令来label操作的

# kubectl label node 10.0.1.201 apps/nginx=true
node/10.0.1.201 labeled

# kubectl get node 10.0.1.201 --show-labels |grep nginx
10.0.1.201   Ready    master   5d3h   v1.18.12   apps/nginx=true,beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,cb/ingress-controller-ready=true,kubernetes.io/arch=amd64,kubernetes.io/hostname=10.0.1.201,kubernetes.io/os=linux,kubernetes.io/role=master,kubevirt.io/schedulable=true

开始基于yaml配置创建服务

# kubectl  apply -f nginx.yaml 
deployment.apps/nginx created

# 因为之前是把pod数量改成了2,所以这里看到2个pod都跑在201上面了,因为我只在201上面做了label标记
# kubectl get pod -o wide
NAME                     READY   STATUS              RESTARTS   AGE   IP       NODE         NOMINATED NODE   READINESS GATES
nginx-867c95f465-kkxnm   0/1     ContainerCreating   0          5s    <none>   10.0.1.201   <none>           <none>
nginx-867c95f465-njv78   0/1     ContainerCreating   0          5s    <none>   10.0.1.201   <none>           <none>

# 我们这里还是先将数量改成1个
# kubectl scale deployment nginx --replicas=1
deployment.apps/nginx scaled

# 后面无论这个pod怎么重启,它都只会在打了label  apps/nginx=true的节点上运行了
# kubectl get pod -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES
nginx-867c95f465-njv78   1/1     Running   0          32s   172.20.84.132   10.0.1.201   <none>           <none>

pod小怪战斗(作业)

# 创建一个nginx的deployment,pod副本数量设为2,并为它创建一个Service服务,尝试用Service的IP来请求pod,并且通过创建一个临时busybox的pod,通过DNS来请求pod
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐