业务链说明

本文档将SFTPGo部署手册与ELK日志采集手册合并为一个项目目录、一个docker-compose.yml,四个服务(sftpgo / elasticsearch / kibana / filebeat)统一编排、统一网络,形成完整业务链:

SFTPGo产生日志 → 写入宿主机共享目录 → Filebeat采集 → 写入Elasticsearch → Kibana可视化查看

sftpgo与filebeat通过同一个宿主机目录./sftpgo/logs)实现日志传递,不依赖网络;filebeat与elasticsearch之间通过compose内部网络elastic-net用容器名互相访问,不再依赖外部域名,部署更内聚、迁移更方便。

一、环境准备

1.安装Docker(已安装可跳过)

# 卸载旧版本(按需)
yum remove docker docker-client docker-client-latest docker-common \
  docker-latest docker-latest-logrotate docker-logrotate docker-engine

# 配置阿里云docker-ce源
yum install -y yum-utils
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 安装docker-ce
yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 启动并设置开机自启
systemctl start docker && systemctl enable docker

二、创建项目目录结构

所有配置、数据、日志统一收拢在一个项目目录下,方便整体迁移与备份:

mkdir -p /home/sftpgo-elk/{sftpgo/home,sftpgo/data,sftpgo/logs,filebeat/config,filebeat/data,filebeat/logs,elasticsearch/es_data,kibana/kibana_data}
cd /home/sftpgo-elk

目录规划:

/home/sftpgo-elk/
├── docker-compose.yml          # 唯一的compose文件,四个服务统一编排
├── sftpgo/
│   ├── home/                   # 挂载至容器 /var/lib/sftpgo(配置/元数据)
│   ├── data/                   # 挂载至容器 /srv/sftpgo(文件存储)
│   └── logs/                   # 挂载至容器 /var/log/sftpgo,同时被filebeat只读挂载采集
├── filebeat/
│   ├── config/filebeat.yml
│   ├── data/                   # filebeat注册表(registry),记录采集进度,需持久化
│   └── logs/                   # filebeat自身运行日志
├── elasticsearch/
│   └── es_data/
└── kibana/
    └── kibana_data/

赋予数据目录读写权限

chown -R 1000:1000 /home/sftpgo-elk/sftpgo/home
chown -R 1000:1000 /home/sftpgo-elk/sftpgo/data
chown -R 1000:1000 /home/sftpgo-elk/sftpgo/logs
chown -R 1000:1000 /home/sftpgo-elk/elasticsearch/es_data
chown -R 1000:1000 /home/sftpgo-elk/kibana/kibana_data

1000为SFTPGo、Elasticsearch、Kibana官方镜像默认使用的容器内运行用户UID/GID,统一赋权避免EACCES权限报错。

三、用cat生成统一docker-compose.yml

⚠️ 以下配置以你实际生产环境正在使用的sftpgo配置为准(日志输出、限流轮转参数均保留),仅将挂载路径统一收拢到项目目录下,并把filebeat、elasticsearch、kibana并入同一份compose、同一网络。

cat <<'EOF' > docker-compose.yml
version: "3.8"

services:
  # ------------------------- 业务服务:SFTPGo -------------------------
  sftpgo:
    image: drakkan/sftpgo
    container_name: sftpgo
    user: "1000:1000"
    working_dir: /var/lib/sftpgo
    restart: always
    command: sftpgo serve
    ports:
      - "2022:2022"
      - "8081:8080"
    volumes:
      - ./sftpgo/home:/var/lib/sftpgo
      - ./sftpgo/data:/srv/sftpgo
      - ./sftpgo/logs:/var/log/sftpgo      # 与filebeat共享的日志目录
    environment:
      - SFTPGO_LOG_FILE_PATH=/var/log/sftpgo/sftpgo.log
      - SFTPGO_LOG_LEVEL=info
      - SFTPGO_LOG_ROTATE_MAX_SIZE=100
      - SFTPGO_LOG_ROTATE_MAX_BACKUPS=30
      - SFTPGO_LOG_ROTATE_MAX_AGE=30       # 保留30天
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "30"
    networks:
      - elastic-net

  # ------------------------- 存储与检索:Elasticsearch -------------------------
  elasticsearch:
    image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/elasticsearch:8.19.18
    container_name: elasticsearch
    restart: unless-stopped
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false        # 内网环境关闭认证,简化Filebeat对接
      - ES_JAVA_OPTS=-Xms1g -Xmx1g          # 根据服务器内存调整,建议≥2g
      - TZ=Asia/Shanghai
    ports:
      - "9200:9200"
    volumes:
      - ./elasticsearch/es_data:/usr/share/elasticsearch/data
    ulimits:
      memlock:
        soft: -1
        hard: -1
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:9200/_cluster/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
    networks:
      - elastic-net

  # ------------------------- 可视化:Kibana -------------------------
  kibana:
    image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/kibana:8.19.18
    container_name: kibana
    restart: unless-stopped
    depends_on:
      elasticsearch:
        condition: service_healthy
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
      - TZ=Asia/Shanghai
    ports:
      - "5601:5601"
    volumes:
      - ./kibana/kibana_data:/usr/share/kibana/data
    networks:
      - elastic-net

  # ------------------------- 采集:Filebeat -------------------------
  filebeat:
    image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/elastic/filebeat:8.19.18
    container_name: filebeat
    user: root
    restart: unless-stopped
    depends_on:
      elasticsearch:
        condition: service_healthy
    volumes:
      - ./filebeat/config/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - ./sftpgo/logs:/var/log/sftpgo:ro    # 与sftpgo共享同一宿主机目录,只读采集
      - ./filebeat/data:/usr/share/filebeat/data
      - ./filebeat/logs:/var/log/filebeat
    environment:
      - TZ=Asia/Shanghai
    networks:
      - elastic-net

