1 什么是Service?

Service主要用于Pod之间的通信,对于Pod的IP地址而言,Service是提前定义好并且是不变的资源类型。

2 Service基本概念

Kubernetes Pod具有生命周期的概念,它可以被创建、删除、销毁,一旦被销毁就意味着生命周期的结束。

通过ReplicaSet能够动态地创建和销毁Pod,例如进行扩缩容和执行滚动升级。

每个Pod都会获取到它自己的IP地址,但是这些IP地址不总是稳定和可依赖的,

这样就会导致一个问题:一组Pod(比如后端的Pod)为其他Pod(比如前端的Pod)提供服务时,它们之间如果使用Pod的IP地址进行通信,在Pod重建后,将无法再进行连接。

为了解决上述问题,Kubernetes引用了Service这样一种抽象概念:逻辑上的一组Pod,即一种可以访问Pod的策略——通常称为微服务。

这一组Pod能够被Service访问到,通常是通过Label Selector(标签选择器)实现的。

举个例子,有一个用作图片处理的backend(后端),运行了3个副本,这些副本是可互换的,所以frontend(前端)不需要关心它们调用了哪个backend副本,

然而组成这一组backend程序的Pod实际上可能会发生变化,即便这样frontend也没有必要知道,而且也不需要跟踪这一组backend的状态,因为Service能够解耦这种关联。

对于Kubernetes集群中的应用,Kubernetes提供了简单的Endpoints API,只要Service中的一组Pod发生变更,应用程序就会被更新。对非Kubernetes集群中的应用,Kubernetes提供了基于VIP的网桥的方式访问Service,再由Service重定向到backend Pod。

3 定义Service

3.1 基于Selector的Service

一个Service在Kubernetes中是一个REST对象,和Pod类似。像所有REST对象一样,Service的定义可以基于POST方式,请求APIServer创建新的实例。例如,假定有一组Pod,它们暴露了8080端口,同时具有app=nginx-app标签。此时可以定义Service如下:

cat >>nginx-svc.yaml<<EOF
kind: Service
apiVersion: v1
metadata:
  labels:
    app: nginx-svc  # service的label
  name: nginx-svc   # svc的名称
spec:
  selector:
    app: nginx-app  # 后端pod的label
  ports:
    - name: http    # service端口的名称
      protocol: TCP # UDP,TCP,SCTP  默认TCP
      port: 80      # service的端口, 用于对外访问, 
      targetPort: 80  # 后端应用pod的端口
EOF

上述配置创建一个名为my-service的Service对象,它会将请求代理到TCP端口为9376并且具有标签app=MyApp的Pod上。这个Service会被分配一个IP地址,通常称为ClusterIP,它会被服务的代理使用。

需要注意的是,Service能够将一个接收端口映射到任意的targetPort。默认情况下,targetPort将被设置为与Port字段相同的值。targetPort可以设置为一个字符串,引用backend Pod的一个端口的名称。

创建svc

[root@k8s-master-01 k8s_test]# kubectl  create -f nginx-svc.yaml 
service/nginx-svc created

[root@k8s-master-01 k8s_test]# kubectl  get svc nginx-svc 
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx-svc    ClusterIP   10.244.72.66   <none>        80/TCP    13s

创建后端pod

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-app
  namespace: default
spec:
  revisionHistoryLimit: 10
  replicas: 3
  selector:
    matchLabels:
      app: nginx-app
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx-app
    spec:
      nodeSelector:
        nginx-app: "true"
      containers:
      - image: nginx:1.15.1
        imagePullPolicy: IfNotPresent
        name: nginx-app
        ports:
        - containerPort: 80
          name: nginx-app
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30

访问svc

  • 直接通过clusterIP访问
[root@k8s-master-01 k8s_test]# curl http://10.244.72.66:80 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
  • 在pod里通过servicename.namespace访问

跨namespace访问时需要加.namespace, 相同namespace下的pod访问svc不需要加

[root@k8s-master-01 k8s_test]# kubectl  exec -it busybox -- sh 

/ # wget nginx-svc.default
Connecting to nginx-svc.default (10.244.72.66:80)
index.html           100% |******************************************************************************************************************************************************************|   612   0:00:00 ETA

/ # cat index.html 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

3.2 使用Service代理k8s集群外部的服务

Service抽象了该如何访问Kubernetes Pod,但也能够抽象其他类型的backend,例如:

  • 希望在生产环境中访问外部的数据库集群。
  • 希望Service指向另一个NameSpace中或其他集群中的服务。
  • 正在将工作负载转移到Kubernetes集群,和运行在Kubernetes集群之外的backend。
  • 在任何这些场景中,都能定义没有Selector的Service:
cat >>baidu-svc-ex.yaml<<EOF
kind: Service
apiVersion: v1
metadata:
  labels:
    app: baidu-svc-ex
  name: baidu-svc-ex
