Kubernetes系列05:深入掌握Service

原文链接:https://blog.csdn.net/levy_cui/article/details/70336283

Service是kubernetes最核心的概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求进行负载分发到后端的各个容器应用上。

本节对Service的使用进行说明,包括Service的负载均衡、外网访问、DNS服务的搭建、Ingress7层路由机制等


1.Service定义详解
yaml格式的Service定义文件的完整内容:
apiVersion: v1
kind: Service
matadata:
  name: string
  namespace: string
  labels:
    - name: string
  annotations:
    - name: string
spec:
  selector: []
  type: string
  clusterIP: string
  sessionAffinity: string
  ports:
  - name: string
    protocol: string
    port: int
    targetPort: int
    nodePort: int
  status:
    loadBalancer:
      ingress:
        ip: string
        hostname: string

对外提供服务的应用程序需要通过某种机制来实现,对于容器应用最简单的方式就是通过TCP/IP机制及监听IP和端口来实现。例如,我们定义一个提供web服务的RC,由两个tomcat容器副本组成,每个容器通过containerPort设置提供服务号为8080
文件webapp-rc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
  name: webapp
spec:
  replicas: 2
  template:
    metadata:
      name: webapp
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: tomcat
        ports:
        - containerPort: 80
创建RC:
#kubectl create -f webapp-rc.yaml
获取Pod的IP地址:
#kubectl get pods -l app=webapp -o yaml|grep podIP
 podIP:172.17.1.4
 podIP:172.17.1.5
访问这两个Pod提供的Tomcat服务
#curl 172.17.1.4:8080

直接通过Pod的IP地址和端口号访问容器应用是不可靠的,例如Podu所在的Node发生故障,Pod将被重新调度到另一台Node进行启动,Pod的IP地址将发生变化,更重要的是,如果容器应用本身是分布式的部署方式,通过多个实例共同提供服务,就需要在这些实例的前端设置一个负载均衡器来实现请求的分发。kubernetes中的Service就是设计出来用于解决这些问题的核心组件。

为了让客户端应用能够访问到两个Tomcat Pod 实例,需要创建一个Service来提供服务,通过kubectl expose命令来创建:
#kubectl expose rc webapp
查看新创建的Service可以看到系统为它分配了一个虚拟的IP地址(clusterIP),而Service所需的端口号则从Pod中的containerPort复制而来:
#kubectl get svc
NAME |Cluster-IP |External-IP |Port(s) |AGE
webapp |169.169.235.79 |<none> |8080/TCP |3s
接下来,我们就可以通过Service的IP地址和Service的端口号访问该Service了:
#curl 169.169.235.79:8080
这里,对Service地址169.169.235.79:8080的访问被自动负载分发到了后端两个Pod之一
除了使用kubectl expose命令创建Service,我们也可以通过配置文件定义Service,再通过kubectl 

create命令进行创建。我们可以设置一个Service:
文件webapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  ports:
  - port: 8081
    targetPort: 8080
  selector:
    app: webapp
Service定义中的关键字段是ports和selector。本例ports定义部分指定了Service所需的虚拟端口号为8081,由于与Pod容器端口号8080不一样,所以需要在通过targetPort来指定后端Pod的端口。

selector定义部分设置的是后端Pod所拥有的label: app=webapp
创建该Service并查看器ClusterIP地址:
#kubectl create -f webapp-svc.yaml
#kubectl get svc
#curl 169.169.28.190:8081

目前kubernetes提供了两种负载分发策略:RoundRobin和SessionAffinity
RoundRobin:轮询模式,即轮询将请求转发到后端的各个Pod上
SessionAffinity:基于客户端IP地址进行会话保持的模式,第一次客户端访问后端某个Pod,之后的请求都转发到这个Pod上
默认是RoundRobin模式

在某些场景中,开发人员希望自己控制负载均衡的策略,不使用Service提供的默认负载,kubernetes通过Headless Service的概念来实现。不给Service设置ClusterIP(无入口IP地址)
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
  clusterIP: None
  selector:
    app: nginx

