fluent-plugin-kubernetes_metadata_filter 2.5.2 → 2.11.1

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
@@ -20,6 +22,7 @@
20
22
  require_relative 'kubernetes_metadata_cache_strategy'
21
23
  require_relative 'kubernetes_metadata_common'
22
24
  require_relative 'kubernetes_metadata_stats'
25
+ require_relative 'kubernetes_metadata_util'
23
26
  require_relative 'kubernetes_metadata_watch_namespaces'
24
27
  require_relative 'kubernetes_metadata_watch_pods'
25
28
 
@@ -33,6 +36,7 @@ module Fluent::Plugin
33
36
 
34
37
  include KubernetesMetadata::CacheStrategy
35
38
  include KubernetesMetadata::Common
39
+ include KubernetesMetadata::Util
36
40
  include KubernetesMetadata::WatchNamespaces
37
41
  include KubernetesMetadata::WatchPods
38
42
 
@@ -47,13 +51,23 @@ module Fluent::Plugin
47
51
  config_param :client_key, :string, default: nil
48
52
  config_param :ca_file, :string, default: nil
49
53
  config_param :verify_ssl, :bool, default: true
50
- config_param :tag_to_kubernetes_name_regexp,
51
- :string,
52
- :default => 'var\.log\.containers\.(?<pod_name>[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)_(?<namespace>[^_]+)_(?<container_name>.+)-(?<docker_id>[a-z0-9]{64})\.log$'
54
+
55
+ REGEX_VAR_LOG_PODS = '(var\.log\.pods)\.(?<namespace>[^_]+)_(?<pod_name>[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)_(?<pod_uuid>[a-z0-9-]*)\.(?<container_name>.+)\..*\.log$'
56
+ REGEX_VAR_LOG_CONTAINERS = '(var\.log\.containers)\.(?<pod_name>[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)_(?<namespace>[^_]+)_(?<container_name>.+)-(?<docker_id>[a-z0-9]{64})\.log$'
57
+
58
+ #tag_to_kubernetes_name_regexp which must include named capture groups:
59
+ # namespace - The namespace in which the pod is deployed
60
+ # pod_name - The pod name
61
+ # container_name - The name of the container
62
+ # pod_uuid (/var/log/pods) | docker_id (/var/log/containers) - Unique identifier used in caching of either pod_uuid or the container hash
63
+ config_param :tag_to_kubernetes_name_regexp, :string, default: "(#{REGEX_VAR_LOG_PODS}|#{REGEX_VAR_LOG_CONTAINERS})"
64
+
53
65
  config_param :bearer_token_file, :string, default: nil
54
66
  config_param :secret_dir, :string, default: '/var/run/secrets/kubernetes.io/serviceaccount'
55
67
  config_param :de_dot, :bool, default: true
56
68
  config_param :de_dot_separator, :string, default: '_'
69
+ config_param :de_slash, :bool, default: false
70
+ config_param :de_slash_separator, :string, default: '__'
57
71
  # if reading from the journal, the record will contain the following fields in the following
58
72
  # format:
59
73
  # CONTAINER_NAME=k8s_$containername.$containerhash_$podname_$namespacename_$poduuid_$rand32bitashex
@@ -65,7 +79,7 @@ module Fluent::Plugin
65
79
  # parse format is defined here: https://github.com/kubernetes/kubernetes/blob/release-1.6/pkg/kubelet/dockertools/docker.go#L317
66
80
  config_param :container_name_to_kubernetes_regexp,
67
81
  :string,
68
- :default => '^(?<name_prefix>[^_]+)_(?<container_name>[^\._]+)(\.(?<container_hash>[^_]+))?_(?<pod_name>[^_]+)_(?<namespace>[^_]+)_[^_]+_[^_]+$'
82
+ default: '^(?<name_prefix>[^_]+)_(?<container_name>[^\._]+)(\.(?<container_hash>[^_]+))?_(?<pod_name>[^_]+)_(?<namespace>[^_]+)_[^_]+_[^_]+$'
69
83
 
