通过ingress暴露内部服务

在kubernetes集群中,有一个常见的需求:如何将内部服务暴露出来,供外部访问?

快速入门Kubernetes一节中,我们使用了Service(Load Balancer)的方式,对外暴露了nginx服务。试想:如果我们有100个内部Deployment,能够使用LB的方式,对外暴露么?

如果你还有印象,LB的对外暴露,要占用一个独立的端口,当需要暴露的服务增多时,光是端口的占用和分配,就已经是一个头疼的问题了。

实际上,Kubernetes为我们提供了三种暴露内部服务的机制:

  • NodePort:在Kubernetes的所有节点上,开放一个端口,转发到内部具体的service上,与LoadBalancer相比,它不会绑定外网IP,多用于临时用途(如debug)

  • LoadBalancer:每个服务可以绑定一个外网IP、端口,当需要暴露的服务不多时,这是官方推荐的选择。

  • Ingress:像一个“智能路由器”,对外只暴露一个IP/端口,可以根据路径、头信息等变量,自动转发到内部的多个不同服务上。

本节,我们将介绍两种不同的Ingress,来实现“暴露内部多组服务这个需求”。

七层ingress

首先,我们来看一下Nginx Ingress Controller,这是一款较早退出的Ingress方案,基于Nginx实现了应用层(http)协议的暴露。

我们在上一节的基础上,添加另一组deployment:

  1. kubectl create deployment my-httpd --image=httpd:2.4
  2. kubectl scale deployment my-httpd --replicas=3

同时,我们将之前创建的ngxin,也缩容为3:

  1. kubectl scale deployment my-nginx --replicas=3
  2. kubectl get pods
  3. NAME READY STATUS RESTARTS AGE
  4. my-httpd-84bdf5b4d9-jjvwv 1/1 Running 0 46s
  5. my-httpd-84bdf5b4d9-n269p 1/1 Running 0 16s
  6. my-httpd-84bdf5b4d9-rw2kk 1/1 Running 0 16s
  7. my-nginx-7bc876dc4b-226g9 1/1 Running 2 4h46m
  8. my-nginx-7bc876dc4b-872v2 1/1 Running 2 4h46m
  9. my-nginx-7bc876dc4b-fzr8s 1/1 Running 2 4h46m

接着,我们创建上述两个deployment的service:

  1. kubectl expose deployment/my-nginx --port=80
  2. kubectl expose deployment/my-httpd --port=80
  3. kubectl get services
  4. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  5. kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9m16s
  6. my-httpd ClusterIP 10.102.22.9 <none> 80/TCP 4s
  7. my-nginx ClusterIP 10.109.10.111 <none> 80/TCP 9s

在配置ingress之前,我们首先要启用ingress:

  1. minikube addons enable ingress

如果你使用的是MacOS,可能会报错,此时需要一些额外的配置,请参考这个帖子

接下来,我们创建ingress.yaml文件:

  1. apiVersion: networking.k8s.io/v1
  2. kind: Ingress
  3. metadata:
  4. name: homs-ingress
  5. annotations:
  6. kubernetes.io/ingress.class: nginx
  7. nginx.ingress.kubernetes.io/rewrite-target: /$1
  8. spec:
  9. tls:
  10. - hosts:
  11. - homs.coder4.com
  12. secretName: homs-secret
  13. rules:
  14. - host: homs.coder4.com
  15. http:
  16. paths:
  17. - path: /my-nginx/?(.*)
  18. pathType: Prefix
  19. backend:
  20. service:
  21. name: my-nginx
  22. port:
  23. number: 80
  24. - path: /my-httpd/?(.*)
  25. pathType: Prefix
  26. backend:
  27. service:
  28. name: my-httpd
  29. port:
  30. number: 80

解释一下:

  • 我们定义了Nginx的Ingress,并使用了转发前清除前缀(rewrite-target配置)

  • 定义了两个不同的前缀my-nginx和my-httpd,通过前缀指向内部服务

  • 同时支持了http和https解析,但https是自签证书,所以后面我们只用http

然后创建它:

  1. kubectl apply -f ./ingress.yaml

稍等一会,ingress的IP分配成功后如下所示:

  1. kubectl get ingress
  2. NAME CLASS HOSTS ADDRESS PORTS AGE
  3. homs-ingress <none> homs.coder4.com 192.168.64.3 80, 443 34s

