亚马逊AWS官方博客

使用 Fluent Bit 实现集中式容器日志记录

作者:Wesley Pettit 和 Michael Hausenblas

AWS 专为构建师打造。构建师一直致力于寻找优化方法,而应用程序日志记录也是如此。并非所有日志都同等重要。一些日志需要实时分析,而一些日志只需长期存储,以便在需要时进行分析。因此,能够轻松地将日志路由到 AWS 及其合作伙伴提供的各种存储和分析工具非常重要。

正因如此,我们支持在 Fluent Bit 的帮助下创建简单的扩展点,以便将日志从容器化应用程序流式传输到 AWS 和合作伙伴解决方案,从而保留日志并进行分析。新推出的 AWS Fluent Bit 插件以容器映像的形式提供,利用该插件,您可以将日志路由到 Amazon CloudWatch Amazon Kinesis Data Firehose 目标位置(其中包括 Amazon S3、Amazon Elasticsearch ServiceAmazon Redshift)。在本博文中,我们将展示如何在 Amazon ECS 和 EKS 集群上实际运用 Fluent Bit 插件。如果您不熟悉工具,可能还要查看有关 Fluentd 和 Kinesis Firehose 基础知识的教程,并了解 AWS 容器路线图中的相关问题,尤其是 #10#66

日志路由简介

从概念上讲,容器化设置(如 Amazon ECS 或 EKS)中的日志路由如下图所示:

日志路由概念

在上图左侧,描述了日志源(从底部开始):

  1. 主机和控制层面级别由 EC2 实例组成,用于托管容器。您可以直接(或不直接)访问这些实例。例如,对于在 Fargate 上运行的容器,您将不会在 EC2 控制台中看到实例。在此级别,您还将获得 AWS 管理的 EKS 控制层面中的日志。
  2. 容器运行时级别通常包括 Docker 引擎生成的日志,例如 ECS 中的代理日志。这些日志通常对具有基础设施管理角色的人员最有用,但也可以帮助开发人员排查问题。
  3. 应用程序级别用于运行用户代码。此级别会生成与特定应用程序有关的日志,例如有关您自己应用程序中的操作结果的日志条目,或现成应用程序组件(如 NGINX)的应用程序日志。

接下来是路由组件:即 Fluent Bit。该组件负责从所有源读取日志,并将日志记录路由到各个目标位置(也称为日志接收器)。此路由组件需要以某种形式运行,例如作为 Kubernetes pod/ECS 任务中的伴生服务,或者作为主机级守护程序集。

下游日志接收器将日志用于不同的目的并供不同的受众使用。其中包括大量使用案例,从日志分析到确保合规性(日志需要在指定的保留期内存储),当人类用户需要收到事件通知时发出提醒;控制面板日志提供(实时)图表集合,帮助人类用户快速了解系统的整体状态。

了解这些基础知识后,下面我们看一个具体的使用案例:使用 Fluent Bit 实现多集群应用程序的集中式日志记录。Amazon ECS Fluent Bit 守护程序服务 GitHub 存储库中提供了所有容器定义和配置。

集中式日志记录的实际应用:多集群日志分析

为了展示 Fluent Bit 在实际中的应用,我们将跨 Amazon ECS 和 Amazon EKS 集群执行多集群日志分析,并将 Fluent Bit 部署和配置为守护程序集。运行在每个集群中的 NGINX 应用程序生成的应用程序级日志由 Fluent Bit 捕获,并通过 Amazon Kinesis Data Firehose 流式传输到 Amazon S3,然后我们可以在这里使用 Amazon Athena 进行查询:

集中式日志记录演示应用程序设置

Amazon ECS 设置

在 EC2 集群上使用以下用户数据(在我们的示例中,在名为 enable-fluent-log-driver.sh 的文件中)创建 ECS,以便在 ECS 代理中启用 Fluentd 日志驱动程序:


#!/bin/bash
echo "ECS_AVAILABLE_LOGGING_DRIVERS=[\"awslogs\",\"fluentd\"]" >> /etc/ecs/ecs.config

注意:此步骤假设您已安装 ECS CLI

例如,我们在 EC2 集群上创建以下 ECS:

