行业知识
第12关 精通K8s下的Ingress-Nginx控制器:生产环境实战配置指南
------> ��程视频同步分享在今日头条和B站
大家好,我是博哥爱运维,这节课带来k8s的流量入口ingress,作为业务对外服务的公网入口,它的重要性不言而喻,大家一定要仔细阅读,跟着博哥的教程一步步实操去理解。
Ingress基本概念
在Kubernetes集群中,Ingress作为集群内服务对外暴露的访问接入点,其几乎承载着集群内服务访问的所有流量。Ingress是Kubernetes中的一个资源对象,用来管理集群外部访问集群内部服务的方式。您可以通过Ingress资源来配置不同的转发规则,从而达到根据不同的规则设置访问集群内不同的Service后端Pod。
Ingress资源仅支持配置HTTP流量的规则,无法配置一些高级特性,例如负载均衡的算法、Sessions Affinity等,这些高级特性都需要在Ingress Controller中进行配置。
Ingress Controller工作原理
为了使得Ingress资源正常工作,集群中必须要有个Ingress Controller来解析Ingress的转发规则。Ingress Controller收到请求,匹配Ingress转发规则转发到后端Service,而Service转发到Pod,最终由Pod处理请求。Kubernetes中Service、Ingress与Ingress Controller有着以下关系:
- Service是后端真实服务的抽象,一个Service可以代表多个相同的后端服务。
- Ingress是反向代理规则,用来规定HTTP/HTTPS请求应该被转发到哪个Service上。例如根据请求中不同的Host和URL路径,让请求落到不同的 Service上。
- Ingress Controller是一个反向代理程序,负责解析Ingress的反向代理规则。如果Ingress有增删改的变动,Ingress Controller会及时更新自己相应的转发规则,当Ingress Controller收到请求后就会根据这些规则将请求转发到对应的Service。
Ingress Controller通过API Server获取Ingress资源的变化,动态地生成Load Balancer(例如Nginx)所需的配置文件(例如nginx.conf),然后重新加载Load Balancer(例如执行nginx -s load重新加载Nginx。)来生成新的路由转发规则。
这节课所用到的yaml配置比较多,但我发现在头条这里发的格式会有问题,所以我另外把笔记文字部分存了一份在我的github上面,大家可以从这里面来复制yaml配置创建服务:
https://github.com/bogeit/LearnK8s/blob/main/2023/%E7%AC%AC12%E5%85%B3%20%20%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%9A%84%E6%B5%81%E9%87%8F%E5%85%A5%E5%8F%A3%E6%8E%A7%E5%88%B6%E5%99%A8Ingress-nginx-controller.md
我们上面学习了通过Service服务来访问pod资源,另外通过修改Service的类型为NodePort,然后通过一些手段作公网IP的端口映射来提供K8s集群外的访问,但这并不是一种很优雅的方式。
通常,services和Pod只能通过集群内网络访问。 所有在边界路由器上的流量都被丢弃或转发到别处。 从概念上讲,这可能看起来像: internet | ------------ [ Services ]
另外可以我们通过LoadBalancer负载均衡来提供外部流量的的访问,但这种模式对于实际生产来说,用起来不是很方便,而且用这种模式就意味着每个服务都需要有自己的的负载均衡器以及独立的公有IP。
我们这是用Ingress,因为Ingress只需要一个公网IP就能为K8s上所有的服务提供访问,Ingress工作在7层(HTTP),Ingress会根据请求的主机名以及路径来决定把请求转发到相应的服务,如下图所示:
Ingress是允许入站连接到达集群服务的一组规则。即介于物理网络和群集svc之间的一组转发规则。 其实就是实现L4 L7的负载均衡:注意:这里的Ingress并非将外部流量通过Service来转发到服务pod上,而只是通过Service来找到对应的Endpoint来发现pod进行转发 internet | [ Ingress ] ---> [ Services ] ---> [ Endpoint ] --|-----|-- | [ Pod,pod,...... ]} serviceAccount: ingress-nginx serviceAccountName: ingress-nginx terminationGracePeriodSeconds: 300 # kubectl taint nodes xx.xx.xx.xx boge/ingress-controller-ready="true":NoExecute # kubectl taint nodes xx.xx.xx.xx boge/ingress-controller-ready:NoExecute- tolerations: - operator: Exists# tolerations:# - effect: NoExecute# key: boge/ingress-controller-ready# operator: Equal# value: "true" volumes: - name: webhook-cert secret: defaultMode: 420 secretName: ingress-nginx-admission - hostPath: path: /etc/localtime type: File name: localtime---apiVersion: networking.k8s.io/v1kind: IngressClassmetadata: name: nginx-master namespace: kube-system annotations: ingressclass.kubernetes.io/is-default-class: "true"spec: controller: k8s.io/ingress-nginx---apiVersion: admissionregistration.k8s.io/v1kind: ValidatingWebhookConfigurationmetadata: labels: name: ingress-nginx name: ingress-nginx-admissionwebhooks: - name: validate.nginx.ingress.kubernetes.io matchPolicy: Equivalent rules: - apiGroups: - networking.k8s.io apiVersions: - v1 operations: - CREATE - UPDATE resources: - ingresses failurePolicy: Fail sideEffects: None admissionReviewVersions: - v1 clientConfig: service: namespace: kube-system name: ingress-nginx-controller-admission path: /networking/v1/ingresses---apiVersion: v1kind: ServiceAccountmetadata: name: ingress-nginx-admission namespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: name: ingress-nginx-admissionrules: - apiGroups: - admissionregistration.k8s.io resources: - validatingwebhookconfigurations verbs: - get - update---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: ingress-nginx-admissionroleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: ingress-nginx-admissionsubjects: - kind: ServiceAccount name: ingress-nginx-admission namespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: ingress-nginx-admission namespace: kube-systemrules: - apiGroups: - "" resources: - secrets verbs: - get - create---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: ingress-nginx-admission namespace: kube-systemroleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ingress-nginx-admissionsubjects: - kind: ServiceAccount name: ingress-nginx-admission namespace: kube-system---apiVersion: batch/v1kind: Jobmetadata: name: ingress-nginx-admission-create labels: name: ingress-nginx namespace: kube-systemspec: template: metadata: name: ingress-nginx-admission-create labels: name: ingress-nginx spec: containers: - name: create# image: registry-vpc.cn-hangzhou.aliyuncs.com/acs/kube-webhook-certgen:v1.1.1 image: registry.cn-beijing.aliyuncs.com/acs/kube-webhook-certgen:v1.5.1 imagePullPolicy: IfNotPresent args: - create - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc - --namespace=$(POD_NAMESPACE) - --secret-name=ingress-nginx-admission env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace restartPolicy: OnFailure serviceAccountName: ingress-nginx-admission securityContext: runAsNonRoot: true runAsUser: 2000---apiVersion: batch/v1kind: Jobmetadata: name: ingress-nginx-admission-patch labels: name: ingress-nginx namespace: kube-systemspec: template: metadata: name: ingress-nginx-admission-patch labels: name: ingress-nginx spec: containers: - name: patch image: registry.cn-hangzhou.aliyuncs.com/acs/kube-webhook-certgen:v1.1.1 # if error use this image imagePullPolicy: IfNotPresent args: - patch - --webhook-name=ingress-nginx-admission - --namespace=$(POD_NAMESPACE) - --patch-mutating=false - --secret-name=ingress-nginx-admission - --patch-failure-policy=Fail env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace securityContext: allowPrivilegeEscalation: false restartPolicy: OnFailure serviceAccountName: ingress-nginx-admission securityContext: runAsNonRoot: true runAsUser: 2000---## Deployment need for aliyun'k8s:#apiVersion: v1#kind: Service#metadata:# annotations:# service.beta.kubernetes.io/alibaba-cloud-loadbalancer-id: "lb-xxxxxxxxxxxxxxxxx"# service.beta.kubernetes.io/alibaba-cloud-loadbalancer-force-override-listeners: "true"# labels:# app: nginx-ingress-lb# name: nginx-ingress-lb-boge# namespace: kube-system#spec:# externalTrafficPolicy: Local# ports:# - name: http# port: 80# protocol: TCP# targetPort: 80# - name: https# port: 443# protocol: TCP# targetPort: 443# selector:# app: ingress-nginx# type: LoadBalancer
h6DaemonSet-00:49:30/h6 p开始部署:/p pre # kubectl apply -f aliyun-ingress-nginx.yaml namespace/ingress-nginx createdserviceaccount/nginx-ingress-controller createdclusterrole.rbac.authorization.k8s.io/nginx-ingress-controller createdclusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-controller createdservice/nginx-ingress-lb createdconfigmap/nginx-configuration createdconfigmap/tcp-services createdconfigmap/udp-services createddaemonset.apps/nginx-ingress-controller created# 这里是以daemonset资源的形式进行的安装# DaemonSet资源和Deployment的yaml配置类似,但不同的是Deployment可以在每个node上运行多个pod副本,但daemonset在每个node上只能运行一个pod副本# 这里正好就借运行ingress-nginx的情况下,把daemonset这个资源做下讲解# 我们查看下pod,会发现空空如也,为什么会这样呢?# kubectl -n kube-system get pod注意上面的yaml配置里面,我使用了节点选择配置,只有打了我指定lable标签的node节点,也会被允许调度pod上去运行 nodeSelector: boge/ingress-controller-ready: "true"# 我们现在来打标签# kubectl label node 10.0.1.201 boge/ingress-controller-ready=truenode/10.0.1.201 labeled# kubectl label node 10.0.1.202 boge/ingress-controller-ready=truenode/10.0.1.202 labeled# 接着可以看到pod就被调试到这两台node上启动了# kubectl -n kube-system get pod -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESnginx-ingress-controller-lchgr 1/1 Running 0 9m1s 10.0.1.202 10.0.1.202 none nonenginx-ingress-controller-x87rp 1/1 Running 0 9m6s 10.0.1.201 10.0.1.201 none none/pre p我们基于前面学到的deployment和service,来创建一个nginx的相应服务资源,保存为nginx.yaml:/p blockquote p注意:记得把前面测试的资源删除掉,以防冲突/p /blockquote pre ---kind: ServiceapiVersion: v1metadata: name: new-nginxspec: selector: app: new-nginx ports: - name: http-port port: 80 protocol: TCP targetPort: 80---# 新版本k8s的ingress配置apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: new-nginx annotations: kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/force-ssl-redirect: "false" nginx.ingress.kubernetes.io/whitelist-source-range: 0.0.0.0/0 nginx.ingress.kubernetes.io/configuration-snippet: | if ($host != 'www.boge.com' ) { rewrite ^ http://www.boge.com$request_uri permanent; }spec: rules: - host: boge.com http: paths: - backend: service: name: new-nginx port: number: 80 path: / pathType: Prefix - host: m.boge.com http: paths: - backend: service: name: new-nginx port: number: 80 path: / pathType: Prefix - host: www.boge.com http: paths: - backend: service: name: new-nginx port: number: 80 path: / pathType: Prefix# tls:# - hosts:# - boge.com# - m.boge.com# - www.boge.com# secretName: boge-com-tls# tls secret create command:# kubectl -n namespace create secret tls boge-com-tls --key boge-com.key --cert boge-com.csr---apiVersion: apps/v1kind: Deploymentmetadata: name: new-nginx labels: app: new-nginxspec: replicas: 3 # 数量可以根据NODE节点数量来定义 selector: matchLabels: app: new-nginx template: metadata: labels: app: new-nginx spec: containers:#-------------------------------------------------- - name: new-nginx image: nginx:1.21.6 env: - name: TZ value: Asia/Shanghai ports: - containerPort: 80 volumeMounts: - name: html-files mountPath: "/usr/share/nginx/html"#-------------------------------------------------- - name: busybox image: registry.cn-hangzhou.aliyuncs.com/acs/busybox:v1.29.2 args: - /bin/sh - -c - > while :; do if [ -f /html/index.html ];then echo "[$(date +%F\ %T)] ${MY_POD_NAMESPACE}-${MY_POD_NAME}-${MY_POD_IP}" > /html/index.html sleep 1 else touch /html/index.html fi done env: - name: TZ value: Asia/Shanghai - name: MY_POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name - name: MY_POD_NAMESPACE valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.namespace - name: MY_POD_IP valueFrom: fieldRef: apiVersion: v1 fieldPath: status.podIP volumeMounts: - name: html-files mountPath: "/html" - mountPath: /etc/localtime name: tz-config#-------------------------------------------------- volumes: - name: html-files emptyDir: medium: Memory sizeLimit: 10Mi - name: tz-config hostPath: path: /usr/share/zoneinfo/Asia/Shanghai---运行它:
# kubectl apply -f nginx.yaml #查看创建的ingress资源# kubectl get ingressNAME CLASS HOSTS ADDRESS PORTS AGEnew-nginx nginx-master boge.com,m.boge.com,www.boge.com 80 8s# 我们在其它节点上,加下本地hosts,来测试下效果10.0.1.201 boge.com m.boge.com www.boge.com# 可以看到请求成功了[root@node-2 ~]# curl www.boge.com# 回到201节点上,看下ingress-nginx的日志# kubectl -n kube-system logs --tail=1 nginx-ingress-controller-nblb9Defaulted container "nginx-ingress-controller" out of: nginx-ingress-controller, init-sysctl (init){"@timestamp": "2023-11-22T22:13:14+08:00","remote_addr": "10.0.1.1","x-forward-for": "10.0.1.1","request_id": "f21a1e569751fb55299ef5f1b039852d","remote_user": "-","bytes_sent": 250,"request_time": 0.003,"status": 200,"vhost": "www.boge.com","request_proto": "HTTP/2.0","path": "/","request_query": "-","request_length": 439,"duration": 0.003,"method": "GET","http_referrer": "-","http_user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36","upstream-sever":"default-new-nginx-80","proxy_alternative_upstream_name":"","upstream_addr":"172.20.247.18:80","upstream_response_length":71,"upstream_response_time":0.003,"upstream_status":200}因为http属于是明文传输数据不安全,在生产中我们通常会配置https加密通信,现在实战下Ingress的tls配置
# 这里我先自签一个https的证书#1. 先生成私钥key# openssl genrsa -out boge.key 2048Generating RSA private key, 2048 bit long modulus..............................................................................................+++.....+++e is 65537 (0x10001)#2.再基于key生成tls证书(注意:这里我用的*.boge.com,这是生成泛域名的证书,后面所有新增加的三级域名都是可以用这个证书的)# openssl req -new -x509 -key boge.key -out boge.csr -days 360 -subj /CN=*.boge.com# 看下创建结果# lltotal 8-rw-r--r-- 1 root root 1099 Nov 27 11:44 boge.csr-rw-r--r-- 1 root root 1679 Nov 27 11:43 boge.key
在生产环境中,如果是自建机房,我们通常会在至少2台node节点上运行有ingress-nginx的pod,那么有必要在这两台node上面部署负载均衡软件做调度,来起到高可用的作用,这里我们用haproxy+keepalived,如果你的生产环境是在云上,假设是阿里云,那么你只需要购买一个负载均衡器SLB,将运行有ingress-nginx的pod的节点服务器加到这个SLB的后端来,然后将请求域名和这个SLB的公网IP做好解析即可,目前我们用二进制部署的K8s集群通信架构如下:
注意在每台node节点上有已经部署有了个精简版的nginx软件kube-lb做四层负载均衡,来转发apiserver的请求的,那么,我们只需要选取两台节点,部署keepalived软件并重新配置kube-lb,来生成VIP达到ha的效果,具体参照文档部署:
https://github.com/easzlab/kubeasz/blob/master/docs/setup/ex-lb.md
做到这里,是不是有点成就感了呢,在已经知道了ingress能给我们带来什么后,我们回过头来理解Ingress的工作原理,这样掌握ingress会更加稳固,这也是我平时学习的方法
如下图,Client客户端对nginx.boge.com进行DNS查询,DNS服务器(我们这里是配的本地hosts)返回了Ingress控制器的IP(也就是我们的VIP:10.0.1.222)。然后Client客户端向Ingress控制器发送HTTP请求,并在请求Host头中指定nginx.boge.com。Ingress控制器从该头部确定Client客户端是想访问哪个服务,通过与该服务并联的Endpoint对象查看具体的Pod IP,并将Client客户端的请求转发给其中一个pod。
生产环境正常情况下大部分是一个Ingress对应一个Service服务,但在一些特殊情况,需要复用一个Ingress来访问多个服务的,下面我们来实践下
再创建一个nginx的deployment和service,注意名称修改下不要冲突了
# Finally you need excute this command:# kubectl create deployment old-nginx --image=nginx:1.21.6 --replicas=1# kubectl expose deployment old-nginx --port=80 --target-port=80# if all done,you need test# curl -H "Host: www.boge.com" -H "foo: bar" http://10.0.0.201# curl -H "Host: www.boge.com" http://10.0.0.201# 1 pod 2 containers and ingress-nginx L7 + 2 service---# namespaceapiVersion: v1kind: Namespacemetadata: name: test-nginx---# SVCkind: ServiceapiVersion: v1metadata: name: new-nginx namespace: test-nginxspec: selector: app: new-nginx ports: - name: http-port port: 80 protocol: TCP targetPort: 80# nodePort: 30088# type: NodePort---# ingress-nginx L7# https://yq.aliyun.com/articles/594019# https://help.aliyun.com/document_detail/200941.html?spm=a2c4g.11186623.6.787.254168fapBIi0A# KAE多个请求参数配置: query("boge_id", /^aaa$|^bbb$/)apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: test-nginx namespace: test-nginx annotations: nginx.ingress.kubernetes.io/ssl-redirect: "false" # 请求参数匹配 curl www.boge.com?boge_id=ID1 nginx.ingress.kubernetes.io/service-match: | new-nginx: query("boge_id", /^aaa$|^bbb$/) # 请求头中满足正则匹配foo=bar的请求才会被路由到新版本服务new-nginx中 #nginx.ingress.kubernetes.io/service-match: | # new-nginx: header("foo", /^bar$/) # 在满足上述匹配规则的基础上仅允许50%的流量会被路由到新版本服务new-nginx中 #nginx.ingress.kubernetes.io/service-weight: | # new-nginx: 50, old-nginx: 50spec: rules: - host: www.boge.com http: paths: - backend: service: name: new-nginx # 新版本服务 port: number: 80 path: / pathType: Prefix - backend: service: name: old-nginx # 老版本服务 port: number: 80 path: / pathType: Prefix---# deploymentapiVersion: apps/v1kind: Deploymentmetadata: name: new-nginx namespace: test-nginx labels: app: new-nginxspec: replicas: 1 selector: matchLabels: app: new-nginx template: metadata: labels: app: new-nginx spec: containers:#-------------------------------------------------- - name: new-nginx image: nginx::1.21.6 ports: - containerPort: 80 volumeMounts: - name: html-files mountPath: "/usr/share/nginx/html"#-------------------------------------------------- - name: busybox image: registry.cn-hangzhou.aliyuncs.com/acs/busybox:v1.29.2 args: - /bin/sh - -c - > while :; do if [ -f /html/index.html ];then echo "[$(date +%F\ %T)] hello" > /html/index.html sleep 1 else touch /html/index.html fi done volumeMounts: - name: html-files mountPath: "/html"#-------------------------------------------------- volumes: - name: html-files # Disk of the working node running pod #emptyDir: {} # Memory of the working node running pod emptyDir: medium: Memory # use temp disk space #medium: "" # if emptyDir file size more than 1Gi ,then this pod will be Evicted, just like restert pod sizeLimit: 1Gi
下一篇:网易版服务器地图怎么换?