spec:
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
EOF
  • 由于这个Service没有Selector,就不会创建相关的Endpoints对象,需要手动将Service映射到指定的Endpoints:
cat >>baidu-ep.yaml<<EOF
kind: Endpoints
apiVersion: v1
metadata:
  name: baidu-svc-ex  # 此处需要和没有Selector的Service name相同, 
subsets:
  - addresses:
      - ip: 220.181.38.148 # 百度的IP地址
    ports:
    - name: http     # 需要与Service中一致
      port: 80       # 需要与Service中一致
      protocol: TCP  # 需要与Service中一致
EOF
  • 访问测试
# 访问外网服务
[root@k8s-master-01 k8s_test]# curl baidu.com -I
HTTP/1.1 200 OK
Date: Sun, 29 May 2022 09:28:18 GMT
Server: Apache
Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
ETag: "51-47cf7e6ee8400"
Accept-Ranges: bytes
Content-Length: 81
Cache-Control: max-age=86400
Expires: Mon, 30 May 2022 09:28:18 GMT
Connection: Keep-Alive
Content-Type: text/html

# 访问svc
[root@k8s-master-01 k8s_test]# curl 10.244.20.4 -I
HTTP/1.1 200 OK
Date: Sun, 29 May 2022 09:28:23 GMT
Server: Apache
Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
ETag: "51-47cf7e6ee8400"
Accept-Ranges: bytes
Content-Length: 81
Cache-Control: max-age=86400
Expires: Mon, 30 May 2022 09:28:23 GMT
Connection: Keep-Alive
Content-Type: text/html

# POD里访问这个SVC, 百度防盗链机制, 需要添加一个Header才能用service名访问
/ # wget http://baidu-svc-ex  --header="Host: baidu.com"
Connecting to baidu-svc-ex (10.244.20.4:80)
index.html           100% |******************************************************************************************|    81   0:00:00 ETA
/ # cat index.html 
<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>
  • 当外部服务ip变动时, 只需要修改ep的ip地址就可以了
cat > baidu-ep.yaml <<EOF
kind: Endpoints
apiVersion: v1
metadata:
  name: baidu-svc-ex
subsets:
  - addresses:
      - ip: 140.205.94.189 # 修改为淘宝的IP地址
    ports:
    - name: http
      port: 80
      protocol: TCP
EOF

# 修改ep
[root@k8s-master-01 k8s_test]# kubectl  replace -f baidu-ep.yaml 
endpoints/baidu-svc-ex replaced
  • 访问测试
# 访问svc
[root@k8s-master-01 k8s_test]# curl 10.244.20.4  -H "Host: taobao.com"
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>302 Found</title></head>
<body bgcolor="white">
<h1>302 Found</h1>
<p>The requested resource resides temporarily under a different URI.</p>
<hr/>Powered by Tengine</body>
</html>

# 直接访问淘宝
[root@k8s-master-01 k8s_test]# curl http://taobao.com
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>302 Found</title></head>
<body bgcolor="white">
<h1>302 Found</h1>
<p>The requested resource resides temporarily under a different URI.</p>
<hr/>Powered by Tengine</body>
</html>

3.3 使用Service代理外部域名

代理外部域名只需要创建一个svc即可, 不需要创建endpoint

ExternalName Service是Service的特例,它没有Selector,也没有定义任何端口和Endpoint,它通过返回该外部服务的别名来提供服务。

比如当查询主机baidu-ex-name时,集群的DNS服务将返回一个值为www.baidu.com的CNAME记录:

[root@k8s-master-01 k8s_test]# cat baidu-ex-name.yaml 
kind: Service
apiVersion: v1
metadata:
  labels:
    app: baidu-ex-name
  name: baidu-ex-name
spec:
  sessionAffinity: None
  type: ExternalName
  externalName: www.baidu.com

[root@k8s-master-01 k8s_test]# kubectl  apply -f baidu-ex-name.yaml 
service/baidu-ex-name created
  • 访问测试
[root@k8s-master-01 k8s_test]# kubectl  exec -it busybox -- sh

/ # wget http://baidu-ex-name.default --header="Host:www.baidu.com"
Connecting to baidu-ex-name.default (14.215.177.38:80)
index.html           100% |********************************************************************************|  2381   0:00:00 ETA

Service的类型

  • ClusterIP, 在集群内部使用, 也是默认值
  • ExternalName, 通过返回定义的CNAME
  • NodePort: 在所有安装了kube-proxy的节点上打开一个端口, 此端口可以代理至后端的pod, 然后集群外部可以使用节点的ip地址和NodePort的端口号访问到集群的pod服务, 默认端口范围30000-32767
  • LoadBalancer: 使用云提供商的负载均衡器公开服务.