fluent-plugin-kubernetes_metadata_filter_splunk 2.2.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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +56 -0
  3. data/.gitignore +20 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE.txt +201 -0
  6. data/README.md +209 -0
  7. data/Rakefile +37 -0
  8. data/fluent-plugin-kubernetes_metadata_filter.gemspec +36 -0
  9. data/lib/fluent/plugin/filter_kubernetes_metadata.rb +446 -0
  10. data/lib/fluent/plugin/kubernetes_metadata_cache_strategy.rb +98 -0
  11. data/lib/fluent/plugin/kubernetes_metadata_common.rb +113 -0
  12. data/lib/fluent/plugin/kubernetes_metadata_stats.rb +46 -0
  13. data/lib/fluent/plugin/kubernetes_metadata_watch_namespaces.rb +65 -0
  14. data/lib/fluent/plugin/kubernetes_metadata_watch_pods.rb +68 -0
  15. data/test/cassettes/invalid_api_server_config.yml +53 -0
  16. data/test/cassettes/kubernetes_docker_metadata.yml +228 -0
  17. data/test/cassettes/kubernetes_docker_metadata_annotations.yml +239 -0
  18. data/test/cassettes/kubernetes_docker_metadata_dotted_labels.yml +231 -0
  19. data/test/cassettes/kubernetes_docker_metadata_using_bearer_token.yml +248 -0
  20. data/test/cassettes/kubernetes_get_api_v1.yml +193 -0
  21. data/test/cassettes/kubernetes_get_api_v1_using_token.yml +195 -0
  22. data/test/cassettes/kubernetes_get_namespace_default.yml +69 -0
  23. data/test/cassettes/kubernetes_get_namespace_default_using_token.yml +71 -0
  24. data/test/cassettes/kubernetes_get_pod.yml +146 -0
  25. data/test/cassettes/kubernetes_get_pod_using_token.yml +148 -0
  26. data/test/cassettes/metadata_from_tag_and_journald_fields.yml +408 -0
  27. data/test/cassettes/metadata_from_tag_journald_and_kubernetes_fields.yml +540 -0
  28. data/test/cassettes/metadata_with_namespace_id.yml +276 -0
  29. data/test/cassettes/non_kubernetes_docker_metadata.yml +97 -0
  30. data/test/cassettes/valid_kubernetes_api_server.yml +55 -0
  31. data/test/cassettes/valid_kubernetes_api_server_using_token.yml +57 -0
  32. data/test/helper.rb +64 -0
  33. data/test/plugin/test.token +1 -0
  34. data/test/plugin/test_cache_stats.rb +36 -0
  35. data/test/plugin/test_cache_strategy.rb +196 -0
  36. data/test/plugin/test_filter_kubernetes_metadata.rb +970 -0
  37. data/test/plugin/test_watch_namespaces.rb +91 -0
  38. data/test/plugin/test_watch_pods.rb +145 -0
  39. data/test/plugin/watch_test.rb +57 -0
  40. metadata +295 -0
