跳转至

kubernetes 服务

这里的服务指的 kubernetes 的 service。

为什么需要服务

指定 IP 来访问 pod 应用,在 k8s 中并不合适,因为:

  1. pod 随时会启动或者关闭,且地址是动态的。虽然可以固定 pod 的 IP,但不建议这么做。
  2. 多副本的 pod,意味着多个 pod 提供相同的服务,通过 pod ip 访问要自己解决负载均衡的问题。

服务的特点

kubernetes service 是一种资源:为一组功能相同的 pod 提供接入的资源。

服务在它的生命周期内,它的 IP 地址和端口不会改变。 客户端通过服务的 IP 地址和端口的连接默认会 均衡分发 到该服务的任意一个 pod 上。客户端不需要知道每个 pod 的 IP 地址。

创建服务

服务通过标签选择器来决定把请求分发到哪些 pod 。

创建一个三副本的 deployment 用于测试

先创建一个三副本的 deployment,名称是 kubia。

kubectl create deployment --image luksa/kubia --port 8080 --replicas 3 kubia #(1)!

    • 8080 端口用于指示 k8s :应用监听于 8080 端口。
    • 默认会打上标签 app=kubia
    • 命令可加 --dry-run=client -o yaml 来仅查看生成的 yaml。

创建服务

这是最小化的服务配置了。

kubia-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubia #(1)!
spec:
  ports:
  - port: 80 #(2)!
    targetPort: 8080 #(3)!
  selector:
    app: kubia  #(4)!
  1. k8s 服务的名称。
  2. k8s 服务的端口(集群内的)。
  3. pod 监听的端口,即服务的实际端口。
  4. 通过标签选择器决定把请求分发到哪些 pod。
kubectl create -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia  
EOF
tee kubia-svc.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia  
EOF

 kubectl create -f kubia-svc.yaml
kubectl expose deployment kubia --name kubia #(1)!
    • 默认是 ClusterIP 服务类型。
    • 标签选择器将使用指定 deployment 的标签:app=kubia
    • targetPort 将使用指定 deployment 定义的:8080
    • 命令可加 --dry-run=client -o yaml 来仅查看生成的 yaml。

检查服务

# kubectl get svc kubia
NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubia   ClusterIP   172.20.19.158   <none>        80/TCP    22m
这时可以通过服务的集群内 IP 的端口来访问 pod 提供的服务,这里是:172.20.19.158:80

查看 pod 列表。

# kubectl get pod -l app=kubia
NAME                     READY   STATUS    RESTARTS   AGE
kubia-679b67d5fc-gltdv   1/1     Running   0          72m
kubia-679b67d5fc-n55kn   1/1     Running   0          72m
kubia-679b67d5fc-xjp5n   1/1     Running   0          72m
访问服务。可以发现请求在 3 个 pod 中轮询。
# curl 172.20.19.158
You have hit kubia-679b67d5fc-xjp5n
# curl 172.20.19.158
You have hit kubia-679b67d5fc-gltdv
# curl 172.20.19.158
You have hit kubia-679b67d5fc-n55kn
 # curl 172.20.19.158
You have hit kubia-679b67d5fc-xjp5n

服务会话亲和性

这里指的是 kubernetes service 的会话亲和性。

在上面的检查服务中,可以发现请求在 3 个 pod 中轮询,如果希望同一客户端的请求都分发到同一个 pod,就可以设置服务的会话亲和性。

会话亲和性默认为 None,可以设置的属性为:ClientIP。k8s 的服务工作在传输层,所以它只能通过 ClientIP 来区别客户端。

设置会话的亲和性

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia
  sessionAffinity: ClientIP #(1)!
  1. 不配置这行的话默认是 None

kubectl patch service kubia -p '{"spec":{"sessionAffinity":"ClientIP"}}'
取消请设置成 Nonenull
kubectl patch service kubia -p '{"spec":{"sessionAffinity":"None"}}'
kubectl patch service kubia -p '{"spec":{"sessionAffinity":null}}'

验证。可以发现 3 次请求都分发给相同的 pod。

