美文网首页
consul+upsync 实现ingress controll

consul+upsync 实现ingress controll

作者: 谁用了我的昵称叫艾特 | 来源:发表于2020-09-10 23:07 被阅读0次

背景

ingress-controller 实现了集群内部服务的负载均衡,对于公有云环境,我们可以通过LoadBalance 类型的Service实现ingress-controller 的负载均衡。但是私有云环境,对于负载均衡的支持有限,虽然有MetalLB这样的开源的解决方案。 但是真正在生产应用的案例并不多。而且对于已有一套完整架构的公司,直接将ingress-controller 暴露给公网业务或者内部调用的情况并不多,大部分都是在ingress-controller 作为upstream 挂载为一组负载均衡(nginx)的后端来提供服务。因此。 如何实现ingress-controller 的无损更新也就成了如何实现upstream 的server 如何无损摘除的问题。这里我们采用的是微博的upsync 插件和 consul 来实现。

具体实现

大致说明

  1. 集群中创建单独的node节点(配置可以略低),仅用来运行ingress-controller(daemonset),通过给节点打标签和污点的方式实现,ingress-controller采用HostNetwork方式占用宿主机端口,并创建ClusterIP 的service。
  2. 集群中部署consul-sync-catalog 服务,consul-sync-catalog 可以将kubernetes中的service 同步到consul 集群中注册为服务
  3. nginx 通过微博的upsync 组件动态获取consul 中服务对应instance 的ip 和端口。当kubernetes 中的endpoints 发生变动时,consul-sync-catalog同步对应的变动到consul,upsync 组件自动变更upstream对应的server列表,无需reload nginx。

拓扑图

操作步骤