70
84
  config_param :annotation_match, :array, default: []
71
85
  config_param :stats_interval, :integer, default: 30
@@ -81,6 +95,11 @@ module Fluent::Plugin
81
95
  config_param :skip_container_metadata, :bool, default: false
82
96
  config_param :skip_master_url, :bool, default: false
83
97
  config_param :skip_namespace_metadata, :bool, default: false
98
+
99
+ # A classname in the form of Test::APIAdapter which will try
100
+ # to be resolved from a relative named file 'test_api_adapter'
101
+ config_param :test_api_adapter, :string, default: nil
102
+
84
103
  # The time interval in seconds for retry backoffs when watch connections fail.
85
104
  config_param :watch_retry_interval, :integer, default: 1
86
105
  # The base number of exponential backoff for retries.
@@ -89,36 +108,36 @@ module Fluent::Plugin
89
108
  config_param :watch_retry_max_times, :integer, default: 10
90
109
 
91
110
  def fetch_pod_metadata(namespace_name, pod_name)
92
- log.trace("fetching pod metadata: #{namespace_name}/#{pod_name}") if log.trace?
93
- begin
94
- metadata = @client.get_pod(pod_name, namespace_name)
95
- unless metadata
96
- log.trace("no metadata returned for: #{namespace_name}/#{pod_name}") if log.trace?
97
- @stats.bump(:pod_cache_api_nil_not_found)
111
+ log.trace("fetching pod metadata: #{namespace_name}/#{pod_name}")
112
+ options = {
113
+ resource_version: '0' # Fetch from API server cache instead of etcd quorum read
114
+ }
115
+ pod_object = @client.get_pod(pod_name, namespace_name, options)
116
+ log.trace("raw metadata for #{namespace_name}/#{pod_name}: #{pod_object}")
117
+ metadata = parse_pod_metadata(pod_object)
118
+ @stats.bump(:pod_cache_api_updates)
119
+ log.trace("parsed metadata for #{namespace_name}/#{pod_name}: #{metadata}")
120
+ @cache[metadata['pod_id']] = metadata
121
+ rescue KubeException => e
122
+ if e.error_code == 401
123
+ # recreate client to refresh token
124
+ log.info("Encountered '401 Unauthorized' exception, recreating client to refresh token")
125
+ create_client()
98
126
  else
99
- begin
100
- log.trace("raw metadata for #{namespace_name}/#{pod_name}: #{metadata}") if log.trace?
101
- metadata = parse_pod_metadata(metadata)
102
- @stats.bump(:pod_cache_api_updates)
103
- log.trace("parsed metadata for #{namespace_name}/#{pod_name}: #{metadata}") if log.trace?
104
- @cache[metadata['pod_id']] = metadata
105
- return metadata
106
- rescue Exception=>e
107
- log.debug(e)
108
- @stats.bump(:pod_cache_api_nil_bad_resp_payload)
109
- log.trace("returning empty metadata for #{namespace_name}/#{pod_name} due to error '#{e}'") if log.trace?
110
- end
127
+ log.error "Exception '#{e}' encountered fetching pod metadata from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}"
128
+ @stats.bump(:pod_cache_api_nil_error)
111
129
  end
112
- rescue Exception=>e
130
+ {}
131
+ rescue StandardError => e
113
132
  @stats.bump(:pod_cache_api_nil_error)
114
- log.debug "Exception '#{e}' encountered fetching pod metadata from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}"
133
+ log.error "Exception '#{e}' encountered fetching pod metadata from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}"
134
+ {}
115
135
  end
116
- {}
117
- end
118
136
 
119
137
  def dump_stats
120
138
  @curr_time = Time.now
121
139
  return if @curr_time.to_i - @prev_time.to_i < @stats_interval
140
+
122
141
  @prev_time = @curr_time
123
142
  @stats.set(:pod_cache_size, @cache.count)
124
143
  @stats.set(:namespace_cache_size, @namespace_cache.count) if @namespace_cache
