【K8S专栏】Kubernetes应用访问管理

微信公众号:运维开发故事,作者:乔克

在Kubernetes中,如果仅仅是单纯的部署Pod,部署Deployment,并没有任何意义,因为我们最终的目的是要为应用和用户提供服务。

在Kubernetes中,提供了Service和Ingress两种对象来实现应用间访问或外部对集群应用访问,这两种对象在实际的工作中会时长使用,非常重要的对象。

Service

对于kubernetes整个集群来说,Pod的地址也可变的,也就是说如果一个Pod因为某些原因退出了,而由于其设置了副本数replicas大于1,那么该Pod就会在集群的任意节点重新启动,这个重新启动的Pod的IP地址与原IP地址不同,这对于业务来说,就不能根据Pod的IP作为业务调度。kubernetes就引入了Service的概念,它为Pod提供一个入口,主要通过Labels标签来选择后端Pod,这时候不论后端Pod的IP地址如何变更,只要Pod的Labels标签没变,那么 业务通过service调度就不会存在问题。

当声明Service的时候,会自动生成一个cluster IP,这个IP是虚拟IP。我们就可以通过这个IP来访问后端的Pod,当然,如果集群配置了DNS服务,比如现在的CoreDNS,那么也可以通过Service的名字来访问,它会通过DNS自动解析Service的IP地址。

Service的类型有4种,Cluster IP,LoadBalance,NodePort,ExternalName。其中Cluster IP是默认的类型。(1)、Cluster IP:通过 集群内部IP暴露服务,默认是这个类型,选择该值,这个Service服务只能通过集群内部访问;(2)、LoadBalance:使用云提供商的负载均衡器,可以向外部暴露服务,选择该值,外部的负载均衡器可以路由到NodePort服务和Cluster IP服务;(3)、NodePort:顾名思义是Node基本的Port,如果选择该值,这个Service可以通过NodeIP:NodePort访问这个Service服务,NodePort会路由到Cluster IP服务,这个Cluster IP会通过请求自动创建;(4)、ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容,没有任何类型代理被创建,可以用于访问集群内其他没有Labels的Pod,也可以访问其他NameSpace里的Service。

kubernetes主要通过kube-proxy创建iptables和ipvs规则,在每个Node节点上都会创建这些规则。

ClusterIP

ClusterIP是Service默认的类型,只能在集群的内部访问,也是工作中最常用的一个类型,定义如下:

apiVersion: v1kind: Servicemetadata: name: nginx-svc labels: app: nginxspec: type: ClusterIP selector: app: nginx ports: – name: http port: 80

其中:

type用户指定Service类型ports用户指定Service的端口selector用户选择后端的Pod

如果Service暴露的端口和后端的端口不一致,还可以这样写:

apiVersion: v1kind: Servicemetadata: name: nginx-svc labels: app: nginxspec: type: ClusterIP selector: app: nginx ports: – name: http port: 80 targetPort: 8080

targetPort用于指定后端的Pod,可以直接指定端口,也可以指定后端声明的端口名字,前提是后端必须声明端口名,如下:

apiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployment labels: app: nginxspec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: – name: nginx image: nginx:1.8 ports: – name: http containerPort: 80 —apiVersion: v1kind: Servicemetadata: name: nginx-svc labels: app: nginxspec: type: ClusterIP selector: app: nginx ports: – name: http port: 80 targetPort: http

如上定义好Service并创建过后,会在集群生成对应的Service和Endpoints,如下:

$ kubectl get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEnginx-svc ClusterIP 10.103.74.70 <none> 80/TCP 12m$ kubectl get endpointsNAME ENDPOINTS AGEnginx-svc 172.16.51.208:80,172.16.51.209:80,172.16.51.237:80 12m

可以看到,endpoints的名字和service的名字一样,而且我们并没有创建endpoints,是由Kubernetes自己创建的。其背后的逻辑是:当我们新增Pod或者删除Pod,是从Endpoints里添加或者剔除,Service本身是不改变的,在同一个namespace下,Service和Endpoints是通过名字进行关联的。

