L B T

记 录 过 去 的 经 验

Prometnehs 告警规则如下:

- alert: HostTCPRetransmitsHigh
expr: increase(node_netstat_Tcp_RetransSegs[5m]) > 20000

Prometheus 监控数据显示: retrans=21736/5m

检查系统上的 网络内核协议栈状态 ,使用工具 nstat ,它能显示网络栈的统计信息,还能通过差值计数快速定位当前发生的异常。默认显示自系统启动以来的所有统计项。

# nstat
#kernel
IpInReceives 741632185 0.0
IpInHdrErrors 1454 0.0
IpInAddrErrors 24848 0.0
IpForwDatagrams 6281793 0.0
IpInUnknownProtos 173793 0.0
IpInDelivers 585081400 0.0
IpOutRequests 633335747 0.0
IpOutDiscards 39 0.0
IpOutNoRoutes 6912 0.0
IpReasmTimeout 104832 0.0
IpReasmReqds 40046807 0.0
IpReasmOKs 19711639 0.0
IpReasmFails 268659 0.0
IpFragOKs 2920 0.0
IpFragCreates 5840 0.0
IcmpInMsgs 28451709 0.0
IcmpInErrors 50710 0.0
IcmpInCsumErrors 4 0.0
IcmpInDestUnreachs 116906 0.0
IcmpInTimeExcds 12355 0.0
IcmpInRedirects 1137 0.0
IcmpInEchos 28320718 0.0
IcmpInEchoReps 47 0.0
IcmpInTimestamps 542 0.0
IcmpOutMsgs 34766477 0.0
IcmpOutDestUnreachs 6455420 0.0
IcmpOutTimeExcds 22793 0.0
IcmpOutRedirects 32 0.0
IcmpOutEchos 16 0.0
...

使用 bpftrace 跟踪重传发生的端口统计数据

# bpftrace -e '
tracepoint:tcp:tcp_retransmit_skb
{
@[args->sport, args->dport] = count();
}'

Attaching 1 probe...


^C

@[10050, 43344]: 4
@[22, 6890]: 5
@[22, 33828]: 5
@[22, 16254]: 5
@[22, 32874]: 5
@[22, 59454]: 6
@[22, 50852]: 6
@[22, 56096]: 7
@[9100, 51150]: 10
@[443, 49917]: 21
@[443, 50447]: 21

使用 bpftrace 实时跟踪指定端口的重传数据

# bpftrace -e '
tracepoint:tcp:tcp_retransmit_skb
/args->sport == 80 || args->sport == 443 || args->sport == 22/
{
printf("%d -> %d retrans\n", args->sport, args->dport);
}'
Attaching 1 probe...
22 -> 44184 retrans
22 -> 45636 retrans
22 -> 49526 retrans
22 -> 46220 retrans
22 -> 50296 retrans
22 -> 45636 retrans
22 -> 45636 retrans
22 -> 45636 retrans
22 -> 35922 retrans
22 -> 44184 retrans
22 -> 49526 retrans
22 -> 45636 retrans
22 -> 45256 retrans
22 -> 45256 retrans
22 -> 45256 retrans
22 -> 46886 retrans
22 -> 45256 retrans
443 -> 42035 retrans
443 -> 42035 retrans
443 -> 12169 retrans
443 -> 42035 retrans
443 -> 42035 retrans
443 -> 12169 retrans
443 -> 12169 retrans
443 -> 42035 retrans

BPF(Berkeley Packet Filter) 是由 Berkeley 学院于 1992 年开发的一款,主要用于提高抓包工具的性能。并于 2014 年被重写进 Linux 内核中,可以用于 内核观测、网络诊断、性能观测、安全、Kubernetes 网络治理等方面[1]

BPF 是一种灵活且高效的技术实现,主要由 指令集(Instruction Set)存储对象(Storage Objects maps)帮助函数(Helper Functions) 构成。因为其 虚拟指令集规范(Virtual Instruction Set Specification) ,可以认为 BPF 是一个虚拟机,BPF 运行于 内核模式(Kernel Mode) 。BPF 基于事件(Events)运行: Socket EventsTracepointsUSDT Probeskprobesuprobesperf_events.

eBPF 允许

在运行中的内核里插入自己的逻辑

比如:

  • 捕获 TCP 连接
  • 统计进程系统调用
  • 跟踪磁盘 IO
  • 分析网络延迟
  • 拦截数据包

而且:

无需修改内核源码,只在内核事件点插入逻辑,无需重启系统,是 Linux 内核的动态扩展机制

这是 eBPF 最核心的价值。

eBPF 基础原理

eBPF 本质 运行在 Linux 内核中的事件驱动程序 。关键点:

  • 运行在 内核空间(Kernel Space)
  • 事件驱动(Event),不会一直运行,只有在 某个事件(Event)发生时,才会运行

eBPF = Hook + Event + Action

eBPF 的完整执行流程

eBPF 的执行流程分六步

  1. 编写 eBPF 程序 。例如 把程序挂到 tcp_connect 函数上

    SEC("kprobe/tcp_connect")
    int trace_tcp() {
    return 0;
    }
  2. 编译为 BPF Bytecode(eBPF 虚拟机字节码) 。eBPF 运行的不是机器码,而是 BPF 字节码

    clang -> BPF bytecode
  3. 加载到内核 。用户态程序通过 bpf() 系统调用,把字节码加载进内核。

  4. Verifier 验证程序安全 ,这是 eBPF 最重要的机制,用户把程序加载到内核,是极其危险的,它会检查

    • 是否有非法内存访问
    • 是否有无限循环
    • 是否可能访问非法指针
    • 其他危险操作
  5. Attach:把程序挂到事件点 。程序通过 verifier 后,要挂到某个 Hook 点 (Event),比如

    • kprobe:tcp_connect ,每次调用 tcp_connect ,就执行 eBPF 程序
    • tracepoint:sys_enter_execve,每次进程执行时触发
    • XDP ,挂到网卡驱动层

      本质是: Attach = 指定“什么时候执行 eBPF” 。这是 eBPF 的事件驱动核心。

  6. 事件发生时,程序运行

Map

Map 是内核(Kernel)与用户态(User Space)之间的通信机制 。eBPF 程序运行在内核,但它不能直接:

  • print 到终端
  • 写文件

所以必须通过 BPF Map 与用户态交换数据。其本质是内核中的 Key-Value 存储。eBPF 程序更新 Map,用户态读取 Map。 Map 是用户态和内核态共享数据桥梁

Hook 点

Hook 点是在内核执行路径中插入的一个触发点或者说在内核某个位置插入 eBPF 程序 。正常内核流程:

事件发生 -> 内核函数执行

加上 eBPF 后:

事件发生 -> Hook 点 -> eBPF 程序执行 -> 原逻辑继续

不同 Hook 点,看到的数据不同,性能不同,用途不同

  • Hook 在系统调用入口,可以观察 哪个进程调用了 open()execve() 等函数,适合 进程行为观测
  • Hook 在网络包入口,可以观察: 收到哪个数据包、包头是什么,适合 网络过滤 / DDoS 防护
  • Hook 在用户程序函数,可以观察:应用函数调用、函数耗时,适合 应用性能分析

所以: Hook 点 = eBPF 能看到什么、能做什么

eBPF 最重要的五类 Hook 点

  1. kprobe ,挂到内核函数,把 eBPF 程序挂到任意的某个内核函数入口,比如 tcp_connect 。因为挂在函数入口,所以常用于:
    • 获取函数参数,比如 tcp_connect(sock, addr) 拿到: socket 信息、目标地址
    • 统计调用次数
    • 统计执行耗时

      kprobe 用于跟踪内核函数行为 。优点是非常灵活,能挂任意内核函数;缺点是依赖内核函数名,不稳定(不同内核版本可能变),内核升级可能导致 kprobe 失效

  2. tracepoint ,挂到内核预定义的稳定事件点。 因为 tracepoint 是 Linux 官方定义接口,相比 kprobe 不会轻易变,跨内核版本更稳定
  3. uprobes ,把 eBPF 程序挂到用户态程序函数,如 Nginx、Mysql 等。主要用于 应用程序性能分析
  4. XDP ,挂到网卡驱动(收包入口)层。XDP 在最早的网络入口处理包,是 Linux 网络性能天花板,Cilium 就是基于 XDP 实现。
  5. TC ,Linux 流量控制(Traffic Control)层。XDP 是网卡驱动层,性能更高。TC 是 Linux 内核网络层,功能更多,更适合复杂网络策略。

bpftrace

bpftrace 是一个基于 BPF 的追踪工具(Trace Tools),提供了高级别的编程能力,同时包含了命令行和脚本使用方式

bpftrace 命令行

bpftrace 命令详细帮助文档请查看 man bpftrace,下表列出常用选项和参数

选项 说明 示例
-l [SEARCH] 列出匹配的事件(Event/Probe),没有 SEARCH 表达式则列出所有的 Event。SEARCH 支持通配符
-e 'program' 跟踪脚本

筛选指定的事件

# bpftrace -l "tracepoint:*exec*"
tracepoint:libata:ata_exec_command
tracepoint:sched:sched_kthread_work_execute_end
tracepoint:sched:sched_kthread_work_execute_start
tracepoint:sched:sched_process_exec
tracepoint:syscalls:sys_enter_execve
tracepoint:syscalls:sys_enter_execveat
tracepoint:syscalls:sys_enter_kexec_file_load
tracepoint:syscalls:sys_enter_kexec_load
tracepoint:syscalls:sys_exit_execve
tracepoint:syscalls:sys_exit_execveat
tracepoint:syscalls:sys_exit_kexec_file_load
tracepoint:syscalls:sys_exit_kexec_load
tracepoint:workqueue:workqueue_execute_end
tracepoint:workqueue:workqueue_execute_start
tracepoint:writeback:writeback_exec

跟踪新启动的进程

执行以下命令,可以追踪系统中新启动的进程及其参数

# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'
Attaching 1 probe...
/home/oneuser/cwrsync_6.2.4_x64_free/data/ops/rsync2Server.sh
/usr/bin/rsync --progress -a -c -u --timeout=300 -z --password-file /home/oneuser/cwrsync_6.2.4_x64_free/rsync.client.pswd --exclude .idea --exclude rsync2Server.sh /home/oneuser/cwrsync_6.2.4_x64_free/data/ops/ [email protected]::backup
/home/oneuser/cwrsync_6.2.4_x64_free/data/ops/rsync2Server.sh
/usr/bin/rsync --progress -a -c -u --timeout=300 -z --password-file /home/oneuser/cwrsync_6.2.4_x64_free/rsync.client.pswd --exclude .idea --exclude rsync2Server.sh /home/oneuser/cwrsync_6.2.4_x64_free/data/ops/ [email protected]::backup
runc --root /var/run/docker/runtime-runc/moby --log /run/containerd/io.containerd.runtime.v2.task/moby/53daccf2e93fcf83831ce6773a495361afe9bb9694881269d1902f399f5c621c/log.json --log-format json --systemd-cgroup exec --process /tmp/runc-process3178774999 --detach --pid-file /run/containerd/io.containerd.runtime.v2.task/moby/53daccf2e93fcf83831ce6773a495361afe9bb9694881269d1902f399f5c621c/c73d8d4d46ac3995bdc1d7648a3562ddb4715a549a509f6125a7cfc4e88abe50.pid 53daccf2e93fcf83831ce6773a495361afe9bb9694881269d1902f399f5c621c
runc init
/watchtower --health-check
/home/oneuser/cwrsync_6.2.4_x64_free/data/ops/rsync2Server.sh
/usr/bin/rsync --progress -a -c -u --timeout=300 -z --password-file /home/oneuser/cwrsync_6.2.4_x64_free/rsync.client.pswd --exclude .idea --exclude rsync2Server.sh /home/oneuser/cwrsync_6.2.4_x64_free/data/ops/ [email protected]::backup
/usr/lib/x86_64-linux-gnu/dcv/dcvpamhelper --stdout -s dcv
/usr/libexec/sssd/krb5_child --debug-microseconds=0 --debug-timestamps=1 --debug-fd=28 --debug-level=0x0070 --chain-id=7187 --sss-creds-password --canonicalize --realm=OPSDEV666.COM --fast-ccache-gid=0 --fast-ccache-uid=0
/home/oneuser/cwrsync_6.2.4_x64_free/data/ops/rsync2Server.sh
/usr/bin/rsync --progress -a -c -u --timeout=300 -z --password-file /home/oneuser/cwrsync_6.2.4_x64_free/rsync.client.pswd --exclude .idea --exclude rsync2Server.sh /home/oneuser/cwrsync_6.2.4_x64_free/data/ops/ [email protected]::backup

参考链接

Systems Performance: Enterprise and the Cloud v2

脚注

服务器配置如下(32CPU 32G RAM, 无 GPU):

# lscpu  | grep NUMA
NUMA node(s): 2
NUMA node0 CPU(s): 0-7,16-23
NUMA node1 CPU(s): 8-15,24-31

# free -h
total used free shared buff/cache available
Mem: 31Gi 10Gi 583Mi 145Mi 19Gi 19Gi
Swap: 0B 0B 0B

# cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.5 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

root@vp-jmpsvr:~# uname -a
Linux vp-jmpsvr 5.15.0-119-generic #129-Ubuntu SMP Fri Aug 2 19:25:20 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

模型推荐清单 (纯 CPU 环境)

模型名称 推荐版本 理由 内存占用 速度预期
DeepSeek-V3 / R1 (Distill) deepseek-r1:7b 或 14b 目前最强的推理模型,逻辑能力极佳。 ~5GB / 9GB, 极快 / 流畅
Llama 3.1 llama3.1:8b 综合素质平衡,适合通用的英文/开发任务。 ~5.5GB 极快
Qwen 2.5 (通义千问) qwen2.5:7b 或 14b 中文语境支持最好,适合文档处理、后端开发辅助。 ~5GB / 9GB 极快 / 流畅
Command R command-r:35b 内存极限挑战。适合需要长上下文(RAG)的任务。 ~20GB 较慢

安装 Ollama

curl -fsSL https://ollama.com/install.sh | sh

查看 Ollama Service 状态

