fluent-plugin-kubelet_metadata 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ff16f93534b3c20d748f06d4198c2615c5bcdb2cca97d72d9a32a1b5fedbfc84
4
+ data.tar.gz: f483fc7148a2a87152d71bbee7f63d958d75da4966693ea4af6a6dc2f31f2de5
5
+ SHA512:
6
+ metadata.gz: a9596bbf690ce009cff3100874430ed10ff18731c960eb5b0fe0f112233889003e496c9416cbcd60e26b58782830e73811b2bfd5a885373c1eb89b8d44353e4e
7
+ data.tar.gz: be1be94b1bc528753875028fce7ba73e5c91997c8794c76027a87fbe98e2268dbd55d93c6d11f3ba446580cf74d26dda5001527686d9d60e9a5bc78bfa657d50
@@ -0,0 +1,20 @@
1
+ Copyright (C) 2013 Michael Grosser <michael@grosser.it>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+ # The file needs to have the name filter_<type> to be auto-discoverable by fluentd
3
+
4
+ require 'fluent/env'
5
+ require 'fluent/plugin/filter'
6
+ require 'set'
7
+ require 'json'
8
+ require 'uri'
9
+ require 'net/http'
10
+
11
+ module Fluent::Plugin
12
+ class KubeletMetadata < Fluent::Plugin::Filter
13
+ Fluent::Plugin.register_filter('kubelet_metadata', self)
14
+
15
+ config_param :statsd, :string, default: nil
16
+
17
+ KUBELET_ERROR_BACKOFF_SECONDS = [0.1, 0.5, 1].freeze
18
+ KUBELET_MAX_REQUESTS_PER_SECOND = 10
19
+ POD_CACHE_SIZE = 200 # assuming users run 10-100 pods per node
20
+
21
+ # rubocop:disable Layout/LineLength
22
+ # from https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter/blob/8f95b0e5fda922ef0576c7ce53d0c72f19a86754/lib/fluent/plugin/filter_kubernetes_metadata.rb#L52
23
+ # for example: input.kubernetes.pod.var.log.containers.fluentd-mgj9v_default_vault-pki-auth-manager-26f3a7bad715d9d324fb3c818681ec01df14831f0585cb83f2df25a7386ee5f4.log
24
+ TAG_REGEX = Regexp.compile('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$')
25
+ # rubocop:enable Layout/LineLength
26
+
27
+ # - no operations on all values to be fast
28
+ # - cannot store nil as value
29
+ class ThreadsafeLruCache
30
+ def initialize(size)
31
+ @size = size
32
+ @data = {}
33
+ @mutex = Mutex.new
34
+ end
35
+
36
+ def [](key)
37
+ @mutex.synchronize do
38
+ value = @data.delete(key) # always remove ... later add it back if necessary
39
+ return if value.nil? # miss
40
+ @data[key] = value # mark as recently used
41
+ end
42
+ end
43
+
44
+ def []=(key, value)
45
+ @mutex.synchronize do
46
+ @data.delete @data.first[0] if @data.size == @size # make room
47
+ @data[key] = value
48
+ end
49
+ end
50
+ end
51
+
52
+ def initialize
53
+ super
54
+ @cache = ThreadsafeLruCache.new(POD_CACHE_SIZE)
55
+ @throttle_mutex = Mutex.new
56
+ end
57
+
58
+ def configure(conf)
59
+ super
60
+ @statsd = Object.const_get(@statsd) if @statsd
61
+ fill_cache unless ARGV.include?('--dry-run')
62
+ end
63
+
64
+ def filter(tag, _time, record)
65
+ return record unless match = tag.match(TAG_REGEX)&.named_captures
66
+
67
+ labels = pod_labels(
68
+ [match.fetch('namespace'), match.fetch('pod_name')],
69
+ tags: [
70
+ "pod_name:#{match.fetch('pod_name')}",
71
+ "namespace:#{match.fetch('namespace')}",
72
+ "container:#{match.fetch('container_name')}"
73
+ ]
74
+ )
75
+
76
+ record.merge(
77
+ 'docker' => { 'container_id' => match.fetch('docker_id') },
78
+ 'kubernetes' => {
79
+ 'container_name' => match.fetch('container_name'),
80
+ 'namespace_name' => match.fetch('namespace'),
81
+ 'pod_name' => match.fetch('pod_name'),
82
+ 'labels' => labels
83
+ }
84
+ )
85
+ end
86
+
87
+ private
88
+
89
+ def pod_labels(key, **args)
90
+ @cache[key] || begin
91
+ inc "soft_miss"
92
+ fill_cache
93
+ @cache[key] || begin
94
+ inc "hard_miss", **args # need tags here to be able to debug
95
+ {}
96
+ end
97
+ end
98
+ end
99
+
100
+ # stores each pods labels that kubelet knows in the cache
101
+ # only storing the labels since pod objects are big
102
+ def fill_cache
103
+ pods.each do |pod|
104
+ @cache[[pod.dig("metadata", "namespace"), pod.dig("metadata", "name")]] = pod.dig("metadata", "labels") || {}
105
+ end
106
+ end
107
+
108
+ # /runningpods/ has much less data, but does not include initContainerStatuses
109
+ #
110
+ # InitContainers are often not available when logs start coming in
111
+ #
112
+ # Full kubelet api see https://stackoverflow.com/questions/35075195/is-there-api-documentation-for-kubelet-api
113
+ def pods
114
+ retry_on_error backoff: KUBELET_ERROR_BACKOFF_SECONDS do
115
+ throttle per_second: KUBELET_MAX_REQUESTS_PER_SECOND, throttled: [] do
116
+ JSON.parse(http_get('https://localhost:10250/pods')).fetch("items")
117
+ end
118
+ end
119
+ rescue StandardError
120
+ []
121
+ end
122
+
123
+ def http_get(url)
124
+ uri = URI(url)
125
+ http = Net::HTTP.new(uri.host, uri.port)
126
+ http.use_ssl = (uri.scheme == "https")
127
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
128
+ http.open_timeout = 2
129
+ http.read_timeout = 5
130
+
131
+ request = Net::HTTP::Get.new(uri.request_uri)
132
+ request['Authorization'] = "Bearer #{File.read("/var/run/secrets/kubernetes.io/serviceaccount/token")}"
133
+
134
+ response = http.start { http.request request }
135
+
136
+ raise "Error response #{response.code} -- #{response.body}" unless response.code == "200"
137
+
138
+ response.body
139
+ end
140
+
141
+ def throttle(per_second:, throttled:)
142
+ t = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i
143
+ c = nil
144
+
145
+ @throttle_mutex.synchronize do
146
+ old_t, c = @throttle
147
+ old_t == t ? c += 1 : c = 1
148
+ @throttle = [t, c]
149
+ end
150
+
151
+ if c > per_second
152
+ inc "throttled"
153
+ throttled
154
+ else
155
+ yield
156
+ end
157
+ end
158
+
159
+ def retry_on_error(backoff:)
160
+ yield
161
+ rescue StandardError
162
+ backoff_index ||= -1
163
+ backoff_index += 1
164
+ inc "kubelet_error", tags: ["error:#{$!.class}"]
165
+ raise unless delay = backoff[backoff_index]
166
+
167
+ sleep delay
168
+ retry
169
+ end
170
+
171
+ def inc(metric, **args)
172
+ @statsd&.increment "fluentd.kubelet_metadata.#{metric}", **args
173
+ end
174
+ end
175
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-kubelet_metadata
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-10-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '1'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
33
+ description:
34
+ email: michael@grosser.it
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - MIT-LICENSE
40
+ - lib/fluent/plugin/filter_kubelet_metadata.rb
41
+ homepage: https://github.com/grosser/fluent-plugin-kubelet_metadata
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.5.0
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.1.3
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Add metadata to docker logs by asking kubelet api
64
+ test_files: []