@@ -131,55 +150,56 @@ module Fluent::Plugin
131
150
  end
132
151
 
133
152
  def fetch_namespace_metadata(namespace_name)
134
- log.trace("fetching namespace metadata: #{namespace_name}") if log.trace?
135
- begin
136
- metadata = @client.get_namespace(namespace_name)
137
- unless metadata
138
- log.trace("no metadata returned for: #{namespace_name}") if log.trace?
139
- @stats.bump(:namespace_cache_api_nil_not_found)
153
+ log.trace("fetching namespace metadata: #{namespace_name}")
154
+ options = {
155
+ resource_version: '0' # Fetch from API server cache instead of etcd quorum read
156
+ }
157
+ namespace_object = @client.get_namespace(namespace_name, nil, options)
158
+ log.trace("raw metadata for #{namespace_name}: #{namespace_object}")
159
+ metadata = parse_namespace_metadata(namespace_object)
160
+ @stats.bump(:namespace_cache_api_updates)
161
+ log.trace("parsed metadata for #{namespace_name}: #{metadata}")
162
+ @namespace_cache[metadata['namespace_id']] = metadata
163
+ rescue KubeException => e
164
+ if e.error_code == 401
165
+ # recreate client to refresh token
166
+ log.info("Encountered '401 Unauthorized' exception, recreating client to refresh token")
167
+ create_client()
140
168
  else
141
- begin
142
- log.trace("raw metadata for #{namespace_name}: #{metadata}") if log.trace?
143
- metadata = parse_namespace_metadata(metadata)
144
- @stats.bump(:namespace_cache_api_updates)
145
- log.trace("parsed metadata for #{namespace_name}: #{metadata}") if log.trace?
146
- @namespace_cache[metadata['namespace_id']] = metadata
147
- return metadata
148
- rescue Exception => e
149
- log.debug(e)
150
- @stats.bump(:namespace_cache_api_nil_bad_resp_payload)
151
- log.trace("returning empty metadata for #{namespace_name} due to error '#{e}'") if log.trace?
152
- end
169
+ log.error "Exception '#{e}' encountered fetching namespace metadata from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}"
170
+ @stats.bump(:namespace_cache_api_nil_error)
153
171
  end
154
- rescue Exception => kube_error
172
+ {}
173
+ rescue StandardError => e
155
174
  @stats.bump(:namespace_cache_api_nil_error)
156
- log.debug "Exception '#{kube_error}' encountered fetching namespace metadata from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}"
157
- end
158
- {}
175
+ log.error "Exception '#{e}' encountered fetching namespace metadata from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}"
176
+ {}
159
177
  end
160
178
 
161
179
  def initialize
162
180
  super
163
181
  @prev_time = Time.now
182
+ @ssl_options = {}
183
+ @auth_options = {}
164
184
  end
165
185
 
166
186
  def configure(conf)
167
187
  super
168
188
 
169
- def log.trace?
170
- level == Fluent::Log::LEVEL_TRACE
171
- end
172
-
173
189
  require 'kubeclient'
174
190
  require 'lru_redux'
175
191
  @stats = KubernetesMetadata::Stats.new
176
192
 
177
- if @de_dot && (@de_dot_separator =~ /\./).present?
193
+ if @de_dot && @de_dot_separator.include?('.')
178
194
  raise Fluent::ConfigError, "Invalid de_dot_separator: cannot be or contain '.'"
179
195
  end
180
196
 
197
+ if @de_slash && @de_slash_separator.include?('/')
198
+ raise Fluent::ConfigError, "Invalid de_slash_separator: cannot be or contain '/'"
199
+ end
200
+
181
201
  if @cache_ttl < 0
182
- log.info "Setting the cache TTL to :none because it was <= 0"
202
+ log.info 'Setting the cache TTL to :none because it was <= 0'
183
203
  @cache_ttl = :none
184
204
  end
185
205
 
@@ -193,15 +213,16 @@ module Fluent::Plugin
193
213
  @namespace_cache = LruRedux::TTL::ThreadSafeCache.new(@cache_size, @cache_ttl)
