Service

Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。通过 Label Selector 这一组 Pod 能够被 Service 访问到,

img

  • Service 能够提供负载均衡的能力,但是在使用上有以下限制:只提供 4 层负载均衡能力,而没有 7 层功能。但是可以通过增加 Ingress 来添加一个 7 层的负载均衡能力

一、Service 代理方式

在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程。kube-proxy 负责为 Service 实现了一种VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。在 Kubernetes v1.0 版本,代理完全在 userspace。在 Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。从 Kubernetes v1.2 起,默认就是 iptables 代理。在 Kubernetes v1.8.0-beta.0 中,添加了 ipvs 代理,在 Kubernetes 1.14 版本开始默认使用 ipvs 代理。

为什么使用服务代理而不使用 DNS 轮询?

  1. DNS 实现的历史由来已久,它不遵守记录 TTL,并且在名称查找结果到期后对其进行缓存。
  2. 有些应用程序仅执行一次 DNS 查找,并无限期地缓存结果。
  3. 即使应用和库进行了适当的重新解析,DNS 记录上的 TTL 值低或为零也可能会给 DNS 带来高负载,从而使管理变得困难。

img

  • Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程
  • apiserver 通过监控 kube-proxy 去进行对服务和端点的监控
  • iptables 是 Service 代理方式的一种,其中保存地址映射及规则,通过 kube-proxy 写入的
  • 客户端访问节点时通过 iptables 来实现
  • kube-proxy 通过 pod 的标签(lables)是否匹配去判断这个断点信息是否写入到 Endpoints(包含服务选择器(通过标签匹配)匹配到的所有 Pod 的引用) 里去。
  • kube-proxy 通过不同的负载均衡策略,访问对应的 Pod。

1. userspace

在这里插入图片描述

2. iptables

在这里插入图片描述

3. ipvs(常用)

这种模式,kube-proxy 会监视 Kubernetes Service 对象和 Endpoints,调用 netlink 接口以相应地创建 ipvs 规则并定期与 Kubernetes Service 对象和 Endpoints 对象同步 ipvs 规则,以确保 ipvs 状态与期望一致。访问服务时,流量将被重定向到其中一个后端 Pod

与 iptables 类似,ipvs 于 netfilter 的 hook 功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着 ipvs 可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs 为负载均衡算法提供了更多选项,例如:

  • rr:轮询调度
  • lc:最小连接数
  • dh:目标哈希
  • sh:源哈希
  • sed:最短期望延迟
  • nq:不排队调度

注意:要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 之前确保 IPVS 内核模块已安装。当 kube-proxy 以 IPVS 代理模式启动时,它将验证节点上 IPVS 内核模块是否可用。 如果未检测到 IPVS 内核模块,则 kube-proxy 将退回到以 iptables 代理模式运行。

在这里插入图片描述

可以看到,集群使用的是 ipvs 的代理方式:

[root@k8s-master01 yaml]# ipvsadm -Ln
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.96.0.1:443 rr
  -> 192.168.66.10:6443           Masq    1      3          0

[root@k8s-master01 yaml]# kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   21d

二、Service的类型

1. ClusterIp

默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 ip,只能被集群内部的应用程序所访问

img

clusterIP 主要在每个 node 节点使用对应的代理模式(这里以 iptable 为例) ,将发向 clusterIP 对应端口的数据,转发到 kube-proxy 中。然后 kube-proxy 自己内部实现有负载均衡的方法,并可以查询到这个 service 下对应 pod 的地址和端口,进而把数据转发给对应的 pod 的地址和端口

在这里插入图片描述

为了实现图上的功能,主要需要以下几个组件的协同工作:

  1. apiserver:用户通过 kubectl 命令向 apiserver 发送创建 service 的命令,apiserver 接收到请求后将数据存储到 etcd
  2. kube-proxykubernetes 的每个节点中都有一个叫做 kube-porxy 的进程,这个进程负责感知 servicepod 的变化,并将变化的信息写入本地的 iptable 规则中
  3. iptable:使用 NAT 等技术将 virtualIP 的流量转至 endpoint
