0
点赞
收藏
分享

微信扫一扫

K8s从0开始-Service资源对象笔记

一葉_code 11-09 15:45 阅读 1

概述

Service是Kubernetes中的一种资源对象,用于定义一组Pod的网络访问规则,它为Pod提供了一个稳定的统一访问入口,允许客户端始终使用同一个IP地址进行访问,避免直接使用Pod IP地址导致的不稳定性.

Service主要有以下两种功能:

  • 负载均衡: 当多个Pod提供服务时,Service通过负载均衡算法将请求分发到这些Pod上,从而实现应用程序的负载均衡
  • 服务发现: Service提供了一种服务发现机制,自动维护后端Pod IP的变化,从而保证客户端访问应用程序不受后端Pod变化的影响


Service定义

假设有一组由Deployment管理的Pod,这些Pod在TCP的80端口上提供服务,Pod的标签为"k8s-app=nginx-2",如图:

kubectl get pods,deployment --show-labels

K8s从0开始-Service资源对象笔记_Service

为了将这组Pod对外公开,创建了一个Service资源,配置如下:

vi service.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx-2
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector: 
    k8s-app: nginx-2

示例中部分字段含义:

  • port: 端口映射列表
  • protocol: 端口协议,支持TCP,UDP和SCTP,默认为TCP
  • targetPort: 目标端口,即容器中应用程序监听的端口,Service能够将任意入站port映射到某个targetPort.默认情况下,出于方便考虑,targetPort会被设置为与port字段相同的值
  • selector: 标签选择器(Label Selector),用于定义Service应该将流量转发到那些Pod上,这里表示只有带有"k8s-app=nginx-2
    "标签的Pod才会被该Service转发流量,标签选择器如下图:

K8s从0开始-Service资源对象笔记_Service_02

创建service资源:

kubectl apply -f service.yaml

查看Service对象:

kubectl get svc nginx-2
NAME      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
nginx-2   ClusterIP   10.97.164.205   <none>        80/TCP    47h

输出结果中,各字段含义如下:

  • NAME: 名称
  • TYPE: 公开类型,默认类型为ClusterIP
  • CLUSTER-IP: 集群IP地址,也称为虚拟IP地址
  • EXTERNAL-IP: 外部IP地址,当正确使用LoadBalancer或ExternalName类型时才会显示外部IP地址,否则显示"none"或者"pending"
  • PORT(S): 公开端口和协议
  • AGE: 创建时间

如果Pod在多个端口提供服务,Service如何公开这些端口,Kubernetes允许你为Service对象配置多个端口定义,示例如下:

apiVersion: v1
kind: Service
metadata:
  name: nginx-2
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  - name: https
    port: 443
    protocol: TCP
    targetPort: 443
  selector: 
    k8s-app: nginx-2

上述示例中,定义了两个端口映射规则,其中第一规则是将80映射到目标Pod的80端口,第二个规则是将443映射到目标Pod的443端口

应用配置后再次查看Service对象

K8s从0开始-Service资源对象笔记_Service_03

可以看到端口映射规则PORT(S)列多个443的规则,在集群中任意Pod或节点上,访问IP地址"10.97.164.205"的80端口Service会将流量转发到目标Pod的80端口,而访问虚拟IP地址的443端口会将流量转发到目标Pod的443端口,Service多端口映射如图:

K8s从0开始-Service资源对象笔记_Service_04


Service公开类型

Service的公开类型定义了Service如何对外网络公开,并支持四种公开类型:

  • ClusterIP: 默认类型. Service会被分配一个虚拟IP地址.集群中的应用可以通过该IP地址访问Service
  • NodePort: 在每个节点上开放一个固定端口,并将该端口映射到Service上.这允许集群外部的用户可以通过节点IP地址和固定端口访问Service.
  • LoadBalancer: 在底层云提供商创建一个负载均衡,并将外部流量转发到集群中的Service
  • ExternalName: 将Service的名称映射到指定的外部域名

ClusterIP

ClusterIP类型适用于应用程序仅需要再集群内部访问的场景,比如我将一个游戏服务进行了拆分,如图:

K8s从0开始-Service资源对象笔记_Service_05

