Service

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

Service介绍

Service的类型

先来看看《kubernetes权威指南》中对Service类型的介绍:

service selector

service通过selector和pod建立关联。k8s会根据service关联到pod的podIP信息组合成一个endpoint。若service定义中没有selector字段,service被创建时,endpoint controller不会自动创建endpoint。

service负载分发策略

service 负载分发策略有两种:

Headless Service 适用场景:应用需要直接与后端的具体Pod进行通信(而不是通过一个代理或负载均衡器)。例如当您部署了有状态应用ClickHouse数据库服务,可以使用Headless Service,使得应用Pod可以直接访问每个ClickHouse Pod,并且均衡地读取数据或针对性地写入数据,提升数据处理的效率。

服务发现

k8s服务发现方式

虽然Service解决了Pod的服务发现问题,但不提前知道Service的IP,怎么发现service服务呢?

k8s提供了两种方式进行服务发现:

● 环境变量: 当创建一个Pod的时候,kubelet会在该Pod中注入集群内所有Service的相关环境变量。需要注意的是,要想一个Pod中注入某个Service的环境变量,则必须Service要先比该Pod创建。这一点,几乎使得这种方式进行服务发现不可用。

● DNS:可以通过cluster add-on的方式轻松的创建KubeDNS来对集群内的Service进行服务发现————这也是k8s官方强烈推荐的方式。为了让Pod中的容器可以使用kube-dns来解析域名,k8s会修改容器的/etc/resolv.conf配置。

k8s服务发现原理

● endpoint
endpoint是k8s集群中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址。
service配置selector,endpoint controller才会自动创建对应的endpoint对象;否则,不会生成endpoint对象.

● endpoint controller
endpoint controller是k8s集群控制器的其中一个组件,其功能如下:

负责生成和维护所有endpoint对象的控制器
负责监听service和对应pod的变化
监听到service被删除,则删除和该service同名的endpoint对象
监听到新的service被创建,则根据新建service信息获取相关pod列表,然后创建对应endpoint对象
监听到service被更新,则根据更新后的service信息获取相关pod列表,然后更新对应endpoint对象
监听到pod事件,则更新对应的service的endpoint对象,将podIp记录到endpoint中

各种port的区别

刚接触 k8s 涉及到端口到内容较多,容易混淆,这里整理如下:

nodePort

nodePort 提供了集群外部客户端访问 Service 的一种方式,nodePort 提供了集群外部客户端访问 Service 的端口,通过 nodeIP:nodePort 提供了外部流量访问k8s集群中service的入口。

比如外部用户要访问k8s集群中的一个Web应用,那么我们可以配置对应service的type=NodePort,nodePort=30001。其他用户就可以通过浏览器http://node:30001访问到该web服务。

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  externalTrafficPolicy: Local或Cluster
  internalTrafficPolicy: Cluster
  type: NodePort            // 配置NodePort,外部流量可访问k8s中的服务
  ports:
  - name: http
    nodePort: 30001         // NodePort,外部客户端访问的端口
    port: 30080             // 服务访问端口,集群内部访问的端口
    protocol: TCP
    targetPort: 80          // pod控制器中定义的端口(应用访问的端口)
  selector:
    name: nginx-pod

而数据库等服务可能不需要被外界访问,只需被内部服务访问即可,那么我们就不必设置service的NodePort。

关于 不同网络插件Flannel/Terway对配置 externalTrafficPolicy: Local或Cluster 的区别,请参考 help.aliyun.com

port

port是暴露在cluster ip上的端口,port提供了集群内部客户端访问service的入口,即clusterIP:port。

mysql容器暴露了3306端口(参考DockerFile),集群内其他容器通过33306端口访问mysql服务,但是外部流量不能访问mysql服务,因为mysql服务没有配置NodePort。对应的service.yaml如下:

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  ports:
  - name: mysql
    port: 3306
    protocol: TCP
    targetPort: 3306
  selector:
    name: mysql-pod

targetPort

targetPort是pod上的端口,从port/nodePort上来的数据,经过kube-proxy流入到后端pod的targetPort上,最后进入容器。

与制作容器时暴露的端口一致(使用DockerFile中的EXPOSE),例如官方的nginx(参考DockerFile)暴露80端口。 对应的service.yaml如下:

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort            // 配置NodePort,外部流量可访问k8s中的服务
  ports:
  - name: http
    nodePort: 30001         // NodePort,外部客户端访问的端口
    port: 30080             // 服务访问端口,集群内部访问的端口
    protocol: TCP
    targetPort: 80          // pod控制器中定义的端口(应用访问的端口)
  selector:
    name: nginx-pod

containerPort

containerPort是在pod控制器中定义的、pod中的容器需要暴露的端口。

例如,mysql 服务需要暴露 3306 端口,redis 暴露 6379 端口

apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  replicas: 1
  selector:
    name: redis-master
  template:
    metadata:
      labels:
        name: redis-master
    spec:
      containers:
      - name: master
        image: kubeguide/redis-master
        ports:
        - containerPort: 6379    # 此处定义暴露的端口

K8S 面试题:service 到底能不能 ping 通

无风 652 1 1 12 发布于 2021-03-06

面试中被问了这道题,其实是在考察你对 k8s service 的使用和底层原理的理解,答案显然没有那么简单。

实验环境搭建

使用 kind (k8s.io) 可以很方便的在本地搭建实验环境,修改配置即可创建 kubeProxyMode 为 iptables 或 ipvs。