194
214
 
195
215
  @tag_to_kubernetes_name_regexp_compiled = Regexp.compile(@tag_to_kubernetes_name_regexp)
216
+
196
217
  @container_name_to_kubernetes_regexp_compiled = Regexp.compile(@container_name_to_kubernetes_regexp)
197
218
 
198
219
  # Use Kubernetes default service account if we're in a pod.
199
220
  if @kubernetes_url.nil?
200
- log.debug "Kubernetes URL is not set - inspecting environ"
221
+ log.debug 'Kubernetes URL is not set - inspecting environ'
201
222
 
202
223
  env_host = ENV['KUBERNETES_SERVICE_HOST']
203
224
  env_port = ENV['KUBERNETES_SERVICE_PORT']
204
- if env_host.present? && env_port.present?
225
+ if present?(env_host) && present?(env_port)
205
226
  if env_host =~ Resolv::IPv6::Regex
206
227
  # Brackets are needed around IPv6 addresses
207
228
  env_host = "[#{env_host}]"
@@ -209,7 +230,7 @@ module Fluent::Plugin
209
230
  @kubernetes_url = "https://#{env_host}:#{env_port}/api"
210
231
  log.debug "Kubernetes URL is now '#{@kubernetes_url}'"
211
232
  else
212
- log.debug "No Kubernetes URL could be found in config or environ"
233
+ log.debug 'No Kubernetes URL could be found in config or environ'
213
234
  end
214
235
  end
215
236
 
@@ -219,24 +240,23 @@ module Fluent::Plugin
219
240
  ca_cert = File.join(@secret_dir, K8_POD_CA_CERT)
220
241
  pod_token = File.join(@secret_dir, K8_POD_TOKEN)
221
242
 
222
- if !@ca_file.present? and File.exist?(ca_cert)
243
+ if !present?(@ca_file) && File.exist?(ca_cert)
223
244
  log.debug "Found CA certificate: #{ca_cert}"
224
245
  @ca_file = ca_cert
225
246
  end
226
247
 
227
- if !@bearer_token_file.present? and File.exist?(pod_token)
248
+ if !present?(@bearer_token_file) && File.exist?(pod_token)
228
249
  log.debug "Found pod token: #{pod_token}"
229
250
  @bearer_token_file = pod_token
230
251
  end
231
252
  end
232
253
 
