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:端口
1.2、端口管理问题
采用NodePort方式暴露服务面临的问题是,服务一旦多起来,NodePort在每个节点上开启的端口会及其庞大,而且难以维护;这时,我们能否使用一个Nginx直接对内进行转发呢?众所周知的是,Pod与Pod之间是可以互相通信的,而Pod是可以共享宿主机的网络名称空间的,也就是说当在共享网络名称空间时,Pod上所监听的就是Node上的端口。那么这又该如何实现呢?简单的实现就是使用 DaemonSet 在每个Node上监听 80,然后写好规则,因为Nginx外面绑定了宿主机80端口(就像NodePort),本身又在集群内,那么向后直接转发到相应 Service IP 就行了,如下图所示:
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 一下,工作流程如下图:
实际上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,常见的七层负载均衡器有nginx
、traefik
,以我们熟悉的nginx为例,假如请求到达nginx,会通过upstream反向代理到后端pod应用,但是后端pod的ip地址是一直在变化的,因此在后端pod前需要加一个service,这个service只是起到分组的作用,那么我们upstream只需要填写service地址即可
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
四、测试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
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!