本页内容

  • 先决条件
  • 第 1 步 - 配置防火墙
  • 第 2 步 - 安装 Docker 和 Docker Compose
  • 第 3 步 - 创建 Docker Compose 文件
  • 第 4 步 - 配置 Fluentd 构建文件
  • 第 5 步 - 运行 Docker 容器
  • 第 6 步 - 配置 Kibana
  • 第 7 步 - 安装 Nginx
  • 第 8 步 - 安装 SSL
  • 第 9 步 - 配置 Nginx
  • 第 10 步 - 使用 Fluentd 日志驱动运行 Docker 容器
  • 结语

日志监控与分析是服务器或容器基础设施的重要组成部分,在处理复杂应用时尤为有用。流行的日志解决方案之一是 Elasticsearch、Fluentd 和 Kibana(EFK)技术栈。在深入了解本教程之前,让我们先了解一下该技术栈的组成部分。

Elasticsearch 是一个实时、分布式且可扩展的搜索引擎,支持全文搜索和分析。它用于对大量数据进行索引和检索。它通常与 Kibana 一同部署,Kibana 是 Elasticsearch 强大的数据可视化仪表盘。Kibana 允许你探索 Elasticsearch 的日志数据,并构建仪表盘和查询以深入了解你的应用。Fluentd 负责收集、转换日志数据并将其发送至 Elasticsearch 后端。

在本教程中,我们将在 Ubuntu 22.04 机器上使用 Docker 安装 EFK 技术栈,并在使用 Fluentd 过滤和转换容器日志后,将其发送至 Kibana。

先决条件

  • 一台运行 Ubuntu 22.04 的服务器,至少具备 6GB 内存。
  • 一个具有 sudo 权限的非 root 用户。
  • uncomplicated Firewall(UFW)已启用并正在运行。
  • 一个指向该服务器的完全限定域名(FQDN),例如 kibana.example.com 。
  • 所有软件包均已更新。$ sudo apt update && sudo apt upgrade

一台运行 Ubuntu 22.04 的服务器,至少具备 6GB 内存。

一个具有 sudo 权限的非 root 用户。

uncomplicated Firewall(UFW)已启用并正在运行。

一个指向该服务器的完全限定域名(FQDN),例如 kibana.example.com 。

所有软件包均已更新。

$ sudo apt update && sudo apt upgrade

第 1 步 - 配置防火墙

在安装任何软件包之前,第一步是配置防火墙以允许 HTTP 和 HTTPS 连接。

检查防火墙的状态。

$ sudo ufw status

你应该会看到类似下面的输出。

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)

允许 HTTP 和 HTTPS 端口。

$ sudo ufw allow http
$ sudo ufw allow https

再次检查状态以确认。

$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443                        ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443 (v6)                   ALLOW       Anywhere (v6)

第 2 步 - 安装 Docker 和 Docker Compose

添加 Docker 的官方 GPG 密钥。

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg

运行以下命令以添加 Docker 仓库。

$ echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

更新系统以加入 Docker 仓库。

$ sudo apt update

安装 Docker 和 Docker Compose 插件。

$ sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin

本教程将使用 Docker Compose v2 插件,而非旧版的二进制文件。因此,运行命令已从 docker-compose 改为 docker compose,本文中均体现这一点。

Docker 以较高权限运行,因此你需要频繁使用 sudo 来执行命令。更好的做法是将你的 Linux 用户账户加入到 docker 用户组。

$ sudo usermod -aG docker ${USER}

${USER} 变量会获取当前登录的系统账户。如果你不是以想要赋予权限的用户登录的,请将 ${USER} 替换为该用户名。

要使新的用户组成员身份生效,请注销服务器后重新登录,或者使用以下命令。系统会提示你输入该用户的密码。

$ su - ${USER}

第 3 步 - 创建 Docker Compose 文件

首先,创建 EFK 项目的目录。

$ mkdir ~/efk

切换到该目录。

$ cd ~/efk

创建并打开 docker-compose.yml 文件进行编辑。

$ nano docker-compose.yml

在其中粘贴以下代码。

