Kubernetes应用访问管理(上)

fldj
发布于 2022-9-5 17:55
浏览
0收藏

作者 | 老郑

来源 |运维开发故事(ID:mygsdcsf)

转载请联系授权(微信ID:wanger5354)

在Kubernetes中,如果仅仅是单纯的部署Pod,部署Deployment,并没有任何意义,因为我们最终的目的是要为应用和用户提供服务。

在Kubernetes中,提供了Service和Ingress两种对象来实现应用间访问或外部对集群应用访问,这两种对象在实际的工作中会时长使用,非常重要的对象。

Service
对于kubernetes整个集群来说,Pod的地址也可变的,也就是说如果一个Pod因为某些原因退出了,而由于其设置了副本数replicas大于1,那么该Pod就会在集群的任意节点重新启动,这个重新启动的Pod的IP地址与原IP地址不同,这对于业务来说,就不能根据Pod的IP作为业务调度。kubernetes就引入了Service的概念,它为Pod提供一个入口,主要通过Labels标签来选择后端Pod,这时候不论后端Pod的IP地址如何变更,只要Pod的Labels标签没变,那么 业务通过service调度就不会存在问题。

当声明Service的时候,会自动生成一个cluster IP,这个IP是虚拟IP。我们就可以通过这个IP来访问后端的Pod,当然,如果集群配置了DNS服务,比如现在的CoreDNS,那么也可以通过Service的名字来访问,它会通过DNS自动解析Service的IP地址。

Service的类型有4种,Cluster IP,LoadBalance,NodePort,ExternalName。其中Cluster IP是默认的类型。
(1)、Cluster IP:通过 集群内部IP暴露服务,默认是这个类型,选择该值,这个Service服务只能通过集群内部访问;
(2)、LoadBalance:使用云提供商的负载均衡器,可以向外部暴露服务,选择该值,外部的负载均衡器可以路由到NodePort服务和Cluster IP服务;
(3)、NodePort:顾名思义是Node基本的Port,如果选择该值,这个Service可以通过NodeIP:NodePort访问这个Service服务,NodePort会路由到Cluster IP服务,这个Cluster IP会通过请求自动创建;
(4)、ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容,没有任何类型代理被创建,可以用于访问集群内其他没有Labels的Pod,也可以访问其他NameSpace里的Service。

kubernetes主要通过kube-proxy创建iptables和ipvs规则,在每个Node节点上都会创建这些规则。 

Kubernetes应用访问管理(上)-鸿蒙开发者社区

ClusterIP
ClusterIP是Service默认的类型,只能在集群的内部访问,也是工作中最常用的一个类型,定义如下:

apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  labels:
    app: nginx
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
  - name: http
    port: 80

其中:

  • type用户指定Service类型
  • ports用户指定Service的端口
  • selector用户选择后端的Pod
    如果Service暴露的端口和后端的端口不一致,还可以这样写:
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  labels:
    app: nginx
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
  - name: http
    port: 80
    targetPort: 8080

targetPort用于指定后端的Pod,可以直接指定端口,也可以指定后端声明的端口名字,前提是后端必须声明端口名,如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.8
        ports:
        - name: http
          containerPort: 80         
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  labels:
    app: nginx
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
  - name: http
    port: 80
    targetPort: http

如上定义好Service并创建过后,会在集群生成对应的Service和Endpoints,如下:

$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
nginx-svc    ClusterIP   10.103.74.70   <none>        80/TCP     12m
$ kubectl get endpoints
NAME            ENDPOINTS                                            AGE
nginx-svc       172.16.51.208:80,172.16.51.209:80,172.16.51.237:80   12m

可以看到,endpoints的名字和service的名字一样,而且我们并没有创建endpoints,是由Kubernetes自己创建的。其背后的逻辑是:当我们新增Pod或者删除Pod,是从Endpoints里添加或者剔除,Service本身是不改变的,在同一个namespace下,Service和Endpoints是通过名字进行关联的。

所以,Endpoints其实也是一个对象,除了由Kubernetes生成还可以自己定义。在实际工作,有些场景是需要自定义Endpoints的,比如在集群外部署了一个Redis服务,集群内部想通过Service的方式进行访问,这时候就可以通过自定义Endpints的方式实现,如下:

kind: Service
apiVersion: v1
metadata:
  name: custom-endpoint-svc
spec:
  ports:
  - port: 30018
    protocol: TCP
    name: http
  type: ClusterIP
---
kind: Endpoints
apiVersion: v1
metadata: 
  name: custom-endpoint-svc
subsets:
- addresses:
  - ip: 192.168.32.8
  ports:
   - port: 8080
     name: http

