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