技术漫谈 |通过Kustomize配置sidecar方式的日志收集


本文简单介绍了Kubernetes中收集pod日志的几种方案,然后通过ECK快速搭建起一套日志系统,实现了本文介绍的第一种日志方案。在实现第二种日志方案时,我们引入了Kustomize这个k8s专用的配置管理工具,将应用本身的部署文件和sidecar日志容器的相关配置分离开来,使k8s的部署配置文件易于维护。

一、Kubernetes收集pod日志的方案

对于K8S集群中pod日志的收集,一般有三种解决方案:
1)在Node上部署一个日志收集程序:也就是在node级别进行日志收集。一般使用DaemonSet部署在每个node中。这种方式优点是耗费资源少,因为只需部署在节点,且对应用无侵入。缺点是只适合容器内应用日志必须都是标准输出。

1.png


2)在pod中附加专用日志收集的容器:也就是在pod中跟随应用容器起一个日志处理容器,一种是直接将应用容器的日志收集并输出到标准输出(叫做Streaming sidecar container),如下图:

2.png


还有一种是将应用容器日志直接输出到日志收集后端,也就是每一个pod中都起一个日志收集agent(比如filebeat)。如下图:

3.png


这种方式的优点是可以收集多种形式的日志(比如文件,socket等),缺点是耗费资源较多,每个pod都要起一个日志收集容器。

3)应用程序直接推送日志:这种方式应用直接将日志推送到存储后端:

4.png


这种方式的优点是无需额外的日志收集工具,缺点是需要侵入应用,增加应用的配置复杂度。