如上所示,“192.168.64.3”就是分配的ingressIP,但我们需要用DNS访问它,这里,我使用nip.io这个黑魔法来避免需要修改hosts的问题,即修改上述yaml中的host为“192.168.64.3.nip.io”。

我们登录到minikube集群内部,尝试访问:

  1. curl -kL "http://192.168.64.3.nip.io/my-httpd"
  2. <html><body><h1>It works!</h1></body></html>
  3. curl -kL "http://192.168.64.3.nip.io/my-nginx"
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <title>Welcome to nginx!</title>
  8. <style>
  9. html { color-scheme: light dark; }
  10. body { width: 35em; margin: 0 auto;
  11. font-family: Tahoma, Verdana, Arial, sans-serif; }
  12. </style>
  13. </head>
  14. <body>
  15. <h1>Welcome to nginx!</h1>
  16. <p>If you see this page, the nginx web server is successfully installed and
  17. working. Further configuration is required.</p>
  18. <p>For online documentation and support please refer to
  19. <a href="http://nginx.org/">nginx.org</a>.<br/>
  20. Commercial support is available at
  21. <a href="http://nginx.com/">nginx.com</a>.</p>
  22. <p><em>Thank you for using nginx.</em></p>
  23. </body>
  24. </html>

如上,我们成功的用prefix的路径(my-nginx / my-httpd),访问了两个不同的内部service!

修改转发前缀

在上述的配置中,我们实现了多服务的转发,但准法后的location存在一些问题,我们换一个service验证一下:

  1. kubectl create deployment service1 --image=mendhak/http-https-echo:23
  2. kubectl create deployment service2 --image=mendhak/http-https-echo:23

对外暴露服务:

  1. kubectl expose deployment/service1 --port=8080
  2. kubectl expose deployment/service2 --port=808

修改一下ingress:

  1. apiVersion: networking.k8s.io/v1
  2. kind: Ingress
  3. metadata:
  4. name: homs-ingress
  5. annotations:
  6. kubernetes.io/ingress.class: nginx
  7. nginx.ingress.kubernetes.io/rewrite-target: /$1
  8. spec:
  9. tls:
  10. - hosts:
  11. - homs.coder4.com
  12. secretName: homs-secret
  13. rules:
  14. - host: homs.coder4.com
  15. http:
  16. paths:
  17. - path: /service1/?(.*)
  18. pathType: Prefix
  19. backend:
  20. service:
  21. name: service1
  22. port:
  23. number: 8080
  24. - path: /service2/?(.*)
  25. pathType: Prefix
  26. backend:
  27. service:
  28. name: service2
  29. port:
  30. number: 8080

登录到minikube后curl:

  1. {
  2. "path": "/",
  3. "headers": {
  4. "host": "192.168.64.11.nip.io",
  5. "x-request-id": "7a00b30a5d4fd4c084d2bcfbfd44f636",
  6. "x-real-ip": "192.168.64.11",
  7. "x-forwarded-for": "192.168.64.11",
  8. "x-forwarded-host": "192.168.64.11.nip.io",
  9. "x-forwarded-port": "443",
  10. "x-forwarded-proto": "https",
  11. "x-scheme": "https",
  12. "user-agent": "curl/7.76.0",
  13. "accept": "*/*"
  14. },
  15. "method": "GET",
  16. "body": "",
  17. "fresh": false,
  18. "hostname": "192.168.64.11.nip.io",
  19. "ip": "192.168.64.11",
  20. "ips": [
  21. "192.168.64.11"
  22. ],
  23. "protocol": "https",
  24. "query": {},
  25. "subdomains": [
  26. "11",
  27. "64",
  28. "168",
  29. "192"
  30. ],
  31. "xhr": false,
  32. "os": {
  33. "hostname": "service2-5686d4f68c-4vz7d"
  34. },
  35. "connection": {}
  36. }

观察上述输出,发现转发后的location被重定向了,如果我们的服务想收到完整的请求,如何实现呢?

我们可以修改ingress配置,在路径上添加一段分组匹配,如下:

  1. apiVersion: networking.k8s.io/v1
  2. kind: Ingress
  3. metadata:
  4. name: homs-ingress
  5. annotations:
  6. kubernetes.io/ingress.class: nginx
  7. nginx.ingress.kubernetes.io/rewrite-target: /$1$2
  8. nginx.ingress.kubernetes.io/app-root: /service1
  9. spec:
  10. tls:
  11. - hosts:
  12. - homs.coder4.com
  13. secretName: homs-secret
  14. rules:
  15. - host: 192.168.64.11.nip.io
  16. http:
  17. paths:
  18. - path: /(service1/?)(.*)
  19. pathType: Prefix
  20. backend:
  21. service:
  22. name: service1
  23. port:
  24. number: 8080
  25. - path: /(service2/?)(.*)
  26. pathType: Prefix
  27. backend:
  28. service:
  29. name: service2
  30. port:
  31. number: 8080

