k8s之Ingress和Ingress Controller

线上k8s使用nginx所在的边缘节点来将外部访问导流到集群内部容器,ingress-nginx-controller是k8s众多ingress controller实现中的一种,以agent+nginx的方式提供服务。agent通过watch k8s的ingress、configmap、endpoint等资源的变化,修改nginx的配置文件,并负责nginx的reload等工作。
在Kubernetes中,服务和Pod的IP地址仅可以在集群网络内部使用,对于集群外的应用是不可见的。
为了使外部的应用能够访问集群内的服务,在Kubernetes 目前 提供了以下几种方案:
  • NodePort
  • LoadBalancer
  • Ingress
1、Ingress 组成
ingress controller
将新加入的Ingress转化成Nginx的配置文件并使之生效
ingress服务
将Nginx的配置抽象成一个Ingress对象,每添加一个新的服务只需写一个新的Ingress的yaml文件即可
Nginx:实现负载均衡到pod的集合
Ingress Controller:从集群api获取services对应pod的ip到nginx配置文件中
Ingress:为nginx创建虚拟主机
2、Ingress 工作原理
  • ingress controller通过和kubernetes api交互,动态的去感知集群中ingress规则变化
  • 然后读取它,按照自定义的规则,规则就是写明了哪个域名对应哪个service或者单个域名多个location形式,生成一段nginx配置
  • 再动态注入到nginx-ingress-control的pod里,这个Ingress controller的pod里运行着一个Nginx服务,控制器会把生成的nginx配置写入/etc/nginx/nginx.conf文件中
  • 然后reload一下使配置生效。
以此达到域名分配置和动态更新的问题
3、Ingress可以解决什么问题?
动态配置服务
如果按照传统方式, 当新增加一个服务时, 我们可能需要在流量入口加一个反向代理指向我们新的k8s服务. 而如果用了Ingress, 只需要配置好这个服务, 当服务启动时, 会自动注册到Ingress的中, 不需要而外的操作.
减少不必要的端口暴露
配置过k8s的都清楚, 第一步是要关闭防火墙的, 主要原因是k8s的很多服务会以NodePort方式映射出去, 这样就相当于给宿主机打了很多孔, 既不安全也不优雅. 而Ingress可以避免这个问题, 除了Ingress自身服务可能需要映射出去, 其他服务都不要用NodePort方式
Service
模型:userspace,iptables,ipvs
Service四种类型:
ClusterIP: 只能给pod内部访问,无法接入集群边界外部的流量。
NodePort:client---->NodeIP:NodePort--->ClusterIP:ServicePort-->PodIP:ccontainerPort
LoadBalancer: 在公有云环境中用LB,在NodeIP:NodePort前,将外部流量接入到k8s集群
ExternelName: 内部的DNS服务的FQDN格式的名称,是CNAME别名记录,指向真正的供外部用户直接访问的FQDN服务的域名。
No ClusterIP:Headless Service(无头服务)。将ServiceName直接解析到PodIP。原本是将ServiceName解析到ClusterIP上。与service的区别就是它没有Cluster IP,解析它的名称时将返回该Headless Service对应的全部Pod的Endpoint列表。
四层代理缺陷:不支持HTTPS,Service也是支持支四层。LVS工作在四层,https工作在七层,如果后端Pod需要支持https,则应该将https配置在能支持https的机器上,也就是后端服务器上。但如果后端内网是安全的。可以在前端调度器上使用https,后端内部使用http。所以需要更换调度方式。不使用iptables或者ipvs,使用ingress
Ingress资源:作为一个独立运行的一个或者一组pod资源,通常就是一个应用程序,拥有七层代理能力和调度能力的应用程序。
定义Ingress就是定义期望Ingress Controller如何给我们建一个前端,以nginx为例。它相当于nginx的虚拟主机Server,可能是location的url映射,同时可能是定义一个后端upstream server。upstream server里面有多少主机,ingress是通过service得到的。Ingress可以直接通过边界注入到Ingress Controller,并保存成配置文件。一旦Ingress发现service选定的后端pod资源改变了,这个改变就会及时反映到ingress,ingress就会及时注入到前端调度器的pod中,也就是注入到upstream的配置文件里面。并且触发pod中的容器重载配置文件。
1、service对后端pod资源分类
2、ingress基于分类识别出有几个pod以及pod的ip地址是什么
3、将ip地址生成配置信息注入到Ingress Controller,然后出发重载配置
想使用https七层代理,就要部署一个Ingress Controller的pod,他作为一个服务被部署。根据需求配置前端,以nginx为例,比如需求是根据虚拟主机server(多个域名),或者location的url匹配来定义前端。根据service收集到的后端pod的IP定义upstream server,然后将这些信息反映给ingress,ingress将信息动态注入到 Ingress Controller。
traefik,Envoy 为了微服务而生。可以监控配置文件发生变化,变化了以后就自动重载配置。支持背后的端点动态的发生变化,发生变换了就自动重载。
外部SLB请求调度到NodePort的service上,然后调度到内部pod Ingress Controller上,根据虚拟主机或者url,Ingress Controller根据Ingress中的定义(虚拟主机或者url),根据每一组主机名或者url调用到不同的后端每一组service上。
Service ingress-nginx是帮Ingress Controller 接入集群外部流量使用。这个Service可以不需要,可以将这个pod换成共享节点网络命名空间的方式,也就是共享宿主机的NodePort。将他定义成DaemonSet的方式运行在特定的节点上。在pod的定义中加上hostNetwork
Service site1和Service site2是给后端pod分组的,而不被调度时使用
查看ingress:

