k8s七层负载均衡器--Ingress和Ingress Controller

k8s七层负载均衡器--Ingress和Ingress Controller

一、四层负载Service存在的问题

1.1、Pod漂移问题

Kubernetes具有强大的副本控制能力,能保证在任意副本(Pod)挂掉时自动从其他机器启动一个新的,还可以动态扩容等,通俗地说,这个Pod可能在任何时刻出现在任何节点上,也可能在任何时刻死在任何节点上;那么自然随着Pod的创建和销毁,Pod IP 肯定会动态变化;那么如何把这个动态的Pod IP暴露出去?这里借助于Kubernetes的 Service 机制,Service可以以标签的形式选定一组带有指定标签的Pod,并监控和自动负载他们的Pod IP,那么我们向外暴露只暴露Service IP就行了;这就是NodePort模式:即在每个节点上开起一个端口,然后转发到内部Pod IP 上,如下图所示:

此时的访问方式:http://nodeip:nodeport/ ,即数据包流向如下:客户端请求-->node节点的ip:端口--->service的ip:端口--->pod的ip:端口

image-20210715150045819

1.2、端口管理问题

采用NodePort方式暴露服务面临的问题是,服务一旦多起来,NodePort在每个节点上开启的端口会及其庞大,而且难以维护;这时,我们能否使用一个Nginx直接对内进行转发呢?众所周知的是,Pod与Pod之间是可以互相通信的,而Pod是可以共享宿主机的网络名称空间的,也就是说当在共享网络名称空间时,Pod上所监听的就是Node上的端口。那么这又该如何实现呢?简单的实现就是使用 DaemonSet 在每个Node上监听 80,然后写好规则,因为Nginx外面绑定了宿主机80端口(就像NodePort),本身又在集群内,那么向后直接转发到相应 Service IP 就行了,如下图所示:

image-20210715150708131

1.3、域名分配及动态更新问题

从上面的方法,采用Nginx-Pod似乎已经解决了问题,但是其实这里面有一个很大缺陷:当每次有新服务加入又该如何修改Nginx配置呢?我们知道使用Nginx可以通过虚拟主机域名进行区分不同的服务,而每个服务通过upstream进行定义不同的负载均衡池,再加上location进行负载均衡的反向代理,在日常使用中只需要修改nginx.conf即可实现,那在K8S中又该如何实现这种方式的调度呢?假设后端的服务初始服务只有eshop,后面增加了bbs和member服务,那么又该如何将这2个服务加入到Nginx-Pod进行调度呢?总不能每次手动改或者Rolling Update 前端Nginx Pod吧!此时Ingress出现了,如果不算上面的Nginx,Ingress 包含两大组件:Ingress Controller 和 Ingress

二、Ingress和 Ingress Controller

2.1、Ingress介绍

Ingress官网定义:Ingress可以把进入到集群内部的请求转发到集群中的一些服务上,从而可以把服务映射到集群外部。Ingress 能把集群内Service 配置成外网能够访问的 URL,流量负载均衡,提供基于域名访问的虚拟主机等。

Ingress简单的理解就是你原来需要改Nginx配置,然后配置各种域名对应哪个 Service,现在把这个动作抽象出来,变成一个 Ingress 对象,你可以用 yaml 创建,每次不要去改Nginx 了,直接改yaml然后创建/更新就行了;那么问题来了:”Nginx 该怎么处理?”

Ingress Controller 这东西就是解决 “Nginx 的处理方式” 的;Ingress Controller 通过与 Kubernetes API 交互,动态的去感知集群中Ingress规则变化,然后读取他,按照他自己模板生成一段 Nginx 配置,再写到 Nginx Pod 里,最后 reload 一下,工作流程如下图:

image-20210715151343676

实际上Ingress也是Kubernetes API的标准资源类型之一,它其实就是一组基于DNS名称(host)或URL路径把请求转发到指定的Service资源的规则。用于将集群外部的请求流量转发到集群内部完成的服务发布。我们需要明白的是,Ingress资源自身不能进行“流量穿透”,仅仅是一组规则的集合,这些集合规则还需要其他功能的辅助,比如监听某套接字,然后根据这些规则的匹配进行路由转发,这些能够为Ingress资源监听套接字并将流量转发的组件就是Ingress Controller

注意:Ingress控制器不同于Deployment控制器的是,Ingress控制器不直接运行为kube-controller-manager的一部分,它仅仅是Kubernetes集群的一个附件,类似于CoreDNS,需要在集群上单独部署。

2.2、Ingress Controller介绍

Ingress Controller是一个七层负载均衡调度器,客户端的请求先到达这个七层负载均衡调度器,由七层负载均衡器在反向代理到后端pod,常见的七层负载均衡器有nginxtraefik,以我们熟悉的nginx为例,假如请求到达nginx,会通过upstream反向代理到后端pod应用,但是后端pod的ip地址是一直在变化的,因此在后端pod前需要加一个service,这个service只是起到分组的作用,那么我们upstream只需要填写service地址即可

