fluent-plugin-kubernetes_metadata_filter 0.33.0 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +12 -0
- data/Rakefile +2 -2
- data/fluent-plugin-kubernetes_metadata_filter.gemspec +3 -3
- data/lib/fluent/plugin/filter_kubernetes_metadata.rb +120 -186
- data/lib/fluent/plugin/kubernetes_metadata_common.rb +85 -0
- data/lib/fluent/plugin/kubernetes_metadata_stats.rb +6 -2
- data/lib/fluent/plugin/kubernetes_metadata_watch_namespaces.rb +60 -0
- data/lib/fluent/plugin/kubernetes_metadata_watch_pods.rb +60 -0
- data/test/helper.rb +1 -0
- data/test/plugin/test_filter_kubernetes_metadata.rb +101 -4
- data/test/plugin/test_watch_namespaces.rb +91 -0
- data/test/plugin/test_watch_pods.rb +102 -0
- data/test/plugin/watch_test.rb +57 -0
- metadata +13 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b64ea97cd4df7be745b06c0f46e82f152e0503ea
|
4
|
+
data.tar.gz: b026a7a6039cd7f15aa938be5969fa7e5a818c7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53f23402783e6a0b7857698b99a3a7dfd0e343f4ccd5b7ea2c440d8c3fc624d61c4ef89846aa2c91b90995e8df7feb408df7cd5f933d577b3bcfe76e5ced5174
|
7
|
+
data.tar.gz: 912ffc8b2ba2e376bb0ffffe9bd9a881cf2ce6f535328e2c76a53941558d0c7e2209a2359ba319d3d8deb0278252673c15b84c169464bfd8dfc8aa5101adacf8
|
data/README.md
CHANGED
@@ -3,6 +3,14 @@
|
|
3
3
|
[](https://codeclimate.com/github/fabric8io/fluent-plugin-kubernetes_metadata_filter)
|
4
4
|
[](https://codeclimate.com/github/fabric8io/fluent-plugin-kubernetes_metadata_filter)
|
5
5
|
|
6
|
+
The Kubernetes metadata plugin filter enriches container log records with pod and namespace metadata.
|
7
|
+
|
8
|
+
This plugin derives basic metadata about the container that emitted a given log record using the source of the log record. Records from journald provide metadata about the
|
9
|
+
container environment as named fields. Records from JSON files encode metadata about the container in the file name. The initial metadata derived from the source is used
|
10
|
+
to lookup additional metadata about the container's associated pod and namespace (e.g. UUIDs, labels, annotations) when the kubernetes_url is configured. If the plugin cannot
|
11
|
+
authoritatively determine the namespace of the container emitting a log record, it will use an 'orphan' namespace ID in the metadata. This behaviors supports multi-tenant systems
|
12
|
+
that rely on the authenticity of the namespace for proper log isolation.
|
13
|
+
|
6
14
|
## Installation
|
7
15
|
|
8
16
|
gem install fluent-plugin-kubernetes_metadata_filter
|
@@ -33,6 +41,10 @@ This must used named capture groups for `container_name`, `pod_name` & `namespac
|
|
33
41
|
* `include_namespace_metadata` - Collect metadata about namespace such as uid, labels and annotations (default: `false`).
|
34
42
|
Note that when this option is enabled, all label from namespace are collected, but annotations are collected according to `annotation_match` value. If `annotation_match` is not presented, no annotations will be collected from the namespace.
|
35
43
|
* `annotation_match` - Array of regular expressions matching annotation field names. Matched annotations are added to a log record.
|
44
|
+
* `allow_orphans` - Modify the namespace and namespace id to the values of `orphaned_namespace_name` and `orphaned_namespace_id`
|
45
|
+
when true (default: `true`)
|
46
|
+
* `orphaned_namespace_name` - The namespace to associate with records where the namespace can not be determined (default: `.orphaned`)
|
47
|
+
* `orphaned_namespace_id` - The namespace id to associate with records where the namespace can not be determined (default: `orphaned`)
|
36
48
|
|
37
49
|
Reading from the JSON formatted log files with `in_tail` and wildcard filenames:
|
38
50
|
```
|
data/Rakefile
CHANGED
@@ -23,11 +23,11 @@ task :headers do
|
|
23
23
|
require 'copyright_header'
|
24
24
|
|
25
25
|
args = {
|
26
|
-
:license => '
|
26
|
+
:license => 'Apache-2.0',
|
27
27
|
:copyright_software => 'Fluentd Kubernetes Metadata Filter Plugin',
|
28
28
|
:copyright_software_description => 'Enrich Fluentd events with Kubernetes metadata',
|
29
29
|
:copyright_holders => ['Red Hat, Inc.'],
|
30
|
-
:copyright_years => ['2015'],
|
30
|
+
:copyright_years => ['2015-2017'],
|
31
31
|
:add_path => 'lib:test',
|
32
32
|
:output_dir => '.'
|
33
33
|
}
|
@@ -4,13 +4,13 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "fluent-plugin-kubernetes_metadata_filter"
|
7
|
-
gem.version = "0.
|
7
|
+
gem.version = "1.0.0"
|
8
8
|
gem.authors = ["Jimmi Dyson"]
|
9
9
|
gem.email = ["jimmidyson@gmail.com"]
|
10
10
|
gem.description = %q{Filter plugin to add Kubernetes metadata}
|
11
|
-
gem.summary = %q{
|
11
|
+
gem.summary = %q{Fluentd filter plugin to add Kubernetes metadata}
|
12
12
|
gem.homepage = "https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter"
|
13
|
-
gem.license = "
|
13
|
+
gem.license = "Apache-2.0"
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
16
16
|
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
|
3
3
|
# Kubernetes metadata
|
4
4
|
#
|
5
|
-
# Copyright
|
5
|
+
# Copyright 2017 Red Hat, Inc.
|
6
6
|
#
|
7
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
8
|
# you may not use this file except in compliance with the License.
|
@@ -16,12 +16,20 @@
|
|
16
16
|
# See the License for the specific language governing permissions and
|
17
17
|
# limitations under the License.
|
18
18
|
#
|
19
|
+
|
19
20
|
require_relative 'kubernetes_metadata_stats'
|
21
|
+
require_relative 'kubernetes_metadata_watch_namespaces'
|
22
|
+
require_relative 'kubernetes_metadata_watch_pods'
|
23
|
+
|
20
24
|
module Fluent
|
21
25
|
class KubernetesMetadataFilter < Fluent::Filter
|
22
26
|
K8_POD_CA_CERT = 'ca.crt'
|
23
27
|
K8_POD_TOKEN = 'token'
|
24
28
|
|
29
|
+
include KubernetesMetadata::Common
|
30
|
+
include KubernetesMetadata::WatchNamespaces
|
31
|
+
include KubernetesMetadata::WatchPods
|
32
|
+
|
25
33
|
Fluent::Plugin.register_filter('kubernetes_metadata', self)
|
26
34
|
|
27
35
|
config_param :kubernetes_url, :string, default: nil
|
@@ -39,8 +47,6 @@ module Fluent
|
|
39
47
|
config_param :bearer_token_file, :string, default: nil
|
40
48
|
config_param :merge_json_log, :bool, default: true
|
41
49
|
config_param :preserve_json_log, :bool, default: true
|
42
|
-
config_param :include_namespace_id, :bool, default: false
|
43
|
-
config_param :include_namespace_metadata, :bool, default: false
|
44
50
|
config_param :secret_dir, :string, default: '/var/run/secrets/kubernetes.io/serviceaccount'
|
45
51
|
config_param :de_dot, :bool, default: true
|
46
52
|
config_param :de_dot_separator, :string, default: '_'
|
@@ -59,77 +65,36 @@ module Fluent
|
|
59
65
|
|
60
66
|
config_param :annotation_match, :array, default: []
|
61
67
|
config_param :stats_interval, :integer, default: 30
|
68
|
+
config_param :allow_orphans, :bool, default: true
|
69
|
+
config_param :orphaned_namespace_name, :string, default: '.orphaned'
|
70
|
+
config_param :orphaned_namespace_id, :string, default: 'orphaned'
|
62
71
|
|
63
|
-
def
|
64
|
-
|
65
|
-
hsh.each_pair do |kk,vv|
|
66
|
-
if vv.is_a?(Hash)
|
67
|
-
vv = syms_to_strs(vv)
|
68
|
-
end
|
69
|
-
if kk.is_a?(Symbol)
|
70
|
-
newhsh[kk.to_s] = vv
|
71
|
-
else
|
72
|
-
newhsh[kk] = vv
|
73
|
-
end
|
74
|
-
end
|
75
|
-
newhsh
|
76
|
-
end
|
77
|
-
|
78
|
-
def parse_pod_metadata(pod_object)
|
79
|
-
labels = syms_to_strs(pod_object['metadata']['labels'].to_h)
|
80
|
-
annotations = match_annotations(syms_to_strs(pod_object['metadata']['annotations'].to_h))
|
81
|
-
if @de_dot
|
82
|
-
self.de_dot!(labels)
|
83
|
-
self.de_dot!(annotations)
|
84
|
-
end
|
85
|
-
kubernetes_metadata = {
|
86
|
-
'namespace_name' => pod_object['metadata']['namespace'],
|
87
|
-
'pod_id' => pod_object['metadata']['uid'],
|
88
|
-
'pod_name' => pod_object['metadata']['name'],
|
89
|
-
'labels' => labels,
|
90
|
-
'host' => pod_object['spec']['nodeName'],
|
91
|
-
'master_url' => @kubernetes_url
|
92
|
-
}
|
93
|
-
kubernetes_metadata['annotations'] = annotations unless annotations.empty?
|
94
|
-
return kubernetes_metadata
|
95
|
-
end
|
96
|
-
|
97
|
-
def parse_namespace_metadata(namespace_object)
|
98
|
-
labels = syms_to_strs(namespace_object['metadata']['labels'].to_h)
|
99
|
-
annotations = match_annotations(syms_to_strs(namespace_object['metadata']['annotations'].to_h))
|
100
|
-
if @de_dot
|
101
|
-
self.de_dot!(labels)
|
102
|
-
self.de_dot!(annotations)
|
103
|
-
end
|
104
|
-
kubernetes_metadata = {
|
105
|
-
'namespace_id' => namespace_object['metadata']['uid']
|
106
|
-
}
|
107
|
-
kubernetes_metadata['namespace_labels'] = labels unless labels.empty?
|
108
|
-
kubernetes_metadata['namespace_annotations'] = annotations unless annotations.empty?
|
109
|
-
return kubernetes_metadata
|
110
|
-
end
|
111
|
-
|
112
|
-
def get_pod_metadata(namespace_name, pod_name)
|
72
|
+
def fetch_pod_metadata(namespace_name, pod_name)
|
73
|
+
log.trace("fetching pod metadata: #{namespace_name}/#{pod_name}") if log.trace?
|
113
74
|
begin
|
114
75
|
metadata = @client.get_pod(pod_name, namespace_name)
|
115
76
|
unless metadata
|
77
|
+
log.trace("no metadata returned for: #{namespace_name}/#{pod_name}") if log.trace?
|
116
78
|
@stats.bump(:pod_cache_api_nil_not_found)
|
117
79
|
else
|
118
80
|
begin
|
81
|
+
log.trace("raw metadata for #{namespace_name}/#{pod_name}: #{metadata}") if log.trace?
|
119
82
|
metadata = parse_pod_metadata(metadata)
|
120
83
|
@stats.bump(:pod_cache_api_updates)
|
84
|
+
log.trace("parsed metadata for #{namespace_name}/#{pod_name}: #{metadata}") if log.trace?
|
85
|
+
@cache[metadata['pod_id']] = metadata
|
121
86
|
return metadata
|
122
87
|
rescue Exception=>e
|
123
88
|
log.debug(e)
|
124
89
|
@stats.bump(:pod_cache_api_nil_bad_resp_payload)
|
125
|
-
|
90
|
+
log.trace("returning empty metadata for #{namespace_name}/#{pod_name} due to error") if log.trace?
|
126
91
|
end
|
127
92
|
end
|
128
93
|
rescue KubeException=>e
|
129
94
|
@stats.bump(:pod_cache_api_nil_error)
|
130
95
|
log.debug "Exception encountered fetching pod metadata from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{e.message}"
|
131
|
-
nil
|
132
96
|
end
|
97
|
+
{}
|
133
98
|
end
|
134
99
|
|
135
100
|
def dump_stats
|
@@ -139,61 +104,76 @@ module Fluent
|
|
139
104
|
@stats.set(:pod_cache_size, @cache.count)
|
140
105
|
@stats.set(:namespace_cache_size, @namespace_cache.count) if @namespace_cache
|
141
106
|
log.info(@stats)
|
107
|
+
if log.level == Fluent::Log::LEVEL_TRACE
|
108
|
+
log.trace(" id cache: #{@id_cache.to_a}")
|
109
|
+
log.trace(" pod cache: #{@cache.to_a}")
|
110
|
+
log.trace("namespace cache: #{@namespace_cache.to_a}")
|
111
|
+
end
|
142
112
|
end
|
143
113
|
|
144
|
-
def
|
114
|
+
def fetch_namespace_metadata(namespace_name)
|
115
|
+
log.trace("fetching namespace metadata: #{namespace_name}") if log.trace?
|
145
116
|
begin
|
146
117
|
metadata = @client.get_namespace(namespace_name)
|
147
118
|
unless metadata
|
119
|
+
log.trace("no metadata returned for: #{namespace_name}") if log.trace?
|
148
120
|
@stats.bump(:namespace_cache_api_nil_not_found)
|
149
121
|
else
|
150
122
|
begin
|
123
|
+
log.trace("raw metadata for #{namespace_name}: #{metadata}") if log.trace?
|
151
124
|
metadata = parse_namespace_metadata(metadata)
|
152
125
|
@stats.bump(:namespace_cache_api_updates)
|
126
|
+
log.trace("parsed metadata for #{namespace_name}: #{metadata}") if log.trace?
|
127
|
+
@namespace_cache[metadata['namespace_id']] = metadata
|
153
128
|
return metadata
|
154
129
|
rescue Exception => e
|
155
130
|
log.debug(e)
|
156
131
|
@stats.bump(:namespace_cache_api_nil_bad_resp_payload)
|
157
|
-
|
132
|
+
log.trace("returning empty metadata for #{namespace_name} due to error") if log.trace?
|
158
133
|
end
|
159
134
|
end
|
160
135
|
rescue KubeException => kube_error
|
161
136
|
@stats.bump(:namespace_cache_api_nil_error)
|
162
137
|
log.debug "Exception encountered fetching namespace metadata from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{kube_error.message}"
|
163
|
-
nil
|
164
138
|
end
|
139
|
+
{}
|
165
140
|
end
|
166
141
|
|
167
142
|
def initialize
|
168
143
|
super
|
169
|
-
@stats = KubernetesMetadata::Stats.new
|
170
144
|
@prev_time = Time.now
|
171
|
-
|
172
145
|
end
|
173
146
|
|
174
147
|
def configure(conf)
|
175
148
|
super
|
176
149
|
|
150
|
+
def log.trace?
|
151
|
+
level == Fluent::Log::LEVEL_TRACE
|
152
|
+
end
|
153
|
+
|
177
154
|
require 'kubeclient'
|
178
155
|
require 'active_support/core_ext/object/blank'
|
179
156
|
require 'lru_redux'
|
157
|
+
@stats = KubernetesMetadata::Stats.new
|
180
158
|
|
181
159
|
if @de_dot && (@de_dot_separator =~ /\./).present?
|
182
160
|
raise Fluent::ConfigError, "Invalid de_dot_separator: cannot be or contain '.'"
|
183
161
|
end
|
184
162
|
|
185
|
-
if @include_namespace_id
|
186
|
-
# For compatibility, use include_namespace_metadata instead
|
187
|
-
@include_namespace_metadata = true
|
188
|
-
end
|
189
|
-
|
190
163
|
if @cache_ttl < 0
|
164
|
+
log.info "Setting the cache TTL to :none because it was <= 0"
|
191
165
|
@cache_ttl = :none
|
192
166
|
end
|
167
|
+
|
168
|
+
# Caches pod/namespace UID tuples for a given container UID.
|
169
|
+
@id_cache = LruRedux::TTL::ThreadSafeCache.new(@cache_size, @cache_ttl)
|
170
|
+
|
171
|
+
# Use the container UID as the key to fetch a hash containing pod metadata
|
193
172
|
@cache = LruRedux::TTL::ThreadSafeCache.new(@cache_size, @cache_ttl)
|
194
|
-
|
195
|
-
|
196
|
-
|
173
|
+
|
174
|
+
# Use the namespace UID as the key to fetch a hash containing namespace metadata
|
175
|
+
@namespace_cache = LruRedux::TTL::ThreadSafeCache.new(@cache_size, @cache_ttl)
|
176
|
+
|
197
177
|
@tag_to_kubernetes_name_regexp_compiled = Regexp.compile(@tag_to_kubernetes_name_regexp)
|
198
178
|
@container_name_to_kubernetes_regexp_compiled = Regexp.compile(@container_name_to_kubernetes_regexp)
|
199
179
|
|
@@ -247,12 +227,10 @@ module Fluent
|
|
247
227
|
end
|
248
228
|
|
249
229
|
if @watch
|
250
|
-
thread = Thread.new(self) { |this| this.
|
230
|
+
thread = Thread.new(self) { |this| this.start_pod_watch }
|
251
231
|
thread.abort_on_exception = true
|
252
|
-
|
253
|
-
|
254
|
-
namespace_thread.abort_on_exception = true
|
255
|
-
end
|
232
|
+
namespace_thread = Thread.new(self) { |this| this.start_namespace_watch }
|
233
|
+
namespace_thread.abort_on_exception = true
|
256
234
|
end
|
257
235
|
end
|
258
236
|
if @use_journal
|
@@ -274,35 +252,74 @@ module Fluent
|
|
274
252
|
|
275
253
|
end
|
276
254
|
|
277
|
-
def get_metadata_for_record(
|
255
|
+
def get_metadata_for_record(match_data, cache_key, create_time)
|
256
|
+
namespace_name = match_data['namespace']
|
257
|
+
pod_name = match_data['pod_name']
|
278
258
|
metadata = {
|
279
|
-
'container_name' => container_name,
|
259
|
+
'container_name' => match_data['container_name'],
|
280
260
|
'namespace_name' => namespace_name,
|
281
|
-
'pod_name' => pod_name
|
261
|
+
'pod_name' => pod_name
|
282
262
|
}
|
283
263
|
if @kubernetes_url.present?
|
284
|
-
|
285
|
-
|
286
|
-
this = self
|
287
|
-
pod_metadata = @cache.getset(cache_key) {
|
288
|
-
@stats.bump(:pod_cache_miss)
|
289
|
-
md = this.get_pod_metadata(
|
290
|
-
namespace_name,
|
291
|
-
pod_name,
|
292
|
-
)
|
293
|
-
md
|
294
|
-
}
|
264
|
+
pod_metadata = get_pod_metadata(cache_key, namespace_name, pod_name, create_time)
|
295
265
|
metadata.merge!(pod_metadata) if pod_metadata
|
266
|
+
end
|
267
|
+
metadata
|
268
|
+
end
|
269
|
+
|
270
|
+
def get_pod_metadata(key, namespace_name, pod_name, record_create_time)
|
271
|
+
ids = @id_cache.fetch(key) do
|
272
|
+
#no metadata found
|
273
|
+
@stats.bump(:id_cache_miss)
|
274
|
+
pod_metadata = fetch_pod_metadata(namespace_name, pod_name)
|
275
|
+
namespace_metadata = fetch_namespace_metadata(namespace_name)
|
276
|
+
ids = {:pod_id=> pod_metadata['pod_id'], :namespace_id => namespace_metadata['namespace_id'] }
|
277
|
+
# pod not found or namespace not found
|
278
|
+
if ids[:pod_id].nil? || ids[:namespace_id].nil?
|
279
|
+
# pod notfound & namespace found
|
280
|
+
if ids[:pod_id].nil? && !ids[:namespace_id].nil?
|
281
|
+
ns_time = Time.parse(namespace_metadata['creation_timestamp'])
|
282
|
+
ids[:pod_id] = key if ns_time <= record_create_time #ns is older then record
|
283
|
+
ids
|
284
|
+
else
|
285
|
+
# nothing found
|
286
|
+
@stats.bump(:pod_cache_orphaned_record)
|
287
|
+
ids = :orphaned
|
288
|
+
end
|
289
|
+
end
|
290
|
+
ids
|
291
|
+
end
|
296
292
|
|
297
|
-
if
|
298
|
-
|
299
|
-
|
300
|
-
|
293
|
+
if ids == :orphaned
|
294
|
+
return {} unless @allow_orphans
|
295
|
+
log.trace("orphaning message for: #{namespace_name}/#{pod_name} ") if log.trace?
|
296
|
+
return {
|
297
|
+
'orphaned_namespace' => namespace_name,
|
298
|
+
'namespace_name' => @orphaned_namespace_name,
|
299
|
+
'namespace_id' => @orphaned_namespace_id
|
301
300
|
}
|
302
|
-
metadata.merge!(namespace_metadata) if namespace_metadata
|
303
301
|
end
|
304
|
-
|
305
|
-
|
302
|
+
metadata = @cache.fetch(ids[:pod_id]) do
|
303
|
+
@stats.bump(:pod_cache_miss)
|
304
|
+
m = fetch_pod_metadata(namespace_name, pod_name)
|
305
|
+
(m.nil? || m.empty?) ? {'pod_id'=>ids[:pod_id]} : m
|
306
|
+
end
|
307
|
+
metadata.merge!(@namespace_cache.fetch(ids[:namespace_id]) do
|
308
|
+
@stats.bump(:namespace_cache_miss)
|
309
|
+
m = fetch_namespace_metadata(namespace_name)
|
310
|
+
(m.nil? || m.empty?) ? {'namespace_id'=>ids[:namespace_id]} : m
|
311
|
+
end)
|
312
|
+
metadata.delete('creation_timestamp') #remove namespace info thats only used for comparison
|
313
|
+
metadata
|
314
|
+
end
|
315
|
+
|
316
|
+
def create_time_from_record(record)
|
317
|
+
time = if @use_journal
|
318
|
+
record['_SOURCE_REALTIME_TIMESTAMP'].nil? ? record['_SOURCE_REALTIME_TIMESTAMP'] : record['__REALTIME_TIMESTAMP']
|
319
|
+
else
|
320
|
+
record['time']
|
321
|
+
end
|
322
|
+
(time.nil? || time.chop.empty?) ? Time.now : Time.parse(time)
|
306
323
|
end
|
307
324
|
|
308
325
|
def filter_stream(tag, es)
|
@@ -315,15 +332,12 @@ module Fluent
|
|
315
332
|
match_data = tag.match(@tag_to_kubernetes_name_regexp_compiled)
|
316
333
|
|
317
334
|
if match_data
|
335
|
+
container_id = match_data['docker_id']
|
318
336
|
metadata = {
|
319
337
|
'docker' => {
|
320
|
-
'container_id' =>
|
338
|
+
'container_id' => container_id
|
321
339
|
},
|
322
|
-
'kubernetes' => get_metadata_for_record(
|
323
|
-
match_data['namespace'],
|
324
|
-
match_data['pod_name'],
|
325
|
-
match_data['container_name'],
|
326
|
-
),
|
340
|
+
'kubernetes' => get_metadata_for_record(match_data, container_id, create_time_from_record(es.first[1]))
|
327
341
|
}
|
328
342
|
end
|
329
343
|
|
@@ -346,15 +360,12 @@ module Fluent
|
|
346
360
|
metadata = nil
|
347
361
|
if record.has_key?('CONTAINER_NAME') && record.has_key?('CONTAINER_ID_FULL')
|
348
362
|
metadata = record['CONTAINER_NAME'].match(@container_name_to_kubernetes_regexp_compiled) do |match_data|
|
363
|
+
container_id = record['CONTAINER_ID_FULL']
|
349
364
|
metadata = {
|
350
365
|
'docker' => {
|
351
|
-
'container_id' =>
|
366
|
+
'container_id' => container_id
|
352
367
|
},
|
353
|
-
'kubernetes' => get_metadata_for_record(
|
354
|
-
match_data['namespace'],
|
355
|
-
match_data['pod_name'],
|
356
|
-
match_data['container_name'],
|
357
|
-
)
|
368
|
+
'kubernetes' => get_metadata_for_record(match_data, container_id, create_time_from_record(record))
|
358
369
|
}
|
359
370
|
|
360
371
|
metadata
|
@@ -381,10 +392,10 @@ module Fluent
|
|
381
392
|
|
382
393
|
def merge_json_log(record)
|
383
394
|
if record.has_key?(@merge_json_log_key)
|
384
|
-
|
385
|
-
if
|
395
|
+
value = record[@merge_json_log_key].strip
|
396
|
+
if value[0].eql?('{') && value[-1].eql?('}')
|
386
397
|
begin
|
387
|
-
record = JSON.parse(
|
398
|
+
record = JSON.parse(value).merge(record)
|
388
399
|
unless @preserve_json_log
|
389
400
|
record.delete(@merge_json_log_key)
|
390
401
|
end
|
@@ -407,82 +418,5 @@ module Fluent
|
|
407
418
|
end
|
408
419
|
end
|
409
420
|
|
410
|
-
def match_annotations(annotations)
|
411
|
-
result = {}
|
412
|
-
@annotations_regexps.each do |regexp|
|
413
|
-
annotations.each do |key, value|
|
414
|
-
if ::Fluent::StringUtil.match_regexp(regexp, key.to_s)
|
415
|
-
result[key] = value
|
416
|
-
end
|
417
|
-
end
|
418
|
-
end
|
419
|
-
result
|
420
|
-
end
|
421
|
-
|
422
|
-
def start_watch
|
423
|
-
begin
|
424
|
-
resource_version = @client.get_pods.resourceVersion
|
425
|
-
watcher = @client.watch_pods(resource_version)
|
426
|
-
rescue Exception => e
|
427
|
-
message = "Exception encountered fetching metadata from Kubernetes API endpoint: #{e.message}"
|
428
|
-
message += " (#{e.response})" if e.respond_to?(:response)
|
429
|
-
|
430
|
-
raise Fluent::ConfigError, message
|
431
|
-
end
|
432
|
-
|
433
|
-
watcher.each do |notice|
|
434
|
-
case notice.type
|
435
|
-
when 'MODIFIED'
|
436
|
-
cache_key = "#{notice.object['metadata']['namespace']}_#{notice.object['metadata']['name']}"
|
437
|
-
cached = @cache[cache_key]
|
438
|
-
if cached
|
439
|
-
@cache[cache_key] = parse_pod_metadata(notice.object)
|
440
|
-
@stats.bump(:pod_cache_watch_updates)
|
441
|
-
else
|
442
|
-
@stats.bump(:pod_cache_watch_misses)
|
443
|
-
end
|
444
|
-
when 'DELETED'
|
445
|
-
cache_key = "#{notice.object['metadata']['namespace']}_#{notice.object['metadata']['name']}"
|
446
|
-
@cache.delete(cache_key)
|
447
|
-
@stats.bump(:pod_cache_watch_deletes)
|
448
|
-
else
|
449
|
-
# Don't pay attention to creations, since the created pod may not
|
450
|
-
# end up on this node.
|
451
|
-
@stats.bump(:pod_cache_watch_ignored)
|
452
|
-
end
|
453
|
-
end
|
454
|
-
end
|
455
|
-
|
456
|
-
def start_namespace_watch
|
457
|
-
begin
|
458
|
-
resource_version = @client.get_namespaces.resourceVersion
|
459
|
-
watcher = @client.watch_namespaces(resource_version)
|
460
|
-
rescue Exception=>e
|
461
|
-
message = "start_namespace_watch: Exception encountered setting up namespace watch from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{e.message}"
|
462
|
-
message += " (#{e.response})" if e.respond_to?(:response)
|
463
|
-
log.debug(message)
|
464
|
-
raise Fluent::ConfigError, message
|
465
|
-
end
|
466
|
-
watcher.each do |notice|
|
467
|
-
case notice.type
|
468
|
-
when 'MODIFIED'
|
469
|
-
cache_key = notice.object['metadata']['name']
|
470
|
-
cached = @namespace_cache[cache_key]
|
471
|
-
if cached
|
472
|
-
@namespace_cache[cache_key] = parse_namespace_metadata(notice.object)
|
473
|
-
@stats.bump(:namespace_cache_watch_updates)
|
474
|
-
else
|
475
|
-
@stats.bump(:namespace_cache_watch_misses)
|
476
|
-
end
|
477
|
-
when 'DELETED'
|
478
|
-
@namespace_cache.delete(notice.object['metadata']['name'])
|
479
|
-
@stats.bump(:namespace_cache_watch_deletes)
|
480
|
-
else
|
481
|
-
# Don't pay attention to creations, since the created namespace may not
|
482
|
-
# be used by any pod on this node.
|
483
|
-
@stats.bump(:namespace_cache_watch_ignored)
|
484
|
-
end
|
485
|
-
end
|
486
|
-
end
|
487
421
|
end
|
488
422
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
|
3
|
+
# Kubernetes metadata
|
4
|
+
#
|
5
|
+
# Copyright 2017 Red Hat, Inc.
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
module KubernetesMetadata
|
20
|
+
module Common
|
21
|
+
|
22
|
+
def match_annotations(annotations)
|
23
|
+
result = {}
|
24
|
+
@annotations_regexps.each do |regexp|
|
25
|
+
annotations.each do |key, value|
|
26
|
+
if ::Fluent::StringUtil.match_regexp(regexp, key.to_s)
|
27
|
+
result[key] = value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_namespace_metadata(namespace_object)
|
35
|
+
labels = syms_to_strs(namespace_object['metadata']['labels'].to_h)
|
36
|
+
annotations = match_annotations(syms_to_strs(namespace_object['metadata']['annotations'].to_h))
|
37
|
+
if @de_dot
|
38
|
+
self.de_dot!(labels)
|
39
|
+
self.de_dot!(annotations)
|
40
|
+
end
|
41
|
+
kubernetes_metadata = {
|
42
|
+
'namespace_id' => namespace_object['metadata']['uid'],
|
43
|
+
'creation_timestamp' => namespace_object['metadata']['creationTimestamp']
|
44
|
+
}
|
45
|
+
kubernetes_metadata['namespace_labels'] = labels unless labels.empty?
|
46
|
+
kubernetes_metadata['namespace_annotations'] = annotations unless annotations.empty?
|
47
|
+
return kubernetes_metadata
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_pod_metadata(pod_object)
|
51
|
+
labels = syms_to_strs(pod_object['metadata']['labels'].to_h)
|
52
|
+
annotations = match_annotations(syms_to_strs(pod_object['metadata']['annotations'].to_h))
|
53
|
+
if @de_dot
|
54
|
+
self.de_dot!(labels)
|
55
|
+
self.de_dot!(annotations)
|
56
|
+
end
|
57
|
+
kubernetes_metadata = {
|
58
|
+
'namespace_name' => pod_object['metadata']['namespace'],
|
59
|
+
'pod_id' => pod_object['metadata']['uid'],
|
60
|
+
'pod_name' => pod_object['metadata']['name'],
|
61
|
+
'labels' => labels,
|
62
|
+
'host' => pod_object['spec']['nodeName'],
|
63
|
+
'master_url' => @kubernetes_url
|
64
|
+
}
|
65
|
+
kubernetes_metadata['annotations'] = annotations unless annotations.empty?
|
66
|
+
return kubernetes_metadata
|
67
|
+
end
|
68
|
+
|
69
|
+
def syms_to_strs(hsh)
|
70
|
+
newhsh = {}
|
71
|
+
hsh.each_pair do |kk,vv|
|
72
|
+
if vv.is_a?(Hash)
|
73
|
+
vv = syms_to_strs(vv)
|
74
|
+
end
|
75
|
+
if kk.is_a?(Symbol)
|
76
|
+
newhsh[kk.to_s] = vv
|
77
|
+
else
|
78
|
+
newhsh[kk] = vv
|
79
|
+
end
|
80
|
+
end
|
81
|
+
newhsh
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
|
3
3
|
# Kubernetes metadata
|
4
4
|
#
|
5
|
-
# Copyright
|
5
|
+
# Copyright 2017 Red Hat, Inc.
|
6
6
|
#
|
7
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
8
|
# you may not use this file except in compliance with the License.
|
@@ -21,7 +21,7 @@ module KubernetesMetadata
|
|
21
21
|
class Stats
|
22
22
|
|
23
23
|
def initialize
|
24
|
-
@stats = LruRedux::TTL::ThreadSafeCache.new(1000, 3600)
|
24
|
+
@stats = ::LruRedux::TTL::ThreadSafeCache.new(1000, 3600)
|
25
25
|
end
|
26
26
|
|
27
27
|
def bump(key)
|
@@ -32,6 +32,10 @@ module KubernetesMetadata
|
|
32
32
|
@stats[key] = value
|
33
33
|
end
|
34
34
|
|
35
|
+
def [](key)
|
36
|
+
@stats[key]
|
37
|
+
end
|
38
|
+
|
35
39
|
def to_s
|
36
40
|
"stats - " + [].tap do |a|
|
37
41
|
@stats.each {|k,v| a << "#{k.to_s}: #{v}"}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
|
3
|
+
# Kubernetes metadata
|
4
|
+
#
|
5
|
+
# Copyright 2017 Red Hat, Inc.
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
require_relative 'kubernetes_metadata_common'
|
20
|
+
|
21
|
+
module KubernetesMetadata
|
22
|
+
module WatchNamespaces
|
23
|
+
|
24
|
+
include ::KubernetesMetadata::Common
|
25
|
+
|
26
|
+
def start_namespace_watch
|
27
|
+
begin
|
28
|
+
resource_version = @client.get_namespaces.resourceVersion
|
29
|
+
watcher = @client.watch_namespaces(resource_version)
|
30
|
+
rescue Exception=>e
|
31
|
+
message = "start_namespace_watch: Exception encountered setting up namespace watch from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{e.message}"
|
32
|
+
message += " (#{e.response})" if e.respond_to?(:response)
|
33
|
+
log.debug(message)
|
34
|
+
raise Fluent::ConfigError, message
|
35
|
+
end
|
36
|
+
watcher.each do |notice|
|
37
|
+
case notice.type
|
38
|
+
when 'MODIFIED'
|
39
|
+
cache_key = notice.object['metadata']['uid']
|
40
|
+
cached = @namespace_cache[cache_key]
|
41
|
+
if cached
|
42
|
+
@namespace_cache[cache_key] = parse_namespace_metadata(notice.object)
|
43
|
+
@stats.bump(:namespace_cache_watch_updates)
|
44
|
+
else
|
45
|
+
@stats.bump(:namespace_cache_watch_misses)
|
46
|
+
end
|
47
|
+
when 'DELETED'
|
48
|
+
# ignore and let age out for cases where
|
49
|
+
# deleted but still processing logs
|
50
|
+
@stats.bump(:namespace_cache_watch_deletes_ignored)
|
51
|
+
else
|
52
|
+
# Don't pay attention to creations, since the created namespace may not
|
53
|
+
# be used by any pod on this node.
|
54
|
+
@stats.bump(:namespace_cache_watch_ignored)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
|
3
|
+
# Kubernetes metadata
|
4
|
+
#
|
5
|
+
# Copyright 2017 Red Hat, Inc.
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
require_relative 'kubernetes_metadata_common'
|
20
|
+
|
21
|
+
module KubernetesMetadata
|
22
|
+
module WatchPods
|
23
|
+
|
24
|
+
include ::KubernetesMetadata::Common
|
25
|
+
|
26
|
+
def start_pod_watch
|
27
|
+
begin
|
28
|
+
resource_version = @client.get_pods.resourceVersion
|
29
|
+
watcher = @client.watch_pods(resource_version)
|
30
|
+
rescue Exception => e
|
31
|
+
message = "Exception encountered fetching metadata from Kubernetes API endpoint: #{e.message}"
|
32
|
+
message += " (#{e.response})" if e.respond_to?(:response)
|
33
|
+
|
34
|
+
raise Fluent::ConfigError, message
|
35
|
+
end
|
36
|
+
|
37
|
+
watcher.each do |notice|
|
38
|
+
case notice.type
|
39
|
+
when 'MODIFIED'
|
40
|
+
cache_key = notice.object['metadata']['uid']
|
41
|
+
cached = @cache[cache_key]
|
42
|
+
if cached
|
43
|
+
@cache[cache_key] = parse_pod_metadata(notice.object)
|
44
|
+
@stats.bump(:pod_cache_watch_updates)
|
45
|
+
else
|
46
|
+
@stats.bump(:pod_cache_watch_misses)
|
47
|
+
end
|
48
|
+
when 'DELETED'
|
49
|
+
# ignore and let age out for cases where pods
|
50
|
+
# deleted but still processing logs
|
51
|
+
@stats.bump(:pod_cache_watch_delete_ignored)
|
52
|
+
else
|
53
|
+
# Don't pay attention to creations, since the created pod may not
|
54
|
+
# end up on this node.
|
55
|
+
@stats.bump(:pod_cache_watch_ignored)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/test/helper.rb
CHANGED
@@ -151,8 +151,15 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
|
|
151
151
|
kubernetes_url https://localhost:8443
|
152
152
|
watch false
|
153
153
|
cache_size 1
|
154
|
-
|
155
|
-
|
154
|
+
include_namespace_metadata true
|
155
|
+
', d: nil)
|
156
|
+
d = create_driver(config) if d.nil?
|
157
|
+
if ENV['LOGLEVEL']
|
158
|
+
logger = Logger.new(STDOUT)
|
159
|
+
logger.level = eval("Logger::#{ENV['LOGLEVEL'].upcase}")
|
160
|
+
instance = d.instance
|
161
|
+
instance.instance_variable_set(:@log,logger)
|
162
|
+
end
|
156
163
|
d.run {
|
157
164
|
d.emit(msg, @time)
|
158
165
|
}.filtered
|
@@ -169,10 +176,82 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
|
|
169
176
|
}.filtered
|
170
177
|
end
|
171
178
|
|
179
|
+
test 'with docker & kubernetes metadata where id cache hit and metadata miss' do
|
180
|
+
VCR.use_cassette('kubernetes_docker_metadata') do
|
181
|
+
driver = create_driver('
|
182
|
+
kubernetes_url https://localhost:8443
|
183
|
+
watch false
|
184
|
+
cache_size 1
|
185
|
+
include_namespace_metadata true
|
186
|
+
')
|
187
|
+
cache = driver.instance.instance_variable_get(:@id_cache)
|
188
|
+
cache['49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'] = {
|
189
|
+
:pod_id =>'c76927af-f563-11e4-b32d-54ee7527188d',
|
190
|
+
:namespace_id =>'898268c8-4a36-11e5-9d81-42010af0194c'
|
191
|
+
}
|
192
|
+
stub_request(:any, 'https://localhost:8443/api/v1/namespaces/default/pods/fabric8-console-controller-98rqc').to_timeout
|
193
|
+
stub_request(:any, 'https://localhost:8443/api/v1/namespaces/default').to_timeout
|
194
|
+
es = emit({'time'=>'2015-05-08T09:22:01Z'}, '', d:driver)
|
195
|
+
expected_kube_metadata = {
|
196
|
+
'time'=>'2015-05-08T09:22:01Z',
|
197
|
+
'docker' => {
|
198
|
+
'container_id' => '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'
|
199
|
+
},
|
200
|
+
'kubernetes' => {
|
201
|
+
'pod_name' => 'fabric8-console-controller-98rqc',
|
202
|
+
'container_name' => 'fabric8-console-container',
|
203
|
+
'namespace_name' => 'default',
|
204
|
+
'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
|
205
|
+
'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
test 'with docker & kubernetes metadata where id cache hit and metadata is reloaded' do
|
214
|
+
VCR.use_cassette('kubernetes_docker_metadata') do
|
215
|
+
driver = create_driver('
|
216
|
+
kubernetes_url https://localhost:8443
|
217
|
+
watch false
|
218
|
+
cache_size 1
|
219
|
+
include_namespace_metadata true
|
220
|
+
')
|
221
|
+
cache = driver.instance.instance_variable_get(:@id_cache)
|
222
|
+
cache['49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'] = {
|
223
|
+
:pod_id =>'c76927af-f563-11e4-b32d-54ee7527188d',
|
224
|
+
:namespace_id =>'898268c8-4a36-11e5-9d81-42010af0194c'
|
225
|
+
}
|
226
|
+
es = emit({'time'=>'2015-05-08T09:22:01Z'}, '', d:driver)
|
227
|
+
expected_kube_metadata = {
|
228
|
+
'time'=>'2015-05-08T09:22:01Z',
|
229
|
+
'docker' => {
|
230
|
+
'container_id' => '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'
|
231
|
+
},
|
232
|
+
'kubernetes' => {
|
233
|
+
'host' => 'jimmi-redhat.localnet',
|
234
|
+
'pod_name' => 'fabric8-console-controller-98rqc',
|
235
|
+
'container_name' => 'fabric8-console-container',
|
236
|
+
'namespace_name' => 'default',
|
237
|
+
'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
|
238
|
+
'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
|
239
|
+
'master_url' => 'https://localhost:8443',
|
240
|
+
'labels' => {
|
241
|
+
'component' => 'fabric8Console'
|
242
|
+
}
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
172
250
|
test 'with docker & kubernetes metadata' do
|
173
251
|
VCR.use_cassette('kubernetes_docker_metadata') do
|
174
|
-
es = emit()
|
252
|
+
es = emit({'time'=>'2015-05-08T09:22:01Z'})
|
175
253
|
expected_kube_metadata = {
|
254
|
+
'time'=>'2015-05-08T09:22:01Z',
|
176
255
|
'docker' => {
|
177
256
|
'container_id' => '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'
|
178
257
|
},
|
@@ -181,6 +260,7 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
|
|
181
260
|
'pod_name' => 'fabric8-console-controller-98rqc',
|
182
261
|
'container_name' => 'fabric8-console-container',
|
183
262
|
'namespace_name' => 'default',
|
263
|
+
'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
|
184
264
|
'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
|
185
265
|
'master_url' => 'https://localhost:8443',
|
186
266
|
'labels' => {
|
@@ -188,6 +268,7 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
|
|
188
268
|
}
|
189
269
|
}
|
190
270
|
}
|
271
|
+
|
191
272
|
assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
|
192
273
|
end
|
193
274
|
end
|
@@ -238,6 +319,7 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
|
|
238
319
|
'pod_name' => 'fabric8-console-controller-98rqc',
|
239
320
|
'container_name' => 'fabric8-console-container',
|
240
321
|
'namespace_name' => 'default',
|
322
|
+
'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
|
241
323
|
'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
|
242
324
|
'master_url' => 'https://localhost:8443',
|
243
325
|
'labels' => {
|
@@ -271,6 +353,7 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
|
|
271
353
|
}.to_json
|
272
354
|
)
|
273
355
|
stub_request(:any, 'https://localhost:8443/api/v1/namespaces/default/pods/fabric8-console-controller-98rqc').to_timeout
|
356
|
+
stub_request(:any, 'https://localhost:8443/api/v1/namespaces/default').to_timeout
|
274
357
|
es = emit()
|
275
358
|
expected_kube_metadata = {
|
276
359
|
'docker' => {
|
@@ -279,7 +362,9 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
|
|
279
362
|
'kubernetes' => {
|
280
363
|
'pod_name' => 'fabric8-console-controller-98rqc',
|
281
364
|
'container_name' => 'fabric8-console-container',
|
282
|
-
'namespace_name' => '
|
365
|
+
'namespace_name' => '.orphaned',
|
366
|
+
'orphaned_namespace' => 'default',
|
367
|
+
'namespace_id' => 'orphaned'
|
283
368
|
}
|
284
369
|
}
|
285
370
|
assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
|
@@ -350,6 +435,15 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
|
|
350
435
|
assert_equal(msg.merge(json_log), es.instance_variable_get(:@record_array)[0])
|
351
436
|
end
|
352
437
|
|
438
|
+
test 'ignores invalid json in log field' do
|
439
|
+
json_log = "{'foo':123}"
|
440
|
+
msg = {
|
441
|
+
'log' => json_log
|
442
|
+
}
|
443
|
+
es = emit_with_tag('non-kubernetes', msg, '')
|
444
|
+
assert_equal(msg, es.instance_variable_get(:@record_array)[0])
|
445
|
+
end
|
446
|
+
|
353
447
|
test 'merges json log data with message field in MESSAGE' do
|
354
448
|
json_log = {
|
355
449
|
'timeMillis' => 1459853347608,
|
@@ -491,6 +585,7 @@ use_journal true
|
|
491
585
|
'pod_name' => 'fabric8-console-controller-98rqc',
|
492
586
|
'container_name' => 'fabric8-console-container',
|
493
587
|
'namespace_name' => 'default',
|
588
|
+
'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
|
494
589
|
'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
|
495
590
|
'master_url' => 'https://localhost:8443',
|
496
591
|
'labels' => {
|
@@ -556,6 +651,7 @@ use_journal true
|
|
556
651
|
'pod_name' => 'fabric8-console-controller-98rqc',
|
557
652
|
'container_name' => 'fabric8-console-container',
|
558
653
|
'namespace_name' => 'default',
|
654
|
+
'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
|
559
655
|
'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
|
560
656
|
'master_url' => 'https://localhost:8443',
|
561
657
|
'labels' => {
|
@@ -595,6 +691,7 @@ use_journal true
|
|
595
691
|
'pod_name' => 'fabric8-console-controller-98rqc',
|
596
692
|
'container_name' => 'fabric8-console-container',
|
597
693
|
'namespace_name' => 'default',
|
694
|
+
'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
|
598
695
|
'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
|
599
696
|
'master_url' => 'https://localhost:8443',
|
600
697
|
'labels' => {
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
|
3
|
+
# Kubernetes metadata
|
4
|
+
#
|
5
|
+
# Copyright 2015 Red Hat, Inc.
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
require_relative '../helper'
|
20
|
+
require 'ostruct'
|
21
|
+
require_relative 'watch_test'
|
22
|
+
|
23
|
+
class WatchNamespacesTestTest < WatchTest
|
24
|
+
|
25
|
+
include KubernetesMetadata::WatchNamespaces
|
26
|
+
|
27
|
+
setup do
|
28
|
+
@created = OpenStruct.new(
|
29
|
+
type: 'CREATED',
|
30
|
+
object: {
|
31
|
+
'metadata' => {
|
32
|
+
'name' => 'created',
|
33
|
+
'uid' => 'created_uid'
|
34
|
+
}
|
35
|
+
}
|
36
|
+
)
|
37
|
+
@modified = OpenStruct.new(
|
38
|
+
type: 'MODIFIED',
|
39
|
+
object: {
|
40
|
+
'metadata' => {
|
41
|
+
'name' => 'foo',
|
42
|
+
'uid' => 'modified_uid'
|
43
|
+
}
|
44
|
+
}
|
45
|
+
)
|
46
|
+
@deleted = OpenStruct.new(
|
47
|
+
type: 'DELETED',
|
48
|
+
object: {
|
49
|
+
'metadata' => {
|
50
|
+
'name' => 'deleteme',
|
51
|
+
'uid' => 'deleted_uid'
|
52
|
+
}
|
53
|
+
}
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
test 'namespace watch ignores CREATED' do
|
58
|
+
@client.stub :watch_namespaces, [@created] do
|
59
|
+
start_namespace_watch
|
60
|
+
assert_equal(false, @namespace_cache.key?('created_uid'))
|
61
|
+
assert_equal(1, @stats[:namespace_cache_watch_ignored])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
test 'namespace watch ignores MODIFIED when info not in cache' do
|
66
|
+
@client.stub :watch_namespaces, [@modified] do
|
67
|
+
start_namespace_watch
|
68
|
+
assert_equal(false, @namespace_cache.key?('modified_uid'))
|
69
|
+
assert_equal(1, @stats[:namespace_cache_watch_misses])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
test 'namespace watch updates cache when MODIFIED is received and info is cached' do
|
74
|
+
@namespace_cache['modified_uid'] = {}
|
75
|
+
@client.stub :watch_namespaces, [@modified] do
|
76
|
+
start_namespace_watch
|
77
|
+
assert_equal(true, @namespace_cache.key?('modified_uid'))
|
78
|
+
assert_equal(1, @stats[:namespace_cache_watch_updates])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
test 'namespace watch ignores DELETED' do
|
83
|
+
@namespace_cache['deleted_uid'] = {}
|
84
|
+
@client.stub :watch_namespaces, [@deleted] do
|
85
|
+
start_namespace_watch
|
86
|
+
assert_equal(true, @namespace_cache.key?('deleted_uid'))
|
87
|
+
assert_equal(1, @stats[:namespace_cache_watch_deletes_ignored])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
|
3
|
+
# Kubernetes metadata
|
4
|
+
#
|
5
|
+
# Copyright 2015 Red Hat, Inc.
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
require_relative '../helper'
|
20
|
+
require 'ostruct'
|
21
|
+
require_relative 'watch_test'
|
22
|
+
|
23
|
+
class DefaultPodWatchStrategyTest < WatchTest
|
24
|
+
|
25
|
+
include KubernetesMetadata::WatchPods
|
26
|
+
|
27
|
+
setup do
|
28
|
+
@created = OpenStruct.new(
|
29
|
+
type: 'CREATED',
|
30
|
+
object: {
|
31
|
+
'metadata' => {
|
32
|
+
'name' => 'created',
|
33
|
+
'namespace' => 'create',
|
34
|
+
'uid' => 'created_uid',
|
35
|
+
'labels' => {},
|
36
|
+
},
|
37
|
+
'spec' => {
|
38
|
+
'nodeName' => 'aNodeName'
|
39
|
+
}
|
40
|
+
}
|
41
|
+
)
|
42
|
+
@modified = OpenStruct.new(
|
43
|
+
type: 'MODIFIED',
|
44
|
+
object: {
|
45
|
+
'metadata' => {
|
46
|
+
'name' => 'foo',
|
47
|
+
'namespace' => 'modified',
|
48
|
+
'uid' => 'modified_uid',
|
49
|
+
'labels' => {},
|
50
|
+
},
|
51
|
+
'spec' => {
|
52
|
+
'nodeName' => 'aNodeName'
|
53
|
+
}
|
54
|
+
}
|
55
|
+
)
|
56
|
+
@deleted = OpenStruct.new(
|
57
|
+
type: 'DELETED',
|
58
|
+
object: {
|
59
|
+
'metadata' => {
|
60
|
+
'name' => 'deleteme',
|
61
|
+
'namespace' => 'deleted',
|
62
|
+
'uid' => 'deleted_uid'
|
63
|
+
}
|
64
|
+
}
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
test 'pod watch notice ignores CREATED' do
|
69
|
+
@client.stub :watch_pods, [@created] do
|
70
|
+
start_pod_watch
|
71
|
+
assert_equal(false, @cache.key?('created_uid'))
|
72
|
+
assert_equal(1, @stats[:pod_cache_watch_ignored])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
test 'pod watch notice is ignored when info not cached and MODIFIED is received' do
|
77
|
+
@client.stub :watch_pods, [@modified] do
|
78
|
+
start_pod_watch
|
79
|
+
assert_equal(false, @cache.key?('modified_uid'))
|
80
|
+
assert_equal(1, @stats[:pod_cache_watch_misses])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
test 'pod watch notice is updated when MODIFIED is received' do
|
85
|
+
@cache['modified_uid'] = {}
|
86
|
+
@client.stub :watch_pods, [@modified] do
|
87
|
+
start_pod_watch
|
88
|
+
assert_equal(true, @cache.key?('modified_uid'))
|
89
|
+
assert_equal(1, @stats[:pod_cache_watch_updates])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
test 'pod watch notice is ignored when delete is received' do
|
94
|
+
@cache['deleted_uid'] = {}
|
95
|
+
@client.stub :watch_pods, [@deleted] do
|
96
|
+
start_pod_watch
|
97
|
+
assert_equal(true, @cache.key?('deleted_uid'))
|
98
|
+
assert_equal(1, @stats[:pod_cache_watch_delete_ignored])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
|
3
|
+
# Kubernetes metadata
|
4
|
+
#
|
5
|
+
# Copyright 2015 Red Hat, Inc.
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
require_relative '../helper'
|
20
|
+
require 'ostruct'
|
21
|
+
|
22
|
+
class WatchTest < Test::Unit::TestCase
|
23
|
+
|
24
|
+
setup do
|
25
|
+
@annotations_regexps = []
|
26
|
+
@namespace_cache = {}
|
27
|
+
@cache = {}
|
28
|
+
@stats = KubernetesMetadata::Stats.new
|
29
|
+
@client = OpenStruct.new
|
30
|
+
def @client.resourceVersion
|
31
|
+
'12345'
|
32
|
+
end
|
33
|
+
def @client.watch_pods(value)
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
def @client.watch_namespaces(value)
|
37
|
+
[]
|
38
|
+
end
|
39
|
+
def @client.get_namespaces
|
40
|
+
self
|
41
|
+
end
|
42
|
+
def @client.get_pods
|
43
|
+
self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def watcher=(value)
|
48
|
+
end
|
49
|
+
|
50
|
+
def log
|
51
|
+
logger = {}
|
52
|
+
def logger.debug(message)
|
53
|
+
end
|
54
|
+
logger
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-kubernetes_metadata_filter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jimmi Dyson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-12-
|
11
|
+
date: 2017-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|
@@ -207,7 +207,10 @@ files:
|
|
207
207
|
- circle.yml
|
208
208
|
- fluent-plugin-kubernetes_metadata_filter.gemspec
|
209
209
|
- lib/fluent/plugin/filter_kubernetes_metadata.rb
|
210
|
+
- lib/fluent/plugin/kubernetes_metadata_common.rb
|
210
211
|
- lib/fluent/plugin/kubernetes_metadata_stats.rb
|
212
|
+
- lib/fluent/plugin/kubernetes_metadata_watch_namespaces.rb
|
213
|
+
- lib/fluent/plugin/kubernetes_metadata_watch_pods.rb
|
211
214
|
- test/cassettes/invalid_api_server_config.yml
|
212
215
|
- test/cassettes/kubernetes_docker_metadata.yml
|
213
216
|
- test/cassettes/kubernetes_docker_metadata_annotations.yml
|
@@ -220,9 +223,12 @@ files:
|
|
220
223
|
- test/plugin/test.token
|
221
224
|
- test/plugin/test_cache_stats.rb
|
222
225
|
- test/plugin/test_filter_kubernetes_metadata.rb
|
226
|
+
- test/plugin/test_watch_namespaces.rb
|
227
|
+
- test/plugin/test_watch_pods.rb
|
228
|
+
- test/plugin/watch_test.rb
|
223
229
|
homepage: https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter
|
224
230
|
licenses:
|
225
|
-
-
|
231
|
+
- Apache-2.0
|
226
232
|
metadata: {}
|
227
233
|
post_install_message:
|
228
234
|
rdoc_options: []
|
@@ -243,7 +249,7 @@ rubyforge_project:
|
|
243
249
|
rubygems_version: 2.6.12
|
244
250
|
signing_key:
|
245
251
|
specification_version: 4
|
246
|
-
summary:
|
252
|
+
summary: Fluentd filter plugin to add Kubernetes metadata
|
247
253
|
test_files:
|
248
254
|
- test/cassettes/invalid_api_server_config.yml
|
249
255
|
- test/cassettes/kubernetes_docker_metadata.yml
|
@@ -257,3 +263,6 @@ test_files:
|
|
257
263
|
- test/plugin/test.token
|
258
264
|
- test/plugin/test_cache_stats.rb
|
259
265
|
- test/plugin/test_filter_kubernetes_metadata.rb
|
266
|
+
- test/plugin/test_watch_namespaces.rb
|
267
|
+
- test/plugin/test_watch_pods.rb
|
268
|
+
- test/plugin/watch_test.rb
|