可以看到日志服务和逻辑服务仅由集群中的网关和bt服内部调用(上图只是简单演示,实际生产环境中可能有相互调用的需求),这种情况下,日志服务和逻辑服务可以使用Service ClusterIP类型来公开相关Pod

注:有时你并不需要负载均衡,也不需要单独的Service IP, 遇到这种情况,可以通过显示设置集群IP(spec.clusterIp)的值为"None"来创建无头服务(Headless Service), 有什么用呢,如上,日志和逻辑服务可以使用Headless Serice,这样每个Pod的IP地址会直接暴漏在在服务发现中,其他Pod可以通过DNS查询获取特定的Pod IP(服务发现后面会提到),建立点对点的通信而不是通过负载均衡来分配请求,这对状态服务特别有用


NodePort

NodePort类型适用于应用程序需要集群外部访问的场景.如图:

K8s从0开始-Service资源对象笔记_Service_06

配置示例:

apiVersion: v1
kind: Service
metadata:
  name: nginx-2
spec:
  type: NodePort
  ports:
  - port:  80
    protocol: TCP
    targetPort: 80
  selector: 
    k8s-app: nginx-2

创建Service资源后,查看对象:

K8s从0开始-Service资源对象笔记_Service_07

在上述结果中, "PORT(S)"列现实的值为"80:32178/TCP",其中冒号(":")左侧表示访问虚拟IP地址对应的端口,右侧为节点开放的固定端口,我们可通过任意节点的IP地址加32178端口来访问这个网站

K8s从0开始-Service资源对象笔记_Service_08

默认情况下,Kubernetes为NodePort类型的Service分配一个固定端口,该端口取值为3000~32767,我们可以通过"nodePort"字段指定这个固定端口,示例:

apiVersion: v1
kind: Service
metadata:
  name: nginx-2
spec:
  type: NodePort
  ports:
  - port:  80
    protocol: TCP
    targetPort: 80
    nodePort: 30002
  selector: 
    k8s-app: nginx-2

需要注意的是指定的端口需要在3000~32767端口范围内,其未被其他Service使用,如果被占用会报错"provided port is already allocated"


LoadBalancer

LoadBalancer类型是针对云提供商(阿里云,AWS,Azure)设计的一种类型,当创建一个LoadBalancer类型的Service时,云提供商的控制器从Kubernetes API中感知到该Service,然后调用云提供商API来创建一个负载均衡器,并将节点地址和nodePort端口作为后端添加到该负载均衡器中,这意味着用户可以通过负载均衡器访问集群中的Service,适用于要求外部直接访问且需要高可用负载均衡的生产环境,如图,对外sdk服务设计:

K8s从0开始-Service资源对象笔记_Service_09


ExternalName

ExternalName是一种特殊的类型,与其他类型不同的是,它不提供负载均衡和服务发现功能,而是专用于将Service的名称映射到指定的外部域名上 如图:

K8s从0开始-Service资源对象笔记_Service_10


EndPoints对象

当创建一个Service时,Kubernetes会根据Service创建一个EndPoints对象,该对象负责维护与Service相关的后端Pod信息,并确保Service可以被正确地转发到后端Pod,Service和EndPoints之间的关系如图:

K8s从0开始-Service资源对象笔记_Service_11

查看endpoints对象

K8s从0开始-Service资源对象笔记_Service_12

在上述结果中: "NAME"列显示的名称与Service名称相对应,"ENDPOINTS"列显示了关联Pod IP地址和端口,其中超出3个显示未省略号.Endpoints资源还可以手动创建,自定义关联的IP地址和端口以满足特定网络代理需求.

比如我们游戏服务容器化了,数据首先是不能存放到服务器上的(云原生最佳实践),业务数据需要使用独立托管的数据库服务,比如阿里云的RDS,我们可以通过Service代理集群外部的MYSQL服务,配置示例

vi service-mysql-proxy.yaml

apiVersion: v1
kind: Endpoints
metadata:
  name: mysql-proxy
subsets:
- addresses:
  - ip: 10.10.10.249
  ports:
  - port: 3306
    protocol: TCP