有时候,一个容器应用提供多个端口服务:
apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  ports:
  - port: 8080
    targetPort: 8080
    name: web
  - port: 8005
    targetPort: 8005
    name: management
  selector:
    app: webapp
另一个例子是两个端口使用了不同的4层协议,即TCp或UDP
apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "KubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 169.169.0.100
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP

3. 集群外部访问Pod或Service
为了让外部客户端可以访问这些服务,可以将Pod或者Service的端口号映射到宿主主机,使得客户端应用能够通过物理机访问容器应用。

1)将容器应用的端口号映射到物理机
(1)通过设置容器级别的hostPort,将容器应用的端口号映射到物理机上:
文件pod-hostport.yaml
apiVersion: v1
kind: Pod
metadata:
  name: webapp
  labels:
    app: webapp
spec:
  containers:
  - name: webapp
    image: tomcat
    ports:
    - containerPort: 8080
      hostPort:8081

通过kubectl create创建这个Pod:
#kubectl create -f pod-hostport.yaml
通过物理机的IP地址和8081端口号访问Pod内的容器服务:
#curl 192.168.18.3:8081

(2)通过设置Pod级别的hostNetwork=true,该Pod中所有容器的端口号都将被直接映射到物理机上,设置hostWork=true是需要注意,在容器的ports定义部分如果不指定hostPort,则默认hostPort等于containerPort,如果指定了hostPort,则hostPort必须等于containerPort的值。
文件pod-hostnetwork.yaml
apiVersion: v1
kind: Pod
metadata:
  name: webapp
  labels:
    app: webapp
spec:
  hostNetwork: true
  containers:
  - name: webapp
    image: tomcat
    imagePullPolicy: Never
    ports:
    - containerPort: 8080
创建这个Pod:
#kubectl create -f pod-hostnetwork.yaml
通过物理机的IP地址和8080端口访问Pod的容器服务
#curl 192.168.18.4:8080

2)将Service的端口号映射到物理机
(1)通过设置nodePort映射到物理机,同时设置Service的类型为NodePort:
apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 8080
    nodePort: 8081
  selector:
    app: webapp

创建这个Service:
#kubectl create -f webapp-svc-nodeport.yaml
通过物理机的IP和端口访问:
#curl 192.168.18.3:8081
如果访问不通,查看下物理机的防火墙设置
同样,对该Service的访问也将被负载分发到后端多个Pod上

(2)通过设置LoadBalancer映射到云服务商提供的LoadBalancer地址。这种用法仅用于在公有云服务提供商的云平台上设置Service的场景。status.loadBalancer.ingress.ip设置的146.148.47.155为云服务商提供的负载均衡器的IP地址。对该Service的访问请求将会通过LoadBalancer转发到后端的Pod上,负载分发的实现方式依赖云服务商提供的LoadBalancer的实现机制。
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: Myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
    nodePort: 30061
  clusterIP: 10.0.171.239
  loadBalancerIP: 78.11.24.19
  type: LoadBalancer
status:
  loadBalancer:
    ingree:
    - ip: 146.148.47.155

4. DNS服务搭建指南
为了能够通过服务的名字在集群内部进行服务的相互访问,需要创建一个虚拟的DNS服务来完成服务名到ClusterIP的解析。

kubernetes提供的虚拟DNS服务名为skydns,由四个组件组成。
1)etcd:NDS存储
2)kube2sky:将kubernetes Master中的Service(服务)注册到etcd
3)skyDNS:提供NDS域名解析服务
4)healthz:提供对skydns服务的健康检查功能


