fluent-plugin-kubernetes_metadata_filter 2.5.3 → 2.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
5
  # Kubernetes metadata
@@ -21,31 +23,20 @@ module KubernetesMetadata
21
23
  def get_pod_metadata(key, namespace_name, pod_name, record_create_time, batch_miss_cache)
22
24
  metadata = {}
23
25
  ids = @id_cache[key]
24
- if !ids.nil?
26
+ if ids.nil?
25
27
  # FAST PATH
26
28
  # Cache hit, fetch metadata from the cache
27
- metadata = @cache.fetch(ids[:pod_id]) do
28
- @stats.bump(:pod_cache_miss)
29
- m = fetch_pod_metadata(namespace_name, pod_name)
30
- (m.nil? || m.empty?) ? {'pod_id'=>ids[:pod_id]} : m
31
- end
32
- metadata.merge!(@namespace_cache.fetch(ids[:namespace_id]) do
33
- @stats.bump(:namespace_cache_miss)
34
- m = fetch_namespace_metadata(namespace_name) unless @skip_namespace_metadata
35
- (m.nil? || m.empty?) ? {'namespace_id'=>ids[:namespace_id]} : m
36
- end)
37
- else
38
- # SLOW PATH
39
29
  @stats.bump(:id_cache_miss)
40
30
  return batch_miss_cache["#{namespace_name}_#{pod_name}"] if batch_miss_cache.key?("#{namespace_name}_#{pod_name}")
31
+
41
32
  pod_metadata = fetch_pod_metadata(namespace_name, pod_name)
42
33
  if @skip_namespace_metadata
43
- ids = { :pod_id=> pod_metadata['pod_id'] }
34
+ ids = { pod_id: pod_metadata['pod_id'] }
44
35
  @id_cache[key] = ids
45
36
  return pod_metadata
46
37
  end
47
38
  namespace_metadata = fetch_namespace_metadata(namespace_name)
48
- ids = { :pod_id=> pod_metadata['pod_id'], :namespace_id => namespace_metadata['namespace_id'] }
39
+ ids = { pod_id: pod_metadata['pod_id'], namespace_id: namespace_metadata['namespace_id'] }
49
40
  if !ids[:pod_id].nil? && !ids[:namespace_id].nil?
50
41
  # pod found and namespace found
51
42
  metadata = pod_metadata
@@ -59,7 +50,7 @@ module KubernetesMetadata
59
50
  # namespace is older then record for pod
60
51
  ids[:pod_id] = key
61
52
  metadata = @cache.fetch(ids[:pod_id]) do
62
- m = { 'pod_id' => ids[:pod_id] }
53
+ { 'pod_id' => ids[:pod_id] }
63
54
  end
64
55
  end
65
56
  metadata.merge!(namespace_metadata)
@@ -87,12 +78,25 @@ module KubernetesMetadata
87
78
  end
88
79
  end
89
80
  @id_cache[key] = ids unless batch_miss_cache.key?("#{namespace_name}_#{pod_name}")
81
+ else
82
+ # SLOW PATH
83
+ metadata = @cache.fetch(ids[:pod_id]) do
84
+ @stats.bump(:pod_cache_miss)
85
+ m = fetch_pod_metadata(namespace_name, pod_name)
86
+ m.nil? || m.empty? ? { 'pod_id' => ids[:pod_id] } : m
87
+ end
88
+ metadata.merge!(@namespace_cache.fetch(ids[:namespace_id]) do
89
+ m = unless @skip_namespace_metadata
90
+ @stats.bump(:namespace_cache_miss)
91
+ fetch_namespace_metadata(namespace_name)
92
+ end
93
+ m.nil? || m.empty? ? { 'namespace_id' => ids[:namespace_id] } : m
94
+ end)
90
95
  end
91
96
 
92
97
  # remove namespace info that is only used for comparison
93
98
  metadata.delete('creation_timestamp')
94
- metadata.delete_if{|k,v| v.nil?}
99
+ metadata.delete_if { |_k, v| v.nil? }
95
100
  end
96
-
97
101
  end
98
102
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
5
  # Kubernetes metadata
@@ -18,9 +20,8 @@
18
20
  #