# systemctl status ollama --no-pager
● ollama.service - Ollama Service
Loaded: loaded (/etc/systemd/system/ollama.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2026-04-28 16:53:27 CST; 3min 46s ago
Main PID: 3683510 (ollama)
Tasks: 15 (limit: 38371)
Memory: 28.1M
CPU: 251ms
CGroup: /system.slice/ollama.service
└─3683510 /usr/local/bin/ollama serve


运行推荐模型 (以 DeepSeek R1 7B 为例)

ollama run deepseek-r1:7b

(Optional)部署 Open WebUI(原 Ollama WebUI) ,它支持 Docker 部署

services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: always
network_mode: host
environment:
# 关键点:访问宿主机的 Ollama
- OLLAMA_BASE_URL=http://127.0.0.1:11434
# 可选:设置 WebUI 的语言
- WEBUI_NAME=My AI Server
# UI 监听端口
- PORT=9000

volumes:
- ./open-webui:/app/data

ansible server rules

# rules
每次运行任务之前,你都必须先读取此文件内容,并严格遵守:
1. 本机(ansible server)仅作为 Ansible 控制节点使用。
- 允许在本机执行只读操作:查看文件、查看 inventory、查看 playbook、查看日志、语法检查、版本检查。
- 禁止在本机执行任何业务变更操作,包括但不限于:安装/卸载软件、修改配置、启动/停止/重启服务、修改系统环境、删除文件。

2. 对远程主机,所有“变更类操作”都必须先得到我的明确确认后才能执行。
变更类操作包括但不限于:
- 安装、升级、卸载软件
- 修改配置文件
- 创建、删除、覆盖文件或目录
- 启动、停止、重启、重载服务
- 执行会改变系统状态的命令
- 变更 systemd、网络、防火墙、用户权限、计划任务、SSH 配置
- 执行 playbook 或 ad-hoc 命令造成远程状态变化

3. 对远程主机的只读检查操作,默认也需要先向我说明检查计划,再等待我确认;
除非我明确说“可以先检查后汇报”,否则不得自行执行。
只读检查包括但不限于:
- 查看系统版本、磁盘、内存、CPU、进程、端口、服务状态、日志、配置内容
- 执行不会修改系统状态的 Ansible ad-hoc 命令

4. 每次收到任务后,必须先给出 Plan,未经我确认不得执行。
Plan 至少包含:
- 任务目标
- 涉及的目标主机
- 需要执行的检查步骤
- 计划执行的 Ansible 命令、playbook 或 shell 命令
- 是否为只读操作或变更操作
- 风险点与影响范围

5. 在我确认前,你不得:
- 自行 SSH 登录远程主机执行命令
- 自行运行任何 Ansible playbook
- 自行运行任何会影响远程主机状态的 ad-hoc 命令
- 将一次确认扩展理解为对其他主机、其他命令或后续步骤的授权

6. 你的执行授权必须按“单次、明确、最小范围”理解。
每次执行前都必须明确说明:
- 目标主机
- 执行内容
- 执行方式
- 预期结果
未被明确确认的内容一律不得执行。

7. 涉及高风险操作时,必须单独再次确认。
高风险操作包括但不限于:
- 删除文件/目录
- 批量修改多台主机
- 重启主机
- 修改网络、防火墙、路由、SSH
- 修改数据库数据
- 覆盖证书、密钥、权限
- 停止生产服务

8. 优先使用安全方式:
- 能只读检查时,先只读检查
- 能使用 `--check` / `--diff` / `--syntax-check` 时,优先先展示结果再申请正式执行
- 不得跳过验证直接执行正式变更

9. 若执行失败、结果异常或存在不确定性:
- 立即停止后续操作
- 汇报实际执行内容、报错信息、影响范围
- 给出修复建议
- 未经我再次确认,不得继续尝试其他方案

10. 回复格式固定为:
- Plan
- 待确认的检查操作
- 待确认的变更操作
- 风险说明
- 等待我的确认

11. 未经我明确允许,不得使用 shell/command 直接在远程主机执行命令;
优先使用 Ansible 模块化方式执行,并说明原因。

12. 未经我明确允许,不得对超过 1 台主机同时执行变更;
批量操作必须逐台或分批确认。

# 当前任务
阅读全文 »

环境信息

  • Centos 7
  • ansible-core 2.16
  • Docker image python:3.12.3

安装

ansible-core 版本及 Python 版本支持对应关系

ansible-core Version Control Node Python Target Python / PowerShell
2.16 Python 3.10 - 3.12 Python 2.7
Python 3.6 - 3.12
Powershell 3 - 5.1

为了环境部署方便灵活,可以选择使用 python:3.12.3 的 Docker 镜像,以其为基础环境安装 ansible-core 2.16 或者直接使用 ansible 镜像启动。

# docker run --rm -it python:3.12.3 bash

# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

# python --version
Python 3.12.3

# pip install ansible

# pip list
Package Version
------------ -------
ansible 9.5.1
ansible-core 2.16.6
cffi 1.16.0
cryptography 42.0.7
Jinja2 3.1.4
MarkupSafe 2.1.5
packaging 24.0
pip 24.0
pycparser 2.22
PyYAML 6.0.1
resolvelib 1.0.1
setuptools 69.5.1
wheel 0.43.0

# ansible --version
ansible [core 2.16.6]
config file = None
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.12/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/local/bin/ansible
python version = 3.12.3 (main, May 14 2024, 07:23:41) [GCC 12.2.0] (/usr/local/bin/python)
jinja version = 3.1.4
libyaml = True

在服务器本地环境部署,如果想要灵活管理配置,也可以使用 pip3 命令安装 ansible

$ pip3 install ansible

$ ansible --version
ansible [core 2.15.13]
config file = None
configured module search path = ['/home/ops/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /home/ops/.local/lib/python3.9/site-packages/ansible
ansible collection location = /home/ops/.ansible/collections:/usr/share/ansible/collections
executable location = /home/ops/.local/bin/ansible
python version = 3.9.25 (main, Dec 10 2025, 00:00:00) [GCC 11.5.0 20240719 (Red Hat 11.5.0-5)] (/usr/bin/python3)
jinja version = 3.1.6
libyaml = True

如此安装的 ansible 默认不加载任何配置文件,此时只需在 ~/.ansible/ 下手动创建 ansible.cfg 即可

~/.ansible/ansible.cfg
[defaults]
inventory = inventory

host_key_checking = False

forks = 20

timeout = 60

gathering = smart

[ssh_connection]


ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ControlPath=/tmp/ansible-control-%r-%h:%p

切换到 ~/.ansible/ 目录,再次查看 Ansible 加载的配置,其已经加载了 ~/.ansible/ansible.cfg

$ ansible --version
ansible [core 2.15.13]
config file = /home/ops/.ansible/ansible.cfg
configured module search path = ['/home/ops/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /home/ops/.local/lib/python3.9/site-packages/ansible
ansible collection location = /home/ops/.ansible/collections:/usr/share/ansible/collections
executable location = /home/ops/.local/bin/ansible
python version = 3.9.25 (main, Dec 10 2025, 00:00:00) [GCC 11.5.0 20240719 (Red Hat 11.5.0-5)] (/usr/bin/python3)
jinja version = 3.1.6
libyaml = True

Ansible 配置说明

Ansible 主配置文件为 /etc/ansible/ansible.cfg其中的配置都可以被 ansible-playbook 或者命令行参数覆盖

ansible 默认会读取环境变量 ANSIBLE_CONFIG 指定的配置文件,当前路径下的 ansible.cfg,以及用户家目录下的 .ansible.cfg,以及 /etc/ansible/ansible.cfg 作为配置文件,已第一个找到的为准

常用配置说明

配置项 说明 示例
inventory 指定 inventory (主机列表)文件的路径,默认为 /etc/ansible/hosts
remote_user (未指定用户时)连接远程主机时使用的用户
remote_port 连接远程主机时使用的(默认)端口
host_key_checking 默认启用。检查主机密钥可以防止服务器欺骗和中间人攻击。
如果主机重新安装并且在 know_hosts 中拥有不同的密钥,ansible 会提示确认密钥。
如果要禁用此行为,可以配置为 False
ask_pass 默认为 False。当设置为 True 时,ansible 要求输入远端服务器的密码,即使配置了免密登录
log_path 日志文件,默认 /var/log/ansible.log
pattern 当没有给出 pattern 时的默认 pattern,默认值是 * 即所有主机

配置示例

/etc/ansible/ansible.cfg
[defaults]
# 设置默认的 inventory 文件路径
inventory = /etc/ansible/hosts

# 关闭主机密钥检查,方便新主机的快速添加
host_key_checking = False

# 设置默认的远程用户
remote_user = ansible

Inventory 配置说明

默认的 inventory 配置文件路径为 /etc/ansible/hosts,主要用来配置 Managed Hosts 列表 [3]

在命令行中,可以使用选项 -i <path> 指定不同的 inventory 或者可以在 ansible 配置文件 ansible.cfg 中使用指令 inventory 指定 inventory 文件位置。

命令行中可以使用 -i <path1> -i <path2> ... 指定多个 inventory

inventory 文件支持多种格式,最常见的是 INIYAML 格式。

  • Ansible 默认创建了 2 个组:
    • all : 包含所有主机
    • ungrouped : 包含所有不在其他组(all 除外)中的所有主机。

      任何一个主机都会至少在 2 个组中,要么 all 和某个组中,要么 allungrouped

  • 一个主机可以包含在多个组中
  • parent/childchild 组被包含在 parent 组中。
    • INI 配置格式中,使用 :children 后缀配置 parent
    • YAML 配置格式中,使用 children: 配置 parent
      • 任何在 child 组中的主机自动成为 parent 组中的一员
      • 一个组可以包括多个 parentchild 组,但是不能形成循环关系
      • 一个主机可以在多个组中,但是在运行时,只能有一个实例存在,Ansible 会自动将属于多个组的主机合并。
  • 主机范围匹配。如果有格式相似的主机,可以通过范围格式使用一条指令来添加多台主机。
    • INI 配置格式中,使用以下格式
      [webservers]
      www[01:50].example.com

      ## 指定步长增长
      www[01:50:2].example.com

      db-[a:f].example.com
    • YAML 配置格式中,使用以下格式
      # ...
      webservers:
      hosts:
      www[01:50].example.com:

      ## 指定步长增长
      www[01:50:2].example.com:
      db-[a:f].example.com:

      范围格式 的第一项和最后一项也包括在内。即匹配 www01www50

Inventory 多配置文件支持

在主机数量较多,或者组织结构较复杂的情况下,使用单个 Inventory 配置文件会导致主机管理较为复杂。将单个 Inventory 配置文件按照项目或者组织或其他规则进行分割会显著降低维护复杂度。

Inventory 多配置文件支持,可以使用以下方法之一

  • 按照项目或者组织或其他规则将主机分割到多个配置中,命令行中可以使用 -i <path1> -i <path2> ... 指定多个 inventory
  • 按照项目或者组织或其他规则将主机分割放置在多个文件中,并将所有文件统一放置在一个单独的目录中(如 /etc/ansible/inventory/),在命令行中使用选项 -i /etc/ansible/inventory/ 或者在 Ansible 配置文件(ansible.cfg)中使用指令 inventory 配置目录。

    注意事项: Ansible 使用字典顺序加载配置文件,如果在不同的配置文件中配置了 parent groupschild groups,那么定义 child groups 的配置要先用定义 parent groups 的文件加载,否则 Ansible 加载配置会报错: Unable to parse /path/to/source_of_parent_groups as an inventory source [4]

  • 使用 group_varshost_vars 目录分别存储组变量和主机变量 [7]

    注意事项: 组变量和主机变量必须使用 YAML 格式,合法的文件扩展名包括: .yamlyml.json 或者无文件扩展名

INI 格式的 Inventory

主机列表中的主机可以单独出现,也可以位于某个或者多个 组([] 开头的行)中

/etc/ansible/hosts
ansible-demo1.local
ansible-demo2.local

[webserver]
webserver1.local
webserver2.local

[nginxserver]
# 匹配多个主机:nginx1.local, nginx2.local, nginx3.local, nginx4.local
nginx[1:4].local variable1=value1 variable2=value2
nginx-bak.local ansible_ssh_host=10.10.0.1 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass=PASSWORD
127.0.0.1 http_port=80 maxRequestPerChild=808


连接主机使用的常用配置说明 [6]

配置项 说明 示例
ansible_host 远程主机地址
ansible_port 远程主机端口
ansible_user 连接远程主机的 ssh 用户
Ansible 默认使用 control node 上执行 ansible 的用户名来连接远程主机 [9]
ansible_password 连接远程主机的 ssh 用户密码,建议使用 key 连接
ansible_ssh_private_key_file 连接远程主机的 ssh 私钥文件路径
ansible_become
ansible_sudo
ansible_su
用户权限提升
ansible_become_method 用户权限提升(escalation)的方式
ansible_become_user
ansible_sudo_user
ansible_su_user
用户权限提升(escalation)后的用户
ansible_become_password
ansible_sudo_password
ansible_su_password
sudo 密码(这种方式并不安全,强烈建议使用 --ask-sudo-pass)
ansible_become_exe
ansible_sudo_exe
ansible_su_exe
设置用户权限提升(escalation)后的可执行文件
ansible_connection 与主机的连接类型.比如:local, ssh 或者 paramiko
Ansible 1.2 以前默认使用 paramiko。1.2 以后默认使用 smart,smart 方式会根据是否支持 ControlPersist, 来判断 ssh 方式是否可行.
ansible_shell_type 目标系统的 shell 类型.默认情况下,命令的执行使用 sh 语法,可设置为 cshfish.
ansible_python_interpreter 目标主机的 python 路径
系统中有多个 Python, 或者命令路径不是 /usr/bin/python
阅读全文 »

Loki 是 Grafana 体系里的日志聚合系统,设计思路和 Prometheus 很像,但处理对象是日志。它和传统全文检索型日志系统的最大区别是: 默认只索引标签(labels),不索引整条日志正文 ,所以存储成本通常更低,扩展性也更好,特别适合 Kubernetes、Docker、主机系统日志、应用日志统一汇聚场景。

截至 2026-04-16,Promtail 已经 EOL,新的采集端优先建议使用 Grafana Alloy ;另外,Loki 本身不内置认证层,生产环境应放在 Nginx 等反向代理或网关之后。

它的典型链路是:

日志源 → 采集器(Alloy)→ Loki → Grafana 查询与展示

其中:

  • Loki 负责接收、压缩、存储、查询日志。
  • Alloy 负责在主机或 K8s 节点上收集日志、打标签、做预处理,再推送到 Loki。
  • Grafana 负责可视化、检索、告警。

Loki 核心架构逻辑上常见有这些组件:

  • Distributor :接收写入请求
  • Ingester :缓存并写入日志块
  • Querier :执行查询
  • Query Frontend :查询拆分与缓存
  • Compactor :压缩、整理、保留策略
  • Index / Storage :索引和日志对象存储

标签设计建议

Loki 的性能很大程度取决于标签设计,因此标签设计非常关键。

建议保留低基数标签

  • job
  • host
  • env
  • app
  • namespace
  • pod
  • container

不要把高基数内容做成标签

  • user_id
  • request_id
  • trace_id
  • URL 全路径
  • 错误详情
  • SQL 文本

官方文档强调 labels 是 Loki 的核心组织方式,但标签过多、基数过高会带来性能和成本问题。

Docker Compose 部署示例

  • Loki version 3.7.1
  • alloy, version v1.15.1
docker-compose.yaml
services:
loki:
image: grafana/loki:latest
command:
- '-config.file=/etc/loki/config.yaml'
ports:
- "3100:3100"
volumes:
- "./config/loki/:/etc/loki/:ro"
- "./data/loki/:/data/loki"

grafana:
image: grafana/grafana
ports:
- "3000:3000"
volumes:
- "./config/grafana/:/etc/grafana/"
- "./data/grafana/:/var/lib/grafana/"


alloy:
image: grafana/alloy
container_name: alloy
restart: unless-stopped
ports:
- "12345:12345"
command:
- run
- --server.http.listen-addr=0.0.0.0:12345
- --storage.path=/var/lib/alloy/data
- /etc/alloy/config.alloy
volumes:
- ./config/alloy/:/etc/alloy/:ro
- ./data/alloy/:/var/lib/alloy/data
- /var/run/docker.sock:/var/run/docker.sock

主配置文件 /etc/loki/config.yaml 内容参考:

/etc/loki/config.yaml
auth_enabled: false

server:
http_listen_port: 3100

common:
path_prefix: /loki
replication_factor: 1
ring:
kvstore:
store: inmemory

schema_config:
configs:
- from: "2024-01-01"
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h

storage_config:
filesystem:
directory: /loki/chunks

limits_config:
allow_structured_metadata: true
retention_period: 168h

compactor:
working_directory: /loki/compactor
retention_enabled: true
delete_request_store: filesystem

Alloy 配置文件参考:

config/alloy/config.alloy
logging {
level = "info"
format = "logfmt"
}

discovery.docker "containers" {
host = "unix:///var/run/docker.sock"
}

discovery.relabel "docker_logs" {
targets = discovery.docker.containers.targets

rule {
source_labels = ["__meta_docker_container_name"]
target_label = "container"
regex = "/(.*)"
replacement = "$1"
}

rule {
source_labels = ["__meta_docker_container_log_stream"]
target_label = "stream"
}

rule {
source_labels = ["__meta_docker_container_label_com_docker_compose_service"]
target_label = "service"
}
}

loki.source.docker "containers" {
host = "unix:///var/run/docker.sock"
targets = discovery.relabel.docker_logs.output
forward_to = [loki.write.local.receiver]
}

loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}

external_labels = {
host = "prometheus-jumpserver",
env = "prod",
}
}

启动成功后,在 Grafana 中添加新的 Data Source。即可在 Grafana 中查看到日志。

配置文件详解

Loki 配置文件

  • Loki version 3.7.1

以下配置适用于单机、单副本、本地文件系统、保留 7 天数据的 Liki

/etc/loki/config.yaml
auth_enabled: false

server:
http_listen_port: 3100 # Loki 的 HTTP 服务监听端口。Grafana、Alloy 或自己用 curl 查询 Loki API 时,默认都会连这个端口。官方配置文档把 server 作为 Loki 进程网络与服务行为的基础配置入口。

common: # Loki 多组件共用的基础参数(通用运行参数)。
path_prefix: /loki # Loki 的本地工作目录前缀。很多组件的临时文件、索引缓存、压缩过程中的文件、规则文件等,都可能基于这个路径展开。官方示例中常见把它设成 /loki。在 Docker 部署中需要持久化
replication_factor: 1 # 只保留 1 份副本。这适合单机或非高可用测试环境。官方示例中单机 / 简化部署也常用 1。想要高可用,通常需要 >1,并配套 ring/多节点/对象存储
ring: # ring 是分布式 Loki 用来协调实例状态、分片、接收写入等的关键机制。
kvstore:
store: inmemory # 表示 Loki 的 ring 元数据存储在内存里。好处是简单,坏处是进程重启后状态不持久,也不适合多实例生产。

schema_config: # 存储 schema 定义。这部分是 Loki 最关键的配置之一。schema 是按时间段生效的,Loki 会根据日志时间决定使用哪套 schema 来存储和查询数据。
configs:
- from: "2024-01-01" # 表示从 2024-01-01 开始写入的数据,使用下面这一整套 schema。from 是时间分界点;如果后面再新增一条新 schema,比如 from: "2026-05-01",那么之后的数据就会按新 schema 存。
store: tsdb # 使用 TSDB 索引。从 Loki 2.8 起,TSDB 是推荐的索引方式,更高效、更快、可扩展性更好。
object_store: filesystem # 日志块和相关对象数据存到本地文件系统,而不是 S3、GCS、Azure Blob 之类的对象存储。正式环境应该优先使用 S3 / MinIO / GCS 等对象存储
schema: v13 # 这是 schema 版本。官方升级文档明确说,Structured Metadata 要求 active schema 同时使用 tsdb 和 v13;迁移文档也把 v13 作为推荐版本。
index:
prefix: index_ # 索引前缀名。 Loki 会按自己的规则生成索引对象名或目录名,这个前缀就是命名的开头。
period: 24h # 这是非常关键的参数,表示索引按 24 小时切分。官方 retention 文档明确说明,retention 仅支持 24h index period。

storage_config: # 底层存储配置
filesystem: # schema 配置了 object_store: filesystem,就需要给 filesystem 指定目录。
directory: /loki/chunks # 这个目录用于保存 Loki 的日志块数据。简单说,日志正文压缩后的 chunk 文件会在这里。你容器删除或这个目录丢失,就会失去本地日志数据。

limits_config: # 限制与功能开关。控制 Loki 的一些行为限制和特性。官方升级文档、structured metadata 文档以及 retention 相关文档都涉及这里的配置项。
allow_structured_metadata: true # 允许 Loki 使用 Structured Metadata(某些字段可以作为结构化元数据随日志返回)。官方说明,Structured Metadata 在 Loki 3.0 默认已启用,但前提是你的 active schema 必须是 TSDB + v13;如果不是,就需要关闭或迁移。
retention_period: 168h # 全局日志保留 168 小时,也就是 7 天。这个只是“希望保留多久”,真正执行删除还依赖 compactor 配置正确,并且索引周期满足要求

compactor: # 压缩与保留删除执行者,Compactor 不只是做压缩,也负责 retention 相关处理。
working_directory: /loki/compactor # compactor 的工作目录,用来存放压缩、保留、删除流程中的临时文件或处理中间状态。
retention_enabled: true # 表示打开 retention 机制。没有这个,即使你写了 retention_period,compactor 也只会做压缩,不会执行基于保留期的删除。
delete_request_store: filesystem # 若启用 retention,必须配置 delete_request_store;它决定删除请求存在哪种后端里。

Alloy 配置文件详解

  • alloy, version v1.15.1

Alloy 配置文件本质上是在描述一条 数据流水线

  • 发现目标 → 读取数据 → 处理/加标签 → 写到 Loki / Prometheus / OTLP 等后端

Alloy 的配置语法由两类基础元素组成:attributes(属性) 和 blocks(块) 。属性用 key = value 赋值,块用来定义组件实例和嵌套配置。

Alloy 官方把一些顶层块称为 configuration blocks ,它们用于配置 Alloy 进程本身,不是在采集业务日志或者输出日志。比如:

  • logging
  • tracing
  • http
  • livedebugging
  • import.*

Linux 下 Alloy 默认配置文件路径是 /etc/alloy/config.alloy

logging {                      # 配置 Alloy 自己的日志
level = "info" # Alloy 自己输出 info 级别日志
format = "logfmt" # 输出为 logfmt 格式
}

discovery.docker "containers" { # 发现 Docker 容器,discovery.docker 会把每个容器映射成 target。
host = "unix:///var/run/docker.sock" # 连接 Docker daemon、从 Docker 中发现容器目标、暴露出一组可供下游使用的 targets。要生效,你的容器通常要挂载 /var/run/docker.sock
}

discovery.relabel "docker_logs" { # 重写或补充标签。把 Docker 自动发现到的元标签,转成在 Loki/Grafana 里更好用的业务标签。labels 是 Loki 的核心组织方式,必须谨慎设计,避免高基数标签。
targets = discovery.docker.containers.targets #

rule {
source_labels = ["__meta_docker_container_name"]
target_label = "container"
regex = "/(.*)"
replacement = "$1"
}

rule {
source_labels = ["__meta_docker_container_label_com_docker_compose_service"]
target_label = "service"
}
}

loki.source.docker "containers" { # 读取 Docker 日志,这是日志采集核心。
host = "unix:///var/run/docker.sock" # 连接哪个 Docker daemon
targets = discovery.docker.containers.targets # 从哪里拿目标列表,从 discovery.docker "containers" 获取目标容器列表
forward_to = [loki.write.local.receiver] # 日志下一步发给谁。把当前读到的日志,发给 loki.write "local" 这个组件的 receiver。
}

loki.write "local" { # 接收来自其他 Loki 组件的日志,把日志发到 Loki,这是输出端。
endpoint { # 定义目标 Loki 地址
url = "http://loki:3100/loki/api/v1/push"
}
external_labels = { # 给所有发出去的日志统一追加静态标签。
host = "prometheus-jumpserver", # 所有日志自动打上 host=prometheus-jumpserver,env=prod 标签
env = "prod",
}
}

可以把 Alloy 理解成 组件图 。比如上面的配置链路:

  • discovery.docker (用于 Docker 目标发现)去发现 Docker 容器
  • loki.source.docker 读取这些容器的日志,并转发给其他 loki.* 组件
  • loki.write 把日志发给 Loki,用于接收日志并通过 Loki logproto 发往远端 Loki

如果不是采集 Docker,而是采集文件日志,这时通常会用:

  • loki.source.file
  • local.file_match
loki.source.file "files" {
file_match {
path_targets = [
{"__path__" = "/var/log/*.log", "job" = "system"},
{"__path__" = "/var/log/nginx/*.log", "job" = "nginx"},
]
}

forward_to = [loki.write.local.receiver]
}

Grafana 是一款用 GO 语言开发的开源数据可视化工具,可以做数据监控和数据统计,带有告警功能。

基础概念

组织(Organization) 与用户(User)

Organization 相当于一个 Namespace,一个 Organization 完全独立于另一个 Organization,包括 datasourcedashboard 等,创建一个 Organization 就相当于打开了一个全新的视图,所有的 datasourcedashboard 等都需要重新创建。一个用户(User) 可以属于多个 Organization。

User 是 Grafana 里面的用户,用户可以有以下 角色

  • admin - 管理员权限,可以执行任何操作。
  • editor - p不可以创建用户不可以新增 Datasource可以创建 Dashboard**
  • viewer - 仅可以查看 Dashboard
  • read only editor - 允许用户修改 Dashboard,但是 不允许保存

数据源 Datasource

Grafana 中操作的数据集、可视化数据的来源

Dashboard

在 Dashboard 页面中,可以组织可视化数据图表。

  • Panel - 在一个 Dashboard 中,Panel 是最基本的可视化单元。通过 Panel 的 Query Editor 可以为每一个 Panel 添加查询的数据源以及数据查询方式。每一个 Panel 都是独立的,可以选择一种或者多种数据源进行查询。一个 Panel 中可以有多个 Query Editor 来汇聚多个可视化数据集
  • Row - 在 Dashboard 中,可以定义一个 Row,来组织和管理一组相关的 Panel

Variables

在 Dashboard 的设置页面中,有 Variables 页面,在其中可以为 Dashboard 配置变量,之后可以在 Panel 的 Query Editor 中使用这些预定义的变量。变量的值也可以是通过表达式获取的值。也可以在 Panel 的标题中使用变量

例如以下 Variables 配置

Node    label_values(kubernetes_io_hostname)

在 Dashboard 中定义了这些变量后,可以在 Panel 的 Query Editor 中使用,在 Query Editor 中使用了 Variables 中定义的变量后,在 Dashboard 的顶部下拉菜单中可以选择预定义的变量的值(需要在定义 Variables 时配置 Show on dashboardLabel and Value 以使在 Dashboard 顶部显示下拉菜单),Panel 中的 Query 表达式就会使用这些变量的值进行计算以及显示图表。

阅读全文 »

在 Docker 以及 Docker Compose 容器化场景下,监控不能只看容器活着没,而要覆盖以下 5 个层面:

  • 主机层 :CPU、内存、磁盘、网络、负载、文件系统、I/O
  • 容器层 :容器 CPU/内存/网络/重启次数/文件系统/生命周期
  • 应用层 :Nginx、MySQL、Redis、Java、Go、Python 等业务指标
  • 日志层 :容器 stdout/stderr 、应用日志、错误日志、访问日志
  • 可用性层 :HTTP/TCP/接口探测、业务 SLA、告警闭环

核心原则:

  • 指标、日志、告警分离
  • 先统一采集,再按业务细化
  • 容器监控 + 应用监控 + 主机监控三位一体
  • 所有组件都容器化,便于 Compose 管理
  • 尽量少侵入业务容器

基础监控栈

  • Prometheus :指标采集与存储
  • Grafana :可视化大盘
  • Alertmanager :告警路由与收敛
  • cAdvisor :容器指标采集
  • docker daemon metrics
  • Node Exporter :宿主机指标采集

日志栈

  • Loki :日志存储
  • Promtail :日志采集
    或者你也可以换成 ELK/EFK,但在 Compose 中 Loki 更轻量、更容易落地

应用 Exporter(按需加)

  • MySQL Exporter
  • Redis Exporter
  • Nginx Exporter
  • Postgres Exporter
  • MongoDB Exporter
  • JMX Exporter(Java)
  • 自研应用 /metrics 接口

日志栈常用组件选择

promtail 和 fluentbit , filebeat 比较

工具 核心定位 特点
Promtail Loki 专用日志采集器(强绑定 Grafana 生态) 只服务 Loki
配置简单
强依赖 label(标签体系)
与 Grafana 联动极好
基本不通用
Fluent Bit 轻量级、高性能日志采集/转发器(云原生首选) 云原生事实标准、CNCF 项目(和 Kubernetes 生态融合好)
插件体系强、最通用、最灵活
支持输出到:
- Loki
- Elasitcsearch
- Kafaka
- S3
- Opensearch
- ClickHouse
- HTTP
Filebeat ELK 生态日志采集器(Elastic 官方) 深度集成 Elasticsearch + Logstash + Kibana
内置大量 module(nginx、mysql、system 等)
优势:开箱即用、解析能力强
劣势:资源占用较高、生态绑定严重

promtail 和 fluentbit , filebeat 性能比较

项目 Promtail Fluent Bit Filebeat
内存占用 ⭐⭐ ⭐⭐⭐⭐(最低)
CPU 占用 ⭐⭐ ⭐⭐⭐⭐ ⭐⭐
吞吐能力 中等 ⭐⭐⭐⭐(非常高) 较高
启动速度 ⭐⭐⭐⭐ 较慢
高并发日志 一般 ⭐⭐⭐⭐ 较好

promtail 和 fluentbit , filebeat 日志处理能力(解析/清洗)

能力 Promtail Fluent Bit Filebeat
正则解析 ✔️ ✔️ ✔️
JSON解析 ✔️ ✔️ ✔️
Pipeline处理 ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐
Lua/扩展脚本 ✔️ ✔️(processor)
复杂ETL能力 很强

AlertManager 选择

Grafana 集成的 Alertmanager 还是 Prometheus 集成的 Alertmanager ?

👉 优先选择:Prometheus 原生 Alertmanager(强烈推荐)

👉 Grafana Alerting 适合作为补充,而不是主告警系统

两种架构对比

维度 Prometheus + Alertmanager Grafana Alerting
架构职责 清晰(采集 / 存储 / 告警分离) 混合(Grafana 做太多)
解耦程度 ⭐⭐⭐⭐ ⭐⭐
稳定性 ⭐⭐⭐⭐(生产级) ⭐⭐⭐
可控性 ⭐⭐⭐⭐ ⭐⭐⭐
学习成本 ⭐⭐⭐ ⭐⭐
多数据源支持 ❌(只 Prometheus) ⭐⭐⭐⭐
UI 操作 ❌(写 YAML) ⭐⭐⭐⭐
运维规范性 ⭐⭐⭐⭐ ⭐⭐

Prometheus Alertmanager 的优势(重点)

  • ✅ 1)真正的 运维级告警系统

    • 告警逻辑在 Prometheus(靠近数据)
    • 告警分发在 Alertmanager(专业做路由)

    👉 职责清晰、可控性强

  • ✅ 2)强大的告警路由能力

    Alertmanager 支持:

    • 分组(group_by)
    • 抑制(inhibit_rules)
    • 静默(silence)
    • 去重(dedup)
    • 升级策略(escalation)

    👉 Grafana 很难做到同级别复杂度

  • ✅ 3)抗压能力强(生产关键)

    Prometheus + Alertmanager:

    • 告警计算在 Prometheus
    • Alertmanager 专职处理

    👉 不会因为 Grafana 挂掉导致告警失效

  • ✅ 4)支持大规模告警治理

    例如:

    • 10万+ time series
    • 多环境(dev/staging/prod)
    • 多团队告警路由

    👉 Prometheus 体系更成熟

  • ✅ 5)行业标准

    几乎所有生产环境:

    • Kubernetes
    • 云原生平台
    • 大厂监控体系

    👉 都是 Prometheus + Alertmanager

  • ✅ 5)版本/状态管理

    • 容易 GitOps
      👉 Grafana 配置在 DB,不好 GitOps,不易审计