集群操作

  • 设置node节点role,并打污点

    $ kubectl label  node pg-k8s-node-01   node-role.kubernetes.io/edge=edge
    $ kubectl label  node pg-k8s-node-02   node-role.kubernetes.io/edge=edge
    $ kubectl taint node pg-k8s-node-01 node-role.kubernetes.io/edge:NoSchedule
    $ kubectl taint node pg-k8s-node-02 node-role.kubernetes.io/edge:NoSchedule
    
  • 修改ingress-controller 为daemonset(我们使用的是istio的ingressgateway),修改部分见yaml

    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      labels:
        app: istio-ingressgateway
        istio: ingressgateway
        operator.istio.io/component: IngressGateways
        operator.istio.io/managed: Reconcile
        operator.istio.io/version: 1.5.0
        release: istio
      name: istio-ingressgateway
      namespace: istio-system
    spec:
      revisionHistoryLimit: 10
      selector:
        matchLabels:
          app: istio-ingressgateway
          istio: ingressgateway
      template:
        metadata:
          annotations:
            kubectl.kubernetes.io/restartedAt: "2020-09-10T10:50:07+08:00"
            sidecar.istio.io/inject: "false"
          creationTimestamp: null
          labels:
            app: istio-ingressgateway
            chart: gateways
            heritage: Tiller
            istio: ingressgateway
            release: istio
            service.istio.io/canonical-name: istio-ingressgateway
            service.istio.io/canonical-revision: "1.5"
        spec:
          containers:
          - args:
            - proxy
            - router
            - --domain
            - $(POD_NAMESPACE).svc.cluster.local
            - --proxyLogLevel=warning
            - --proxyComponentLogLevel=misc:error
            - --log_output_level=default:info
            - --drainDuration
            - 45s
            - --parentShutdownDuration
            - 1m0s
            - --connectTimeout
            - 10s
            - --serviceCluster
            - istio-ingressgateway
            - --zipkinAddress
            - zipkin.istio-system:9411
            - --proxyAdminPort
            - "15000"
            - --statusPort
            - "15020"
            - --controlPlaneAuthPolicy
            - NONE
            - --discoveryAddress
            - istio-pilot.istio-system.svc:15012
            - --trust-domain=cluster.local
            env:
            - name: SERVICE_NAME
              value: ingress-test
            - name: JWT_POLICY
              value: first-party-jwt
            - name: PILOT_CERT_PROVIDER
              value: istiod
            - name: ISTIO_META_USER_SDS
              value: "true"
            - name: CA_ADDR
              value: istio-pilot.istio-system.svc:15012
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: spec.nodeName
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.namespace
            - name: INSTANCE_IP
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: status.podIP
            - name: HOST_IP
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: status.hostIP
            - name: SERVICE_ACCOUNT
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: spec.serviceAccountName
            - name: ISTIO_META_WORKLOAD_NAME
              value: istio-ingressgateway
            - name: ISTIO_META_OWNER
              value: kubernetes://apis/apps/v1/namespaces/istio-system/deployments/istio-ingressgateway
            - name: ISTIO_META_MESH_ID
              value: cluster.local
            - name: ISTIO_AUTO_MTLS_ENABLED
              value: "true"
            - name: ISTIO_META_POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            - name: ISTIO_META_CONFIG_NAMESPACE
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.namespace
            - name: ISTIO_META_ROUTER_MODE
              value: sni-dnat
            - name: ISTIO_META_CLUSTER_ID
              value: Kubernetes
            image: dockerhub.piggy.xiaozhu.com/istio/proxyv2:1.5.0
            imagePullPolicy: IfNotPresent
            lifecycle:
              preStop:
                exec:
                  command:
                  - /bin/sh
                  - -c
                  - sleep 40
            name: istio-proxy
            ports:
            - containerPort: 15020
              hostPort: 15020
              protocol: TCP
            - containerPort: 80
              hostPort: 80
              protocol: TCP
            - containerPort: 443
              hostPort: 443
              protocol: TCP
            - containerPort: 15029
              hostPort: 15029
              protocol: TCP
            - containerPort: 15030
              hostPort: 15030
              protocol: TCP
            - containerPort: 15031
              hostPort: 15031
              protocol: TCP
            - containerPort: 15032
              hostPort: 15032
              protocol: TCP
            - containerPort: 15443
              hostPort: 15443
              protocol: TCP
            - containerPort: 15011
              hostPort: 15011
              protocol: TCP
            - containerPort: 8060
              hostPort: 8060
              protocol: TCP
            - containerPort: 853
              hostPort: 853
              protocol: TCP
            - containerPort: 15090
              hostPort: 15090
              name: http-envoy-prom
              protocol: TCP
            readinessProbe:
              failureThreshold: 30
              httpGet:
                path: /healthz/ready
                port: 15020
                scheme: HTTP
              initialDelaySeconds: 1
              periodSeconds: 2
              successThreshold: 1
              timeoutSeconds: 1
            resources:
              limits:
                cpu: "2"
                memory: 1Gi
              requests:
                cpu: 100m
                memory: 128Mi
            terminationMessagePath: /dev/termination-log
            terminationMessagePolicy: File
            volumeMounts:
            - mountPath: /var/run/secrets/istio
              name: istiod-ca-cert
            - mountPath: /var/run/ingress_gateway
              name: ingressgatewaysdsudspath
            - mountPath: /etc/istio/pod
              name: podinfo
            - mountPath: /etc/istio/ingressgateway-certs
              name: ingressgateway-certs
              readOnly: true
            - mountPath: /etc/istio/ingressgateway-ca-certs
              name: ingressgateway-ca-certs
              readOnly: true
          dnsPolicy: ClusterFirstWithHostNet  # 如果不修改,pod 的dns-server 会变成宿主机的,无法访问集群内部的svc
          hostNetwork: true # pod 使用宿主机网络
          nodeSelector:
            node-role.kubernetes.io/edge: edge   # 进部署在edge(边缘)节点
          tolerations:
          - key: "node-role.kubernetes.io/edge"
            operator: "Exists"
            effect: "NoSchedule"  # 增加容忍
          restartPolicy: Always
          schedulerName: default-scheduler
          securityContext: {}
          serviceAccount: istio-ingressgateway-service-account
          serviceAccountName: istio-ingressgateway-service-account
          terminationGracePeriodSeconds: 30
          volumes:
          - configMap:
              defaultMode: 420
              name: istio-ca-root-cert
            name: istiod-ca-cert
          - emptyDir: {}
            name: data
          - configMap:
              defaultMode: 420
              name: consul-client-config
            name: config
          - downwardAPI:
              defaultMode: 420
              items:
              - fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.labels
                path: labels
              - fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.annotations
                path: annotations
            name: podinfo
          - emptyDir: {}
            name: ingressgatewaysdsudspath
          - name: ingressgateway-certs
            secret:
              defaultMode: 420
              optional: true
              secretName: istio-ingressgateway-certs
          - name: ingressgateway-ca-certs
            secret:
              defaultMode: 420
              optional: true
              secretName: istio-ingressgateway-ca-certs
      updateStrategy:
        rollingUpdate:
          maxUnavailable: 1
        type: RollingUpdate
    
  • 部署consul-sync-catalog 服务

    $ helm repo add hashicorp https://helm.releases.hashicorp.com # helm添加consul源
    $ cat config.yaml
    syncCatalog:
      enabled: true  # 默认不安装sync,需要手动开启
      toConsul: true # 开启k8s->consul的同步
      toK8S: false # 关闭consul->k8s的同步
      default: false # If true, all valid services in K8S are synced by default. If false, the service must be annotated properly to sync. In either case an annotation can override the default
    $ helm install consul hashicorp/consul --set global.name=consul -n consul -f config.yaml # 会安装consul,consul-server,consul-sync-catalog
    
  • 通过helm 安装的consul-server pod 处于pending 中,因为他需要pvc 而我本地并没有,我们将需要持久化的数据目录修改为emptydir 来解决。并修改consul-server 的svc 类型为nodeport 以便外部访问

  • 给ingressgateway 的svc 添加annotations

    apiVersion: v1
    kind: Service
    metadata:
      annotations:
        consul.hashicorp.com/service-name: ingressgateway
        consul.hashicorp.com/service-port: http2
        consul.hashicorp.com/service-sync: "true"
    ...
    
  • 从consu-ui 中查看service ,已经有一个名为ingressgateway 的服务注册成功了。包含两个instances。