# curl 172.20.19.158
You have hit kubia-679b67d5fc-gltdv
# curl 172.20.19.158
You have hit kubia-679b67d5fc-gltdv
# curl 172.20.19.158
You have hit kubia-679b67d5fc-gltdv

同一服务暴露多端口

在创建一个有多个端口的服务时,必须给每个端口指定名字

kind: Deployment
spec:
  template:
    spec:
      contaniners:
      - name: kubia
        ports:
        - name: http
          containerPort: 8080
        - name: https
          containerPort: 8443  
---
apiVersion: v1
kind: Service
metadata:
  name: kubia2
spec:
  ports:
  - name: http
    port: 80
    targetPort: http  #(1)!
  - name: https
    port: 443
    targetPort: https #(2)!
  selector:
    app: kubia

  1. http 是上面第 8 行指定的端口名称。
  2. https 是上面第 10 行指定的端口名称。

服务使用命名端口后,在 pod 的服务端口号变更的情况下,不需要变更服务定义里的配置。

服务发现

kubernetes 为客户端提供了发现服务的 IP 和端口的方式。

1,通过环境变量发现

服务早于 pod 创建,pod 里才能通过环境变量的方式发现服务的 IP 和端口。

可以通过在 pod.spec 上将 enableServiceLinks 标志设置为 false 来禁用变量自动注入。

在 pod 里运行 env 命令发现服务的 IP 和端口。

# kubectl exec kubia-64cdbdbff-5klvc -- env | grep SERVICE
KUBIA_SERVICE_HOST=172.20.19.158
KUBIA_SERVICE_PORT=80

环境变量中的服务名称中的横杠被转换成下画线,且全部都是大写。

2,通过 DNS 发现

在部署了 coredns 的情况下,k8s 会修改每个容器的 /etc/resolv.conf ,让容器使用内部的 DNS 来进行域名解析。因此,在知道服务名称的情况下,能查询出服务的 FQDN(Fully Qualified Domain Name)并通过它访问服务。

pod 是否使用内部 DNS 服务器是根据 pod 中 spec 的 dnsPolicy 属性来决定。

进入一个容器 ping 服务名称。

# kubectl exec -it kubia-64cdbdbff-5klvc -- bash

root@kubia-64cdbdbff-5klvc:/# ping -c1 kubia #(1)!
PING kubia.default.svc.cluster.local (172.20.19.158): 56 data bytes
64 bytes from 172.20.19.158: icmp_seq=0 ttl=64 time=0.156 ms

  1. kubia 是前面创建的服务名称。

返回的 kubia.default.svc.cluster.local 中:

  1. kubia 是服务名称。
  2. default 是命名空间(namespace)。
  3. svc.cluster.local 是集群域后缀。

端口号还是需要从环境变量中获取。

通过域名访问服务

# kubectl exec -it kubia-64cdbdbff-5klvc -- bash

root@kubia-64cdbdbff-5klvc:/# curl http://kubia.default.svc.cluster.local
You have hit kubia-64cdbdbff-dkmct

root@kubia-64cdbdbff-5klvc:/# curl http://kubia.default
You have hit kubia-64cdbdbff-5klvc

root@kubia-64cdbdbff-5klvc:/# curl http://kubia #(1)!
You have hit kubia-64cdbdbff-pv49x

  1. 如果是同一个 namespace 下,可直接用服务名。

endpoint

endpoint 是介于 service 和 pod 之间的一种资源,是暴露一个服务的 IP 地址和端口的列表。

查看 endpoint 。

# kubectl describe svc kubia
Name:              kubia
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=kubia #(1)!
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                172.20.19.158
IPs:               172.20.19.158
Port:              <unset>  80/TCP
TargetPort:        8080/TCP
Endpoints:         172.21.116.134:8080,172.21.67.195:8080,172.21.71.195:8080 #(2)!
Session Affinity:  None
Events:            <none>
  1. 这个标签选择器用于构建 endpoint 列表。
  2. endpoint 的 IP 址址和端口列表。