Grafana Alerting 的优势

  • ✅ 1)支持多数据源告警(最大优势)

    Grafana 可以对:

    • Prometheus
    • Loki
    • Elasticsearch
    • MySQL
    • CloudWatch

    统一做告警

    👉 Prometheus 做不到

  • ✅ 2)UI 配置(非常友好)

    • 不用写 YAML
    • 点点点就能配
    • 对新手友好
  • ✅ 3)适合日志告警

    比如:

    • Loki 日志错误数
    • Elasticsearch error log
    • SQL 查询结果

    👉 Prometheus 不擅长日志

  • ✅ 4)适合小团队

    • 不需要复杂告警治理
    • 不需要多环境策略

docker daemon 进程监控

监控 Docker Engine,可以开启 Docker daemon metrics

/etc/docker/daemon.json
{
"metrics-addr": "0.0.0.0:9323",
"experimental": true
}

重启 Docker

systemctl restart docker

Prometheus 增加抓取 Docker Daemon Metrics:

- job_name: docker-daemon
static_configs:
- targets: ['host-ip:9323']

这样能看到:

  • Docker Engine 内部指标
  • 镜像拉取行为
  • 守护进程状态
  • API 请求等

cAdvisor 部署和采集

cAdvisor(Container Advisor)主要负责:

  • 容器 CPU / 内存 / 网络 / 磁盘
  • 容器生命周期
  • Docker runtime 统计
  • cgroup 级别资源

👉 不负责:

  • 应用指标(要 exporter)
  • 日志(要 Loki/ELK)
  • 告警(要 Prometheus + Alertmanager)

cAdvisor 推荐作为一个独立的监控容器运行,最小可用 docker-compose.yml 参考

docker-compose.yml

services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
privileged: true

ports:
- "8080:8080"

volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
- /dev/disk:/dev/disk:ro

devices:
- /dev/kmsg


  • /var/lib/docker 用来读取容器元数据(必须)
  • /sys ,/proc 获取 cgroup 和资源统计
  • /var/run 读取 Docker socket 信息
  • privileged: true 解决权限问题(生产建议保留)
  • /dev/kmsg 采集 kernel 相关指标(可选但推荐)

启动 cAdvisor 并通过 http://<host-ip>:8080http://<host-ip>:8080/metrics 验证是否能看到:

  • 容器列表
  • CPU / Memory / Network 图表

在 Prometheus 中使用以下配置接入:

scrape_configs:
- job_name: cadvisor
static_configs:
- targets: ['cadvisor:8080']

Nginx 服务配置

全局通用配置

nginx.conf
user nginx nginx;    

# 建议设置为 cpu 核心数或者 cpu 核心数的 2 倍,进程会包含一个 `master process`,多个 `worker process`
# master process 负责绑定端口、调度进程等,不负责业务的处理
# worker process 是业务进程,负责业务的处理
worker_processes auto;

# 一个 worker 进程可以打开的最大的 fd 个数,受 Linux 内核限制
# 理论值应该是系统最多打开文件数(ulimit -n)与 nginx 进程数相除
# 可通过 ulimit 设置或修改系统文件:`/etc/securit/limits.conf`
worker_rlimit_nofile 1024;

# cpu 亲和性设置
worker_cpu_affinity 0001 0010 0100 1000;

# 工作进程调度优先级,-20 到 19 之间的值,值越小越优先调用。
# 如果系统同时运行多个任务,你可能需要提高 nginx 的工作进程的优先级
worker_priority 0;

# ssl 硬件加速服务器,需要硬件支持
# ssl_engine ssl_engine device;

# nginx 是否以守护进程运行,是否让 nignx 运行于后台;调试时可为 off,使得所有信息直接输出在控制台
daemon on | off;

# events 模块中包含 nginx 中所有处理连接的设置。
events {
# 每个 worker 进程允许的最多连接数,
# nginx 服务最大连接数:worker_processes * worker_connections (受 worker_rlimit_nofile 限制)
worker_connections 1024;
use epoll;

# 是否允许一次性地响应多个用户请求
multi_accept on;

# 是否打开 nginx 的 accept 锁;此锁能够让多个 worker 进行轮流地、序列化地与新的客户端建立连接;
# 而通常当一个 worker 进程的负载达到其上限的 7/8,master 就尽可能不将请求调度至 worker.
accept_mutex on | off;
}

# HTTP 模块控制着 nginx http 处理的所有核心特性
http {
include mime.types;
default_type application/octet-stream;

# 是否在错误页面中显示和响应头字段中发出 nginx 版本号。
# 安全考虑建议关闭
server_tokens on | off | string;

# 是否启用 sendfile 内核复制模式功能。作为静态服务器可以提供最大的 IO 访问速度。
sendfile on | off;

# 尽快发送数据,否则会在数据包达到一定大小后再发送数据。这样会减少网络通信次数,降低阻塞概率,但也会影响响应及时性。
# 比较适合于文件下载这类的大数据通信场景。
tcp_nodelay on|off;

# 单位s,适当降低此值可以提高响应连接数量
keepalive_timeout 65;

# 一次长连接上允许的最大请求数
keepalive_requests 100;

# 禁止指定浏览器使用 keepalive
keepalive_disable msie6|none;

# 读取 http 请求首部的超时时长。如果客户端在此时间内未传输整个头,则会向客户端返回 408(请求超时)错误
client_header_timeout 1;

# 读取 http 请求包体的超时时间。
client_body_timeout 2;

# 发送响应的超时时长。超时后连接将关闭。
send_timeout 5;

# http 请求包体的最大值,常用于限定客户端所能够请求的最大包体,根据请求首部中的 Content-Length 来检查,以避免无用的传输。
client_max_body_size 1m;

# 限制客户端每秒传输的字节数,默认为0,表示没有限制。单位 Byte/s
limit_rate 0;

# nginx 向客户端发送响应报文时,如果大小超过了此处指定的值,则后续的发送过程开始限速,单位 Byte
limit_rate_after 0;

# 是否忽略不合法的 http 首部,默认为 on,off 意味着请求首部中出现不合规的首部将拒绝响应。
ignore_invalid_headers on|off;

# 用户访问的文件不存在时,是否将其记录到错误日志中。
log_not_found on|off;

# nginx 使用的 dns 地址,及缓存解析结果的时间
resolver 8.8.8.8 [valid=time] [ipv6=on|off];

# dns 解析超时时间
resolver_timeout 2;

# 是否打开文件缓存功能,max:用于缓存条目的最大值,
# inactive:某缓存条目在指定时长内没有被访问过时,将自动被删除,即缓存有效期,通常默认为 60s。
open_file_cache off;
open_file_cache max=N [inactive=time];

# 是否缓存文件找不到或没有权限访问等相关信息。
open_file_cache_errors on | off;

# 多长时间检查一次缓存中的条目是否超出非活动时长。
# 建议值:小于等于 open_file_cache inactive
open_file_cache_valid 60;

# 在 open_file_cache inactive指 定的时长内被访问超过此处指定的次数时,才不会被删除(删除低命中率的缓存)。
open_file_cache_min_uses 2;

# 开启内容压缩,可以有效降低客户端的访问流量和网络带宽
gzip on | off;

# 内容超过最少长度后才开启压缩,太短的内容压缩效果不佳,且会浪费系统资源。
# 压缩长度会作为 http 响应头 Content-Length 字段返回给客户端。 建议值:64
gzip_min_length length;

# 压缩级别,默认值为 1。范围为1~9级,压缩级别越高压缩率越高,但对系统性能要求越高。建议值:4
gzip_comp_level 1~9;

# 压缩内容类型,默认为 text/html;。只压缩 html 文本,一般我们都会压缩 js、css、json 之类的,可以把这些常见的文本数据都配上。
如:text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_types mime-type …;

# 自动显示目录
autoindex on;

# off : 以人类易读的方式显示文件大小,on:以 bytes 显示文件大小
autoindex_exact_size off;

# 定义限制区域,imit_req_zone 只能放在 http {} 内,使用此限制可以在 http {} (对服务器内所有的网站生效)、server {} (对具体的一个网站生效)或 location {} (对具体的一个网址生效)
# 此区域名称为 test,可根据需求自定义; 10m 表示此区域存储 key($binary_remote_addr)使用的大小
# 存储大小,一般 1m 空间大约能保存 1.6 万条 IP 地址,空间满了新数据会覆盖旧数据
# rate=1r/m ,限制访问请求频率为每分钟 1 次,可根据需要自行设置,1r/s 是 1 秒 1 次,时间单位只能选择 s (秒)或 m (分),最低频率限制是每分钟 1 次访问请求
# rate=10r/m,1分钟最多访问 10 次,同时不能超过 1r/6s,即 6s 内最多访问 1 次。超过 1r/6s 返回 503
limit_req_zone $binary_remote_addr zone=test:10m rate=1r/m;

# 定义日志格式
log_format main '{ time: $time_iso8601|'
'http_host:$http_host|'
'cdn_ip:$remote_addr|'
'request:$request|'
'request_method:$request_method|'
'http_user_agent:$http_user_agent|'
'size:$body_bytes_sent|'
'responsetime:$request_time|'
'upstreamtime:$upstream_response_time|'
'upstreamhost:$upstream_addr|'
'upstreamstatus:$upstream_status|'
'url:$http_host$uri|'
'http_x_forwarded_for:$clientRealIp|'
'status:$status}';

# server 负责具体的 http 服务器实现
server {
listen 80 [default_server] [rcvbuf=SIZE] [sndbuf=SIZE] [ssl];

# 可使用通配符*或正则表达式(~开头),多个域名先精确匹配,再通配,再正则,'_'表示空主机头
server_name _ ;

access_log logs/access.log main;
error_log logs/access.err.log;

# 跨域配置
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers 'Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With';
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Credentials: true;

location / {
# web 资源路径
root html;

# 定义默认页面,从左往右匹配
index index.html index.htm;

# 自左向右读取指定路径,找到即停止,如果都不存在,返回一个错误码
try_files $uri $uri.html $uri/index.html =404;

# 自左向右读取指定路径,找到即停止,如果都不存在,返回一个 uri
try_files $uri $uri.html $uri/index.html /404.html;
}

location /i/ {
# 路径别名,只能用于 location 中。
# 访问 http://a.com/i/a.html, 资源路径为:/data/www/html/a.html
# 若是root指令,访问 http://a.com/i/a.html,资源路径为:/data/www/html/i/a.html
alias /data/www/html/;
}

# 对于某个请求发生错误,如果匹配到错误码,重定向到新的 url
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;

# 对于某个请求发生错误,如果匹配到错误码,重定向到新的 url,同时可以更改返回码
error_page 404 =200 /404.html;
}

# 包含其他配置文件
include vhosts/*.conf;
}


阅读全文 »

Kasm Workspaces 是在浏览器里运行完整应用/桌面/浏览器的 云工作空间平台

你会看到:

  • 浏览器里再打开一个 Chrome / Linux 桌面
  • 像用本地电脑一样操作
  • 所有计算都在服务器

Kasm 的核心能力

  • 浏览器里跑浏览器(最常用 Chrome / Chromium / Firefox)、OS 桌面(Ubuntu / XFCE、Kali Linux 等)、应用(GUI 软件、Terminal 等)
  • 多用户(账号)登录且完全隔离(每个用户一个容器)
  • 支持自动销毁
  • WebRTC 显示协议提供低延迟画面流

部署步骤

docker-compose.yaml
services:
kasm:
image: lscr.io/linuxserver/kasm:latest
container_name: kasm
privileged: true
environment:
- KASM_PORT=443
volumes:
- ./data:/opt
- ./profiles:/profiles
ports:
- 3000:3000
- 443:443
restart: unless-stopped

启动后,首先使用 https://<IP>:3000 进行初始化,初始化时需要设置管理员账号密码信息。初始化成功后,通过 https://<IP>:443 连接 Workspaces。

常用配置

在 Workspaces 中使用中文输入法

默认大多数的 Workspace 容器启动时都是没有中文输入的。如果要提供中文输入,可以使用系统提供的 IME Input Mode 。它可以将本地电脑的输入映射到 Kasm Workspaces 中。只需在对应的 Workspace 中开启 IME 配置即可。

  1. 使用管理员登录 Kasm。定位到 Workspaces ,编辑对应的 Workspace(如 Kali Linux),找到 Docker Run Configuration Override (JSON)

    填入以下内容(如果有其他内容,插入以下内容),开启 IME

    {
    "environment": {
    "KASM_IME": "1"
    }
    }

  2. 销毁之前的 Workspace Session,运行新的 Workspace Session,打开左侧的配置菜单,找到 Advanced Settings ,将 IME Input Mode 配置为 On

    即可在 Workspace 中使用中文

用户数据持久化方案

在 Kasm 中,实现 Workspace 用户数据(如 Chrome 书签、桌面配置、文档等)持久化的标准方案是使用 Persistent Profiles(持久化配置层)

核心原理

Kasm 会将容器内的用户目录(通常是 /home/kasm )映射到宿主机的特定路径(即 docker-compose.yaml 中定义的 /profiles 目录)。当用户结束会话时,Kasm 会将该目录打包;下次启动时,再自动解压回容器。

开启 Persistent Profiles

  1. 确认宿主机存储路径,在 docker-compose.yaml 中,已经配置了持久化卷 /opt/kasm-workspaces/profiles:/profiles ,这表示所有持久化数据将保存在宿主机的 /opt/kasm-workspaces/profiles 目录下。
  2. 在 Kasm 管理后台开启功能(Web UI 操作)
    1. 登录管理后台 :使用 [email protected] (管理员账户)登录。
    2. 导航到 Settings -> Storage Providers ,点击 Add Storage Provider 添加存储,本示例使用本地存储(Local)
      • Name : local-storage
      • Provider Type : 保持为 Custom
      • Default Target : /profiles
      • Volume Configuration (JSON):
        {
        "driver": "local",
        "driver_opts": {
        "o": "bind",
        "type": "none",
        "device": "/profiles"
        }
        }
      • Mount Configuration (JSON) : 保持默认或确保它是空的 JSON 对象
  3. 点击左侧菜单 Workspaces -> Workspaces 。找到 Chrome 或 Ubuntu ,点击 Edit
  4. 导航到 Storage Mapping 页面,Add Storage Mapping ,选择之前创建的 local-storage 并添加
  5. Details 页面中,下翻页面找到 Persistent Profile Path ,在其中填入 /profiles/{user_id} ,启用 Enforce Workspace Persistent Profile

    Kasm 会自动将 {user_id} 替换为登录用户的 ID(例如 admin )。这样即便有多个用户,他们的数据也会分开放,互不干扰。

  6. 删除并开启新的 Session,如 Chrome 浏览器,在其中保存书签,删除 Session,重新启动 Session,看看之前保存的书签是否存在。或者检查宿主机的目录 /opt/kasm-workspaces/profiles/ 中是否有文件存在。配置了 Persistent Profile 后,在 Kasm Workspaces 中启动 Session 也会显示已经启用了 Persistent Profile

Docker Compose 部署 Opensearch 集群并启用 Security Plugin

  • Opensearch 3.5.0

在每台节点机器上使用本文档的目录结构,通过 Docker Compose 以 Host Network 模式部署 OpenSearch 3 节点集群,并启用 OpenSearch Security + TLS(HTTP 与 Transport 双层 TLS)。

规划如下信息如下:

  • opensearch-1:172.16.10.72 ,配置 32 vCPU, 32G RAM

  • opensearch-2: 172.16.10.70

  • opensearch-3: 172.16.10.71

  • 集群名:opensearch-prod

  • 角色 :3 台节点都同时作为

    • cluster-manager
    • data
    • ingest

    这样 3 节点具备仲裁能力,任意挂 1 台仍可工作。

  • Docker Host Network 模式会直接占用宿主机端口:

    • HTTP:9200
    • Transport:9300
  • 生产建议(需要你在系统层面自行设置):

    • echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf ,执行命令 sudo sysctl -p 生效
    • 进程打开文件数 nofile >= 65536
    • 结合 bootstrap.memory_lock: true,确保 memlock 相关限制满足
    • 关闭 SWAP: sudo swapoff -a

创建持久化数据目录:

mkdir -p /opt/opensearch-cluster

mkdir -p /opt/opensearch-cluster/{config/opensearch/certs,data,logs}

mkdir -p /opt/opensearch-cluster/config/opensearch/opensearch-security

整体目录结构如下:

.
├── config
│ └── opensearch
│ ├── certs
│ │ ├── opensearch-ca.key
│ │ ├── opensearch-ca.p12
│ │ ├── opensearch-ca.pem
│ │ ├── instances.yml
│ │ ├── opensearch-1
│ │ │ └── opensearch-1.p12
│ │ ├── opensearch-2
│ │ │ └── opensearch-2.p12
│ │ └── opensearch-3
│ │ └── opensearch-3.p12
│ ├── jvm.options.d
│ ├── opensearch-security
│ │ ├── action_groups.yml
│ │ ├── allowlist.yml
│ │ ├── audit.yml
│ │ ├── config.yml
│ │ ├── internal_users.yml
│ │ ├── nodes_dn.yml
│ │ ├── roles_mapping.yml
│ │ ├── roles.yml
│ │ └── tenants.yml
│ └── opensearch.yml
├── data
├── docker-compose.yaml
├── logs
└── README.md

通用的 Docker Compose 配置

docker-compose.yaml
services:
opensearch:
image: opensearchproject/opensearch:3.5.0
container_name: opensearch
restart: unless-stopped

network_mode: host # 推荐使用 Host Network 模式,否则网络很难管理,容易出问题

ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536

environment:
- OPENSEARCH_JAVA_OPTS=-Xms16g -Xmx16g
- DISABLE_INSTALL_DEMO_CONFIG=true
- TZ=Asia/Shanghai

volumes:
- ./config/opensearch/opensearch.yml:/usr/share/opensearch/config/opensearch.yml:ro
- ./config/opensearch/jvm.options.d:/usr/share/opensearch/config/jvm.options.d:ro
- ./config/opensearch/certs:/usr/share/opensearch/config/certs:ro
- ./config/opensearch/opensearch-security:/usr/share/opensearch/config/opensearch-security:ro
- ./data:/usr/share/opensearch/data

阅读全文 »

Zabbix 支持 TimescaleDB 作为 PostgreSQL 扩展,用于大规模时间序列数据。

TimescaleDB 不支持给 Zabbix Proxy 当数据库 ,只能给中心 zabbix-server 的 PostgreSQL 后端使用。

Zabbix 7.0.6 开始把 TimescaleDB 最大支持版本提升到 2.17.x,7.0.13 提升到 2.19.x。

使用 Docker Compose 部署 PostgreSQL

  • pg16
  • timescaledb 2.19.x

目录结构示例:

.
├── config # postgresql 等配置文件路径
│ ├── pg_hba.conf
│ └── postgresql.conf
├── docker-compose.yaml
├── .env # 环境变量统一存储
├── initdb # 数据库初始化 sql,只有在数据库为空的情况下才会执行
│ ├── 01-create-zabbix-user.sql
│ ├── 02-create-timescaledb.sql
│ └── 03-create-db.sql
├── postgres-data # Postgresql/scaledb 持久化数据
│ └── pgdata
└── zabbix-data # zabbix 持久化数据
├── alertscripts
├── enc
├── export
├── externalscripts
└── ssl

10 directories, 6 files


.env 内容参考

.env
TZ=Asia/Shanghai

POSTGRES_DB=zabbix
POSTGRES_USER=postgres
POSTGRES_PASSWORD=ReplaceMe_SuperStrong_Zabbix

ZABBIX_DB_USER=zabbix
ZABBIX_DB_PASSWORD=ReplaceMe_SuperStrong_Zabbix

PGDATA=/var/lib/postgresql/data/pgdata

initdb 中的初始化 SQL

initdb/01-create-zabbix-user.sql
DO
$$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'zabbix') THEN
CREATE ROLE zabbix LOGIN PASSWORD 'ReplaceMe_SuperStrong_Zabbix';
END IF;
END
$$;

initdb/02-create-timescaledb.sql
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
initdb/03-create-db.sql
GRANT ALL PRIVILEGES ON DATABASE zabbix TO zabbix;
ALTER DATABASE zabbix OWNER TO zabbix;

Postgresql 主配置 config/postgresql.conf

config/postgresql.conf
listen_addresses = '*'
port = 5432
max_connections = 200

shared_buffers = 8GB
effective_cache_size = 24GB
work_mem = 32MB
maintenance_work_mem = 1GB

wal_level = replica
max_wal_size = 8GB
min_wal_size = 1GB
checkpoint_timeout = 15min
checkpoint_completion_target = 0.9
wal_compression = on

random_page_cost = 1.1
effective_io_concurrency = 200

default_statistics_target = 200
shared_preload_libraries = 'timescaledb'

autovacuum = on
autovacuum_max_workers = 5
autovacuum_naptime = 10s
autovacuum_vacuum_scale_factor = 0.02
autovacuum_analyze_scale_factor = 0.01

log_timezone = 'Asia/Shanghai'
timezone = 'Asia/Shanghai'
log_min_duration_statement = 1000
log_checkpoints = on
log_connections = off
log_disconnections = off
log_lock_waits = on

timescaledb.telemetry_level = off

config/pg_hba.conf 配置示例

# TYPE  DATABASE        USER            ADDRESS                 METHOD

local all all trust
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256

# Docker 网段,仅示例
host zabbix zabbix 10.244.0.0/16 scram-sha-256
host zabbix zabbix 172.18.0.0/16 scram-sha-256

# 运维堡垒机
host all postgres 192.168.20.10/32 scram-sha-256

docker-compose.yaml 配置示例

docker-compose.yaml
services:
timescaledb:
image: timescale/timescaledb:2.19.3-pg16
container_name: zabbix-timescaledb
hostname: zabbix-timescaledb
restart: unless-stopped

env_file:
- .env

environment:
TZ: ${TZ}
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ZABBIX_DB_USER: ${ZABBIX_DB_USER}
ZABBIX_DB_PASSWORD: ${ZABBIX_DB_PASSWORD}
PGDATA: ${PGDATA}
POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=trust --data-checksums"

ports:
- "5432:5432"

volumes:
- ./postgres-data:/var/lib/postgresql/data
- ./initdb:/docker-entrypoint-initdb.d:ro
- ./config/postgresql.conf:/etc/postgresql/postgresql.conf:ro
- ./config/pg_hba.conf:/etc/postgresql/pg_hba.conf:ro
- /etc/localtime:/etc/localtime:ro

command:
[
"postgres",
"-c", "config_file=/etc/postgresql/postgresql.conf",
"-c", "hba_file=/etc/postgresql/pg_hba.conf"
]

healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 12

ulimits:
nofile:
soft: 65536
hard: 65536

zabbix-server:
image: zabbix/zabbix-server-pgsql:alpine-7.4-latest
container_name: zabbix-server
hostname: zabbix-server
restart: unless-stopped

env_file:
- .env

environment:
TZ: ${TZ}
DB_SERVER_HOST: timescaledb
DB_SERVER_PORT: 5432
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${ZABBIX_DB_USER}
POSTGRES_PASSWORD: ${ZABBIX_DB_PASSWORD}
ENABLE_TIMESCALEDB: "true"

depends_on:
timescaledb:
condition: service_healthy

ports:
- "10051:10051"

volumes:
- ./zabbix-data/alertscripts:/usr/lib/zabbix/alertscripts:ro
- ./zabbix-data/externalscripts:/usr/lib/zabbix/externalscripts:ro
- ./zabbix-data/export:/var/lib/zabbix/export
- ./zabbix-data/enc:/var/lib/zabbix/enc
- /etc/localtime:/etc/localtime:ro

zabbix-web:
image: zabbix/zabbix-web-nginx-pgsql:alpine-7.4-latest
container_name: zabbix-web
hostname: zabbix-web
restart: unless-stopped

env_file:
- .env

environment:
DB_SERVER_HOST: timescaledb
DB_SERVER_PORT: 5432
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${ZABBIX_DB_USER}
POSTGRES_PASSWORD: ${ZABBIX_DB_PASSWORD}
ZBX_SERVER_HOST: zabbix-server
PHP_TZ: ${TZ}

depends_on:
- zabbix-server

ports:
- "8080:8080"

volumes:
- ./zabbix-data/ssl:/etc/ssl/nginx:ro
- /etc/localtime:/etc/localtime:ro


常用操作

从 Zabbix Server 测试 Zabbix Agent 连通性

以下命令可以用于在 Zabbix Server 检查到 Zabbix Agent 的连通性,连接 Agent 正常会返回 1

docker compose exec -T zabbix-server zabbix_get -s 172.247.1.1 -p 10050 -k agent.ping

如果防火墙已经放通,却依然无法连接,很可能是 zabbix-agent2 配置中的 Server= 配置的 IP 不是 Zabbix Server 实际使用的 IP。此时可以通过 tcpdump 抓包确定请求 Zabbix Agent 的 Zabbix Server IP

# tcpdump -n dst port 10050
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes

12:39:34.936583 IP 172.18.0.3.37688 > 172.247.2.14.10050: Flags [S], seq 1718798137, win 64240, options [mss 1460,sackOK,TS val 2935844814 ecr 0,nop,wscale 7], length 0
12:39:35.946554 IP 172.18.0.3.37688 > 172.247.2.14.10050: Flags [S], seq 1718798137, win 64240, options [mss 1460,sackOK,TS val 2935845824 ecr 0,nop,wscale 7], length 0

也可查看 zabbix-agent2 日志:

# tail -f /var/log/zabbix/zabbix_agent2.log 
2026/03/30 01:48:15.505489 connection from "172.18.0.3" rejected, allowed hosts: "23.225.3.6/29"
2026/03/30 01:48:39.128438 connection from "172.18.0.3" rejected, allowed hosts: "23.225.3.6/29"

分路由发送告警

为了将不同的告警发送给不同的用户,首先需要为每个用户添加 Media,以 Admin 用户为例,为其添加 Media。定位到 Users -> Users: Admin -> Media -> Add ,以添加 Telegram 为例,在 Send to 中填入 群组 ID

配置 Medias 。定位到 Alerts -> Media types 。Zabbix Server 已经配置了非常多可用的告警 Medias,选择 Telegram,将 api_token 修改为你自己的 Telegram Bot API Token,确保配置中的 api_chat_id 的值为 {ALERT.SENDTO}

不要在这里硬编码 api_chat_id ,否则使用 Telegram 这个渠道只能将所有的消息发送到同一个群组,无法达到告警分流的作用,正确做法是将其配置为 {ALERT.SENDTO} ,当用户 Media 被调用时,会自动使用用户 Media 变量 Send to 的值达到告警分流到不通用户的 Media

创建 Actions 触发发送告警 。告警 Medias 配置好后,需要创建一个 Action(或修改默认的),以决定什么时候触发告警

定位到 Alerts -> Actions -> Trigger actions 。配置 Action

配置 Operations 发送消息。在 Alerts -> Actions -> Trigger actions -> Action: Operations 中配置

确定 Actions Trigger ,在 Monitoring -> Problems 中观察具体告警中的 Actions,看是否有关联,如果 Actions 为空,说明Action 根本没有被触发 ,检查 Action 配置

如果 Problems 中的事件是在创建 Action 之前产生,那么它不会自动关联到新建的 Actions。此时可以 Update problems,将其关闭(Close),使其重新产生, Action 配置正常的话,会自动关联到 Action。

Zabbix 告警默认只会发送一次,(未恢复)不会持续发送。要配置未恢复的问题间隔几分钟后再次发送。可以参考以下配置:

  1. Alerts -> Actions -> Trigger actions -> Action: Operations 中配置时间间隔
    Default operation step duration = 5m
    每一步之间间隔 5分钟
  2. 增加多个 Step

常见问题处理

Zabbix Web 中主机状态显示 Unknow

在 Zabbix Server 测试主机连通性返回 1 ,说明 网络 + agent 都是 OK 的

root@zabbix:/opt/zabbix# docker compose exec -T zabbix-server zabbix_get -s 172.17.0.1 -p 10050 -k agent.ping
1

root@zabbix:/opt/zabbix# docker compose exec -T zabbix-server zabbix_get -s 172.17.0.1 -p 10050 -k system.uptime
368373

但是 Zabbix Web 中主机状态显示 Unknow 。这种情况大概率是 Host 配置问题,重点检查:

  • 是否绑定了 Templates ,如果没有模版,就没有抓取项(Items),Zabbix Server 不会被动去抓取主机指标
  • Interfaces 配置是否正确

环境信息

  • ansible-core 2.16
  • Docker image python:3.12.3
Ansible 安装部署参考 Ansible templates 使用介绍

Ansible Playbook 语法

Playbooks 使用 YAML 语法定义(描述)。一个 playbook 由一个或多个 play 依序组成。每个 play 运行一个或多个 task,每个 task 也称为一个 module

每一个 play 中包含了一个 tasks 列表,tasks 列表中的每个 task 在其对应的 hosts依次执行即一个 task 执行完毕,下一个 task 才会执行

在运行 playbook 的过程中,如果一个 host 执行 task 失败,这个 host 将从整个 playbook 中移除。如果发生执行失败的情况,需要修正 playbook 中的错误,重新执行。

Ansible playbook 示例:

playbook.yml
---
- name: Update web servers
hosts: webservers
remote_user: root

tasks:
- name: Ensure apache is at the latest version
ansible.builtin.yum:
name: httpd
state: latest

- name: Write the apache config file
ansible.builtin.template:
src: /srv/httpd.j2
dest: /etc/httpd.conf

- name: Update db servers
hosts: databases
remote_user: root
vars:
port: 8080

tasks:
- name: Ensure postgresql is at the latest version
ansible.builtin.yum:
name: postgresql
state: latest

- name: Ensure that postgresql is started
ansible.builtin.service:
name: postgresql
state: started

---
- name: Install multiple packages
hosts: webservers
tasks:
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- git
- curl

一个 Ansible playbook 由一个或多个 plays 组成,每个 play 包含以下部分:

  • name : 描述性的名称
  • hosts : 指定目标主机, 必须字段
  • become : 提升权限(默认是使用 sudo 提升到 root 用户)
  • remote_user : 用于连接到远程主机的账户。(如果 Inventory 中定义了远程连接的用户,会覆盖此处的配置)
  • tasks : 要执行的一系列任务列表
  • vars : 用于定义变量,便于管理和重用
  • gather_facts : 收集 Facts, 默认值为 yes

tasks 是一个任务列表,每个任务执行特定的操作。任务包含以下元素:

  • name : 描述任务的目的。
  • module_name : Ansible 模块名称,如 aptservice 等。
  • module_options : 模块的参数,以键值对的形式提供。
  • when : 条件语句,控制任务是否执行。
  • loop : 循环执行任务

执行以下命令运行 playbook.yml

ansible-playbook playbook.yml -f 10

常用选项说明

选项 说明 示例
-f
--forks
指定并发执行的数量,默认为 5
-v
--verbose
-vvvvvv
打印 debug 信息,详细程度从 -v-vvvvvv
-C
--check
Check mode,不执行任何实际操作,而是对要执行的操作进行验证
-D
--diff
- 只使用 --diff 会执行 play 定义的实际操作,并对所有受影响的文件或者模板显示其变更前后的具体差异
- --check 一起使用,不会执行 play 定义的实际操作,只显示变更前后的差异,可以在实际执行前,调试/预览将要进行的变更,防止意外配置变更或文件修改
主要用于文件或者模板的变更,对于其他类型的任务(如包安装、服务管理、修改主机名等),不会显示具体的差异( 配合 --check 使用时,结果会显示为 skipping,实际执行时结果为 changed )。
--list-hosts 不执行任何实际操作,只列出符合 pattern 的目标主机
--list-tasks 不执行任何实际操作,只列出将要执行的 task
--syntax-check 不执行任何实际操作,只检查 playbook 文件是否有语法错误
-i INVENTORY, --inventory INVENTORY, --inventory-file INVENTORY ansible-playbook 命令行中指定使用那个 Inventory 文件
-l SUBSET, --limit SUBSET 只能在 playbook 已经选定的 hosts 范围内做进一步过滤
阅读全文 »

nftables 是一个 netfilter 项目,旨在替换现有的 {ip,ip6,arp,eb}tables 框架,为 {ip,ip6}tables 提供一个新的包过滤框架、一个新的用户空间实用程序(nft)和一个兼容层(iptables-nft)。它使用现有的钩子、链接跟踪系统、用户空间排队组件和 netfilter 日志子系统。

在 Linux 内核版本高于 3.13 时可用

它由三个主要组件组成:

  • 内核实现: 内核提供了一个 netlink 配置接口以及运行时规则集评估
  • libnl netlink : libnl 包含了与内核通信的基本函数
  • nftables : 用户空间前端。nftables 的用户空间实用程序 nft 评估大多数规则集并传递到内核。规则存储在链中,链存储在表中。

iptables 不同点

  • nftables 在用户空间中运行,iptables 中的每个模块都运行在内核(空间)中
  • 表和链是完全可配置的。在 nftables 中,表是没有特定语义的链的容器。iptables 附带了具有预定义数量的基链的表,即使您只需要其中之一,所有链都已注册,未使用的基础链也会损害性能。
  • 可以在一个规则中指定多个操作。在 iptables 中,您只能指定一个目标。这是一个长期存在的局限性,用户可以通过跳到自定义链来解决,但代价是使规则集结构稍微复杂一些。
  • 每个链和规则没有内置计数器。在 nftables 中,这些是可选的,因此您可以按需启用计数器。由于 iptables 内置了一个数据包计数器,所以即使这些内置的链是空的,也会带来性能损耗
  • 更好地支持动态规则集更新。在 nftables 中,如果添加新规则,则剩余的现有规则将保持不变,因为规则集以链表形式表示,这与整体式 blob 表示相反,后者在执行规则集更新时内部状态信息的维护很复杂。
  • 简化的双堆栈 IPv4/IPv6 管理,通过新的 inet 系列,可让您注册同时查看 IPv4 和 IPv6 流量的基链。 因此,您不再需要依赖脚本来复制规则集。

服务名称默认为 nftables,默认配置文件为 /etc/nftables.conf ,其中已经包含一个名为 inet filter 的简单 ipv4/ipv6 防火墙列表。

/etc/nftables.conf
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
chain input {
type filter hook input priority 0;
}
chain forward {
type filter hook forward priority 0;
}
chain output {
type filter hook output priority 0;
}
}

nftables 服务的 systemd 配置文件如下:

/lib/systemd/system/nftables.service
[Unit]
Description=nftables
Documentation=man:nft(8) http://wiki.nftables.org
Wants=network-pre.target
Before=network-pre.target shutdown.target
Conflicts=shutdown.target
DefaultDependencies=no

[Service]
Type=oneshot
RemainAfterExit=yes
StandardInput=null
ProtectSystem=full
ProtectHome=true
ExecStart=/usr/sbin/nft -f /etc/nftables.conf
ExecReload=/usr/sbin/nft -f /etc/nftables.conf
ExecStop=/usr/sbin/nft flush ruleset

[Install]
WantedBy=sysinit.target

nftables 使用的内核模块如下,加载这些模块,服务才能正常运行

# lsmod | grep nf
nf_log_syslog 20480 1
nft_chain_nat 16384 17
nf_nat 49152 3 xt_nat,nft_chain_nat,xt_MASQUERADE
nf_conntrack_netlink 49152 0
nft_counter 16384 142
nf_reject_ipv4 16384 1 ipt_REJECT
nf_conntrack 172032 6 xt_conntrack,nf_nat,xt_state,xt_nat,nf_conntrack_netlink,xt_MASQUERADE
nf_defrag_ipv6 24576 1 nf_conntrack
nf_defrag_ipv4 16384 1 nf_conntrack
nft_compat 20480 143
nf_tables 249856 570 nft_compat,nft_counter,nft_chain_nat
nfnetlink 20480 5 nft_compat,nf_conntrack_netlink,nf_tables,ip_set

nftables 在启动时(执行 nft -f /etc/nftables.conf )是顺序解析的。在使用了 include 动态加载 set (集合)的场景中, 如果它解析主配置文件没问题,但是子配置( include 文件)有语法错误,可能会出现主配置加载成功并已经应用,但是子配置未加载导致规则不全 ,因此建议修改 nftables 服务的启动流程,启动之前首先检查所有的配置文件( nft -c -f /etc/nftables.conf ),如果配置检查不通过,则不启动。

使用 systemctl edit 创建一个 drop-in 覆盖文件,而不是直接修改 /lib/systemd/system/nftables.service (这样可以防止系统更新时被覆盖)。

systemctl edit nftables.service

在其中写入以下内容

[Service]
# 在执行真正的启动 (ExecStart) 或重载 (ExecReload) 之前,先运行语法检查
# 注意:这里假设你的主配置文件是 /etc/nftables.conf,如果不是,请替换为实际路径
ExecStartPre=/usr/sbin/nft -c -f /etc/nftables.conf
阅读全文 »

K3S 是 Kubernetes(K8S)的简化部署版本,日常使用几乎一模一样,差别主要在 安装、资源占用、默认组件 。适合

  • 小服务器(2C2G)
  • 边缘计算
  • 开发 / 测试
  • 单节点集群
  • homelab

K3S 安装部署

安装部署非常简单

# curl -sfL https://get.k3s.io | sh -s
[INFO] Finding release for channel stable
[INFO] Using v1.34.5+k3s1 as release
[INFO] Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.34.5%2Bk3s1/sha256sum-amd64.txt
[INFO] Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.34.5%2Bk3s1/k3s
[INFO] Verifying binary download
[INFO] Installing k3s to /usr/local/bin/k3s
[INFO] Skipping installation of SELinux RPM
[INFO] Creating /usr/local/bin/kubectl symlink to k3s
[INFO] Skipping /usr/local/bin/crictl symlink to k3s, command exists in PATH at /usr/bin/crictl
[INFO] Skipping /usr/local/bin/ctr symlink to k3s, command exists in PATH at /usr/bin/ctr
[INFO] Creating killall script /usr/local/bin/k3s-killall.sh
[INFO] Creating uninstall script /usr/local/bin/k3s-uninstall.sh
[INFO] env: Creating environment file /etc/systemd/system/k3s.service.env
[INFO] systemd: Creating service file /etc/systemd/system/k3s.service
[INFO] systemd: Enabling k3s unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s.service → /etc/systemd/system/k3s.service.
[INFO] systemd: Starting k3s
  • 网络插件(CNI)默认用 Flannel
  • Ingress Controller 默认用 Traefik
  • 存储默认用 local-path

安装之后会启动 k3s (systemd service) ,查看服务状态

systemctl status k3s

K3s 自带的 kubeconfig 一般在 export KUBECONFIG=/etc/kubernetes/admin.conf ,要注意此变量值,否则 kubectl 可能连接到错误的集群或无法连接

# kubectl get nodes
E0325 15:37:50.808781 3442105 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: the server could not find the requested resource"
E0325 15:37:50.814831 3442105 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: the server could not find the requested resource"

K3S 依赖宿主机的 /etc/resolv.conf ,如果其中配置了 nameserver 127.0.0.53 会导致容器无法解析外部域名而不可用,需要修改为容器可以访问的 DNS 地址,如 8.8.8.8

nftables 防火墙示例规则参考:

table inet filter {
chain input {
type filter hook input priority filter + 10; policy drop;
ct state established,related counter packets 2702 bytes 272968 accept
iifname "lo" counter packets 12 bytes 876 accept
icmp type echo-request counter packets 3 bytes 204 accept
icmp type echo-reply counter packets 0 bytes 0 accept

tcp dport 22 counter packets 0 bytes 0 accept comment "for sshd"
ip saddr 10.0.0.0/8 accept comment "for k3s"
udp dport 8472 accept comment "for k3s"
tcp dport 10250 accept comment "for k3s"
tcp dport { 80, 443 } counter packets 0 bytes 0 accept comment "for k3s"
counter packets 595 bytes 36023 drop
}

chain forward {
type filter hook forward priority filter; policy accept;
}

chain output {
type filter hook output priority filter; policy accept;
}

chain DOCKER {
}
}

安装常用工具

lrzsz

dnf install -y https://www.rpmfind.net/linux/centos-stream/9-stream/BaseOS/x86_64/os/Packages/lrzsz-0.12.20-55.el9.x86_64.rpm

crontab

sudo dnf install -y cronie
sudo systemctl enable crond
sudo systemctl start crond

docker compose

dnf install -y docker


DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v5.0.1/docker-compose-linux-x86_64 \
-o $DOCKER_CONFIG/cli-plugins/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
docker compose version

mkdir -p ~/.docker/cli-plugins

curl -L https://github.com/docker/buildx/releases/download/v0.17.1/buildx-v0.17.1.linux-amd64 \
-o ~/.docker/cli-plugins/docker-buildx

chmod +x ~/.docker/cli-plugins/docker-buildx

OpenClaw ,包括 OpenCode 等都是是开源社区针对官方 Claude Code 打造的全能型、无限制开源替代方案。

核心架构:三位一体

OpenClaw 的运行由三个核心部分驱动:

  • 配置文件 (Config) :定义 身体 ——连接哪些模型、通过什么频道通话、开放哪些端口。
  • 工作区 (Workspace) :定义 心智 ——我是谁、我的性格、我记住了什么、我要做什么。
  • 主动任务 (Heartbeat/Cron) :定义 意识 ——在没人找我时,我该主动检查什么。

OpenClaw 安装部署

  • OpenClaw v2026.3.8
  • Node.js v22.22.1

OpenClaw 依赖 Node.js 22 或更高版本。可以参考以下命令安装 Node.js

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc

nvm install 22
nvm use 22
nvm alias default 22

安装 OpenClaw,以下两种方式任选其一即可,也可用于升级。

curl -fsSL https://openclaw.ai/install.sh | bash

npm install -g openclaw@latest

初始化与配置 ,安装完成后,你需要运行 onboard 向导来配置 API Key(如 Anthropic, OpenAI 或本地的 Ollama)以及通信渠道。

openclaw onboard --install-daemon

配置渠道(以 Telegram 为例)

openclaw channels login telegram

查看 OpenClaw 安装的 Skills

openclaw skills list

安装完成后,其配置和常用文件位于 ~/.openclaw/

# tree .openclaw/
.openclaw/
├── agents
│ └── main
│ ├── agent
│ │ └── auth-profiles.json
│ └── sessions
│ ├── 6a0afd9a-145e-4d81-8c06-470b1fff0be9.jsonl
│ └── sessions.json
├── canvas
│ └── index.html
├── completions
│ ├── openclaw.bash
│ ├── openclaw.fish
│ ├── openclaw.ps1
│ └── openclaw.zsh
├── cron
│ └── jobs.json
├── devices
│ ├── paired.json
│ └── pending.json
├── identity
│ ├── device-auth.json
│ └── device.json
├── logs
│ └── config-audit.jsonl
├── openclaw.json # 主配置文件
├── openclaw.json.bak
├── update-check.json
└── workspace
├── AGENTS.md
├── BOOTSTRAP.md
├── HEARTBEAT.md
├── IDENTITY.md
├── SOUL.md
├── TOOLS.md
└── USER.md

阅读全文 »

AWS OpenSearch 是一个完全开源的搜索和分析引擎,用于日志分析、实时应用程序监控和点击流分析等用例。

AWS OpenSearch(AOS) 和 Elasticsearch 对比

OpenSearch Elasticsearch
背景 AWS 主导的开源分支 Elastic 公司主导
授权 Apache 2.0 SSPL + Elastic License
商业控制 社区驱动 公司控制
K8s 生态 非常友好 也支持
云原生趋势 越来越主流 商业版更强
日志检索 很强 很强
向量搜索 更成熟
ML 功能 更强
APM 基础 商业版更强
插件生态 少一些 更丰富

原本是同一个项目

2021 年因授权问题分叉

👉 7.10 是最后一个纯开源 Elasticsearch 版本
之后 AWS fork 出:

🔹 OpenSearch

而原厂继续发展:

🔹 Elasticsearch

在 AWS 上,Amazon 托管服务默认是 OpenSearch。

创建 OpenSearch 域 (AOS)

  • AWS OpenSearch Service Version: v 3.3.0

OpenSearch 服务域是 OpenSearch 集群的同义词.域是包含您指定的设置、实例类型、实例计数和存储资源的集群。
登录控制台:前往 Amazon OpenSearch Service。 Domains -> Create domain

  • 填写 域(Domain)名称
  • 选择 标准创建
  • 选择实例类型和数量
  • 网络选项包含 2 种,创建集群后不能再变更:
    • VPC 访问,此中方式创建的 Domain 只支持 VPC 内部访问,不支持互联网公开访问,如果要互联网访问,可以通过在 VPC 内部使用 Nginx 代理。
    • 公开访问权限,允许互联网访问

使用 公开访问权限 部署的 AOS,可以直接使用 OpenSearch 控制面板 URL 在互联网访问。如果要控制访问,可以使用以下方法:

  • 修改 集群访问策略 (Access Policy)

    1. 进入 Amazon OpenSearch Service 控制台,点击进入你的 Domain (网域)
    2. 点击 Security configuration (安全配置) 选项卡。
    3. 滚动到 Access policy (访问策略) 部分。
    4. 在 JSON 编辑框中,添加基于 IpAddressCondition
      {
      "Version": "2012-10-17",
      "Statement": [
      {
      "Effect": "Allow",
      "Principal": {
      "AWS": "*"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-southeast-1:你的账号ID:domain/你的域/*",
      "Condition": {
      "IpAddress": {
      "aws:SourceIp": [
      "1.2.3.4/32", // 你办公室的公网出口 IP
      "5.6.7.8/32" // EKS 节点的公网出口 IP (NAT Gateway EIP)
      ]
      }
      }
      }
      ]
      }
阅读全文 »

Node.js 是一个 基于 Chrome V8 引擎的 JavaScript 运行环境,可以让 JavaScript 在服务器端运行

  • JavaScript : 原本只能在浏览器运行
  • Node.js : 让 JS 可以读写服务器

Node.js 的核心特点:

  • 单线程模型 ,通过 事件循环(Event Loop) 实现高并发。
  • 非阻塞 IO ,适合 IO 密集型任务,不适合 CPU 密集型任务(单线程一旦被卡住,整个服务器就会卡住)。
  • npm 生态npm 是世界最大的包管理平台。

Node.js 安装

wget https://nodejs.org/dist/latest/node-v15.12.0-linux-x64.tar.gz
tar -xf node-v15.12.0-linux-x64.tar.gz -C /usr/local
ln -s /usr/local/node-v15.12.0-linux-x64/bin/* /bin/

安装pm2

npm install pm2 -g
npm install -g [email protected] # 安装指定版本
npm install -g pm2@latest # 安装最新版本

Node.js 相关常见操作

安装包

npm install pm2

安装指定版本的包

npm install -g [email protected]

查看可用的安装版本

hexo 安装包为例,以下命令查看 hexo 安装包有哪些可选版本

# npm show hexo versions
[
'3.3.9', '3.4.0', '3.4.1', '3.4.2', '3.4.3',
'3.4.4', '3.5.0', '3.6.0', '3.7.0', '3.7.1',
'3.8.0', '3.9.0', '4.0.0', '4.1.0', '4.1.1',
'4.2.0', '4.2.1', '5.0.0', '5.0.1', '5.0.2',
'5.1.0', '5.1.1', '5.2.0', '5.3.0', '5.4.0',
'5.4.1', '5.4.2', '6.0.0', '6.1.0', '6.2.0',
'6.3.0', '7.0.0-rc1', '7.0.0-rc2'
]

查看已安装的包名

以下命令可显示安装的包及它们的版本

npm ls

如果要查看全局类型的包,使用 -g 选项

npm ls -g

卸载安装的包

npm uninstall package_name

卸载全局安装的包

npm uninstall package_name -g

Node.js 常见错误

WARN EACCES user “root” does not have permission to access the dev dir “/root/.node-gyp/11.15.0”
ERR! stack Error: EACCES: permission denied, mkdir ‘node_modules/sqlite3/.node-gyp’

[解决方法]:

npm install --unsafe-perm

Node.js 基础项目结构

一个 Node 项目通常是:

project
├─ node_modules
├─ package.json
├─ package-lock.json
├─ app.js
└─ routes

在 Node.js 生态中,通常有两个 核心基础配置文件

  • package.json : 这是每个 Node.js 项目的核心。它定义了项目的元数据、依赖项和运行脚本。
  • .env : 我们绝不应该把数据库密码、API 密钥等敏感信息直接写在代码里。通常配合 dotenv 库使用。

package.json 配置文件详解

{
"name": "admin",
"version": "4.4.0",
"description": "A magical vue admin. An out-of-box UI solution for enterprise applications. Newest development stack of vue. Lots of awesome features",
"author": "Auth <[email protected]>",
"main": "app.js",
"scripts": {
"dev": "vue-cli-service serve",
"build": "prisma generate && nest build",
"lint": "eslint --ext .js,.vue src",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"start": "nest start",
"preview": "node build/index.js --preview",
"new": "plop",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
"test:unit": "jest --clearCache && vue-cli-service test:unit",
"test:ci": "npm run lint && npm run test:unit"
},
"dependencies": {
"axios": "0.18.1",
"clipboard": "2.0.4"

},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-plugin-eslint": "4.4.4",
"vue-template-compiler": "2.6.10"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"bugs": {
"url": "https://github.com/PanJiaChen/vue-element-admin/issues"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"keywords": [
"vue",
"admin",
"dashboard",
"element-ui",
"management-system"
],
"license": "MIT",
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/PanJiaChen/vue-element-admin.git"
}
}

  • name & version : 项目名称和版本。发布 npm 包时这是必填的唯一标识。
  • main : 程序主入口,Node 默认的执行文件。
  • scripts : 这是整个配置中最重要的部分。它定义了 快捷命令(命令脚本)
    • "start": "node app.js" -> 执行 npm start 命令,实际会运行 node app.js 启动程序。
    • "dev": "vue-cli-service serve" -> 执行 npm run dev 命令,会使用 vue-cli-service serve 启动服务
  • dependencies : 行环境必需的包(如 express, mongoose 等)。安装命令:npm install <pkg>
  • devDependencies : 仅开发环境需要的包(如 eslint, jest, nodemon )。安装命令:npm i <pkg> -D
  • engines : 指定 Node.js 或 npm 的版本范围,防止因环境版本不同导致代码崩溃。

初始化项目 ,会生成 package.json

npm init

安装依赖

npm install

启动

node app.js

PM2

如果直接使用 node app.js 这种方式启动,会存在以下问题:

  • 1️⃣ 程序崩溃自动退出,不会自动启动
  • 2️⃣ 服务器重启后,程序不会自动启动
  • 3️⃣ 无法负载均衡
  • 4️⃣ 日志不好管理

PM2 就是为解决这些问题而生,它是 Node.js 最流行的进程管理工具 。主要负责以下功能:

功能 说明
进程守护 程序崩溃自动重启
负载均衡 Node.js 是单线程的,pm2 可以让其充分利用多核 CPU 实现多进程
日志管理 自动手机程序日志
开机自启 服务器启动自动运行
性能监控 CPU/内存
后台运行 让程序以守护进程(Daemon)方式在后台运行

PM2 启动程序,假设程序是 app.js

pm2 start app.js --name "app-name"

查看进程状态

pm2 list

pm2 常用命令

  • 程序生命周期管理

    pm2 start app.js

    pm2 restart app-name|all|id

    pm2 stop app-name|all|id

    pm2 delete app

    pm2 reload <name> --update-env # PM2 会缓存环境变量。如果你修改了配置文件中的 env 变量,直接 pm2 restart 有时是不生效的。

    pm2 reload all # 平滑重启, 相比 restart,reload 会逐个重启进程,实现 0 秒停机

    pm2 env <name> # 查看程序加载的 env 配置,这在定位问题时很有用,通过此命令可以直观的看到程序加载的环境变量
  • 查看进程

    pm2 list
  • 日志管理

    pm2 logs     # 默认日志位置 ~/.pm2/logs
    pm2 logs app-name

    pm2 flush # 清空日志

    pm2 set pm2-logrotate:max_size 10M # PM2 日志会越来越大。建议配置 日志轮转
  • 监控程序,查看 CPU/Memory 实时监控界面

    pm2 monit
  • 配置 pm2 开机自启动

    pm2 save      # 保存当前进程列表
    pm2 startup # 生成启动脚本命令,复制终端弹出的那行代码并执行。
    pm2 resurrect # 查看开机启动进程

PM2 集群模式

Node.js 是单线程 。PM2 可以启动 多进程利用多核 CPU

  • ✔ 提高性能

  • ✔ 提高并发

  • ✔ 程序挂掉自动拉起

  • 集群模式启动命令

    pm2 start app.js -i max
    • -i max : 根据 CPU 核心数启动,如 4 vCPU 则启动 4 个 node 进程。

PM2 配置文件

生产环境一般使用 ecosystem.config.js 作为 pm2 管理配置文件来管理所有的项目,让你的部署过程 版本化、自动化、可复用

ecosystem.config.js
module.exports = {
apps : [{
// --- 基础配置 ---
name: "myapp", // 应用名称,用于 pm2 list 展示
cwd: "./", // 应用运行的根目录
script: "app.js", // 启动脚本路径
args: "-- port 3000", // 传递给运行脚本的参数

// --- 进阶控制 ---
instances: "max", // 开启集群模式,利用多 CPU。数字或 "max"(根据 CPU 核心数启动)
exec_mode: "cluster", // 模式:'cluster'(集群)或 'fork'(单实例),默认为 'fork'
watch: false, // 监控目录,文件变动则自动重启,'false' 或者监控目标 '["node_modules", "logs"]'
ignore_watch: [ // 忽略监听目录,防止频繁重启。
"node_modules",
"logs"
],

// --- 日志管理 ---
error_file: "./logs/err.log", // 错误日志路径
out_file: "./logs/out.log", // 普通输出日志路径
log_date_format: "YYYY-MM-DD HH:mm:ss", // 给日志加上时间戳

// --- 环境变量控制 ---
env: { // 默认环境变量 (pm2 start)
NODE_ENV: "development"
},
env_production : { // 生产环境变量 (pm2 start --env production)
NODE_ENV: "production"
},

// --- 重启策略 ---
autorestart: true, // 程序崩溃是否自动重启
min_uptime: 60, // 程序运行多久才算启动成功
max_restarts: 10, // 最大重启次数,防止程序有问题还一直不停重启
max_memory_restart: "1G", // 内存占用超过 1G 则自动重启,防止内存泄漏
restart_delay: 3000 // 异常重启之间的延迟(毫秒)

}]
}
  • 启动命令
    pm2 start ecosystem.config.js

    pm2 start ecosystem.config.js --env production # 启动生产环境 env

使用 PM2 配置文件时,需要注意以下事项:

  • 集群模式(Cluster)与单实例(Fork)

    如果你的应用是单机版的,没做多进程适配(例如:在内存里存 Session、使用本地变量计数),开启 instances: max 会导致数据不一致。

    生产环境尽量使用 cluster 模式以压榨多核性能,但确保你的应用是 无状态(Stateless) 的。

  • 内存溢出防御

    • Node.js 程序如果不小心写了内存泄漏,长期运行会导致服务器宕机。
    • 务必配置 max_memory_restart 。比如你的服务器有 2G 内存,给每个实例设个 800M 左右的阈值,能有效防止全机卡死。
  • 避免 无限重启循环

    • 如果你的程序在启动阶段就报错,PM2 会疯狂尝试重启。
    • 设置 min_uptime (程序运行多久才算启动成功)和 max_restarts (最大重试次数),避免刷爆 CPU 和日志文件。
  • Watch 模式的风险

    • 不要在生产环境开启 watch: true
    • 生产环境下任何小的配置改动或日志写入(如果路径不对)都可能触发进程重启,导致服务抖动。 watch 仅建议在开发或测试环境使用。
  • 环境变量的优先级

    • PM2 会缓存环境变量。如果你修改了配置文件中的 env 变量,直接 pm2 restart 有时是不生效的。
    • 建议使用 pm2 reload <name> --update-env 或直接 pm2 delete 后再重新 start

使用 Docker Compose 部署 3 个节点的 Elasticsearch 集群并开启安全认证

环境信息

  • Rocky9 Linux
  • Elastic Stack 8.12

三台 Rocky9 Linux 服务器, 配置为 4CPU 16G RAM , 内网地址和主机名分别为:

  • 172.31.29.164 vp-elk-1
  • 172.31.24.61 vp-elk-2
  • 172.31.25.106 vp-elk-3

修改系统参数 /etc/sysctl.conf ,Elasticsearch 必须配置:

/etc/sysctl.conf
vm.max_map_count = 262144

执行命令 sysctl -p 生效,使用命令 sysctl vm.max_map_count 验证

创建 ELK 目录

mkdir -p /data/elk
cd /data/elk
mkdir -p data/{elasticsearch,kibana} config/certs

目录结构如下:

/data/elk
├── config
│ └── certs
├── data
│ ├── elasticsearch
│ └── kibana
└── docker-compose.yml

因为要开启集群安全认证(X-Pack Security) xpack.security.enabled: true ,就必须为节点之间的通信配置 Transport 层 TLS 加密 ,否则 ES 会拒绝在生产模式下启动。

证书可以选用以下两种方式之一

  1. 生成集群证书(仅在一个节点上操作即可) 。利用 ES 自带的工具生成 CA 和节点证书,每个节点有自己的证书。

    cd /data/elk

    # 首先生成 CA 证书
    docker run --rm \
    -v $(pwd)/config/certs:/certs \
    docker.elastic.co/elasticsearch/elasticsearch:8.12.2 \
    bin/elasticsearch-certutil ca \
    --silent \
    --pem \
    --out /certs/ca.zip

    # 解压 CA 证书,获得 ca.crt ca.key
    cd config/certs
    unzip ca.zip

    创建 实例配置文件,比如 config/certs/instances.yml 用来为节点生成证书

    config/certs/instances.yml
    instances:
    - name: vp-elk-1
    ip:
    - 172.31.29.164

    - name: vp-elk-2
    ip:
    - 172.31.24.61

    - name: vp-elk-3
    ip:
    - 172.31.25.106

    使用实例配置文件,比如 config/certs/instances.yml 生成节点证书

    docker run --rm \
    -v $(pwd)/config/certs:/certs \
    docker.elastic.co/elasticsearch/elasticsearch:8.12.2 \
    bin/elasticsearch-certutil cert \
    --silent \
    --pem \
    --in /certs/instances.yml \
    --ca-cert /certs/ca/ca.crt \
    --ca-key /certs/ca/ca.key \
    --out /certs/certs.zip

    解压证书文件,获得节点证书,文件结构如下:

    # tree
    .
    ├── ca
    │ ├── ca.crt
    │ └── ca.key
    ├── ca.zip
    ├── certs.zip
    ├── instances.yml
    ├── vp-elk-1
    │ ├── vp-elk-1.crt
    │ └── vp-elk-1.key
    ├── vp-elk-2
    │ ├── vp-elk-2.crt
    │ └── vp-elk-2.key
    └── vp-elk-3
    ├── vp-elk-3.crt
    └── vp-elk-3.key

    4 directories, 11 files

  2. 生成 p12 类型的节点证书

    生成 config/instances.yml

    config/instances.yml
    instances:
    - name: vp-elk-1
    dns:
    - vp-elk-1
    - localhost
    ip:
    - 172.31.29.164
    - 127.0.0.1

    - name: vp-elk-2
    dns:
    - vp-elk-2
    - localhost
    ip:
    - 172.31.24.61
    - 127.0.0.1

    - name: vp-elk-3
    dns:
    - vp-elk-3
    - localhost
    ip:
    - 172.31.25.106
    - 127.0.0.1

    生成 CA

    cd /data/elk

    docker run --rm -v ./config/certs:/certs docker.elastic.co/elasticsearch/elasticsearch:8.12.2 \
    bin/elasticsearch-certutil ca --out /certs/elastic-ca.p12 --pass ""

    会生成无密码的 config/certs/elastic-ca.p12 CA 证书文件,接着使用 CA 根证书生成节点证书,使用 config/instances.yml 配置证书中包含的 SAN

    docker run --rm -v ./config/certs:/certs docker.elastic.co/elasticsearch/elasticsearch:8.12.2 \
    bin/elasticsearch-certutil cert --ca /certs/elastic-ca.p12 --ca-pass "" \
    --in /certs/instances.yml \
    --out /certs/elastic-certificates.p12 --pass ""

    会生成 ./config/certs/elastic-certificates.p12 ,这实际上是个 zip 压缩文件,要解压后获得各个节点的证书

    可以通过以下方式检查证书中的 SAN 信息

    # # cd config/certs/

    # file elastic-certificates.p12
    elastic-certificates.p12: Zip archive data, at least v2.0 to extract

    # unzip elastic-certificates.p12
    Archive: elastic-certificates.p12
    creating: vp-elk-1/
    inflating: vp-elk-1/vp-elk-1.p12
    creating: vp-elk-2/
    inflating: vp-elk-2/vp-elk-2.p12
    creating: vp-elk-3/
    inflating: vp-elk-3/vp-elk-3.p12


    # openssl pkcs12 -in vp-elk-1/vp-elk-1.p12 -nodes -passin pass: \
    | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
    X509v3 Subject Alternative Name:
    IP Address:172.31.29.164, IP Address:127.0.0.1, DNS:localhost, DNS:vp-elk-1

生成证书后,将证书文件分发到其他两个节点上。

Elasticsearch 集群配置文件 ./config/elasticsearch.yml每个节点都要配置 ,修改 node.name 为对应的节点名称; 修改证书路径为对应节点的证书

./config/elasticsearch.yml
cluster.name: vp-elk-cluster

node.name: vp-elk-1 # 其他节点修改为对应值

network.host: 172.31.29.164 # 其他节点修改为对应值
http.port: 9200
transport.port: 9300

discovery.seed_hosts:
- 172.31.29.164
- 172.31.24.61
- 172.31.25.106

cluster.initial_master_nodes:
- vp-elk-1
- vp-elk-2
- vp-elk-3


xpack.security.enabled: true
xpack.security.enrollment.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: /usr/share/elasticsearch/config/certs/vp-elk-1/vp-elk-1.p12 # 其他节点修改为对应值
xpack.security.transport.ssl.truststore.path: /usr/share/elasticsearch/config/certs/vp-elk-1/vp-elk-1.p12 # 其他节点修改为对应值

xpack.security.http.ssl.enabled: false

bootstrap.memory_lock: true

Kibana 配置文件 config/kibana.yml ,只需要在一个节点上配置即可。

config/kibana.yml
server.name: kibana
server.host: "0.0.0.0"

server.ssl.enabled: false # Kibana 使用 HTTP 和 ES 通行,ES 已经配置 xpack.security.http.ssl.enabled: false

elasticsearch.hosts:
- http://172.31.29.164:9200
- http://172.31.24.61:9200
- http://172.31.25.106:9200

elasticsearch.username: "kibana_system" # 这里不允许使用 elastic 用户
elasticsearch.password: "YourStrongPassword"

monitoring.ui.container.elasticsearch.enabled: true

Docker Compose 配置文件 docker-compose.yml ,3 台 ES 节点使用同样的配置即可, ES 配置在配置文件在每个节点的 config/elasticsearch.yml 。Kibana 只需要在一台服务器部署即可。

docker-compose.yml
services:

elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.2
container_name: elasticsearch
network_mode: host
environment:
- ES_JAVA_OPTS=-Xms4g -Xmx4g

volumes:
- ./data/elasticsearch:/usr/share/elasticsearch/data
- ./config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./config/certs:/usr/share/elasticsearch/config/certs:ro

ulimits:
memlock:
soft: -1
hard: -1
restart: always
mem_limit: 8g



kibana:
image: docker.elastic.co/kibana/kibana:8.12.2
container_name: kibana
restart: always
network_mode: host
depends_on:
- elasticsearch

volumes:
- ./config/kibana.yml:/usr/share/kibana/config/kibana.yml
- ./config/certs:/usr/share/kibana/config/certs:ro
- ./data/kibana:/usr/share/kibana/data

启动

docker compose up -d

elastic 等用户重置密码

# docker compose exec -it elasticsearch bin/elasticsearch-setup-passwords interactive
******************************************************************************
Note: The 'elasticsearch-setup-passwords' tool has been deprecated. This command will be removed in a future release.
******************************************************************************

Initiating the setup of passwords for reserved users elastic,apm_system,kibana,kibana_system,logstash_system,beats_system,remote_monitoring_user.
You will be prompted to enter passwords as the process progresses.
Please confirm that you would like to continue [y/N]y


Enter password for [elastic]:
Reenter password for [elastic]:
Enter password for [apm_system]:
Enter password for [apm_system]:
Reenter password for [apm_system]:
Enter password for [kibana_system]:
Reenter password for [kibana_system]:
...
Changed password for user [beats_system]
Changed password for user [remote_monitoring_user]
Changed password for user [elastic]


重置 elastic 用户密码

# docker compose exec elasticsearch bin/elasticsearch-reset-password -u elastic
This tool will reset the password of the [elastic] user to an autogenerated value.
The password will be printed in the console.
Please confirm that you would like to continue [y/N]y


Password for the [elastic] user successfully reset.
New value: GjJadE-ihZJ+Ddb5SvKs

Elasticsearch 集群常规检查

  1. 首先确认节点是否全部加入集群。

    # GET /
    {
    "name": "vp-elk-1",
    "cluster_name": "vp-elk-cluster",
    "cluster_uuid": "Wvi6Vl5mTsKGlDUTp12xhQ",
    "version": {
    "number": "8.12.2",
    "build_flavor": "default",
    "build_type": "docker",
    "build_hash": "48a287ab9497e852de30327444b0809e55d46466",
    "build_date": "2024-02-19T10:04:32.774273190Z",
    "build_snapshot": false,
    "lucene_version": "9.9.2",
    "minimum_wire_compatibility_version": "7.17.0",
    "minimum_index_compatibility_version": "7.0.0"
    },
    "tagline": "You Know, for Search"
    }
  2. 检查集群健康状态

    # GET /_cluster/health?pretty
    {
    "cluster_name": "vp-elk-cluster",
    "status": "green",
    "timed_out": false,
    "number_of_nodes": 3,
    "number_of_data_nodes": 3,
    "active_primary_shards": 29,
    "active_shards": 59,
    "relocating_shards": 0,
    "initializing_shards": 0,
    "unassigned_shards": 0,
    "delayed_unassigned_shards": 0,
    "number_of_pending_tasks": 0,
    "number_of_in_flight_fetch": 0,
    "task_max_waiting_in_queue_millis": 0,
    "active_shards_percent_as_number": 100
    }
  3. 检查节点状态

    # GET /_cat/nodes?v
    ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
    172.31.25.106 8 59 0 0.00 0.00 0.00 cdfhilmrstw - vp-elk-3
    172.31.24.61 12 60 0 0.03 0.02 0.00 cdfhilmrstw * vp-elk-2
    172.31.29.164 25 60 1 0.02 0.02 0.01 cdfhilmrstw - vp-elk-1
  4. 检查节点角色

    # GET /_cat/nodes?v&h=name,ip,node.role,master
    name ip node.role master
    vp-elk-3 172.31.25.106 cdfhilmrstw -
    vp-elk-2 172.31.24.61 cdfhilmrstw *
    vp-elk-1 172.31.29.164 cdfhilmrstw -

    • m master
    • d data
    • i **ingest
  5. 检查分片分布

    # GET /_cat/shards?v
    index shard prirep state docs store dataset ip node
    .kibana_analytics_8.12.2_001 0 p STARTED 5 2.3mb 2.3mb 172.31.24.61 vp-elk-2
    .kibana_analytics_8.12.2_001 0 r STARTED 5 2.3mb 2.3mb 172.31.29.164 vp-elk-1
    .internal.alerts-observability.apm.alerts-default-000001 0 p STARTED 0 249b 249b 172.31.24.61 vp-elk-2
    .internal.alerts-observability.apm.alerts-default-000001 0 r STARTED 0 249b 249b 172.31.29.164 vp-elk-1
    .ds-.kibana-event-log-ds-2026.03.13-000001 0 p STARTED 1 6.3kb 6.3kb 172.31.25.106 vp-elk-3
  6. 检查索引状态

    # GET /_cat/indices?v
    health status index uuid pri rep docs.count docs.deleted store.size pri.store.size dataset.size
    green open .internal.alerts-observability.logs.alerts-default-000001 QQ1ALFIwTS6Cr1IUjp384w 1 1 0 0 498b 249b 249b
    green open .internal.alerts-observability.threshold.alerts-default-000001 UzpYLZbzTMyK2yCYnmayKw 1 1 0 0 498b 249b 249b
    green open .kibana-observability-ai-assistant-kb-000001 yV9I-sIMQgyf0edNxw1kPA 1 1 0 0 498b 249b 249b
    green open .internal.alerts-observability.apm.alerts-default-000001 0HOJ4bCgT_2X4c29FryFCw 1 1 0 0 498b 249b 249b
    green open .internal.alerts-stack.alerts-default-000001 NJCmcGptQOWG6rd0I259Uw 1 1 0 0 498b 249b 249b
    green open .internal.alerts-observability.slo.alerts-default-000001 rSSFxAYfR0O9L9XXBIpZlA 1 1 0 0 498b 249b 249b
    green open .internal.alerts-ml.anomaly-detection.alerts-default-000001 fT9FJoirRiSMVXx1V3F7dQ 1 1 0 0 498b 249b 249b
    green open .internal.alerts-observability.metrics.alerts-default-000001 E9vjU7WETSebjg8Y_ddPHw 1 1 0 0 498b 249b 249b
  7. 检查 Master 选举

    # GET /_cat/master?v
    id host ip node
    5hG_mSEjRd6Ov-rClowAoQ 172.31.24.61 172.31.24.61 vp-elk-2

    只有一个 Master 就正常

  8. 检查 JVM Heap

    # GET /_cat/nodes?v&h=name,heap.percent
    name heap.percent
    vp-elk-3 20
    vp-elk-2 48
    vp-elk-1 57
  9. 检查磁盘使用情况

    # GET /_cat/allocation?v
    shards disk.indices disk.used disk.avail disk.total disk.percent host ip node node.role
    20 2.8mb 14.1gb 1009.7gb 1023.9gb 1 172.31.29.164 172.31.29.164 vp-elk-1 cdfhilmrstw
    20 754.3kb 11gb 1012.8gb 1023.9gb 1 172.31.25.106 172.31.25.106 vp-elk-3 cdfhilmrstw
    19 2.9mb 11gb 1012.8gb 1023.9gb 1 172.31.24.61 172.31.24.61 vp-elk-2 cdfhilmrstw

  10. 检查线程池情况

    # GET /_cat/thread_pool?v
    node_name name active queue rejected
    vp-elk-3 analyze 0 0 0
    vp-elk-3 auto_complete 0 0 0
    vp-elk-3 azure_event_loop 0 0 0
    vp-elk-3 ccr 0 0 0
    vp-elk-3 cluster_coordination 0 0 0
    ...

    关注 queue、rejected > 0 ,说明集群过载

  11. 检查 Pending Tasks

    # GET /_cluster/pending_tasks?pretty
    {
    "tasks": []
    }
  12. 检查证书

    # GET /_ssl/certificates
    [
    {
    "path": "/usr/share/elasticsearch/config/certs/vp-elk-1/vp-elk-1.p12",
    "format": "PKCS12",
    "alias": "ca",
    "subject_dn": "CN=Elastic Certificate Tool Autogenerated CA",
    "serial_number": "825a4f350b8815940e60d557036edbe205f68a93",
    "has_private_key": false,
    "expiry": "2029-03-11T13:21:08.000Z",
    "issuer": "CN=Elastic Certificate Tool Autogenerated CA"
    },
    {
    "path": "/usr/share/elasticsearch/config/certs/vp-elk-1/vp-elk-1.p12",
    "format": "PKCS12",
    "alias": "vp-elk-1",
    "subject_dn": "CN=vp-elk-1",
    "serial_number": "220b64fa6c86b2145a86c274eb914f9e3b299350",
    "has_private_key": true,
    "expiry": "2029-03-12T01:29:26.000Z",
    "issuer": "CN=Elastic Certificate Tool Autogenerated CA"
    },
    {
    "path": "/usr/share/elasticsearch/config/certs/vp-elk-1/vp-elk-1.p12",
    "format": "PKCS12",
    "alias": "vp-elk-1",
    "subject_dn": "CN=Elastic Certificate Tool Autogenerated CA",
    "serial_number": "825a4f350b8815940e60d557036edbe205f68a93",
    "has_private_key": false,
    "expiry": "2029-03-11T13:21:08.000Z",
    "issuer": "CN=Elastic Certificate Tool Autogenerated CA"
    }
    ]
  13. 查看集群的全局配置

    # GET /_cluster/settings?include_defaults

    这会输出 Elasticsearch 集群的所有配置,包括默认配置。

  14. 查看模版配置

    # GET /_index_template?pretty

    其中可以查看 number_of_shardsnumber_of_replicas ,默认值都是 1

常见错误

Elasticsearch exited unexpectedly, with exit code 137

在 Docker 中 几乎 90% 是被系统 OOM Killer 杀掉(内存不够)。重点检查 mem_limit: 8g , 和 ES_JAVA_OPTS=-Xms8g -Xmx8g

filebeat 上传数据到 elasticsearch 问题汇总

filebeat 上传数据到 elasticsearch 报错

适用版本信息说明

  • filebeat 7
  • elasticsearch 7

filebeat 7.5.2 上传数据到 Elasticsearch 报错:

# journalctl -f -u filebeat
{"type":"illegal_argument_exception","reason":"Validation Failed: 1: this action would add [2] total shards, but this cluster currently has [6924]/[3000] maximum shards open;"}

此错误原因是由于 Elasticsearch 的集群中打开的分片数量超过了集群的最大分片限制。在 Elasticsearch 中,每个索引由多个分片组成,而集群有一个设置的最大分片数限制。这个限制是为了防止分片数过多导致性能问题。

错误消息 {"type":"illegal_argument_exception","reason":"Validation Failed: 1: this action would add [2] total shards, but this cluster currently has [6924]/[3000] maximum shards open;"} 显示当前集群已有 6924 个分片,超过了 3000 个的限制。

要解决这个问题,可以考虑以下几个选项:

  1. 调整 Elasticsearch 集群设置,增加最大分片数限制

    可以通过更改 Elasticsearch 配置来增加最大分片数的限制。但请注意,这可能会导致性能问题,尤其是如果硬件资源有限的话。

    这可以通过修改 cluster.max_shards_per_node 设置来实现

    PUT /_cluster/settings
    {
    "persistent": {
    "cluster.max_shards_per_node": "新的分片数限制"
    }
    }

    获取 Elasticsearch 集群的最大分片数限制

    curl -X GET "http://[your_elasticsearch_host]:9200/_cluster/settings?include_defaults=true&pretty"

  2. 删除一些不必要的索引 :如果有些索引不再需要,可以删除它们来减少分片数。

    curl -X DELETE "localhost:9200/my_index"
    curl -X DELETE "localhost:9200/logstash-2021.11.*"
  3. 合并一些小索引:如果有很多小的索引,可以考虑将它们合并为更大的索引,以减少总分片数。

  4. 优化现有索引的分片策略:可以优化索引的分片数量,例如,通过减少每个索引的主分片数量。

filebeat 错误

filebeat 配置上传数据到 elasticsearch 报错

适用版本信息说明

  • filebeat 7
  • elasticsearch 7

使用以下 filebeat 配置文件

/etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
paths:
- /home/logs/laravel-2023*
tags: ["admin-log"]
close_timeout: 3h
clean_inactive: 72h
ignore_older: 70h
close_inactive: 5m

output.elasticsearch:
hosts: ["1.56.219.122:9200", "1.57.115.214:9200", "1.52.53.31:9200"]
username: "elastic"
password: "passwd"
index: "logstash-admin-%{+yyyy.MM.dd}"
setup.template.enabled: true
setup.template.name: "logstash-admin"
setup.template.pattern: "logstash-admin-*"

filebeat 启动后报错,elasticsearch 上未创建相应的索引,关键错误信息 Failed to connect to backoff(elasticsearch(http://1.57.115.214:9200)): Connection marked as failed because the onConnect callback failed: resource 'filebeat-7.5.2' exists, but it is not an alias

journalctl -f -u filebeat
INFO [index-management] idxmgmt/std.go:269 ILM policy successfully loaded.
ERROR pipeline/output.go:100 Failed to connect to backoff(elasticsearch(http://1.57.115.214:9200)): Connection marked as failed because the onConnect callback failed: resource 'filebeat-7.5.2' exists, but it is not an alias
INFO pipeline/output.go:93 Attempting to reconnect to backoff(elasticsearch(http://1.57.115.214:9200)) with 3 reconnect attempt(s)
INFO elasticsearch/client.go:753 Attempting to connect to Elasticsearch version 7.6.2
INFO [index-management] idxmgmt/std.go:256 Auto ILM enable success.
INFO [index-management.ilm] ilm/std.go:138 do not generate ilm policy: exists=true, overwrite=false
INFO [index-management] idxmgmt/std.go:269 ILM policy successfully loaded.
ERROR pipeline/output.go:100 Failed to connect to backoff(elasticsearch(http://1.56.219.122:9200)): Connection marked as failed because the onConnect callback failed: resource 'filebeat-7.5.2' exists, but it is not an alias
INFO pipeline/output.go:93 Attempting to reconnect to backoff(elasticsearch(http://1.56.219.122:9200)) with 3 reconnect attempt(s)

这表明 Filebeat 无法正常连接到 Elasticsearch 集群。出现这个问题的主要原因可能为:

  • 索引/别名冲突: Filebeat 试图创建或使用一个名为 filebeat-7.5.2 的索引或别名,但这个资源在 Elasticsearch 中已存在且不是一个别名。解决方法为 删除或重命名冲突索引

  • ILM 配置问题

    使用此配置文件,解决 索引/别名冲突 问题后,filebeat 运行正常,但是 Elasticsearch 上未创建配置中的索引 logstash-admin-*,而是将数据上传到了索引 filebeat-7.5.2-*。这个问题是由 ILM 导致,可以禁用 ILM。参考以下配置,禁用 ILM (setup.ilm.enabled: false)

    /etc/filebeat/filebeat.yml
    filebeat.inputs:
    - type: log
    paths:
    - /home/logs/laravel-2023*
    tags: ["admin-log"]
    close_timeout: 3h
    clean_inactive: 72h
    ignore_older: 70h
    close_inactive: 5m

    output.elasticsearch:
    hosts: ["1.56.219.122:9200", "1.57.115.214:9200", "1.52.53.31:9200"]
    username: "elastic"
    password: "passwd"
    index: "logstash-admin-%{+yyyy.MM.dd}"
    setup.ilm.enabled: false
    setup.template.enabled: true
    setup.template.name: "logstash-admin"
    setup.template.pattern: "logstash-admin-*"