1)skydns配置文件说明
skydns服务由一个RC和一个Service的定义组成,分别由配置文件skydns-rc.yaml和skydns-svc.yaml定义skydns的RC配置文件skydns-rc.yaml的内容如下,包含4个容器的定义:
文件skydns-rc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
  name: kube-dns-v11
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    version: v11
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 1
  selector:
    k8s-app: kube-dns
    version: v11
  template:
    metadata:
      labels:
        k8s-app: kube-dns
        version: v11
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: etcd
        image: gcr.io/google_containers/etcd-amd64:2.2.1
        resources:
          limits:
            cpu: 100m
            memory: 50Mi
          requests:
            cpu: 100m
            memory: 50Mi
        command:
        - /usr/local/bin/etcd
        - -data-dir
        - /tmp/data
        - -listten-client-urls
        - http://127.0.0.1:2379,http://127.0.0.1:4001
        - -advertise-client-urls
        - http://127.0.0.1:2379,http://127.0.0.1:4001
        - -initial-cluster-token
        - skydns-etcd
        volumeMounts:
        - name: etcd-storage
          mountPath: /tmp/data
      - name: kube2sky
        image: gcr.io/google_containers/kube2sky-amd64:1.15
        resources:
          limits:
            cpu: 100m
            memory: 50Mi
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 5
        readlinessProbe:
          httpGet:
            path: /readiness
            port: 8081
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        args:
        - --kube-master-url=http://192.168.18.3:8080
        - --domain=cluster.local
      - name: skydns
        image: gcr.io/google_containers/skydns:2015-10-13-8c72f8c
        resources:
          limits:
            cpu: 100m
            memory: 50Mi
          requests:
            cpu: 100m
            memory: 50Mi
        args:
        - -machines=http://127.0.0.1:4001
        - -addr=0.0.0.0:53
        - -ns-rostate=false
        - -domain=cluster.local
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        - name: healthz
          image:gcr.io/google_containers/exechealthz:1.0
          resources:
            limits:
              cpu: 10m
              memory: 20Mi
            requests:
              cpu: 10m
              memory: 20Mi
         args:
         - -cmd=nslookup kubernetes.default.svc.cluster.local 127.0.0.1 > /dev/null
         - -port=8080
         ports:
         - containerPort: 8080
           protocol: TCP
       volumes:
       - name: etcd-storage
         emptyDir: {}
       dnsPolicy: Default  #Don't use cluster DNS.


skydns的Service配置文件skydns-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service:"true"
    kubernetes.io/name: "kubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 169.169.0.100
  ports:
  - name: dns
    prot: 53
    protocol: UDP
  - name:dns-tcp
    port: 53
    protocol: TCP
注意:skydns服务使用的clusterIP需要指定一个固定IP,每个Node的kubelet进程都将使用这个IP地址,不能通过kubernetes自动分配。

另外,这个IP地址需要在kube-apiserver启动参数--service-cluster-ip-range指定IP地址范围在创建skydns容器之前,先修改每个Node上kubelet启动参数。

2)修改每台Node上的kubelet启动参数
加上一下两个参数:
--cluster_dns=169.169.0.100 为NDS服务的ClusterIP地址
--cluster_dns=cluster.local 为DNS服务中设置的域名

然后重启kubelet服务

3)创建skydns RC和Service
#kubectl create -f skydns-rc.yaml
#kubectl create -f skydns-svc.yaml
查看RC、Pod和Service,确保容器启动
#kubectl get rc --namespace=kube-system

#kubectl get pods --namespace=kube-system

#kubectl get service --namespace=kube-system

然后我们为redis-master应用创建一个Service:
文件redis-master-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    name: redis-master

到此,集群虚拟DNS服务搭建完成,在需要访问redis-master的应用中,仅需要配置上redis-master Service的名字和服务的端口号,就能可以访问,如redis-master:6379

4)通过DNS查找Service
使用一个带有nslookup工具的Pod来验证DNS服务:
文件busybox.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - name: busybox
    image: gcr.io/google_containers/busybox
    conmmand:
      - sleep
      - "3600"
运行kubectl create -f busybox.yaml
测试nslookup
#kubectl exec busybox -- nslookup redis-master

