What is Ingress?

    internet
        |
  ------------
  [ Services ]

service通常只是在集群内部有效,从集群外是无法访问到的。

在kubernets的世界里,服务要对集群外暴漏,有这么几种方式:

  • NodePort
  • LoadBalancer
  • External IPs
  • Ingress

之前比较常用的是NodePort,搭配keepalived可以获得一定程度的HA。NodePort、LoadBalance、External IPs可以认为是L4的,而Ingress则是L7的。与前者通常使用iptables、LVS等手段不同,Ingress通常是基于nginx的,可以认为是一个openresty。

    internet
        |
   [ Ingress ]
   --|-----|--
   [ Services ]

Ingress是一种资源,和Pod、Configmap等等类似,也需要一个controller来管理。

kubernetes的ingress是这样玩的:集群部署时,创建一个或多个ingress controller,他们会去监听api server;当用户创建Ingress时,controller会通过api server获取新创建的Ingress的信息(主要是vhost、路径、service+port。注意ingress-nginx会直接将service翻译为endpoint,减少了service这一层转换),根据nginx模板生成新的nginx配置文件(主要是proxy_pass到RS),最后reload nginx重新加载配置。在下面的几个示例中,将看到这个过程。

Ingress controller可以有不同的实现,目前社区可以用的主要是 ingress-nginxingress-gce

我们采用的是ingress-nginx。

ingress types

来看看ingress有哪些类型。

single service ingress

单服务,无vhost,无路径。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
spec:
  backend:
    serviceName: testsvc
    servicePort: 80

这种情况下,所有到该ingress的流量(/后的所有http请求。),都会转到 testsvc:80。感觉应该不怎么会用到。

        upstream argo-http-svc-80 {
                least_conn;
                keepalive 32;
                server 10.244.3.118:8080 max_fails=0 fail_timeout=0;
        }
        server {
               location / {
                        proxy_pass http://argo-http-svc-80;
                        proxy_redirect                          off;
                }

Simple fanout

fanout是个很有意思的词。简单理解就是这种ingress会将用户的请求根据路径fanout到多个Real Server去。例如下例,发给foo.bar.com的请求会根据url进行路由。

foo.bar.com -> 178.91.123.132 -> / foo    s1:80
                                 / bar    s2:80

编排文件如下。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: s1
          servicePort: 80
      - path: /bar
        backend:
          serviceName: s2
          servicePort: 80

nginx配置。

        upstream argo-s1-80 {
                least_conn;
                keepalive 32;
                server 10.244.3.118:8080 max_fails=0 fail_timeout=0;
        }

        upstream argo-s2-80 {
                least_conn;
                keepalive 32;
                server 10.244.3.147:8080 max_fails=0 fail_timeout=0;
        }

        ## start server foo.bar.com
        server {
                server_name foo.bar.com ;
                listen 80;
                listen [::]:80;
                set $proxy_upstream_name "-";
                location / {
                        # proxy balabala
                        proxy_pass http://upstream-default-backend;
                        proxy_redirect                          off;
                }
                location /foo {
                        # proxy balabala
                        proxy_pass http://argo-s1-80;
                        proxy_redirect                          off;
                }
                location /bar {
                        # proxy balabala
                        proxy_pass http://argo-s2-80;
                        proxy_redirect                          off;

                }

注意!如果ingress中没有指定hosts,也可以用IP地址来访问,但ingress-nginx默认要求https,所以这时会遇到访问总是308跳转的问题。

例如下面这个ingress。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: mini-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
  - http:
      paths:
      - path: /mini
        backend:
          serviceName: nginx
          servicePort: 80

访问/mini路径时,总是返回308重定向.

此时的nginx.conf对应的配置如下,可以看到,如果redirect_to_https,则会跳转到https。

                location ~* ^/mini\/?(?<baseuri>.*) {
                        # enforce ssl on server side
                        if ($redirect_to_https) {
                                return 308 https://$best_http_host$request_uri;
                        }

怎么解决呢?在issues/1567给了一个比较好的办法:指定 nginx.ingress.kubernetes.io/ssl-redirect为false,也就是不要对http强制跳转。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: geth
  annotations:
  namespace: xxp
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
  - http:
      paths:
      - path: /eth
        backend:
          serviceName: geth
          servicePort: 8545

ingress-nginx的处理

Name based virtual hosting

nginx可以根据用户请求的hosts,来转发到对应的Real Server。如下图。注意IP地址、端口号都是不变的。

foo.bar.com --|                 |-> foo.bar.com s1:80
              | 178.91.123.132  |
bar.foo.com --|                 |-> bar.foo.com s2:80

编排文件

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          serviceName: s1
          servicePort: 80
  - host: bar.foo.com
    http:
      paths:
      - backend:
          serviceName: s2
          servicePort: 80

对应的nginx配置。

        upstream argo-http-svc-80 {
                least_conn;
                keepalive 32;
                server 10.244.3.118:8080 max_fails=0 fail_timeout=0;
        }

        upstream argo-http-svc2-80 {
                least_conn;
                keepalive 32;
                server 10.244.3.147:8080 max_fails=0 fail_timeout=0;
        }

        server {
                server_name bar.foo.com ;
                listen 80;
                listen [::]:80;
                set $proxy_upstream_name "-";
                location / {
                        # proxy balabala
                        proxy_pass http://argo-http-svc2-80;
                        proxy_redirect                          off;
                }
        }
        ## end server bar.foo.com

        server {
                server_name foo.bar.com ;
                listen 80;
                listen [::]:80;
                location / {
                        # proxy balabala
                        proxy_pass http://argo-http-svc-80;
                        proxy_redirect                          off;
                }
        }
        ## end server foo.bar.com

Multiple ingress controllers

某些情况下我们可能希望区分不同的ingress(例如不同区域、不同配置、不同环境),此时k8s集群上会有多个ingress controller,创建Ingress时,需要指定希望哪个controller来处理该请求。

不同的ingress controller,通过 ingress-class来区分。

spec:
  template:
     spec:
       containers:
         - name: nginx-ingress-internal-controller
           args:
             - /nginx-ingress-controller
             - '--default-backend-service=ingress/nginx-ingress-default-backend'
             - '--ingress-class=nginx'
             - '--configmap=ingress/nginx-ingress-controller'

创建ingress时,指定不同的ingress-class

metadata:
  name: foo
  annotations:
    kubernetes.io/ingress.class: "gce"
metadata:
  name: foo
  annotations:
    kubernetes.io/ingress.class: "nginx"

Ref: