业务链说明
本文档将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_data1000为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_on加condition: 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 kibana2.等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 -d4.验证四个服务是否都正常运行
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.9kbhealth为green、索引名不带.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,填写:
- Name:
sftpgo-logs - Index pattern:
sftpgo-logs-* - Timestamp field:
@timestamp
保存后即完成配置,sftpgo-logs-*可自动匹配后续每天新生成的索引(如sftpgo-logs-2026.07.04)。
2.进入Discover查看日志
左上角汉堡菜单 → Analytics → Discover,顶部Data View下拉框选择sftpgo-logs,右上角时间范围选择器调整为Last 24 hours或Today,中间区域即可看到实时采集的日志条目。
3.常用查询与筛选(KQL语法,输入于顶部搜索框)
app: "sftpgo" and log.level: "error"message: *upload*点击单条日志左侧箭头可展开查看完整字段,包括filebeat.yml中自定义添加的app: sftpgo、env: 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 kibana2.索引磁盘占用查看
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-file,max-size: 100m、max-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 kibana2.索引出现.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.yml中data_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 filebeat3.单节点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