233
- if @kubernetes_url.present?
234
-
235
- ssl_options = {
236
- client_cert: @client_cert.present? ? OpenSSL::X509::Certificate.new(File.read(@client_cert)) : nil,
237
- client_key: @client_key.present? ? OpenSSL::PKey::RSA.new(File.read(@client_key)) : nil,
238
- ca_file: @ca_file,
239
- verify_ssl: @verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
254
+ if present?(@kubernetes_url)
255
+ @ssl_options = {
256
+ client_cert: present?(@client_cert) ? OpenSSL::X509::Certificate.new(File.read(@client_cert)) : nil,
257
+ client_key: present?(@client_key) ? OpenSSL::PKey::RSA.new(File.read(@client_key)) : nil,
258
+ ca_file: @ca_file,
259
+ verify_ssl: @verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
240
260
  }
241
261
 
242
262
  if @ssl_partial_chain
@@ -244,39 +264,43 @@ module Fluent::Plugin
244
264
  require 'openssl'
245
265
  ssl_store = OpenSSL::X509::Store.new
246
266
  ssl_store.set_default_paths
247
- if defined? OpenSSL::X509::V_FLAG_PARTIAL_CHAIN
248
- flagval = OpenSSL::X509::V_FLAG_PARTIAL_CHAIN
249
- else
250
- # this version of ruby does not define OpenSSL::X509::V_FLAG_PARTIAL_CHAIN
251
- flagval = 0x80000
252
- end
267
+ flagval = if defined? OpenSSL::X509::V_FLAG_PARTIAL_CHAIN
268
+ OpenSSL::X509::V_FLAG_PARTIAL_CHAIN
269
+ else
270
+ # this version of ruby does not define OpenSSL::X509::V_FLAG_PARTIAL_CHAIN
271
+ 0x80000
272
+ end
253
273
  ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL | flagval
254
- ssl_options[:cert_store] = ssl_store
274
+ @ssl_options[:cert_store] = ssl_store
255
275
  end
256
276
 
257
- auth_options = {}
258
-
259
- if @bearer_token_file.present?
260
- bearer_token = File.read(@bearer_token_file)
261
- auth_options[:bearer_token] = bearer_token
277
+ if present?(@bearer_token_file)
278
+ @auth_options[:bearer_token_file] = @bearer_token_file
262
279
  end
263
280
 
264
- log.debug "Creating K8S client"
265
- @client = Kubeclient::Client.new @kubernetes_url, @apiVersion,
266
- ssl_options: ssl_options,
267
- auth_options: auth_options
281
+ create_client()
282
+
283
+ if @test_api_adapter
284
+ log.info "Extending client with test api adaper #{@test_api_adapter}"
285
+ require_relative @test_api_adapter.underscore
286
+ @client.extend(eval(@test_api_adapter))
287
+ end
268
288
 
269
289
  begin
270
290
  @client.api_valid?
271
- rescue KubeException => kube_error
272
- raise Fluent::ConfigError, "Invalid Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{kube_error.message}"
291
+ rescue KubeException => e
292
+ raise Fluent::ConfigError, "Invalid Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{e.message}"
273
293
  end
274
294
 
275
295
  if @watch
276
- pod_thread = Thread.new(self) { |this| this.set_up_pod_thread }
296
+ if ENV['K8S_NODE_NAME'].nil? || ENV['K8S_NODE_NAME'].strip.empty?
297
+ log.warn("!! The environment variable 'K8S_NODE_NAME' is not set to the node name which can affect the API server and watch efficiency !!")
298
+ end
299
+
300
+ pod_thread = Thread.new(self, &:set_up_pod_thread)
277
301
  pod_thread.abort_on_exception = true
278
302
 
279
- namespace_thread = Thread.new(self) { |this| this.set_up_namespace_thread }
303
+ namespace_thread = Thread.new(self, &:set_up_namespace_thread)
280
304
  namespace_thread.abort_on_exception = true
281
305
  end
282
306
  end
@@ -287,52 +311,50 @@ module Fluent::Plugin
287
311
 
288
312
  @annotations_regexps = []
289
313
  @annotation_match.each do |regexp|
290
- begin
291
- @annotations_regexps << Regexp.compile(regexp)
292
- rescue RegexpError => e
293
- log.error "Error: invalid regular expression in annotation_match: #{e}"
294
- end
314
+ @annotations_regexps << Regexp.compile(regexp)
315
+ rescue RegexpError => e
316
+ log.error "Error: invalid regular expression in annotation_match: #{e}"
295
317
  end
318
+ end
296
319
 
320
+ def create_client()
321
+ log.debug 'Creating K8S client'
322
+ @client = nil
323
+ @client = Kubeclient::Client.new(
324
+ @kubernetes_url,
325
+ @apiVersion,
326
+ ssl_options: @ssl_options,
327
+ auth_options: @auth_options,
328
+ as: :parsed_symbolized
329
+ )
297
330
  end
298
331
 
299
- def get_metadata_for_record(namespace_name, pod_name, container_name, container_id, create_time, batch_miss_cache)
332
+ def get_metadata_for_record(namespace_name, pod_name, container_name, cache_key, create_time, batch_miss_cache, docker_id)
300
333
  metadata = {
301
- 'docker' => {'container_id' => container_id},
334
+ 'docker' => { 'container_id' => "" },
302
335
  'kubernetes' => {
303
- 'container_name' => container_name,
304
- 'namespace_name' => namespace_name,
305
- 'pod_name' => pod_name
336
+ 'container_name' => container_name,
337
+ 'namespace_name' => namespace_name,
338
+ 'pod_name' => pod_name
306
339
  }
307
340
  }
308
- if @kubernetes_url.present?
309
- pod_metadata = get_pod_metadata(container_id, namespace_name, pod_name, create_time, batch_miss_cache)
310
-
311
- if (pod_metadata.include? 'containers') && (pod_metadata['containers'].include? container_id) && !@skip_container_metadata
312
- metadata['kubernetes']['container_image'] = pod_metadata['containers'][container_id]['image']
313
- metadata['kubernetes']['container_image_id'] = pod_metadata['containers'][container_id]['image_id']
341
+ metadata['docker']['container_id'] = docker_id unless docker_id.nil?
342
+ container_cache_key = container_name
343
+ if present?(@kubernetes_url)
344
+ pod_metadata = get_pod_metadata(cache_key, namespace_name, pod_name, create_time, batch_miss_cache)
345
+ if (pod_metadata.include? 'containers') && (pod_metadata['containers'].include? container_cache_key) && !@skip_container_metadata
346
+ metadata['kubernetes']['container_image'] = pod_metadata['containers'][container_cache_key]['image']
347
+ metadata['kubernetes']['container_image_id'] = pod_metadata['containers'][container_cache_key]['image_id'] unless pod_metadata['containers'][container_cache_key]['image_id'].empty?
348
+ metadata['docker']['container_id'] = pod_metadata['containers'][container_cache_key]['containerID'] unless pod_metadata['containers'][container_cache_key]['containerID'].empty?
314
349
  end
315
350
 
316
351
  metadata['kubernetes'].merge!(pod_metadata) if pod_metadata
317
352
  metadata['kubernetes'].delete('containers')
318
353
  end
354
+ metadata.delete('docker') if metadata['docker'] && (metadata['docker']['container_id'].nil? || metadata['docker']['container_id'].empty?)
319
355
  metadata
320
356
  end
321
357
 
322
- def create_time_from_record(record, internal_time)
323
- time_key = @time_fields.detect{ |ii| record.has_key?(ii) }
324
- time = record[time_key]
325
- if time.nil? || time.chop.empty?
326
- # `internal_time` is a Fluent::EventTime, it can't compare with Time.
327
- return Time.at(internal_time.to_f)
328
- end
329
- if ['_SOURCE_REALTIME_TIMESTAMP', '__REALTIME_TIMESTAMP'].include?(time_key)
330
- timei= time.to_i
331
- return Time.at(timei / 1000000, timei % 1000000)
332
- end
333
- return Time.parse(time)
334
- end
335
-
336
358
  def filter_stream(tag, es)
337
359
  return es if (es.respond_to?(:empty?) && es.empty?) || !es.is_a?(Fluent::EventStream)
338
360
  new_es = Fluent::MultiEventStream.new
@@ -341,26 +363,31 @@ module Fluent::Plugin
341
363
  batch_miss_cache = {}
342
364
  es.each do |time, record|
343
365
  if tag_match_data && tag_metadata.nil?
366
+ cache_key = if tag_match_data.names.include?('pod_uuid') && !tag_match_data['pod_uuid'].nil?
367
+ tag_match_data['pod_uuid']
368
+ else
369
+ tag_match_data['docker_id']
370
+ end
371
+ docker_id = tag_match_data.names.include?('docker_id') ? tag_match_data['docker_id'] : nil
344
372
  tag_metadata = get_metadata_for_record(tag_match_data['namespace'], tag_match_data['pod_name'], tag_match_data['container_name'],
345
- tag_match_data['docker_id'], create_time_from_record(record, time), batch_miss_cache)
373
+ cache_key, create_time_from_record(record, time), batch_miss_cache, docker_id)
346
374
  end
