# Service
#### 介绍视频
<video controls="controls" preload="none" id="object1937819180433" class="idp-external-video" src="https://res-video.hc-cdn.com/cloudbu-site/china/zh-cn/video/cce/service.mp4" title="Service" poster="https://support.huaweicloud.com/basics-cce/zh-cn_image_0000002358303293.jpg" height="340.0000" width="600.0000"></video>
#### 直接访问Pod的问题
Pod创建完成后，如何访问Pod呢？直接访问Pod会有如下几个问题：
- Pod会随时被Deployment这样的控制器删除重建，那访问Pod的结果就会变得不可预知。
- Pod的IP地址是在Pod启动后才被分配，在启动前并不知道Pod的IP地址。
- 应用往往都是由多个运行相同镜像的一组Pod组成，逐个访问Pod也变得不现实。
举个例子，假设有这样一个应用程序，使用Deployment创建了前台和后台，前台会调用后台做一些计算处理，如[图1]所示。后台运行了3个Pod，这些Pod是相互独立且可被替换的，当Pod出现状况被重建时，新建的Pod的IP地址是新IP，前台的Pod无法直接感知。
图1Pod间访问   
![](https://support.huaweicloud.com/basics-cce/zh-cn_image_0258894622.png "点击放大")
#### 使用Service解决Pod的访问问题
Kubernetes中的Service对象就是用来解决上述Pod访问问题的。Service有一个固定IP地址（在创建CCE集群时有一个服务网段的设置，这个网段专门用于给Service分配IP地址），Service将访问它的流量转发给Pod，具体转发给哪些Pod通过Label来选择，而且Service可以给这些Pod做负载均衡。
那么对于上面的例子，为后台添加一个Service，通过Service来访问Pod，这样前台Pod就无需感知后台Pod的变化，如[图2]所示。
图2通过Service访问Pod   
![](https://support.huaweicloud.com/basics-cce/zh-cn_image_0258889981.png "点击放大")
#### 创建后台Pod
首先创建一个3副本的Deployment，即3个Pod，且Pod上带有标签"app: nginx"，具体如下所示。
```
apiVersion: apps/v1      
kind: Deployment         
metadata:
  name: nginx            
spec:
  replicas: 3                    
  selector:              
    matchLabels:
      app: nginx
  template:              
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:latest
        name: container-0
        resources:
          limits:
            cpu: 100m
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
      imagePullSecrets:
      - name: default-secret
```
#### 创建Service
下面示例创建一个名为"nginx"的Service，通过selector选择到标签"app: nginx"的Pod，目标Pod的端口为80，Service对外暴露的端口为8080。
访问服务只需要通过"服务名称:对外暴露的端口"接口，对应本例即"nginx:8080"。这样，在其他Pod中，只需要通过"nginx:8080"就可以访问到"nginx"关联的Pod。
```
apiVersion: v1
kind: Service
metadata:
  name: nginx        # Service的名称
spec:
  selector:          # Label Selector，选择访问包含app: nginx标签的Pod
    app: nginx
  ports:
  - name: service0
    targetPort: 80   # Pod的端口
    port: 8080       # Service对外暴露的端口
    protocol: TCP    # 转发协议类型，支持TCP和UDP
  type: ClusterIP    # Service的类型
```
将上面Service的定义保存到nginx-svc.yaml文件中，使用kubectl创建这个Service。
```
$ kubectl create -f nginx-svc.yaml
service/nginx created
$ kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.247.0.1       <none>        443/TCP    7h19m
nginx        ClusterIP   10.247.124.252   <none>        8080/TCP   5h48m
```
您可以看到Service有个Cluster IP，这个IP是固定不变的，除非Service被删除，所以您也可以使用ClusterIP在集群内部访问Service。
下面创建一个Pod并进入容器，使用ClusterIP访问Pod，可以看到能直接返回内容。
```
$ kubectl run -i --tty --image nginx:latest test --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # curl 10.247.124.252:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
```
#### 使用ServiceName访问Service
通过DNS进行域名解析后，可以使用"ServiceName:Port"访问Service，这也是Kubernetes中最常用的一种使用方式。在创建CCE集群的时候，会默认要求安装CoreDNS插件，在kube-system命名空间下可以查看到CoreDNS的Pod。
```
$ kubectl get po --namespace=kube-system
NAME                                      READY   STATUS    RESTARTS   AGE
coredns-7689f8bdf-295rk                   1/1     Running   0          9m11s
coredns-7689f8bdf-h7n68                   1/1     Running   0          11m
```
CoreDNS安装成功后会成为DNS服务器，当创建Service后，CoreDNS会将Service的名称与IP记录起来，这样Pod就可以通过向CoreDNS查询Service的名称获得Service的IP地址。
访问时通过nginx.\<namespace\>.svc.cluster.local访问，其中nginx为Service的名称，\<namespace\>为命名空间名称，svc.cluster.local为域名后缀，在实际使用中，在同一个命名空间下可以省略\<namespace\>.svc.cluster.local，直接使用ServiceName即可。
例如上面创建的名为nginx的Service，直接通过"nginx:8080"就可以访问到Service，进而访问后台Pod。
使用ServiceName的方式有个主要的优点就是可以在开发应用程序时可以将ServiceName写在程序中，这样无需感知具体Service的IP地址。
执行以下命令，创建一个Pod并进入容器环境，使用nslookup查询nginx的域名解析结果，可以看到nginx实际对应的是Service的完整域名nginx.default.svc.cluster.local，其解析出的IP地址为 10.247.124.252。随后通过curl nginx:8080访问该服务，可以成功返回页面内容，说明服务可正常访问。
```
$ kubectl run -i --tty --image tutum/dnsutils dnsutils --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup nginx
Server:10.247.3.10
Address:10.247.3.10#53
Name:nginx.default.svc.cluster.local
Address: 10.247.124.252
/ # curl nginx:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
```
#### Service是如何做到服务发现的
前面说到有了Service后，无论Pod如何变化，Service都能够发现到Pod。
如果调用kubectl describe命令查看Service的信息，您会看到如下信息。
```
$ kubectl describe svc nginx
Name:              nginx
......
Endpoints:         172.16.2.132:80,172.16.3.6:80,172.16.3.7:80
......
```
可以看到一个Endpoints，Endpoints同样也是Kubernetes的一种资源对象，可以查询得到。Kubernetes正是通过Endpoints监控到Pod的IP，从而让Service能够发现Pod。
```
$ kubectl get endpoints
NAME         ENDPOINTS                                     AGE
nginx        172.16.2.132:80,172.16.3.6:80,172.16.3.7:80   5h48m
```
这里的172.16.2.132:80，172.16.3.6:80，172.16.3.7:80是Pod的IP:Port，通过如下命令可以查看到Pod的IP，与上面的IP一致。
```
$ kubectl get po -o wide
NAME                     READY   STATUS    RESTARTS   AGE     IP             NODE         
nginx-869759589d-dnknn   1/1     Running   0          5h40m   172.16.3.7     192.168.0.212
nginx-869759589d-fcxhh   1/1     Running   0          5h40m   172.16.3.6     192.168.0.212
nginx-869759589d-r69kh   1/1     Running   0          5h40m   172.16.2.132   192.168.0.94
```
![](https://support.huaweicloud.com/basics-cce/zh-cn_image_0259535886.png "点击放大")
如果删除一个Pod，Deployment会将Pod重建，新的Pod IP会发生变化。
```
$ kubectl delete po nginx-869759589d-dnknn
pod "nginx-869759589d-dnknn" deleted
$ kubectl get po -o wide
NAME                     READY   STATUS    RESTARTS   AGE     IP             NODE         
nginx-869759589d-fcxhh   1/1     Running   0          5h41m   172.16.3.6     192.168.0.212
nginx-869759589d-r69kh   1/1     Running   0          5h41m   172.16.2.132   192.168.0.94 
nginx-869759589d-w98wg   1/1     Running   0          7s      172.16.3.10    192.168.0.212
```
再次查看Endpoints，会发现Endpoints的内容随着Pod发生了变化。
```
$ kubectl get endpoints
NAME         ENDPOINTS                                      AGE
kubernetes   192.168.0.127:5444                             7h20m
nginx        172.16.2.132:80,172.16.3.10:80,172.16.3.6:80   5h49m
```
下面进一步了解这又是如何实现的。
在[Kubernetes集群架构](https://support.huaweicloud.com/basics-cce/kubernetes_0003.html#kubernetes_0003__section94531832152913)中介绍过Node节点上的kube-proxy，实际上Service相关的事情都由节点上的kube-proxy处理。在Service创建时Kubernetes会分配IP给Service，同时通过API Server通知所有kube-proxy有新Service创建了，kube-proxy收到通知后通过iptables记录Service对应的IP和端口，从而让Service在节点上可以被查询到。
下图是一个实际访问Service的图示，Pod X访问Service（10.247.124.252:8080），在往外发数据包时，在节点上根据iptables规则目的IP:Port被随机替换为Pod1的IP:Port，从而通过Service访问到实际的Pod。
除了记录Service对应的IP和端口，kube-proxy还会监控Service和Endpoint的变化，从而保证Pod重建后仍然能通过Service访问到Pod。
图3Pod X访问Service的过程   
![](https://support.huaweicloud.com/basics-cce/zh-cn_image_0264605050.png "点击放大")
#### Service的类型与使用场景
Service的类型除了ClusterIP还有NodePort、LoadBalancer和Headless Service，这几种类型的Service有着不同的用途。
- ClusterIP：用于在集群内部互相访问的场景，通过ClusterIP访问Service。
- NodePort：用于从集群外部访问的场景，通过节点上的端口访问Service，详细介绍请参见[NodePort类型的Service]。
- LoadBalancer：用于从集群外部访问的场景，其实是NodePort的扩展，通过一个特定的LoadBalancer访问Service，这个LoadBalancer将请求转发到节点的NodePort，而外部只需要访问LoadBalancer，详细介绍请参见[LoadBalancer类型的Service]。
- Headless Service：用于Pod间的互相发现，该类型的Service并不会分配单独的ClusterIP， 而且集群也不会为它们进行负载均衡和路由。您可通过指定spec.clusterIP字段的值为"None"来创建Headless Service，详细介绍请参见[Headless Service]。
 
 #### NodePort类型的Service
NodePort类型的Service可以让Kubernetes集群每个节点上保留一个相同的端口， 外部访问连接首先访问节点IP:Port，然后将这些连接转发给服务对应的Pod。如下图所示。
图4NodePort Service   
![](https://support.huaweicloud.com/basics-cce/zh-cn_image_0264667450.png "点击放大")
下面是一个创建NodePort类型的Service。创建完成后，可以通过节点的IP:Port访问到后台Pod。
```
apiVersion: v1
kind: Service
metadata:
  name: nodeport-service
spec:
  type: NodePort          # Service的类型
  ports:
  - port: 8080            # Service端口
    targetPort: 80        # Pod的端口
    nodePort: 30120       # 节点访问端口
  selector:               # Label Selector，选择访问包含app: nginx标签的Pod
    app: nginx
```
创建并查看，可以看到PORT这一列为8080:30120/TCP，说明Service的8080端口是映射到节点的30120端口。
```
$ kubectl create -f nodeport.yaml 
service/nodeport-service created
$ kubectl get svc -o wide
NAME               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE    SELECTOR
kubernetes         ClusterIP   10.247.0.1       <none>        443/TCP          107m   <none>
nginx              ClusterIP   10.247.124.252   <none>        8080/TCP         16m    app=nginx
nodeport-service   NodePort    10.247.210.174   <none>        8080:30120/TCP   17s    app=nginx
```
此时，通过节点IP:端口访问Service可以访问到Pod，如下所示。
```
$ kubectl run -i --tty --image nginx:latest test --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # curl 192.168.0.212:30120
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
......
```
 #### LoadBalancer类型的Service
LoadBalancer类型的Service其实是NodePort类型Service的扩展，通过一个特定的LoadBalancer访问Service，这个LoadBalancer将请求转发到节点的NodePort。
LoadBalancer本身不是属于Kubernetes的组件，这部分通常是由具体厂商（云服务提供商）提供，不同厂商的Kubernetes集群与LoadBalancer的对接实现各不相同，例如CCE对接了ELB。这就导致了创建LoadBalancer类型的Service有不同的实现。
图5LoadBalancer Service   
![](https://support.huaweicloud.com/basics-cce/zh-cn_image_0264668224.png "点击放大")
下面是一个创建LoadBalancer类型的Service。创建完成后，可以通过ELB的IP:Port访问到后台Pod。
```
apiVersion: v1 
kind: Service 
metadata: 
  annotations:   
    kubernetes.io/elb.id: 3c7caa5a-a641-4bff-801a-feace27424b6     # ELB实例的ID，您需要提前创建一个ELB实例
    kubernetes.io/elb.class: union                                 # ELB实例类型：union表示共享型，performance表示独享型
  labels:     # Label Selector，选择访问包含app: nginx标签的Pod
    app: nginx 
  name: nginx 
spec: 
  loadBalancerIP: 10.78.42.242     #  ELB实例的IP地址
  ports: 
  - name: service0 
    port: 80               # Service端口，同时也作为ELB的监听器端口对外提供访问
    protocol: TCP 
    targetPort: 80         # Pod的端口
    nodePort: 30120        # Pod所在节点访问端口
  selector:                # Label Selector，选择包含app: nginx标签的Pod
    app: nginx 
  type: LoadBalancer    # Service类型为LoadBalancer
```
上面metadata.annotations里的参数配置是CCE的LoadBalancer类型Service需要配置的参数，表示这个Service绑定哪个ELB实例。CCE还支持创建LoadBalancer类型Service时新建ELB实例，详细的内容请参见[负载均衡(LoadBalancer)](https://support.huaweicloud.com/usermanual-cce/cce_10_0014.html)。
 #### Headless Service
前面讲的Service解决了Pod的内外部访问问题，允许客户端连接到Service关联的某个Pod。但还有下面这些问题没解决。
- 同时访问所有Pod
- 一个Service内部的Pod互相访问
为了解决以上问题，Kubernetes提供了另一种较为特殊的Service类型，称为Headless Service。对于其他Service来说，客户端在访问服务时，DNS查询时只会返回Service的ClusterIP地址，具体访问到哪个Pod是由集群转发规则（IPVS或iptables）决定的。而Headless Service并不会分配单独的ClusterIP，在进行DNS查询时会返回所有Pod的DNS记录，这样就可查询到每个Pod的IP地址。[有状态负载（StatefulSet）](https://support.huaweicloud.com/basics-cce/kubernetes_0015.html)中StatefulSet正是使用Headless Service解决Pod间互相访问的问题。
```
apiVersion: v1
kind: Service       # 对象类型为Service
metadata:
  name: nginx-headless
  labels:
    app: nginx
spec:
  ports:
    - name: nginx     # Pod间通信的端口名称
      port: 80        # Pod间通信的端口号
  selector:
    app: nginx        # 选择标签为app: nginx的Pod
  clusterIP: None     # 必须设置为None，表示Headless Service
```
执行如下命令创建Headless Service。
```
# kubectl create -f headless.yaml 
service/nginx-headless created
```
创建完成后可以查询Service。
```
# kubectl get svc
NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx-headless   ClusterIP   None         <none>        80/TCP    5s
```
创建一个Pod来查询DNS，可以看到能返回所有Pod的记录，这就解决了访问所有Pod的问题了。
```
$ kubectl run -i --tty --image tutum/dnsutils dnsutils --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup nginx-headless
Server:         10.247.3.10
Address:        10.247.3.10#53
Name:   nginx-headless.default.svc.cluster.local
Address: 172.16.0.31
Name:   nginx-headless.default.svc.cluster.local
Address: 172.16.0.18
Name:   nginx-headless.default.svc.cluster.local
Address: 172.16.0.19
```