[root@k8s-master yaml]# kubectl explain ingress
部署配置Ingress
ingress的部署,需要考虑两个方面:
  • ingress-controller是作为pod来运行的,以什么方式部署比较好
  • ingress解决了把如何请求路由到集群内部,那它自己怎么暴露给外部比较好
下面列举一些目前常见的部署和暴露方式,具体使用哪种方式还是得根据实际需求来考虑决定
1、Deployment+LoadBalancer模式的Service
如果要把ingress部署在公有云,那用这种方式比较合适。用Deployment部署ingress-controller,创建一个type为LoadBalancer的service关联这组pod。大部分公有云,都会为LoadBalancer的service自动创建一个负载均衡器,通常还绑定了公网地址。只要把域名解析指向该地址,就实现了集群服务的对外暴露。
2、Deployment+NodePort模式的Service
同样用deployment模式部署ingress-controller,并创建对应的服务,但是type为NodePort。这样,ingress就会暴露在集群节点ip的特定端口上。由于nodeport暴露的端口是随机端口,所以测试的时候可以手动增加type: Nodeport,手动指定端口或者一般会在前面再搭建一套负载均衡器来转发请求。该方式一般用于宿主机是相对固定的环境ip地址不变的场景。
nodePort的部署思路就是通过在每个节点上开辟nodePort的端口,将流量引入进来,而后通过iptables首先转发到ingress-controller容器中(图中的nginx容器),而后由nginx根据ingress的规则进行判断,将其转发到对应的应用web容器中。NodePort方式暴露ingress虽然简单方便,但是NodePort多了一层NAT,在请求量级很大时可能对性能会有一定影响。
3、DaemonSet+HostNetwork+nodeSelector
hostNetwork模式不再需要创建一个nodePort的svc,而是直接在每个节点都创建一个ingress-controller的容器,而且将该容器的网络模式设为hostNetwork。也就是说每个节点物理机的80和443端口将会被ingress-controller中的nginx容器占用。当流量通过80/443端口进入时,将直接进入到nginx中。而后nginx根据ingress规则再将流量转发到对应的web应用容器中。使用hostNetwork的方式,ingress-controller将会使用的是物理机的DNS域名解析(即物理机的/etc/resolv.conf)。而无法使用内部的比如coredns的域名解析
用DaemonSet结合nodeselector来部署ingress-controller到特定的node上,然后使用HostNetwork直接把该pod与宿主机node的网络打通,直接使用宿主机的80/433端口就能访问服务。这时,ingress-controller所在的node机器就很类似传统架构的边缘节点,比如机房入口的nginx服务器。该方式整个请求链路最简单,性能相对NodePort模式更好。缺点是由于直接利用宿主机节点的网络和端口,一个node只能部署一个ingress-controller pod。比较适合大并发的生产环境使用。
github提供了两种方式下载ingress部署文件 :
  • 默认下载最新的yaml
  • 指定版本号下载对应的yaml