实验涉及的 config 和 yaml 可以在我的 github repo 找到: win5do/k8s-svc-test (github.com)

网络调试工具可以通过创建 netshoot 容器 attach 到目标 network namespace:nicolaka/netshoot: a Docker + Kubernetes network trouble-shooting swiss-army container (github.com)

ClusterIP/NodePort

这类 svc 都会分配 ClusterIP,这个 IP 地址是 VIP(虚拟 IP),是在所有 node 上添加一些 netfilter 规则,主要有 iptables 和 ipvs 两种方案,能不能 ping 通要看具体实现。

Headless: ClusterIP=None

示例配置:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx

Headless svc 不会分配 clusterIP,而是返回对应 DNS 的 A 记录,如果 svc 后端有3个 pod 则返回 3 个 pod IP。访问 svc 时会随机选择一个 IP,所以 headless svc 是可以 ping 通的。

Loadbalancer

Loadbalancer 类型 svc 也要看云厂商的具体实现。

ExternalName

示例配置:

apiVersion: v1
kind: Service
metadata:
  name: airflow
spec:
  type: ExternalName
  externalName: 10.84.14.156

ExternalName 对应 DNS 的 CNAME,如果配置的域名可以 ping 通则 svc 可以 ping 通。

K8S容器编排之Headless浅谈

前言: 最近在基于K8S开发平台的过程中遇到了有个问题没有弄懂,就是CoreDNS的作用,就好像在Docker Swarm里面,我们可以通过Service name来访问一组容器,在K8S里,我们想要通过name来访问服务的方式就是在Deployment上面添加一层Service,这样我们就可以通过Service name来访问服务了,那其中的原理就是和CoreDNS有关,它将Service name解析成Cluster IP,这样我们访问Cluster IP的时候就通过Cluster IP作负载均衡,把流量分布到各个POD上面。我想的问题是CoreDNS是否会直接解析PODname,在Service的服务里,是不可以的,因为ServiceCluster IP,直接被CoreDNS解析了,那怎么才能让它解析POD呢,有大牛提出了可以使用Headless Service,所以我们就来探究一下什么是Headless Service

Headless Service也是一种Service,但不同的是会定义spec:clusterIP: None,也就是不需要Cluster IPService
我们首先想想ServiceCluster IP的工作原理:一个Service可能对应多个EndPoint(Pod)client访问的是Cluster IP,通过iptables规则转到Real Server,从而达到负载均衡的效果。具体操作如下所示:

kubectl get svc
NAME                      CLUSTER-IP       EXTERNAL-IP       PORT(S)           AGE
nginx-service             10.107.124.218   192.168.128.158   80/TCP,443/TCP    1d
kubectl describe  svc nginx-service
Name:                   nginx-service
Namespace:              default
Labels:
Selector:               component=nginx
Type:                   ClusterIP
IP:                     10.107.124.218
External IPs:           192.168.128.158
Port:                   nginx-http      80/TCP
Endpoints:              10.244.2.9:80
Port:                   nginx-https     443/TCP
Endpoints:              10.244.2.9:443
Session Affinity:       None
No events.
nslookup nginx-service.default.svc.cluster.local  10.96.0.10
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   nginx-service.default.svc.cluster.local
Address: 10.107.124.218

从上面的结果中我们可以看到虽然Service有2个endpoint,但是dns查询时只会返回Service的地址。具体client访问的是哪个Real Server,是由iptables来决定的。
那么我们再来看看Headless Service的效果呢?

kubectl get service
NAME                      CLUSTER-IP       EXTERNAL-IP       PORT(S)    AGE
nginx                     None                         80/TCP     1h
kubectl describe  service nginx
Name:                   nginx
Namespace:              default
Labels:                 app=nginx
Selector:               app=nginx
Type:                   ClusterIP
IP:                     None
Port:                   web     80/TCP
Endpoints:              10.244.2.17:80,10.244.2.18:80
Session Affinity:       None
No events.
nslookup nginx.default.svc.cluster.local 10.96.0.10
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   nginx.default.svc.cluster.local
Address: 10.244.2.17
Name:   nginx.default.svc.cluster.local
Address: 10.244.2.18

根据结果得知dns查询会如实的返回2个真实的endpoint,即精确到pod的ip。

所以,顾名思义,Headless Service就是没头的Service。有什么使用场景呢?

kubectl get statefulsets web
NAME      DESIRED   CURRENT   AGE
web       2         2         1h
kubectl get pods
web-0                       1/1       Running   0          1h
web-1                       1/1       Running   0          1h
nslookup nginx.default.svc.cluster.local 10.96.0.10
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   nginx.default.svc.cluster.local
Address: 10.244.2.17
Name:   nginx.default.svc.cluster.local
Address: 10.244.2.18

nslookup web-1.nginx.default.svc.cluster.local 10.96.0.10
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-1.nginx.default.svc.cluster.local
Address: 10.244.2.18

nslookup web-0.nginx.default.svc.cluster.local 10.96.0.10
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-0.nginx.default.svc.cluster.local
Address: 10.244.2.17

如上,web为我们创建的StatefulSets,对应的pod的域名为web-0web-1,他们之间可以互相访问,这样对于一些集群类型的应用就可以解决互相之间身份识别的问题了。

完整示例:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.11
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
      nodeSelector:
        node: kube-node3
      volumes:
        - name: www
          hostPath:
            path: /mydir

发布于 2019-01-06 02:07