Skip to content

Commit

Permalink
Guard launching duplicated fluentd instance with same configuration
Browse files Browse the repository at this point in the history
Before:

  If you launch multiple Fluentd instance with same
  configuration file, it causes a disaster with inconsistent
  processed buffer or pos file.

After:

  Detect fluentd service's main process and fetch FLUENT_CONF.
  if configuration is same as spawned process, abort it.

  It can block the following conditions are met:

  * fluentd is launched as a systemd service.
    configuration file is specified via FLUENT_CONF in fluentd.service.
  * manually try to launch fluentd  with same configuration file like this:

    sudo /usr/sbin/fluentd -c /etc/fluent/fluentd.conf

  Note that following case doen't resolved.

   sudo /opt/fluent/bin/fluentd -c /etc/fluent/fluentd.conf

hus

  Thus running fluentd service and manually try to launch normal

  user case can't be detected.

NOTE: Windows is out of scope in this PR.
  • Loading branch information
kenhys committed Feb 19, 2024
1 parent eada0a7 commit 4017f30
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 1 deletion.
4 changes: 3 additions & 1 deletion fluent-package/Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -1099,11 +1099,13 @@ class BuildTask
configs.concat([
"etc/logrotate.d/#{SERVICE_NAME}",
fluentd_conf_default,
]) unless windows? || macos?
"opt/#{PACKAGE_DIR}/share/conflict_detector.rb"]) unless windows? || macos?
configs.each do |config|
src =
if config == fluentd_conf_default
template_path(fluentd_conf)
elsif config == "opt/#{PACKAGE_DIR}/share/conflict_detector.rb"
template_path("conflict_detector.rb")
else
template_path(config)
end
Expand Down
96 changes: 96 additions & 0 deletions fluent-package/templates/conflict_detector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
require 'optparse'
module Fluent
class ConflictDetector
def self.linux?
/linux/ === RUBY_PLATFORM
end

def self.running_fluentd_conf
# Detect if same configuration is used
unless linux?
return nil
end

IO.popen(["/usr/bin/ps", "-e", "-o", "uid,pid,ppid,cmd"]) do |_io|
_io.readlines.each do |line|
uid, pid, ppid, cmd = line.split(' ', 4)
# skip self and supervisor process
next if Process.pid == pid.to_i or Process.pid == ppid.to_i
if cmd and cmd.chomp.include?("fluentd") and ppid.to_i == 1
# under systemd control
File.open("/proc/#{pid.to_i}/environ") do |file|
conf = file.read.split("\u0000").select { |entry| entry.include?("FLUENT_CONF") }
return conf.first.split('=').last unless conf.empty?
end
end
end
end
return nil
end
end

class FakeOptionParser < OptionParser
attr_reader :config_path
def initialize
@config_path = nil
@opt = OptionParser.new
@opt.on('-c', '--config VAL') { |v|
@config_path = v
}
@opt.on('-s', '--setup DIR')
@opt.on('--dry-run')
@opt.on('--show-plugin-config=PLUGIN')
@opt.on('-p', '--plugin DIR')
@opt.on('-I PATH')
@opt.on('-r NAME')
@opt.on('-d', '--daemon PIDFILE')
@opt.on('--under-supervisor')
@opt.on('--no-supervisor')
@opt.on('--workers NUM')
@opt.on('--user USER')
@opt.on('--group GROUP')
@opt.on('--umask UMASK')
@opt.on('-o', '--log PATH')
@opt.on('--log-rotate-age AGE')
@opt.on('--log-rotate-size BYTES')
@opt.on('--log-event-verbose')
@opt.on('-i', '--inline-config CONFIG_STRING')
@opt.on('-h', '--help')
end

def parse(argv)
@opt.parse(argv)
end

end
end

begin
running_fluentd_conf = Fluent::ConflictDetector.running_fluentd_conf
if ENV["FLUENT_CONF"] and ENV["FLUENT_CONF"] == running_fluentd_conf
# /usr/sbin/fluentd sets FLUENT_CONF=/etc/fluent/fluentd.conf by default
# If it matches with running other instance, abort it
puts "Error: can't start duplicate Fluentd instance with same #{ENV['FLUENT_CONF']}"
exit 2
else
# /opt/fluent/bin/fluentd does not set FLUENT_CONF=/etc/fluent/fluentd.conf,
# if -c option is given from command line, check and abort it.
unless ARGV.empty?
# preflight with dummy parser
opt = Fluent::FakeOptionParser.new
begin
opt.parse(ARGV)
if opt.config_path and opt.config_path == running_fluentd_conf
puts "Error: can't start duplicate Fluentd instance with same #{running_fluentd_conf}"
exit 2
end
rescue
end
end
end
rescue Errno::EACCES
# e.g. unprivileged access error, can't detect duplicated instance from command line parameter.
rescue Errno::ENOENT
# e.g. can't detect duplicated instance from ps.
end

2 changes: 2 additions & 0 deletions fluent-package/templates/usr/sbin/fluentd.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ if ARGV.include?("--version")
puts "fluent-package #{PACKAGE_VERSION} fluentd #{Fluent::VERSION} (#{FLUENTD_REVISION})"
exit 0
end

load "<%= install_path %>/share/conflict_detector.rb"
load "<%= install_path %>/bin/fluentd"
3 changes: 3 additions & 0 deletions fluent-package/yum/fluent-package.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ for man in `find %{buildroot} -type f -wholename '*/man/man[1-9]/*.[1-9]'`; do
gzip ${man}
done

# Patch against generated file
sed -e '/^if Gem.respond_to?/i load "/opt/fluent/share/conflict_detector.rb"' -i'' %{buildroot}/opt/fluent/bin/fluentd

cd -
mkdir -p %{buildroot}%{_localstatedir}/run/@PACKAGE_DIR@
mkdir -p %{buildroot}%{_localstatedir}/log/@PACKAGE_DIR@
Expand Down

0 comments on commit 4017f30

Please sign in to comment.