kubernetes Ingress

什么是Ingress

Ingress是kubernetes中用来对集群外部进来的请求进行负载、路由控制的一种机制。经过ingress,能够方便的将集群内的service以http或https方式对外提供服务,并且不用将各个服务再单独暴露。Ingress功能由Ingress resource和Ingress Controllers共同协做完成。java

Ingress resource

Ingress resource是kubernetes中的一种资源,是相似于pod、deployment的一种API对象,能够经过kubectl命令建立、销毁、更新,其对应的Kind是Ingress,能够在其spec属性中定义服务路由的规则信息。一般Ingress resource中定义的规则须要支持如下的特性:node

  • 基于内容路由nginx

    • 基于目的主机路由:根据请求头中的请求地址(域名),将请求路由到不一样的服务中。如请求地址是foo.example.com的分发到一组服务中,请求地址是bar.example.com分发到别的服务组中
    • 基于请求路径路由:根据请求URL中的路径信息,将请求路由到不一样的服务中。如将请求路径/serviceA/**的请求转发到服务A中,将请求路径/serviceB/**的请求转发到服务B中
  • 对每个主机均可以单独设置TLS/SSL链接信息git

Ingress Controllers

Ingress resource只是定义了路由的规则信息,真正利用这些信息对请求进行控制是经过Ingress Controllers来实现的。不像kube-controller-manager中的其余controller组件,它们是被做为集群的一部分随着集群直接安装好,Ingress controllers 须要咱们本身来安装启动,而且kubernetes支持不少种实现来知足不一样需求,具体参考Ingress Controllersgithub

在kubernetes中,Ingress Controller以pod形式运行,监控API Server的/ingress接口后端的backend services,若是service发生变化,Ingress Controller自动更新转发规则。如Nginx Ingress Controller的工做过程以下:spring

  • 监听API Server获取全部的ingress 的定义
  • 基于Ingress的定义,进行规则合并,生成Nginx所需的配置文件/etc/nginx/nginx.conf
  • 执行nginx -s reload命令,从新加载nginx.conf

为何须要Ingress

  1. 端口竞争docker

    一般状况下,咱们部署在kubernetes集群中的应用须要给外部访问,这时咱们须要在kubernetes中定义NodePort、LoadBalancer等类型的Servcie来实现,具体参考Service。其中LoadBalancer须要在提供相应机制的云环境中才能使用,因此在自建的kubernetes集群中都是经过NodePort类型的Service来实现,就是将宿主机的port的Service的Port作个映射,经过访问宿主机的端口来对service进行访问。在kubernetes集群中只有一个应用,或者应用数量比较少,可以正确分配各个应用对应的宿主机端口时还能够应付。随着应用的追加,端口的映射就变的混乱起来,有的应用还会由于限制必须使用特定的端口号,而这些端口号可能前期已经分配给了别的应用,这时就出现了端口竞争后端

  2. 服务动态更新api

    为了不端口竞争,能够在系统中部署反向代理服务(nginx、HAproxy)等,这时能够把对外的集群服务都经过反向代理服务来暴露,这样就带来了另外一个问题,当有新的服务追加进来,或者旧的服务须要删除,这时还要从新编辑反向代理的配置文件,而后对反向代理服务进行升级。上线/下线一个应用却须要编辑另外一个应用的配置文件,服务的升级、回滚等等都须要考虑,不只麻烦还容易出错服务器

Ingress如何解决上面问题

当采用Ingress机制时,部署新应用,只须要建立针对新应用的Ingress resource,Ingress Controllers就会自动把Ingress resource中的规则合并起来,做为总体路由规则对外服务,而且服务都经过Ingress Controllers统一对外提供,也解决了端口竞争的问题。接下来以nginx-ingress为例来说解具体的原理以及在集群中部署Ingress。

nginx-ingress

  1. nginx-ingress实现的原理

    1. 用户经过kubectl命令向API server发送建立ingress source对象的请求,
    2. ingress-controller监听API server获取到本身对应的ingress source的变化(当集群中有多个ingress controller时,建立ingress source对象时能够指定ingress.class属性,参考Using multiple Ingress controllers),
    3. ingress-controller获取ingress source中的规则后,根据本身的模版生成相应的配置文件,以后reload配置文件,使新的配置生效
    4. 外部请求进入到ingress,访问到实际ingress-controller后,ingress-controller根据配置文件中的规则将请求分发到具体的service
    5. 其中nginx-ingress-controller是在nginx之上又额外添加了一些功能,如监听API server,自动合并规则,并从新加载等,当用容器形式部署时能够经过Deployment、RC等形式启动,和启动普通容器方法是同样的,想要让nginx-ingress-controller能被外部访问,也能够用NodePort形式的service来实现,或者经过Daemonset形式发布时直接指定暴露端口的形式。
  2. 部署nginx-ingress-controller
    目前部署nginx-ingress-controller有两个官方的安装方式:nginxinc/kubernetes-ingresskubernetes/ingress-nginx,具体对比参考区别

    部署前可参阅下这个异同,下面以kubernetes/ingress-nginx为例,进行nginx-ingress-controller的部署

    1. 下载启动文件

      $ mkdir -p /opt/k8s/yml/ingress-nginx
      $ cd /opt/k8s/yml/ingress-nginx
      $ wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
      • 若是搭建的kubernetes是1.14以前的版本,须要把下载的mandatory.yaml文件中217行的kubernetes.io/os改为beta.kubernetes.io/os
    2. 下载镜像,上传到本身的私有镜像仓库

      mandatory.yaml中指定的原始镜像是quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0,在国内可能没有办法下载,改为从阿里镜像仓库下载,为了启动时不用从新从互联网下载,把这个镜像push到咱们本身的镜像仓库中

      $ docker pull registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.30.0
      $ docker tag registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.30.0 192.168.0.107/k8s/nginx-ingress-controller:0.30.0
      $ docker push 192.168.0.107/k8s/nginx-ingress-controller:0.30.0
  3. 修改下载的mandatory.yaml,将镜像名称改为咱们私有仓库中的镜像名称,以后启动服务

    ```
    $ cd /opt/k8s/yml/ingress-nginx
    $ kubectl create -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
    deployment.apps/nginx-ingress-controller created
    limitrange/ingress-nginx created
    
    ```
    * 主要是发布了deployment类型的nginx-ingress-controller,默认replicate是1,权限经过nginx-ingress-serviceaccount来设置
    * 建立了一个serviceaccount:nginx-ingress-serviceaccount,并赋予相关的权限
    1. 由于咱们是在裸机上部署的kubernetes,还须要部署一个service,将nginx-ingress-controller暴露出去,使集群外的服务可以访问,此处采用NodePort类型的service

      $ cd /opt/k8s/yml/ingress-nginx
      $ wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
      
      $ kubectl create -f service-nodeport.yaml
      service/ingress-nginx created
      • 这样咱们经过建立一个service,把nginx-ingress-controller暴露出去,经过以下命令查看具体暴露的端口号

        $ kubectl get svc -n ingress-nginx
        NAME            TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
        ingress-nginx   NodePort   10.254.10.208   <none>        80:18797/TCP,443:29468/TCP   108s
        • 若是kubernetes给咱们随机选择的端口号不能知足要求,能够经过修改service-nodeport.yaml,指定本身须要的端口号
    2. 验证安装状况

      $ kubectl get pods -n ingress-nginx
      NAME                                        READY   STATUS    RESTARTS   AGE
      nginx-ingress-controller-7fdc95bf86-rgmdn   1/1     Running   0          22m
      • 若是状态不是running,经过kubectl describe查看具体缘由
    3. 查看安装的controller版本

      $ kubectl exec -it nginx-ingress-controller-7fdc95bf86-rgmdn -n ingress-nginx -- /nginx-ingress-controller --version
      
      NGINX Ingress controller
        Release:       0.30.0
        Build:         git-7e65b90c4
        Repository:    https://github.com/kubernetes/ingress-nginx
        nginx version: nginx/1.17.8
      • 其中nginx-ingress-controller-7fdc95bf86-rgmdn是上一步骤获取到的pod的名称

验证

  1. 利用官方给咱们提供的例子http-svc,启动一个服务,经过Ingress controller的端口访问咱们的服务。由于官方例子http-svc.yaml中的镜像在gcr.io中,国内没法访问,须要修改为阿里仓库中的镜像registry.aliyuncs.com/google_containers/echoserver:1.4,这个镜像的做用是接收客户端的请求,返回服务端和客户端的header信息。

    启动文件:

    $ cd /opt/k8s/yml/ingress-nginx
    
    $ cat > http-svc.yaml<< EOF
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    apiVersion: apps/v1
      name: http-svc
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: http-svc
      template:
        metadata:
          labels:
            app: http-svc
        spec:
          containers:
          - name: http-svc
            image: registry.aliyuncs.com/google_containers/echoserver:1.4
            ports:
            - containerPort: 8080
            env:
              - name: NODE_NAME
                valueFrom:
                  fieldRef:
                    fieldPath: spec.nodeName
              - name: POD_NAME
                valueFrom:
                  fieldRef:
                    fieldPath: metadata.name
              - name: POD_NAMESPACE
                valueFrom:
                  fieldRef:
                    fieldPath: metadata.namespace
              - name: POD_IP
                valueFrom:
                  fieldRef:
                    fieldPath: status.podIP
    
    ---
    
    apiVersion: v1
    kind: Service
    metadata:
      name: http-svc
      labels:
        app: http-svc
    spec:
      ports:
      - port: 80
        targetPort: 8080
        protocol: TCP
        name: http
      selector:
        app: http-svc
    EOF

    启动命令

    $ cd /opt/k8s/yml/ingress-nginx
    $ kubectl create -f http-svc.yaml
  2. 建立一个Ingress resources

    目前咱们只建立了一个后端服务,因此建立一个简单的Ingress,把全部请求都路由到http-svc:80

    $ cd /opt/k8s/yml/ingress-nginx
    $ cat > single-ingress.yml <<EOF
    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress
    metadata:
      name: single-ingress
      annotations:
         kubernetes.io/ingress.class: "nginx"
    spec:
      backend:
        serviceName: http-svc
        servicePort: 80
    EOF

    建立 ingress

    $ cd /opt/k8s/yml/ingress-nginx
    $ kubectl create -f single-ingress.yml

    查看ingress信息

    $ kubectl get ingress -o wide
    NAME             HOSTS   ADDRESS         PORTS   AGE
    single-ingress   *       10.254.10.208   80      11m 
    $ kubectl describe ingresses single-ingress
    Name:             single-ingress
    Namespace:        default
    Address:          10.254.10.208
    Default backend:  http-svc:80 (172.30.22.3:8080)
    Rules:
      Host  Path  Backends
      ----  ----  --------
      *     *     http-svc:80 (172.30.22.3:8080)
    Annotations:
      kubernetes.io/ingress.class:  nginx
    Events:
      Type    Reason  Age   From                      Message
      ----    ------  ----  ----                      -------
      Normal  CREATE  11m   nginx-ingress-controller  Ingress default/single-ingress
      Normal  UPDATE  11m   nginx-ingress-controller  Ingress default/single-ingress
  3. 经过Ingress controller暴露出来的http端口或者https端口访问http-svc服务,查看Ingress controller服务暴露的地址(按照前面的部署流程,咱们是经过一个NodePort类型的service来暴露的)

    $  kubectl get svc -n ingress-nginx
    NAME            TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
    ingress-nginx   NodePort   10.254.10.208   <none>        80:18797/TCP,443:29468/TCP   12h

    经过18797端口访问服务

    # http端口访问
    $ curl http://192.168.0.107:18797
    # https端口访问
    $curl --insecure https://192.168.0.107:29468
    
    CLIENT VALUES:
    client_address=172.30.22.7
    command=GET
    real path=/
    query=nil
    request_version=1.1
    request_uri=http://192.168.0.107:8080/
    
    SERVER VALUES:
    server_version=nginx: 1.10.0 - lua: 10001
    
    HEADERS RECEIVED:
    accept=*/*
    host=192.168.0.107:18797
    user-agent=curl/7.58.0
    x-forwarded-for=172.30.22.1
    x-forwarded-host=192.168.0.107:18797
    x-forwarded-port=80
    x-forwarded-proto=http
    x-real-ip=172.30.22.1
    x-request-id=9c9b6f86b5a0d0a664c0f9f01a0bde47
    x-scheme=http
    BODY:
    -no body in request-
    • 其中192.168.0.107是集群中一个节点的IP地址
    • 返回信息中client_address是nginx-ingress-controller对应的pod的地址,说明请求先进入到nginx-ingress-controller中,以后路由到具体的服务中。对于如何正确拿到client IP地址,能够参照Source IP address,也可参照个人另外一篇文章kubernetes 网络
    • 头信息x-forwarded-host中存放了咱们真实访问的服务器的地址