# kubectl get ep kubia
NAME    ENDPOINTS                                                   AGE
kubia   172.21.116.134:8080,172.21.67.195:8080,172.21.71.195:8080   9h  

手动创建 endpoint

  1. 创建没有选择器的服务,没有选择器意味着不会自动创建 endpoint,需要手动管理 endpoint。

    apiVersion: v1
    kind: Service
    metadata:
      name: svc1 #(1)!
    spec:
      ports:
      - port: 80
    

    1. 这里的服务名称要和创建的 endpoint 名称要一致。
  2. 为没有选择器的服务创建 endpoint。

    apiVersion: v1
    kind: Endpoints
    metadata:
      name: svc1 #(1)!
    subsets:
    - addresses:
      - ip: 183.2.172.185
      - ip: 121.14.77.201
      ports:
      - port: 80
    

    1. 这里的名称要和前面创建的服务名称要一致。

如果将外部服务迁移到 k8s 集群中后,可以为服务添加选择器来让 k8s 自动管理 endpoint。

连接集群外部服务

让在集群里的 pod 像连接到内部服务一样的方式连接到外部服务。

  1. 通过手动管理 endpoint 的方式来实现,endpoint 里指定外部的 IP 地址和端口,如上面的例子。
  2. 通过 DNS 域名来实现。

创建 ExternalName 类型的服务。此类型不会创建 endpoint。

apiVersion: v1
kind: Service
metadata:
  name: external-svc
spec:
  type: ExternalName
  externalName: www.baidu.com
  ports:
  - port: 80

创建服务后,pod 可以通过 external-svc.default.svc.cluster.local 来访问外部服务。 如果要指向另外的服务,只需要修改 externalName 属性即可,pod 侧访问的 DNS 地址不用变更。

连接到此类型服务的客户端将直接连接到外部服务,完全绕过服务代理。

对外暴露服务

1,NodePort

如果客户端只指定某一个节点,那么该节点发生故障时,客户端将无法访问服务,即单点故障。

集群中的每个节点都打开一个端口(所有节点都使用相同的端口号)。端口的范围在 api-serverservice-node-port-range 参数里指定。

该端口不会在节点上有监听。

创建 NodePort 类型服务

apiVersion: v1
kind: Service
metadata:
  name: kubia-nodeport
spec:
  ports:
  - port: 80 #(1)!
    targetPort: 8080 #(2)!
    nodePort: 30123 #(3)!
  selector:
    app: kubia
  type: NodePort #(4)!
  1. 集群内访问的端口号。
  2. pod 的端口,即实际服务的端口。
  3. nodePort 上的服务端口,可选。如果不配置则从配置的端口范围内随机选择一个。
  4. 指定服务类型为 NodePort。
kubectl expose deployment kubia --name kubia-nodeport --type NodePort --port 80 --target-port 8080 --selector app=kubia

查看服务

# kubectl get svc kubia-nodeport
NAME             TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubia-nodeport   NodePort   172.20.188.1   <none>        80:30123/TCP   25s
访问服务
$ curl 172.20.188.1:80 #(1)!
You have hit kubia-64cdbdbff-pv49x

$ curl 10.95.22.1:30123 #(2)!
You have hit kubia-64cdbdbff-dkmct

$ curl 10.95.22.2:30123
You have hit kubia-64cdbdbff-5klvc

  1. 通过集群内的 cluster-ip 访问。
  2. 通过本机节点的 IP 地址访问。

2,LoadBalance

LoadBalance 是 NodePort 类型的一种扩展。

创建 LoadBanlancer 类型服务

apiVersion: v1
kind: Service
metadata:
  name: kubia-lb
spec:
  ports:
  - port: 18880 #(1)!
    targetPort: 8080 #(2)!
  selector:
    app: kubia
  type: LoadBalancer #(3)!
  allocateLoadBalancerNodePorts: false #(4)!
  1. 外部(集群内部也可以)访问的端口号。
  2. pod 的端口,即实际服务的端口。
  3. 指定服务类型为 LoadBalancer。
  4. 可选,如果不想分配 NodePort 端口则加上此配置。