其中,在Endpoints的subsets中用于定义目的地址和端口,需要注意的是:

  • Endpoints和Service的metadata.name须保持一致
  • Service.spec.ports[x].name和Endpoints.subsets[x].ports[x].name须保持一致
    NodePort
    如果Service的类型是NodePort,表示把Service的端口映射到了Node节点上,这时候就可以通过NodeIP:NodePort进行访问,可以实现在外部对集群内应用进行访问。

NodePort并不是随便选择的,当安装好Kubernetes集群后,会给定一个默认的NodePort范围,它们是从30000到32767端口,如果没有指定特定端口,默认会从这个区间范围内随机选择一个。

一个NodePort类型的Service定义如下:

apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  labels:
    app: nginx
spec:
  type: NodePort 
  selector:
    app: nginx
  ports:
  - name: http
    port: 80
    targetPort: http

除了Type类型不一样,其他的和定义ClusterIP类型的一样。

当创建完成过后,生成的Service如下:

$ kubectl get svc
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
nginx-svc        NodePort    10.103.74.70     <none>        80:32710/TCP   54m

NodePort类型的Service,既可以在集群内部通过ClusterIP:Port进行访问,也可以在集群外部通过NodeIP:NodePort进行访问。如上,80:32710中,80是集群内部端口,32710是暴露出来的节点端口。

LoadBalancer
原则上,如果要使用LoadBalancer需要云厂商的支持,因为公网IP这些基本都是云厂商来分配。定义如下:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis
  name: redis-master
spec:
  clusterIP: 10.96.246.58
  externalTrafficPolicy: Local
  healthCheckNodePort: 32006
  ports:
  - name: redis
    nodePort: 30997
    port: 6379
    protocol: TCP
    targetPort: 6379
  selector:
    app: redis
    release: redis
    role: master
  sessionAffinity: None
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - ip: 121.189.210.111

这样就可以直接通过公网IP和端口(121.189.210.111:6379)进行访问。

但是,为了在内网环境也使用LoadBalancer功能,有很多开源方案,其中OpenELB是一个非常不错的项目,我们下面可以来简单使用一下。

OpenELB
OpenELB 之前叫 PorterLB,是为物理机(Bare-metal)、边缘(Edge)和私有化环境设计的负载均衡器插件,可作为 ?Kubernetes、K3s、KubeSphere 的 LB 插件对集群外暴露 LoadBalancer 类型的服务,现阶段是 CNCF 沙箱项目,核心功能包括:

  • 基于 BGP 与 Layer 2 模式的负载均衡
  • 基于路由器 ECMP 的负载均衡
  • IP 地址池管理
  • 使用 CRD 进行 BGP 配置
    (1)安装 要安装OpenELB非常简单,直接使用以下命令即可。
$ kubectl apply -f https://raw.githubusercontent.com/openelb/openelb/master/deploy/openelb.yaml

值得注意的是,如果本地不能获取k8s.gcr.io镜像,可以把上面的YAML文件下载下来,根据里面的注释做相应的镜像替换即可。

安装完成过后,会生成如下Pod:

$ kubectl get pod -n openelb-system 
NAME                              READY   STATUS      RESTARTS   AGE
openelb-admission-create-ltfcv    0/1     Completed   0          4m19s
openelb-admission-patch-h485q     0/1     Completed   0          4m19s
openelb-keepalive-vip-7mnl7       1/1     Running     0          3m8s
openelb-manager-98764b5ff-4c58s   1/1     Running     0          4m19s

(2)配置 首先,需要为kube-proxy启动strictARP,以便Kubernetes集群中的所有网卡停止响应其他网卡的ARP请求,而由OpenELB来处理ARP请求。

$ kubectl edit configmap kube-proxy -n kube-system
......
ipvs:
  strictARP: true
......

然后重启kube-proxy组件,命令如下:

$ kubectl rollout restart daemonset kube-proxy -n kube-system

如果安装 OpenELB 的节点有多个网卡,则需要指定 OpenELB 在二层模式下使用的网卡,如果节点只有一个网卡,则可以跳过此步骤,假设安装了 OpenELB 的 master1 节点有两个网卡(eth0 192.168.0.2 和 ens33 192.168.0.111),并且 eth0 192.168.0.2 将用于 OpenELB,那么需要为 master1 节点添加一个 annotation 来指定网卡:

$ kubectl annotate nodes master1 layer2.openelb.kubesphere.io/v1alpha1="192.168.0.2"

接下来就可以创建一个EIP对象来充当OpenELB的IP地址池,YAML清单如下:

apiVersion: network.kubesphere.io/v1alpha2
kind: Eip
metadata:
  name: eip-pool
spec:
  address: 192.168.205.50-192.168.205.60
  protocol: layer2
  disable: false
  interface: ens33