347
375
  metadata = Marshal.load(Marshal.dump(tag_metadata)) if tag_metadata
348
376
  if (@use_journal || @use_journal.nil?) &&
349
- (j_metadata = get_metadata_for_journal_record(record, time, batch_miss_cache))
377
+ (j_metadata = get_metadata_for_journal_record(record, time, batch_miss_cache))
350
378
  metadata = j_metadata
351
379
  end
352
- if @lookup_from_k8s_field && record.has_key?('kubernetes') && record.has_key?('docker') &&
353
- record['kubernetes'].respond_to?(:has_key?) && record['docker'].respond_to?(:has_key?) &&
354
- record['kubernetes'].has_key?('namespace_name') &&
355
- record['kubernetes'].has_key?('pod_name') &&
356
- record['kubernetes'].has_key?('container_name') &&
357
- record['docker'].has_key?('container_id') &&
358
- (k_metadata = get_metadata_for_record(record['kubernetes']['namespace_name'], record['kubernetes']['pod_name'],
359
- record['kubernetes']['container_name'], record['docker']['container_id'],
360
- create_time_from_record(record, time), batch_miss_cache))
361
- metadata = k_metadata
380
+ if @lookup_from_k8s_field && record.key?('kubernetes') && record.key?('docker') &&
381
+ record['kubernetes'].respond_to?(:has_key?) && record['docker'].respond_to?(:has_key?) &&
382
+ record['kubernetes'].key?('namespace_name') &&
383
+ record['kubernetes'].key?('pod_name') &&
384
+ record['kubernetes'].key?('container_name') &&
385
+ record['docker'].key?('container_id') &&
386
+ (k_metadata = get_metadata_for_record(record['kubernetes']['namespace_name'], record['kubernetes']['pod_name'],
387
+ record['kubernetes']['container_name'], record['docker']['container_id'],
388
+ create_time_from_record(record, time), batch_miss_cache, record['docker']['container_id']))
389
+ metadata = k_metadata
362
390
  end