ClusterIP 实例
  1. 创建一个 Deployment(后面几种 Service 类型都使用的这个 Deployment),先写一个 svc-deployment.yaml 资源清单:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
     name: myapp-deploy		# Deployment 名称
     namespace: default
    spec:
     replicas: 3
     selector:
       matchLabels:
         app: myapp
         release: stable
     template:
       metadata:
         name: myapp	# Pod 名
         labels:
           app: myapp
           release: stable
       spec:
         containers:
         - name: myapp						# 容器名
           image: wangyanglinux/myapp:v2	# nginx
           imagePullPolicy: IfNotPresent
           ports:
           - name: http
             containerPort: 80
    
  2. 创建 Service 资源清单,来代理上面创建的三个 Pod。myapp-svc.yaml:

    apiVersion: v1
    kind: Service
    metadata:
      name: myapp-svc       # Service名称
    spec:
      type: ClusterIP		# Service 类型,不写默认就是 ClusterIP
      selector:             # 用于匹配后端的 Pod 资源对象,需和上面 Pod 的标签一致
        app: myapp
        release: stable
      ports:
      - name: http
        port: 80            # Service端口号
        targetPort: 80      # 后端 Pod 端口号
        protocol: TCP       # 使用的协议
    
  3. 访问

    [root@k8s-master01 yaml]# kubectl get pod -o wide
    NAME                            READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
    myapp-deploy-6998f78dfc-nt28j   1/1     Running   0          11m   10.244.1.29   k8s-node01   <none>           <none>
    myapp-deploy-6998f78dfc-p9bkc   1/1     Running   0          11m   10.244.1.30   k8s-node01   <none>           <none>
    myapp-deploy-6998f78dfc-xqwbk   1/1     Running   0          11m   10.244.2.25   k8s-node02   <none>           <none>
    
    # svc 是 service 的简写,使用 kubectl get service 也可
    [root@k8s-master01 yaml]# kubectl get svc
    NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
    myapp-svc    ClusterIP   10.101.140.64   <none>        80/TCP    6s
    
    # 可以看到负载均衡策略是轮询
    [root@k8s-master01 yaml]# ipvsadm -Ln
    IP Virtual Server version 1.2.1 (size=4096)
    Prot LocalAddress:Port Scheduler Flags
      -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
    TCP  10.101.140.64:80 rr
      -> 10.244.1.29:80               Masq    1      0          2
      -> 10.244.1.30:80               Masq    1      0          3
      -> 10.244.2.25:80               Masq    1      0          3
    
    # 多访问几次也可以看到负载均衡策略是轮询
    [root@k8s-master01 yaml]# curl 10.101.140.64/hostname.html
    myapp-deploy-6998f78dfc-nt28j
    
Headless Service(无头服务)

有时不需要或不想要负载均衡,以及单独的 Service IP 。遇到这种情况,可以通过指定 ClusterIPspec.clusterIP)的值为 “None” 来创建 Headless Service 。这类 Service 并不会分配 Cluster IPkube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。主要用来解决 HostnamePodname 变化问题。在创建 StatefulSet 时,必须先创建一个 Headless Service