19
21
  module KubernetesMetadata
20
22
  module Common
21
-
22
23
  class GoneError < StandardError
23
- def initialize(msg="410 Gone")
24
+ def initialize(msg = '410 Gone')
24
25
  super
25
26
  end
26
27
  end
@@ -38,13 +39,13 @@ module KubernetesMetadata
38
39
  end
39
40
 
40
41
  def parse_namespace_metadata(namespace_object)
41
- labels = String.new
42
+ labels = ''
42
43
  labels = syms_to_strs(namespace_object[:metadata][:labels].to_h) unless @skip_labels
43
44
 
44
45
  annotations = match_annotations(syms_to_strs(namespace_object[:metadata][:annotations].to_h))
45
46
  if @de_dot
46
- self.de_dot!(labels) unless @skip_labels
47
- self.de_dot!(annotations)
47
+ de_dot!(labels) unless @skip_labels
48
+ de_dot!(annotations)
48
49
  end
49
50
  kubernetes_metadata = {
50
51
  'namespace_id' => namespace_object[:metadata][:uid],
@@ -56,43 +57,44 @@ module KubernetesMetadata
56
57
  end
57
58
 
58
59
  def parse_pod_metadata(pod_object)
59
- labels = String.new
60
+ labels = ''
60
61
  labels = syms_to_strs(pod_object[:metadata][:labels].to_h) unless @skip_labels
61
62
 
62
63
  annotations = match_annotations(syms_to_strs(pod_object[:metadata][:annotations].to_h))
63
64
  if @de_dot
64
- self.de_dot!(labels) unless @skip_labels
65
- self.de_dot!(annotations)
65
+ de_dot!(labels) unless @skip_labels
66
+ de_dot!(annotations)
66
67
  end
67
68
 
68
69
  # collect container information
69
70
  container_meta = {}
70
71
  begin
71
- pod_object[:status][:containerStatuses].each do|container_status|
72
+ pod_object[:status][:containerStatuses].each do |container_status|
72
73
  # get plain container id (eg. docker://hash -> hash)
73
- container_id = container_status[:containerID].sub /^[-_a-zA-Z0-9]+:\/\//, ''
74
- unless @skip_container_metadata
75
- container_meta[container_id] = {
76
- 'name' => container_status[:name],
77
- 'image' => container_status[:image],
78
- 'image_id' => container_status[:imageID]
79
- }
80
- else
81
- container_meta[container_id] = {
82
- 'name' => container_status[:name]
83
- }
84
- end
74
+ container_id = container_status[:containerID].sub(%r{^[-_a-zA-Z0-9]+://}, '')
75
+ container_meta[container_id] = if @skip_container_metadata
76
+ {
77
+ 'name' => container_status[:name]
78
+ }
79
+ else
80
+ {
81
+ 'name' => container_status[:name],
82
+ 'image' => container_status[:image],
83
+ 'image_id' => container_status[:imageID]
84
+ }
85
+ end
85
86
  end
86
- rescue
87
+ rescue StandardError
87
88
  log.debug("parsing container meta information failed for: #{pod_object[:metadata][:namespace]}/#{pod_object[:metadata][:name]} ")
88
89
  end
89
90
 
90
91
  kubernetes_metadata = {
91
- 'namespace_name' => pod_object[:metadata][:namespace],
92
- 'pod_id' => pod_object[:metadata][:uid],
93
- 'pod_name' => pod_object[:metadata][:name],
94
- 'containers' => syms_to_strs(container_meta),
95
- 'host' => pod_object[:spec][:nodeName]
92
+ 'namespace_name' => pod_object[:metadata][:namespace],
93
+ 'pod_id' => pod_object[:metadata][:uid],
94
+ 'pod_name' => pod_object[:metadata][:name],
95
+ 'pod_ip' => pod_object[:status][:podIP],
96
+ 'containers' => syms_to_strs(container_meta),
97
+ 'host' => pod_object[:spec][:nodeName]
96
98
  }
97
99
  kubernetes_metadata['annotations'] = annotations unless annotations.empty?
98
100
  kubernetes_metadata['labels'] = labels unless labels.empty?
@@ -102,7 +104,7 @@ module KubernetesMetadata
102
104
 
103
105
  def syms_to_strs(hsh)
104
106
  newhsh = {}
105
- hsh.each_pair do |kk,vv|
107
+ hsh.each_pair do |kk, vv|
106
108
  if vv.is_a?(Hash)
107
109
  vv = syms_to_strs(vv)
108
110
  end
@@ -114,6 +116,5 @@ module KubernetesMetadata
114
116
  end
115
117
  newhsh
116
118
  end
117
-
118
119
  end
119
120
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
5
  # Kubernetes metadata
@@ -19,17 +21,16 @@
19
21
  require 'lru_redux'
20
22
  module KubernetesMetadata
21
23
  class Stats
22
-
23
24
  def initialize
24
25
  @stats = ::LruRedux::TTL::ThreadSafeCache.new(1000, 3600)
25
26
  end
26
27
 
27
28
  def bump(key)
28
- @stats[key] = @stats.getset(key) { 0 } + 1
29
+ @stats[key] = @stats.getset(key) { 0 } + 1
29
30
  end
30
31
 
31
32
  def set(key, value)
32
- @stats[key] = value
33
+ @stats[key] = value
33
34
  end
34
35
 
35
36
  def [](key)
@@ -37,10 +38,9 @@ module KubernetesMetadata
37
38
  end
38
39
 
39
40
  def to_s
40
- "stats - " + [].tap do |a|
41
- @stats.each {|k,v| a << "#{k.to_s}: #{v}"}
41
+ 'stats - ' + [].tap do |a|
42
+ @stats.each { |k, v| a << "#{k}: #{v}" }
42
43
  end.join(', ')
43
44
  end
44
-
45
45
  end
46
46
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
5
+ # Kubernetes metadata
6
+ #
7
+ # Copyright 2021 Red Hat, Inc.
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+ require 'kubeclient'
22
+
23
+ module KubernetesMetadata
24
+ module TestApiAdapter
25
+
26
+ def api_valid?
27
+ true
28
+ end
29
+ def get_namespace(namespace_name)
30
+ return {
31
+ metadata: {
32
+ name: namespace_name,
33
+ uid: namespace_name + 'uuid',
34
+ labels: {
35
+ foo_ns: 'bar_ns'
36
+ }
37
+ }
38
+ }
39
+ end
40
+
41
+ def get_pod(pod_name, namespace_name)
42
+ return {
43
+ metadata: {
44
+ name: pod_name,
45
+ namespace: namespace_name,
46
+ uid: namespace_name + namespace_name + "uuid",
47
+ labels: {
48
+ foo: 'bar'
49
+ }
50
+ },
51
+ spec: {
52
+ nodeName: 'aNodeName',
53
+ containers: [{
54
+ name: 'foo',
55
+ image: 'bar'
56
+ }, {
57
+ name: 'bar',
58
+ image: 'foo'
59
+ }]
60
+ },
61
+ status: {
62
+ podIP: '172.17.0.8'
63
+ }
64
+ }
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
5
+ # Kubernetes metadata
6
+ #
7
+ # Copyright 2021 Red Hat, Inc.
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+ module KubernetesMetadata
22
+ module Util
23
+ def create_time_from_record(record, internal_time)
24
+ time_key = @time_fields.detect { |ii| record.key?(ii) }
25
+ time = record[time_key]
26
+ if time.nil? || time.is_a?(String) && time.chop.empty?
27
+ # `internal_time` is a Fluent::EventTime, it can't compare with Time.
28
+ return Time.at(internal_time.to_f)
29
+ end
30
+
31
+ if ['_SOURCE_REALTIME_TIMESTAMP', '__REALTIME_TIMESTAMP'].include?(time_key)
32
+ timei = time.to_i
33
+ return Time.at(timei / 1_000_000, timei % 1_000_000)
34
+ end
35
+ return Time.at(time) if time.is_a?(Numeric)
36
+
37
+ Time.parse(time)
38
+ end
39
+ end
40
+ end
41
+
42
+ #https://stackoverflow.com/questions/5622435/how-do-i-convert-a-ruby-class-name-to-a-underscore-delimited-symbol
43
+ class String
44
+ def underscore
45
+ word = self.dup
46
+ word.gsub!(/::/, '_')
47
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
48
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
49
+ word.tr!("-", "_")
50
+ word.downcase!
51
+ word
52
+ end
53
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
5
  # Kubernetes metadata
@@ -21,7 +23,6 @@ require_relative 'kubernetes_metadata_common'
21
23
 
22
24
  module KubernetesMetadata
23
25
  module WatchNamespaces
24
-
25
26
  include ::KubernetesMetadata::Common
26
27
 
27
28
  def set_up_namespace_thread
@@ -36,48 +37,47 @@ module KubernetesMetadata
36
37
  # processing will be swallowed and retried. These failures /
37
38
  # exceptions could be caused by Kubernetes API being temporarily
38
39
  # down. We assume the configuration is correct at this point.
39
- while true
40
- begin
41
- namespace_watcher ||= get_namespaces_and_start_watcher
42
- process_namespace_watcher_notices(namespace_watcher)
43
- rescue GoneError => e
44
- # Expected error. Quietly go back through the loop in order to
45
- # start watching from the latest resource versions
46
- @stats.bump(:namespace_watch_gone_errors)
47
- log.info("410 Gone encountered. Restarting namespace watch to reset resource versions.", e)
40
+ loop do
41
+ namespace_watcher ||= get_namespaces_and_start_watcher
42
+ process_namespace_watcher_notices(namespace_watcher)
43
+ rescue GoneError => e
44
+ # Expected error. Quietly go back through the loop in order to
45
+ # start watching from the latest resource versions
46
+ @stats.bump(:namespace_watch_gone_errors)
47
+ log.info('410 Gone encountered. Restarting namespace watch to reset resource versions.', e)
48
+ namespace_watcher = nil
49
+ rescue StandardError => e
50
+ @stats.bump(:namespace_watch_failures)
51
+ if Thread.current[:namespace_watch_retry_count] < @watch_retry_max_times
52
+ # Instead of raising exceptions and crashing Fluentd, swallow
53
+ # the exception and reset the watcher.
54
+ log.info(
55
+ 'Exception encountered parsing namespace watch event. ' \
56
+ 'The connection might have been closed. Sleeping for ' \
57
+ "#{Thread.current[:namespace_watch_retry_backoff_interval]} " \
58
+ 'seconds and resetting the namespace watcher.', e
59
+ )
60
+ sleep(Thread.current[:namespace_watch_retry_backoff_interval])
61
+ Thread.current[:namespace_watch_retry_count] += 1
62
+ Thread.current[:namespace_watch_retry_backoff_interval] *= @watch_retry_exponential_backoff_base
48
63
  namespace_watcher = nil
49
- rescue => e
50
- @stats.bump(:namespace_watch_failures)
51
- if Thread.current[:namespace_watch_retry_count] < @watch_retry_max_times
52
- # Instead of raising exceptions and crashing Fluentd, swallow
53
- # the exception and reset the watcher.
54
- log.info(
55
- "Exception encountered parsing namespace watch event. " \
56
- "The connection might have been closed. Sleeping for " \
57
- "#{Thread.current[:namespace_watch_retry_backoff_interval]} " \
58
- "seconds and resetting the namespace watcher.", e)
59
- sleep(Thread.current[:namespace_watch_retry_backoff_interval])
60
- Thread.current[:namespace_watch_retry_count] += 1
61
- Thread.current[:namespace_watch_retry_backoff_interval] *= @watch_retry_exponential_backoff_base
62
- namespace_watcher = nil
63
- else
64
- # Since retries failed for many times, log as errors instead
65
- # of info and raise exceptions and trigger Fluentd to restart.
66
- message =
67
- "Exception encountered parsing namespace watch event. The " \
68
- "connection might have been closed. Retried " \
69
- "#{@watch_retry_max_times} times yet still failing. Restarting."
70
- log.error(message, e)
71
- raise Fluent::UnrecoverableError.new(message)
72
- end
64
+ else
65
+ # Since retries failed for many times, log as errors instead
66
+ # of info and raise exceptions and trigger Fluentd to restart.
67
+ message =
68
+ 'Exception encountered parsing namespace watch event. The ' \
69
+ 'connection might have been closed. Retried ' \
70
+ "#{@watch_retry_max_times} times yet still failing. Restarting."
71
+ log.error(message, e)
72
+ raise Fluent::UnrecoverableError, message
73
73
  end
74
74
  end
75
75
  end
76
76
 
77
77
  def start_namespace_watch
78
78
  get_namespaces_and_start_watcher
79
- rescue => e
80
- message = "start_namespace_watch: Exception encountered setting up " \
79
+ rescue StandardError => e
80
+ message = 'start_namespace_watch: Exception encountered setting up ' \
81
81
  "namespace watch from Kubernetes API #{@apiVersion} endpoint " \
82
82
  "#{@kubernetes_url}: #{e.message}"
83
83
  message += " (#{e.response})" if e.respond_to?(:response)
@@ -90,7 +90,7 @@ module KubernetesMetadata
90
90
  # starting from that resourceVersion.
91
91
  def get_namespaces_and_start_watcher
92
92
  options = {
93
- resource_version: '0' # Fetch from API server cache instead of etcd quorum read
93
+ resource_version: '0' # Fetch from API server cache instead of etcd quorum read
94
94
  }
95
95
  namespaces = @client.get_namespaces(options)
96
96
  namespaces[:items].each do |namespace|
@@ -118,35 +118,35 @@ module KubernetesMetadata
118
118
  def process_namespace_watcher_notices(watcher)
119
119
  watcher.each do |notice|
120
120
  case notice[:type]
121
- when 'MODIFIED'
122
- reset_namespace_watch_retry_stats
123
- cache_key = notice[:object][:metadata][:uid]
124
- cached = @namespace_cache[cache_key]
125
- if cached
126
- @namespace_cache[cache_key] = parse_namespace_metadata(notice[:object])
127
- @stats.bump(:namespace_cache_watch_updates)
128
- else
129
- @stats.bump(:namespace_cache_watch_misses)
130
- end
131
- when 'DELETED'
132
- reset_namespace_watch_retry_stats
133
- # ignore and let age out for cases where
134
- # deleted but still processing logs
135
- @stats.bump(:namespace_cache_watch_deletes_ignored)
136
- when 'ERROR'
137
- if notice[:object] && notice[:object][:code] == 410
138
- @stats.bump(:namespace_watch_gone_notices)
139
- raise GoneError
140
- else
141
- @stats.bump(:namespace_watch_error_type_notices)
142
- message = notice[:object][:message] if notice[:object] && notice[:object][:message]
143
- raise "Error while watching namespaces: #{message}"
144
- end
121
+ when 'MODIFIED'
122
+ reset_namespace_watch_retry_stats
123
+ cache_key = notice[:object][:metadata][:uid]
124
+ cached = @namespace_cache[cache_key]
125
+ if cached
126
+ @namespace_cache[cache_key] = parse_namespace_metadata(notice[:object])
127
+ @stats.bump(:namespace_cache_watch_updates)
145
128
  else
146
- reset_namespace_watch_retry_stats
147
- # Don't pay attention to creations, since the created namespace may not
148
- # be used by any namespace on this node.
149
- @stats.bump(:namespace_cache_watch_ignored)
129
+ @stats.bump(:namespace_cache_watch_misses)
130
+ end
131
+ when 'DELETED'
132
+ reset_namespace_watch_retry_stats
133
+ # ignore and let age out for cases where
134
+ # deleted but still processing logs
135
+ @stats.bump(:namespace_cache_watch_deletes_ignored)
136
+ when 'ERROR'
137
+ if notice[:object] && notice[:object][:code] == 410
138
+ @stats.bump(:namespace_watch_gone_notices)
139
+ raise GoneError
140
+ else
141
+ @stats.bump(:namespace_watch_error_type_notices)
142
+ message = notice[:object][:message] if notice[:object] && notice[:object][:message]
143
+ raise "Error while watching namespaces: #{message}"
144
+ end
145
+ else
146
+ reset_namespace_watch_retry_stats
147
+ # Don't pay attention to creations, since the created namespace may not
148
+ # be used by any namespace on this node.
149
+ @stats.bump(:namespace_cache_watch_ignored)
150
150
  end
151
151
  end
152
152
  end