$ ecs-cli up \
          --size 2 \
          --instance-type t2.medium \
          --extra-user-data enable-fluent-log-driver.sh \
          --keypair fluent-bit-demo-key \
          --capability-iam \
          --cluster-config fluent-bit-demo

接下来,我们需要构建一个包含 Fluent Bit 配置的容器映像。为此,我们将创建一个包含以下内容的 Dockerfile

FROM amazon/aws-for-fluent-bit:1.2.0
ADD fluent-bit.conf /fluent-bit/etc/
ADD parsers.conf /fluent-bit/etc/

注意:此次未遵循良好的安全实践,没有定义 USER,而是让其以 root 身份运行。这是有意而为,因为 Fluent Bit 当前需要以 root 身份运行。

上述 Dockerfile 又依赖两个配置文件:第一个是 fluent-bit.conf

[SERVICE]
    Parsers_File parsers.conf

[INPUT]
    Name forward
    unix_path /var/run/fluent.sock

[FILTER]
    Name parser
    Match **
    Parser nginx
    Key_Name log

[OUTPUT]
    Name firehose
    Match **
    delivery_stream my-stream
    region some-region

其次是 parsers.conf,用于解析 NGINX 日志:

[PARSER]
    Name   nginx
    Format regex
    Regex ^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")? \"-\"$ Time_Key time Time_Format %d/%b/%Y:%H:%M:%S %z

现在,我们将构建自定义容器映像,并将其推送到一个名为 fluent-bit-demo 的 ECR 存储库:

# build the custom image
$ docker build --tag fluent-bit-demo:0.1 .
# push to Amazon ECR:
$ ecs-cli push fluent-bit-demo:0.1

访问 ECR 控制台,确认映像构建和推送是否成功;您应会看到与下面类似的内容:

包含自定义 Fluent Bit 容器映像的 Amazon ECR 存储库

现在,我们可以使用上述容器映像启动包含守护程序计划策略的 ECS 服务,以将自定义配置的 Fluent Bit 部署到集群中:

$ aws cloudformation deploy \
      --template-file ecs-fluent-bit-daemonset.yml \
      --stack-name ecs-fluent-bit-daemon-service \
      --parameter-overrides \
      EnvironmentName=fluentbit-daemon-service \
      DockerImage=XXXXXXXXXXXX.dkr.ecr.us-west-2.amazonaws.com/fluent-bit-demo:0.1 \
      Cluster=fluent-bit-demo \
      --region $(aws configure get region) \
      --capabilities CAPABILITY_NAMED_IAM

在 ECS 控制台中,您现在应会看到与下面类似的内容:

现在,我们可以基于以下任务定义,启动 ECS 服务,即运行 NGINX:

{
    "taskDefinition": {
        "taskDefinitionArn": "arn:aws:ecs:us-west-2:XXXXXXXXXXXX:task-definition/nginx:1",
        "containerDefinitions": [
            {
                "name": "nginx",
                "image": "nginx:1.17",
                "memory": 100,
                "essential": true,
                "portMappings": [
                    {
                        "hostPort": 80,
                        "protocol": "tcp",
                        "containerPort": 80
                    }
                ],
                "logConfiguration": {
                    "logDriver": "fluentd",
                    "options": {
                        "fluentd-address": "unix:///var/run/fluent.sock",
                        "tag": "logs-from-nginx"
                    }
                }
            }
        ],
        "family": "nginx"
    }
}

创建上述任务定义之后,现在应该会在 ECS 控制台中看到以下内容:

Amazon ECS 任务定义

现在,我们可以基于上述任务定义启动 ECS 服务:

$ aws ecs create-service \
      --cluster fluent-bit-demo \
      --service-name nginx-svc \
      --task-definition nginx:1 \
      --desired-count 1

如果一切顺利,您应该会在 ECS 控制台中看到与下面类似的内容:

Amazon ECS 服务

至此,我们已设置好 ECS 部分。现在,我们在 Amazon EKS 上运行的 Kubernetes 集群中配置相同的设置。

Amazon EKS 设置