这样咱们就完成了经过nginx-ingress-controller对后端服务的访问(若是用spring cloud作过微服务开发,会对这个操做很熟悉,由于和zuul、getway等网关功能很相似)。

追加服务

假如咱们又在集群中新启动了一个服务,为了能从外部访问这个服务,咱们也要把这个新服务经过nginx-ingress-controller对外暴露,咱们能够新追加一个ingress source对象,定义咱们这个新服务相关的信息.

  1. 启动新服务,咱们这个新服务是本身编写的一个spring boot 工程,里面只有一个restful接口

    @RequestMapping("/header/list")
    public String listHeader(HttpServletRequest request) {
    
        log.info("host is" + request.getHeader("host"));
    
        log.info("remoteAddr is " + request.getRemoteHost());
    
        log.info("remotePort is " + request.getRemotePort());
    
        return "OK";
    }
    • 接收外部请求后打印出client相关信息
  2. 启动这个新服务

    $ cd /opt/k8s/yml/ingress-nginx
    $ cat > clientip.yml <<EOF
    apiVersion: v1
    kind: Service
    metadata:
      name: clientip
    spec:
      selector:
        app: clientip
      ports:
      - name: http
        port: 8080
        targetPort: 8080
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: clientip-deployment
    spec:
      selector:
        matchLabels:
          app: clientip
      replicas: 1
      template:
        metadata:
          labels:
            app: clientip
        spec:
          nodeSelector:
            sample: slave
          containers:
          - name: clientip
            image: 192.168.0.107/k8s/client-ip-test:0.0.2
            ports:
            - containerPort: 8080
    
    EOF

    启动服务

    $ cd /opt/k8s/yml/ingress-nginx
    $ kubectl create -f clientip.yml
  3. 新服务对应的ingress source文件

    $ cd /opt/k8s/yml/ingress-nginx
    $ cat > clientip-ingress.yml<< EOF
    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress
    metadata:
      name: clientip-ingress
      annotations:
         kubernetes.io/ingress.class: "nginx"
         nginx.ingress.kubernetes.io/rewrite-target: /$2
    spec:
      rules:
      - http:
          paths:
          - path: /clientip
            backend:
              serviceName: clientip
              servicePort: 8080
            path: /clientip(/|$)(.*)
    EOF
    • 表示请求uri以clientip开头的服务会路由到这个新启动的服务中
    • 追加了rewrite-target的注解,由于虽然请求以/clientip开头,可是我么真实的请求地址中并无clientip,因此须要用rewrite-target功能把请求地址重写,详情参考rewrite

    建立新的ingress source

    $ cd /opt/k8s/yml/ingress-nginx
    $ kubectl create -f clientip-ingress.yml
    ingress.networking.k8s.io/clientip-ingress created
  4. 启动后经过nginx-ingress-controller访问

    # 访问新服务,注意咱们在请求路径中的clientip
    $ curl http://192.168.0.107:18797/clientip/header/list
    OK
    
    # 访问原来部署好的服务
    $ curl http://192.168.0.107:18797
    CLIENT VALUES:
    client_address=172.30.22.7
    command=GET
    real path=/
    query=nil
    request_version=1.1
    request_uri=http://192.168.0.107:8080/
    
    SERVER VALUES:
    server_version=nginx: 1.10.0 - lua: 10001
    
    HEADERS RECEIVED:
    accept=*/*
    host=192.168.0.107:18797
    user-agent=curl/7.58.0
    x-forwarded-for=172.30.22.1
    x-forwarded-host=192.168.0.107:18797
    x-forwarded-port=80
    x-forwarded-proto=http
    x-real-ip=172.30.22.1
    x-request-id=883570a193258f151a6d8bd5f96761af
    x-scheme=http
    BODY:
    -no body in request-
    • 能够看到,新追加的服务也能够经过nginx-ingress-controller进行访问,而且原来部署好的服务不受影响仍旧能够访问
相关文章
相关标签/搜索