363
-
364
391
  record = record.merge(metadata) if metadata
365
392
  new_es.add(time, record)
366
393
  end
@@ -370,16 +397,16 @@ module Fluent::Plugin
370
397
 
371
398
  def get_metadata_for_journal_record(record, time, batch_miss_cache)
372
399
  metadata = nil
373
- if record.has_key?('CONTAINER_NAME') && record.has_key?('CONTAINER_ID_FULL')
400
+ if record.key?('CONTAINER_NAME') && record.key?('CONTAINER_ID_FULL')
374
401
  metadata = record['CONTAINER_NAME'].match(@container_name_to_kubernetes_regexp_compiled) do |match_data|
375
402
  get_metadata_for_record(match_data['namespace'], match_data['pod_name'], match_data['container_name'],
376
- record['CONTAINER_ID_FULL'], create_time_from_record(record, time), batch_miss_cache)
403
+ record['CONTAINER_ID_FULL'], create_time_from_record(record, time), batch_miss_cache, record['CONTAINER_ID_FULL'])
377
404
  end
378
405
  unless metadata
379
406
  log.debug "Error: could not match CONTAINER_NAME from record #{record}"
380
407
  @stats.bump(:container_name_match_failed)
381
408
  end
382
- elsif record.has_key?('CONTAINER_NAME') && record['CONTAINER_NAME'].start_with?('k8s_')
409
+ elsif record.key?('CONTAINER_NAME') && record['CONTAINER_NAME'].start_with?('k8s_')
383
410
  log.debug "Error: no container name and id in record #{record}"
384
411
  @stats.bump(:container_name_id_missing)
385
412
  end
@@ -388,13 +415,27 @@ module Fluent::Plugin
388
415
 
389
416
  def de_dot!(h)
390
417
  h.keys.each do |ref|
391
- if h[ref] && ref =~ /\./
392
- v = h.delete(ref)
393
- newref = ref.to_s.gsub('.', @de_dot_separator)
394
- h[newref] = v
395
- end
418
+ next unless h[ref] && ref =~ /\./
419
+
420
+ v = h.delete(ref)
421
+ newref = ref.to_s.gsub('.', @de_dot_separator)
422
+ h[newref] = v
396
423
  end
397
424
  end
398
425
 