所以,Endpoints其实也是一个对象,除了由Kubernetes生成还可以自己定义。在实际工作,有些场景是需要自定义Endpoints的,比如在集群外部署了一个Redis服务,集群内部想通过Service的方式进行访问,这时候就可以通过自定义Endpints的方式实现,如下:

kind: ServiceapiVersion: v1metadata: name: custom-endpoint-svcspec: ports: – port: 30018 protocol: TCP name: http type: ClusterIP—kind: EndpointsapiVersion: v1metadata: name: custom-endpoint-svcsubsets:- addresses: – ip: 192.168.32.8 ports: – port: 8080 name: http

其中,在Endpoints的subsets中用于定义目的地址和端口,需要注意的是:

Endpoints和Service的metadata.name须保持一致Service.spec.ports[x].name和Endpoints.subsets[x].ports[x].name须保持一致NodePort

如果Service的类型是NodePort,表示把Service的端口映射到了Node节点上,这时候就可以通过NodeIP:NodePort进行访问,可以实现在外部对集群内应用进行访问。

NodePort并不是随便选择的,当安装好Kubernetes集群后,会给定一个默认的NodePort范围,它们是从30000到32767端口,如果没有指定特定端口,默认会从这个区间范围内随机选择一个。

一个NodePort类型的Service定义如下:

apiVersion: v1kind: Servicemetadata: name: nginx-svc labels: app: nginxspec: type: NodePort selector: app: nginx ports: – name: http port: 80 targetPort: http

除了Type类型不一样,其他的和定义ClusterIP类型的一样。

当创建完成过后,生成的Service如下:

$ kubectl get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEnginx-svc NodePort 10.103.74.70 <none> 80:32710/TCP 54m

NodePort类型的Service,既可以在集群内部通过ClusterIP:Port进行访问,也可以在集群外部通过NodeIP:NodePort进行访问。如上,80:32710中,80是集群内部端口,32710是暴露出来的节点端口。

LoadBalancer

原则上,如果要使用LoadBalancer需要云厂商的支持,因为公网IP这些基本都是云厂商来分配。定义如下:

apiVersion: v1kind: Servicemetadata: labels: app: redis name: redis-masterspec: clusterIP: 10.96.246.58 externalTrafficPolicy: Local healthCheckNodePort: 32006 ports: – name: redis nodePort: 30997 port: 6379 protocol: TCP targetPort: 6379 selector: app: redis release: redis role: master sessionAffinity: None type: LoadBalancerstatus: loadBalancer: ingress: – ip: 121.189.210.111

这样就可以直接通过公网IP和端口(121.189.210.111:6379)进行访问。

但是,为了在内网环境也使用LoadBalancer功能,有很多开源方案,其中OpenELB是一个非常不错的项目,我们下面可以来简单使用一下。

OpenELB

OpenELB 之前叫 PorterLB,是为物理机(Bare-metal)、边缘(Edge)和私有化环境设计的负载均衡器插件,可作为 ?Kubernetes、K3s、KubeSphere 的 LB 插件对集群外暴露 LoadBalancer 类型的服务,现阶段是 CNCF 沙箱项目,核心功能包括:

基于 BGP 与 Layer 2 模式的负载均衡基于路由器 ECMP 的负载均衡IP 地址池管理使用 CRD 进行 BGP 配置

(1)安装要安装OpenELB非常简单,直接使用以下命令即可。

$ kubectl apply -f https://raw.githubusercontent.com/openelb/openelb/master/deploy/openelb.yaml

值得注意的是,如果本地不能获取k8s.gcr.io镜像(需要翻墙),可以把上面的YAML文件下载下来,根据里面的注释做相应的镜像替换即可。

安装完成过后,会生成如下Pod:

$ kubectl get pod -n openelb-system NAME READY STATUS RESTARTS AGEopenelb-admission-create-ltfcv 0/1 Completed 0 4m19sopenelb-admission-patch-h485q 0/1 Completed 0 4m19sopenelb-keepalive-vip-7mnl7 1/1 Running 0 3m8sopenelb-manager-98764b5ff-4c58s 1/1 Running 0 4m19s