---
apiVersion: v1
kind: Service
metadata:
  name: mysql-proxy
spec:
  ports:
  - port:  3306
    targetPort: 3306

示例中我们定义一个名为"mysql-proxy"的Endpoints资源,其中subsets(子集)字段包含了一个独立MYSQL服务的IP地址和端口,然后定义了一个名为"mysql-proxy"的Service资源,将访问3306端口的流量给转发到目标端口3306也就是说当集群中的Pod访问这个Service时,Kubernetes会将流量给转发给这个外部的mysql服务

创建资源后查看Service和Endpoints对象:

K8s从0开始-Service资源对象笔记_Service_13

创建测试Pod验证

K8s从0开始-Service资源对象笔记_Service_14

看起来使用Service name还是使用虚拟IP 10.98.220.202都可以连接这个外部的mysql服务,为什么呢,这个就是后面要提到的服务发现原理


Service服务发现

Kubernetes提供了两种服务发现模式,环境变量和DNS,Pod中的应用程序可以通过它们访问其他Service


环境变量

当创建一个Pod时,Kubernetes默认会将同一个命名空间下的所有Service信息以环境变量的形式注入Pod,其中,"<SERVICE_NAME>_SERVICE_HOST"环境变量保存了Service的虚拟IP地址,"<SERVICE_NAME>_SERVICE_PORT"环境变量保存了Service的端口号,这样,容器中的应用程序可以通过这些环境变量来获取Service的访问地址

比如我在default空间里有这样的Service

kubectl get svc nginx-2 -n default

K8s从0开始-Service资源对象笔记_Service_15

我现在在default空间创建一个临时Pod使用env查看环境变量

kubectl run -it --rm test --image=harbor.peng.com/library/busybox  -- sh

K8s从0开始-Service资源对象笔记_Service_16

可以看到环境变量{SVCNAME}_SERVICE_HOST和{SVCNAME}_SERVICE_PORT信息

官网这里有个说明需要注意:

当你的Pod需要访问某Service,并且你在使用环境变量方法将端口和集群IP发布到客户端Pod时,必须在客户端Pod出现之前创建该Service,否则,这些客户端Pod将不会出现对应的环境变量.

如果仅使用DNS来发现Service的集群IP,则无需担心此顺序问题


DNS

Kubernetes默认使用CoreDNS作为集群内部的DNS服务,它主要负责解析Service名称,这允许集群中的应用程序可以通过Service名称进行通信,而无须硬编码具体的虚拟IP地址.

Service名称的域名格式为"<SERVICE_NAME>.<NAMESPACE>.svc.cluster.local",比如我之前在default空间创建有个名叫nginx-2的Service,现在我在default空间中再创建一个测试Pod来进行域名解析测试

kubectl run -it --rm dns-test --image=harbor.peng.com/library/busybox -- sh

K8s从0开始-Service资源对象笔记_Service_17

域名  "nginx-2.default.svc.cluster.local"解析IP地址正式对应的虚拟IP地址,这意味可以通过这个域名访问Service


CoreDNS工作流程

在容器中访问一个域名时,系统会将DNS查询请求发送到"/etc/resolv.conf"文件中配置的DNS服务器地址,即CoreDNS Service的虚拟IP地址,CoreDNS服务接收到DNS查询请求后,根据Service名称解析为响应的虚拟IP地址,CoreDNS工作流程如图:

K8s从0开始-Service资源对象笔记_Service_18

容器中"/etc/resolv.conf"文件内容如下:

K8s从0开始-Service资源对象笔记_Service_19

上述结果各字段含义如下:

  • nameserver: DNS服务器的IP地址,这里的值为"10.96.0.10"即CoreDNS Service的虚拟IP地址
  • search: DNS查询的搜索域列表
  • options: 选项参数

查看CoreDNS和Service对象:

kubectl get pod,svc -l k8s-app=kube-dns -n kube-system

K8s从0开始-Service资源对象笔记_Service_20


Pod的DNS策略