networks:
  elastic-net:
    driver: bridge
EOF

相比原两份手册的关键调整

  • 四个服务合并进同一个elastic-net网络,用容器名http://elasticsearch:9200,脱离宿主机DNS解析,迁移到其他环境无需改配置。
  • sftpgo的日志目录/var/log/sftpgo与filebeat的采集源目录挂载同一个宿主机路径./sftpgo/logs,业务链闭环全部在compose内部完成。
  • kibana/filebeat显式depends_oncondition: service_healthy,保证ES健康后再启动,避免filebeat启动时连接ES失败反复重试。

四、用cat生成filebeat.yml

cat <<'EOF' > filebeat/config/filebeat.yml
# ======================== Filebeat Configuration =========================
setup.template.name: "sftpgo-logs"
setup.template.pattern: "sftpgo-logs-*"
setup.ilm.enabled: false
# ⭐ 关键:关闭Filebeat自动管理索引模板,防止其自动携带data_stream字段
# 导致索引被强制转换为Data Stream(.ds-前缀,无法按常规方式删除/管理)
setup.template.enabled: false
# ------------------------------ Inputs -----------------------------------
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/sftpgo/*.log
    # 如果 SFTPGo 日志包含多行堆栈或跨行记录,请启用以下配置
    # multiline.pattern: '^\d{4}-\d{2}-\d{2}'
    # multiline.negate: true
    # multiline.match: after
    fields:
      app: sftpgo
      env: production
    fields_under_root: true
# ------------------------------ Output ------------------------------------
output.elasticsearch:
  # ⭐ 已并入同一compose网络,直接用容器名访问,无需外部域名
  hosts: ["http://elasticsearch:9200/"]
  # 自定义索引名称(按天滚动)
  index: "sftpgo-logs-%{+yyyy.MM.dd}"
  data_stream.enabled: false
  # 如果 ES 开启了 X-Pack Security,取消下面的注释并填入密码
  # username: "elastic"
  # password: "your_secure_password"
# ------------------------------ Performance -------------------------------
queue.mem:
  events: 512
  flush.min_events: 128
  flush.timeout: 1s
# ------------------------------ Logging -------------------------------------
logging.level: info
logging.to_stderr: true
EOF

五、启动业务链

由于filebeat自动创建的索引模板会导致索引被识别为Data Stream(详见附录2),需按以下固定顺序启动,中间插入一步手动建模板:

1.先启动Elasticsearch、Kibana(filebeat依赖它们健康检查通过)

docker compose up -d elasticsearch kibana

2.等ES就绪后,手动创建不含data_stream字段的索引模板(实际测试可以跳过这一部分直接执行步骤3)

# 等健康检查通过
until curl -sf http://localhost:9200/_cluster/health > /dev/null; do sleep 2; done

curl -X PUT "http://localhost:9200/_index_template/sftpgo-logs" \
  -H "Content-Type: application/json" \
  -d '{
    "index_patterns": ["sftpgo-logs-*"],
    "priority": 150,
    "template": {
      "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 0
      },
      "mappings": {
        "properties": {
          "@timestamp": { "type": "date" },
          "message": { "type": "text" }
        }
      }
    }
  }'

number_of_replicas设为0是因为单节点ES无法分配副本分片,设1会导致索引长期处于yellow状态。

3.启动(实测可以跳过步骤1和2直接执行这一步)

docker compose up -d

4.验证四个服务是否都正常运行

docker compose ps

预期四个服务的STATUS均为Up(elasticsearch/kibana会显示healthy)。

5.验证日志是否成功写入ES

curl "http://localhost:9200/_cat/indices/sftpgo-logs-*?v&s=index"

预期返回类似:

health status index                  uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   sftpgo-logs-2026.07.03 Uwlh87QdQ9ebIIEy9789vg   1   0         49            0    141.9kb        141.9kb      141.9kb

healthgreen、索引名不带.ds-前缀,代表数据已按预期以普通索引形式写入。

以后每次启动整个业务链(比如服务器重启后),直接:

cd /home/sftpgo-elk
docker compose up -d

因为模板已经手动建好、restart: unless-stopped/always已配置,四个服务会按依赖关系自动拉起,无需再重复第2步。

六、Kibana配置查看sftpgo日志

1.创建Data View

登入 http://<host_ip>:5601,左上角汉堡菜单 → Management → Stack Management → Kibana → Data Views,点击右上角Create data view,填写:

  • Namesftpgo-logs
  • Index patternsftpgo-logs-*
  • Timestamp field@timestamp

保存后即完成配置,sftpgo-logs-*可自动匹配后续每天新生成的索引(如sftpgo-logs-2026.07.04)。

2.进入Discover查看日志

左上角汉堡菜单 → Analytics → Discover,顶部Data View下拉框选择sftpgo-logs,右上角时间范围选择器调整为Last 24 hoursToday,中间区域即可看到实时采集的日志条目。

3.常用查询与筛选(KQL语法,输入于顶部搜索框)

app: "sftpgo" and log.level: "error"
message: *upload*

点击单条日志左侧箭头可展开查看完整字段,包括filebeat.yml中自定义添加的app: sftpgoenv: production字段。

4.保存查询与可视化(可选)

筛选条件配置完成后,点击顶部Save可保存为常用查询;点击搜索框右侧Visualize可基于当前筛选条件快速生成柱状图,用于监控错误日志频率等场景。

七、日常运维

1.查看四个服务运行状态与日志

docker compose ps
docker compose logs -f sftpgo
docker compose logs -f filebeat
docker compose logs -f elasticsearch
docker compose logs -f kibana

2.索引磁盘占用查看

curl "http://localhost:9200/_cat/indices/sftpgo-logs-*?v&s=index"

3.历史索引清理(按需,测试/内网环境无ILM时需手动清理)

curl -X DELETE "http://localhost:9200/sftpgo-logs-2026.07.01"

4.整体业务链停止/重启

cd /home/sftpgo-elk
docker compose stop      # 停止所有服务
docker compose start     # 启动所有服务
docker compose restart   # 重启所有服务

5.SFTPGo容器日志轮转

sftpgo容器已在compose中配置logging.driver: json-filemax-size: 100mmax-file: 30,Docker会自动轮转与清理容器标准输出日志,无需再额外配置系统级logrotate。应用层日志(sftpgo.log,即filebeat采集的对象)由SFTPGO_LOG_ROTATE_*系列环境变量控制轮转,二者互不冲突。

八、附录

1.Kibana容器启动报错Unable to write to UUID file ... EACCES

原因:Kibana容器内部以非root用户(UID 1000)运行,宿主机挂载目录kibana_data属主/权限不匹配导致无法写入。

处理

docker compose stop kibana
chown -R 1000:1000 ./kibana/kibana_data
docker compose start kibana

若属主已正确但仍报错,大概率是SELinux拦截,检查:

getenforce

若为Enforcing,在docker-compose.yml对应挂载卷末尾加:Z标签:

volumes:
  - ./kibana/kibana_data:/usr/share/kibana/data:Z

修改后重建容器:

docker compose up -d --force-recreate kibana

2.索引出现.ds-前缀(被误判为Data Stream)问题

现象_cat/indices查询结果索引名类似.ds-sftpgo-logs-2026.07.03-2026.07.03-000001,而非期望的sftpgo-logs-2026.07.03

原因:Filebeat自动创建的composable template默认携带data_stream字段,导致匹配的索引被ES按Data Stream处理,不受filebeat.ymldata_stream.enabled: false配置项影响。

处理(需先停止Filebeat):

docker compose stop filebeat

# 1.删除已生成的数据流(会删除该数据流下已写入的日志数据)
curl -X DELETE "http://localhost:9200/_data_stream/sftpgo-logs-2026.07.03"

# 2.删除被data_stream字段污染的模板
curl -X DELETE "http://localhost:9200/_index_template/sftpgo-logs"

# 3.按 五、2 手动重建不含data_stream字段的模板

# 4.确认 filebeat.yml 中已有 setup.template.enabled: false

docker compose start filebeat

3.单节点ES索引健康状态长期为yellow

原因:默认number_of_replicas为1,单节点环境副本分片无法分配到其他节点。

处理:在索引模板中显式设置number_of_replicas: 0(见五、2模板配置),或对已存在索引直接调整:

curl -X PUT "http://localhost:9200/sftpgo-logs-*/_settings" \
  -H "Content-Type: application/json" \
  -d '{"index": {"number_of_replicas": 0}}'

4.整体迁移到新服务器

因所有数据(sftpgo数据/配置、ES索引、Kibana配置、filebeat采集进度)均已收拢在/home/sftpgo-elk项目目录下,迁移时只需:

# 旧服务器
cd /home/sftpgo-elk
docker compose down
tar czf sftpgo-elk-backup.tar.gz /home/sftpgo-elk

# 新服务器
tar xzf sftpgo-elk-backup.tar.gz -C /home/
cd /home/sftpgo-elk
docker compose up -d
最后修改:2026 年 07 月 03 日
如果觉得我的文章对你有用,请随意赞赏