image-20210715151901116

2.3、Ingress和Ingress Controller总结

Ingress Controller :可以理解为控制器,它通过不断的跟 Kubernetes API 交互,实时获取后端Service、Pod的变化,比如新增、删除等,结合Ingress 定义的规则生成配置,然后动态更新上边的 Nginx 或者traefik负载均衡器,并刷新使配置生效,来达到服务自动发现的作用。

Ingress :定义规则,通过它定义某个域名的请求过来之后转发到集群中指定的 Service。它可以通过 Yaml 文件定义,可以给一个或多个 Service 定义一个或多个 Ingress 规则。

2.4、Ingress Controller代理k8s内部应用的流程

(1)部署Ingress controller,我们ingress controller使用的是nginx
(2)创建Service,用来分组pod
(3)创建Pod应用,可以通过控制器创建pod
(4)创建Ingress http规则,测试通过http访问应用(域名访问或者ip:端口)
(5)创建Ingress https规则,测试通过https访问应用(域名访问或者ip:端口)

三、测试Ingress HTTP代理tomcat

3.1、安装nginx ingress controller

1)创建default-http-backend

[root@k8s-master1 ingress]# cat default-backend.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: default-http-backend
  labels:
    k8s-app: default-http-backend
  namespace: kube-system
spec:
  replicas: 1
  selector:
   matchLabels:
     k8s-app: default-http-backend
  template:
    metadata:
      labels:
        k8s-app: default-http-backend
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: default-http-backend
        # Any image is permissable as long as:
        # 1. It serves a 404 page at /
        # 2. It serves 200 on a /healthz endpoint
        image: registry.cn-hangzhou.aliyuncs.com/hachikou/defaultbackend:1.0
        livenessProbe:
          httpGet:
            path: /healthz   #这个URI是 nginx-ingress-controller中nginx里配置好的localtion 
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30   #30s检测一次/healthz
          timeoutSeconds: 5
        ports:
        - containerPort: 8080
#        resources:
#          limits:
#            cpu: 10m
#            memory: 20Mi
#          requests:
#            cpu: 10m
#            memory: 20Mi
      nodeName: k8s-node1
---
apiVersion: v1
kind: Service     #为default backend 创建一个service
metadata:
  name: default-http-backend
  namespace: kube-system
  labels:
    k8s-app: default-http-backend
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    k8s-app: default-http-backend

[root@k8s-master1 ingress]# kubectl apply -f default-backend.yaml
[root@k8s-master1 ingress]# kubectl get pods -n kube-system |grep default
default-http-backend-bb5c9474-9x746        1/1     Running   0          41s
[root@k8s-master1 ingress]# kubectl get svc -n kube-system 
NAME                   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                  AGE
default-http-backend   ClusterIP   10.100.84.60   <none>        80/TCP                   52s
kube-dns               ClusterIP   10.96.0.10     <none>        53/UDP,53/TCP,9153/TCP   6d19h

2)创建nginx-ingress-controller-rbac.yml

[root@k8s-master1 ingress]# cat nginx-ingress-controller-rbac.yml 
---
apiVersion: v1
kind: ServiceAccount    
metadata:
  name: nginx-ingress-serviceaccount #创建一个serveerAcount
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole   #这个ServiceAcount所绑定的集群角色
rules:
  - apiGroups:
      - "" 
    resources:    #此集群角色的权限,它能操作的API资源 
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
        - events
    verbs:
        - create
        - patch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses/status
    verbs:
      - update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:         
  name: nginx-ingress-role  #这是一个角色,而非集群角色 
  namespace: kube-system
rules:  #角色的权限 
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get
      - create
      - update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding       #角色绑定
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount #绑定在这个用户 
    namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding      #集群绑定
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount   #集群绑定到这个serviceacount
    namespace: kube-system   #集群角色是可以跨namespace,但是这里只指明给这个namespce来使用

[root@k8s-master1 ingress]# kubectl apply -f nginx-ingress-controller-rbac.yml

3)创建nginx-ingress-controller.yaml