Pod的DNS策略指定了Pod内部用于域名解析的策略,在Pod配置中"dnsPolicy"字段的设置具有以下可选值:

  • ClusterFirst: 默认值,优先使用集群内部的CoreDNS服务进行域名解析,如果无法解析,则尝试使用主机上配置的DNS服务器进行域名解析
  • ClusterFirstWithHostNet: 作用与ClusterFirst一样,但仅在Pod运行在主机网络命名空间中(hostNetwork: true)时使用,确保Pod仍然可以使用集群内部的CoreDNS服务进行域名解析
  • Default: Pod使用主机上配置的DNS服务器进行域名解析,这意味着它不支持Service名称解析
  • None: 当设置为None时,需要通过"dnsConfig"字段来手动配置DNS参数

上面策略中ClusterFirstWithHostNet描述有些不好理解,实际上在生产环境中,有些应用为了性能或网络配置的原因,需要直接使用宿主机的网络(即hostNetwork: true)但是它可能还需要访问Kubernetes内部服务(例如,监控,日志服务)这时候 ClusterFirstWithHostNet DNS策略就可以派上用场了,示例:

vi dns-test.yaml

apiVersion: v1
kind: Pod
metadata:
  name: dns-test
spec:
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet
  containers:
    - image: harbor.peng.com/library/busybox
      name: busybox
      command: ['/bin/sh', '-c', 'sleep 1d']

创建资源后,尝试解析ServiceName域名

K8s从0开始-Service资源对象笔记_Service_21


自定义DNS记录

CoreDNS允许用户自定义DNS记录,以满足更多的域名解析需求,比如我们游戏服务实现容器化了,但是游戏层需要调用gm api服务, 而gm api服务是独立于游戏集群外的,虽然我们可以使用Service ExternalName这种独代的方式去满足容器应用请求外部服务的需求,但是往往这个gm api服务为多个域名,甚至是同一个域名后缀有多个这样的API服务游戏层里面会用到,那么这时候通过自定义DNS的方式会更加灵活地满足我们的需求. 编辑CoreDNS配置文件(存储在ConfiMap对象中):

kubectl edit configmap coredns -n kube-system

K8s从0开始-Service资源对象笔记_Service_22

上述配置中新增了"hosts{}"部分用于定义域名和IP地址映射,其中一条映射表示将域名"gm.xxxx.com"解析到IP地址"10.10.10.242",重建Pod使配置生效

kubectl rollout restart deployment/coredns -n kube-system

启动测试容器进行解析测试如下:

K8s从0开始-Service资源对象笔记_Service_23


指定外部DNS服务器

假设公司内部有一台DNS服务器,负责对内部应用程序的域名进行解析,现在,我们希望集群中的应用程序能够继续使用该域名访问内部应用程序,这虽然可以通过自定义DNS记录来实现,但是当域名较多或者动态变化时,会显得很不方便,在这种情况下,可以给CoreDNS指定外部DNS服务器作为上游服务器,将来自指定域名后缀的DNS请求转发到这台DNS服务器上,整个转发流程如图:

K8s从0开始-Service资源对象笔记_Service_24

指定外部DNS服务器的配置示例如下:

K8s从0开始-Service资源对象笔记_Service_25

上述配置新增了"xxx.com:53{}"部分,这意味这来自"xxx.com"域名后缀的DNS查询请求会被转发到外部DNS服务器"10.10.10.1"


Service代理模式

当访问Service的虚拟IP地址时,流量会被转到到后端Pod,那么这个转发是怎么实现的呢?

Service是一个抽象的资源对象,主要用于定义端口映射规则.具体的流量转发工作由kube-proxy组件负责,它利用主机上的iptables和IPVS技术来实现夹具体网络转发

kube-proxy组件默认使用iptables作为代理模式,通过在节点上自动配置iptables规则来实现Pod的负载均衡和网络转发,而在使用IPVS作为代理模式时,kube-proxy在节点上自动配置IPVS规则来实现Pod的负载均衡和网络转发

iptables和IPVS两种工作模式的总结对比

模式

性能

使用场景

优点

缺点

iptables

小规模集群

部署简单

规则多时性能下降

IPVS

大规模集群,高并发场景

性能优异,支持更高并发

需要额外的内核模块


参考文档:

服务(Service) | Kubernetes

举报

相关推荐

0 条评论