值得注意的是:

  • address用于配置地址池,如果是在layer2层,则需要和集群的地址保持在同一网段
  • protocol用户指定模式,默认是bgp,我们这里指定的是layer2。
  • interface用于指定网卡,由于这里是layer2,所以需要指定interface。
  • disable表示禁用Eip对象
    创建完成过后,可以看看Eip地址池状态:
$ kubectl get eip
NAME       CIDR                            USAGE   TOTAL
eip-pool   192.168.205.50-192.168.205.60   1       11

最后,我们创建一个LoadBalancer类型的Service,如下:

apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  labels:
    app: nginx
  annotations:
    lb.kubesphere.io/v1alpha1: openelb
    protocol.openelb.kubesphere.io/v1alpha1: layer2
    eip.openelb.kubesphere.io/v1alpha2: eip-pool
spec:
  type: LoadBalancer 
  selector:
    app: nginx
  ports:
  - name: http
    port: 80
    targetPort: http

注意这里我们为 Service 添加了几个 annotations 注解:

  • lb.kubesphere.io/v1alpha1: openelb 用来指定该 Service 使用 OpenELB
  • protocol.openelb.kubesphere.io/v1alpha1: layer2  表示指定 OpenELB 用于 Layer2 模式
  • eip.openelb.kubesphere.io/v1alpha2: eip-pool 用来指定了 OpenELB 使用的 Eip 对象,如果未配置此注解,OpenELB 会自动使用与协议匹配的第一个可用 Eip 对象,此外也可以删除此注解并添加 spec:loadBalancerIP 字段(例如 spec:loadBalancerIP: 192.168.0.108)以将特定 IP 地址分配给 Service。
    Service创建过后,可以查看其状态是否正确:
$ kubectl get svc
NAME             TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE
nginx-svc        LoadBalancer   10.103.74.70     192.168.205.50   80:32710/TCP   105m

可以看到在EXTERNAL-IP处为我们分配了一个Eip,现在就可以直接使用192.168.205.50:80从外部直接访问集群内的服务了。

ExternalName
ExternalName 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。例如:

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

当查询主机 my-service.prod.svc.cluster.local (后面服务发现的时候我们会再深入讲解)时,集群的 DNS 服务将返回一个值为 my.database.example.com 的 CNAME 记录。访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发。如果后续决定要将数据库迁移到 Kubernetes 集群中,可以启动对应的 Pod,增加合适的 Selector 或 Endpoint,修改 Service 的 type,完全不需要修改调用的代码,这样就完全解耦了。

Headless Service
上面介绍的几种Service的类型,都会生成一个ClusterIP供集群使用,如果在Kubernetes集群中配置了DNS,解析Service Name会得到ClusterIP。但是在一些特殊的场景下,我们希望直接和应用Pod进行通信,比如数据库等有状态应用,为此,社区提供了Headless Service,也就是无头服务。

无头服务,也就是在配置Service的时候将spec.clusterIP的值设置为“None”,如下:

apiVersion: v1
kind: Service
metadata:
  name: nginx-headless-service
  labels:
    name: nginx-headless-service
spec:
  clusterIP: None
  selector:
    name: nginx
  ports:
  - port: 8000
    targetPort: 80

创建过后,就不会生成ClusterIP,如下:

$ kubectl get svc
NAME                     TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE     17d
nginx-headless-service   ClusterIP      None             <none>           8000/TCP       3s

这种类型的Service主要用在有状态服务,后续在有状态应用管理的时候会感受到具体是如何使用的。

Ingress
上面介绍的Service主要用在集群内部,当然NodePort和LoadBalancer类型也可以用于外部访问,但是它们也有一定的弊端:
(1)如果使用NodePort类型,需要维护好每个应用的端口地址,如果服务太多就不好管理
(2)如果使用LoadBalancer类型,基本是在云上使用,需要的IP比较多,价格也比较昂贵
(3)不论是NodePort还是LoadBalancer,都是工作在四层,对于HTTPS类型的请求无法直接进行SSL校验

因此,社区提供了Ingress对象,为集群提供统一的入口,逻辑如下: 

Kubernetes应用访问管理(上)-鸿蒙开发者社区

其中Ingress代理的并不是Pod的Service,而是Pod,之所以在配置的时候是配置的Service,是为了获取Pod的信息。

Ingress提供七层访问入口,但是Kubernetes的Ingress对象本身没有任何功能,需要借助一些Controller来实现,常用的有:

  • Nginx Ingress Controller
  • Traefik Ingress Controller
  • Kong Ingress Controller
  • APISix Ingress Controller
  • .....
    最常用的是nginx ingress controller,这里也会安装nginx ingress controller来进行介绍。

分类
标签
已于2022-9-5 17:55:37修改
收藏
回复
举报
回复
    相关推荐