Kubernetes深度實踐(七)

CICD搭建完成之后又迎來新的問題,鏈路追蹤、日志、監(jiān)控告警、在線調(diào)試、服務(wù)更新策略,先從鏈路追蹤說起。

鏈路追蹤

鏈路追蹤的話有很多選項,比如zipkin、skywalking等,由于我們公司是基于java的技術(shù)路線的,所以我選擇了skywalking來做鏈路追蹤,通過sidecar的方式將其無感的注入到服務(wù)中。

skywalking可以通過helm直接安裝,具體安裝流程就不詳細(xì)說明了,我是把監(jiān)控告警這些工具全都裝在了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

這樣就可以在對代碼零侵入的情況下實現(xiàn)對鏈路的追蹤以及運行指標(biāo)的收集。

apisix也支持skywalking插件,需要在配置文件中進(jìn)行如下設(shè)置,然后針對每個需要收集的路由單獨啟用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也加了進(jìn)去。我使用的網(wǎng)關(guān)是Apisix,它同樣支持skywalking插件,可以收集鏈路以及日志信息(skywalking日志收集有版本要求,需要apisix版本>=2.12)。但說句實在話,skywalking的日志展示功能實在有點一言難盡,只能說能用,而且收集的日志全都是base64編碼的,這個可以說是非常的坑,即使想要用kibana來進(jìn)行展示也會有問題,雖然kibana支持base64解碼,但是解碼出來的不支持關(guān)鍵字搜索,畢竟skywalking的強項是鏈路追蹤和收集指標(biāo),所以也不能要求太多。我建議只收集apisix的日志就可以了,服務(wù)的日志通過fluentd這類的工具來收集,然后通過kibana來展示。

我是用的是kube-fluentd-operator,vm出品,直接通過helm進(jìn)行安裝即可,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 那樣的查看日志,還支持按照服務(wù)名、實例進(jìn)行過濾等功能,非常好用,但是要注意kibana、es以及l(fā)ogtrail插件的版本不能差一個大版本,我是用的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"
            }
        }
      }
    ]
}

監(jiān)控告警

這個其實沒什么好多選的,就prometheus+grafana+alertmanager即可,絕對是不二之選,這個我就不想描述太多了,主要講一下如何把apisix集成到prometheus里面。首先在apisix里面設(shè)置一下metric的暴露地址,并且把prefer_name設(shè)置為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的指標(biāo)信息。

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中導(dǎo)入一個apisix的dashboard就可以查看到從apisix進(jìn)入集群的所有請求指標(biāo)了。


image.png

在線調(diào)試

相信還是有很多人想要在線調(diào)試代碼的,雖然這種操作不適合在生產(chǎn)環(huán)境中進(jìn)行,但是在開發(fā)以及測試環(huán)境還是可以的。針對java技術(shù)棧的人,我提供下面兩種在線調(diào)試的思路供大家參考:

  • 容器啟動的時候添加-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005,然后通過Lens把pod的5005端口暴露到本地,IDEA直接attach上去進(jìn)行調(diào)試。

  • 通過telepherence工具,把容器的流量劫持到本地進(jìn)行調(diào)試。

第一種方式比較簡單,具體的啟動腳本如下:

#!/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

通過獲取環(huán)境變量的方式?jīng)Q定是否啟動debug端口。把這個sh文件打包到鏡像里面,然后通過這個腳本去啟動jar即可。

第二種方式是通過telepherence工具的方式,這個就需要用到config文件,下面是對應(yīng)的用戶需要的權(quán)限

---
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

具體的操作指令可以從官網(wǎng)上去查。之前版本的telepherence用起來蠻簡單,現(xiàn)在感覺越來越難用了,有興趣的可以去深入研究一下。

服務(wù)更新策略

一般來說服務(wù)上線之后我們都不希望停機維護(hù),而是無感升級,下面提供兩種情況下的升級思路

  • 在線升級