官方部署文件地址长期更新:https://kubernetes.github.io/ingress-nginx/deploy/

Deployment+NodePort模式的service
一、下载nginx-ingress的部署配置文件
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
将镜像拉下来并更改mandatory.yaml中的镜像地址或者去阿里云公开镜像找相关镜像替换
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1
替换镜像地址
sed -i 's#quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1#registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1#g' mandatory.yaml
二、下载部署service-nodeport文件用于对外提供服务

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/baremetal/service-nodeport.yaml
修改service文件,指定一下nodePort,使用30080端口和30443端口作为nodePort。因为nodePort端口必须在30000以上,更改完如下所示:

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort  # 指定端口类型
  ports:
    - name: http
      port: 80
      targetPort: 80
      nodePort: 30080  # 新增30080端口,http访问时需要加上该端口
      protocol: TCP
    - name: https
      port: 443
      targetPort: 443
      nodePort: 30443  # 新增30443端口来对外映射,https访问时需要加上该端口
      protocol: TCP
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
三、部署一个tomcat用于测试ingress转发功能

# cat tomcat-svc-deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: tomcat
  namespace: ingress-nginx
spec:
  selector:
   app: tomcat
   release: canary
  ports:
  - name: http
    targetPort: 8080  # 容器port
    port: 8080        # service port
  - name: ajp
    targetPort: 8009
    port: 8009
 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-deploy
  namespace: ingress-nginx
spec:
  replicas: 1
  selector:
   matchLabels:
     app: tomcat
     release: canary
  template:
   metadata:
     labels:
       app: tomcat
       release: canary
   spec:
     containers:
     - name: tomcat
       image: tomcat
       ports:
       - name: http
         containerPort: 8080
四、定义ingress策略

# cat ingress-tomcat.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-tomcat
  namespace: ingress-nginx
  annotations:
    kubernetes.io/ingress.class: "nginx"
    # nginx.ingress.kubernetes.io/rewrite-target: /  # 重写规则
   # nginx.ingress.kubernetes.io/use-regex: "true"  # 开启use-regex启动正则path匹配
spec:
  rules:
  - host: tomcat.ingress.com   # 对应的域名 
    http:
      paths:
      - path:     # url上下文
        backend:  # 后向转发,到对应的 serviceName:servicePort
          serviceName: tomcat
          servicePort: 8080
五、本地做hosts解析
将tomcat.ingress.com域名在本地做hosts解析,解析的ip为ingress-controller这个pod所在的node机器外网地址,然后浏览器访问:http://tomcat.ingress.com:30080

成功访问 到tomcat界面表示deployment+nodeport方式的ingress已经成功
DaemonSet+HostNetwork+nodeSelector
更改 mandatory.yaml 文件中deployment部分:
修改参数如下:
  • kind: Deployment #修改为DaemonSet
  • replicas: 1 #注销此行,DaemonSet不需要此参数
  • hostNetwork: true #添加该字段让docker使用物理机网络,在物理机暴露服务端口(80),注意物理机80端口提前不能被占用
  • dnsPolicy: ClusterFirstWithHostNet #使用hostNetwork后容器会使用物理机网络包括DNS,会无法解析内部service,使用此参数让容器使用K8S的DNS。当前测试没添加。
  • nodeSelector:isingress: "true" #添加节点标签
  • tolerations: 添加对指定节点容忍度。当前测试没添加
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
# 注释Replicas
# 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:
      serviceAccountName: nginx-ingress-serviceaccount
      # 选择对应标签的node
      nodeSelector:
        isIngress: "true"
      # 使用hostNetwork暴露服务
      hostNetwork: true
      containers:
        - name: nginx-ingress-controller
          image: registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1