kubectl expose deployment kubia --name kubia-lb --type LoadBalancer --port 18880 --target-port 8080 --selector app=kubia

如果不指定 --port,则会取跟 --target-port 一样的端口。( k8s v1.25.2 中)

查看服务

# kubectl get svc kubia-lb
NAME       TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)           AGE
kubia-lb   LoadBalancer   10.196.69.86   10.96.130.224   18880:30654/TCP   15s

系统会自动给服务分配一个 NodePort 端口,如果不想分配请加上配置 spec.allocateLoadBalancerNodePorts: false

访问服务

$ curl 10.196.69.86:18880 #(1) 通过集群内的 cluster-ip 访问加发布的端口访问。
You have hit kubia-865dd996f4-9lczw

$ curl 10.96.130.224:18880 #(2) 通过 LB 地址加发布的端口访问。
You have hit kubia-865dd996f4-9lczw

$ curl 10.96.0.1:30654 #(3) 通过节点的 IP 地址加 NodePort 端口访问。
You have hit kubia-865dd996f4-9lczw

  1. 通过集群内的 cluster-ip 访问加发布的端口访问。
  2. 通过 LB 地址加发布的端口访问。
  3. 通过节点的 IP 地址加 NodePort 端口访问。

浏览器使用 keep-alive 连接,连接有效期内的请求都会发送到同一个 pod。

externalTrafficPolicy: Local 特点(默认是 Cluster):

  1. 连接进入节点后只转给该节点的 pod,不会跨节点转发。
  2. 该节点上的应用能拿到请求的源 IP。
  3. 可能导致跨节点的 pod 负载不均衡:A 节点有 1 个 pod 承载 50% 请求,B 节点有 2个 pod 每个 pod 承载 25% 请求。

3,Ingress

Ingress 运行在 HTTP 层(网络协议第 7 层)。

😄 更新中。。。


就绪探针

就绪探针会定期调用,就绪探测返回成功时,表示容器已准备好接收请求。

可以配置一个等待时间(initialDelaySeconds),经过等待时间后才执行第一次就绪检查。

如果就绪检查失败,并不会终止或者重新 pod,只是把 pod 从服务的 endpoint 中移除。

可以通过删除 pod 或者更改 pod 标签来决定 pod 是否在服务中。

当删除 pod 时,k8s 会自动把 pod 从服务中移除。

就绪探针的三种类型:

  1. Exec 探针,在容器里执行命令,如果返回状态码 0 说明已就绪。
  2. HTTP GET 探针,向容器发 HTTP GET 请求,如果返回 2xx 或 3xx 说明已就绪。
  3. TCP socket 探针,与容器建立 TCP 连接,如果连接能建立则表示容器已就绪。

添加就绪探针例子,在spec.template.spec.containers里添加。

        readinessProbe:
          exec:
            command:
            - ls
            - /var/ready
        readinessProbe:
          httpGet:
            path: /health
            port: 80
            scheme: http
        readinessProbe:
          tcpSocket:
            port: 80

因为没有 /var/ready 文件,所以容器都没就绪( READY 为 0/1 )。

# kubectl get pods -w
NAME                   READY   STATUS    RESTARTS   AGE
kubia-8797b788-6th9s   0/1     Running   0          27s
kubia-8797b788-gprrg   0/1     Running   0          26s
kubia-8797b788-w82cz   0/1     Running   0          26s
给第一个容器生成文件,最晚过 10 秒后第一个容器就绪。
# kubectl exec kubia-8797b788-6th9s -- touch /var/ready

# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
kubia-8797b788-6th9s   1/1     Running   0          10m
kubia-8797b788-gprrg   0/1     Running   0          10m
kubia-8797b788-w82cz   0/1     Running   0          10m
可以通过kubectl describe查看容器的就绪探测的参数信息。
k8s v1.25.14 的默认值
Readiness:      exec [ls /var/ready] delay=0s timeout=1s period=10s #success=1 #failure=3