services:
  # Deploy using the custom image automatically be created during the build process.
  fluentd:
    build: ./fluentd
    volumes:
      - ./fluentd/conf:/fluentd/etc
    links: # Sends incoming logs to the elasticsearch container.
      - elasticsearch
    depends_on:
      - elasticsearch
    ports: # Exposes the port 24224 on both TCP and UDP protocol for log aggregation
      - 24224:24224
      - 24224:24224/udp

  elasticsearch:
    image: elasticsearch:8.7.1
    expose:
      - 9200
    environment:
      - discovery.type=single-node # Runs as a single-node
      - xpack.security.enabled=false
    volumes: # Stores elasticsearch data locally on the esdata Docker volume
      - esdata:/usr/share/elasticsearch/data

  kibana:
    image: kibana:8.7.1
    links: # Links kibana service to the elasticsearch container
      - elasticsearch
    depends_on:
      - elasticsearch
    ports:
      - 5601:5601
    environment: # Defined host configuration
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200

# Define the Docker volume named esdata for the Elasticsearch container.
volumes:
  esdata:

按 Ctrl + X 保存文件,并在提示时输入 Y 确认。我们已配置启动三个服务,分别为 Fluentd、Elasticsearch 和 Kibana 各一个。

对于 Fluentd,我们将构建容器而非使用现成镜像。Fluentd 的构建文件将在下一步中设置。我们已为其构建文件挂载了一个目录,并为配置文件挂载了卷,同时开放了 TCP 和 UDP 两种协议的 24224 端口用于日志聚合。

下一个服务是 Elasticsearch,我们使用的是撰写本教程时可用的最新版本。我们已通过 9200 端口将其开放,并设置了若干环境变量,以便以单节点集群方式运行,同时禁用了安全功能。通常不建议这样做,但启用安全功能超出了本教程的范围。我们还为 Elasticsearch 数据挂载了本地卷。

最后,我们配置 Kibana 并通过 5601 端口开放,该端口将用于访问仪表盘。我们还设置了一个变量来配置供其访问的 Elasticsearch 主机。

第 4 步 - 配置 Fluentd 构建文件

创建 Fluentd 及配置目录。

$ mkdir fluentd/conf -p

运行 tree 命令以验证目录结构。

$ tree

它应当类似下面的样子。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

切换到 Fluentd 目录。

$ cd fluentd

创建并打开 Dockerfile 进行编辑。

$ nano Dockerfile

在其中粘贴以下代码。这段代码拉取 Fluentd Debian Docker 镜像,并安装用于 Elasticsearch 的 Fluentd 插件。

# fluentd/Dockerfile
FROM fluent/fluentd:v1.16-debian-1
USER root
RUN ["gem", "install", "fluent-plugin-elasticsearch", "--no-document", "--version", "5.3.0"]
USER fluent

按 Ctrl + X 保存文件,并在提示时输入 Y 确认。

切换到配置目录。

$ cd conf

创建并打开 fluentd.conf 文件进行编辑。

$ nano fluentd.conf

在其中粘贴以下代码。

# bind fluentd on IP 0.0.0.0
# port 24224
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

# sendlog to the elasticsearch
# the host must match to the elasticsearch
# container service
<match *.**>
  @type copy
  <store>
    @type elasticsearch_dynamic
    hosts elasticsearch:9200
    logstash_format true
    logstash_prefix fluentd
    logstash_dateformat %Y%m%d
    include_tag_key true
    tag_key @log_name
    include_timestamp true
    flush_interval 30s
  </store>
  <store>
    @type stdout
  </store>
</match>

按 Ctrl + X 保存文件,并在提示时输入 Y 确认。

上面的 source 指令使用 forward 插件,该插件将 Fluentd 变成一个 TCP 端点以接受 TCP 数据包。