使用场景:

  • 第一种:自主选择权,有时候 client 想自己来决定使用哪个 Real Server,可以通过查询 DNS 来获取 Real Server 的信息。

  • 第二种:Headless Services 还有一个用处(PS:也就是我们需要的那个特性)。Headless Service 的对应的每一个 Endpoints,即每一个 Pod,都会有对应的 DNS 域名。当删除 Pod 时,Pod 的 IP 会变,但是 Pod 的名字不会改变,这样各 Pod 之间就可以通过 Pod 名来互相访问。

  1. 创建一个 Headless Service,还是匹配上面创建的 Deployment(ClusterIP 实例) 下的 Pod

    apiVersion: v1
    kind: Service
    metadata:
      name: myapp-headless    #service对象名
    spec:
      clusterIP: None    #将ClusterIP字段设置为None,即表示为headless类型的service资源对象
      selector:
        app: myapp    #匹配上面定义的pod资源
      ports:
      - port: 80    #service端口
        targetPort: 80    #后端pod端口
        protocol: TCP    #协议
    
  2. 查看 svc

    # 可以看到,Cluster-IP 对应位置的值为 None
    [root@k8s-master01 yaml]# kubectl get svc
    NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
    myapp-headless   ClusterIP   None            <none>        80/TCP    8s
    
  3. 在 DNS 中查询域名的 A 记录

    # 查看 k8s coredns 的ip
    [root@k8s-master01 yaml]# kubectl get pod -n kube-system -o wide
    NAME                                   READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
    coredns-5c98db65d4-5ztqn               1/1     Running   6          21d   10.244.0.11     k8s-master01   <none>           <none>
    coredns-5c98db65d4-pc62t               1/1     Running   6          21d   10.244.0.10     k8s-master01   <none>           <none>
    
    # 使用 dig 解析域名(没有 dig 要安装:yum -y install bind-utils):dig -t A 域名 @DNS服务器IP
    # DNS服务器IP:上面获取的两个 coredns ip 中选取一个
    # 默认域名:SVC_NAME.NAMESPACE.svc.cluster.local
    [root@k8s-master01 yaml]# dig -t A myapp-headless.default.svc.cluster.local. @10.244.0.11
    ;; ANSWER SECTION:
    myapp-headless.default.svc.cluster.local. 30 IN	A 10.244.1.30
    myapp-headless.default.svc.cluster.local. 30 IN	A 10.244.1.29
    myapp-headless.default.svc.cluster.local. 30 IN	A 10.244.2.25 
    # 可以看到解析的结果和前面创建的 Pod 是对应的,因此可以通过域名访问这几个 Pod
    

2. NodePort

ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 NodeIp:NodePort 来访问该服务

img

a. NodePort 实例
  1. 创建一个 NodePort Service,匹配 ClusterIP 实例中创建的 Deployment

    apiVersion: v1
    kind: Service
    metadata:
      name: myapp       #service对象名
    spec:
      type: NodePort        #这里指定使用ClusterIP,默认也是ClusterIP,这里可有可无
      selector:
        app: myapp          #匹配上面定义的pod资源
        release: stable
      ports:
      - port: 80            #service端口
        targetPort: 80      #后端pod端口
        nodePort: 30001     #节点端口,物理机上暴露出来的端口
        protocol: TCP       #协议
    
  2. 查看 svc

    [root@k8s-master01 yaml]# kubectl get svc
    NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
    myapp            NodePort    10.97.100.171   <none>        80:30001/TCP   7s
    

    可以从外部访问到 30001 端口(每个 k8s 集群的节点都可以该端口):

    在这里插入图片描述

b. 几个端口的说明

在这里插入图片描述

3. LoadBalancer

loadBalancernodePort 其实是同一种方式。区别在于 loadBalancernodePort 的基础上,借助 cloud provider 创建了 LB 来向节点导流(外部负载均衡器),并将请求转发到 NodeIp:NodePort

  1. LB 是供应商提供的,是收费的
  2. 服务器必须是云服务器

在这里插入图片描述

4. ExternalName

把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持

这种类型的 Service 通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如私有仓库:hub.zyx.com)。ExternalName ServiceService 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务

img

ExternalName 实例
apiVersion: v1
kind: Service
metadata:
  name: my-service-1
  namespace: default
spec:
  type: ExternalName
  externalName: hub.zyx.com

当查询主机 my-service.defalut.svc.cluster.local ( SVC_NAME.NAMESPACE.svc.cluster.local )时,集群的 DNS 服务将返回一个值 hub.zyx.comCNAME(别名) 记录。访问这个服务的工作方式和其他的相
同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发

[root@k8s-master01 yaml]# kubectl get svc
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-service-1     ExternalName   <none>          hub.zyx.com   <none>         13s

