fluent-plugin-kubernetes_metadata_filter 2.1.4 → 2.9.4

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