当我们运行多个后端pod来接受客户端的请求时,客户端pod无需知道最终是哪个pod处理请求。而pod 部署在集群中是无状态的,可在集群中任意(不设节点策略时)节点重建,销毁,当pod变化时,我们需要集群能够动态感知,并处理ip的变化,使之能够正常处理客户端请求,service就是为解决这一问题抽象出来的资源。
service
使用标签将一组功能相同的pod和它(具有一个固定的ip)绑定,无视后端pod变化(变化就增加删除endpoint 还是会和此service绑定),请求会经由service根据负载均衡策略(四层负载)转发到具体的pod进行处理。
简单介绍service 类型:
1. ClusterIP 是默认模式,只能在集群内部访问clusterip:port
2. NodePort 基于ClusterIp,在每个节点上都监听一个同样的端口号(30000-32767),自动创建ClusterIP和路由规则。集群外部可以访问<NodeIP>:<NodePort>联系到集群内部服务
3. LoadBalancer 基于NodePort,要配合支持公有云负载均衡如GCE、AWS,把<NodeIP>: <NodePort>自动添加到公有云的负载均衡当中
4. ExternalName 创建一个dns别名指到service name上,主要是防止service name发生变化,要配合dns插件使用
ClusterIP < NodePort < LoadBalancer,后者可兼容前者的访问方式
本文仅就clusterIP为例。NodePort 会导致节点端口大量占用,一般配ingress暴露集群服务使用,关于ingres下一篇会详细介绍。注意,service仅支持四层负载均衡,即ip+port。
创建service
下面我们测试一下,用deployment先创建一个简单nginx服务:
➜ service cat nginx-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx-server # 这个 Deployment 管理着那些拥有这个标签的 Pod
template:
metadata:
labels:
app: nginx-server # 为所有 Pod 都打上这个标签
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
执行kubectl apply -f nginx-deployment.yaml,就成功运行两个nginx服务的pod
再创建一个service和它绑定:
➜ service cat nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
name: nginx-service
spec:
ports:
- port: 5000
targetPort: 80
selector: #此处selector实现和上面的pod绑定
app: nginx-server
执行kubectl apply -f nginx-service.yaml生效
此时我们查看创建的service:
➜ service kubectl describe services nginx-service
Name: nginx-service
Namespace: default
Labels: name=nginx-service
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"name":"nginx-service"},"name":"nginx-service","namespace":"def...
Selector: app=nginx-server
Type: ClusterIP
IP: 10.254.115.73 //集群内部虚拟ip
Port: <unset> 5000/TCP
TargetPort: 80/TCP
Endpoints: 10.213.180.132:80,10.213.180.184:80 //可直接访问
Session Affinity: None
Events: <none>
可见,此service获得了一个cluster ip 10.254.115.7 ,这是个虚拟ip(VIP),只能在集群内访问。(注意:我当前连接集群的开发机并不算集群内部),并绑定了两个pod的endpoint,10.213.180.132:80,10.213.180.184:80,此endpoint可直接访问,service会将请求负载给其中一个endpoint
负载策略:
RoundRobin:轮询模式,即轮询将请求转发到后端的各个pod上(默认模式);
SessionAffinity:基于客户端IP地址进行会话保持的模式,第一次客户端访问后端某个pod,之后的请求都转发到这个pod上
不同pod服务直接获取ip:port即是服务发现,
k8s服务发现具有两种方式:
1. 环境变量
需要先创建service,后创建pod,此时的pod 环境变量中才会存在这个service ip。为了明确这一概念,我们使用比service创建的更早的pod进行测试,你也可以直接运行一个简单pod 访问service
之前创建的pod:
test-erbao-huang-78997568f6-84r7g 1/1 Running 0 40d
上文中我们已经创建好了service ( 10.254.115.73:5000),我们继续使用
进入容器并查询env:
➜ k8s kubectl exec -it test-erbao-huang-78997568f6-nqzhl bash
root@test-erbao-huang-78997568f6-nqzhl:/# env |grep NGINX #发现什么也没有
NGINX_VERSION=1.15.2-1~stretch
并没有记录此服务ip,此时我们重启此pod,因为删除pod时候集群自动监测副本数目,有变化会自动重建pod,因此我们通过删除来达到重启的目的
kubectl delete pod test-erbao-huang-78997568f6-84r7g -n default
此时查看 -84r7g 已经被删除,取而代之的是-k58ms
test-erbao-huang-78997568f6-k58ms 1/1 Running 0 16m
我们再次进入这个pod的容器内部查询env:
kubectl exec -it test-erbao-huang-78997568f6-k58ms bash
# env |grep NGINX
NGINX_VERSION=1.15.2-1~stretch
NGINX_SERVICE_PORT_5000_TCP=tcp://10.254.115.73:5000
NGINX_SERVICE_PORT_5000_TCP_PORT=5000
NGINX_SERVICE_PORT_5000_TCP_PROTO=tcp
NGINX_SERVICE_SERVICE_HOST=10.254.115.73
NGINX_SERVICE_PORT_5000_TCP_ADDR=10.254.115.73
NGINX_SERVICE_PORT=tcp://10.254.115.73:5000
此时 service 的vip已经加载进来了 就可以访问了
/# curl 10.254.115.73:5000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
......
由此可见,我们新建了pod后才出现环境变量,使用环境变量进行服务的弊端就是你的service必须在pod之前创建,否则不能在pod内获取服务ip
2.dns
此方式不需要先后pod和service的先后顺序,使用dns进行服务发现需要查看集群是否具有kubedns 或者coredns组件运行,否则不能采用此方式,查看是否具有kubedns
➜ service kubectl get services kube-dns --namespace=kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.254.254.254 <none> 53/UDP,53/TCP 3y
还是在这个容器内,dns相关配置会在pod建立时写到/etc/resolv.conf中,我们访问search中的域名,记得加vip端口 :
/ # cat /etc/resolv.conf #是DNS客户机配置文件,用于设置DNS服务器的IP地址及DNS域名,还包含了主机的域名搜索顺序
nameserver 10.254.254.250 //DNS服务器ip
search default.svc.qihoo.local svc.qihoo.local qihoo.local //定义域名的搜索顺序列表,设定值加上需查询的名称依次尝试进行dns查找
/# curl nginx-service.default:5000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
......
实现了在集群内部通过 Service 的vip:port形式进行互相通信了,访问nginx-service.default.svc、nginx-service.default、nginx-service +port,也是可以通的,为什么都可以通呢???
若我们输入为nginx-service,实际会search 中查找并+default.svc.qihoo.local 变为 nginx-service.default.svc.qihoo.local,若我们查找输入为nginx-service.default则解析会自动+svc.qihoo.local变为nginx-service.default.svc.qihoo.local,所以这三个访问其实都会转化为一个dns记录进行查找。这就是/etc/resolv.conf文件的作用了,其中的search 会在输入无法直接访问时,则DNS会利用search的设定值加上需查询的名称,按search列表顺序依次尝试进行解析 具体官网dns访问规则可以解释为什么都会转化为最终的这一个域名查找
所以 我们直接访问nginx-service.default.svc.qihoo.local也是可以的,此时dns服务器可直接处理
root@curl:/# curl nginx-service.default.svc.qihoo.local:5000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
此方式进行服务发现不需要事先创建service,不需要加载环境变量到pod中,而是根据dns服务器记录进行查找iptables操作,进而实现服务发现