Service 是 k8s 网络部分的核心概念,在 k8s 中,Service 主要担任了四层负载均衡的职责。本文从负载均衡、外网访问、DNS 服务的搭建及 Ingress 七层路由机制等方面,讲解 k8s 的网络相关原理。
Service 是主要用来实现应用程序对外提供服务的机制。
如上图所示,Service 是对 Pod 的一层抽象,主要通过 TCP/IP 机制及监听 IP 和端口号来对外提供服务。与 Pod 不同的是,Service 一旦创建,系统会为其分发一个 ClusterIP (也可以自己指定),且在其生命周期内不会发生变化。
在创建好 RC 后,可以通过命令行 kubectl expose
来快速创建一个对应的 Service 。比如现已有一个名为 hdls 的 rc:
kubectl expose rc hdls
这种方式创建出来的 Service,其 ClusterIP 是系统自动为其分配的,而 Service 的端口号是从 Pod 中的 containerPort 复制而来。
apiVersion: v1
kind: Service
metadata:
name: hdls
spec:
ports:
- port: 8080 # Service 的虚拟端口
targetPort: 8000 # 指定后端 Pod 的端口号
selector: # Label 选择器
app: hdls
定义好 YAML 文件后,通过命令 kubectl create-f<service.yml>
即可创建。Service 的定义需要指定以下几个关键字段:
k8s 提供了两种负载分发策略:
在默认情况下,k8s 采用轮询模式进行路由选择,但我们也可以通过将 service.spec.SessionAffinity 设置为 “ClusterIP” 来启用 SessionAffinity 模式。
开发人员需要自己控制负载均衡策略的情况
在这种情况下,k8s 通过 Headless Service 的概念来实现,即不给 Service 设置 ClusterIP (无入口 IP),仅通过 Label Selector 将后端的 Pod 列表返回给调用的客户端。
apiVersion: v1
kind: Service
metadata:
name: hdls
spec:
ports:
- port: 8080
targetPort: 8000
clusterIP: None
selector:
app: hdls
该 Service 没有虚拟的 ClusterIP ,对其访问可以获得所有具有 app=hdls
的 Pod 列表,客户端需要实现自己的负责均衡策略,再确定具体访问哪一个 Pod。
需要将某些服务作为后端服务
一般来说,应用系统需要将外部数据库作为后端服务进行连接,或另一个集群或 namespace 中的服务作为后端服务。这些情况,可以通过建立一个无 Label Selector 的 Service 来实现:
apiVersion: v1
kind: Service
metadata:
name: hdls
spec:
ports:
- port: 8080
targetPort: 8000
该 Service 没有标签选择器,即无法选择后端 Pod。这时系统不会自动创建 Endpoint,需要手动创建一个与该 Service 同名的 Endpoint,用于指向实际的后端访问地址。
apiVersion: v1
kind: Endpoints
metadata:
name: hdls # 与 Service 同名
subsets:
- addresss:
- IP: 1.2.3.4 # 用户指定的 IP
ports:
- port: 8000
此时,如上面的 YAML 创建出来 Endpoint,访问无 Label Selector 的 Service ,即可将请求路由到用户指定的 Endpoint 上。
多端口的 Service
在 service.spec.ports 中定义多个 port 即可,包括指定 port 的名字和协议。
apiVersion: v1
kind: Service
metadata:
name: hdls
spec:
ports:
- name: dns
port: 8080
protocol: TCP
- name: dns-tcp
port: 8080
protocol: UDP
selector:
app: hdls
Pod 和 Service 都是 k8s 集群内部的虚拟概念,所以集群外的客户无法访问。但在某些特殊条件下,我们需要外网可以访问 Pod 或 Service,这时我们需要将 Pod 或 Service 的端口号映射到宿主机,这样客户就可以通过物理机访问容器应用。
将容器应用的端口号映射到物理机上。有两种方式,如下。
这种是将容器应用的端口号映射到物理机。设置如下:
apiVersion: v1
kind: Pod
metadata:
name: hdls-pod
spec:
containers:
- name: hdls-container
image: ***
ports:
- containerPort: 8000
hostPort: 8000
这种是将该 Pod 中所有容器端口号都直接映射到物理机上。此时需要注意的是,在容器的 ports 定义部分,若不指定 hostPort,默认 hostPort=containerPort,若设置了 hostPort,则 hostPort 必须等于 containerPort。设置如下:
apiVersion: v1
kind: Pod
metadata:
name: hdls-pod
spec:
hostNetwork: true
containers:
- name: hdls-container
image: ***
ports:
- containerPort: 8000
对于外网访问 Service 也有两种方式。
首先需要设置 nodePort 映射到物理机,同时需要设置 Service 的类型为 NodePort:
apiVersion: v1
kind: Service
metadata:
name: hdls
spec:
type: NodePort # 指定类型为 NodePort
ports:
- port: 8080
targetPort: 8000
nodePort: 8000 # 指定 nodePort
selector:
app: hdls
这种用法仅用于在公有云服务提供商的云平台上设置 Service 的场景。需要将 service.status.loadBalancer.ingress.ip 设置为云服务商提供的负载均衡器的 IP。则对该 Service 的访问请求将会通过 LoadBalancer 转发到后端 Pod,且负载均衡的实现方式依赖于云服务商提供的 LoadBalancer 的实现机制。
为了能够实现通过服务名在集群内部进行服务的相互访问,需要创建一个虚拟的 DNS 服务来完成服务名到 ClusterIP 的解析。
k8s 提供的 DNS 服务名为 skydns,由下面四个组件组成:
skyDNS 服务由一个 RC 和一个 Service 组成。在 RC 的配置文件中,需要定义 etcd / kube2sky / skydns / healthz 四个容器,以保证 DNS 服务正常工作。需要注意的是:
--domain
设置为 k8s 集群中 Service 所属域名。容器启动后 kube2sky 会通过 API Server 监控集群中所有 service 的定义,生成相应的记录并保存到 etcd ;-addr=<IP:Port>
表示本机 TCP 和 UDP 的 Port 端口提供服务。在 DNS Service 的配置文件中,skyDNS 的 ClusterIP 需要我们指定,每个 Node 的 kubelet 都会使用这个 IP 地址,不会通过系统自动分配;另外,这个 IP 需要在 kube-apiserver 启动参数 --service-cluster-ip-range
内。
在 skydns 容器创建之前,需要先修改每个 Node 上 kubelet 的启动参数:
/etc/resolv.conf
中增加一条 nameserver 配置和 search 配置,通过 nameserver 访问的实际上就是 skydns 在对应端口上提供的 DNS 解析服务;Service 工作在 TCP/IP 层,而 Ingress 将不同的 URL 访问请求转发到后端不同的 Service ,实现 HTTP 层的业务路由机制。而在 k8s 中,需要结合 Ingress 和 Ingress Controller ,才能形成完整的 HTTP 负载均衡。
Ingress Controller 用来实现为所有后端 Service 提供一个统一的入口,需要实现基于不同 HTTP URL 向后转发的负载分发规则。Ingress Controller 以 Pod 的形式运行,需要实现的逻辑:
/etc/nginx/nginx.conf
;nginx-s reload
,重新加载 nginx.conf 配置文件的内容。k8s 中有一种单独的名为 Ingress 的资源,在其配置文件中可以设置到后端 Service 的转发规则。比如,为 hdls.me 定义一个 ingress.yml:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: hdls-ingress
spec:
rules:
- host: hdls.me
http:
paths:
- path: /web
backend:
serviceName: hdls
servicePort: 8000
最后采用 kubectl create-f ingress.yml
创建 Ingress。可以登录 nginx-ingress Pod 查看其自动生成的 nginx.conf 配置文件内容。