生效后,再次curl:

  1. curl -kL "http://192.168.64.11.nip.io/service2"
  2. {
  3. "path": "/service2",
  4. "headers": {
  5. "host": "192.168.64.11.nip.io",
  6. "x-request-id": "b5759cf6f47d0ed713142178ddea4f96",
  7. "x-real-ip": "192.168.64.11",
  8. "x-forwarded-for": "192.168.64.11",
  9. "x-forwarded-host": "192.168.64.11.nip.io",
  10. "x-forwarded-port": "443",
  11. "x-forwarded-proto": "https",
  12. "x-scheme": "https",
  13. "user-agent": "curl/7.76.0",
  14. "accept": "*/*"
  15. },
  16. "method": "GET",
  17. "body": "",
  18. "fresh": false,
  19. "hostname": "192.168.64.11.nip.io",
  20. "ip": "192.168.64.11",
  21. "ips": [
  22. "192.168.64.11"
  23. ],
  24. "protocol": "https",
  25. "query": {},
  26. "subdomains": [
  27. "11",
  28. "64",
  29. "168",
  30. "192"
  31. ],
  32. "xhr": false,
  33. "os": {
  34. "hostname": "service2-5686d4f68c-4vz7d"
  35. },
  36. "connection": {}
  37. }

成功!

Nginx Ingress也支持通过不同的Host来区分不同Service,也支持nginx的部分自定义配置,推荐你阅读官方ingress例子)。

四层ingress

在上述两个例子中,我们实现了7层http协议的暴露 & 转发,ingress也支持4层的TCP协议。

为了防止影响,我们首先重置集群,并重新启用ingress。

  1. minikube delete
  2. minikube start
  3. minikube addons enable ingress

接着,创建一个TCP的服务,我们以redis为例:

  1. kubectl create deployment redis --image=redis:6
  2. kubectl expose deployment/redis --port=6379

接着,我们创建映射关系,TCP的ingress是通过ConfigMap额外配置的。

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: tcp-services
  5. namespace: ingress-nginx
  6. data:
  7. 6379: "default/redis:6379"

最后,我们将端口映射,修改到ingress上:

  1. kubectl edit service -n ingress-nginx ingress-nginx-controller

在规则处添加如下代码:

  1. - name: redis
  2. port: 6379
  3. protocol: TCP
  4. targetPort: 6379

这里我们并没有填写nodePort,这是系统会自动分配的,不用我们手动处理。

保存成功后,我们尝试通过ingress的端口连接:

  1. kubectl get services --all-namespaces
  2. NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  3. default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 35m
  4. default redis ClusterIP 10.109.20.237 <none> 6379/TCP 33m
  5. ingress-nginx ingress-nginx-controller NodePort 10.110.48.51 <none> 80:30958/TCP,443:32737/TCP,6379:32765/TCP 34m
  6. ingress-nginx ingress-nginx-controller-admission ClusterIP 10.103.12.249 <none> 443/TCP 34m
  7. kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 35m

我们本地使用redis连接:

  1. redis-cli -h $(minikube ip) -p 32765
  2. > info
  3. info
  4. # Server
  5. redis_version:6.2.6
  6. redis_git_sha1:00000000
  7. redis_git_dirty:0
  8. redis_build_id:1527eab61b27d3bf
  9. redis_mode:standalone
  10. os:Linux 4.19.182 x86_64
  11. arch_bits:64
  12. multiplexing_api:epoll
  13. .....

成功!

至此,我们成功使用Ingress暴露了内部的TCP端口。

如果你仔细对比HTTP和TCP的Ingress,不难发现:

  • HTTP的Ingress更加实用,可以通过不同Host甚至不同Path,区分多个内部Service

  • TCP的Ingress相对来说,比较”凑合”,虽然能够工作,但配置繁琐、还需要耗费多个端口,并不方便。

因此,再实际工作中,如果想从k8s集群外访问集群内的TCP服务,多采用网络打通的方式进行,我们将在后续章节介绍这一功能。