[root@k8s-master01 yaml]# dig -t A my-service-1.default.svc.cluster.local. @10.244.0.11
;; ANSWER SECTION:
my-service-1.default.svc.cluster.local.	30 IN CNAME hub.zyx.com.

三、Ingress-Nginx

Ingress-Nginx github 地址:https://github.com/kubernetes/ingress-nginx

Ingress-Nginx 官方网站:https://kubernetes.github.io/ingress-nginx/

在这里插入图片描述

1. 部署 Ingress-Nginx

官方网址:https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal

我现在的版本是 controller-v1.0.3,去官网找一下最新版本,采用裸机的安装方式:

官方给的方法:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.3/deploy/static/provider/baremetal/deploy.yaml

我先下载 .yaml 文件,然后再创建:

# 先获取 yaml 文件,可以用这个文件来创建或者删除 Ingress
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.3/deploy/static/provider/baremetal/deploy.yaml

# 安装 Ingress
kubectl apply -f deploy.yaml

# 删除 Ingress
kubectl delete -f deploy.yaml

2. 部署过程中常见问题

1)yaml 文件版本报错
# 安装时发现 yaml 文件中 ValidatingWebhookConfiguration 版本报错,先获取版本
# 没有报错可以不用管这条命令
[root@k8s-master01 yaml]# kubectl explain ValidatingWebhookConfiguration
KIND:     ValidatingWebhookConfiguration
VERSION:  admissionregistration.k8s.io/v1beta1

# 修改下载 yaml 中 ValidatingWebhookConfiguration 对应的 pod 的版本,再重新安装
2)镜像下载不下来

查看 pod 日志,找到是哪个镜像下载不下来,然后到 DockerHub 上找到相应替代的镜像,修改 yaml 文件中对应的镜像,再重新启动

这是我找到的两个替代镜像,(注意版本对应):

image: k8s.gcr.io/ingress-nginx/controller:v1.0.3@sha256:4ade87838eb8256b094fbb5272d7dda9b6c7fa8b759e6af5383c1300996a7452
替换为:
image: liangjw/ingress-nginx-controller:v1.0.3@sha256:4ade87838eb8256b094fbb5272d7dda9b6c7fa8b759e6af5383c1300996a7452
image: k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.0@sha256:f3b6b39a6062328c095337b4cadcefd1612348fdd5190b1dcbcb9b9e90bd8068
替换为:
image: liangjw/kube-webhook-certgen:v1.0@sha256:f3b6b39a6062328c095337b4cadcefd1612348fdd5190b1dcbcb9b9e90bd8068

3. Ingress HTTP 代理访问示例

  1. 先创建两个 PodClusterIP Service,提供 Nginx 内部访问

    # vim deployment-nginx.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
     name: nginx-dm
    spec:
     replicas: 2
     selector:
       matchLabels:
         name: nginx
     template:
       metadata:
         labels:
           name: nginx
       spec:
         containers:
         - name: nginx
           image: wangyanglinux/myapp:v1
           ports:
           - name: http
             containerPort: 80
    ---
    # 定义nginx 的 svc
    apiVersion: v1
    kind: Service
    metadata:
      name: nginx-svc
      annotations:
    	kubernets.io/ingress.class: "nginx"
    spec:
      ports:
      - port: 80
        targetPort: 80
        protocol: TCP
      selector:
        name: nginx
    
  2. 再创建 Ingress 将服务暴露到外部
    官方文档:https://kubernetes.io/docs/concepts/services-networking/ingress/

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: nginx-test
    spec:
      rules:							# 规则,List,可配置多个域名。
      - host: "www.zyx.com"				# 主机域名
        http:
          paths:						# 路径
          - path: /
            backend:
              serviceName: nginx-svc	# 这里链接的是上面创建的 svc 的名称 
              servicePort: 80			# svc 的端口
    
    # 查看 ingress
    kubectl get ingress
    

    Ingress 资源清单中的 spec.rules 最终会转换为 nginx 的虚拟主机配置,进入到 ingress-nginx 容器中查看配置

    kubectl exec ingress-nginx-controller-78fd88bd5-sbrz5 -n ingress-nginx -it -- /bin/bash
    
    cat nginx.conf 
    
  3. 修改 hosts 文件,设置上面的域名解析

    192.168.66.10   www.zyx.com
    
  4. 查看端口

    [root@k8s-master01 ingress]# kubectl get svc -n ingress-nginx
    NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
    ingress-nginx-controller             NodePort    10.96.189.184   <none>        80:31534/TCP,443:31345/TCP   10h
    
  5. 域名访问
    在这里插入图片描述

