Kubernetes深度实践(七)

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平台,就是不知道什么时候能得偿所望了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容