使用 eksctl 创建一个名为 fluent-bit-demo 的 Amazon EKS 集群,如 EKS 文档中所示,然后创建一个名为 eks-fluent-bit-daemonset-policy.json 的策略文件(来源),其中包含以下内容:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "firehose:PutRecordBatch"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:PutLogEvents",
            "Resource": "arn:aws:logs:*:*:log-group:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:DescribeLogStreams",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:log-group:*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "*"
        }
    ]
}

要将此策略文件附加到 EC2 工作线程节点上的 EKS,请按以下顺序执行操作:

$ STACK_NAME=$(eksctl get nodegroup --cluster fluent-bit-demo -o json | jq -r '.[].StackName')

$ INSTANCE_PROFILE_ARN=$(aws cloudformation describe-stacks --stack-name $STACK_NAME | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="InstanceProfileARN") | .OutputValue')

$ ROLE_NAME=$(aws cloudformation describe-stacks --stack-name $STACK_NAME | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="InstanceRoleARN") | .OutputValue' | cut -f2 -d/)

$ aws iam put-role-policy \
    --role-name $ROLE_NAME \
    --policy-name FluentBit-DS \
    --policy-document file://eks-fluent-bit-ds-policy.json

现在我们继续定义 Kubernetes RBAC 设置,即 Fluent Bit pod 将使用的服务账户以及角色和角色绑定。

首先创建服务账户 fluent-bit(稍后我们将在守护程序集中使用):

$ kubectl create sa fluent-bit

接下来,在名为 eks-fluent-bit-daemonset-rbac.yaml 的文件(来源)中定义角色和绑定:

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: pod-log-reader
rules:
- apiGroups: [""]
  resources:
  - namespaces
  - pods
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: pod-log-crb
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: pod-log-reader
subjects:
- kind: ServiceAccount
  name: fluent-bit
  namespace: default

现在,通过执行 kubectl apply -f eks-fluent-bit-ds-rbac.yaml,创建上面定义的角色和角色绑定。

接下来,在名为 eks-fluent-bit-configmap.yaml 的文件(来源)中通过 Kubernetes 配置映射定义 Fluent Bit 配置:

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  labels:
    app.kubernetes.io/name: fluentbit
data:
  fluent-bit.conf: |
    [SERVICE]
        Parsers_File  parsers.conf
    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10
    [FILTER]
        Name parser
        Match **
        Parser nginx
        Key_Name log
    [OUTPUT]
        Name firehose
        Match **
        delivery_stream eks-stream
        region us-west-2 
  parsers.conf: |
    [PARSER]
        Name   nginx
        Format regex
        Regex ^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")? \"-\"$ Time_Key time Time_Format %d/%b/%Y:%H:%M:%S %z

通过执行 kubectl apply -f eks-fluent-bit-confimap.yaml 应用此配置映射,然后在名为 eks-fluent-bit-daemonset.yaml 的文件(来源)中定义 Kubernetes 守护程序集(使用提到的配置映射):

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentbit
  labels:
    app.kubernetes.io/name: fluentbit
spec:
  selector:
    matchLabels:
      name: fluentbit
  template:
    metadata:
      labels:
        name: fluentbit
    spec:
      serviceAccountName: fluent-bit
      containers:
      - name: aws-for-fluent-bit
        image: amazon/aws-for-fluent-bit:1.2.0
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: fluent-bit-config
          mountPath: /fluent-bit/etc/
        - name: mnt
          mountPath: /mnt
          readOnly: true
        resources:
          limits:
            memory: 500Mi
          requests:
            cpu: 500m
            memory: 100Mi
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: fluent-bit-config
        configMap:
          name: fluent-bit-config
      - name: mnt
        hostPath:
          path: /mnt

最后,通过执行以下命令启动 Fluent Bit 守护程序集:

$ kubectl apply -f eks-fluent-bit-daemonset.yaml

通过查看日志,验证 Fluent Bit 守护程序集:

$ kubectl logs ds/fluentbit
Found 3 pods, using pod/fluentbit-9zszm
Fluent Bit v1.1.3
Copyright (C) Treasure Data

[2019/07/08 13:44:54] [ info] [storage] initializing...
[2019/07/08 13:44:54] [ info] [storage] in-memory
[2019/07/08 13:44:54] [ info] [storage] normal synchronization mode, checksum disabled
[2019/07/08 13:44:54] [ info] [engine] started (pid=1)
[2019/07/08 13:44:54] [ info] [in_fw] listening on unix:///var/run/fluent.sock
...
[2019/07/08 13:44:55] [ info] [sp] stream processor started

