跳到主要内容

06、Kubernetes - 实战:Service

一、Kubernetes Service

Service的介绍

Service可以看作是一组提供相同服务的Pod对外的访问接口。借助Service,应用可以方便地实现服务发现和负载均衡。

service默认只支持4层负载均衡能力,没有7层功能。(可以通过Ingress实现)

ClusterIP:默认值,k8s系统给service自动分配的虚拟IP,只能在集群内部访问。

NodePort:将Service通过指定的Node上的端口暴露给外部,访问任意一个NodeIP:nodePort都将路由到ClusterIP。

LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部的负载均衡器,并将请求转发到 :NodePort,此模式只能在云服务器上使用。

ExternalName:将服务通过 DNS CNAME 记录方式转发到指定的域名(通过 spec.externlName 设定)。

service的优化

Service 是由 kube-proxy 组件,加上 iptables 来共同实现的.

kube-proxy 通过 iptables 处理 Service 的过程,需要在宿主机上设置相当多的 iptables 规则,如果宿主机有大量的Pod,不断刷新iptables规则,会消耗大量的CPU资源。

IPVS模式的service,可以使K8s集群支持更多量级的Pod。

开启kube-proxy的ipvs模式:

# yum install -y ipvsadm 			//所有节点安装
kubectl edit cm kube-proxy -n kube-system	//修改IPVS模式
    修改: 43     mode: "ipvs"

由于有些pod已经在运行,因此需要更新kube-proxy pod:

kubectl get pod -n kube-system |grep kube-proxy | awk '{system("kubectl delete pod "$1" -n kube-system")}'		//更新kube-proxy pod```

