美文网首页
Kubernetes深度实践(七)

Kubernetes深度实践(七)

作者: 哦呵呵_3579 | 来源:发表于2022-04-01 21:59 被阅读0次

    CICD搭建完成之后又迎来新的问题,链路追踪、日志、监控告警、在线调试、服务更新策略,先从链路追踪说起。

    链路追踪

    链路追踪的话有很多选项,比如zipkin、skywalking等,由于我们公司是基于java的技术路线的,所以我选择了skywalking来做链路追踪,通过sidecar的方式将其无感的注入到服务中。

    skywalking可以通过helm直接安装,具体安装流程就不详细说明了,我是把监控告警这些工具全都装在了monitoring的namespace中。

    ## deployment.yml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: service-backend
      labels:
        app: service-backend
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: service-backend
      template:
        metadata:
          labels:
            app: service-backend
        spec:
          initContainers:
            - name: init-skywalking-agent
              image: skywalking-java-agent:8.7.0
              command: [ "/bin/sh" ]
              args: [ "-c", "cp -R /skywalking/agent /agent" ]
              volumeMounts:
                - mountPath: /agent
                  name: skywalking-agent
          containers:
            - name: service-backend
              image: service-backend:latest
              env:
                - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
                  value: skywalking-oap.monitoring.svc:11800
                - name: SW_AGENT_NAME
                  value: service-backend
                - name: SW_AGENT_INSTANCENAME
                  valueFrom:
                    fieldRef:
                      fieldPath: status.podIP
                - name: SW_GRPC_LOG_SERVER_HOST
                  value: skywalking-oap.monitoring.svc
                - name: SW_GRPC_LOG_SERVER_PORT
                  value: '11800'
                - name: SW_GRPC_LOG_MAX_MESSAGE_SIZE
                  value: '10485760'
                - name: SW_GRPC_LOG_GRPC_UPSTREAM_TIMEOUT
                  value: '30'
                - name: SERVER_PORT
                  value: '80'
                - name: TZ
                  value: Asia/Shanghai
                - name: JAVA_OPTS
                  value: ' -Xmx4096m -Xms2048m'
                - name: JAVA_ENABLE_DEBUG
                  value: 'false'
              ports:
                - name: http
                  containerPort: 80
              volumeMounts:
                - mountPath: /opt/skywalking/
                  name: skywalking-agent
                - name: time
                  mountPath: /etc/localtime
          volumes:
            - name: skywalking-agent
              emptyDir: {}
            - name: time
              hostPath:
                path: /etc/localtime
                type: ''
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxUnavailable: 1
          maxSurge: 1
      revisionHistoryLimit: 3
      progressDeadlineSeconds: 600
    
    ## 容器启动脚本 
    #!/bin/sh
    echo 'ready to start service'
    if [ x"${JAVA_ENABLE_DEBUG}" != x ] && [ "${JAVA_ENABLE_DEBUG}" != "false" ] 
    then
        java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 /home/*.jar
    elif [ -f "/opt/skywalking/agent/skywalking-agent.jar" ] 
    then
        java -javaagent:/opt/skywalking/agent/skywalking-agent.jar ${JAVA_OPTS} -jar /home/*.jar
    else
        java ${JAVA_OPTS} -jar /home/*.jar
    fi
    

    这样就可以在对代码零侵入的情况下实现对链路的追踪以及运行指标的收集。

    apisix也支持skywalking插件,需要在配置文件中进行如下设置,然后针对每个需要收集的路由单独启用skywalking插件即可。

    ...
      - real-ip
      - skywalking
      - skywalking-logger
    stream_plugins:
      - mqtt-proxy
      - ip-restriction
      - limit-conn
    plugin_attr:
      skywalking:
        service_name: APISIX
        endpoint_addr: http://skywalking-oap.monitoring.svc:12800
        service_instance_name: $hostname
      skywalking-logger:
        endpoint_addr: http://skywalking-oap.monitoring.svc:12800
        service_name: APISIX
        service_instance_name: $hostname
    

    日志收集

    由于skywalking也支持日志收集和展示,所以我把SW_GRPC_LOG_SERVER_HOST也加了进去。我使用的网关是Apisix,它同样支持skywalking插件,可以收集链路以及日志信息(skywalking日志收集有版本要求,需要apisix版本>=2.12)。但说句实在话,skywalking的日志展示功能实在有点一言难尽,只能说能用,而且收集的日志全都是base64编码的,这个可以说是非常的坑,即使想要用kibana来进行展示也会有问题,虽然kibana支持base64解码,但是解码出来的不支持关键字搜索,毕竟skywalking的强项是链路追踪和收集指标,所以也不能要求太多。我建议只收集apisix的日志就可以了,服务的日志通过fluentd这类的工具来收集,然后通过kibana来展示。

    我是用的是kube-fluentd-operator,vm出品,直接通过helm进行安装即可,github地址:https://github.com/vmware/kube-fluentd-operator

    安装完成之后,只需要在需要采集日志的namespace下面添加一个configmap即可自动采集日志,并且支持热加载,ES可以跟skywalking公用一个ES集群。

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: fluentd-config
      namespace:
    data:
      fluent.conf: |
        <match $labels(reportlog=es)>
          @type elasticsearch
          host elasticsearch-master-headless.monitoring.svc
          port 9200
          logstash_format true
          buffer_type memory
          buffer_chunk_limit 1M
          buffer_queue_limit 32
          flush_interval 2s
          max_retry_wait 30
          disable_retry_limit
          num_threads 16
          reload_connections "true"
        </match>
    

    这里要推荐一个kibana的插件logtrail,它可以让我们像tail -f 那样的查看日志,还支持按照服务名、实例进行过滤等功能,非常好用,但是要注意kibana、es以及logtrail插件的版本不能差一个大版本,我是用的es版本是6.8.6、kibana版本是6.8.12、logtrail插件版本是6.8.12。


    image.png

    这是logrtail的配置文件logtrail.json

    {
        "index_patterns" : [
          {
            "es": {
              "default_index": "logs*",
              "allow_url_parameter": false
            },
            "tail_interval_in_seconds": 10,
            "es_index_time_offset_in_seconds": 0,
            "display_timezone": "local",
            "display_timestamp_format": "MMM DD HH:mm:ss",
            "max_buckets": 500,
            "default_time_range_in_days" : 0,
            "max_hosts": 100,
            "max_events_to_keep_in_viewer": 5000,
            "fields" : {
              "mapping" : {
                  "timestamp" : "@timestamp",
                  "display_timestamp" : "@timestamp",
                  "hostname" : "kubernetes.container_name",
                  "program": "kubernetes.pod_name",
                  "message": "log"
              },
              "message_format": "{{{log}}}",
              "keyword_suffix" : "keyword"
            },
            "color_mapping" : {
                "field": "loglevel",
                "mapping": {
                    "ERROR": "#FF0000",
                    "WARN": "#FFEF96",
                    "DEBUG": "#B5E7A0",
                    "TRACE": "#CFE0E8",
                    "INFO": "#339999"
                }
            }
          }
        ]
    }
    

    监控告警

    这个其实没什么好多选的,就prometheus+grafana+alertmanager即可,绝对是不二之选,这个我就不想描述太多了,主要讲一下如何把apisix集成到prometheus里面。首先在apisix里面设置一下metric的暴露地址,并且把prefer_name设置为true。

    ......
    plugin_attr:
      prometheus:
        prefer_name: true
        export_addr:
          ip: 0.0.0.0
          port: 9091
    

    然后添加一个svc,通过svc来访问pod的metric

    apiVersion: v1
    kind: Service
    metadata:
      name: apisix-metric-svc
      namespace: apisix
      labels:
        app: apisix-metric-svc
    spec:
      ports:
        - name: http-metric
          protocol: TCP
          port: 80
          targetPort: 9091
      selector:
        app.kubernetes.io/instance: apisix
        app.kubernetes.io/name: apisix
      type: ClusterIP
    

    接着添加一个ServiceMonitor资源,让prometheus去获取apisix的指标信息。

    apiVersion: monitoring.coreos.com/v1
    kind: ServiceMonitor
    metadata:
      labels:
        app: apisix-monitor
        release: prometheus
      name: apisix-apisix-metric
      namespace: monitoring
    spec:
      endpoints:
        - interval: 60s
          path: /apisix/prometheus/metrics
          port: http-metric
      namespaceSelector:
        matchNames:
          - apisix
      selector:
        matchLabels:
          app: apisix-metric-svc
    

    最后再在grafana中导入一个apisix的dashboard就可以查看到从apisix进入集群的所有请求指标了。


    image.png

    在线调试

    相信还是有很多人想要在线调试代码的,虽然这种操作不适合在生产环境中进行,但是在开发以及测试环境还是可以的。针对java技术栈的人,我提供下面两种在线调试的思路供大家参考:

    • 容器启动的时候添加-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005,然后通过Lens把pod的5005端口暴露到本地,IDEA直接attach上去进行调试。

    • 通过telepherence工具,把容器的流量劫持到本地进行调试。

    第一种方式比较简单,具体的启动脚本如下:

    #!/bin/sh
    echo 'ready to start service'
    if [ x"${JAVA_ENABLE_DEBUG}" != x ] && [ "${JAVA_ENABLE_DEBUG}" != "false" ] 
    then
        java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 /home/*.jar
    else
        java ${JAVA_OPTS} -jar /home/*.jar
    fi
    

    通过获取环境变量的方式决定是否启动debug端口。把这个sh文件打包到镜像里面,然后通过这个脚本去启动jar即可。

    第二种方式是通过telepherence工具的方式,这个就需要用到config文件,下面是对应的用户需要的权限

    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: test                                   # Update value for appropriate user name
      namespace: ambassador                                # Traffic-Manager is deployed to Ambassador namespace
    ---
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name:  telepresence-role
    rules:
    - apiGroups:
      - ""
      resources: ["pods"]
      verbs: ["get", "list", "create", "watch", "delete"]
    - apiGroups:
      - ""
      resources: ["services"]
      verbs: ["update"]
    - apiGroups:
      - ""
      resources: ["pods/portforward"]
      verbs: ["create"]
    - apiGroups:
      - "apps"
      resources: ["deployments", "replicasets", "statefulsets"]
      verbs: ["get", "list", "update"]
    - apiGroups:
      - "getambassador.io"
      resources: ["hosts", "mappings"]
      verbs: ["*"]
    - apiGroups:
      - ""
      resources: ["endpoints"]
      verbs: ["get", "list", "watch"]
    ---
    kind: RoleBinding                                      # RBAC to access ambassador namespace
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: t2-ambassador-binding
      namespace: ambassador
    subjects:
    - kind: ServiceAccount
      name: test                                     # Should be the same as metadata.name of above ServiceAccount
      namespace: ambassador
    roleRef:
      kind: ClusterRole
      name: telepresence-role
      apiGroup: rbac.authorization.k8s.io
    ---
    kind: RoleBinding                                      # RoleBinding T2 namespace to be intecpeted
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: telepresence-test-binding                      # Update "test" for appropriate namespace to be intercepted
      namespace: default                                      # Update "test" for appropriate namespace to be intercepted
    subjects:
    - kind: ServiceAccount
      name: test                                      # Should be the same as metadata.name of above ServiceAccount
      namespace: ambassador
    roleRef:
      kind: ClusterRole
      name: telepresence-role
      apiGroup: rbac.authorization.k8s.io
    ---
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name:  telepresence-namespace-role
    rules:
    - apiGroups:
      - ""
      resources: ["namespaces"]
      verbs: ["get", "list", "watch"]
    - apiGroups:
      - ""
      resources: ["services"]
      verbs: ["get", "list", "watch"]
    ---
    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: telepresence-namespace-binding
    subjects:
    - kind: ServiceAccount
      name: test                                    # Should be the same as metadata.name of above ServiceAccount
      namespace: ambassador
    roleRef:
      kind: ClusterRole
      name: telepresence-namespace-role
      apiGroup: rbac.authorization.k8s.io
    

    具体的操作指令可以从官网上去查。之前版本的telepherence用起来蛮简单,现在感觉越来越难用了,有兴趣的可以去深入研究一下。

    服务更新策略

    一般来说服务上线之后我们都不希望停机维护,而是无感升级,下面提供两种情况下的升级思路

    • 在线升级

    特点:可以无缝升级,升级的同时不影响实际的使用,适合一些小版本的迭代以及hotfix

    要求:服务在线升级采用金丝雀方案,要求新的版本一定要兼容旧的版本

    • 离线升级

    特点:升级的时候需要停止线上的服务,无法对外提供服务,适合大版本更新

    要求:需要在网关上把对应服务的流量断开,整体升级完成并测试没有什么大问题之后重新开放流量

    在线升级更新步骤

    1、更新对应服务的deployment-canary.yml文件,把版本号更新到新的版本,并将replica设置成1,跟已经在运行的容器比例大概会是1:10的比例,server会把不到10%的流量导入到新的服务。

    2、等服务启动之后需要等服务稳定运行至少1天,期间可以适当的增加replica数量,每次递增的时间间隔保证在6h以上,每次递增数量以1个为准。

    3、升级期间需要增加对链路和日志的观测,链路一旦出现问题立刻把replica设置成0,停止升级,待解决问题之后从第1步重新开始。

    4、等服务稳定运行之后更新deployment.yml中的版本号,等待服务慢慢滚动升级,目前设置了一次只升级一个pod,如果升级途中链路出现任何问题或者日志有错误则马上进行回滚操作,待解决问题之后从第1步开始重新升级。

    5、等deployment.yml中的pod全部升级完成之后,将deployment-canary.yml中的replica设置为0。

    离线升级更新步骤

    1、对于需要大版本升级的服务,需要先评估其本身的影响以及与其相关的服务影响,明确影响范围。

    2、定好升级的时间点,提前告知用户。

    3、在网关中找到对应的服务,启用ip-restriction插件,设置IP白名单,所有白名单以外的流量全部拦截,只返回固定的结果。

    4、开始升级对应服务的deployment.yml文件,如果涉及到数据库表结构变更的需要提前备份数据库。

    5、全部升级完成之后用白名单进行整体测试,跑对应的test case,如果遇到严重问题确认无法马上解决的,马上进行版本的回滚以及数据库数据的回滚。

    6、如果test case跑下来没有问题,则把consumer-restriction插件停用,把服务向大众开放。

    7、如果有一些小的问题,则参考在线升级的方式进行hotfix。

    # deployment-canary.yml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-canary
    spec:
      replicas: 0
      selector:
        matchLabels:
          app: nginx
          role: canary
      template:
        metadata:
          labels:
            app: nginx
            role: canary
        spec:
          volumes:
            - name: time
              hostPath:
                path: /etc/localtime
                type: ''
          containers:
            - name: nginx
              image: nginx:latest
              env:
                - name: TZ
                  value: Asia/Shanghai
              volumeMounts:
                - name: time
                  mountPath: /etc/localtime
              ports:
                - name: http
                  containerPort: 80
              readinessProbe:
                httpGet:
                  path: /
                  port: http
                initialDelaySeconds: 30
                periodSeconds: 15
                failureThreshold: 6
              livenessProbe:
                httpGet:
                  path: /
                  port: http
                initialDelaySeconds: 60
                periodSeconds: 30
      revisionHistoryLimit: 2
    
    # deployment.yml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
      labels:
        app: nginx
        role: release
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nginx
          role: release
      template:
        metadata:
          labels:
            app: nginx
            role: release
        spec:
          volumes:
            - name: time
              hostPath:
                path: /etc/localtime
                type: ''
          containers:
            - name: nginx
              image: nginx:latest
              env:
                - name: TZ
                  value: Asia/Shanghai
              volumeMounts:
                - name: time
                  mountPath: /etc/localtime
              ports:
                - name: http
                  containerPort: 80
              readinessProbe:
                httpGet:
                  path: /
                  port: http
                initialDelaySeconds: 30
                periodSeconds: 15
                failureThreshold: 6
              livenessProbe:
                httpGet:
                  path: /
                  port: http
                initialDelaySeconds: 60
                periodSeconds: 30
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxUnavailable: 1
          maxSurge: 1
      revisionHistoryLimit: 3
      progressDeadlineSeconds: 600
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: nginx-svc
      labels:
        app: nginx-svc
    spec:
      ports:
        - protocol: TCP
          port: 80
          targetPort: 80
      selector:
        app: nginx
      type: ClusterIP
      sessionAffinity: None
    
    

    总结

    至此,包括K8s集群、网络、存储、CICD、日志、监控告警、调试、服务更新,基本上涵盖了DevOps所有涉及到的内容,涉及到了非常的组件,其中不乏要自己写一些代码来把各个组件粘合起来,所以要把K8s顺畅的用起来并非一件简单的事情。

    暂时只是把工作中的一些大致的东西记录了下来,但由于最近公司996赶项目,空闲时间不是很多,写的比较粗,实际上还有很多的细节没有写,后续有时间会慢慢补上。

    后面还会介绍几个基于K8s的数据库方案,包括mysql和pgsql相关的内容。后续等工作没那么忙了也考虑把一些组件进行整合,目标是整合成一个完整的DevOps平台,就是不知道什么时候能得偿所望了。

    相关文章

      网友评论

          本文标题:Kubernetes深度实践(七)

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