[root@k8s-master1 ingress]# cat nginx-ingress-controller.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  labels:
    k8s-app: nginx-ingress-controller
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
       k8s-app: nginx-ingress-controller
  template:
    metadata:
      labels:
        k8s-app: nginx-ingress-controller
    spec:
      # hostNetwork makes it possible to use ipv6 and to preserve the source IP correctly regardless of docker configuration
      # however, it is not a hard dependency of the nginx-ingress-controller itself and it may cause issues if port 10254 already is taken on the host
      # that said, since hostPort is broken on CNI (https://github.com/kubernetes/kubernetes/issues/31307) we have to use hostNetwork where CNI is used
      # like with kubeadm
      # hostNetwork: true #注释表示不使用宿主机的80口,
      terminationGracePeriodSeconds: 60
      hostNetwork: true  #表示容器使用和宿主机一样的网络
      serviceAccountName: nginx-ingress-serviceaccount #引用前面创建的serviceacount
      containers:   
      - image: registry.cn-hangzhou.aliyuncs.com/peter1009/nginx-ingress-controller:0.20.0      #容器使用的镜像
        name: nginx-ingress-controller  #容器名
        readinessProbe:   #启动这个服务时要验证/healthz 端口10254会在运行的node上监听。 
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
        livenessProbe:
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10  #每隔10做健康检查 
          timeoutSeconds: 1
        ports:
        - containerPort: 80  
          hostPort: 80    #80映射到80
#        - containerPort: 443
#          hostPort: 443
        env:
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
        args:
        - /nginx-ingress-controller
        - --default-backend-service=$(POD_NAMESPACE)/default-http-backend
#        - --default-ssl-certificate=$(POD_NAMESPACE)/ingress-secret    #这是启用Https时用的
#      nodeSelector:  #指明运行在哪,此IP要和default backend是同一个IP
#        kubernetes.io/hostname: 10.3.1.17   #上面映射到了hostport80,确保此IP80,443没有占用.
# 
      nodeName: k8s-node1
[root@k8s-master1 ingress]# kubectl apply -f nginx-ingress-controller.yaml
[root@k8s-master1 ingress]# kubectl get pods -n kube-system 
NAME                                        READY   STATUS    RESTARTS   AGE
default-http-backend-bb5c9474-9x746         1/1     Running   0          4m23s
nginx-ingress-controller-86d8667cb7-ps677   1/1     Running   0          87s

注意:default-backend.yaml和nginx-ingress-controller.yaml文件指定了nodeName:k8s-node1表示default和nginx-ingress-controller部署在node1节点,大家的node节点如果主机名不是k8s-node1,需要自行修改成自己的主机名,这样才会调度成功,一定要让default-http-backend和nginx-ingress-controller这两个pod在一个节点上。

3.2、部署后端tomcat

[root@k8s-master1 ingress-http-tomcat]# cat demo-tomcat.yaml 
apiVersion: v1
kind: Service
metadata:
  name: tomcat
  namespace: default
spec:
  selector:
    app: tomcat
    release: canary
  ports:
  - name: http
    targetPort: 8080
    port: 8080
  - name: ajp
    targetPort: 8009
    port: 8009
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-deploy
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: tomcat
      release: canary
  template:
    metadata:
      labels:
        app: tomcat
        release: canary
    spec:
      containers:
      - name: tomcat
        image: tomcat:8.5.34-jre8-alpine   
        ports:
        - name: http
          containerPort: 8080
          name: ajp
          containerPort: 8009
          
[root@k8s-master1 ingress-http-tomcat]# cat demo-tomcat.yaml
[root@k8s-master1 ingress-http-tomcat]# kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
tomcat-deploy-66b67fcf7b-qqc9l   1/1     Running   0          6s
tomcat-deploy-66b67fcf7b-w6dw9   1/1     Running   0          6s

3.3、创建ingress

[root@k8s-master1 ingress-http-tomcat]# cat ingress-myapp.yaml 
apiVersion: extensions/v1beta1          #api版本
kind: Ingress           #清单类型
metadata:                #元数据
  name: ingress-myapp    #ingress的名称
  namespace: default     #所属名称空间
  annotations:           #注解信息
    kubernetes.io/ingress.class: "nginx" # 将配置传到ingress controller中的nginx配置中
spec:      #规格
  rules:   #定义后端转发的规则
  - host: tomcat.kubeprom.com    #通过域名进行转发
    http:
      paths:       
      - path:       #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
        backend:    #配置后端服务
          serviceName: tomcat
          servicePort: 8080

[root@k8s-master1 ingress-http-tomcat]# kubectl apply -f ingress-myapp.yaml
[root@k8s-master1 ingress-http-tomcat]# kubectl get ingress
NAME            CLASS    HOSTS                 ADDRESS   PORTS   AGE
ingress-myapp   <none>   tomcat.kubeprom.com             80      5s
[root@k8s-master1 ingress-http-tomcat]# kubectl describe ingress ingress-myapp 
Name:             ingress-myapp
Namespace:        default
Address:          
Default backend:  default-http-backend:80 (10.244.36.74:8080)
Rules:
  Host                 Path  Backends
  ----                 ----  --------
  tomcat.kubeprom.com  
                          tomcat:8080 (10.244.36.77:8080,10.244.36.78:8080)