(2)配置首先,需要为kube-proxy启动strictARP,以便Kubernetes集群中的所有网卡停止响应其他网卡的ARP请求,而由OpenELB来处理ARP请求。

$ kubectl edit configmap kube-proxy -n kube-system……ipvs: strictARP: true……

然后重启kube-proxy组件,命令如下:

$ kubectl rollout restart daemonset kube-proxy -n kube-system

如果安装 OpenELB 的节点有多个网卡,则需要指定 OpenELB 在二层模式下使用的网卡,如果节点只有一个网卡,则可以跳过此步骤,假设安装了 OpenELB 的 master1 节点有两个网卡(eth0 192.168.0.2 和 ens33 192.168.0.111),并且 eth0 192.168.0.2 将用于 OpenELB,那么需要为 master1 节点添加一个 annotation 来指定网卡:

$ kubectl annotate nodes master1 layer2.openelb.kubesphere.io/v1alpha1=”192.168.0.2″

接下来就可以创建一个EIP对象来充当OpenELB的IP地址池,YAML清单如下:

apiVersion: network.kubesphere.io/v1alpha2kind: Eipmetadata: name: eip-poolspec: address: 192.168.205.50-192.168.205.60 protocol: layer2 disable: false interface: ens33

值得注意的是:

address用于配置地址池,如果是在layer2层,则需要和集群的地址保持在同一网段protocol用户指定模式,默认是bgp,我们这里指定的是layer2。interface用于指定网卡,由于这里是layer2,所以需要指定interface。disable表示禁用Eip对象

创建完成过后,可以看看Eip地址池状态:

$ kubectl get eipNAME CIDR USAGE TOTALeip-pool 192.168.205.50-192.168.205.60 1 11

最后,我们创建一个LoadBalancer类型的Service,如下:

apiVersion: v1kind: Servicemetadata: name: nginx-svc labels: app: nginx annotations: lb.kubesphere.io/v1alpha1: openelb protocol.openelb.kubesphere.io/v1alpha1: layer2 eip.openelb.kubesphere.io/v1alpha2: eip-poolspec: type: LoadBalancer selector: app: nginx ports: – name: http port: 80 targetPort: http

注意这里我们为 Service 添加了几个 annotations 注解:

lb.kubesphere.io/v1alpha1: openelb 用来指定该 Service 使用 OpenELBprotocol.openelb.kubesphere.io/v1alpha1: layer2 表示指定 OpenELB 用于 Layer2 模式eip.openelb.kubesphere.io/v1alpha2: eip-pool 用来指定了 OpenELB 使用的 Eip 对象,如果未配置此注解,OpenELB 会自动使用与协议匹配的第一个可用 Eip 对象,此外也可以删除此注解并添加 spec:loadBalancerIP 字段(例如 spec:loadBalancerIP: 192.168.0.108)以将特定 IP 地址分配给 Service。

Service创建过后,可以查看其状态是否正确:

$ kubectl get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEnginx-svc LoadBalancer 10.103.74.70 192.168.205.50 80:32710/TCP 105m

可以看到在??EXTERNAL-IP??处为我们分配了一个Eip,现在就可以直接使用192.168.205.50:80从外部直接访问集群内的服务了。

ExternalName

ExternalName 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。例如:

kind: ServiceapiVersion: v1metadata: name: my-service namespace: prodspec: type: ExternalName externalName: my.database.example.com

当查询主机 my-service.prod.svc.cluster.local (后面服务发现的时候我们会再深入讲解)时,集群的 DNS 服务将返回一个值为 my.database.example.com 的 CNAME 记录。访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发。如果后续决定要将数据库迁移到 Kubernetes 集群中,可以启动对应的 Pod,增加合适的 Selector 或 Endpoint,修改 Service 的 type,完全不需要修改调用的代码,这样就完全解耦了。

Headless Service

