K8S学习笔录 - Service基本用法

1,261 阅读8分钟

原文链接

Service可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。

为什么需要Service

Service的存在主要为了解决 客户端无法确定为它提供服务的Pod的地址 的问题。

可能由于一些问题(Pod调度、异常等),该Pod从该节点被驱逐到其他节点上,此时Pod的IP可能会变; 或者由于Pod的水平伸缩,多出来或者少几个提供同样服务的Pod。

此时需要一个中间层转发客户端的请求,为客户端提供稳定的服务。 同时该中间层也可以做负载均衡、服务暴露等工作。

Service是如何工作的

Service在配置过程中,会要求配置一个Pod选择规则。通过该规则找到提供服务的Pod列表。 然后客户端不会直接访问这些Pod,而是通过Service来访问这些Pod。

例如客户端要访问前端,前端要访问后端。

外部客户端不关心前端Pod数量,而直接连接到前端服务上,由前端服务选择一个Pod来提供服务。 前端服务也不关心后端Pod数量,而直接连接到后端服务商,由后端服务选择一个Pod来提供服务。 在此期间,不论是Pod变更、迁移,上游都不需要关心。

svc-overview.png

Service配置定义

通过Service的结构可以清晰地了解Service完整的配置内容。

更多说明可以通过kubernetes.io/docs/concep…查看。

列出常用的Service配置结构。

type Service struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    // Service的定义
    Spec ServiceSpec `json:"spec,omitempty"`

    // Service最近的状态,由系统维护。只读。
    Status ServiceStatus `json:"status,omitempty"`
}

type ServiceSpec struct {
    // 服务暴露端口配置列表
    Ports []ServicePort `json:"ports,omitempty"`

    // 服务的路由配置
    // 使用该字段配置和Pod的Label配置进行匹配
    // 如果该字段为空或者未定义,会假设有一个外部的Endpoints
    // 只能在Type字段为ClusterIP、NodePort和LoadBalancer时生效,在Type是ExternalName时被忽略
    Selector map[string]string `json:"selector,omitempty"`

    // 集群IP。
    // 通常情况下该值是由master随机分配的,但是如果手动声明了,就要保证在集群中未被使用,否则该服务会创建失败。
    // 该字段不能被更新
    // 有效值为None,空字符串("")和有效的IP地址
    // 如果使用Headeless服务,不需要代理时,可以使用None
    // 该字段只在Type为ClusterIP、NodePOrt和LoadBalancer生效,在Type是ExternalName时被忽略
    ClusterIP string `json:"clusterIP,omitempty"`

    // 服务类型,声明了服务如何暴露出去
    // 有效值为ClusterIP(默认)、ExternalName、NodePort和LoadBalancer时起作用
    // ClusterIP 分配一个集群内网IP给该服务。通过Selector配置选择指定的服务Pod,如果未配置Selector,则根据已有的Endpoints对象来确定。
    // 如果cluserIP为None,则不会分配虚拟IP,endpoints也会被定为一组endpoints而非一个确定的IP
    // NodePort 在分配了集群IP的基础上(不管是指定的还是master分配的),额外在节点上分配一个端口,可以该端口将请求路由到该集群IP上
    // LoadBalancer 在NodePort的基础上,额外创建一个负载均衡器来路由到该集群IP上
    Type ServiceType `json:"type,omitempty"`

    // 集群中节点的IP地址列表。
    // 服务将会在该列表中的节点上暴露服务端口。
    // 这些端口暴露行为不属于K8S系统的管理部分。
    ExternalIPs []string `json:"externalIPs,omitempty"`

    // Session亲和配置
    // 该字段可以使用ClientIP和None(默认)作为值
    // ClientIP,则相同IP每次访问都会访问到同一个Pod
    SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"`

    // Only applies to Service Type: LoadBalancer
    // LoadBalancer will get created with the IP specified in this field.
    // This feature depends on whether the underlying cloud-provider supports specifying
    // the loadBalancerIP when a load balancer is created.
    // This field will be ignored if the cloud-provider does not support the feature.
    LoadBalancerIP string `json:"loadBalancerIP,omitempty"`

    // If specified and supported by the platform, this will restrict traffic through the cloud-provider
    // load-balancer will be restricted to the specified client IPs. This field will be ignored if the
    // cloud-provider does not support the feature."
    // More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/
    LoadBalancerSourceRanges []string `json:"loadBalancerSourceRanges,omitempty"`

    // 使用该字段后,服务在被访问的时候kubedns将会将该字段的值作为CNAME返回。
    // 使用该字段Type必须为ExternalName
    ExternalName string `json:"externalName,omitempty"`

    // 定义了服务将外部请求路由到本地节点还是集群中其他节点。
    // 该字段值可以为Local和Cluster
    // 如果该字段值为Local,则可以保留客户端的IP,并且避免了Loadbalancer和Nodeport中网络的第二跳,但存在流量不均衡的问题。
    // 如果该字段为Cluster,则不会保留真正客户端的IP,但是流量分布均匀
    ExternalTrafficPolicy ServiceExternalTrafficPolicyType `json:"externalTrafficPolicy,omitempty"`

    // 该字段为服务健康检查使用的端口
    // 如果未定义,则service api后端会自动分配一个端口
    // 如果定义了,则使用用户定义的端口
    // 该字段只会在Type设置为LoadBalancer 和 ExternalTrafficPolicy设置为Local时生效
    HealthCheckNodePort int32 `json:"healthCheckNodePort,omitempty"`

    // publishNotReadyAddresses,意为发布未准备好的地址到DNS,默认为false
    // 当该字段设置为true时,DNS则会为Endpoints查找Service发布notReadyAddresses。
    // 防止readinessProbe在服务没启动时找不到DNS。
    PublishNotReadyAddresses bool `json:"publishNotReadyAddresses,omitempty"`

    // sessionAffinityConfig可以配置Session的最大过期时间,默认为3小时
    SessionAffinityConfig *SessionAffinityConfig `json:"sessionAffinityConfig,omitempty"`

    // 定义了服务的IP类型偏好(IPv4、IPv6)
    // 如果定义了该字段并且集群支持该IP类型,则clusterIP字段将会被分配该类的IP
    // 如果未定义,则使用集群IP的类型
    IPFamily *IPFamily `json:"ipFamily,omitempty"`
}