match 指令查找带有匹配标签的事件,在此处表示匹配所有事件。我们将使用 elasticsearch_dynamic 插件进行存储,该插件允许动态指定配置值。hosts 字段指定了 Elasticsearch 应用的主机名,即 Docker Compose 文件中的服务名。logstash_format 设置为 true,意味着 Fluentd 使用 logstash-%Y.%m.%d 这一惯例命名格式。写入事件的索引前缀名称设置为 fluend。include_tag_key 设置为 true,会以 JSON 格式添加 Fluentd 标签。tag_key 是用于提取标签的字段名。将变量 include_timestamp 设置为 true 会为日志添加一个时间戳字段。flush_interval 指定了数据刷写的间隔。我们还使用了 stdout 插件将事件/日志打印到标准输出。

第 5 步 - 运行 Docker 容器

切换回 EFK 目录。

$ cd ~/efk

使用以下命令启动容器。

$ docker compose up -d

检查运行中容器的状态。

$ docker ps
b3780c311154   efk-fluentd           "tini -- /bin/entryp…"   9 seconds ago   Up 8 seconds   5140/tcp, 0.0.0.0:24224->24224/tcp, 0.0.0.0:24224->24224/udp, :::24224->24224/tcp, :::24224->24224/udp   efk-fluentd-1
5a48f0a9ade1   kibana:8.7.1          "/bin/tini -- /usr/l…"   9 seconds ago   Up 7 seconds   0.0.0.0:5601->5601/tcp, :::5601->5601/tcp                                                                efk-kibana-1
dab3a0ab0312   elasticsearch:8.7.1   "/bin/tini -- /usr/l…"   9 seconds ago   Up 8 seconds   9200/tcp, 9300/tcp                                                                                       efk-elasticsearch-1

你也可以使用以下命令来实现。

$ docker compose ps
NAME                  IMAGE                 COMMAND                  SERVICE             CREATED             STATUS              PORTS
efk-elasticsearch-1   elasticsearch:8.7.1   "/bin/tini -- /usr/l…"   elasticsearch       37 seconds ago      Up 36 seconds       9200/tcp, 9300/tcp
efk-fluentd-1         efk-fluentd           "tini -- /bin/entryp…"   fluentd             37 seconds ago      Up 36 seconds       5140/tcp, 0.0.0.0:24224->24224/tcp, 0.0.0.0:24224->24224/udp, :::24224->24224/tcp, :::24224->24224/udp
efk-kibana-1          kibana:8.7.1          "/bin/tini -- /usr/l…"   kibana              37 seconds ago      Up 36 seconds       0.0.0.0:5601->5601/tcp, :::5601->5601/tcp

运行以下命令检查 EFK 构建过程的日志。

$ docker logs efk-fluentd-1
$ docker logs efk-kibana-1
$ docker logs efk-elasticsearch-1

检查 Elasticsearch 容器。它会打印出该容器的详细配置。

$ docker inspect efk-elasticsearch-1

你可以注意到分配给该容器的 IP 地址。