IPVS模式下,kube-proxy会在service创建后,在宿主机上添加一个虚拟网卡:kube-ipvs0,并分配service IP。kube-proxy通过linux的IPVS模块,以rr轮询方式调度service中的Pod。
```java 
ipvsadm -ln

 

二、Kubernetes Service实列

ClusterIP service

在集群内部菜才能访问
首先创建pod:

[root@server1 ~]# vim deployment.yaml 
[root@server1 ~]# cat deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-nginx
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: ikubernetes/myapp:v1
        ports:
        - containerPort: 80

[root@server1 ~]# kubectl apply -f deployment.yaml 
deployment.apps/deployment-nginx created
[root@server1 ~]# kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP            NODE      NOMINATED NODE   READINESS GATES
deployment-nginx-56d786cd98-fcmh9   1/1     Running   0          55s   10.244.2.34   server3   <none>           <none>
deployment-nginx-56d786cd98-pkvpg   1/1     Running   0          55s   10.244.1.32   server2   <none>           <none>

现在可以在集群内部访问这两个pod:

[root@server1 ~]# kubectl run test -it --image=radial/busyboxplus
If you don't see a command prompt, try pressing enter.
/ curl 10.244.2.34
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
/ curl 10.244.1.32
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

创建service:(ClusterIP方式):

[root@server1 ~]# vim service.yaml
[root@server1 ~]# cat service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  selector:					#service与pod关联时使用的标签,此处设置的标签要和需要关联的pod标签相同
      app: nginx
  type: ClusterIP

[root@server1 ~]# kubectl apply -f service.yaml 
service/myservice created

service通过标签与pod关联,查看service状态:

[root@server1 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1     <none>        443/TCP   4d2h
myservice    ClusterIP   10.97.61.44   <none>        80/TCP    6s

可以看出service暴露出来的ip为10.97.61.44,只有在集群内部才可以通过这个ip访问pod:

[root@server1 ~]# kubectl attach test -it 
If you don't see a command prompt, try pressing enter.
/ curl 10.97.61.44
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

NodePort service

使用NodePort方式可以使我们在集群外访问集群内的pod

在上个实验时我们已经创建了一个ClusterIP方式的service,我们可以直接更改其方式:

[root@server1 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   4d3h
myservice    ClusterIP   10.111.87.107   <none>        80/TCP    84m
[root@server1 ~]# kubectl edit svc myservice 

 
再查看service状态:

[root@server1 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        4d3h
myservice    NodePort    10.111.87.107   <none>        80:31549/TCP   84m

 
可以发现已经变成NodePort方式了,同时也可以看出端口为31549,再集群外部主机访问pod:

[root@foundation63 Desktop]# curl 172.25.63.3:31549
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
[root@foundation63 Desktop]# curl 172.25.63.2:31549
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

访问两个节点的31549端口均可以访问到pod。
实验后删除:

[root@server1 ~]# kubectl delete -f service.yaml 
service "myservice" deleted

同时也可以在创建service的时候就指定为NodePort的方式。

[root@server1 ~]# vim service.yaml 
[root@server1 ~]# cat service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  selector:
      app: nginx
  type: NodePort			#指定方式

[root@server1 ~]# kubectl apply -f service.yaml 
service/myservice created             
[root@server1 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP        4d3h
myservice    NodePort    10.103.247.110   <none>        80:30256/TCP   12s

Kube-dns sevice

Kubernetes 在kube-system命名空间提供了一个 DNS Service。可以使用以下命令查看:

kubectl get svc kube-dns -n kube-system

 首先我们运行一个service方式为ClusterIP:

[root@server1 ~]# vim service.yaml 
[root@server1 ~]# cat service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  selector:
      app: nginx
  type: ClusterIP
[root@server1 ~]# kubectl apply -f service.yaml 
service/myservice created
[root@server1 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP   4d3h
myservice    ClusterIP   10.105.237.243   <none>        80/TCP    23s

通过kube-dns我们可以通过直接访问service的名称来访问pod:

[root@server1 ~]# kubectl attach test -it			#之前使用busyboxplus创建的pod
Defaulting container name to test.
Use 'kubectl describe pod/test -n default' to see all of the containers in this pod.
If you don't see a command prompt, try pressing enter.
/ nslookup myservice				#查看myservice的解析
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      myservice
Address 1: 10.105.237.243 myservice.default.svc.cluster.local
/ 
/ curl myservice.default.svc.cluster.local		#域名格式:$(servicename).$(namespace).svc.cluster.local,也可以直接访问myservice
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
/ curl myservice.default.svc.cluster.local/hostname.html
deployment-nginx-56d786cd98-fcmh9
/ curl myservice.default.svc.cluster.local/hostname.html
deployment-nginx-56d786cd98-tg2t6
/ curl myservice.default.svc.cluster.local/hostname.html
deployment-nginx-56d786cd98-pkvpg
/ curl myservice.default.svc.cluster.local/hostname.html
deployment-nginx-56d786cd98-k8psl
/ ping myservice				#直接访问myservice
PING myservice (10.105.237.243): 56 data bytes
64 bytes from 10.105.237.243: seq=0 ttl=64 time=0.140 ms
64 bytes from 10.105.237.243: seq=1 ttl=64 time=0.073 ms

可以看出通过服务名称可以直接访问pod并自动实现负载均衡。

实验后删除:

[root@server1 ~]# kubectl delete -f service.yaml 
service "myservice" deleted

Headless Service举例

Headless Service “无头服务” Headless Service不需要分配一个VIP,而是直接以DNS记录的方式解析出被代理Pod的IP地址。

域名格式: ( s e r v i c e n a m e ) . (servicename). (servicename).(namespace).svc.cluster.local

[root@server1 ~]# vim service.yaml 
[root@server1 ~]# cat service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: nginx
  clusterIP: None			#指定为Headless方式
[root@server1 ~]# kubectl apply -f service.yaml 
service/myservice created
[root@server1 ~]# kubectl get svc			#可以看出ip为none
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   4d4h
myservice    ClusterIP   None         <none>        80/TCP    42s

可以直接通过service的名称直接访问到后端的pod:

[root@server1 ~]# kubectl attach test -it
Defaulting container name to test.
Use 'kubectl describe pod/test -n default' to see all of the containers in this pod.
If you don't see a command prompt, try pressing enter.
/ nslookup myservice			#查看myservice的解析,可以看到后端的4个pod
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      myservice
Address 1: 10.244.2.35 10-244-2-35.myservice.default.svc.cluster.local
Address 2: 10.244.2.34 10-244-2-34.myservice.default.svc.cluster.local
Address 3: 10.244.1.32 10-244-1-32.myservice.default.svc.cluster.local
Address 4: 10.244.1.34 10-244-1-34.myservice.default.svc.cluster.local
/ curl myservice
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
/ curl myservice/hostname.html
deployment-nginx-56d786cd98-k8psl
/ curl myservice/hostname.html
deployment-nginx-56d786cd98-tg2t6
/ curl myservice/hostname.html
deployment-nginx-56d786cd98-pkvpg
/ curl myservice/hostname.html
deployment-nginx-56d786cd98-fcmh9		#可以看出可以实现负载均衡

当pod更新后依然可以解析:

[root@server1 ~]# vim deployment.yaml 
[root@server1 ~]# cat deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-nginx
  labels:
    app: nginx
spec:
  replicas: 4
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: ikubernetes/myapp:v2			#更新到v2
        ports:
        - containerPort: 80

[root@server1 ~]# kubectl apply -f deployment.yaml 
[root@server1 ~]# kubectl attach demo -it
Defaulting container name to demo.
Use 'kubectl describe pod/demo -n default' to see all of the containers in this pod.
If you don't see a command prompt, try pressing enter.
bash-4.3# 
bash-4.3# dig myservice.default.svc.cluster.local

 
实验后删除:

[root@server1 ~]# kubectl delete -f service.yaml 
service "myservice" deleted

LoadBalancer举例

从外部访问 Service 的第一种方式为NodePort,之前已经介绍过,下面介绍另外两种方式。

从外部访问 Service 的第二种方式,适用于公有云上的 Kubernetes 服务。这时候,你可以指定一个 LoadBalancer 类型的 Service。

[root@server1 ~]# vim service.yaml 
[root@server1 ~]# cat service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: nginx
  type: LoadBalancer

在service提交后,Kubernetes就会调用 CloudProvider 在公有云上为你创建一个负载均衡服务,并且把被代理的 Pod 的 IP地址配置给负载均衡服务做后端。

[root@server1 ~]# kubectl apply -f service.yaml 
service/myservice created
[root@server1 ~]# kubectl get svc
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP        4d4h
myservice    LoadBalancer   10.105.70.198   <pending>     80:31184/TCP   45s

实验后删除:

[root@server1 ~]# kubectl delete svc myservice 
service "myservice" deleted

ExternalName 举例

从外部访问的第三种方式叫做ExternalName,这种方式也可以使pod能够访问外部的地址。

[root@server1 ~]# vim External.yaml 
[root@server1 ~]# cat External.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  type:  ExternalName
  externalName: www.baidu.com			#指定外部有效地址

接下来创建service:

[root@server1 ~]# kubectl apply -f External.yaml 
service/myservice created
[root@server1 ~]# kubectl get svc
NAME         TYPE           CLUSTER-IP   EXTERNAL-IP     PORT(S)   AGE
kubernetes   ClusterIP      10.96.0.1    <none>          443/TCP   4d4h
myservice    ExternalName   <none>       www.baidu.com   <none>    6s

可以看出并没有分配ip地址,只是在dns里面更新了以下CNAME。
描述这样的做法有一个好处,即使需要访问的外部地址变了,也只需要更改service的yaml文件即可,依然可以通过访问myservice.default.svc.cluster.local来访问外部地址,如下:

[root@server1 ~]# vim External.yaml 
[root@server1 ~]# cat External.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  type:  ExternalName
  externalName: www.qq.com			#外部网站变更
[root@server1 ~]# kubectl apply -f External.yaml 
service/myservice configured

ExternalName方式的service允许为其分配一个公有IP。

[root@server1 ~]# vim service.yaml 
[root@server1 ~]# cat service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: nginx
  externalIPs:
    - 172.25.63.100			#指定的ip地址
[root@server1 ~]# kubectl apply -f service.yaml 
service/myservice created
[root@server1 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP     PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1        <none>          443/TCP   4d4h
myservice    ClusterIP   10.106.224.140   172.25.63.100   80/TCP    10s

查看svc的状态说明指定成功,在集群外访问:

[root@foundation63 Desktop]# curl 172.25.63.100
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
[root@foundation63 Desktop]# curl 172.25.63.100
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
[root@foundation63 Desktop]# curl 172.25.63.100
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>