Kubernetes Service 从逻辑上定义了运行在集群中的一组 Pod,这些 Pod 提供了相同的功能。当每个 Service 创建时,会被分配一个唯一的 IP 地址(也称为 clusterIP,virtual IP)。Service 工作在 TCP 四层。
介绍
- Service 的名称解析强依赖于 CoreDNS(之前称 kube-dns)
- 工作结构
api-server <--watch--> kube-proxy <----> iptables/ipvs
Service <---> Endpoints <---> Pods
kube-proxy 的工作模式:
- userspace v1.1 之前,使用 kube-proxy 代理流量,效率低,已废弃
- iptables v1.10 之前
- ipvs v1.11 之后
- IPVS 专为负载平衡而设计,并基于内核内哈希表
- 基于 IPVS 的 kube-proxy 具有更复杂的负载均衡算法(最小连接、局部性、 加权、持久性)
- 相关命令
- apt install ipvsadm
- ipvsadm -ln
Service 示例
查看帮助 kubectl explain service/svc
下面列举 Service 和 Deployment 为本文的示例基础:
- deployment-hello-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-app-dp
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: hello-app
release: canary
template:
metadata:
name: hello-app-pod
labels:
app: hello-app
release: canary
spec:
containers:
- name: hello-app-1
image: gcriogooglesamples/hello-app:1.0
ports:
- name: http
containerPort: 8080
- deployment-hello-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-app-dp
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: hello-app
release: canary
template:
metadata:
name: hello-app-pod
labels:
app: hello-app
release: canary
spec:
containers:
- name: hello-app-1
image: gcriogooglesamples/hello-app:1.0
ports:
- name: http
containerPort: 8080
$ kubectl run t1 --image=xiexianbin/alpine:1 -it --restart=Never -- /bin/sh
Service Type
- ClusterIP(Default)
- NodePort
- LoadBalancer
- ExternalName
ClusterIP
通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。默认的 ServiceType
- 使用
iptables/ipvs
实现的 virtual IP
- No ClusterIP: Headless Service
- DNS: ServiceName -> PodIP
NodePort
通过每个节点上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到自动创建的 ClusterIP 服务。通过请求 <节点 IP>:<节点端口>
,可以从集群的外部访问一个 NodePort 服务
- 节点IP:kube-proxy 中的
--nodeport-addresses
参数或者将 kube-proxy
配置文件中的等效 nodePortAddresses=10.0.0.0/8,192.0.2.0/25
字段设置为特定的 IP 块
- 节点端口:Kubernetes 控制平面将在
--service-node-port-range
标志指定范围内分配端口(默认值:30000-32767
)
代理结构如下:
client -> NodeIP:NodePort -> ClusterIP:ServerPort -> PodIP:containerPort
NodePort 具有如下特点:
示例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app: MyApp
ports:
- port: 80
targetPort: 80
nodePort: 30080
- targetPort:容器接收流量的端口
- port:抽象的 Service 端口,可以使任何其它 Pod 访问该 Service 的端口
若需要自定义,在 /etc/kubernetes/manifests/kube-apiserver.yaml
中添加:
...
spec:
containers:
- command:
- kube-apiserver
- --service-node-port-range=2500-32767
...
参考:How to change Kubernetes service’s node port range?
LoadBalancer
一般有云服务商提供,外部负载均衡器将流量路由到 Node Pod(通过 NodePort 或 ClusterIP)。Kubernetes 调用 IaaS 上的 LBaaS 服务,代理结构如下:
IaaS LBaaS <--> LB's Node Pod <--> Service <--> Pod
ExternalName
Service 代理外部的服务,供内部的其他服务使用,一般为 FQDN (CNAME 方式实现)
服务发现
Kubernetes 支持两种服务发现模式:
环境变量服务发现
hello-app-svc
的 Service 暴露了 TCP 端口 80, 同时给它分配了 Cluster IP 地址 10.100.108.93,Pod 中可以看到的环境变量如下:
HELLO_APP_SVC_PORT_80_TCP=tcp://10.100.108.93:80
HELLO_APP_SVC_SERVICE_HOST=10.100.108.93
HELLO_APP_SVC_SERVICE_PORT_PORT_80=80
HELLO_APP_SVC_SERVICE_PORT=80
HELLO_APP_SVC_PORT=tcp://10.100.108.93:80
HELLO_APP_SVC_PORT_80_TCP_ADDR=10.100.108.93
HELLO_APP_SVC_PORT_80_TCP_PORT=80
HELLO_APP_SVC_PORT_80_TCP_PROTO=tcp
DNS 服务发现
A/AAAA 记录查询
格式为:
[SVC_NAME].[NS_NAME].svc.DOMAIN.LTD
其中 svc.DOMAIN.LTD
一般为 svc.cluster.local
,本示例为 svc.kb.cx
。
本示例中 Kubernetes 命名空间 default
中有一个名为 hello-app-svc
的服务,则控制平面和 DNS 服务共同为 hello-app-svc.default
/ # nslookup hello-app-svc
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: hello-app-svc.default.svc.kb.cx
Address: 10.100.108.93
/ # nslookup hello-app-svc.default
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: hello-app-svc.default.svc.kb.cx
Address: 10.100.108.93
/ # nslookup hello-app-svc.default.svc.kb.cx
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: hello-app-svc.default.svc.kb.cx
Address: 10.100.108.93
/ # dig -t A hello-app-svc.default.svc.kb.cx @10.96.0.10
; <<>> DiG 9.16.27 <<>> -t A hello-app-svc.default.svc.kb.cx @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40645
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 47e66e4565215444 (echoed)
;; QUESTION SECTION:
;hello-app-svc.default.svc.kb.cx. IN A
;; ANSWER SECTION:
hello-app-svc.default.svc.kb.cx. 30 IN A 10.100.108.93
;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sat Mar 19 06:17:08 UTC 2022
;; MSG SIZE rcvd: 119
可以看到,通过 A 记录执行 Server 的 Cluster IP。因此,由于 DNS A 记录的存在,因此我们可以通过 curl 命令通过 svc 访问服务,在 t1 容器中,执行如下命令:
/ # curl http://hello-app-svc.default.svc.kb.cx
Hello, world!
Version: 2.0.0
Hostname: hello-app-dp-665877bb77-bzbcp
A/AAAA 查询无头 Service
无头(Headless)服务
(即没有 ClusterIP)也会可以通过 DNS A 或 AAAA 记录实现服务发现,格式与有头服务相同,示例:
- service-hello-app-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: hello-app-svc-hl
namespace: default
spec:
selector:
app: hello-app
release: canary
clusterIP: None
ports:
- name: port-80
port: 80
targetPort: 8080
protocol: TCP
查看 svc CLUSTER-IP 为 None:
root@k8s-master:~/manifests# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-app-svc-hl ClusterIP None <none> 80/TCP 4s
在 t1 中查询 A 记录:
/ # dig -t A hello-app-svc-hl.default.svc.kb.cx @10.96.0.10
; <<>> DiG 9.16.1-Ubuntu <<>> -t A hello-app-svc.default.svc.kb.cx @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19031
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 4783e3724a1159ed (echoed)
;; QUESTION SECTION:
;hello-app-svc.default.svc.kb.cx. IN A
;; ANSWER SECTION:
hello-app-svc.default.svc.kb.cx. 30 IN A 10.244.1.59
hello-app-svc.default.svc.kb.cx. 30 IN A 10.244.1.58
;; Query time: 8 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sat Mar 19 12:12:29 CST 2022
;; MSG SIZE rcvd: 166
发现 A 记录不是解析到 Service 的 ClusterIP,而是直接解析到 Pod 的 IP 地址,实现 DNS 级别的负载均衡。
SRV 记录查询
格式为:
[_MY_PORT_NAME].[_MY_PORT_PROTOCOL].[SVC_NAME].[NS_NAME].svc.cluster.local
示例中,default
命名空间中 hello-app-svc
服务的 DNS SRV(服务)记录信息如下:
- MY_PORT_NAME: port-80
- MY_PORT_PROTOCOL: tcp
因此,Service hello-app-svc
的 SRV 记录为 _port-80._tcp.hello-app-svc.default.svc.kb.cx
,在 t1 中执行 DNS SRV 查询如下:
/ # nslookup
> set type=srv
> _port-80._tcp.hello-app-svc.default.svc.kb.cx
Server: 10.96.0.10
Address: 10.96.0.10#53
_port-80._tcp.hello-app-svc.default.svc.kb.cx service = 0 100 80 hello-app-svc.default.svc.kb.cx.
Pods 记录查询
[_POD-IP].[SVC_NAME].[NS_NAME].pod.cluster.local
本示例中,IP 地址为 10.244.1.63
的 Pod,DNS 名称为 10-244-1-63.default.pod.kb.cx
/ # dig -t A 10-244-1-63.default.pod.kb.cx @10.96.0.10 +short
10.244.1.63
[_POD-IP].[SVC_NAME].[NS_NAME].svc.cluster.local
本示例中,IP 地址为 10.244.1.63
的 Pod,DNS 名称为 10-244-1-63.hello-app-svc.default.svc.kb.cx
/ # dig -t A 10-244-1-63.hello-app-svc.default.svc.kb.cx +short
10.244.1.63
Pod 规约中包含一个可选的 hostname
字段,可以用来指定 Pod 的主机名。当这个字段被设置时,它将优先于 Pod 的名字成为该 Pod 的主机名。
Pod 规约还有一个可选的 subdomain
字段,可以用来指定 Pod 的子域名。
hostname-subdomain-hello-app.yaml
创建无头服务
---
apiVersion: v1
kind: Service
metadata:
name: hello-app-subdomain
spec:
selector:
name: hello-app
clusterIP: None
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: v1
kind: Pod
metadata:
name: hello-app-v1
labels:
name: hello-app
spec:
hostname: hello-app-v1
subdomain: hello-app-subdomain
setHostnameAsFQDN: true
containers:
- image: gcriogooglesamples/hello-app:1.0
name: hello-app
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Pod
metadata:
name: hello-app-v2
labels:
name: hello-app
spec:
hostname: hello-app-v2
subdomain: hello-app-subdomain
setHostnameAsFQDN: true
containers:
- image: gcriogooglesamples/hello-app:2.0
name: hello-app
ports:
- name: http
containerPort: 8080
注意:
- 只有
pod.spec.setHostnameAsFQDN: true
下面的测试才生效
- Service 的 name 和 Pod 的 subdomain 是相同的
进入容器中查看 pod 的 DNS:
$ kubectl exec -it hello-app-v1 -- sh
/ # hostname -d
hello-app-subdomain.default.svc.kb.cx
/ # hostname -f
hello-app-v1.hello-app-subdomain.default.svc.kb.cx
其他错误系统,使用如下命令:
hostname --fqdn
通过 hello-app-subdomain.default.svc.kb.cx
查看无头服务:
/ # dig hello-app-subdomain.default.svc.kb.cx +short
10.244.1.79
10.244.1.78
/ # dig hello-app-v1.hello-app-subdomain.default.svc.kb.cx +short
10.244.1.79
/ # dig hello-app-v2.hello-app-subdomain.default.svc.kb.cx +short
10.244.1.78
说明:通过 hostname 成功为无头服务配置域名。