上面介绍的几种Service的类型,都会生成一个ClusterIP供集群使用,如果在Kubernetes集群中配置了DNS,解析Service Name会得到ClusterIP。但是在一些特殊的场景下,我们希望直接和应用Pod进行通信,比如数据库等有状态应用,为此,社区提供了Headless Service,也就是无头服务。

无头服务,也就是在配置Service的时候将spec.clusterIP的值设置为“None”,如下:

apiVersion: v1kind: Servicemetadata: name: nginx-headless-service labels: name: nginx-headless-servicespec: clusterIP: None selector: name: nginx ports: – port: 8000 targetPort: 80

创建过后,就不会生成ClusterIP,如下:

$ kubectl get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 17dnginx-headless-service ClusterIP None <none> 8000/TCP 3s

这种类型的Service主要用在有状态服务,后续在有状态应用管理的时候会感受到具体是如何使用的。

Ingress

上面介绍的Service主要用在集群内部,当然NodePort和LoadBalancer类型也可以用于外部访问,但是它们也有一定的弊端:(1)如果使用NodePort类型,需要维护好每个应用的端口地址,如果服务太多就不好管理(2)如果使用LoadBalancer类型,基本是在云上使用,需要的IP比较多,价格也比较昂贵(3)不论是NodePort还是LoadBalancer,都是工作在四层,对于HTTPS类型的请求无法直接进行SSL校验

因此,社区提供了Ingress对象,为集群提供统一的入口,逻辑如下:

其中Ingress代理的并不是Pod的Service,而是Pod,之所以在配置的时候是配置的Service,是为了获取Pod的信息。

Ingress提供七层访问入口,但是Kubernetes的Ingress对象本身没有任何功能,需要借助一些Controller来实现,常用的有:

Nginx Ingress ControllerTraefik Ingress ControllerKong Ingress ControllerAPISix Ingress Controller…..

最常用的是nginx ingress controller,这里也会安装nginx ingress controller来进行介绍。

安装Nginx Ingress Controller

安装方式非常简单,可以使用Helm和普通的YAML进行安装,由于我们还没有学习Helm,所以采用YAML清单的方式。

如果网络条件好(可以翻墙),可直接使用以下命令安装:

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.3.0/deploy/static/provider/cloud/deploy.yaml

如果网络条件不好,可以使用:

$ kubectl apply -f https://raw.githubusercontent.com/joker-bai/kubernetes-software-yaml/main/ingress/nginx/ingress-nginx.yaml

安装完成过后,资源信息如下:

$ kubectl get all -n ingress-nginx NAME READY STATUS RESTARTS AGEpod/ingress-nginx-admission-create-kj7ch 0/1 Completed 0 8m16spod/ingress-nginx-admission-patch-gtncg 0/1 Completed 0 8m16spod/ingress-nginx-controller-d5c4c7ffb-6q5gn 1/1 Running 0 8m16sNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEservice/ingress-nginx-controller LoadBalancer 10.109.208.249 192.168.205.51 80:31306/TCP,443:31079/TCP 8m16sservice/ingress-nginx-controller-admission ClusterIP 10.103.169.99 <none> 443/TCP 8m16sNAME READY UP-TO-DATE AVAILABLE AGEdeployment.apps/ingress-nginx-controller 1/1 1 1 8m16sNAME DESIRED CURRENT READY AGEreplicaset.apps/ingress-nginx-controller-d5c4c7ffb 1 1 1 8m16sNAME COMPLETIONS DURATION AGEjob.batch/ingress-nginx-admission-create 1/1 5s 8m16sjob.batch/ingress-nginx-admission-patch 1/1 6s 8m16s

在这里,ingress-nginx对外暴露采用的是LoadBalancer,也就是说把域名解析到192.168.205.51地址上,可以直接通过80端口访问。

暴露第一个服务

上面已经把ingress-nginx controller部署好了,接下来就可以创建一个应用进行暴露。

我们这里通过暴露一个nginx服务,如下:

apiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployment labels: app: nginxspec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: – name: nginx image: nginx:1.8 ports: – name: http containerPort: 80 —apiVersion: v1kind: Servicemetadata: name: nginx-svc labels: app: nginxspec: type: ClusterIP selector: app: nginx ports: – name: http port: 80 targetPort: http—apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: nginx annotations: kubernetes.io/ingress.class: “nginx”spec: rules: – host: nginx.dev.jokerbai.com http: paths: – path: / backend: service: name: nginx-svc port: number: 80 pathType: Prefix

注意:对于不同版本的Ingress配置可能不一样,在实际中可以通过??kubectl explain ingrss??来查看具体的配置。

部署完成过后,查看ingress信息如下:

$ kubectl get ingressNAME CLASS HOSTS ADDRESS PORTS AGEnginx <none> nginx.dev.jokerbai.com 192.168.205.51 80 88s

将域名进行解析,则可以访问,如下:

$ curl -x 192.168.205.51:80 http://nginx.dev.jokerbai.com<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; }</style></head><body><h1>Welcome to nginx!</h1><p>If you see this page, the nginx web server is successfully installed andworking. Further configuration is required.</p><p>For online documentation and support please refer to<a href=”http://nginx.org/”>nginx.org</a>.<br/>Commercial support is available at<a href=”http://nginx.com/”>nginx.com</a>.</p><p><em>Thank you for using nginx.</em></p></body></html>进阶使用

上面使用ingress暴露了第一个域名,但是在实际的生成中,可能还有更多复杂的应用,比如:

域名使用SSL证书访问域名重定向地址重写认证访问黑白名单域名限速灰度发布……使用SSL证书

在实际工作中,为了安全考虑,更多的是采用HTTPS进行访问,Ingress也支持配置HTTPS。

首先需要域名证书,这里手动创建私有证书进行配置。

$ openssl genrsa -out tls.key 2048$ openssl req -new -x509 -key tls.key -out tls.crt -subj “/C=CN/ST=Chongqing/L=Chongqing/OU=DevOps/CN=nginx.dev.jokerbai.com”

然后将证书保存到secret对象中(在Kubernetes应用配置管理会介绍)。

$ kubectl create secret tls tls-secret –key tls.key –cert tls.crt

证书创建好了之后,就可以配置ingress了,如下:

apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: nginx annotations: kubernetes.io/ingress.class: “nginx”spec: tls: – hosts: – nginx.dev.jokerbai.com secretName: tls-secret rules: – host: nginx.dev.jokerbai.com http: paths: – path: / backend: service: name: nginx-svc port: number: 80 pathType: Prefix

其中Ingress.spec.tls字段用来配置域名证书。

接下来就可以通过HTTPS进行访问。

PS:由于是自定义的证书,是无效的,所以访问的时候会报错,在实际工作中是会购买专用证书。

域名重定向

有时候需要把域名请求重定向到另外的域名,在nginx中,我们可以配置redirect,在ingress中,也可以使用redirect,不过是配置在annotation中,如下:

apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: nginx-redirect annotations: kubernetes.io/ingress.class: “nginx” nginx.ingress.kubernetes.io/permanent-redirect: “https://www.baidu.com”spec: rules: – host: nginx-redirect.dev.jokerbai.com http: paths: – path: / backend: service: name: nginx-svc port: number: 80 pathType: Prefix

访问的时候可以看到301重定向,如下:

$ curl -I -x 192.168.205.51:80 http://nginx-redirect.dev.jokerbai.comHTTP/1.1 301 Moved PermanentlyDate: Fri, 22 Jul 2022 05:50:59 GMTContent-Type: text/htmlContent-Length: 162Connection: keep-aliveLocation: https://www.baidu.com

其实现方式是在ingress配置的annotation中添加声明配置:nginx.ingress.kubernetes.io/permanent-redirect: “?https://www.baidu.com” 即可。

地址重写

地址重写在ingress中通过在annotation中添加??nginx.ingress.kubernetes.io/rewrite-target: “/$1”??这种类型的配置即可。

通过地址重写,我们可以实现诸如访问a.com/foo 重写到a.com,访问a.com/foo重写到a.com/foo/bar,需要注意的是重写后的地址需要是能真实访问到资源的地址,不然重写也没什么意义。

