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