亚马逊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 Service 和 Amazon Redshift)。在本博文中,我们将展示如何在 Amazon ECS 和 EKS 集群上实际运用 Fluent Bit 插件。如果您不熟悉工具,可能还要查看有关 Fluentd 和 Kinesis Firehose 基础知识的教程,并了解 AWS 容器路线图中的相关问题,尤其是 #10 和 #66。
日志路由简介
从概念上讲,容器化设置(如 Amazon ECS 或 EKS)中的日志路由如下图所示:
在上图左侧,描述了日志源(从底部开始):
- 主机和控制层面级别由 EC2 实例组成,用于托管容器。您可以直接(或不直接)访问这些实例。例如,对于在 Fargate 上运行的容器,您将不会在 EC2 控制台中看到实例。在此级别,您还将获得 AWS 管理的 EKS 控制层面中的日志。
- 容器运行时级别通常包括 Docker 引擎生成的日志,例如 ECS 中的代理日志。这些日志通常对具有基础设施管理角色的人员最有用,但也可以帮助开发人员排查问题。
- 应用程序级别用于运行用户代码。此级别会生成与特定应用程序有关的日志,例如有关您自己应用程序中的操作结果的日志条目,或现成应用程序组件(如 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 控制台,确认映像构建和推送是否成功;您应会看到与下面类似的内容:
现在,我们可以使用上述容器映像启动包含守护程序计划策略的 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 控制台中看到以下内容:
现在,我们可以基于上述任务定义启动 ECS 服务:
$ aws ecs create-service \
--cluster fluent-bit-demo \
--service-name nginx-svc \
--task-definition nginx:1 \
--desired-count 1
如果一切顺利,您应该会在 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
即可。)
创建并激活传输流需要几分钟时间。当您看到与下面类似的内容时,表示您可以进行下一步:
现在,我们需要为在 ECS 和 EKS 中运行的 NGINX 容器生成一些负载。您可以获取 ECS 和 EKS 的负载生成器文件并执行以下命令;这将每两秒对相应的 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 CloudWatch 和 Kinesis 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 容器路线图的问题。