Logstash的容器化与Ansible多环境下单配置文件发布

原因

为了发布、迁移方便,最近决定将公司项目中用的logstash容器化,最初原打算沿用原多进程方案,在容器内通过supervisor启动多个不同配置进程,但使用官方容器时并支持启动多个实例。

最终解决方式:将集群的配置文件组装在一起,不同的path对应不同的filter与output;由于不同主机上组件不同,日志也不同。因此在ansible发布时通过脚本检测生成log file 的path列表,适配集群上的不同组件。这样的好处是只需要维护一份配置,操作简单。

官方logstash镜像

在dockerhub上搜索了到一个即将被废弃的镜像:https://hub.docker.com/_/logstash/。
最新版镜像在es官方进行维护: https://www.elastic.co/guide/en/logstash/current/docker.html
由于直接使用原版镜像时映射到容器内部的pipeline.conf一直提示No permission,明明都已经设置成755,宿主机上并没有logstash这一用户,chown不方便。所以决定对官方镜像稍加修改,再推送到公司内的dockerhub私有docker registry上。

FROM docker.elastic.co/logstash/logstash:5.6.1
MAINTAINER CodingCrush
ENV LANG en_US.UTF-8
# Disable ES monitoring in the official image
ENV XPACK_MONITORING_ENABLED false
# TimeZone: Asia/Shanghai
ENV TZ Asia/Shanghai
ENV PIPELINE_WORKERS 5
# Default user is logstash
USER root

这个Dockerfile很简单,主要设置了User为root,解决conf文件的权限不足问题。另外关闭容器自带的es xpack检查功能,设置时区与pipeline的 worker数量。
然后打个tag,推到私有docker registry上。

docker build -t dockerhub.xxx.net/logstash:5.6.1
docker push dockerhub.xxx.net/logstash:5.6.1

容器便修改好了,在生产集群上可pull。

pipeline.conf

这个简化的配置文件中path应该是一个列表,但目前用了一个占位字符串,发布时动态检测通过sed进行更换,实际项目中的组件配置更多,这里写4个意思一下。
值得注意的是: 通过if else 对path进行匹配时如果出现/,需要进行转义,否则ruby进行正则匹配时会报错。

input {
    file {
        path => LOG_FILES_PLACEHOLDER
        type => "nginx"
        start_position => "beginning"
        sincedb_path => "/data/projects/logstash/data/logstash.db"
        codec=>plain{charset=>"UTF-8"}
    }
}
filter {
    if [path] =~ "component1\/logs\/access" {
        grok & mutate
    } else if [path] =~ "component1\/logs\/error" {
        grok & mutate
    } else if [path] =~ "component2\/logs\/access" {
        grok & mutate
    } else if [path] =~ "component2\/logs\/error" {
        grok & mutate
    }
}
output {
    if [path] =~ "component1\/logs\/access" {
        stdout{codec=>rubydebug}
    }  else if [path] =~ "component1\/logs\/error" {
        stdout{codec=>rubydebug}
    } else if [path] =~ "component2\/logs\/access.log" {
        stdout{codec=>rubydebug}
    } else if [path] =~ "component2\/logs\/error" {
        stdout{codec=>rubydebug}
    }
}

日志文件检测脚本 detect_logfiles.py

检测脚本同样很简单,用os.path.exists进行过滤。往标准输出打一个列表的string。
此处注意,需要用re.escape进行转义,因为后面还需要用sed进行替换。

import os
import sys
import re
paths = [
    "/data/projects/component1/logs/access.log*",
    "/data/projects/component1/logs/error.log",
    "/data/projects/component2/logs/access.log*",
    "/data/projects/component2/logs/error.log*",
    ......
]
available_paths = [path for path in paths if os.path.exists(path.replace("*", ""))]
sys.stdout.write(re.escape(str(available_paths)))

ansible剧本

在发布时通过sed替换占位串,之所以大费周章搞这么个方式,主要因为我们环境比较复杂,将来横向拓展时组件布局不定,多配置文件维护起来挺麻烦。
为什么用shell?而不用ansible的module? 因为我不会,还懒得去学ansible发明的DSL

- name: Modify lostash.conf
  args:
    chdir: "/data/projects/logstash/"
  shell: 'sed -i "s|LOG_FILES_PLACEHOLDER|$(python detect_logfiles.py)|" logstash.conf'
  become: true
  become_method: sudo
- name: Modify docker-compose.yml
  args:
    chdir: "/data/projects/logstash/"
  shell: 'sed -i "s|HOST_PLACEHOLDER|{{ host }}|" docker-compose.yml'
  become: true
  become_method: sudo

docker-compose

挂载整个文件卷,HOST_PLACEHOLDER占位符是因为我希望通过container名字直观看到组件所在的主机名,而避免生产服务器上误操作。

version: '2'
services:
  logstash:
    image: dockerhub.xxx.net/logstash:5.6.1
    volumes:
       - /etc/localtime:/etc/localtime:ro
       - /data/projects/logstash/hosts:/etc/hosts
       - /data/projects:/data/projects
    container_name: HOST_PLACEHOLDER.logstash
    command: logstash -f /data/projects/logstash/logstash.conf --path.data=/data/projects/logstash/data

启动的command时指定工作目录,将运行状态持久化到外部存储上,方便迁移。

标签:容器Ansible 发布于:2019-11-07 06:13:22