Service

发布时间: 更新时间: 总字数:2131 阅读时间:5m 作者: IP上海 分享 网址

Kubernetes Service 从逻辑上定义了运行在集群中的一组 Pod,这些 Pod 提供了相同的功能。当每个 Service 创建时,会被分配一个唯一的 IP 地址(也称为 clusterIP,virtual IP)。Service 工作在 TCP 四层。

介绍

  • Service 的名称解析强依赖于 CoreDNS(之前称 kube-dns)
  • 工作结构
api-server <--watch--> kube-proxy <----> iptables/ipvs
  • 代理结构
Service <---> Endpoints <---> Pods

kube-proxy 的工作模式:

  • userspace v1.1 之前,使用 kube-proxy 代理流量,效率低,已废弃
  • iptables v1.10 之前
  • ipvs v1.11 之后
    • IPVS 专为负载平衡而设计,并基于内核内哈希表
    • 基于 IPVS 的 kube-proxy 具有更复杂的负载均衡算法(最小连接、局部性、 加权、持久性)
    • 相关命令
      • apt install ipvsadm
      • ipvsadm -ln

Service 示例

查看帮助 kubectl explain service/svc

下面列举 Service 和 Deployment 为本文的示例基础:

  • deployment-hello-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-app-dp
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-app
      release: canary
  template:
    metadata:
      name: hello-app-pod
      labels:
        app: hello-app
        release: canary
    spec:
      containers:
      - name: hello-app-1
        image: gcriogooglesamples/hello-app:1.0
        ports:
        - name: http
          containerPort: 8080
  • deployment-hello-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-app-dp
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-app
      release: canary
  template:
    metadata:
      name: hello-app-pod
      labels:
        app: hello-app
        release: canary
    spec:
      containers:
      - name: hello-app-1
        image: gcriogooglesamples/hello-app:1.0
        ports:
        - name: http
          containerPort: 8080
  • 准备 t1 容器,用于测试命令:
$ kubectl run t1 --image=xiexianbin/alpine:1 -it --restart=Never -- /bin/sh

Service Type

  • ClusterIP(Default)
  • NodePort
  • LoadBalancer
  • ExternalName

ClusterIP

通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。默认的 ServiceType

  • 使用 iptables/ipvs 实现的 virtual IP
  • No ClusterIP: Headless Service
    • DNS: ServiceName -> PodIP

NodePort