接下来,通过 kubectl apply -f eks-nginx-app.yaml 部署以下 NGINX 应用程序

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app.kubernetes.io/name: nginx
spec:
  replicas: 4 
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.17
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx

至此,我们已完成日志源和路由设置。现在,我们将使用从在 ECS 和 EKS 中运行的 NGINX 容器收集的所有日志数据执行实际任务:我们将对日志进行集中分析。

跨集群执行日志分析

目标是对在 ECS 和 EKS 集群中运行的 NGINX 容器进行日志分析。为此,我们使用 Amazon Athena,这样可以使用 SQL 以交互方式从 Amazon S3 查询服务日志数据。但是,在查询 S3 中的数据之前,我们需要从中获得日志数据。

请注意,在上述 ECS 和 EKS 的 Fluent Bit 配置中,我们将输出设置为 delivery_stream xxx-stream。 这是 Amazon Kinesis Firehose 传输流,我们需要先为 ECS 和 EKS 创建传输流。

首先,通过定义允许 Firehose 写入 S3 的有效策略来设置访问控制部分。为此,我们需要使用两个策略文件创建一个新的 IAM 角色。第一个是 firehose-policy.json来源):

{
  "Version": "2012-10-17",
  "Statement": {
      "Effect": "Allow",
      "Principal": {
        "Service": "firehose.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
  }
}

第二个是 firehose-delivery-policy.json 策略文件(来源),在该文件中,将 XXXXXXXXXXXX 替换为您自己的账户 ID(如果您不确定 ID 是什么,可以通过执行 aws sts get-caller-identity --output text --query 'Account' 获取)。同样,在 S3 部分,将 mh9-firelens-demo 替换为您自己的存储桶名称。

现在,我们可以创建 firehose_delivery_role,以用于 ECS 和 EKS 传输流:

$ aws iam create-role \
        --role-name firehose_delivery_role \
        --assume-role-policy-document file://firehose-policy.json

记下上述命令 JSON 输出结果中的角色 ARN,其格式为 arn:aws:iam::XXXXXXXXXXXXX:role/firehose_delivery_role。我们稍后在创建传输流时会用到,但在创建传输流之前,我们必须实施在 firehos- delivery-policy.json 中定义的策略:

$ aws iam put-role-policy \
        --role-name firehose_delivery_role \
        --policy-name firehose-fluentbit-s3-streaming \
        --policy-document file://firehose-delivery-policy.json

现在,创建 ECS 传输流:

$ aws firehose create-delivery-stream \
            --delivery-stream-name ecs-stream \
            --delivery-stream-type DirectPut \
            --s3-destination-configuration \
RoleARN=arn:aws:iam::XXXXXXXXXXXX:role/example_firehose_delivery_role,\
BucketARN="arn:aws:s3:::mh9-firelens-demo",\
Prefix=ecs

注意:以上命令中的空格很重要:RoleARN 等必须位于一行中且不含空格。

现在,我们必须对 EKS 传输流重复以上操作,重用在第一步中创建的角色。(换句话说,您只需重复执行 aws firehose create-delivery-stream 命令,将 ecs-stream 替换为 eks-stream,将 Prefix=ecs 替换为 Prefix=eks 即可。)

创建并激活传输流需要几分钟时间。当您看到与下面类似的内容时,表示您可以进行下一步:

Amazon Kinesis Firehose 传输流

现在,我们需要为在 ECS 和 EKS 中运行的 NGINX 容器生成一些负载。您可以获取 ECSEKS 的负载生成器文件并执行以下命令;这将每两秒对相应的 NGINX 服务执行一次 curl 操作(在后台执行),直到您终止脚本。

$ ./load-gen-ecs.sh &
$ ./load-gen-eks.sh &

现在我们有了来自 NGINX Web 服务器的一些日志数据,便可以通过 Athena 查询 S3 中的日志条目。为此,我们必须首先为 ECS 和 EKS 创建表,告知 Athena 我们正在使用的架构(下面显示了 ECS 日志数据的架构,对于 EKS,也是如此):

CREATE EXTERNAL TABLE fluentbit_ecs (
    agent string,
    code string,
    host string,
    method string,
    path string,
    referer string,
    remote string,
    size string,
    user string
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://mh9-firelens-demo/ecs2019/'

注意:Amazon Athena 不导入或提取数据;它直接在 S3 中查询数据。因此,当日志数据通过 Fluent Bit 和 Firehose 传输流从 NGINX 容器到达 S3 存储桶中时,您可以使用 Athena 进行查询。

接下来,使用以下 SQL 语句,创建 ECS 和 EKS 日志条目的整合视图

CREATE OR REPLACE VIEW "fluentbit_consolidated" AS
SELECT * , 'ECS' as source
FROM fluentbit_ecs
UNION
SELECT * , 'EKS' as source
FROM fluentbit_eks

这样,我们可以合并这两个表(使用相同的架构),再添加一个源以标记源 ECS 或 EKS。现在,我们可以执行 SQL 查询,找出两个集群中使用 NGINX 服务的前 10 位用户

SELECT source,
         remote AS IP,
         count(remote) AS num_requests
FROM fluentbit_consolidated
GROUP BY  remote, source
ORDER BY  num_requests DESC LIMIT 10

这将生成与下面类似的结果:

就这么简单! 您已经成功设置了 Fluent Bit 插件,并在两个不同的托管 AWS 容器环境(ECS 和 EKS)中使用它来执行日志分析。

完成之后,不要忘记删除相应的工作负载,包括 Kubernetes NGINX 服务(它进而会删除负载均衡器),并销毁 EKS 和 ECS 集群(这会销毁其中的容器)。最后但同样重要的一点是,清理包含日志数据的 Kinesis 传输流和 S3 存储桶。

为了未来使用更加便利,我们还在开发一项进一步简化在 AWS Fargate、Amazon ECS 和 Amazon EKS 上安装和配置 Fluent Bit 插件的功能。您可以通过 AWS 容器路线图的问题 10 关注该功能。

性能和后续步骤说明

为了更好地了解性能,我们执行了基准测试,将上面的 Fluent Bit 插件与 Fluentd CloudWatchKinesis Firehose 插件进行了比较。我们所有的测试都是在 c5.9xlarge EC2 实例上执行的。 结果如下:

CloudWatch 插件:Fluentd 与 Fluent Bit

每秒日志行数 数据输出 Fluentd CPU Fluent Bit CPU Fluentd 内存 Fluent Bit 内存
100 25KB/秒 0.013 vCPU 0.003 vCPU 146MB 27MB
1000 250KB/秒 0.103 vCPU 0.03 vCPU 303MB 44MB
10000 2.5MB/秒 1.03 vCPU 0.19 vCPU 376MB 65MB

测试表明,Fluent Bit 插件比 Fluentd 更节省资源。平均而言,Fluentd 使用的 CPU 是 Fluent Bit 插件的 4 倍以上,所用内存是 Fluent Bit 插件的 6 倍以上。

Kinesis Firehose 插件:Fluentd 与 Fluent Bit

每秒日志行数 数据输出 Fluentd CPU Fluent Bit CPU Fluentd 内存 Fluent Bit 内存
100 25KB/秒 0.006 vCPU 0.003 vCPU 84MB 27MB
1000 250KB/秒 0.073 vCPU 0.033 vCPU 102MB 37MB
10000 2.5MB/秒 0.86 vCPU 0.13 vCPU 438MB 55MB

在此基准测试中,平均而言,Fluentd 使用的 CPU 和内存分别是 Fluent Bit 插件的 3 倍和 4 倍以上。请注意,这些数据并未提供任何保证;您占用的资源可能有所不同。不过,上述数据点表明 Fluent Bit 插件的效率显著高于 Fluentd。

后续步骤

我们很高兴您能够在自己的集群上尝试此插件。如果某些操作未按预期运行,请告知我们,并且请分享您对性能/资源占用以及使用案例的见解。请就 GitHub 中的问题留言,或者在 GitHub 上提出有关 AWS 容器路线图的问题。

Tirumarai Selvan

Wesley Pettit

AWS 容器服务团队的软件开发人员。