Kubernetes中每个Pod都会被分配一个单独的IP地址,而且每个Pod都提供了一个独立的Endpoint(Pod IP+ContainerPort)以被客户端访问,现在多个Pod副本组成了一个集群来提供服务,那么客户端如何来访问它们呢?

运行在每个Node上的kube-proxy进程就是软件负载均衡器,负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。

每个Service都被分配了一个全局唯一的虚拟IP地址(Cluster IP)。这样每个服务就变成了具备唯一IP地址的通信节点,服务调用就变成了最基础的TCP网络通信问题。

Pod 的 Endpoint 地址会随着 Pod 的销毁和重新创建而发生改变,因为新 Pod 的IP地址与之前旧 Pod 的不同。而Service一旦被创建,Kubernetes就会自动为它分配一个可用的Cluster IP,而且在Service的整个生命周期内其Cluster IP不会发生改变。只要用 Service 的 Name 与 Service 的 Cluster IP 地址做一个DNS域名映射即可解决集群内部的服务发现需求。

对于 Kubernetes 的 Service,Cluster-Ip 和 NodePort 均是四层的负载。

K8S中IP概念

Node IP
Kubernetes 集群中每个节点的物理网卡的IP地址,是一个真实存在的物理网络。

Pod IP
Pod的IP地址,通常是一个虚拟的二层网络。位于不同Node上的不同Pod的通信是通过Pod IP所在的虚拟二层网络进行通信的,而真实的TCP/IP流量走的是Node IP所在的物理网卡。

Cluster IP
Cluster IP也是一种虚拟的IP,但更像“伪造”的IP网络,原因如下:

  • Cluster IP仅仅作用于Service对象,由Kubernetes管理和分配IP地址
  • Cluster IP无法被Ping,因为没有一个“实体网络对象”来响应。
  • Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备TCP/IP通信能力

容器端口映射

要求:将容器应用端口号映射到物理机

方式一:设置Container级别的hostPort,将容器应用的端口号映射到物理机上
image.png
方式二:设置Pod级别的hostNetwork,该Pod中所有容器的端口号都将被直接映射到物理机上
image.png
说明:
设置hostNetwork=true时,在容器的ports定义部分若不指定hostPort,则默认hostPort等于containerPort。若指定hostPort,其值必须等于containerPort。

SVC的使用

有组 Pod 服务,对外暴露 8080 端口,有 app=myapp 标签,对应的 svc 对象如下:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: myservice
  5. spec:
  6. selector:
  7. app: myapp
  8. ports:
  9. - protocol: TCP
  10. port: 80
  11. targetPort: 8080
  12. name: myapp-http

Service 能够支持 TCP 和 UDP 协议,默认是 TCP 协议。

SVC支持的服务类型:

  • ClusterIP:通过集群的内部 IP 暴露服务,是默认的服务类型。
  • NodePort:通过每个 Node 节点上的 IP 和静态端口(NodePort)暴露服务。
  • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。
  • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。

SVC端口映射

映射随机端口

当 svc 中 type 值为 NodePort,Kubernetes 将从配置范围内(默认:30000-32767)分配端口(不指定端口将随机分配),每个 node 将流量从此端口代理到 svc。

示例 service-demo.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name:  nginx-deploy
  namespace: default
spec:
  replicas: 3  # 期望的 Pod 副本数量,默认值为1
  selector:  # Label Selector,必须匹配 Pod 模板中的标签
    matchLabels:
      app: myapp
  template:  # Pod 模板
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  selector:
    app: myapp
  type: NodePort
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    name: myapp-http
# 应用资源
$ kubectl apply -f service-demo.yaml

# 查看
$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        87d
myservice    NodePort    10.110.89.149   <none>        80:30710/TCP   3m58s

# 访问
$ curl -I  10.110.89.149
HTTP/1.1 200 OK
...
Accept-Ranges: bytes

$ curl -I  localhost:30710  # localhost 可以是集群中任意node节点的ip
HTTP/1.1 200 OK
...
Accept-Ranges: bytes

映射指定端口

apiVersion: apps/v1
kind: Deployment
metadata:
  name:  nginx-deploy
  namespace: default
spec:
  replicas: 3  
  selector:  
    matchLabels:
      app: myapp
  template:  # Pod 模板
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  selector:
    app: myapp
  type: NodePort
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30001  # 指定映射到node端口
    name: myapp-http

映射多端口

有些服务存在多端口需求,通常一个端口提供业务服务,另外一个端口提供管理服务。

apiVersion: v1
kind: Service
metadata:
  name: xxx
  namespace: open
spec:
  type: NodePort
  ports:
  - name: tcp
    port: 8888
    targetPort: 8888
    nodePort: 8888
    protocol: TCP
  - name: http
    port: 8080
    targetPort: 8080
    nodePort: 8080
    protocol: TCP
  selector:
    name: xxx