# ============================================================ # lokistack - Production Docker Compose Configuration # ============================================================ # ⚠️ 重要:本平台是【单写者】架构,只能跑一个应用实例。 # # 原因:全部状态存在 /app/data/opsxloki.json(JSON 文件 DB), # 进程内用一把读写锁 + 全量写盘来保证一致性。两个实例各挂各的卷时: # - 数据完全不共享 → 页面刷新两次可能看到不同结果(负载均衡打到不同实例) # - 写操作只落一个实例 → 另一个实例随后全量写盘会把对方的数据覆盖掉 # - 不是高可用,是数据分裂 # 曾经此文件配置 lokistack-1 / lokistack-2 双实例 + nginx 轮询, # 属于**看起来高可用、实际会丢数据**的错误配置,已改为单实例。 # # 真要高可用,得先把存储层换成外部数据库(MySQL/PostgreSQL), # 那是架构变更,不是加个副本能解决的。 # # 本文件提供的生产特性: # - 只读根文件系统 + tmpfs 可写目录(容器逃逸面收敛) # - 严格资源限制与 pids 上限 # - Docker Secrets 注入密钥(不落环境变量/命令行) # - 健康检查 + 优雅停机(30s,等 DB 落盘完成) # - 日志轮转(json-file max-size/max-file) # - nginx 只做 TLS/反代,不做负载均衡 # ============================================================ services: # ---- Nginx 反向代理(单后端,仅做 TLS / 统一入口)---- nginx: image: nginx:1.27-alpine container_name: lokistack-lb restart: unless-stopped ports: - "8080:8080" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro networks: - lokistack-net healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/v1/health"] interval: 30s timeout: 5s retries: 3 deploy: resources: limits: cpus: '0.5' memory: 128M reservations: cpus: '0.1' memory: 32M logging: driver: "json-file" options: max-size: "20m" max-file: "3" security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - NET_BIND_SERVICE read_only: true tmpfs: - /var/cache/nginx:noexec,nosuid,size=10m - /var/run:noexec,nosuid,size=5m depends_on: lokistack: condition: service_healthy # ---- 应用(唯一实例)---- lokistack: build: context: . dockerfile: Dockerfile args: TARGETARCH: ${TARGETARCH:-amd64} image: lghost/lokistack:${IMAGE_TAG:-latest} container_name: lokistack hostname: lokistack restart: unless-stopped security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - NET_BIND_SERVICE # 只读根文件系统:数据目录与 tmpfs 之外不可写 read_only: true tmpfs: - /tmp:noexec,nosuid,size=50m,uid=1000,gid=1000 volumes: # 唯一数据卷:DB + kubeconfigs/ + known_hosts/ + backups/ - lokistack-data:/app/data - ${KUBECONFIG_PATH:-~/.kube}:/home/lokistack/.kube:ro - ${SSH_KEY_PATH:-~/.ssh}:/home/lokistack/.ssh:ro environment: - SERVER_PORT=8080 - DATA_DIR=/app/data - KUBECONFIG=/home/lokistack/.kube/config # 密钥走 Docker Secrets 文件注入,不进环境变量(ps/env 里看不到) - JWT_SECRET_FILE=${JWT_SECRET_FILE:-/run/secrets/jwt_secret} - ENCRYPTION_KEY_FILE=${ENCRYPTION_KEY_FILE:-/run/secrets/encryption_key} secrets: - jwt_secret - encryption_key healthcheck: # 用 API 健康端点而不是静态文件:静态文件返回 200 只能说明 nginx/文件在, # 不能说明 DB 和业务可用 test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/v1/health"] interval: 30s timeout: 5s retries: 3 start_period: 20s deploy: resources: limits: # 平台要扛 JSON DB 全量读写 + 告警引擎 + 并发 API; # 实测 1 核/256M 在批量发布时会顶到 OOM,这里按 2C/2G 起步 cpus: '2.0' memory: 2G pids: 200 reservations: cpus: '0.5' memory: 512M restart_policy: condition: on-failure delay: 5s max_attempts: 3 window: 60s logging: driver: "json-file" options: max-size: "50m" max-file: "5" # 优雅停机:给正在进行的 DB 落盘留时间,避免 JSON 被截断 stop_grace_period: 30s networks: - lokistack-net # ---- Named Volumes ---- volumes: lokistack-data: driver: local # ---- Docker Secrets ---- secrets: jwt_secret: file: ${JWT_SECRET_FILE_PATH:-./secrets/jwt_secret.txt} encryption_key: file: ${ENCRYPTION_KEY_FILE_PATH:-./secrets/encryption_key.txt} # ---- Network ---- networks: lokistack-net: driver: bridge ipam: config: - subnet: 172.28.1.0/24