Loading

K8S的Service

原文博客:https://nosae.top

apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-service-1 # EndpointSlice命令的最佳实践是以svc的名称作为前缀
labels:
# 这个label的值必须是svc的名称,将EndpointSlice与svc关联起来
kubernetes.io/service-name: my-service
addressType: IPv4
ports:

  • name: http # should match with the name of the service port defined above
    appProtocol: http
    protocol: TCP
    port: 9376
    endpoints:

    注意endpoint不能是虚拟IP

  • addresses:
    • "10.4.5.6"
  • addresses:
    • "10.1.2.3"

Service 支持多个端口到目标端口的映射:

``` yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
    - name: https
      protocol: TCP
      port: 443
      targetPort: 9377

Service 的类型

之前我们默认使用的都是 ClusterIP 类型的 Service,实际上 Service 一共有以下几种类型:

  • ClusterIP:为 Service 分配一个仅在集群内可访问的虚拟 IP,如果要暴露到公网需要在接一个 Ingress 或者 Gateway

    apiVersion: v1
    kind: Service
    metadata:
      name: service-clusterip
    spec:
      selector:
        app.kubernetes.io/name: MyApp
      ports:
        - protocol: TCP
          port: 80
          targetPort: 8080
    

    结果:

    $ kubectl get svc service-clusterip
    NAME                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
    service-clusterip   ClusterIP   10.96.229.241   <none>        80/TCP    2m28s
    
  • NodePort:在 ClusterIP 的基础上,为每个 node 开放一个静态端口

    apiVersion: v1
    kind: Service
    metadata:
      name: service-nodeport
    spec:
      type: NodePort
      selector:
        app.kubernetes.io/name: MyApp-nodeport
      ports:
        - port: 80
          targetPort: 80
          nodePort: 30007 # nodePort也可以由控制面动态分配
    

    结果:

    $ kubectl get svc service-nodeport
    NAME               TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
    service-nodeport   NodePort   10.96.31.20   <none>        80:30007/TCP   2m46s
    
  • LoadBalancer:在 Nodeport 的基础上,自动创建一个云厂商提供的负载均衡器

  • ExternalName:最特殊的类型,没有 backend,不涉及代理和转发,仅仅是将请求重定向到一个外部的 DNS 名称。比如下面的配置,当访问 my-service.prod.svc.cluster.local 名称时,会返回一个 CNAME 记录,值为 my.database.example.com

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
      namespace: prod
    spec:
      type: ExternalName
      externalName: my.database.example.com
    

无头 Service

普通的 Service 会分配一个 ClusterIP,并负载均衡所有背后的 Pod,旨在将多个无状态 backend 抽象为统一的入口。但无头 Service,不会分配 ClusterIP、kube-proxy 也不会处理(不生成任何 iptables/IPVS 规则、不做负载均衡),旨在将每个 Pod 的 IP 直接暴露出去,让应用层来决定如何连接。

创建无头 Service 只需要将 ClusterIP 类型的 Service 的 .spec.clusterIP 指定为 None

spec:
  clusterIP: None

无头 Service 暴露的 Pod IP 方式是集群 DNS。

其实对于普通 Service 来说,也会涉及到 DNS,DNS 会创建一个 Service 的完全限定域名(FQDN,Fully Qualified Domain Name),命令为 <service-name>.<namespace>.svc.cluster.local,其有一条值为 ClusterIP 的 A 记录。

无头 Service 的完全限定域名下则包含了 n 条 A 记录( n 为 backend Pod 的数量),值分别为每个 Pod 的 IP,并且还会为每个 Pod 创建单独完全限定域名,命名为 <pod-name>.<service-name>.<namespace>.svc.cluster.local,每个域名下包含值为该 Pod 的 IP 的 A 记录。

因此,无头 Service 的核心价值在于它提供了两种粒度的 DNS 解析:Service 域名(用于发现)和 Pod 域名(用于身份识别),前者可以用来做客户端侧负载均衡,后者用来为每个 Pod 提供稳定的身份,用于在有状态服务中 Pod 之间互相识别对等节点以稳定协同工作,即使 Pod 重建了,只要 Pod 名称没变,域名也不会变。举个 MySQL 主从节点的例子:

环节 动作描述 无头 Service 的作用
集群初始化 mysql-1 需要知道主节点 mysql-0稳定地址 来进行复制配置。 mysql-1 通过解析 mysql-0.mysql-headless.default.svc.cluster.local 稳定地找到主节点的 IP,建立复制连接。
内部访问 应用客户端需要读写数据,并将连接分散到主从节点。 客户端可以查询 mysql-headless,获取所有 Pod IP 列表,然后根据自身的逻辑(例如,写操作连主节点 IP,读操作连从节点 IP)进行客户端侧的连接管理。
节点故障/重启 mysql-0 Pod 发生故障,被 K8s 重新调度。 即使 Pod IP 变了,它的稳定域名 mysql-0.mysql-headless 仍然解析到新的 IP 地址,复制关系和配置无需手动修改。

DNS

关于上面提到的集群 DNS,更详细的内容参考 https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/

posted @ 2025-12-08 00:02  NOSAE  阅读(9)  评论(0)    收藏  举报