特點:可以無縫升級,升級的同時不影響實際的使用,適合一些小版本的迭代以及hotfix

要求:服務(wù)在線升級采用金絲雀方案,要求新的版本一定要兼容舊的版本

  • 離線升級

特點:升級的時候需要停止線上的服務(wù),無法對外提供服務(wù),適合大版本更新

要求:需要在網(wǎng)關(guān)上把對應(yīng)服務(wù)的流量斷開,整體升級完成并測試沒有什么大問題之后重新開放流量

在線升級更新步驟

1、更新對應(yīng)服務(wù)的deployment-canary.yml文件,把版本號更新到新的版本,并將replica設(shè)置成1,跟已經(jīng)在運行的容器比例大概會是1:10的比例,server會把不到10%的流量導(dǎo)入到新的服務(wù)。

2、等服務(wù)啟動之后需要等服務(wù)穩(wěn)定運行至少1天,期間可以適當(dāng)?shù)脑黾觬eplica數(shù)量,每次遞增的時間間隔保證在6h以上,每次遞增數(shù)量以1個為準(zhǔn)。

3、升級期間需要增加對鏈路和日志的觀測,鏈路一旦出現(xiàn)問題立刻把replica設(shè)置成0,停止升級,待解決問題之后從第1步重新開始。

4、等服務(wù)穩(wěn)定運行之后更新deployment.yml中的版本號,等待服務(wù)慢慢滾動升級,目前設(shè)置了一次只升級一個pod,如果升級途中鏈路出現(xiàn)任何問題或者日志有錯誤則馬上進(jìn)行回滾操作,待解決問題之后從第1步開始重新升級。

5、等deployment.yml中的pod全部升級完成之后,將deployment-canary.yml中的replica設(shè)置為0。

離線升級更新步驟

1、對于需要大版本升級的服務(wù),需要先評估其本身的影響以及與其相關(guān)的服務(wù)影響,明確影響范圍。

2、定好升級的時間點,提前告知用戶。

3、在網(wǎng)關(guān)中找到對應(yīng)的服務(wù),啟用ip-restriction插件,設(shè)置IP白名單,所有白名單以外的流量全部攔截,只返回固定的結(jié)果。

4、開始升級對應(yīng)服務(wù)的deployment.yml文件,如果涉及到數(shù)據(jù)庫表結(jié)構(gòu)變更的需要提前備份數(shù)據(jù)庫。

5、全部升級完成之后用白名單進(jìn)行整體測試,跑對應(yīng)的test case,如果遇到嚴(yán)重問題確認(rèn)無法馬上解決的,馬上進(jìn)行版本的回滾以及數(shù)據(jù)庫數(shù)據(jù)的回滾。

6、如果test case跑下來沒有問題,則把consumer-restriction插件停用,把服務(wù)向大眾開放。

7、如果有一些小的問題,則參考在線升級的方式進(jìn)行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

總結(jié)

至此,包括K8s集群、網(wǎng)絡(luò)、存儲、CICD、日志、監(jiān)控告警、調(diào)試、服務(wù)更新,基本上涵蓋了DevOps所有涉及到的內(nèi)容,涉及到了非常的組件,其中不乏要自己寫一些代碼來把各個組件粘合起來,所以要把K8s順暢的用起來并非一件簡單的事情。

暫時只是把工作中的一些大致的東西記錄了下來,但由于最近公司996趕項目,空閑時間不是很多,寫的比較粗,實際上還有很多的細(xì)節(jié)沒有寫,后續(xù)有時間會慢慢補上。

后面還會介紹幾個基于K8s的數(shù)據(jù)庫方案,包括mysql和pgsql相關(guān)的內(nèi)容。后續(xù)等工作沒那么忙了也考慮把一些組件進(jìn)行整合,目標(biāo)是整合成一個完整的DevOps平臺,就是不知道什么時候能得償所望了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容