[
    {
        "Id": "dab3a0ab03120d3a7192045e1ea84fdd0f8fdb7819cc6d6780e05109d61e0b66",
        "Created": "2023-05-04T09:58:00.256169904Z",
        "Path": "/bin/tini",
        "Args": [
            "--",
            "/usr/local/bin/docker-entrypoint.sh",
            "eswrapper"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 23619,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2023-05-04T09:58:00.563700803Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:59075530be34d3a06866f894ae9735f6d739a7a751ad45efb86dec3c9bd16836",
        "ResolvConfPath": "/var/lib/docker/containers/dab3a0ab03120d3a7192045e1ea84fdd0f8fdb7819cc6d6780e05109d61e0b66/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/dab3a0ab03120d3a7192045e1ea84fdd0f8fdb7819cc6d6780e05109d61e0b66/hostname",
        "HostsPath": "/var/lib/docker/containers/dab3a0ab03120d3a7192045e1ea84fdd0f8fdb7819cc6d6780e05109d61e0b66/hosts",
        "LogPath": "/var/lib/docker/containers/dab3a0ab03120d3a7192045e1ea84fdd0f8fdb7819cc6d6780e05109d61e0b66/dab3a0ab03120d3a7192045e1ea84fdd0f8fdb7819cc6d6780e05109d61e0b66-json.log",
        "Name": "/efk-elasticsearch-1",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "docker-default",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "efk_default",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "ConsoleSize": [
                0,
                0
            ],
            "CapAdd": null,
            "CapDrop": null,
            "CgroupnsMode": "private",
            "Dns": null,
            "DnsOptions": null,
            "DnsSearch": null,
            "ExtraHosts": [],
            "GroupAdd": null,
            "IpcMode": "private",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": null,
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": null,
            "DeviceCgroupRules": null,
            "DeviceRequests": null,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": null,
            "PidsLimit": null,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0,
            "Mounts": [
                {
                    "Type": "volume",
                    "Source": "efk_esdata",
                    "Target": "/usr/share/elasticsearch/data",
                    "VolumeOptions": {}
                }
            ],
            "MaskedPaths": [
                "/proc/asound",
                "/proc/acpi",
                "/proc/kcore",
                "/proc/keys",
                "/proc/latency_stats",
                "/proc/timer_list",
                "/proc/timer_stats",
                "/proc/sched_debug",
                "/proc/scsi",
                "/sys/firmware"
            ],
            "ReadonlyPaths": [
                "/proc/bus",
                "/proc/fs",
                "/proc/irq",
                "/proc/sys",
                "/proc/sysrq-trigger"
            ]
        },
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/ee03648cf34e03601848b1769569b4d3bb7192db118102ca050215ba87060bbf-init/diff:/var/lib/docker/overlay2/51d6cfcb59e473a3f163e68984a1ba1325a2c816ed7925c4dffdefcf2e104d11/diff:/var/lib/docker/overlay2/b9c096454bda31f1cb2ea33f108be8b29b2e94827ebe94cc17563eb596b7cab1/diff:/var/lib/docker/overlay2/effe604c5b015ba02cf3b7a238bd3ff5dad7970a72e689ef5275fcf03fd0bcd1/diff:/var/lib/docker/overlay2/72fbf23251467ea2f6af8d9458c7fdd8fa3ef716eeafd9319ceff59d07d96788/diff:/var/lib/docker/overlay2/02094ec9e4ebb04371f782744a3a46852a00bf6fd7e8820d466a3576aeb9d5fc/diff:/var/lib/docker/overlay2/ce364cdd636b67e10c879aa152360d965d08fe456663ed8fbe78c3bd37bde6c7/diff:/var/lib/docker/overlay2/33bf44b475ea5ea249845b7eed75ded47dd9dc7877b9231fa4195b4753071945/diff:/var/lib/docker/overlay2/4f19bd8089599ef879075012c710ec464d8e0446fc0a0813850657dddd23a5dc/diff:/var/lib/docker/overlay2/a39a61b12d8565c6d5b33c17a04d47c8bd47609a787e0548fbac0d47d00eecc8/diff:/var/lib/docker/overlay2/cbd9d77eb9ed6b600511f9a676aab511d2aa2b3dbd18d5403559699558546996/diff",
                "MergedDir": "/var/lib/docker/overlay2/ee03648cf34e03601848b1769569b4d3bb7192db118102ca050215ba87060bbf/merged",
                "UpperDir": "/var/lib/docker/overlay2/ee03648cf34e03601848b1769569b4d3bb7192db118102ca050215ba87060bbf/diff",
                "WorkDir": "/var/lib/docker/overlay2/ee03648cf34e03601848b1769569b4d3bb7192db118102ca050215ba87060bbf/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [
            {
                "Type": "volume",
                "Name": "efk_esdata",
                "Source": "/var/lib/docker/volumes/efk_esdata/_data",
                "Destination": "/usr/share/elasticsearch/data",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],
        "Config": {
            "Hostname": "dab3a0ab0312",
            "Domainname": "",
            "User": "elasticsearch:root",
            "AttachStdin": false,
            "AttachStdout": true,
            "AttachStderr": true,
            "ExposedPorts": {
                "9200": {},
                "9200/tcp": {},
                "9300/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "xpack.security.enabled=false",
                "discovery.type=single-node",
                "PATH=/usr/share/elasticsearch/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "ELASTIC_CONTAINER=true"
            ],
            "Cmd": [
                "eswrapper"
            ],
            "Image": "elasticsearch:8.7.1",
            "Volumes": null,
            "WorkingDir": "/usr/share/elasticsearch",
            "Entrypoint": [
                "/bin/tini",
                "--",
                "/usr/local/bin/docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {
                "com.docker.compose.config-hash": "51c818791aa87ea7eccc389578c76ec4d596265eba8baefb8833bf5df13777e3",
                "com.docker.compose.container-number": "1",
                "com.docker.compose.depends_on": "",
                "com.docker.compose.image": "sha256:59075530be34d3a06866f894ae9735f6d739a7a751ad45efb86dec3c9bd16836",
                "com.docker.compose.oneoff": "False",
                "com.docker.compose.project": "efk",
                "com.docker.compose.project.config_files": "/home/navjot/efk/docker-compose.yml",
                "com.docker.compose.project.working_dir": "/home/navjot/efk",
                "com.docker.compose.service": "elasticsearch",
                "com.docker.compose.version": "2.17.3",
                "org.label-schema.build-date": "2023-04-27T04:33:42.127815583Z",
                "org.label-schema.license": "Elastic-License-2.0",
                "org.label-schema.name": "Elasticsearch",
                "org.label-schema.schema-version": "1.0",
                "org.label-schema.url": "https://www.elastic.co/products/elasticsearch",
                "org.label-schema.usage": "https://www.elastic.co/guide/en/elasticsearch/reference/index.html",
                "org.label-schema.vcs-ref": "f229ed3f893a515d590d0f39b05f68913e2d9b53",
                "org.label-schema.vcs-url": "https://github.com/elastic/elasticsearch",
                "org.label-schema.vendor": "Elastic",
                "org.label-schema.version": "8.7.1",
                "org.opencontainers.image.created": "2023-04-27T04:33:42.127815583Z",
                "org.opencontainers.image.documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/index.html",
                "org.opencontainers.image.licenses": "Elastic-License-2.0",
                "org.opencontainers.image.ref.name": "ubuntu",
                "org.opencontainers.image.revision": "f229ed3f893a515d590d0f39b05f68913e2d9b53",
                "org.opencontainers.image.source": "https://github.com/elastic/elasticsearch",
                "org.opencontainers.image.title": "Elasticsearch",
                "org.opencontainers.image.url": "https://www.elastic.co/products/elasticsearch",
                "org.opencontainers.image.vendor": "Elastic",
                "org.opencontainers.image.version": "8.7.1"
            }
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "bf47cd7764585766349085d35100611e086cf233fc9fc655c6eb9e086f1cd59a",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {
                "9200/tcp": null,
                "9300/tcp": null
            },
            "SandboxKey": "/var/run/docker/netns/bf47cd776458",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "",
            "Gateway": "",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "",
            "IPPrefixLen": 0,
            "IPv6Gateway": "",
            "MacAddress": "",
            "Networks": {
                "efk_default": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": [
                        "efk-elasticsearch-1",
                        "elasticsearch",
                        "dab3a0ab0312"
                    ],
                    "NetworkID": "1bc8ac0185982b84a24a201852f2cddc0432a3ffff1a2bd4008074875f696cac",
                    "EndpointID": "e1c67199e679f350d1da47f0b1e208ec6a7767eb57d60f773ba08b88a6962dcf",
                    "Gateway": "172.23.0.1",
                    "IPAddress": "172.23.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:17:00:02",
                    "DriverOpts": null
                }
            }
        }
    }
]