type ServicePort struct {

    // 服务内端口的名称,必须是一个DNS_LABEL
    // 所有在ServiceSpec中声明的端口必须有一个唯一名称
    // 如果需要定义Endpoint的话,该名字必须和定义的Endpoint中的EndpointPort一样
    Name string `json:"name,omitempty"`

    // 该端口的IP协议,当前支持TCP(默认)、UDP和SCTP
    Protocol Protocol `json:"protocol,omitempty"`

    // 要暴露的服务端口
    Port int32 `json:"port"`

    // 与服务对应的Pod上的端口号或者端口名
    // 端口号需要在1-65535之间,名字必须是一个IANA_SVC_NAME
    // 如果是名字(字符串),将会在Pod的容器中查找与之对应的端口。如果未找到,则port字段的值将会被使用
    // 当Service的ClusterIP=None时,忽略此字段,会缺省为port字段的值
    // 更多信息 : https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service
    TargetPort intstr.IntOrString `json:"targetPort,omitempty"`

    // 节点端口
    // 当type=NodePort或者LoadBalancer时会使用该值。通常情况下该字段的值由操作系统来分配。
    // 如果定义了,如果端口被占用,则服务创建失败
    // 在需要该字段的时候,默认为自动分配
    // 更多信息:https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
    NodePort int32 `json:"nodePort,omitempty"`
}

// 服务类型
type ServiceType string
const (
    // 集群IP,服务只能在集群内被发现
    ServiceTypeClusterIP ServiceType = "ClusterIP"

    // ClusterIP的扩展,额外使用节点端口,将会在所有部署了该服务的节点上都暴露一个端口
    ServiceTypeNodePort ServiceType = "NodePort"

    // NodePort的扩展,额外使用负载均衡,服务将会被暴露在一个已经存在的负载均衡器中,由负载均衡器负责分发访问
    ServiceTypeLoadBalancer ServiceType = "LoadBalancer"

    // 额外的名字,在访问服务的时候将会返回一个CNAME记录。使用该字段的Service不会暴露或者代理任何的Pod。
    ServiceTypeExternalName ServiceType = "ExternalName"
)

// Session亲和类型
type ServiceAffinity string
const (
    ServiceAffinityClientIP ServiceAffinity = "ClientIP"
    ServiceAffinityNone ServiceAffinity = "None"
)

// Service External Traffic Policy Type string
type ServiceExternalTrafficPolicyType string
const (
    // 该值定义了使用节点本地的行为
    ServiceExternalTrafficPolicyTypeLocal ServiceExternalTrafficPolicyType = "Local"

    // 该值定义了使用节全局节点的行为
    ServiceExternalTrafficPolicyTypeCluster ServiceExternalTrafficPolicyType = "Cluster"
)

type SessionAffinityConfig struct {
    // 针对客户端IP的配置信息
    ClientIP *ClientIPConfig `json:"clientIP,omitempty"`
}

type ClientIPConfig struct {
    // 超时时间定义,单位秒,值必须在0~86400之间
    TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`
}

type IPFamily string
const (
    // IPv4Protocol IPv4协议
    IPv4Protocol IPFamily = "IPv4"
    // IPv6Protocol IPv6协议
    IPv6Protocol IPFamily = "IPv6"
    // 服务最多支持的拓扑域个数
    MaxServiceTopologyKeys = 16
)

基本用法

以此Deployment配置为基础,进行Service的测试

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - name: http
          containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - name: web-8080
    port: 8080
    targetPort: http
  selector:
    app: nginx

创建该Service,再创建一个busybox执行 wget nginx:8080 进行验证

上述配置创建了一个名称为nginx的Service对象,该对象对外暴露了8080端口。Service会将请求代理到使用具有标签app=nginx的Pod的80端口上。

服务的默认协议是tcp,除此之外也支持udp和sctp。

由于许多服务需要公开多个端口,因此在服务对象上支持多个端口定义。每个端口定义的协议可以不同。

需要注意的是 如果配置的是多端口Service,那么配置端口的时候需要配置每个端口的name属性,单端口则不需要

$ kubectl run -it --rm --generator=run-pod/v1 busybox --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget nginx:8080
Connecting to nginx:8080 (10.96.100.16:8080)
saving to 'index.html'
index.html           100% |*******************************|   612  0:00:00 ETA
'index.html' saved