负载均衡配置

  • 安装upsync 插件

    $ cd /root 
    $ yum install git pcre-devel openssl-devel # 安装openresty 依赖相关包
    $ git clone https://github.com/weibocom/nginx-upsync-module.git # 下载upsync 插件
    $ wget https://openresty.org/download/openresty-1.17.8.1.tar.gz -o openresty-1.17.8.1.tar.gz && tar zxf openresty-1.17.8.1.tar.gz && cd openresty-1.17.8.1
    $ ./configure --prefix=/usr/local/openresty --add-module=../nginx-upsync-module/
    $ gmake -j 4 && gmake install 
    
  • 配置upstream 从consul 获取server列表

    upstream app {
       upsync 10.4.10.176:8500/v1/catalog/service/ingressgateway upsync_timeout=6m upsync_interval=1000ms upsync_type=consul_services strong_dependency=off;
       upsync_dump_path /tmp/servers_app.conf;
       include /tmp/servers_app.conf;
       server 0.0.0.0:80 down;   # 如果不加这一行,第一次reload 会因为没有server 而报错。
    }
    server {
      listen       80;
      server_name  api.itanony.com;
      charset utf-8;
    
      location /upstream_list {
          upstream_show;
      }
      location = /api/v1/products {
          proxy_pass http://app;
          proxy_http_version 1.1;
      }
    
    }
    

相关问题

502

在ingressgateway滚动更新过程中进行压测,使用如下命令,日志中还是会有502 的情况, 怀疑是consul-sync-catalog同步不及时。

for i in `seq 1 1000`; do  curl -o /dev/null -s -w "%{time_total}:%{http_code}\n"  http://api.xiaozhu.com/api/v1/products| tee -a 1.log; done

查看到官方文档 中有consulWriteInterval的配置

consulWriteInterval (string: null) - Override the default interval to perform syncing operations creating Consul services.

这个参数应该是控制consul-sync-catalog向consul 集群同步间隔的(看来consul-sync-catalog不是实时的)。这种情况下, 我们可以通过调小这个间隔或者通过给ingressgateway添加一段prestop来解决。注意sleep 的时间要大于consulWriteInterval的值

最后的解决方法:

$ cat config.yaml
syncCatalog:
  enabled: true
  toConsul: true
  toK8S: false
  default: false
  consulWriteInterval: 10s
$ helm upgrade consul hashicorp/consul --set global.name=consul -n consul -f config.yaml
$ kubectl get daemonsets.apps  -n istio-system  istio-ingressgateway  -o yaml
...
        lifecycle:
          preStop:
            exec:
              command:
              - /bin/sh
              - -c
              - sleep 40
...

一些思考

  • 这种方式的好处是ingress-controller 直接通过宿主机网络来实现流量收发。性能相对比nodeport 要高,而且可以避免nodeport 的一些弊端。相比lvs 方案, 也可以避免vrrp 切换中间的流量损失
  • 为什么不用采用NodePort 方式暴露ingress?在测试中。将NodePort的外部流量策略改为Local 或者 Cluster 的情况下。consul-sync-catalog 均会根据pod 的分布,将没有pod 处于不可用状态的node节点从instance 中摘除。但是NodePort 相比直接采用宿主机网络会经过一次目的地址转换。效率自然相比宿主机网络模式要低一点。
  • 弊端:虽然保证了pod滚动更新情况下的完全无损。但是。如果pod 因为一些原因,就绪探针失败而从svc 上摘除, 这块还是没法实现完全无损,不考虑同步的网络延时。这个故障间隔最大是consulWriteInterval。 这里可以通过nginx_upstream_check_module 在负载层做健康监测主动摘除减低影响。 或者应用+负载层的重试来避免。
  • 如果企业内部已经通过consul 来做服务发现。 那其实我们可以借助一些非cluster network plugins如macvlan来实现内部业务的无缝迁移kubernetes。仅使用kubernetes 的调度和资源编排能力, 业务的服务发现和服务注册依然通过consul 来实现。
  • 对于同一个svc中定义了多个端口的服务,consul-sync-catalog默认会以第一个端口作为instance 的port。 其他端口通过metadata 的形式写入consul中。 当然我们也可以通过添加consul.hashicorp.com/service-port 的注解来显式指定哪个端口作为instance 的端口。其他端口想同步。 那我们就再定义几个服务吧。。。

参考

https://www.consul.io/docs/k8s/service-sync#syncing-kubernetes-and-consul-services

https://www.consul.io/docs/k8s/helm

相关文章

网友评论

      本文标题:consul+upsync 实现ingress controll

      本文链接:https://www.haomeiwen.com/subject/fapmektx.html