共计 5028 个字符,预计需要花费 13 分钟才能阅读完成。
Services 和 Pods
KubernetesPods 是有生命周期的。他们可以被创建,而且销毁不会再启动。如果您使用 Deployment 来运行您的应用程序,则它可以动态创建和销毁 Pod。
一个 Kubernetes 的 Service
是一种抽象,它定义了一组 Pods
的逻辑集合和一个用于访问它们的策略 – 有的时候被称之为微服务。一个 Service
的目标 Pod
集合通常是由[Label Selector](https://link.zhihu.com/?target=http%3A//kubernetes.kansea.com/docs/user-guide/labels/%23label-selectors)
来决定的(下面有讲一个没有选择器的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](https://link.zhihu.com/?target=http%3A//4.4.4.1%3A30080/) 或[http://4.4.4.2:30080](https://link.zhihu.com/?target=http%3A//4.4.4.1%3A30080/)
对 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。
PS: 参考文章