实例如下:

apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: nginx-rewrite annotations: kubernetes.io/ingress.class: “nginx” nginx.ingress.kubernetes.io/rewrite-target: “/$1″spec: rules: – host: nginx-rewrite.dev.jokerbai.com http: paths: – path: /foo/?(.*) backend: service: name: nginx-svc port: number: 80 pathType: Prefix

然后通过在访问URL中带/foo即可访问到Nginx服务,如下:

$ curl -x 192.168.205.51:80 http://nginx-rewrite.dev.jokerbai.com/foo<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; }</style></head><body><h1>Welcome to nginx!</h1><p>If you see this page, the nginx web server is successfully installed andworking. Further configuration is required.</p><p>For online documentation and support please refer to<a href=”http://nginx.org/”>nginx.org</a>.<br/>Commercial support is available at<a href=”http://nginx.com/”>nginx.com</a>.</p><p><em>Thank you for using nginx.</em></p></body></html>认证访问

有时候,我们暴露出来的域名不想被其他人使用(可能有人会说:那你不暴露出来不就完事了),比如说prometheus的后台,默认是没有登录管理的,而运维是需要到后台去查询监控信息的,为了安全,我们需要给这类域名加上认证,以便在一定程度上降低风险。

ingress提供base auth的认证方式,我们可以通过这种方式为我们的域名提供认证。

(1)创建密码

$ htpasswd -c auth jokerNew password: Re-type new password: Adding password for user joker

(2)将密码保存到secret中

$ kubectl create secret generic basic-auth –from-file=authsecret/basic-auth created

(3)在Ingress中配置认证只需要在ingress配置中添加两个annotation即可完成。

nginx.ingress.kubernetes.io/auth-type: basic 指明认证方式nginx.ingress.kubernetes.io/auth-secret: basic-auth 指定认证的账号密码apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: nginx-auth annotations: kubernetes.io/ingress.class: “nginx” nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-secret: basic-authspec: rules: – host: nginx-auth.dev.jokerbai.com http: paths: – path: / backend: service: name: nginx-svc port: number: 80 pathType: Prefix

然后不带认证信息访问,结果如下:

$ curl -x 192.168.205.51:80 http://nginx-auth.dev.jokerbai.com/foo<html><head><title>401 Authorization Required</title></head><body><center><h1>401 Authorization Required</h1></center><hr><center>nginx</center></body></html>

直接返回401,需要认证才能访问。

下面带上认证信息访问,结果如下:

$ curl -u “joker:123″ -x 192.168.205.51:80 http://nginx-auth.dev.jokerbai.com/<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; }</style></head><body><h1>Welcome to nginx!</h1><p>If you see this page, the nginx web server is successfully installed andworking. Further configuration is required.</p><p>For online documentation and support please refer to<a href=”http://nginx.org/”>nginx.org</a>.<br/>Commercial support is available at<a href=”http://nginx.com/”>nginx.com</a>.</p><p><em>Thank you for using nginx.</em></p></body></html>

如果是从浏览器访问,会给我们弹出一个输入框,输入账号密码即可进入应用。

黑白名单

有时候,光有认证访问也并不安全,这时候我们可以通过配置黑白名单的方式,把访问的范围降低,在nginx中,我们可以通过配置allow和deny来配置,在ingress中,也支持类似的配置。

配置白名单

在ingress里配置白名单可以通过两种方式实现:

添加annotation,这种是只针对单个域名在ingress-nginx的configmap中配置,全局有效

(1)通过annotation配置,如下:

apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: nginx-whitelist annotations: kubernetes.io/ingress.class: “nginx” nginx.ingress.kubernetes.io/whitelist-source-range: 10.1.10.2spec: rules: – host: nginx-whitelist.dev.jokerbai.com http: paths: – path: / backend: service: name: nginx-svc port: number: 80 pathType: Prefix

配置了一个不存在的地址10.1.10.2,查看访问效果。