Annotations:           kubernetes.io/ingress.class: nginx
Events:                <none>

3.4、进入ingress-controller查看nginx的配置

[root@k8s-master1 ingress-http-tomcat]# kubectl exec -it nginx-ingress-controller-86d8667cb7-ps677 -n kube-system -- cat nginx.conf
...
	server {
		server_name tomcat.kubeprom.com ;
		
		listen 80;
		
		listen [::]:80;
		
		set $proxy_upstream_name "-";
		
		location / {
			
			set $namespace      "default";
			set $ingress_name   "ingress-myapp";
			set $service_name   "tomcat";
			set $service_port   "8080";
			set $location_path  "/";
			
			rewrite_by_lua_block {
				
				balancer.rewrite()
				
			}
			
			log_by_lua_block {
				
				balancer.log()
				
				monitor.call()
			}
			
			port_in_redirect off;
			
			set $proxy_upstream_name "default-tomcat-8080";
			
			client_max_body_size                    1m;
			
			proxy_set_header Host                   $best_http_host;
			
			# Pass the extracted client certificate to the backend
			
			# Allow websocket connections
			proxy_set_header                        Upgrade           $http_upgrade;
			
			proxy_set_header                        Connection        $connection_upgrade;
			
			proxy_set_header X-Request-ID           $req_id;
			proxy_set_header X-Real-IP              $the_real_ip;
			
			proxy_set_header X-Forwarded-For        $the_real_ip;
			
			proxy_set_header X-Forwarded-Host       $best_http_host;
			proxy_set_header X-Forwarded-Port       $pass_port;
			proxy_set_header X-Forwarded-Proto      $pass_access_scheme;
			
			proxy_set_header X-Original-URI         $request_uri;
			
			proxy_set_header X-Scheme               $pass_access_scheme;
			
			# Pass the original X-Forwarded-For
			proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;
			
			# mitigate HTTPoxy Vulnerability
			# https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
			proxy_set_header Proxy                  "";
			
			# Custom headers to proxied server
			
			proxy_connect_timeout                   5s;
			proxy_send_timeout                      60s;
			proxy_read_timeout                      60s;
			
			proxy_buffering                         off;
			proxy_buffer_size                       4k;
			proxy_buffers                           4 4k;
			proxy_request_buffering                 on;
			
			proxy_http_version                      1.1;
			
			proxy_cookie_domain                     off;
			proxy_cookie_path                       off;
			
			# In case of errors try the next upstream server before returning an error
			proxy_next_upstream                     error timeout;
			proxy_next_upstream_tries               3;
			
			proxy_pass http://upstream_balancer;
			
			proxy_redirect                          off;
			
		}
		
	}
...

3.5、解析hosts并访问

192.168.40.181 tomcat.kubeprom.com

image-20210715161036775

四、测试Ingress HTTPS代理tomcat

4.1、构建TLS站点

# 准备证书,在k8s的master1节点操作
[root@k8s-master1 ingress-https-tomcat]# openssl genrsa -out tls.key 2048
[root@k8s-master1 ingress-https-tomcat]# openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=Beijing/L=Beijing/O=DevOps/CN=tomcat.kubeprom.com
[root@k8s-master1 ingress-https-tomcat]# ls
tls.crt  tls.key

# 生成secret,在k8s的master1节点操作
[root@k8s-master1 ingress-https-tomcat]# kubectl create secret tls tomcat-ingress-secret --cert=tls.crt --key=tls.key

# 查看secret
[root@k8s-master1 ingress-https-tomcat]# kubectl get secret
NAME                    TYPE                                  DATA   AGE
default-token-cm4mx     kubernetes.io/service-account-token   3      6d19h
tomcat-ingress-secret   kubernetes.io/tls                     2      14s

# 查看tomcat-ingress-secret详细信息
[root@k8s-master1 ingress-https-tomcat]# kubectl describe secret tomcat-ingress-secret
Name:         tomcat-ingress-secret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  kubernetes.io/tls

Data
====
tls.crt:  1302 bytes
tls.key:  1679 bytes

4.2、创建Ingress

[root@k8s-master1 ingress-https-tomcat]# cat ingress-tomcat-tls.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-tomcat-tls
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"  # 将配置传到ingress controller中的nginx配置中
spec:
  tls:
  - hosts:
    - tomcat.kubeprom.com
    secretName: tomcat-ingress-secret
  rules:
  - host: tomcat.kubeprom.com
    http:
      paths:
      - path:
        backend:
          serviceName: tomcat
          servicePort: 8080

[root@k8s-master1 ingress-https-tomcat]# kubectl apply -f ingress-tomcat-tls.yaml

浏览器访问https://tomcat.kubeprom.com

image-20210715161707187

posted @ 2021-07-15 16:18  运维人在路上  阅读(2890)  评论(0编辑  收藏  举报