4. Ingress HTTPS 代理访问示例

  1. 创建证书,以及 cert 存储方式

    # 生成私钥和证书
    openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
    
    # kubectl 创建 secret 资源,这个 secret 后面要用
    kubectl create secret tls tls-secret --key tls.key --cert tls.crt
    
    # 查看kubectl 的 secret 资源
    kubectl get secret tls-secret
    
  2. 创建 Deployment 和 Service,这里仍使用上面创建的 Deployment

  3. 创建 Ingress

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: nginx-test-https
    spec:
      tls:
      - hosts:
        - www.zyx3.com					# host 主机
        secretName: tls-secret			# 与上面创建的 secret 要对应上
      rules:							# 规则,List,可配置多个域名。
      - host: www.zyx3.com				# 主机域名
        http:
          paths:						# 路径
          - path: /						# 域名的根路径
            backend:
              serviceName: nginx-svc	# 这里链接的是上面创建的 svc 的名称 
              servicePort: 80			# svc 的端口
    
  4. 获取 https 连接的端口

    [root@k8s-master01 https]# kubectl get svc -n ingress-nginx
    NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
    ingress-nginx-controller             NodePort    10.96.189.184   <none>        80:31534/TCP,443:31345/TCP   11h
    
  5. 配置 hosts,然后访问域名

    在这里插入图片描述
    在这里插入图片描述

5. Nginx 进行 BasicAuth

  1. 创建密钥文件
    yum install -y httpd
    
    # 创建密钥文件
    # -c 创建,创建文件为 auth,用户名为 foo
    htpasswd -c auth foo
    # 然后连输两次密码,这个密码为为之后认证使用
    
    # 构建基础权限认证,根据文件
    kubectl create secret generic basic-auth --from-file=auth
    
  2. 创建 Ingress
    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: ingress-with-auth
      annotations:
        # 身份验证类型
        nginx.ingress.kubernetes.io/auth-type: basic
        # secret 的名字(上面定义好了)
        nginx.ingress.kubernetes.io/auth-secret: basic-auth
        # 要显示的信息,说明为什么需要认证
        nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'
    spec:
      rules:
      - host: auth.zyx.com
        http:
          paths:
          - path: /
            backend:
              serviceName: nginx-svc
              servicePort: 80
    
  3. 添加 hosts 域名解析,然后访问就可以看到 BasicAuth 已经成功
    在这里插入图片描述

6. Nginx 进行重写

名称描述
nginx.ingress.kubernetes.io/rewrite-target必须重定向流量的目标URI
nginx.ingress.kubernetes.io/ssl-redirect指示位置部分是否仅可访问SSL(当Ingress包含证书时默认为True)布尔
nginx.ingress.kubernetes.io/force-ssl-redirect即使Ingress未启用TLS,也强制重定向到HTTPS布尔
nginx.ingress.kubernetes.io/app-root定义Controller必须重定向的应用程序根,如果它在’/'上下文中
nginx.ingress.kubernetes.io/use-regex指示Ingress上定义的路径是否使用正则表达式布尔

重定向示例:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-test
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: https://auth.zyx.com:31345
spec:
  rules:
  - host: re.zyx.com		# 访问这个地址,就会被重定向到 https://auth.zyx.com:31345
    http:					# 这里配置不配置均可
      paths:
      - path: /
        backend:
          serviceName: nginx-svc
          servicePort: 80
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