$ curl -I -x 192.168.205.51:80 http://nginx-whitelist.dev.jokerbai.comHTTP/1.1 403 ForbiddenDate: Fri, 22 Jul 2022 06:57:45 GMTContent-Type: text/htmlContent-Length: 146Connection: keep-alive

给我们返回403拒绝访问。

如果我们把地址配置成可以访问的IP,则可以访问。

(2)配置ConfigMap部署好ingress-nginx后,会在ingress-nginx的namespace下生成叫“ingress-nginx-controller”的ConfigMap配置文件,我们只需要在这个配置文件进行配置即可。

$ kubectl edit cm -n ingress-nginx ingress-nginx-controllerapiVersion: v1data: allow-snippet-annotations: “true” compute-full-forwarded-for: “true” use-forwarded-headers: “true” whitelist-source-range: 172.16.0.0/24kind: ConfigMapmetadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.3.0 name: ingress-nginx-controller namespace: ingress-nginx

配置完保存即可,ingress-nginx的pod会自动reload。不过这种配置是全局生效,在使用的时候慎重。

配置黑名单

有白名单就有黑名单,ingress的黑名单配置只能通过ConfigMap来,而且是全局生效的。

目前支持以下三种黑名单:

block-cidrs:限制IPblock-user-agents:限制User-Agentblock-referers:限制referer

配置很简单,如下:

$ kubectl edit cm -n ingress-nginx ingress-nginx-controllerapiVersion: v1data: allow-snippet-annotations: “true” compute-full-forwarded-for: “true” use-forwarded-headers: “true” whitelist-source-range: 172.16.0.0/24 block-cidrs: 10.1.10.100kind: ConfigMapmetadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.3.0 name: ingress-nginx-controller namespace: ingress-nginx访问限速

有时候访问量太大,可以通过在ingress进行限速,配置如下:

apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: nginx-limit annotations: kubernetes.io/ingress.class: “nginx” nginx.ingress.kubernetes.io/limit-rate: “100K” nginx.ingress.kubernetes.io/limit-whitelist: “10.1.10.100” nginx.ingress.kubernetes.io/limit-rps: “1” nginx.ingress.kubernetes.io/limit-rpm: “30”spec: rules: – host: nginx-limit.dev.jokerbai.com http: paths: – path: / backend: service: name: nginx-svc port: number: 80 pathType: Prefix

其中:

nginx.ingress.kubernetes.io/limit-rate:限制客户端每秒传输的字节数nginx.ingress.kubernetes.io/limit-whitelist:白名单中的IP不限速nginx.ingress.kubernetes.io/limit-rps:单个IP每秒的连接数nginx.ingress.kubernetes.io/limit-rpm:单个IP每分钟的连接数灰度发布

?Nginx Annotations 支持以下 4 种 Canary 规则:

??nginx.ingress.kubernetes.io/canary-by-header???:基于 Request Header 的流量切分,适用于灰度发布以及 A/B 测试。当 Request Header 设置为??always???时,请求将会被一直发送到 Canary 版本;当 Request Header 设置为??never??时,请求不会被发送到 Canary 入口;对于任何其他 Header 值,将忽略 Header,并通过优先级将请求与其他金丝雀规则进行优先级的比较。??nginx.ingress.kubernetes.io/canary-by-header-value??:要匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口。该规则允许用户自定义 Request Header 的值,必须与上一个 annotation (即:canary-by-header)一起使用。??nginx.ingress.kubernetes.io/canary-weight??:基于服务权重的流量切分,适用于蓝绿部署,权重范围 0 – 100 按百分比将请求路由到 Canary Ingress 中指定的服务。权重为 0 意味着该金丝雀规则不会向 Canary 入口的服务发送任何请求。权重为 100 意味着所有请求都将被发送到 Canary 入口。??nginx.ingress.kubernetes.io/canary-by-cookie???:基于 Cookie 的流量切分,适用于灰度发布与 A/B 测试。用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的cookie。当 cookie 值设置为??always???时,它将被路由到 Canary 入口;当 cookie 值设置为??never??时,请求不会被发送到 Canary 入口;对于任何其他值,将忽略 cookie 并将请求与其他金丝雀规则进行优先级的比较。

