kubernetes 核心技术-ingress
ingress是什么
介绍service时有说了service暴露服务的三种方式ClusterIP、NodePort与LoadBalance,这几种方式都是在service的维度提供的,service的作用体现在两个方面,对集群内部,它不断跟踪pod的变化,更新endpoint中对应pod的对象,提供了ip不断变化的pod的服务发现机制,对集群外部,他类似负载均衡器,可以在集群内外部对pod进行访问。但是,单独用service暴露服务的方式,在实际生产环境中不太合适:
- ClusterIP的方式只能在集群内部访问。
- NodePort方式的话,端口范围只能是 30000-32767(可调),且每个端口只能是一个服务,在测试环境使用还行,当有几十上百的服务在集群中运行时,NodePort的端口管理是灾难。
- LoadBalance方式受限于云平台,而且它没有过滤条件,没有路由。
所以,在生产环境中都不建议直接使用service的方式暴露服务。
所幸k8s还提供了一种集群维度暴露服务的方式,也就是ingress。ingress可以简单理解为service的service。
Ingress 处于多个服务的前端,扮演着 “智能路由” 或者集群入口的角色;Ingress 可以给 Service 提供集群外部访问的 URL、负载均衡、SSL 终止、HTTP 路由等
一个请求进来,Ingress 会根据已配置的请求转发的规则,把请求路由到某个或多个service中。
举个例子,现在集群有api、文件存储、前端3个service,可以通过一个ingress对象来实现图中的请求转发:
ingress规则是很灵活的,可以根据不同域名、不同path转发请求到不同的service,并且支持https/http。
ingress与ingress-controller
ingress有两个概念,ingress和ingress-controller:
- ingress对象:
指的是k8s中的一个api对象,一般用yaml配置。作用是定义请求如何转发到service的规则,可以理解为配置模板。
- ingress-controller:
具体实现反向代理及负载均衡的程序,对ingress定义的规则进行解析,根据配置的规则来实现请求转发。
简单来说,ingress-controller才是负责具体转发的组件,通过各种方式将它暴露在集群入口,外部对集群的请求流量会先到ingress-controller,而ingress对象是用来告诉ingress-controller该如何转发请求,比如哪些域名哪些path要转发到哪些服务等等。
最粗暴的类比,ingress-controller即nginx,ingress对象即nginx.conf
ingress-controller
ingress-controller并不是k8s自带的组件,实际上ingress-controller只是一个统称,用户可以选择不同的ingress-controller实现,目前,由k8s维护的ingress-controller只有google云的GCE与ingress-nginx两个,其他还有很多第三方维护的ingress-controller,具体可以参考官方文档。但是不管哪一种ingress-controller,实现的机制都大同小异,只是在具体配置上有差异。一般来说,ingress-controller的形态都是一个pod,里面跑着daemon程序和反向代理程序。daemon负责不断监控集群的变化,根据ingress对象生成配置并应用新配置到反向代理,比如nginx-ingress就是动态生成nginx配置,动态更新upstream,并在需要的时候reload程序应用新配置。为了方便,后面的例子都以k8s官方维护的nginx-ingress为例。
ingress
ingress是一个API对象,和其他对象一样,通过yaml文件来配置。ingress通过http或https暴露集群内部service,给service提供外部URL、负载均衡、SSL/TLS能力以及基于host的方向代理。ingress要依靠ingress-controller来具体实现以上功能。上述例子的图,如果用ingress来表示,大概就是如下配置:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: abc-ingress annotations: kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/use-regex: "true" spec: tls: - hosts: - api.abc.com secretName: abc-tls rules: - host: api.abc.com http: paths: - backend: serviceName: apiserver servicePort: 80 - host: www.abc.com http: paths: - path: /image/* backend: serviceName: fileserver servicePort: 80 - host: www.abc.com http: paths: - backend: serviceName: feserver servicePort: 8080
与其他k8s对象一样,ingress配置也包含了apiVersion、kind、metadata、spec等关键字段。有几个关注的点,在spec字段中,tls用于定义https密钥、证书。rule用于指定请求路由规则。这里值得关注的是metadata.annotations字段。前面有说ingress-controller有很多不同的实现,而不同的ingress-controller就可以根据"kubernetes.io/ingress.class:"来判断要使用哪些ingress配置,同时,不同的ingress-controller也有对应的annotations配置,用于自定义一些参数。列如上面配置的'nginx.ingress.kubernetes.io/use-regex: "true"',最终是在生成nginx配置中,会采用location ~来表示正则匹配。
使用 Nginx Ingress Controller
本次实践的主要目的就是将入口统一,不再通过 service的 NodePort 或者 LoadBalancer 等方式将端口暴露出来,而是使用 Ingress 提供的反向代理负载均衡功能作为我们的唯一入口。
1.部署 Tomcat(需要被代理的服务)
部署 Tomcat 但仅允许在内网访问,我们要通过 Ingress 提供的反向代理功能路由到该 Tomcat 之上
apiVersion: apps/v1 kind: Deployment metadata: name: tomcat-app spec: replicas: 2 selector: matchLabels: name: tomcat template: metadata: labels: name: tomcat spec: containers: - name: tomcat image: tomcat ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: tomcat-http spec: ports: - port: 8080 targetPort: 8080 # ClusterIP, NodePort, LoadBalancer type: ClusterIP selector: name: tomcat
Deployment创建了tomcat的pod,副本为2个,端口为8080;再创建一个type为clusterIP的service
[root@master ingress]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES tomcat-app-585d667458-62mgb 1/1 Running 0 27s 10.244.2.3 node2 <none> <none> tomcat-app-585d667458-w5pk5 1/1 Running 0 27s 10.244.1.6 node1 <none> <none> [root@master ingress]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13d tomcat-http ClusterIP 10.101.91.247 <none> 8080/TCP 36s
2.安装 Nginx Ingress Controller
官方文档中,部署只要简单的执行一个yaml
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
mandatory.yaml这一个yaml中包含了很多资源的创建,包括namespace、ConfigMap、role,ServiceAccount等等所有部署ingress-controller需要的资源。
主要是deployment部分:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-ingress-controller namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx template: metadata: labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx annotations: prometheus.io/port: "10254" prometheus.io/scrape: "true" spec: hostNetwork: true # wait up to five minutes for the drain of connections terminationGracePeriodSeconds: 300 serviceAccountName: nginx-ingress-serviceaccount nodeSelector: kubernetes.io/os: linux containers: - name: nginx-ingress-controller image: lizhenliang/nginx-ingress-controller:0.30.0 args: - /nginx-ingress-controller - --configmap=$(POD_NAMESPACE)/nginx-configuration - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - --udp-services-configmap=$(POD_NAMESPACE)/udp-services - --publish-service=$(POD_NAMESPACE)/ingress-nginx - --annotations-prefix=nginx.ingress.kubernetes.io securityContext: allowPrivilegeEscalation: true capabilities: drop: - ALL add: - NET_BIND_SERVICE # www-data -> 101 runAsUser: 101 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace ports: - name: http containerPort: 80 protocol: TCP - name: https containerPort: 443 protocol: TCP livenessProbe: failureThreshold: 3 httpGet: path: /healthz port: 10254 scheme: HTTP
。。。。。。
这里要注意增加 hostNetwork: true,意思是开启主机网络模式,暴露 Nginx 服务端口 80与443,并在10254端口做了健康检查。
apply执行这个yaml,会创建多个资源,包括ns、configmap、role、rolebinding等等,当然最重要是一个以pod为形态的nginx:
[root@master ingress]# kubectl apply -f ingress-controller.yaml namespace/ingress-nginx created configmap/nginx-configuration created configmap/tcp-services created configmap/udp-services created serviceaccount/nginx-ingress-serviceaccount created clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created role.rbac.authorization.k8s.io/nginx-ingress-role created rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created deployment.apps/nginx-ingress-controller created limitrange/ingress-nginx created
检查下:
[root@master ingress]# kubectl get pod -n ingress-nginx -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-ingress-controller-766fb9f77-k8qvk 1/1 Running 0 8m7s 172.31.93.202 node2 <none> <none>
可以看到,nginx-controller的pod已经部署在在node2上了,172.31.93.202就是外部请求进来时实际访问的ip,到node2上看下本地端口:
[root@node2 ~]# netstat -pntl |grep nginx tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 8944/nginx: master tcp 0 0 0.0.0.0:8181 0.0.0.0:* LISTEN 8944/nginx: master tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 8944/nginx: master tcp 0 0 127.0.0.1:10245 0.0.0.0:* LISTEN 8920/nginx-ingress- tcp 0 0 127.0.0.1:10246 0.0.0.0:* LISTEN 8944/nginx: master tcp 0 0 127.0.0.1:10247 0.0.0.0:* LISTEN 8944/nginx: master tcp6 0 0 :::10254 :::* LISTEN 8920/nginx-ingress- tcp6 0 0 :::80 :::* LISTEN 8944/nginx: master tcp6 0 0 :::8181 :::* LISTEN 8944/nginx: master tcp6 0 0 :::443 :::* LISTEN 8944/nginx: master
由于配置了hostnetwork,nginx已经在node2主机本地监听80/443/8181端口。其中8181是nginx-controller默认配置的一个default backend。这样,只要访问node2主机有公网IP,就可以直接映射域名来对外网暴露服务了。
3.配置ingress资源
部署完ingress-controller,接下来就按照测试的需求来创建ingress资源:
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: nginx-web annotations: # 指定 Ingress Controller 的类型 kubernetes.io/ingress.class: "nginx" # 指定我们的 rules 的 path 可以使用正则表达式 nginx.ingress.kubernetes.io/use-regex: "true" # 连接超时时间,默认为 5s nginx.ingress.kubernetes.io/proxy-connect-timeout: "600" # 后端服务器回转数据超时时间,默认为 60s nginx.ingress.kubernetes.io/proxy-send-timeout: "600" # 后端服务器响应超时时间,默认为 60s nginx.ingress.kubernetes.io/proxy-read-timeout: "600" # 客户端上传文件,最大大小,默认为 20m nginx.ingress.kubernetes.io/proxy-body-size: "10m" # URL 重写 nginx.ingress.kubernetes.io/rewrite-target: / spec: # 路由规则 rules: # 主机名,只能是域名,修改为你自己的 - host: exa.xulan.com http: paths: / - path: backend: # 后台部署的 Service Name,与上面部署的 Tomcat 对应 serviceName: tomcat-http # 后台部署的 Service Port,与上面部署的 Tomcat 对应 servicePort: 8080
查看ingress:
[root@master ingress]# kubectl get ing NAME CLASS HOSTS ADDRESS PORTS AGE nginx-web <none> exa.xulan.com 80 17h
4.测试访问
在本地hosts文件中添加域名映射:
172.31.93.202 exa.xulan.com
然后在浏览器访问 http://exa.xulan.com/
可见实际访问到的是tomcat,这个例子中请求顺序为:ingress(exa.xulan.com:80)>> svc(ClusterIP:8080)>> pod(两个tomcat,8080)
这只是一个简单的用ingress代理svc的例子,在测试过程中使用kubectl edit svc修改svc的type,发现三种类型(ClusterIP、NodePort、LoadBalancer)都可以被ingress代理。
在实际应用中,一般使用ingress来实现通过不同域名的不同path来访问不同的后端服务,类似于这样: