L B T

记 录 过 去 的 经 验

环境信息

  • 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
阅读全文 »

环境信息

  • Docker 26.1.1
  • Confluence Image: atlassian/confluence:8.5.15
  • Jira Image: atlassian/jira-software:9.12.11
  • postgresql 13

配置流程

创建项目所需新目录,用于存储持久化数据

# mkdir -p /opt/devops/{confluence,jira,postgresql}

下载 atlassian-agent.jar 文件,将其分别放置在 ./confluence/atlassian-agent.jar./jira/atlassian-agent.jar ,用于之后为 Confluence 和 Jira 生成 License

docker-compose.yaml 文件内容如下

docker-compose.yaml
services:
# --- 数据库服务 ---
postgres-db:
image: postgres:13
container_name: devops-postgres-db
volumes:
- ./postgresql:/var/lib/postgresql/data
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=<PG_PASSWORD>
- POSTGRES_DB=confluence
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
networks:
- devops-net


# --- Confluence ---
confluence:
image: atlassian/confluence:8.5.15
container_name: confluence
depends_on:
postgres-db:
condition: service_healthy
ports:
- "8090:8090"
- "8091:8091" # 同步端口(可选)
environment:
- ATL_JDBC_USER=postgres
- ATL_JDBC_PASSWORD=74ni4ikEHVot8IJf
- ATL_JDBC_URL=jdbc:postgresql://postgres-db:5432/confluence
- ATL_DB_TYPE=postgresql
- JVM_MINIMUM_MEMORY=2048m
- JVM_MAXIMUM_MEMORY=4096m
- TZ='Asia/Shanghai'
- JVM_SUPPORT_RECOMMENDED_ARGS=-javaagent:/var/atlassian/application-data/confluence/atlassian-agent.jar
volumes:
- ./confluence:/var/atlassian/application-data/confluence
networks:
- devops-net

# --- Jira ---
jira:
image: atlassian/jira-software:9.12.11
container_name: jira
depends_on:
postgres-db:
condition: service_healthy
ports:
- "8080:8080" # Jira 默认端口
environment:
- ATL_JDBC_USER=postgres
- ATL_JDBC_PASSWORD=<PG_PASSWORD>
- ATL_JDBC_URL=jdbc:postgresql://postgres-db:5432/jira # 注意这里是指向新创建的 jira 库
- ATL_DB_TYPE=postgresql
- JVM_MINIMUM_MEMORY=2048m
- JVM_MAXIMUM_MEMORY=4096m
- TZ='Asia/Shanghai'
- JVM_SUPPORT_RECOMMENDED_ARGS=-javaagent:/var/atlassian/application-data/jira/atlassian-agent.jar
volumes:
- ./jira:/var/atlassian/application-data/jira
networks:
- devops-net

networks:
devops-net:
driver: bridge

启动容器

# docker compose up -d

postgres 数据库启动正常后,执行以下命令为 Jira 创建数据库,Confluece 数据库在容器启动是会自动创建。

docker compose exec -it postgres-db psql -U postgres -c "CREATE DATABASE jira WITH ENCODING='UTF8' LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8' CONNECTION LIMIT=-1;"

Confluence 初始化部署

浏览器访问 http://<IP>:8090 在 Confluence 初始化页面拿到 Server ID

登录 Confluence 容器,执行以下命令获取破解得 License

# docker compose exec -it confluence bash
root@81d2f5a8f7e5:/var/atlassian/application-data/confluence# java -jar atlassian-agent.jar -d -p conf -m Hello@world.com -n Hello@world.com -o your-org -s B66H-ET1W-2LYR-Q7R2

====================================================
======= Atlassian Crack Agent v1.3.1 =======
======= https://zhile.io =======
======= QQ Group: 30347511 =======
====================================================

Your license code(Don't copy this line!!!):

AAABmA0ODAoPeJxtUVuPojAUfu+vINlnFKoLaNJkHWBWZwEZxbk8FjxII7akFBz21y8ik00mk/Sl5
7Tf9UdSNNoT5RrGmuEsZ3g5m2u/w0TDBraQK4EqJrhHFZDbRDdMHWPkt7Rshg3JaVkD8qDOJKuGy
YGX7MIUHLWSZcBr0NJOK5Sq6uV0+rdgJUyYQFt5opzVd5BONFIX8oQywfMJzRRrgSjZAHIFV/3dD
ykryRrKUvy6ClkeJ5m4oBF/TeuChO7VfXxczdPjayzCPG9e/NxN8dP845x4cbxeFc/VWwPTFrfRG
zarNGni0+HP+WotwmhFyJ16r6hUIEdXwyi4kyRdBRG9AHG3Yejv3M0qQL0oroBTnoH/UTHZjTk5C
92w+4PGvxuPBBtv70d6YNrWwrAN++fcdhy0B9mC7NcPlrXW/cR81XHwvtOf7R2+s/eI1AV+0zTkc
YbuBWR9C820jB7Jmc3MT57vRcSNzApaw9cSx/Q+4TDaN+n/Fge2QULUXFKQ2/xQ9y+JbqLeCPnGz
FjVENKXpv4BHKbJjjAtAhRlC24D9XOs3Z9LZwzE3PehVo5lhgIVAIaOykfNKCdhuAZn7PZsJR+qr
edbX02jn

命令参数说明:

-d 调试模式 必选,用于输出生成的许可证信息。

-m 电子邮箱 随便填 ,如 devops@example.com

-n 用户名 随便填 ,如 Organization_Admin。

-p 产品标识 不可乱填 。Confluence 是 conf ,Jira 是 jira ,Bitbucket 是 bitbucket

-o 组织/URL 建议填你的访问地址,如 https://wiki.mysite.com

-s Server ID 核心参数。必须填 Web 页面上显示的那个 16 位代码

License 验证成功后,填写数据库连接信息并测试连接成功

Jira 初始化部署

浏览器访问 http://<IP>:8080 打开 Jira 初始化页面,在 Jira setup 中选择 I'll set it up myself 。因为后面要指定数据库信息,因此要选择自定义。

根据提示填入数据库连接信息,测试无误后,到 Specify your license key 页面,复制 Server ID

登入 jira 容器,使用以下命令为 Jira 生成 License

root@69487899ebea:/var/atlassian/application-data/jira# java -jar atlassian-agent.jar -d -p jira -m Hello@world.com -n Hello@world.com  -o your-org -s BYTU-X57R-U6U3-LSIB

====================================================
======= Atlassian Crack Agent v1.3.1 =======
======= https://zhile.io =======
======= QQ Group: 30347511 =======
====================================================

Your license code(Don't copy this line!!!):

AAAB5Q0ODAoPeJyNU9GSmjAUfecrmOkzboKuqDOZ6YpYmQW0onb7GPEK2SKhSdDFry8K2+6q43QmL
8ncc+455958+QEbfQxrHWMddQcmGpgd/Zu/0E1kdrVYAGQJz3MQLY9FkElYlDkEdAfEnvq+M7fdJ
0+zBVDFeDaiCsgJaCDTwFi7AxmBjATLTyiyzFK2Y6oSktYAfV3qiVK5HDw8HBOWQotxzacsU5DRL
ALnLWeibLr1+gayqqO9MkHfVTobVlMHnuu7C2ekBcVuDWK6XUoQkhh/xd3hygXfFJFqnS6G5Ft1o
AJaV0R3ammk2B6IEgV8yvLj+x14pYraULkWdWkTz6pqfDJnamGx/hfjucTZ07Q4D4NsaSob+kuiq
YhpxmRdV/JCGFzEms0zVSlzqqRTMoE05V8PXKSbVsR3Nc+V90bRhMqE+PbBHk/i+W8c8mdIO8fiu
UBHvJj23KHnhqun5DvDc0f1VsfZMB6/vqDH/jHocJaUMxQTUrf4z2hCRcXJTm2ymaU7Ip47Cp3A8
LBlIauPeman38GfVuPWNoYg9iAq+PDnYmm8PFpzY9ldtg0vdIfaLyjfE8ddhCzUa7fxra9xvXSzQ
kQJlXD5MT6Cz2PJBZON6Uo+uWGhGc5Z+cVs/gCvg0phMC0CFBCTfaevRY4wQYedPfgvyTwghlJmA
hUAlodnLvDqtFh/z+wXjAncJwqlqNc=X02n3


注意其中的参数 -p jira

根据提示完成其他配置即可开始使用 Jira。

Confluence 迁移

部署好相同版本的 Confluence 和 PostgreSQL 环境,

在开始之前,请确保:

  • 停止旧环境和新环境的 Confluence 容器,以保证数据一致性。
  • 确认新环境的 PostgreSQL 13 已经启动,并创建了一个空的数据库(例如名为 confluence )。
  1. 迁移 PostgreSQL 数据库

    由于版本一致,直接使用 pg_dump 是最稳妥的方案。

    docker exec -t <旧数据库容器名> pg_dump -U <用户名> <数据库名> > confluence_db.sql

    先删除新环境中的数据库 confluence,再新建

    # psql -h <HOST> -U postgres -d postgres

    postgres=> \l
    List of databases
    Name | Owner | Encoding | Collate | Ctype | Access privileges
    ------------+----------+----------+-------------+-------------+-----------------------
    confluence | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
    jira | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
    postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
    rdsadmin | rdsadmin | UTF8 | en_US.UTF-8 | en_US.UTF-8 | rdsadmin=CTc/rdsadmin
    template0 | rdsadmin | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/rdsadmin +
    | | | | | rdsadmin=CTc/rdsadmin
    template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
    | | | | | postgres=CTc/postgres
    (6 rows)

    postgres=> DROP DATABASE confluence;
    DROP DATABASE
    postgres=>

    postgres=> CREATE DATABASE confluence;
    CREATE DATABASE

    然后导入备份数据,将 confluence_db.sql 拷贝到新服务器,然后执行:

    cat confluence_db.sql | docker exec -i <新数据库容器名> psql -U <用户名> -d <数据库名>
  2. 迁移 Confluence Home 目录

    Confluence 的用户附件、索引、插件配置等都存储在 Home 目录下。进入旧服务器的映射目录,执行

    tar -zcvf confluence_home_backup.tar.gz /path/to/old/confluence_home

    将备份文件传输到新 Confluence 环境 Home 目录并解压

  3. 修改配置文件(如果数据库连接变了)

    如果新环境的数据库 IP、端口或密码与旧环境不同,你需要修改新环境 Home 目录下的配置文件:

    文件路径: <confluence-home>/confluence.cfg.xml

迁移完成后,重启 Confluence,即可加载到旧环境中的数据。

常见问题

迁移后文档无法编辑

迁移完成后,Confluence 打开正常,数据已经恢复,但是编辑文档保存时报错: Something went wrong after loading the editor. Copy your unsaved changes and refresh the page to keep editing.

这个错误通常意味着 Confluence 协作编辑(Collaborative Editing) 服务出现了通讯故障。在 Confluence 9.0.2 中,这通常与 Synchrony(负责实时协作的组件)的配置或网络环境有关。

可以 刷新 Synchrony 状态 解决

  1. 以管理员身份登录 Confluence。

  2. 前往 管理 (Confluence administration) > 协作编辑 (Collaborative editing)

  3. 点击 Change mode ,将其切换为 Disabled

  4. 等待几秒钟后,重新切换回 Enabled

    这会重启 Synchrony 进程并清理过时的锁定状态。

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;
}


阅读全文 »

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 范围内做进一步过滤
阅读全文 »

环境信息

  • 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


sudo mkdir -p /usr/local/lib/docker/cli-plugins
sudo curl -SL https://github.com/docker/compose/releases/download/v5.0.1/docker-compose-linux-x86_64 \
-o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
sudo ln -sf /usr/local/lib/docker/cli-plugins/docker-compose /usr/local/bin/docker-compose
docker compose version

sudo curl -L https://github.com/docker/buildx/releases/download/v0.17.1/buildx-v0.17.1.linux-amd64 \
-o /usr/local/lib/docker/cli-plugins/docker-buildx
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
sudo ln -sf /usr/local/lib/docker/cli-plugins/docker-buildx /usr/local/bin/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 ls-remote # 列出可选安装版本

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 pm2@3.5.1 # 安装指定版本
npm install -g pm2@latest # 安装最新版本

Node.js 相关常见操作

安装包

npm install pm2

安装指定版本的包

npm install -g pm2@3.5.1

查看可用的安装版本

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 <auth@gmail.com>",
"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-*"

版本信息

  • Ubuntu 22.04.5 LTS
  • Elasticsearch v9.3.1
  • Kibana v9.3.1
  • Fluent Bit v4.2.2

在 Elasticsearch 8.x 和 9.x 版本中,Enrollment Token(注册令牌)机制是深度绑定 SSL 的,

Docker Compose 部署 EFK

为项目创建以下目录,分别用于存放配置文件和数据:

mkdir config/{fluent-bit,kibana,elasticsearch} -p

mkdir data/{fluent-bit,kibana,elasticsearch} -p

项目整体目录如下:

# tree
.
├── config
│ ├── elasticsearch
│ ├── fluent-bit
│ │ └── fluent-bit.conf
│ └── kibana
├── data
│ ├── elasticsearch
│ ├── fluent-bit
│ └── kibana
└── docker-compose.yml

fluent-bit.conf 示例配置如下:

fluent-bit.conf
[SERVICE]
Flush 1
Log_Level info
Daemon off

[INPUT]
Name cpu
Tag cpu_usage

[INPUT]
Name forward
Listen 0.0.0.0
Port 24224

[OUTPUT]
Name es
Match *
Host elasticsearch
Port 9200
# 要配置 ES 用户密码才能同步数据
HTTP_User elastic
HTTP_Passwd changeme
Index fluentbit
Type _doc
Suppress_Type_Name On

docker-compose.yml 配置如下

docker-compose.yml
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:9.3.1
container_name: elasticsearch
environment:
- node.name=elasticsearch
- discovery.type=single-node
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- xpack.security.enabled=true
- xpack.security.enrollment.enabled=true
- xpack.security.transport.ssl.enabled=false
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- ./data/elasticsearch:/usr/share/elasticsearch/data # 核心:数据持久化
ports:
- "19200:9200"
networks:
- efk-net

kibana:
image: docker.elastic.co/kibana/kibana:9.3.1
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=kibana_system # 使用账户名密码认证 ES,密码使用命令重置 docker compose exec -it elasticsearch bin/elasticsearch-reset-password -u kibana_system
- ELASTICSEARCH_PASSWORD=dHCC5hm-lwK1Ifoz=E3I # 密码无需使用 单引号或者双引号
volumes:
- ./data/kibana:/usr/share/kibana/data
ports:
- "5601:5601"
depends_on:
- elasticsearch
networks:
- efk-net

fluent-bit:
image: fluent/fluent-bit:4.2.2
container_name: fluent-bit
volumes:
- ./config/fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
# 如果你想采集宿主机的系统日志,可以加上:
- /var/log:/var/log:ro
depends_on:
- elasticsearch
networks:
- efk-net

networks:
efk-net:
driver: bridge

如遇启动失败,请查看日志,启动正常后,登录 Kibana 链接 <KIBANA_IP>:5601

看到这个界面说明你的 Elasticsearch 已经成功启动了。这是 Elastic 9.x 系列的新安全特性: 由于启用了安全验证,Kibana 启动后需要一个“准入许可证”(Enrollment Token)来和 Elasticsearch 握手

你可以选择:生成 Token彻底关闭验证 或者使用密码验证,本示例中使用 密码验证

  1. 生成 相关密码

    1. 为管理员用户 elastic 生成密码(重置密码)

      # docker compose exec -it elasticsearch /usr/share/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: xf52=nGPAf3TBOIbMuKR
    2. kibana_system 生成密码
      docker compose exec -it elasticsearch bin/elasticsearch-reset-password -u kibana_system

  2. 彻底关闭验证(最快,推荐用于开发环境)

    1. 点击你截图页面下方的 Configure manually
    2. 在地址栏输入: http://elasticsearch:9200
      xpack.security.enabled: false
      # kibana 环境变量
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200

常见错误总结

This is a superuser account that cannot write to system indices that Kibana needs to function

Kibana 不能配置使用 ES 管理员账户 elastic 去认证,否则无法启动

docker-compose.yml
kibana:
image: docker.elastic.co/kibana/kibana:9.3.1
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=kibana_system ## 这里不能使用 elastic 账户,否则 kibana 无法启动
- ELASTICSEARCH_PASSWORD='dHCC5hm-lwK1Ifoz=E3I'

环境信息

  • Centos 7

ssh 免密登陆

在需要免密码登陆的场景下,可以配置 ssh 密钥登陆。配置步骤如下

  1. 在本地服务器上面执行命令生成密钥对
    $ ssh-keygen 
    Generating public/private rsa key pair.
    Enter file in which to save the key (/home/testuser/.ssh/id_rsa):
    Enter passphrase (empty for no passphrase):
    Enter same passphrase again:
    Your identification has been saved in /home/testuser/.ssh/id_rsa.
    Your public key has been saved in /home/testuser/.ssh/id_rsa.pub.
    The key fingerprint is:
    SHA256:Lzvl8GbOQETBVcTf8lf0Qk9KUQAESs9h8wARud+iQrk testuser@k8s-uat-master1.148962587001
    The key's randomart image is:
    +---[RSA 2048]----+
    | .BBB*=.o+.|
    | oo= =. o o|
    | o.o .+ *.|
    | .. = =|
    | .S. . +.|
    | o...+ . o|
    | . .o*.. .|
    | E o== |
    | ..=o |
    +----[SHA256]-----+
    以上命令生成了公私密钥对,分别存储在了 /home/testuser/.ssh/id_rsa.pub/home/testuser/.ssh/id_rsa 中。
  2. 在本地服务器上面执行命令将其公钥添加到目标主机的 /home/testuser/.ssh/authorized_keys。或者手动拷贝公钥追加到目标主机的 .ssh/authorized_keys
    $ ssh-copy-id -p 30000 testuser@172.31.30.115
    /bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/testuser/.ssh/id_rsa.pub"
    The authenticity of host '[172.31.30.115]:30000 ([172.31.30.115]:30000)' can't be established.
    ECDSA key fingerprint is SHA256:vKD5th2QpWYv/hmt+180BsENDHWNcJdKiEBOH06h/K8.
    ECDSA key fingerprint is MD5:bf:8c:b9:e6:31:92:1f:a9:b6:7b:8f:50:d7:10:9e:fd.
    Are you sure you want to continue connecting (yes/no)? yes
    /bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
    /bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keystestuser@172.31.30.115's password:

    Number of key(s) added: 1

    Now try logging into the machine, with: "ssh -p '30000' 'testuser@172.31.30.115'"
    and check to make sure that only the key(s) you wanted were added.
  3. 在本地服务器上面验证可以免密登陆到目标服务器。

如果要配置双向免密,将以上步骤反过来操作一遍即可

常见配置

登录服务器,经常遇见以下提示信息,说明有主机一直在尝试暴力破解用户名密码

There were 696 failed login attempts since the last successful login.

查看登录失败的用户名和 ip 地址

$ grep "Failed password for invalid user " /var/log/secure | awk '{print $11,$13}' | sort | uniq -c | sort -k1 -n
3 wangli 47.74.0.77
3 work 47.74.0.77
3 yt 47.74.0.77
3 yx 47.74.0.77
3 yyz 47.74.0.77
3 zabbix 47.74.0.77
3 zd 47.74.0.77
3 zhangfan 47.74.0.77
3 zxy 47.74.0.77
4 client003 47.74.0.77
4 client004 47.74.0.77
4 dell 47.74.0.77
4 ftpuser 47.74.0.77
4 inspur 47.74.0.77
4 wang 47.74.0.77
5 git 47.74.0.77
5 nagios 47.74.0.77
5 testuser 47.74.0.77
6 omnisky 47.74.0.77
7 oracle 47.74.0.77
8 jenkins 47.74.0.77
10 hadoop 47.74.0.77
10 postgres 47.74.0.77
11 ubuntu 47.74.0.77
11 user 47.74.0.77
12 admin 47.74.0.77
15 test 47.74.0.77

阅读全文 »

Google Antigravity IDE

  • Trae 也可以依此操作配置

本示例演示 Google Antigravity AI 在运维场景中的使用方法。假如需要让 Google Antigravity AI 远程 SSH 连接到目标服务器并利用其 AI Agent 定位运行问题,可以参考以下步骤:

  1. 建立 SSH 远程连接 。Antigravity 基于 VS Code 内核,因此其连接方式与 VS Code 保持一致,但强化了 AI 对远程环境的感知。

    1. 安装插件 。确保已在 Antigravity 中安装了 Remote - SSH 扩展。

      1. 定位到 Setting -> Extensions (或者左侧边栏)中,在顶部搜索框中输入 @installed remote - ssh ,如果列表中出现了 Remote - SSH(通常由 Microsoft 发布),说明已经安装。如果没有任何显示,说明尚未安装。
      2. 如果没有安装,在搜索框中搜索 Remote - SSH,找到由 Microsoft 发布的版本(Antigravity 完美兼容 VS Code 市场插件),点击 Install

        或者直接发送需求给 Antigravity 让其自动安装

    2. 配置主机 。在 IDE 侧边栏的 Remote Explorer(远程资源管理器) 中添加并连接服务器。

      如果是首次连接,Antigravity 会在目标服务器上安装一个小型的服务端组件(Server Component),以便 AI Agent 能直接在服务器环境下运行。

  2. 打开项目目录 ,连接成功后,点击 Open Folder(打开文件夹) ,选择你运行服务所在的代码目录或配置目录(如 /etc/nginx 或你的后端代码路径)。

  3. 连接成功后,你不需要像以前那样手动翻阅数 GB 的日志。你可以通过侧边栏的 Chat 面板(Ctrl + L)指挥 AI。

    1. 自动日志分析 (Log Auditing)

      • 指令示例 :查看 /var/log 下最近 5 分钟的系统错误日志,找出导致我的 Java 应用崩溃的原因。

      • AI 行为: Agent 会自动执行 tail -n 100grep 命令,捕获堆栈信息(Stack Trace),并结合项目代码分析是 OOM(内存溢出)、端口冲突还是权限问题

    2. 如果你发现服务无法访问(例如 404 或 502)

      • 指令示例 :检查 8080 端口是否在监听,并测试 Nginx 反向代理到该端口的连通性。

      • AI 行为 :Agent 会执行 netstat -tunlpcurl -I localhost:8080 ,如果发现端口没启动,它会尝试查看启动脚本(如 docker-compose.ymlsystemd 配置)来定位根源。

  4. 闭环修复 (The “Fix” Loop)

    这是 Antigravity 区别于普通 IDE 的地方:

    • 生成修复计划 (The Plan)

      当 Agent 定位到问题(例如:K8s 配置的 resources.limits 太小导致 Pod 被杀)时,它会生成一个 Plan Artifact。

    • 审批修改

      它会展示修改后的代码或配置文件。你只需点击 Apply ,它就会在服务器上直接修改文件。

    • 验证结果

      你可以紧接着说:”修改后重新启动服务,并尝试用内置浏览器访问,确认首页能正常加载。”
      Antigravity 会启动内置浏览器,通过 SSH 隧道访问远程服务,截图反馈给你看。

例如处理 K8s 集群问题:如果你在服务器上配置了 kubectl ,你可以对 Antigravity 说:”帮我列出所有 Pending 状态的 Pod,并分析它们的事件日志,看看是不是节点资源不足。” 它能像一个初级运维工程师一样帮你完成繁琐的查询。

.agent/rules

生成环境中,AI 所做的操作一定要得到审批。否则会产生不可预期的结果

不是所有的 AI 工具都会遵循此规则,使用之前一定要确认清楚。比如 Trae 就不会加载 .agent/rules 文件去读取规则。

为了防止 AI 在服务器上乱改,你可以在项目中创建一个 .agent/rules 文件,写明:”严禁直接修改生产数据库” 或 “修改配置前必须先备份旧文件”。Agent 在执行任务时会强制遵守这些规则

关于 .agent/rules 的生效范围(作用域):

  • 项目级规则(最推荐) : 放在你项目代码的根目录下。规则仅对当前打开的特定项目生效。
  • 全局(Global)规则 : 如果你希望在打开任何项目文件夹时,AI 智能体都遵循一套通用的逻辑。通常存放在用户家目录的配置文件夹中
    • Windows : %USERPROFILE%\.antigravity\rules
    • Linux : ~/.antigravity/rules

如果要知道目前使用的 rules 都有哪些,可以直接让 AI 进行汇总

快速创建并测试 ,你不需要手动去文件夹里新建,直接在 Antigravity 的 Chat 面板里对 AI 下令即可:

指令请给出创建全局规则的步骤

如果您希望为您使用的 AI Agent 配置跨所有项目生效的全局规则,通常可以通过以下步骤完成(具体取决于您使用的编辑器客户端):

1. 通过编辑器的 AI 设置配置(最常见且推荐)
大部分 AI 辅助编辑器(如 Cursor、Windsurf 或相关 VS Code 插件)都支持在界面中直接配置全局系统提示词 (System Prompts) 或规则:

步骤:
打开编辑器的全局设置面板(通常快捷键为 Ctrl + , 或 Cmd + ,)。
搜索 Rules, Rules for AI, 或对应 AI 助手的名称(如 Agent Rules)。
在对应的文本框中,输入您期望的全局规则。例如:

1. 禁止以 root 用户执行操作
2. 切换到用户 wind
3. 执行任何变更操作都必须得到审批确认才能执行

保存设置。此后无论打开哪个项目,Agent 都会优先加载这些全局规则。

2. 在用户主目录下创建全局规则文件
部分 AI 助手会自动读取用户主目录(Home 目录 ~)下的特定隐藏文件作为全局指令。

步骤:
切换到您的宿主机主目录:cd ~
创建全局规则文件。根据具体的插件约定,通常是 ~/.agent/rules、~/.cursorrules 或 ~/.windsurfrules 等。
将规则写入该文件:

mkdir -p ~/.agent
echo "1. 禁止以 root 用户执行操作" > ~/.agent/rules
echo "2. 切换到用户 wind" >> ~/.agent/rules
echo "3. 执行任何变更操作都必须得到审批确认才能执行" >> ~/.agent/rules

3. 使用全局 Workflows(工作流)

如果您希望在所有项目中都可以随时调用某个标准化的“规则校验流程”,可以使用全局的 Workflows 功能。

步骤:
在全局或项目的 .agents/workflows/(或 _agents/workflows/)目录下创建 Markdown 格式的流程文件。
例如创建一个 .agents/workflows/enforce-rules.md:

---
description: 确保并校验 Agent 遵循基础安全规则
---
1. 检查当前是否为 root 用户,如果是则拒绝操作并提示用户。
2. 确保后续命令以 `su - wind -c` 或类似方式作为 wind 用户执行。
3. 在进行任何系统变更前,暂停并等待用户审批。

Claude Code

对于无法配置 SSH(例如内网穿透、防火墙严格限制或云厂商限制)的服务器,Claude Code 是目前最顶级的 终端代理(Terminal Agent) 方案

Claude Code 的配置与使用

Claude Code 的优势在于它是一个 CLI 工具,可以直接安装在目标服务器上。它不需要 SSH 隧道,因为它通过 HTTPS 与 Anthropic 的服务器通信,只要服务器能访问外网(或通过代理访问),就能工作。

  • 安装步骤(服务器端) 。在你的 Linux 服务器终端执行:

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

    部分地区下载有限制,如 HK,可以在可用地区下载脚本上传到服务器后安装

    或者使用 npm

    npm install -g @anthropic-ai/claude-code

  • 配置与初始化

    1. 登录 :输入 claude 启动,按照提示完成 OAuth 登录(会给出一个 URL 和授权码)。
      Select login method:

      ❯ 1. Claude account with subscription · Pro, Max, Team, or Enterprise

      2. Anthropic Console account · API usage billing

      3. 3rd-party platform · Amazon Bedrock, Microsoft Foundry, or Vertex AI

  • 执行 claude 命令运行

服务器上运行 gemini cli

Gemini 在服务器运维场景下有一个独特的优势:超长上下文窗口(Context Window) 。相比 Claude,Gemini 能够一次性“吞下”更多的日志文件或整个 Kubernetes 命名空间下的所有配置(YAML),从而提供更具全局观的分析。

  1. 安装 Google AI CLI (基于 Node.js)

    如果还未按照 Node.js , 可以使用以下命令( nvm 管理 Node.js 版本 )安装最新版本(版本太低的话, gemini 不支持)

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

    nvm install 20
    nvm use 20
    nvm alias default 20

    安装 Node.js 后安装 gemini-cli

    # 安装
    npm install -g @google/gemini-cli

    # 配置 API Key (从 Google AI Studio 免费获取)
    export GEMINI_API_KEY="你的_API_KEY"

    # 开始对话
    gemini

    参考以下步骤,获取 API Key:

    1. 打开浏览器,访问 Google AI Studio

    2. 进入页面后,点击左侧导航栏顶部的 Get API key 按钮(通常带有一个钥匙图标)。

    3. 在打开的界面中,你会看到 Create API key 的选项:

      • Create API key in new project :如果你是第一次使用,选这个。Google 会在你的 Google Cloud 控制台中自动创建一个名为 Generative Language Client 的项目。
    4. 立即复制并保存这串密钥。

  2. 使用命令 gemini 运行