@@ -0,0 +1,98 @@
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 CacheStrategy
21
+ def get_pod_metadata(key, namespace_name, pod_name, record_create_time, batch_miss_cache)
22
+ metadata = {}
23
+ ids = @id_cache[key]
24
+ if !ids.nil?
25
+ # FAST PATH
26
+ # Cache hit, fetch metadata from the cache
27
+ metadata = @cache.fetch(ids[:pod_id]) do
28
+ @stats.bump(:pod_cache_miss)
29
+ m = fetch_pod_metadata(namespace_name, pod_name)
30
+ (m.nil? || m.empty?) ? {'pod_id'=>ids[:pod_id]} : m
31
+ end
32
+ metadata.merge!(@namespace_cache.fetch(ids[:namespace_id]) do
33
+ @stats.bump(:namespace_cache_miss)
34
+ m = fetch_namespace_metadata(namespace_name) unless @skip_namespace_metadata
35
+ (m.nil? || m.empty?) ? {'namespace_id'=>ids[:namespace_id]} : m
36
+ end)
37
+ else
38
+ # SLOW PATH
39
+ @stats.bump(:id_cache_miss)
40
+ return batch_miss_cache["#{namespace_name}_#{pod_name}"] if batch_miss_cache.key?("#{namespace_name}_#{pod_name}")
41
+ pod_metadata = fetch_pod_metadata(namespace_name, pod_name)
42
+ if @skip_namespace_metadata
43
+ ids = { :pod_id=> pod_metadata['pod_id'] }
44
+ @id_cache[key] = ids
45
+ return pod_metadata
46
+ end
47
+ namespace_metadata = fetch_namespace_metadata(namespace_name)
48
+ ids = { :pod_id=> pod_metadata['pod_id'], :namespace_id => namespace_metadata['namespace_id'] }
49
+ if !ids[:pod_id].nil? && !ids[:namespace_id].nil?
50
+ # pod found and namespace found
51
+ metadata = pod_metadata
52
+ metadata.merge!(namespace_metadata)
53
+ else
54
+ if ids[:pod_id].nil? && !ids[:namespace_id].nil?
55
+ # pod not found, but namespace found
56
+ @stats.bump(:id_cache_pod_not_found_namespace)
57
+ ns_time = Time.parse(namespace_metadata['creation_timestamp'])
58
+ if ns_time <= record_create_time
59
+ # namespace is older then record for pod
60
+ ids[:pod_id] = key
61
+ metadata = @cache.fetch(ids[:pod_id]) do
62
+ m = { 'pod_id' => ids[:pod_id] }
63
+ end
64
+ end
65
+ metadata.merge!(namespace_metadata)
66
+ else
67
+ if !ids[:pod_id].nil? && ids[:namespace_id].nil?
68
+ # pod found, but namespace NOT found
69
+ # this should NEVER be possible since pod meta can
70
+ # only be retrieved with a namespace
71
+ @stats.bump(:id_cache_namespace_not_found_pod)
72
+ else
73
+ # nothing found
74
+ @stats.bump(:id_cache_orphaned_record)
75
+ end
76
+ if @allow_orphans
77
+ log.trace("orphaning message for: #{namespace_name}/#{pod_name} ") if log.trace?
78
+ metadata = {
79
+ 'orphaned_namespace' => namespace_name,
80
+ 'namespace_name' => @orphaned_namespace_name,
81
+ 'namespace_id' => @orphaned_namespace_id
82
+ }
83
+ else
84
+ metadata = {}
85
+ end
86
+ batch_miss_cache["#{namespace_name}_#{pod_name}"] = metadata
87
+ end
88
+ end
89
+ @id_cache[key] = ids unless batch_miss_cache.key?("#{namespace_name}_#{pod_name}")
90
+ end
91
+
92
+ # remove namespace info that is only used for comparison
93
+ metadata.delete('creation_timestamp')
94
+ metadata.delete_if{|k,v| v.nil?}
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,113 @@
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 = String.new
36
+ labels = syms_to_strs(namespace_object['metadata']['labels'].to_h) unless @skip_labels
37
+
38
+ annotations = match_annotations(syms_to_strs(namespace_object['metadata']['annotations'].to_h))
39
+ if @de_dot
40
+ self.de_dot!(labels) unless @skip_labels
41
+ self.de_dot!(annotations)
42
+ end
43
+ kubernetes_metadata = {
44
+ 'namespace_id' => namespace_object['metadata']['uid'],
45
+ 'creation_timestamp' => namespace_object['metadata']['creationTimestamp']
46
+ }
47
+ kubernetes_metadata['namespace_labels'] = labels unless labels.empty?
48
+ kubernetes_metadata['namespace_annotations'] = annotations unless annotations.empty?
49
+ return kubernetes_metadata
50
+ end
51
+
52
+ def parse_pod_metadata(pod_object)
53
+ labels = String.new
54
+ labels = syms_to_strs(pod_object['metadata']['labels'].to_h) unless @skip_labels
55
+
56
+ annotations = match_annotations(syms_to_strs(pod_object['metadata']['annotations'].to_h))
57
+ if @de_dot
58
+ self.de_dot!(labels) unless @skip_labels
59
+ self.de_dot!(annotations)
60
+ end
61
+
62
+ # collect container informations
63
+ container_meta = {}
64
+ begin
65
+ pod_object['status']['containerStatuses'].each do|container_status|
66
+ # get plain container id (eg. docker://hash -> hash)
67
+ container_id = container_status['containerID'].sub /^[-_a-zA-Z0-9]+:\/\//, ''
68
+ unless @skip_container_metadata
69
+ container_meta[container_id] = {
70
+ 'name' => container_status['name'],
71
+ 'image' => container_status['image'],
72
+ 'image_id' => container_status['imageID']
73
+ }
74
+ else
75
+ container_meta[container_id] = {
76
+ 'name' => container_status['name']
77
+ }
78
+ end
79
+ end
80
+ rescue
81
+ log.debug("parsing container meta information failed for: #{pod_object['metadata']['namespace']}/#{pod_object['metadata']['name']} ")
82
+ end
83
+
84
+ kubernetes_metadata = {
85
+ 'namespace_name' => pod_object['metadata']['namespace'],
86
+ 'pod_id' => pod_object['metadata']['uid'],
87
+ 'pod_name' => pod_object['metadata']['name'],
88
+ 'containers' => syms_to_strs(container_meta),
89
+ 'host' => pod_object['spec']['nodeName']
90
+ }
91
+ kubernetes_metadata['annotations'] = annotations unless annotations.empty?
92
+ kubernetes_metadata['labels'] = labels unless labels.empty?
93
+ kubernetes_metadata['master_url'] = @kubernetes_url unless @skip_master_url
94
+ return kubernetes_metadata
95
+ end
96
+
97
+ def syms_to_strs(hsh)
98
+ newhsh = {}
99
+ hsh.each_pair do |kk,vv|
100
+ if vv.is_a?(Hash)
101
+ vv = syms_to_strs(vv)
102
+ end
103
+ if kk.is_a?(Symbol)
104
+ newhsh[kk.to_s] = vv
105
+ else
106
+ newhsh[kk] = vv
107
+ end
108
+ end
109
+ newhsh
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,46 @@
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 'lru_redux'
20
+ module KubernetesMetadata
21
+ class Stats
22
+
23
+ def initialize
24
+ @stats = ::LruRedux::TTL::ThreadSafeCache.new(1000, 3600)
25
+ end
26
+
27
+ def bump(key)
28
+ @stats[key] = @stats.getset(key) { 0 } + 1
29
+ end
30
+
31
+ def set(key, value)
32
+ @stats[key] = value
33
+ end
34
+
35
+ def [](key)
36
+ @stats[key]
37
+ end
38
+
39
+ def to_s
40
+ "stats - " + [].tap do |a|
41
+ @stats.each {|k,v| a << "#{k.to_s}: #{v}"}
42
+ end.join(', ')
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,65 @@
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
+ loop do
28
+ log.error "watchthread: Starting kubernetes namespace"
29
+ begin
30
+ resource_version = @client.get_namespaces.resourceVersion
31
+ watcher = @client.watch_namespaces(resource_version)
32
+ rescue Exception=>e
33
+ message = "start_namespace_watch: Exception encountered setting up namespace watch from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{e.message}"
34
+ message += " (#{e.response})" if e.respond_to?(:response)
35
+ log.debug(message)
36
+ raise Fluent::ConfigError, message
37
+ end
38
+ watcher.each do |notice|
39
+ log.error "watchthread: Received namespace update #{notice.object['metadata']['namespace']}"
40
+ case notice.type
41
+ when 'MODIFIED'
42
+ cache_key = notice.object['metadata']['uid']
43
+ cached = @namespace_cache[cache_key]
44
+ if cached
45
+ @namespace_cache[cache_key] = parse_namespace_metadata(notice.object)
46
+ @stats.bump(:namespace_cache_watch_updates)
47
+ else
48
+ @stats.bump(:namespace_cache_watch_misses)
49
+ end
50
+ when 'DELETED'
51
+ # ignore and let age out for cases where
52
+ # deleted but still processing logs
53
+ @stats.bump(:namespace_cache_watch_deletes_ignored)
54
+ else
55
+ # Don't pay attention to creations, since the created namespace may not
56
+ # be used by any pod on this node.
57
+ @stats.bump(:namespace_cache_watch_ignored)
58
+ end
59
+ end
60
+ log.error "watchthread: Ending kybernetes namespace"
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,68 @@
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
+ loop do
28
+ log.error "watchthread: Starting kubernetes pod"
29
+ begin
30
+ resource_version = @client.get_pods.resourceVersion
31
+ watcher = @client.watch_pods(resource_version)
32
+ rescue Exception => e
33
+ message = "Exception encountered fetching metadata from Kubernetes API endpoint: #{e.message}"
34
+ message += " (#{e.response})" if e.respond_to?(:response)
35
+
36
+ raise Fluent::ConfigError, message
37
+ end
38
+
39
+ watcher.each do |notice|
40
+ log.error "watchthread: Received pod update #{notice.object['metadata']['namespace']} #{notice.object['metadata']['name']}"
41
+ case notice.type
42
+ when 'MODIFIED'
43
+ cache_key = notice.object['metadata']['uid']
44
+ cached = @cache[cache_key]
45
+ if cached
46
+ @cache[cache_key] = parse_pod_metadata(notice.object)
47
+ @stats.bump(:pod_cache_watch_updates)
48
+ elsif ENV['K8S_NODE_NAME'] == notice.object['spec']['nodeName'] then
49
+ @cache[cache_key] = parse_pod_metadata(notice.object)
50
+ @stats.bump(:pod_cache_host_updates)
51
+ else
52
+ @stats.bump(:pod_cache_watch_misses)
53
+ end
54
+ when 'DELETED'
55
+ # ignore and let age out for cases where pods
56
+ # deleted but still processing logs
57
+ @stats.bump(:pod_cache_watch_delete_ignored)
58
+ else
59
+ # Don't pay attention to creations, since the created pod may not
60
+ # end up on this node.
61
+ @stats.bump(:pod_cache_watch_ignored)
62
+ end
63
+ end
64
+ log.error "watchthread: Ending kubernetes pod"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,53 @@
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
+ ---
20
+ http_interactions:
21
+ - request:
22
+ method: get
23
+ uri: https://localhost:8443/api
24
+ body:
25
+ encoding: US-ASCII
26
+ string: ''
27
+ headers:
28
+ Accept:
29
+ - "*/*; q=0.5, application/xml"
30
+ Accept-Encoding:
31
+ - gzip, deflate
32
+ User-Agent:
33
+ - Ruby
34
+ Authorization:
35
+ - Bearer YzYyYzFlODMtODdhNS00ZTMyLWIzMmItNmY4NDc4OTI1ZWF
36
+ response:
37
+ status:
38
+ code: 401
39
+ message: Unauthorized
40
+ headers:
41
+ Content-Type:
42
+ - text/plain; charset=utf-8
43
+ Date:
44
+ - Sat, 09 May 2015 14:04:39 GMT
45
+ Content-Length:
46
+ - '13'
47
+ body:
48
+ encoding: UTF-8
49
+ string: |
50
+ Unauthorized
51
+ http_version:
52
+ recorded_at: Sat, 09 May 2015 14:04:39 GMT
53
+ recorded_with: VCR 2.9.3