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: 使用云提供商的负载均衡器公开服务.