...
给节点打标签,让ingress-controller以ds的方式跑在这台机器上

[root@k8s-master ingrress-nginx]# kubectl label node 192.168.2.220 isIngress=true
[root@k8s-master ingrress-nginx]# kubectl get node -l isIngress=true
NAME            STATUS   ROLES   AGE   VERSION
192.168.2.220   Ready    node    76d   v1.15.0
用修改完成后的mandatory.yaml文件部署ingress-controller控制器

[root@k8s-master ingrress-nginx]# kubectl apply -f mandatory.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
daemonset.extensions/nginx-ingress-controller created
[root@k8s-master ingress-daemonset]# kubectl get ds -n ingress-nginx
NAME                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR    AGE
nginx-ingress-controller   1         1         1       1            1           isIngress=true   6m8s
[root@k8s-master ingress-daemonset]# kubectl get po -n ingress-nginx -o wide
NAME                             READY   STATUS    RESTARTS   AGE    IP              NODE            NOMINATED NODE   READINESS GATES
nginx-ingress-controller-d52lg   1/1     Running   0          2m1s   192.168.2.220   192.168.2.220   <none>           <none>
可以看到,nginx-controller的pod已经部署在在192.168.2.220这个节点上了

暴露nginx-controller
到192.168.2.220上看下本地端口:

[root@k8s-node01 ~]# netstat -lntup | grep nginx
tcp        0      0 127.0.0.1:10246         0.0.0.0:*               LISTEN      95817/nginx: master 
tcp        0      0 127.0.0.1:10247         0.0.0.0:*               LISTEN      95817/nginx: master 
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      95817/nginx: master 
tcp        0      0 0.0.0.0:8181            0.0.0.0:*               LISTEN      95817/nginx: master 
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      95817/nginx: master 
tcp        0      0 127.0.0.1:10245         0.0.0.0:*               LISTEN      95782/nginx-ingress 
tcp6       0      0 :::10254                :::*                    LISTEN      95782/nginx-ingress 
tcp6       0      0 :::80                   :::*                    LISTEN      95817/nginx: master 
tcp6       0      0 :::8181                 :::*                    LISTEN      95817/nginx: master 
tcp6       0      0 :::443                  :::*                    LISTEN      95817/nginx: master
由于配置了hostnetwork,nginx已经在node主机本地监听80/443/8181端口。其中8181是nginx-controller默认配置的一个default backend。这样,只要访问node主机有公网IP,就可以直接映射域名来对外网暴露服务了。如果要nginx高可用的话,可以在多个node上部署,并在前面再搭建一套LVS+keepalive做负载均衡。用hostnetwork的另一个好处是,如果lvs用DR模式的话,是不支持端口映射的,这时候如果用nodeport,暴露非标准的端口,管理起来会很麻烦。
配置ingress资源
创建一个tomcat的deployment应用用来测试ingress转发功能。
因为我们创建的ingress-controller采用的时hostnetwork模式,所以无需在创建nodePort形式的ingress-svc服务来把端口映射到节点主机上。即svc文件中不需要指定nodePort

# cat tomcat-svc-deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: tomcat
  namespace: ingress-nginx
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: ingress-nginx
spec:
  replicas: 1
  selector: 
    matchLabels:
      app: tomcat
      release: canary
  template:
    metadata:
      labels:
        app: tomcat
        release: canary
    spec:
      containers:
      - name: tomcat
        image: tomcat
        ports:
        - name: httpd
          containerPort: 8080
定义ingress策略,通过Ingress把tomcat发布出去

# cat ingress-tomcat.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-tomcat
  namespace: ingress-nginx
  annotations:
    kubernetes.io/ingress.class: "nginx"
    # nginx.ingress.kubernetes.io/rewrite-target: /  # 重写规则
   # nginx.ingress.kubernetes.io/ssl-redirect: "true" # 设置是否强制跳转
   # nginx.ingress.kubernetes.io/use-regex: "true"  # 开启use-regex启动正则path匹配