如你所见,该容器获得了 172.23.0.2 作为 IP 地址。运行以下命令以验证 Elasticsearch 是否正常工作。

$ curl 172.23.0.2:9200
{
  "name" : "dab3a0ab0312",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "gldMFBtQSxS5sL93rBAdzA",
  "version" : {
    "number" : "8.7.1",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "f229ed3f893a515d590d0f39b05f68913e2d9b53",
    "build_date" : "2023-04-27T04:33:42.127815583Z",
    "build_snapshot" : false,
    "lucene_version" : "9.5.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

第 6 步 - 配置 Kibana

既然 EFK 技术栈已经部署完成,现在可以配置 Kibana 了。在浏览器中打开 URL http://:5601 。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

点击 Explore on my own 按钮进入 Kibana 仪表盘。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

点击 Stack Management 链接以设置 Kibana 数据视图。从左侧边栏选择 Kibana >> Data Views 选项,打开数据视图页面。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

点击 Create data view 按钮继续。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

输入数据视图名称和索引模式 fluentd-* 。确保 Timestamp 字段设置为 @timestamp 。source 字段会自动更新。点击 Save data view to Kibana 按钮完成数据视图的创建。

接下来,点击顶部菜单(省略号),再点击 Discover 选项以显示日志监控。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

你会看到以下页面,确认你的配置运行正常。这些日志全部取自 Elasticsearch,并由 Fluentd 日志聚合发送。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

第 7 步 - 安装 Nginx

Ubuntu 22.04 自带的 Nginx 版本较旧。你需要下载官方 Nginx 仓库以安装最新版本。

导入 Nginx 的签名密钥。

$ curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

添加 Nginx 稳定版的仓库。

$ echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg arch=amd64] \
http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list

更新系统仓库。

$ sudo apt update

安装 Nginx。

$ sudo apt install nginx

验证安装。

$ nginx -v
nginx version: nginx/1.24.0

启动 Nginx 服务器。

$ sudo systemctl start nginx

第 8 步 - 安装 SSL

第一步是安装 Let's Encrypt 的 SSL 证书。我们需要安装 Certbot 来生成 SSL 证书。你既可以使用 Ubuntu 的仓库安装 Certbot,也可以通过 Snapd 工具获取最新版本。我们将使用 Snapd 版本。

Ubuntu 22.04 默认已安装 Snapd。运行以下命令以确保你的 Snapd 版本是最新的。

$ sudo snap install core && sudo snap refresh core

安装 Certbot。

$ sudo snap install --classic certbot

使用以下命令,通过创建指向 /usr/bin 目录的符号链接,确保 Certbot 命令可以运行。

$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

为域名 kibana.example.com 生成 SSL 证书。

$ sudo certbot certonly --nginx --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m 
[email protected]
 -d kibana.example.com

上述命令会将证书下载到你服务器上的 /etc/letsencrypt/live/kibana.example.com 目录。

生成 Diffie-Hellman 组证书。

$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096

检查 Certbot 续期调度服务。

$ sudo systemctl list-timers

你会发现 snap.certbot.renew.service 是计划运行的服务之一。

NEXT                        LEFT          LAST                        PASSED         UNIT                     ACTIVATES
------------------------------------------------------------------------------------------------------------------------------------
Mon 2023-05-06 13:37:57 UTC 3h 45min left Mon 2023-05-01 07:20:42 UTC 2h 31min ago   ua-timer.timer           ua-timer.service
Mon 2023-05-06 14:39:29 UTC 4h 47min left Sat 2023-02-04 16:04:18 UTC 2 months ago   motd-news.timer          motd-news.service
Mon 2023-05-06 15:53:00 UTC 6h left       n/a                         n/a            snap.certbot.renew.timer snap.certbot.renew.service

对该过程进行一次干运行,以检查 SSL 续期是否正常工作。

$ sudo certbot renew --dry-run

如果你没有看到任何错误,说明一切就绪。你的证书将自动续期。

第 9 步 - 配置 Nginx

创建并打开用于 Kibana 的 Nginx 配置文件。

$ sudo nano /etc/nginx/conf.d/kibana.conf

在其中粘贴以下代码。将 IP 地址替换为你 Elasticsearch 服务器的私有 IP 地址。

server {
        listen 80; listen [::]:80;
        server_name kibana.example.com;
        return 301 https://$host$request_uri;
}

server {
        server_name kibana.example.com;
        charset utf-8;

        listen 443 ssl http2;
        listen [::]:443 ssl http2;

        access_log /var/log/nginx/kibana.access.log;
        error_log /var/log/nginx/kibana.error.log;

        ssl_certificate /etc/letsencrypt/live/kibana.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/kibana.example.com/privkey.pem;
        ssl_trusted_certificate /etc/letsencrypt/live/kibana.example.com/chain.pem;
        ssl_session_timeout 1d;
        ssl_session_cache shared:MozSSL:10m;
        ssl_session_tickets off;

        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

		resolver 8.8.8.8;

        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_dhparam /etc/ssl/certs/dhparam.pem;

        location / {
                proxy_pass http://localhost:5601;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
        }
}

按 Ctrl + X 保存文件,并在提示时输入 Y 确认。

打开 /etc/nginx/nginx.conf 文件进行编辑。

$ sudo nano /etc/nginx/nginx.conf

在 include /etc/nginx/conf.d/*.conf; 这一行之前添加以下行。

server_names_hash_bucket_size  64;

按 Ctrl + X 保存文件,并在提示时输入 Y 确认。

验证配置。

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

重启 Nginx 服务。

$ sudo systemctl restart nginx

还需要最后一步。打开 Docker Compose 文件进行编辑。

$ nano ~/docker-compose.yml

在 Kibana 服务下的 environment 部分添加如下一行 SERVER_PUBLICBASEURL=https://kibana.example.com 。

    environment: # Defined host configuration
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
      - SERVER_PUBLICBASEURL=https://kibana.example.com

完成后按 Ctrl + X 保存文件,并在提示时输入 Y 确认。

停止并移除容器。

$ docker compose down --remove-orphans

使用更新后的配置重新启动容器。

$ docker compose up -d

你的 Kibana 仪表盘应当可以通过 URL https://kibana.example.com 在任何位置访问。

第 10 步 - 使用 Fluentd 日志驱动运行 Docker 容器

现在,我们将运行一个带有 Fluentd 日志驱动器的 Docker 容器,使其自动将日志发送到该技术栈。我们将使用 Nginx 容器进行测试。

从 Docker Hub 仓库拉取 Nginx 镜像。我们使用 alpine 版本,因为它是体积最小的镜像。

$ docker pull nginx:alpine

运行以下命令以创建并启动 Nginx 容器。我们将日志驱动设置为 Fluentd,并将端口设为 8080,因为默认的 80 端口已被代理模式下的 Nginx 服务器占用。

$ docker run --name nginx-fluentd-test -d --log-driver=fluentd -p 8080:80 nginx:alpine

检查容器状态。

$ docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                                                                                    NAMES
038c43e4e1a3   nginx:alpine          "/docker-entrypoint.…"   12 seconds ago   Up 11 seconds   0.0.0.0:8080->80/tcp, :::8080->80/tcp                                                                    nginx-fluentd-test
a94ca706bd0c   efk-fluentd           "tini -- /bin/entryp…"   8 hours ago      Up 8 hours      5140/tcp, 0.0.0.0:24224->24224/tcp, 0.0.0.0:24224->24224/udp, :::24224->24224/tcp, :::24224->24224/udp   efk-fluentd-1
0cf04a446425   kibana:8.7.1          "/bin/tini -- /usr/l…"   8 hours ago      Up 8 hours      0.0.0.0:5601->5601/tcp, :::5601->5601/tcp                                                                efk-kibana-1
7c7ad8f9b123   elasticsearch:8.7.1   "/bin/tini -- /usr/l…"   8 hours ago      Up 8 hours      9200/tcp, 9300/tcp                                                                                       efk-elasticsearch-1

运行以下命令访问 Nginx 容器并生成访问日志。

$ curl localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

或者,你可以在浏览器中打开 URL http://:8080 ,你会看到以下页面。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

打开 Kibana 仪表盘,点击左侧边栏菜单中的 Discover 链接。点击顶部菜单中的 + 号,弹出 Add filter 对话框。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

从下拉列表中选择 container_name 字段,以 is 作为运算符,并将容器名称(nginx-fluentd-test)填入字段值。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

点击 Add filter 按钮以可视化来自 Nginx 容器的数据。

在 Ubuntu 22.04 上搭建 Elasticsearch Fluentd Kibana 日志栈

结语

至此,我们关于在 Ubuntu 22.04 机器上安装 Elasticsearch、Fluentd 和 Kibana(EFK)日志技术栈的教程就结束了。如果你有任何疑问,请在下方评论中留言。