通过每个节点上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到自动创建的 ClusterIP 服务。通过请求 <节点 IP>:<节点端口>,可以从集群的外部访问一个 NodePort 服务

  • 节点IP:kube-proxy 中的 --nodeport-addresses 参数或者将 kube-proxy 配置文件中的等效 nodePortAddresses=10.0.0.0/8,192.0.2.0/25 字段设置为特定的 IP 块
  • 节点端口:Kubernetes 控制平面将在 --service-node-port-range 标志指定范围内分配端口(默认值:30000-32767

代理结构如下:

client -> NodeIP:NodePort -> ClusterIP:ServerPort -> PodIP:containerPort

NodePort 具有如下特点:

  • 一个端口只能给一个服务使用
  • 只支持4层负载均衡

示例:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort
  selector:
    app: MyApp
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080
  • targetPort:容器接收流量的端口
  • port:抽象的 Service 端口,可以使任何其它 Pod 访问该 Service 的端口

若需要自定义,在 /etc/kubernetes/manifests/kube-apiserver.yaml 中添加:

...
spec:
  containers:
  - command:
    - kube-apiserver
    - --service-node-port-range=2500-32767
...

参考:How to change Kubernetes service’s node port range?

LoadBalancer

一般有云服务商提供,外部负载均衡器将流量路由到 Node Pod(通过 NodePort 或 ClusterIP)。Kubernetes 调用 IaaS 上的 LBaaS 服务,代理结构如下:

IaaS LBaaS <--> LB's Node Pod <--> Service <--> Pod

ExternalName

Service 代理外部的服务,供内部的其他服务使用,一般为 FQDN (CNAME 方式实现)

服务发现

Kubernetes 支持两种服务发现模式:

  • 环境变量
  • DNS

环境变量服务发现

hello-app-svc 的 Service 暴露了 TCP 端口 80, 同时给它分配了 Cluster IP 地址 10.100.108.93,Pod 中可以看到的环境变量如下:

HELLO_APP_SVC_PORT_80_TCP=tcp://10.100.108.93:80
HELLO_APP_SVC_SERVICE_HOST=10.100.108.93
HELLO_APP_SVC_SERVICE_PORT_PORT_80=80
HELLO_APP_SVC_SERVICE_PORT=80
HELLO_APP_SVC_PORT=tcp://10.100.108.93:80
HELLO_APP_SVC_PORT_80_TCP_ADDR=10.100.108.93
HELLO_APP_SVC_PORT_80_TCP_PORT=80
HELLO_APP_SVC_PORT_80_TCP_PROTO=tcp

DNS 服务发现

A/AAAA 记录查询

格式为:

[SVC_NAME].[NS_NAME].svc.DOMAIN.LTD

其中 svc.DOMAIN.LTD 一般为 svc.cluster.local,本示例为 svc.kb.cx

本示例中 Kubernetes 命名空间 default 中有一个名为 hello-app-svc 的服务,则控制平面和 DNS 服务共同为 hello-app-svc.default

/ # nslookup hello-app-svc
Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	hello-app-svc.default.svc.kb.cx
Address: 10.100.108.93

/ # nslookup hello-app-svc.default
Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	hello-app-svc.default.svc.kb.cx
Address: 10.100.108.93

/ # nslookup hello-app-svc.default.svc.kb.cx
Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	hello-app-svc.default.svc.kb.cx
Address: 10.100.108.93
/ # dig -t A hello-app-svc.default.svc.kb.cx @10.96.0.10

; <<>> DiG 9.16.27 <<>> -t A hello-app-svc.default.svc.kb.cx @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40645
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 47e66e4565215444 (echoed)
;; QUESTION SECTION:
;hello-app-svc.default.svc.kb.cx. IN	A

;; ANSWER SECTION:
hello-app-svc.default.svc.kb.cx. 30 IN	A	10.100.108.93

;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sat Mar 19 06:17:08 UTC 2022
;; MSG SIZE  rcvd: 119

可以看到,通过 A 记录执行 Server 的 Cluster IP。因此,由于 DNS A 记录的存在,因此我们可以通过 curl 命令通过 svc 访问服务,在 t1 容器中,执行如下命令:

/ # curl http://hello-app-svc.default.svc.kb.cx
Hello, world!
Version: 2.0.0
Hostname: hello-app-dp-665877bb77-bzbcp

A/AAAA 查询无头 Service

无头(Headless)服务(即没有 ClusterIP)也会可以通过 DNS A 或 AAAA 记录实现服务发现,格式与有头服务相同,示例:

  • service-hello-app-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-app-svc-hl
  namespace: default
spec:
  selector:
    app: hello-app
    release: canary
  clusterIP: None
  ports:
  - name: port-80
    port: 80
    targetPort: 8080
    protocol: TCP

查看 svc CLUSTER-IP 为 None:

root@k8s-master:~/manifests# kubectl get svc
NAME                TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
hello-app-svc-hl    ClusterIP      None             <none>        80/TCP         4s

在 t1 中查询 A 记录:

/ # dig -t A hello-app-svc-hl.default.svc.kb.cx @10.96.0.10

; <<>> DiG 9.16.1-Ubuntu <<>> -t A hello-app-svc.default.svc.kb.cx @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19031
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 4783e3724a1159ed (echoed)
;; QUESTION SECTION:
;hello-app-svc.default.svc.kb.cx. IN	A

;; ANSWER SECTION:
hello-app-svc.default.svc.kb.cx. 30 IN	A	10.244.1.59
hello-app-svc.default.svc.kb.cx. 30 IN	A	10.244.1.58

;; Query time: 8 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sat Mar 19 12:12:29 CST 2022
;; MSG SIZE  rcvd: 166

发现 A 记录不是解析到 Service 的 ClusterIP,而是直接解析到 Pod 的 IP 地址,实现 DNS 级别的负载均衡。

SRV 记录查询

格式为:

[_MY_PORT_NAME].[_MY_PORT_PROTOCOL].[SVC_NAME].[NS_NAME].svc.cluster.local

示例中,default 命名空间中 hello-app-svc 服务的 DNS SRV(服务)记录信息如下:

  • MY_PORT_NAME: port-80
  • MY_PORT_PROTOCOL: tcp

因此,Service hello-app-svc 的 SRV 记录为 _port-80._tcp.hello-app-svc.default.svc.kb.cx,在 t1 中执行 DNS SRV 查询如下:

/ # nslookup
> set type=srv
> _port-80._tcp.hello-app-svc.default.svc.kb.cx
Server:		10.96.0.10
Address:	10.96.0.10#53

_port-80._tcp.hello-app-svc.default.svc.kb.cx	service = 0 100 80 hello-app-svc.default.svc.kb.cx.

Pods 记录查询

  • 通过 Pod IP 暴露 Pod 格式为:
[_POD-IP].[SVC_NAME].[NS_NAME].pod.cluster.local

本示例中,IP 地址为 10.244.1.63 的 Pod,DNS 名称为 10-244-1-63.default.pod.kb.cx

/ # dig -t A 10-244-1-63.default.pod.kb.cx @10.96.0.10 +short
10.244.1.63
  • 通过 Service 暴露 Pod 格式为:
[_POD-IP].[SVC_NAME].[NS_NAME].svc.cluster.local

本示例中,IP 地址为 10.244.1.63 的 Pod,DNS 名称为 10-244-1-63.hello-app-svc.default.svc.kb.cx

/ # dig -t A 10-244-1-63.hello-app-svc.default.svc.kb.cx +short
10.244.1.63
  • 通过 subdomain 暴露

Pod 规约中包含一个可选的 hostname 字段,可以用来指定 Pod 的主机名。当这个字段被设置时,它将优先于 Pod 的名字成为该 Pod 的主机名。

Pod 规约还有一个可选的 subdomain 字段,可以用来指定 Pod 的子域名。

  • hostname-subdomain-hello-app.yaml 创建无头服务
---
apiVersion: v1
kind: Service
metadata:
  name: hello-app-subdomain
spec:
  selector:
    name: hello-app
  clusterIP: None
  ports:
  - name: http
    port: 80
    targetPort: 8080
---
apiVersion: v1
kind: Pod
metadata:
  name: hello-app-v1
  labels:
    name: hello-app
spec:
  hostname: hello-app-v1
  subdomain: hello-app-subdomain
  setHostnameAsFQDN: true
  containers:
  - image: gcriogooglesamples/hello-app:1.0
    name: hello-app
    ports:
    - name: http
      containerPort: 8080
---
apiVersion: v1
kind: Pod
metadata:
  name: hello-app-v2
  labels:
    name: hello-app
spec:
  hostname: hello-app-v2
  subdomain: hello-app-subdomain
  setHostnameAsFQDN: true
  containers:
  - image: gcriogooglesamples/hello-app:2.0
    name: hello-app
    ports:
    - name: http
      containerPort: 8080

注意:

  1. 只有 pod.spec.setHostnameAsFQDN: true下面的测试才生效
  2. Service 的 name 和 Pod 的 subdomain 是相同的

进入容器中查看 pod 的 DNS:

$ kubectl exec -it hello-app-v1 -- sh
/ # hostname -d
hello-app-subdomain.default.svc.kb.cx
/ # hostname -f
hello-app-v1.hello-app-subdomain.default.svc.kb.cx

其他错误系统,使用如下命令:

hostname --fqdn

通过 hello-app-subdomain.default.svc.kb.cx 查看无头服务:

/ # dig hello-app-subdomain.default.svc.kb.cx +short
10.244.1.79
10.244.1.78
  • 查看用户自定义的域名,在 t1 中执行命令:
/ # dig hello-app-v1.hello-app-subdomain.default.svc.kb.cx +short
10.244.1.79
/ # dig hello-app-v2.hello-app-subdomain.default.svc.kb.cx +short
10.244.1.78

说明:通过 hostname 成功为无头服务配置域名。

Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数