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