在下文我们同时实现了第一种和第二种sidecar container with a logging agent方案。
第一种方案我们通过ECK(https://www.elastic.co/cn/elastic-cloud-kubernetes)搭建起es集群和kibana UI,通过fluentd将pod标准输出的日志收集到es集群中,同时fluentd对接K8S的apiserver自动给日志添加元数据信息,使我们非常方便地分辨日志来自于哪个pod。但是如果应用本身将日志输出到容器的内部,并且我们需要定制化日志收集程序的配置,比如合并多行日志时,第一种日志方案便无能为力了。

这时,我们可以通过第二种日志方案,以sidecar的方式将日志收集到es当中,并且可以自由配置容器收集工具,来实现各种高级日志功能。我们在配置sidecar容器收集工具时,使用了Kustomize工具,来将应用本身的部署文件和sidecar日志容器的相关配置分离开来,实现了分离关注点,使相关信息配置文件更加清晰,易于维护。

二、通过ECK快速部署EFK日志系统

下面我们首先通过ECK部署es+kibana+fluned。您可以使用breeze(https://github.com/wise2c-devops/breeze)里的ECK组件来部署,也可以参考ECK官网上的文档来进行部署。这里我们给出部署所需的yaml文件,您只需apply这些文件即可完成EFK日志收集系统的快速搭建。
1、安装ECK的crd,RBAC规则及eck-operator:
kubectl apply -f https://download.elastic.co/downloads/eck/1.1.2/all-in-one.yaml


在安装过程中可以查看eck-operator的日志,来确认其是否启动正常:
kubectl -n elastic-system logs -f statefulset.apps/elastic-operator


2、部署es集群:
cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1beta1
kind: Elasticsearch
metadata:
name: quickstart
spec:
version: 7.8.0
http:
tls:
  selfSignedCertificate:
    disabled: true
nodeSets:
- name: default
count: 3
config:
  node.master: true
  node.data: true
  node.ingest: true
  node.store.allow_mmap: false
podTemplate:
  spec:
    volumes:
    - name: elasticsearch-data
      emptyDir: {}
EOF


上面的命令会部署起一个3节点的es集群,不过存储是使用的emptyDir的方式,在pod被删除后,其数据也会一起被删除,所以这种方式是用于测试环境快速搭建es集群。集群搭建好后,可以通过如下命令获取es的elastic用户的密码:

kubectl get secret quickstart-es-elastic-user -o=jsonpath='{.data.elastic}' | base64 --decode; echo

此密码也可以用于后面搭建的kibana页面的登录。

3、部署kibana:
cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1beta1
kind: Kibana
metadata:
name: quickstart
spec:
version: 7.8.0
http:
tls:
  selfSignedCertificate:
    disabled: true
count: 1
elasticsearchRef:
name: quickstart
EOF


可以通过如下命令获取到访问kibana的ClusterIp:
kubectl get service quickstart-kb-http


您可以edit上述service将其改为NodePort的方式,从而可以通过NodePort的端口访问到kibana的web页面,用户名密码在上一步已经获取。

4、部署flunetd:

cat <<EOF | kubectl apply -f -

apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd

namespace: kube-system

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: fluentd
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- pods
- namespaces
verbs:
- get
- list

- watch

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fluentd
roleRef:
kind: ClusterRole
name: fluentd
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: fluentd

namespace: kube-system

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: kube-system
labels:
k8s-app: fluentd-logging
version: v1
spec:
selector:
matchLabels:
  k8s-app: fluentd-logging
  version: v1
template:
metadata:
  labels:
    k8s-app: fluentd-logging
    version: v1
spec:
  serviceAccount: fluentd
  serviceAccountName: fluentd
  tolerations:
  - key: node-role.kubernetes.io/master
    effect: NoSchedule
  containers:
  - name: fluentd
    image: 192.168.206.156/library/fluentd-kubernetes-daemonset:v1-debian-elasticsearch
    env:
      - name:  FLUENT_ELASTICSEARCH_HOST
        value: "quickstart-es-http.default.svc.cluster.local"
      - name:  FLUENT_ELASTICSEARCH_PORT
        value: "9200"
      - name: FLUENT_ELASTICSEARCH_SCHEME
        value: "http"
      - name: FLUENT_ELASTICSEARCH_USER
        value: "elastic"
      - name: FLUENT_ELASTICSEARCH_PASSWORD
        value: "change-to-your-elstic-users-password"
    resources:
      limits:
        memory: 200Mi
      requests:
        cpu: 100m
        memory: 200Mi
    volumeMounts:
    - name: varlog
      mountPath: /var/log
    - name: varlibdockercontainers
      mountPath: /var/lib/docker/containers
      readOnly: true
  terminationGracePeriodSeconds: 30
  volumes:
  - name: varlog
    hostPath:
      path: /var/log
  - name: varlibdockercontainers
    hostPath:
      path: /var/lib/docker/containers

EOF


部署fluentd时只需注意将上述yaml文件里的FLUENT_ELASTICSEARCH_PASSWORD的值修改为您es集群里实际的密码。

这样搭建起来的日志系统便实现了我们日志方案一。下面我们来实现第二种sidecar container with a logging agent的方案。在实现此方案时,我们引入了Kustomize这个工具来管理相关的yaml文件,首先我们简单了解一下Kustomize。

三、Kustomize是什么?

如果您想在Kubernetes 集群中部署应用,您可能会拷贝一些包含Kubernetes API对象的YAML文件,并且根据需求来修改这些文件,通过这些YAML文件来定义Kubernetes配置。

在Kustomize出现之前,Kubernetes管理应用的方式主要是通Helm或者上层Paas来完成。这些工具通常通过特定领域配置语言(DSL,如Go template、jsonnet)来维护并管理应用,并且需要参数化模板方式(如helm)来自定义配置,这需要学习复杂的DSL语法极其容易出错。

Kustomize工具提供了一个全新的、纯粹的声明式的方法来定制kubernetes配置,遵循并利用我们熟悉且精心设计的Kubernetes API。Kustomize是Kubernetes原生的配置管理工具,以无模板方式来定制应用的配置。它使用k8s原生概念帮助创建并复用资源配置(YAML),允许用户以一个应用描述文件(YAML文件)为基础(Base YAML),然后通过Overlay的方式生成最终部署应用所需的描述文件。

四、Kustomize 解决了什么痛点?

一般应用都会存在多套部署环境:开发环境、测试环境、生产环境,多套环境意味着存在多套K8S应用资源YAML。而这么多套YAML之间只存在微小配置差异,比如镜像版本不同、Label不同等,而这些不同环境下的YAML经常会因为人为疏忽导致配置错误。再者,多套环境的YAML维护通常是通过把一个环境下的YAML拷贝出来然后对差异的地方进行修改。Kustomize通过以下几种方式解决了上述问题:
  • Kustomize通过Base & Overlays方式方式维护不同环境的应用配置。
  • Kustomize使用patch方式复用Base配置,并在Overlay描述与Base应用配置的差异部分来实现资源复用。
  • Kustomize管理的都是Kubernetes原生YAML文件,不需要学习额外的DSL语法。


五、通过Kustomize实现filebeat sidecar配置

关于Kustomize的简单教程,网上有很多,我们这里不再重复,您可以参考官方的helloWorld教程来实际操作一下。下面我们就来通过Kustomize实现filebeat sidecar的配置。

1、首先创建一个工作空间:
mkdir demo
cd demo


2、创建用于保存应用部署文件的目录,命名为base,并在里面放置实际应用的部署文件,这里我们只放置一个最简单的deployment文件:
mkdir base
cat > base/deploy.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: configserver
name: configserver
spec:
replicas: 1
selector:
matchLabels:
  app: configserver
template:
metadata:
  labels:
    app: configserver
spec:
  containers:
  - image: registry.cn-shenzhen.aliyuncs.com/wangyijie/configserver:v1
    name: configserver
    ports:
    - containerPort: 8080
    args:
    - java
    - -Dspring.cloud.config.server.git.uri=https://gitee.com/wise2c-wangyijie/config-repo.git
    - -Dlogging.file.name=/tmp/configserver.log
    - -jar
    - /configserver.jar
    volumeMounts:
    - name: log-volume
      mountPath: /tmp
  volumes:
  - name: log-volume
    emptyDir: {}
EOF


可以看到,这就是一个java的应用程序,在实际的情况下,您的应用可能还需创建service,configmap等k8s对象组成一个更加完整的应用。此应用将日志输出到了pod容器内部的/tmp目录中。在这个deploy文件中,没有任何关于filebeat sidecar的配置信息。

接下来创建Kustomize的配置文件:

cat > base/kustomization.yaml << EOF
resources:
- deploy.yaml
EOF

文件内容非常简单,就2行,指定了该目录下的资源配置文件。

3、创建用于保存filebeat sidecar部署及配置文件的目录,命名为filebeat-sidecar,并在里面放置filebeat的deployment及filebeat的配置文件:
mkdir filebeat-sidecar
cat >filebeat-sidecar/sidecar.yaml<< EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: configserver
spec:
template:
spec:
  containers:
  - name: filebeat
    image: docker.elastic.co/beats/filebeat:7.8.0
    args: [
"-c", "/etc/filebeat/filebeat.yml",
"-e",
    ]
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          fieldPath: metadata.name
    - name: POD_NAMESPACE
      valueFrom:
        fieldRef:
          fieldPath: metadata.namespace
    - name: POD_NODENAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName
    volumeMounts:
    - mountPath: /tmp
      name: log-volume
    - name: config-vol
      mountPath: /etc/filebeat     
  volumes:
  - name: log-volume
    emptyDir: {}
  - name: config-vol
    configMap: 
      name: filebeat-configmap
EOF


这个deploy通过downward api取到了其所在pod的元数据信息,添加到了pod的环境变量当中;另外pod也通过一个叫做filebeat-configmap的configmap保存filebeat的配置信息(配置文件稍后给出),该配置使filebeat其从emptyDir类型存储卷的/tmp目录来获取日志,下面给出此配置文件:
cat >filebeat-sidecar/filebeat.yml<< EOF
filebeat.inputs:
- type: log
paths: 
- /tmp/*.log
fields: 
podname: '${POD_NAME:}'
podnamespace: '${POD_NAMESPACE:}'
podnodename: '${POD_NODENAME:}'

output.elasticsearch:
hosts: ['quickstart-es-http:9200']
username: 'elastic'
password: 'change-to-your-elstic-users-password'
EOF


配置文件也很简单,它从/tmp目录下取得所有以.log结尾的文件的日志;并且给日志添加了fields的信息,标明日志是从哪个namespace的哪个pod下获取的;最后将日志输出到了我们在第二部分搭建的es集群当中。

最后给出Kustomize的配置文件:
cat >filebeat-sidecar/kustomization.yaml<< EOF
bases:
- ../base
patchesStrategicMerge:
- sidecar.yaml
configMapGenerator:
- name: filebeat-configmap
files:
- filebeat.yml
EOF


此配置文件,通过patchesStrategicMerge策略,将此目录下的sidecar.yaml的deployment和base目录下的相同名称的deployment对象合并,生成一个带sidecar容器的deployment;另外通过configmapGenerator,生成了一个configmap,供deploy使用。

通过tree demo/命令查看当前的目录结构,如下:
demo/
├── base
│   ├── deploy.yaml
│   └── kustomization.yaml
└── filebeat-sidecar
├── filebeat.yml
├── kustomization.yaml
└── sidecar.yaml


我们可以通过kubectl kustomize filebeat-sidecar/ 命令查看生成后yaml文件,结果如下(结果做了省略):
apiVersion: v1
kind: ConfigMap
metadata:   
name: filebeat-configmap-7dtkh7km6k

...

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: configserver
name: configserver
spec:
...
template:
...
spec:
...
  volumes:
  - emptyDir: {}
    name: log-volume
  - configMap:
      name: filebeat-configmap-7dtkh7km6k
    name: config-vol


我们发现,生成的合并后的yaml文件里,configmap的名称被自动添加了一个哈希后缀,这样实现的目的是ConfigMap中的更改将导致哈希值更改和滚动更新。

我们可以直接通过kubectl apply -k filebeat-sidecar/ 命令来将这个生成的yaml提交到k8s当中。可以看到,filebeat已经将应用日志收集到了es当中:

5.png


下面我们来修改一下filebeat.yml这个配置文件,添加合并多行日志的配置,修改后的配置文件如下:
cat >filebeat-sidecar/filebeat.yml
filebeat.inputs:
- type: log
paths: 
- /tmp/*.log
fields: 
podname: '${POD_NAME:}'
podnamespace: '${POD_NAMESPACE:}'
podnodename: '${POD_NODENAME:}'
multiline.pattern: '^\d{4}-\d{1,2}-\d{1,2}'
multiline.negate: true
multiline.match: after
multiline.timeout: 10s  

output.elasticsearch:
hosts: ['quickstart-es-http:9200']
username: 'elastic'
password: 'change-to-your-elstic-users-password'


再通过kubectl kustomize filebeat-sidecar/ 命令重新查看一下生成的yaml文件,发现configmap名称的hash后缀发生了变动:
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-configmap-tm9867k666

...

apiVersion: apps/v1
kind: Deployment
metadata:
...
name: configserver
...
  volumes:
  - emptyDir: {}
    name: log-volume
  - configMap:
      name: filebeat-configmap-tm9867k666
    name: config-vol


这时再从新执行kubectl apply -k filebeat-sidecar/ 命令,便会创建一个新的configmap,同时触发deployment的滚动更新。

如果新的配置不符合要求,可以通过kubectl rollout undo deploy/configserver 命令来回滚到之前的配置,不过此时filebeat-sidecar/filebeat.yml这个配置文件还需要手动还原到之前的配置。或者我们不使用回滚命令,只修改filebeat-sidecar/filebeat.yml配置文件,然后一直使用kubectl apply -k filebeat-sidecar/命令,使deployment一直和最新的配置保持一致。

通过上面的demo可以看出,我们将应用的部署文件与filebeat的部署配置分成了两个独立的部分,使配置文件更加清晰,易于管理。如果我们想替换filebeat,使用fluentbit作为收集日志的sidecar的logging agent,我们只需要仿照filebeat的方式,再创建一个新的fluentbit-sidecar的文件夹,在里面添加修改fluentbit的部署配置文件,而无需对原有应用的部署文件做任何修改。
当然,如果您的应用将日志写到了容器内部的文件当中,同时对应用日志没有过于复杂的需求,只需要把应用日志收集到es里,可以使用本文第二种方案的Streaming sidecar container的方式,部署一个sidecar容器,将输出到容器内的日志文件读到sidecar容器的标准输出,这时EFK的日志系统就会收集到标准输出的日志,同时也会自动添加日志的元数据信息,这样就省略了filebeat或fluentbit的配置,更加简洁。

原创作者:杨冬
原文链接:https://mp.weixin.qq.com/s/0k018a3MDzl-v4S3DLeIww

关于睿云智合
深圳睿云智合科技有限公司成立于2012年,总部位于深圳。早期专注于为中国金融保险等大型企业提供创新技术、电子商务、CRM等领域专业咨询服务。核心骨干人员全部为来自金融、科技行业知名企业资深业务和技术专家。

自2016年始,睿云智合率先将容器技术引进到中国保险行业客户,此后,公司组建了专业的云原生技术产品研发和实施服务团队,以自主可控与开源技术的融合为产品设计宗旨,不断深耕软件定义和云原生领域创新技术以及国内企业数字化转型服务市场。并分别在成都、深圳设立了研发中心,在北京、上海设立了分支机构。旨在帮助中国企业客户将云原生趋势技术应用于企业新一代IT架构数字化底座的建设,并与生态合作伙伴一起为企业客户提供云原生数字化底座衍生出的PaaS云平台、工业互联网、大数据中心、AI机器学习平台以及区块链BaaS平台等解决方案的落地。为企业数字化转型以及业务发展赋能。

此外,凭借多年来在呼叫中心领域的业务经验与技术积累,睿云智合率先在业界推出基于开源软交换技术的微服务架构多媒体数字化业务平台,将语音、视频、webchat、微信、微博等多种客户接触渠道集成,实现客户统一接入、精准识别、智能路由的CRM策略,并通过容器化治理方式来支持平台的全生命周期管理,显著提升了数字化业务处理的灵活、高效、弹性、稳定等特性。

睿云智合是Linux与CNCF云原生基金会会员,是全球首批Kuberentes官方认证服务提供商 (KCSP),也是Kubernetes官方认证平台产品提供商。睿云智合依托趋势IT技术为企业客户的平台赋能数字化转型战略提供了包括产品、技术服务、咨询、培训等服务。通过数字化底座+行业解决方案为全国金融、政企、医疗、运营商、制造、能源、教育以及互联网等各个行业企业和用户打造一个全面感知、可靠传输、智能处理、精准决策的产业能力底座。帮助企业客户打通数据链接,加速业务创新,赋能产业竞争力。

0 个评论

要回复文章请先登录注册