fluent-plugin-kubernetes_metadata_filter 2.1.0 → 3.4.0

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