如果某个Service属于不同的命名空间,需要带上namespace名字
#kubectl exec busybox -- nslookup kube-dns.kube-system

5)DNS服务的工作原理解析
(1)kube2sky容器应用通过调用kubernetes Master的API获得集群中所有Service的信息,并持续监控新Service的生成,然后写入etcd中。

查看etcd中存储的Service信息:
#kubectl exec kube-dns-v8-5tpm2 -c etcd --namespace=kube-system etcdctl ls 

/skydns/local/cluster
(2)根据kubelet启动参数的设置(--cluster_dns),kubelet会在每个新创建的Pod中设置DNS域名

解析配置文件/etc/resolv.conf文件,在其中增加了一条nameserver配置和一条search配置:
nameserver 169.169.0.100
通过服务器169.169.0.100访问的实际就是skydns在53端口上提供的DNS解析服务。

(3)最后应用程序就能够像访问网站域名一样,仅仅通过服务的名字就能够访问到服务了。

5. Ingress:HTT前面P 7层路由机制

根据前面对Service的使用说明,我们知道Service的表现形式为IP:Port,即工作在TCP/IP层,而对于基于HTTP的服务来说,不同的URL地址经常对应到不同的后端服务或者虚拟服务器,这些应用层的转发机制仅通过kubernetes的Service机制是无法实现的。kubernetes V1.1版本中新增的Ingress将不同URL的访问请求转发到后端不同的Service,实现HTTP层的业务路由机制。在kubernetes集群中,Ingress的实现需要通过Ingress的定义与Ingress Controller的定义结合起来,才能形成完整的HTTP负载分发功能。

1)创建Ingress Controller
使用Nginx来实现一个Ingress Controller,需要实现的基本逻辑如下:
a)监听apiserver,获取全部ingress的定义
b)基于ingress的定义,生成Nginx所需的配置文件/etc/nginx/nginx.conf
c)执行nginx -s relaod命令,重新加载nginx.conf文件,写个脚本。

通过直接下载谷歌提供的nginx-ingress镜像来创建Ingress Controller:
文件nginx-ingress-rc.yaml
apiVersion: v1
kind: ReplicationController
matadata:
  name: nginx-ingress
  labels:
    app: nginx-ingress
spec:
  replicas: 1
  selector:
    app: nginx-ingress
  template:
    metadata:
      labels:
        app: nginx-ingress
    spec:
      containers:
      - image: gcr.io/google_containers/nginx-ingress:0.1
        name: nginx
        ports:
        - containerPort: 80
          hostPort: 80
这里,Nginx应用配置设置了hostPort,即它将容器应用监听的80端口号映射到物理机,以使得客户端应用可以通过URL地址“http://物理机IP:80”来访问该Ingress Controller
#kubectl create -f nginx-ingress-rc.yaml
#kubectl get pods

2)定义Ingress
为mywebsite.com定义Ingress,设置到后端Service的转发规则:
apiVersion: extensions/vlbeta1
kind: Ingress
metadata:
  name: mywebsite-ingress
spec:
  rules:
  - host: mywebsite.com
    http:
      paths:
      - path: /web
        backend:
          serviceName: webapp
          servicePort: 80

这个Ingress的定义说明对目标http://mywebsite.com/web的访问将被转发到kubernetes的一个Service上 webapp:80

创建该Ingress
#kubectl create -f Ingress.yaml

#kubectl get ingress
NAME |Hosts |Address |Ports |Age
mywebsite-ingress |mywebsite.com |80 |17s

创建后登陆nginx-ingress Pod,查看自动生成的nginx.conf内容

3)访问http://mywebsite.com/web
我们可以通过其他的物理机对其进行访问。通过curl --resolve进行指定
#curl --resolve mywebsite.com:80:192.169.18.3 mywebsite.com/web

猜你喜欢

转载自blog.csdn.net/zhaipengfei1231/article/details/81413087