定义两个版本的代码。V1版本代码如下:

package mainimport ( “github.com/gin-gonic/gin” “net/http”)func main(){ g:=gin.Default() g.GET(“/”, func(c *gin.Context) { c.JSON(http.StatusOK,gin.H{ “version”: “v1”, “data”: “hello world”, }) }) _ = g.Run(“8080”)}

V2版本代码如下:

package mainimport ( “github.com/gin-gonic/gin” “net/http”)func main(){ g:=gin.Default() g.GET(“/”, func(c *gin.Context) { c.JSON(http.StatusOK,gin.H{ “version”: “v2”, “data”: “hello world,SB”, }) }) _ = g.Run(“8080”)}

然后制作镜像,Dockerfile如下:

FROM golang AS build-envADD . /go/src/appWORKDIR /go/src/appRUN go get -u -v github.com/gin-gonic/ginRUN govendor syncRUN GOOS=linux GOARCH=386 go build -v -o /go/src/app/app-server-v1FROM alpineRUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtimeCOPY –from=build-env /go/src/app/app-server-v1 /usr/local/bin/app-server-v1EXPOSE 8080CMD [ “/usr/local/bin/app-server-v1” ]

制作镜像并上传:

$ docker build -t registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v1 .$ docker push registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v1

PS:V2版本操作类似

V1和V2版本的Deployment和Service如下:

apiVersion: apps/v1 kind: Deploymentmetadata: name: app-server-v1spec: selector: matchLabels: app: app-server-v1 replicas: 2 template: metadata: labels: app: app-server-v1 spec: containers: – name: app-server-v1 image: registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v1 imagePullPolicy: IfNotPresent ports: – name: http containerPort: 8080—apiVersion: v1kind: Servicemetadata: name: app-server-v1-svcspec: selector: app: app-server-v1 ports: – name: http port: 8080—apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-server-v1 annotations: kubernetes.io/ingress.class: “nginx”spec: rules: – host: canary.dev.jokerbai.com http: paths: – path: / backend: service: name: app-server-v1-svc port: number: 8080 pathType: Prefix—apiVersion: apps/v1 kind: Deploymentmetadata: name: app-server-v2spec: selector: matchLabels: app: app-server-v2 replicas: 2 template: metadata: labels: app: app-server-v2 spec: containers: – name: app-server-v2 image: registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v2 imagePullPolicy: IfNotPresent ports: – name: http containerPort: 8080—apiVersion: v1kind: Servicemetadata: name: app-server-v2-svcspec: selector: app: app-server-v2 ports: – name: http port: 8080—apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-server-v2 annotations: kubernetes.io/ingress.class: “nginx” nginx.ingress.kubernetes.io/canary: “true” nginx.ingress.kubernetes.io/canary-weight: “10”spec: rules: – host: canary.dev.jokerbai.com http: paths: – path: / backend: service: name: app-server-v2-svc port: number: 8080 pathType: Prefix

说明:

nginx.ingress.kubernetes.io/canary: true 表示开启canarynginx.ingress.kubernetes.io/canary-weight: 10 表示权重为10,也就是v1:v2大致为9:1

在实际使用中,也是可以通过部署不同版本的Deployment来实现灰度发布。

总结

目前集群内应用的访问主要是通过Service和Ingress两种方式,其中Service是四层,Ingress是七层,在企业应用中,除了一些必须使用四层的应用,比如MySQL、Redis,其他的基本都采用Ingress进行访问。

Ingress的选型非常多,可以根据企业的实际情况(比如技术栈、熟悉程度、规模大小)进行选择,而Service主要还是在集群内部使用。

最后,求关注。如果你还想看更多优质原创文章,欢迎关注我们的公众号「运维开发故事」。

我是 乔克,《运维开发故事》公众号团队中的一员,一线运维农民工,云原生实践者,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!

只有流过血的手指才能弹出世间的绝唱。

【K8S专栏】Kubernetes应用访问管理

相关文章:

你感兴趣的文章:

标签云: