Service是Kubemetes最核心的概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,井且将请求负载分发到后端的各个容器应用上。
先来看看《kubernetes权威指南》中对Service类型的介绍:
service通过selector和pod建立关联。k8s会根据service关联到pod的podIP信息组合成一个endpoint。若service定义中没有selector字段,service被创建时,endpoint controller不会自动创建endpoint。
service 负载分发策略有两种:
Headless Service 适用场景:应用需要直接与后端的具体Pod进行通信(而不是通过一个代理或负载均衡器)。例如当您部署了有状态应用ClickHouse数据库服务,可以使用Headless Service,使得应用Pod可以直接访问每个ClickHouse Pod,并且均衡地读取数据或针对性地写入数据,提升数据处理的效率。
虽然Service解决了Pod的服务发现问题,但不提前知道Service的IP,怎么发现service服务呢?
k8s提供了两种方式进行服务发现:
● 环境变量: 当创建一个Pod的时候,kubelet会在该Pod中注入集群内所有Service的相关环境变量。需要注意的是,要想一个Pod中注入某个Service的环境变量,则必须Service要先比该Pod创建。这一点,几乎使得这种方式进行服务发现不可用。
● DNS:可以通过cluster add-on的方式轻松的创建KubeDNS来对集群内的Service进行服务发现————这也是k8s官方强烈推荐的方式。为了让Pod中的容器可以使用kube-dns来解析域名,k8s会修改容器的/etc/resolv.conf配置。
● endpoint
endpoint是k8s集群中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址。
service配置selector,endpoint controller才会自动创建对应的endpoint对象;否则,不会生成endpoint对象.
● endpoint controller
endpoint controller是k8s集群控制器的其中一个组件,其功能如下:
负责生成和维护所有endpoint对象的控制器
负责监听service和对应pod的变化
监听到service被删除,则删除和该service同名的endpoint对象
监听到新的service被创建,则根据新建service信息获取相关pod列表,然后创建对应endpoint对象
监听到service被更新,则根据更新后的service信息获取相关pod列表,然后更新对应endpoint对象
监听到pod事件,则更新对应的service的endpoint对象,将podIp记录到endpoint中
刚接触 k8s 涉及到端口到内容较多,容易混淆,这里整理如下:
nodePort 提供了集群外部客户端访问 Service 的一种方式,nodePort 提供了集群外部客户端访问 Service 的端口,通过 nodeIP:nodePort 提供了外部流量访问k8s集群中service的入口。
比如外部用户要访问k8s集群中的一个Web应用,那么我们可以配置对应service的type=NodePort,nodePort=30001。其他用户就可以通过浏览器http://node:30001访问到该web服务。
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
externalTrafficPolicy: Local或Cluster
internalTrafficPolicy: Cluster
type: NodePort // 配置NodePort,外部流量可访问k8s中的服务
ports:
- name: http
nodePort: 30001 // NodePort,外部客户端访问的端口
port: 30080 // 服务访问端口,集群内部访问的端口
protocol: TCP
targetPort: 80 // pod控制器中定义的端口(应用访问的端口)
selector:
name: nginx-pod
而数据库等服务可能不需要被外界访问,只需被内部服务访问即可,那么我们就不必设置service的NodePort。
关于 不同网络插件Flannel/Terway对配置 externalTrafficPolicy: Local或Cluster 的区别,请参考 help.aliyun.com
port是暴露在cluster ip上的端口,port提供了集群内部客户端访问service的入口,即clusterIP:port。
mysql容器暴露了3306端口(参考DockerFile),集群内其他容器通过33306端口访问mysql服务,但是外部流量不能访问mysql服务,因为mysql服务没有配置NodePort。对应的service.yaml如下:
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
ports:
- name: mysql
port: 3306
protocol: TCP
targetPort: 3306
selector:
name: mysql-pod
targetPort是pod上的端口,从port/nodePort上来的数据,经过kube-proxy流入到后端pod的targetPort上,最后进入容器。
与制作容器时暴露的端口一致(使用DockerFile中的EXPOSE),例如官方的nginx(参考DockerFile)暴露80端口。 对应的service.yaml如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort // 配置NodePort,外部流量可访问k8s中的服务
ports:
- name: http
nodePort: 30001 // NodePort,外部客户端访问的端口
port: 30080 // 服务访问端口,集群内部访问的端口
protocol: TCP
targetPort: 80 // pod控制器中定义的端口(应用访问的端口)
selector:
name: nginx-pod
containerPort是在pod控制器中定义的、pod中的容器需要暴露的端口。
例如,mysql 服务需要暴露 3306 端口,redis 暴露 6379 端口
apiVersion: v1
kind: ReplicationController
metadata:
name: redis-master
labels:
name: redis-master
spec:
replicas: 1
selector:
name: redis-master
template:
metadata:
labels:
name: redis-master
spec:
containers:
- name: master
image: kubeguide/redis-master
ports:
- containerPort: 6379 # 此处定义暴露的端口
无风 652 1 1 12 发布于 2021-03-06
面试中被问了这道题,其实是在考察你对 k8s service 的使用和底层原理的理解,答案显然没有那么简单。
使用 kind (k8s.io) 可以很方便的在本地搭建实验环境,修改配置即可创建 kubeProxyMode 为 iptables 或 ipvs。
实验涉及的 config 和 yaml 可以在我的 github repo 找到: win5do/k8s-svc-test (github.com)
网络调试工具可以通过创建 netshoot 容器 attach 到目标 network namespace:nicolaka/netshoot: a Docker + Kubernetes network trouble-shooting swiss-army container (github.com)
这类 svc 都会分配 ClusterIP,这个 IP 地址是 VIP(虚拟 IP),是在所有 node 上添加一些 netfilter 规则,主要有 iptables 和 ipvs 两种方案,能不能 ping 通要看具体实现。
示例配置:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
Headless svc 不会分配 clusterIP,而是返回对应 DNS 的 A 记录,如果 svc 后端有3个 pod 则返回 3 个 pod IP。访问 svc 时会随机选择一个 IP,所以 headless svc 是可以 ping 通的。
Loadbalancer 类型 svc 也要看云厂商的具体实现。
示例配置:
apiVersion: v1
kind: Service
metadata:
name: airflow
spec:
type: ExternalName
externalName: 10.84.14.156
ExternalName 对应 DNS 的 CNAME,如果配置的域名可以 ping 通则 svc 可以 ping 通。
前言: 最近在基于K8S开发平台的过程中遇到了有个问题没有弄懂,就是CoreDNS的作用,就好像在Docker Swarm里面,我们可以通过Service name来访问一组容器,在K8S里,我们想要通过name来访问服务的方式就是在Deployment上面添加一层Service,这样我们就可以通过Service name来访问服务了,那其中的原理就是和CoreDNS有关,它将Service name解析成Cluster IP,这样我们访问Cluster IP的时候就通过Cluster IP作负载均衡,把流量分布到各个POD上面。我想的问题是CoreDNS是否会直接解析POD的name,在Service的服务里,是不可以的,因为Service有Cluster IP,直接被CoreDNS解析了,那怎么才能让它解析POD呢,有大牛提出了可以使用Headless Service,所以我们就来探究一下什么是Headless Service。
Headless Service也是一种Service,但不同的是会定义spec:clusterIP: None,也就是不需要Cluster IP的Service。
我们首先想想Service的Cluster IP的工作原理:一个Service可能对应多个EndPoint(Pod),client访问的是Cluster IP,通过iptables规则转到Real Server,从而达到负载均衡的效果。具体操作如下所示:
kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service 10.107.124.218 192.168.128.158 80/TCP,443/TCP 1d
kubectl describe svc nginx-service
Name: nginx-service
Namespace: default
Labels:
Selector: component=nginx
Type: ClusterIP
IP: 10.107.124.218
External IPs: 192.168.128.158
Port: nginx-http 80/TCP
Endpoints: 10.244.2.9:80
Port: nginx-https 443/TCP
Endpoints: 10.244.2.9:443
Session Affinity: None
No events.
nslookup nginx-service.default.svc.cluster.local 10.96.0.10
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx-service.default.svc.cluster.local
Address: 10.107.124.218
从上面的结果中我们可以看到虽然Service有2个endpoint,但是dns查询时只会返回Service的地址。具体client访问的是哪个Real Server,是由iptables来决定的。
那么我们再来看看Headless Service的效果呢?
kubectl get service
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx None 80/TCP 1h
kubectl describe service nginx
Name: nginx
Namespace: default
Labels: app=nginx
Selector: app=nginx
Type: ClusterIP
IP: None
Port: web 80/TCP
Endpoints: 10.244.2.17:80,10.244.2.18:80
Session Affinity: None
No events.
nslookup nginx.default.svc.cluster.local 10.96.0.10
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx.default.svc.cluster.local
Address: 10.244.2.17
Name: nginx.default.svc.cluster.local
Address: 10.244.2.18
根据结果得知dns查询会如实的返回2个真实的endpoint,即精确到pod的ip。
所以,顾名思义,Headless Service就是没头的Service。有什么使用场景呢?
client想自己来决定使用哪个Real Server,可以通过查询DNS来获取Real Server的信息。Headless Services还有一个用处(PS:也就是我们需要的那个特性)。Headless Service的对应的每一个Endpoints,即每一个Pod,都会有对应的DNS域名;这样Pod之间就可以互相访问。我们还是看上面的这个例子。kubectl get statefulsets web
NAME DESIRED CURRENT AGE
web 2 2 1h
kubectl get pods
web-0 1/1 Running 0 1h
web-1 1/1 Running 0 1h
nslookup nginx.default.svc.cluster.local 10.96.0.10
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx.default.svc.cluster.local
Address: 10.244.2.17
Name: nginx.default.svc.cluster.local
Address: 10.244.2.18
nslookup web-1.nginx.default.svc.cluster.local 10.96.0.10
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-1.nginx.default.svc.cluster.local
Address: 10.244.2.18
nslookup web-0.nginx.default.svc.cluster.local 10.96.0.10
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-0.nginx.default.svc.cluster.local
Address: 10.244.2.17
如上,web为我们创建的StatefulSets,对应的pod的域名为web-0,web-1,他们之间可以互相访问,这样对于一些集群类型的应用就可以解决互相之间身份识别的问题了。
完整示例:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.11
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
nodeSelector:
node: kube-node3
volumes:
- name: www
hostPath:
path: /mydir
发布于 2019-01-06 02:07