K8S service 深入解读
今天深入来解读 k8s service 相关概念和应用。
一、概要
将在一组 Pod 上运行的应用程序公开为网络服务的抽象方法。使用 Kubernetes,您无需修改应用程序即可使用不熟悉的服务发现机制。 Kubernetes 为 Pod 提供了自己的 IP 地址和一组 Pod 的单个 DNS 名称,并且可以在它们之间进行负载平衡。
创建和销毁 Kubernetes Pod 以匹配集群的状态。 Pod 是非永久性资源。如果您使用 Deployment 来运行您的应用程序,它可以动态地创建和销毁 Pod。
每个 Pod 都有自己的 IP 地址,但是在部署中,某一时刻运行的 Pod 集可能与稍后运行该应用程序的 Pod 集不同。
这会导致一个问题:如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能,前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用后端部分的工作量?
理解各种port
● port
port是k8s集群内部访问service的端口,即通过clusterIP:port可以从Pod所在的Node上访问到service
● nodePort
nodePort是外部访问k8s集群中service的端口,通过nodeIP:nodePort可以从外部访问到某个service
● targetPort
targetPort是Pod的端口,从port或nodePort来的流量经过kube-proxy反向代理负载均衡转发到后端Pod的targetPort上,最后进入容器。
● containerPort
containerPort是Pod内部容器的端口,targetPort映射到containerPort。
创建Deployment
名词解释
#test-pod
apiVersion: v1 #指定api版本,此值必须在kubectl apiversion中
kind: Pod #指定创建资源的角色/类型
metadata: #资源的元数据/属性
name: test-pod #资源的名字,在同一个namespace中必须唯一
labels: #设定资源的标签
k8s-app: apache
version: v1
kubernetes.io/cluster-service: "true"
annotations: #自定义注解列表
- name: String #自定义注解名字
spec: #specification of the resource content 指定该资源的内容
restartPolicy: Always #表明该容器一直运行,默认k8s的策略,在此容器退出后,会立即创建一个相同的容器
nodeSelector: #节点选择,先给主机打标签kubectl label nodes kube-node1 zone=node1
zone: node1
containers:
- name: test-pod #容器的名字
image: 10.192.21.18:5000/test/chat:latest #容器使用的镜像地址
imagePullPolicy: Never #三个选择Always、Never、IfNotPresent,每次启动时检查和更新(从registery)images的策略,
# Always,每次都检查
# Never,每次都不检查(不管本地是否有)
# IfNotPresent,如果本地有就不检查,如果没有就拉取
command: ['sh'] #启动容器的运行命令,将覆盖容器中的Entrypoint,对应Dockefile中的ENTRYPOINT
args: ["$(str)"] #启动容器的命令参数,对应Dockerfile中CMD参数
env: #指定容器中的环境变量
- name: str #变量的名字
value: "/etc/run.sh" #变量的值
resources: #资源管理
requests: #容器运行时,最低资源需求,也就是说最少需要多少资源容器才能正常运行
cpu: 0.1 #CPU资源(核数),两种方式,浮点数或者是整数+m,0.1=100m,最少值为0.001核(1m)
memory: 32Mi #内存使用量
limits: #资源限制
cpu: 0.5
memory: 1000Mi
ports:
- containerPort: 80 #容器开发对外的端口
name: httpd #名称
protocol: TCP
livenessProbe: #pod内容器健康检查的设置
httpGet: #通过httpget检查健康,返回200-399之间,则认为容器正常
path: / #URI地址
port: 80
#host: 127.0.0.1 #主机地址
scheme: HTTP
initialDelaySeconds: 180 #表明第一次检测在容器启动后多长时间后开始
timeoutSeconds: 5 #检测的超时时间
periodSeconds: 15 #检查间隔时间
#也可以用这种方法
#exec: 执行命令的方法进行监测,如果其退出码不为0,则认为容器正常
# command:
# - cat
# - /tmp/health
#也可以用这种方法
#tcpSocket: //通过tcpSocket检查健康
# port: number
lifecycle: #生命周期管理
postStart: #容器运行之前运行的任务
exec:
command:
- 'sh'
- 'yum upgrade -y'
preStop:#容器关闭之前运行的任务
exec:
command: ['service httpd stop']
volumeMounts: #挂载持久存储卷
- name: volume #挂载设备的名字,与volumes[*].name 需要对应
mountPath: /data #挂载到容器的某个路径下
readOnly: True
volumes: #定义一组挂载设备
- name: volume #定义一个挂载设备的名字
#meptyDir: {}
hostPath:
path: /opt #挂载设备类型为hostPath,路径为宿主机下的/opt,这里设备类型支持很多种
#nfs
(1)创建一个yaml文件
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.10
ports:
- containerPort: 80
(2)创建deployment
[root@master-01 YAML_k8s]# kubectl create -f nginx-deployment.yaml
deployment.apps "nginx-deployment" created
[root@master-01 YAML_k8s]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-deployment-6b7b4d57b4-26wzj 1/1 Running 0 2m 10.20.184.83 master-01
nginx-deployment-6b7b4d57b4-9w7tm 1/1 Running 0 2m 10.20.190.60 node-01
nginx-deployment-6b7b4d57b4-mhh8t 1/1 Running 0 2m 10.20.254.108 node-03
[root@master-01 YAML_k8s]# kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx-deployment 3 3 3 3 2m
创建Service提供对外访问的接口
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
app: nginx
spec:
ports:
- port: 88
targetPort: 80
selector:
app: nginx
####
apiVersion: 指定版本
kind: 类型
name: 指定服务名称
labels: 标签
port: Service 服务暴露的端口
targetPort: 容器暴露的端口
seletor: 关联的Pod的标签
创建service
# kubectl create -f nginx-service.yaml
查看service(访问Pod是有负载均衡的)
[root@master-01 YAML_k8s]# kubectl get svc/nginx-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP 10.254.131.176 <none> 88/TCP 1m
Endpoint
一个service对应的“后端”由Pod的IP和容器端口号
组成,即一个完整的“IP:Port”访问地址,这在 k8s 系统中叫做 EndPoint(端)。通过查看Service 的详细信息,可以查看其后端EndPoint 列表:
# 查看所有命名空间下的pod
kubectl get pods --all-namespaces
kubectl get pod -n seldon
[root@centos03 ~]# kubectl get pod -n seldon
NAME READY STATUS RESTARTS AGE
iris-model-default-0-classifier-7d79b85bf-hjlbg 2/2 Running 9 17d
查看服务
[root@centos03 ~]# kubectl get svc -n seldon
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
iris-model-default ClusterIP 10.233.53.254 <none> 8000/TCP,5001/TCP 8d
iris-model-default-classifier ClusterIP 10.233.61.74 <none> 9000/TCP,9500/TCP 17d
查看服务描述
[root@centos03 ~]# kubectl describe svc iris-model-default -n seldon
Name: iris-model-default
Namespace: seldon
Labels: app.kubernetes.io/managed-by=seldon-core
seldon-app=iris-model-default
seldon-deployment-id=iris-model
Annotations: getambassador.io/config:
---
apiVersion: ambassador/v1
kind: Mapping
name: seldon_seldon_iris-model_default_rest_mapping
prefix: /seldon/seldon/iris-model/
rewrite: /
service: iris-model-default.seldon:8000
timeout_ms: 3000
---
apiVersion: ambassador/v1
kind: Mapping
name: seldon_seldon_iris-model_default_grpc_mapping
grpc: true
prefix: /(seldon.protos.*|tensorflow.serving.*|inference.*)/.*
prefix_regex: true
rewrite: ""
service: iris-model-default.seldon:5001
timeout_ms: 3000
headers:
namespace: seldon
seldon: iris-model
---
Selector: seldon-app=iris-model-default
Type: ClusterIP
IP Families: <none>
IP: 10.233.53.254
IPs: 10.233.53.254
Port: http 8000/TCP
TargetPort: 8000/TCP
Endpoints: 10.233.72.119:8000
Port: grpc 5001/TCP
TargetPort: 5001/TCP
Endpoints: 10.233.72.119:5001
Session Affinity: None
Events: <none>
实际上,k8s 自动创建了与 Service 关联的 Endpoint 资源对象,这可以通过查询 Endpoint 对象进行查看:
[root@centos03 ~]# kubectl get endpoints -n seldon
NAME ENDPOINTS AGE
iris-model-default 10.233.72.119:5001,10.233.72.119:8000 9d
iris-model-default-classifier 10.233.72.119:9000,10.233.72.119:9500 17d
[root@centos03 ~]#
Service 不仅具有标准网络协议的IP地址,还以DNS域名的形式存在。Service 的域名表示方法为 <servicename>.<namespace>.svc.<clusterdomain>
, servicename 为服务的名称,namespace 为其所在namespace 的名称,clusterdomain 为 k8s 集群设置的域名后缀。服务名称的命名规则遵循 RFC 1123 规范。
在客户端访问 Service 的地址时,k8s 自动完成了将客户端请求转发到后端多个 Endpoint 的负载分发工作。
二、4中类型Service
Services 和 Pods
Kubernetes Pods是有生命周期的。他们可以被创建,而且销毁不会再启动。 如果您使用 Deployment来运行您的应用程序,则它可以动态创建和销毁 Pod。
一个Kubernetes的Service是一种抽象,它定义了一组 Pods
的逻辑集合和一个用于访问它们的策略 - 有的时候被称之为微服务。一个Service的目标Pod集合通常是由 Label Selector
来决定的(下面有讲一个没有选择器的Service 有什么用处)。
举个例子,想象一个处理图片的后端运行了三个副本。这些副本都是可以替代的 - 前端不关心它们使用的是哪一个后端。尽管实际组成后端集合的Pod可能会变化,前端的客户端却不需要知道这个变化,也不需要自己有一个列表来记录这些后端服务。Service抽象能让你达到这种解耦。
不像 Pod 的 IP 地址,它实际路由到一个固定的目的地,Service 的 IP 实际上不能通过单个主机来进行应答。 相反,我们使用 iptables(Linux 中的数据包处理逻辑)来定义一个虚拟IP地址(VIP),它可以根据需要透明地进行重定向。 当客户端连接到 VIP 时,它们的流量会自动地传输到一个合适的 Endpoint。 环境变量和 DNS,实际上会根据 Service 的 VIP 和端口来进行填充。
kube-proxy支持三种代理模式: 用户空间,iptables和IPVS;它们各自的操作略有不同。
Userspace
作为一个例子,考虑前面提到的图片处理应用程序。 当创建 backend Service 时,Kubernetes master 会给它指派一个虚拟 IP 地址,比如 10.0.0.1。 假设 Service 的端口是 1234,该 Service 会被集群中所有的 kube-proxy 实例观察到。 当代理看到一个新的 Service, 它会打开一个新的端口,建立一个从该 VIP 重定向到新端口的 iptables,并开始接收请求连接。
当一个客户端连接到一个 VIP,iptables 规则开始起作用,它会重定向该数据包到 Service代理 的端口。 Service代理 选择一个 backend,并将客户端的流量代理到 backend 上。
这意味着 Service 的所有者能够选择任何他们想使用的端口,而不存在冲突的风险。 客户端可以简单地连接到一个 IP 和端口,而不需要知道实际访问了哪些 Pod。
iptables
再次考虑前面提到的图片处理应用程序。 当创建 backend Service 时,Kubernetes 控制面板会给它指派一个虚拟 IP 地址,比如 10.0.0.1。 假设 Service 的端口是 1234,该 Service 会被集群中所有的 kube-proxy 实例观察到。 当代理看到一个新的 Service, 它会配置一系列的 iptables 规则,从 VIP 重定向到 per-Service 规则。 该 per-Service 规则连接到 per-Endpoint 规则,该 per-Endpoint 规则会重定向(目标 NAT)到 backend。
当一个客户端连接到一个 VIP,iptables 规则开始起作用。一个 backend 会被选择(或者根据会话亲和性,或者随机),数据包被重定向到这个 backend。 不像 userspace 代理,数据包从来不拷贝到用户空间,kube-proxy 不是必须为该 VIP 工作而运行,并且客户端 IP 是不可更改的。 当流量打到 Node 的端口上,或通过负载均衡器,会执行相同的基本流程,但是在那些案例中客户端 IP 是可以更改的。
IPVS
在大规模集群(例如10,000个服务)中,iptables 操作会显着降低速度。 IPVS 专为负载平衡而设计,并基于内核内哈希表。 因此,您可以通过基于 IPVS 的 kube-proxy 在大量服务中实现性能一致性。 同时,基于 IPVS 的 kube-proxy 具有更复杂的负载平衡算法(最小连接,局部性,加权,持久性)。
下面我们详细说下k8s支持的4种类型的Service。
ClusterIP
创建ClusterIP的Service yaml如下:
apiVersion: v1
kind: Service
metadata:
name: service-python
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 443
selector:
run: pod-python
type: ClusterIP
使用 kuebctl get svc
:
类型为ClusterIP的service,这个service有一个Cluster-IP,其实就一个VIP。具体实现原理依靠kubeproxy组件,通过iptables或是ipvs实现。
这种类型的service 只能在集群内访问。
NodePort
我们的场景不全是集群内访问,也需要集群外业务访问。那么ClusterIP就满足不了了。NodePort当然是其中的一种实现方案。
创建NodePort 类型service 如下:
apiVersion: v1
kind: Service
metadata:
name: service-python
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 443
nodePort: 30080
selector:
run: pod-python
type: NodePort
使用 kuebctl get svc
:
此时我们可以通过http://4.4.4.1:30080或http://4.4.4.2:30080 对pod-python访问。该端口有一定的范围,比如默认Kubernetes 控制平面将在--service-node-port-range标志指定的范围内分配端口(默认值:30000-32767)。
LoadBalancer
LoadBalancer类型的service 是可以实现集群外部访问服务的另外一种解决方案。不过并不是所有的k8s集群都会支持,大多是在公有云托管集群中会支持该类型
。负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过Service的status.loadBalancer
字段被发布出去。
创建 LoadBalancer service 的yaml 如下:
apiVersion: v1
kind: Service
metadata:
name: service-python
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 443
nodePort: 30080
selector:
run: pod-python
type: LoadBalancer
使用 kuebctl get svc
:
可以看到external-ip。我们就可以通过该ip来访问了。
当然各家公有云支持诸多的其他设置。大多是公有云负载均衡器的设置参数,都可以通过svc的注解来设置,例如下面的aws:
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: "true"
# Specifies whether access logs are enabled for the load balancer
service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: "60"
# The interval for publishing the access logs. You can specify an interval of either 5 or 60 (minutes).
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: "my-bucket"
# The name of the Amazon S3 bucket where the access logs are stored
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix: "my-bucket-prefix/prod"
# The logical hierarchy you created for your Amazon S3 bucket, for example `my-bucket-prefix/prod`
ExternalName
类型为 ExternalName
的service将服务映射到 DNS 名称,而不是典型的选择器,例如 my-service
或者 cassandra
。 您可以使用 spec.externalName
参数指定这些服务。
创建 ExternalName 类型的服务的 yaml 如下:
kind: Service
apiVersion: v1
metadata:
name: service-python
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 443
type: ExternalName
externalName: remote.server.url.com
说明:您需要 CoreDNS 1.7 或更高版本才能使用ExternalName类型。
当查找主机 service-python.default.svc.cluster.local时,集群DNS服务返回CNAME记录,其值为my.database.example.com。 访问service-python的方式与其他服务的方式相同,但主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发。
将生产工作负载迁移到Kubernetes集群并不容易。大多数我们不可以停止所有服务并在Kubernetes集群上启动它们。有时,尝试迁移轻量且不会破坏你服务的服务是很好的。在此过程中,一个可能不错的解决方案是使用现有的有状态服务(例如DB),并首先从无状态容器开始。
从Pod中访问外部服务的最简单正确的方法是创建ExternalName service。例如,如果您决定保留AWS RDS,但您还希望能够将MySQL容器用于测试环境。让我们看一下这个例子:
kind: Service
apiVersion: v1
metadata:
name: test-service
namespace: default
spec:
type: ExternalName
externalName: test.database.example.com
你已将Web应用程序配置为使用URL测试服务访问数据库,但是在生产集群上,数据库位于AWS RDS上,并且具有以下URL test.database.example.com。创建ExternalName service 并且你的Web Pod尝试访问test-service上的数据库之后,Kubernetes DNS服务器将返回值为test.database.example.com的CNAME记录。问题解决了。
ExternalName service 也可以用于从其他名称空间访问服务。例如:
kind: Service
apiVersion: v1
metadata:
name: test-service-1
namespace: namespace-a
spec:
type: ExternalName
externalName: test-service-2.namespace-b.svc.cluster.local
ports:
- port: 80
在这里,我可以使用名称空间a中定义的test-service-1访问命名空间b中的服务
test-service-2。
这个意义在哪里?
ExternalName service 也是一种service,那么ingress controller 会支持,那么就可以实现跨namespace的ingress。
相关文章:
k8s service官方文档
知乎|详解k8s 4种类型Service
Kubernetes之yaml文件详解(汇总-详细)
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)