当我们运行多个后端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操作,进而实现服务发现