426
+ def de_slash!(h)
427
+ h.keys.each do |ref|
428
+ next unless h[ref] && ref =~ /\//
429
+
430
+ v = h.delete(ref)
431
+ newref = ref.to_s.gsub('/', @de_slash_separator)
432
+ h[newref] = v
433
+ end
434
+ end
435
+
436
+ # copied from activesupport
437
+ def present?(object)
438
+ object.respond_to?(:empty?) ? !object.empty? : !!object
439
+ end
399
440
  end
400
441
  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,31 +23,18 @@ 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?
25
- # FAST PATH
26
- # 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
26
+ if ids.nil?
39
27
  @stats.bump(:id_cache_miss)
40
28
  return batch_miss_cache["#{namespace_name}_#{pod_name}"] if batch_miss_cache.key?("#{namespace_name}_#{pod_name}")
29
+
41
30
  pod_metadata = fetch_pod_metadata(namespace_name, pod_name)
42
31
  if @skip_namespace_metadata
43
- ids = { :pod_id=> pod_metadata['pod_id'] }
32
+ ids = { pod_id: pod_metadata['pod_id'] }
44
33
  @id_cache[key] = ids
45
34
  return pod_metadata
46
35
  end
47
36
  namespace_metadata = fetch_namespace_metadata(namespace_name)
48
- ids = { :pod_id=> pod_metadata['pod_id'], :namespace_id => namespace_metadata['namespace_id'] }
37
+ ids = { pod_id: pod_metadata['pod_id'], namespace_id: namespace_metadata['namespace_id'] }
49
38
  if !ids[:pod_id].nil? && !ids[:namespace_id].nil?
50
39
  # pod found and namespace found
51
40
  metadata = pod_metadata
@@ -59,7 +48,7 @@ module KubernetesMetadata
59
48
  # namespace is older then record for pod
60
49
  ids[:pod_id] = key
61
50
  metadata = @cache.fetch(ids[:pod_id]) do
62
- m = { 'pod_id' => ids[:pod_id] }
51
+ { 'pod_id' => ids[:pod_id] }
63
52
  end
64
53
  end
65
54
  metadata.merge!(namespace_metadata)
@@ -74,7 +63,7 @@ module KubernetesMetadata
74
63
  @stats.bump(:id_cache_orphaned_record)
75
64
  end
76
65
  if @allow_orphans
77
- log.trace("orphaning message for: #{namespace_name}/#{pod_name} ") if log.trace?
66
+ log.trace("orphaning message for: #{namespace_name}/#{pod_name} ")
78
67
  metadata = {
79
68
  'orphaned_namespace' => namespace_name,
80
69
  'namespace_name' => @orphaned_namespace_name,
@@ -87,12 +76,25 @@ module KubernetesMetadata
87
76
  end
88
77
  end
89
78
  @id_cache[key] = ids unless batch_miss_cache.key?("#{namespace_name}_#{pod_name}")
79
+ else
80
+ # SLOW PATH
81
+ metadata = @cache.fetch(ids[:pod_id]) do
82
+ @stats.bump(:pod_cache_miss)
83
+ m = fetch_pod_metadata(namespace_name, pod_name)
84
+ m.nil? || m.empty? ? { 'pod_id' => ids[:pod_id] } : m
85
+ end
86
+ metadata.merge!(@namespace_cache.fetch(ids[:namespace_id]) do
87
+ m = unless @skip_namespace_metadata
88
+ @stats.bump(:namespace_cache_miss)
89
+ fetch_namespace_metadata(namespace_name)
90
+ end
91
+ m.nil? || m.empty? ? { 'namespace_id' => ids[:namespace_id] } : m
92
+ end)
90
93
  end
91
94
 
92
95
  # remove namespace info that is only used for comparison
93
96
  metadata.delete('creation_timestamp')
94
- metadata.delete_if{|k,v| v.nil?}
97
+ metadata.delete_if { |_k, v| v.nil? }
95
98
  end
96
-
97
99
  end
98
100
  end