spec:
  rules:
  - host: tomcat.ingress.com
    http:
      paths:
      - path:
        backend:
          serviceName: tomcat  # 指定需要绑定暴露的svc名称
          servicePort: 8080
部署tomcat应用的svc和deployment,并且部署ingress规则

kubectl apply -f tomcat-svc-deployment.yaml
kubectl apply -f ingress-tomcat.yaml

[root@k8s-master ingress-daemonset]# kubectl get ds,po -n ingress-nginx
NAME                                            DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR    AGE
daemonset.extensions/nginx-ingress-controller   1         1         1       1            1           isIngress=true   10m

NAME                                 READY   STATUS    RESTARTS   AGE
pod/nginx-ingress-controller-d52lg   1/1     Running   0          5m53s
pod/tomcat-deploy-69d84cbf7c-qxnnd   1/1     Running   0          83s

[root@k8s-master ingress-daemonset]# kubectl get ing -n ingress-nginx
NAME             HOSTS                ADDRESS   PORTS   AGE
ingress-tomcat   tomcat.ingress.com             80      37s
最后浏览器直接通过域名访问,不加任何端口。成功访问到tomcat界面表示ingress+DaemonSet+nodeSelector方式部署成功

生产环境中,建议把ingress通过DaemonSet的方式部署集群中,而且该节点打上污点不允许业务pod进行调度,以避免业务应用与Ingress服务发生资源争抢。然后通过SLB把ingress节点主机添为后端服务器,进行流量转发

配置ingress-nginx使用https
生成ingress-nginx的tls类型secret创建命令:

kubectl create secret tls ingress-https --key xxxx.key --cert xxxx.pem <-n namespace>
一、制作自签证书
如果是阿里云上购买的域名,则可以申请一个免费的证书与之绑定,然后把证书下载下来再用来创建

[root@k8s-master ingress-daemonset]# openssl genrsa -out tls.key 2048
[root@k8s-master ingress-daemonset]# openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=HangZhou/L=ZheJiang/O=devops/CN=tomcat.ingress.com
这时候会生成两个文件:tls.crt和tls.key
第二步:利用这两个文件创建secret

[root@k8s-master ingress-daemonset]# kubectl create secret tls tomcat-ingress-https --cert=tls.crt --key=tls.key -n ingress-nginx
secret/tomcat-ingress-https created
[root@k8s-master ingress-daemonset]# kubectl get secret -n ingress-nginx
NAME                                       TYPE                                  DATA   AGE
default-token-8465p                        kubernetes.io/service-account-token   3      17h
nginx-ingress-serviceaccount-token-k2j27   kubernetes.io/service-account-token   3      17h
tomcat-ingress-https                       kubernetes.io/tls                     2      6s
第三步:修改ingress策略,发布暴露tomcat应用添加tls部分

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-tomcat
  namespace: ingress-nginx
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  tls:  # 添加tls
  - hosts:
    - tomcat.ingress.com  # 指定证书绑定的域名
    secretName: tomcat-ingress-https  # 指定刚才创建的secret名称
  rules:
  - host: tomcat.ingress.com
    http:
      paths:
      - path:
        backend:
          serviceName: tomcat
          servicePort: 8080
更新原来ingress发布策略

[root@k8s-master ingress-daemonset]# kubectl apply -f ingress-tomcat.yaml 
ingress.extensions/ingress-tomcat configured

web浏览器使用https访问测试:https://tomcat.ingress.com。

由于因为自制证书,所以这个是正常的。如果自制证书不想看到不安全,可以设置浏览器信任该证书。正常访问,则表示部署https方式的ingress成功

博客地址:https://www.ayunw.cn

公众号:竹下侯小姜运维

 

版权声明:
作者:allenjol
链接:https://www.ayunw.cn/archives/627
来源:爱生活,爱运维
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>