定时器参数:

        readinessProbe:
          exec:
            command:
            - ls
            - /var/ready
          initialDelaySeconds: 0     # 容器启动立即探测
          timeoutSeconds: 1          # 表示容器必须在 1s 内返回结果,否则视为探测失败
          periodSeconds: 10          # 探测周期,每 30s 探测一次
          successThreshold: 1        # 连续探测 1 次成功表示成功
          failureThreshold: 3        # 连续探测 3 次失败表示失败


headless 服务

也叫无头服务,有以下特点:

  • 不分配 ClusterIP
  • 请求不过负载均衡器(kube-proxy 不会为其创建转发规则),直接转发给后端 pod 。
  • 对该服务名的 DNS 解析,返回的是全部后端 pod IP 。

StatefulSet 使用 Headless Service 解决 Pod 间互相访问的问题。 下面是一个与有状态服务一起使用的例子。

创建 headless 类型服务

apiVersion: v1
kind: Service
metadata:
  name: kubia-headless
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: web
  clusterIP: None #(1)!
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: kubia-headless #(2)!
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: luksa/kubia
  1. clusterIP 设置成 None,表示 headless 服务。
  2. headless service 的名称,要和上面的第 11 行匹配。
kubectl expose sts web --name kubia-headless --cluster-ip None --port 80 --target-port 8080 --selector app=web

有状态副本集创建后,pod 的信息如下:

# kubectl get pods -o wide -w
NAME    READY   STATUS    RESTARTS   AGE     IP               NODE          NOMINATED NODE   READINESS GATES
web-0   1/1     Running   0          49m     172.21.95.243    k8s-master3   <none>           <none>
web-1   1/1     Running   0          49m     172.21.103.242   k8s-master1   <none>           <none>
web-2   1/1     Running   0          49m     172.21.6.11      k8s-master2   <none>           <none>

有状态副本集 pod 的命名:有状态副本集的名称-序号

无状态服务的信息:

# kubectl get svc kubia-headless
NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubia-headless   ClusterIP   None #(1)    <none>        80/TCP    113s

  1. CLUSTER-IPNone 表示是无头服务。

起个有 dns 工具的 pod 来查询域名,查询无头服务时,返回全部的 pod IP。

# kubectl run -it --image=tutum/dnsutils --image-pull-policy=IfNotPresent dnsutils --restart=Never --rm --overrides='{ "apiVersion": "v1", "spec": { "nodeName": "k8s-master3" } }' -- /bin/bash
If you don't see a command prompt, try pressing enter.
root@dnsutils:/#
root@dnsutils:/# nslookup kubia-headless
Server:         172.20.0.10
Address:        172.20.0.10#53

Name:   kubia-headless.default.svc.cluster.local
Address: 172.21.6.11
Name:   kubia-headless.default.svc.cluster.local
Address: 172.21.95.243
Name:   kubia-headless.default.svc.cluster.local
Address: 172.21.103.242

还为每个 pod 生成了对应的服务,格式为:<pod-name>.<svc-name>.<namespace>.svc.cluster.local

root@dnsutils:/# nslookup web-0.kubia-headless
Server:         172.20.0.10
Address:        172.20.0.10#53

Name:   web-0.kubia-headless.default.svc.cluster.local
Address: 172.21.95.243

root@dnsutils:/# nslookup web-1.kubia-headless
Server:         172.20.0.10
Address:        172.20.0.10#53

Name:   web-1.kubia-headless.default.svc.cluster.local
Address: 172.21.103.242

root@dnsutils:/# nslookup web-2.kubia-headless
Server:         172.20.0.10
Address:        172.20.0.10#53

Name:   web-2.kubia-headless.default.svc.cluster.local
Address: 172.21.6.11

请求 headless 服务,会按 DNS 解析回来的第一条进行访问。

root@dnsutils:/# curl kubia-headless:8080 #(1)!
You have hit web-1
root@dnsutils:/# curl kubia-headless:8080
You have hit web-0
root@dnsutils:/# curl kubia-headless:8080
You have hit web-2

  1. 目标端口使用 pod 实际的监听端口。
创建时间: 2024-04-20 23:26:21 最后更新: 2024-05-24 11:41 更新次数: 5 浏览次数: