fluent-plugin-kubernetes_metadata_filter 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fc794d4eb88ae0226aaeaa73ed83ca71149db1a3
4
- data.tar.gz: 9c69ce241a593c562f4cccb244dc940111a6fc8c
3
+ metadata.gz: 3c5cc732f8758c1f6d82d606134748d36d7b2b04
4
+ data.tar.gz: dafc960f1353bc761e535098d3c24f3ed30f078d
5
5
  SHA512:
6
- metadata.gz: 24575557e613c96da0c79d9592c740f3ced4c0f6c36ff18b9af7aac88483af6b1e790f04b71425aa04a13e9390c629c7ce3a2332fd94dde6b41bf4461a8f47f3
7
- data.tar.gz: 2f9053e6a5775b2d4a857b6d3ad67821b64d9007c93f7019d58f0326b59324752fdee04f2305612ee79a8c973653c0e95cdae7d6b3df4d23a3f14118cb0116c3
6
+ metadata.gz: d4aae90186c4aab8bab96449d6ecf31bb46beed4d7d223571fb217879d3a89f1fde36a211a1ce0585f4174a29a6a8a248bb719e050a0b689706cdb845c74886c
7
+ data.tar.gz: a0e303e2e1d14b8a1b09db8235d6258b97f7d48d0e56a928232f27ed4f4915a3031742c6ca7188f5e8bd87aa0fc9731e76392f2ee5ec93f4add5fd65f3b28230
data/README.md CHANGED
@@ -11,35 +11,33 @@
11
11
 
12
12
  Configuration options for fluent.conf are:
13
13
 
14
- * `kubernetes_url` - URL to the API server. *This is required*
14
+ * `kubernetes_url` - URL to the API server. Set this to retrieve further kubernetes metadata for logs from kubernetes API server
15
15
  * `apiVersion` - API version to use (default: `v1beta3`)
16
16
  * `ca_file` - path to CA file for Kubernetes server certificate validation
17
17
  * `verify_ssl` - validate SSL certificates (default: true)
18
18
  * `client_cert` - path to a client cert file to authenticate to the API server
19
19
  * `client_key` - path to a client key file to authenticate to the API server
20
20
  * `bearer_token_file` - path to a file containing the bearer token to use for authentication
21
- * `container_name_to_kubernetes_name_regexp` - the regular expression used to extract kubernetes metadata (pod name, container name, namespace) from the Docker container name. This must used named capture groups for `pod_container_name`, `pod_name` & `namespace` (default: `'\/?[^_]+_(?<pod_container_name>[^\.]+)[^_]+_(?<pod_name>[^_]+)_(?<namespace>[^_]+)'`)
21
+ * `tag_to_kubernetes_name_regexp` - the regular expression used to extract kubernetes metadata (pod name, container name, namespace) from the current fluentd tag.
22
+ This must used named capture groups for `container_name`, `pod_name` & `namespace` (default: `\.(?<pod_name>[^\._]+)_(?<namespace>[^_]+)_(?<container_name>.+)-(?<docker_id>[a-z0-9]{64})\.log$</pod>)`)
22
23
  * `cache_size` - size of the cache of Kubernetes metadata to reduce requests to the API server (default: `1000`)
23
24
  * `cache_ttl` - TTL in seconds of each cached element. Set to negative value to disable TTL eviction (default: `3600` - 1 hour)
25
+ * `watch` - set up a watch on pods on the API server for updates to metadata (default: `true`)
26
+ * `merge_json_log` - merge logs in JSON format as top level keys (default: `true`)
24
27
 
25
28
  ```
26
29
  <source>
27
30
  type tail
28
- path /var/lib/docker/containers/*/*-json.log
31
+ path /var/log/containers/*.log
29
32
  pos_file fluentd-docker.pos
30
33
  time_format %Y-%m-%dT%H:%M:%S
31
- tag docker.*
34
+ tag kubernetes.*
32
35
  format json
33
36
  read_from_head true
34
37
  </source>
35
38
 
36
- <filter docker.var.lib.docker.containers.*.*.log>
37
- type docker_metadata
38
- </filter>
39
-
40
- <filter docker.var.lib.docker.containers.*.*.log>
39
+ <filter kubernetes.var.lib.docker.containers.*.*.log>
41
40
  type kubernetes_metadata
42
- kubernetes_url https://localhost:8443
43
41
  </filter>
44
42
 
45
43
  <match **>
@@ -49,12 +47,10 @@ Configuration options for fluent.conf are:
49
47
 
50
48
  ## Example input/output
51
49
 
52
- Docker logs in JSON format. Log files are normally in
53
- `/var/lib/docker/containers/*/*-json.log`, depending on what your Docker
54
- data directory is.
50
+ Kubernetes creates symlinks to Docker log files in `/var/log/containers/*.log`. Docker logs in JSON format.
55
51
 
56
52
  Assuming following inputs are coming from a log file:
57
- df14e0d5ae4c07284fa636d739c8fc2e6b52bc344658de7d3f08c36a2e804115-json.log:
53
+
58
54
  ```
59
55
  {
60
56
  "log": "2015/05/05 19:54:41 \n",
@@ -70,11 +66,6 @@ Then output becomes as belows
70
66
  "stream": "stderr",
71
67
  "docker": {
72
68
  "id": "df14e0d5ae4c07284fa636d739c8fc2e6b52bc344658de7d3f08c36a2e804115",
73
- "name": "/k8s_fabric8-console-container.efbd6e64_fabric8-console-controller-9knhj_default_8ae2f621-f360-11e4-8d12-54ee7527188d_7ec9aa3e",
74
- "container_hostname": "fabric8-console-controller-9knhj",
75
- "image": "fabric8/hawtio-kubernetes:latest",
76
- "image_id": "b2bd1a24a68356b2f30128e6e28e672c1ef92df0d9ec01ec0c7faea5d77d2303",
77
- "labels": {}
78
69
  }
79
70
  "kubernetes": {
80
71
  "host": "jimmi-redhat.localnet",
@@ -4,7 +4,7 @@ $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.5.0"
7
+ gem.version = "0.6.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}
@@ -20,7 +20,7 @@ module Fluent
20
20
  class KubernetesMetadataFilter < Fluent::Filter
21
21
  Fluent::Plugin.register_filter('kubernetes_metadata', self)
22
22
 
23
- config_param :kubernetes_url, :string
23
+ config_param :kubernetes_url, :string, default: ''
24
24
  config_param :cache_size, :integer, default: 1000
25
25
  config_param :cache_ttl, :integer, default: 60 * 60
26
26
  config_param :watch, :bool, default: true
@@ -29,9 +29,9 @@ module Fluent
29
29
  config_param :client_key, :string, default: ''
30
30
  config_param :ca_file, :string, default: ''
31
31
  config_param :verify_ssl, :bool, default: true
32
- config_param :container_name_to_kubernetes_name_regexp,
32
+ config_param :tag_to_kubernetes_name_regexp,
33
33
  :string,
34
- :default => '\/?[^_]+_(?<pod_container_name>[^\.]+)[^_]+_(?<pod_name>[^_]+)_(?<namespace>[^_]+)'
34
+ :default => '\.(?<pod_name>[^\._]+)_(?<namespace>[^_]+)_(?<container_name>.+)-(?<docker_id>[a-z0-9]{64})\.log$'
35
35
  config_param :bearer_token_file, :string, default: ''
36
36
  config_param :merge_json_log, :bool, default: true
37
37
 
@@ -40,12 +40,12 @@ module Fluent
40
40
  metadata = @client.get_pod(pod_name, namespace)
41
41
  if metadata
42
42
  return {
43
- uid: metadata['metadata']['uid'],
44
- namespace: metadata['metadata']['namespace'],
45
- pod_name: metadata['metadata']['name'],
46
- container_name: container_name,
47
- labels: metadata['metadata']['labels'].to_h,
48
- host: metadata['spec']['host']
43
+ uid: metadata['metadata']['uid'],
44
+ namespace: metadata['metadata']['namespace'],
45
+ pod_name: metadata['metadata']['name'],
46
+ container_name: container_name,
47
+ labels: metadata['metadata']['labels'].to_h,
48
+ host: metadata['spec']['host']
49
49
  }
50
50
  end
51
51
  rescue KubeException
@@ -64,59 +64,80 @@ module Fluent
64
64
  require 'active_support/core_ext/object/blank'
65
65
  require 'lru_redux'
66
66
 
67
- @client = Kubeclient::Client.new @kubernetes_url, @apiVersion
68
-
69
- @client.ssl_options(
70
- client_cert: @client_cert.present? ? OpenSSL::X509::Certificate.new(File.read(@client_cert)) : nil,
71
- client_key: @client_key.present? ? OpenSSL::PKey::RSA.new(File.read(@client_key)) : nil,
72
- ca_file: @ca_file,
73
- verify_ssl: @verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
74
- )
75
-
76
- if @bearer_token_file.present?
77
- bearer_token = File.read(@bearer_token_file)
78
- @client.bearer_token(bearer_token)
79
- end
80
-
81
- begin
82
- @client.api_valid?
83
- rescue KubeException => kube_error
84
- raise Fluent::ConfigError, "Invalid Kubernetes API endpoint: #{kube_error.message}"
85
- end
86
-
87
67
  if @cache_ttl < 0
88
68
  @cache_ttl = :none
89
69
  end
90
- @cache = LruRedux::TTL::ThreadSafeCache.new(@cache_size, @cache_ttl)
91
- @container_name_to_kubernetes_name_regexp_compiled = Regexp.compile(@container_name_to_kubernetes_name_regexp)
70
+ @cache = LruRedux::TTL::ThreadSafeCache.new(@cache_size, @cache_ttl)
71
+ @tag_to_kubernetes_name_regexp_compiled = Regexp.compile(@tag_to_kubernetes_name_regexp)
72
+
73
+ if @kubernetes_url.present?
74
+ @client = Kubeclient::Client.new @kubernetes_url, @apiVersion
75
+
76
+ @client.ssl_options(
77
+ client_cert: @client_cert.present? ? OpenSSL::X509::Certificate.new(File.read(@client_cert)) : nil,
78
+ client_key: @client_key.present? ? OpenSSL::PKey::RSA.new(File.read(@client_key)) : nil,
79
+ ca_file: @ca_file,
80
+ verify_ssl: @verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
81
+ )
82
+
83
+ if @bearer_token_file.present?
84
+ bearer_token = File.read(@bearer_token_file)
85
+ @client.bearer_token(bearer_token)
86
+ end
92
87
 
93
- if @watch
94
- thread = Thread.new(self) { |this|
95
- this.start_watch
96
- }
97
- thread.abort_on_exception = true
88
+ begin
89
+ @client.api_valid?
90
+ rescue KubeException => kube_error
91
+ raise Fluent::ConfigError, "Invalid Kubernetes API endpoint: #{kube_error.message}"
92
+ end
93
+
94
+ if @watch
95
+ thread = Thread.new(self) { |this|
96
+ this.start_watch
97
+ }
98
+ thread.abort_on_exception = true
99
+ end
98
100
  end
99
101
  end
100
102
 
101
103
  def filter_stream(tag, es)
102
104
  new_es = MultiEventStream.new
103
105
 
104
- es.each { |time, record|
105
- if record.has_key?(:docker) && record[:docker].has_key?(:id) && record[:docker].has_key?(:name)
106
- this = self
107
- metadata = @cache.getset(record[:docker][:id]) {
108
- match_data = record[:docker][:name].match(@container_name_to_kubernetes_name_regexp_compiled)
109
- if match_data
110
- this.get_metadata(
111
- match_data[:pod_name],
112
- match_data[:pod_container_name],
113
- match_data[:namespace]
114
- )
115
- end
116
- }
106
+ match_data = tag.match(@tag_to_kubernetes_name_regexp_compiled)
107
+
108
+ if match_data
109
+ metadata = {
110
+ docker: {
111
+ container_id: match_data['docker_id']
112
+ },
113
+ kubernetes: {
114
+ namespace: match_data['namespace'],
115
+ pod_name: match_data['pod_name'],
116
+ container_name: match_data['container_name']
117
+ }
118
+ }
117
119
 
118
- record[:kubernetes] = metadata if metadata
120
+ if @kubernetes_url.present?
121
+ cache_key = "#{metadata['namespace']}_#{metadata['pod_name']}_#{metadata['pod_container_name']}"
122
+
123
+ if cache_key.present?
124
+ this = self
125
+ metadata = @cache.getset(cache_key) {
126
+ if metadata
127
+ metadata[:kubernetes] = this.get_metadata(
128
+ metadata[:kubernetes][:pod_name],
129
+ metadata[:kubernetes][:container_name],
130
+ metadata[:kubernetes][:namespace]
131
+ )
132
+ metadata
133
+ end
134
+ }
135
+ end
119
136
  end
137
+ end
138
+
139
+ es.each { |time, record|
140
+ record = record.merge(metadata) if metadata
120
141
 
121
142
  if @merge_json_log
122
143
  record = merge_json_log(record)
@@ -134,7 +155,7 @@ module Fluent
134
155
  if log[0].eql?('{') && log[-1].eql?('}')
135
156
  begin
136
157
  parsed_log = JSON.parse(log)
137
- record = record.merge(parsed_log)
158
+ record = record.merge(parsed_log)
138
159
  unless parsed_log.has_key?('log')
139
160
  record.delete('log')
140
161
  end
@@ -31,14 +31,13 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
31
31
  end
32
32
 
33
33
  def create_driver(conf = '')
34
- Test::FilterTestDriver.new(KubernetesMetadataFilter).configure(conf, true)
34
+ Test::FilterTestDriver.new(KubernetesMetadataFilter, 'var.log.containers.fabric8-console-controller-98rqc_default_fabric8-console-container-49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459.log').configure(conf, true)
35
35
  end
36
36
 
37
37
  sub_test_case 'configure' do
38
38
  test 'check default' do
39
- assert_raise Fluent::ConfigError do
40
- create_driver
41
- end
39
+ d = create_driver
40
+ assert_equal(1000, d.instance.cache_size)
42
41
  end
43
42
 
44
43
  test 'kubernetes url' do
@@ -80,7 +79,7 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
80
79
 
81
80
  sub_test_case 'filter_stream' do
82
81
 
83
- def emit(msg, config='
82
+ def emit(msg={}, config='
84
83
  kubernetes_url https://localhost:8443
85
84
  watch false
86
85
  cache_size 1
@@ -91,16 +90,24 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
91
90
  }.filtered
92
91
  end
93
92
 
93
+ def emit_with_tag(tag, msg={}, config='
94
+ kubernetes_url https://localhost:8443
95
+ watch false
96
+ cache_size 1
97
+ ')
98
+ d = create_driver(config)
99
+ d.run {
100
+ d.emit_with_tag(tag, msg, @time)
101
+ }.filtered
102
+ end
103
+
94
104
  test 'with docker & kubernetes metadata' do
95
105
  VCR.use_cassette('kubernetes_docker_metadata') do
96
- msg = {
97
- docker: {
98
- id: '/49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459',
99
- name: '/k8s_fabric8-console-container.efbd6e64_fabric8-console-controller-98rqc_default_c76927af-f563-11e4-b32d-54ee7527188d_42cbc279'
100
- }
101
- }
102
- es = emit(msg)
106
+ es = emit()
103
107
  expected_kube_metadata = {
108
+ docker: {
109
+ container_id: '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'
110
+ },
104
111
  kubernetes: {
105
112
  host: 'jimmi-redhat.localnet',
106
113
  pod_name: 'fabric8-console-controller-98rqc',
@@ -112,25 +119,22 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
112
119
  }
113
120
  }
114
121
  }
115
- assert_equal(msg.merge(expected_kube_metadata), es.instance_variable_get(:@record_array)[0])
122
+ assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
116
123
  end
117
124
  end
118
125
 
119
126
  test 'with docker & kubernetes metadata using bearer token' do
120
127
  VCR.use_cassette('kubernetes_docker_metadata_using_bearer_token') do
121
- msg = {
122
- docker: {
123
- id: '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459',
124
- name: '/k8s_fabric8-console-container.efbd6e64_fabric8-console-controller-98rqc_default_c76927af-f563-11e4-b32d-54ee7527188d_42cbc279'
125
- }
126
- }
127
- es = emit(msg, '
128
+ es = emit({}, '
128
129
  kubernetes_url https://localhost:8443
129
130
  verify_ssl false
130
131
  watch false
131
132
  bearer_token_file test/plugin/test.token
132
133
  ')
133
134
  expected_kube_metadata = {
135
+ docker: {
136
+ container_id: '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'
137
+ },
134
138
  kubernetes: {
135
139
  host: 'jimmi-redhat.localnet',
136
140
  pod_name: 'fabric8-console-controller-98rqc',
@@ -142,47 +146,41 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
142
146
  }
143
147
  }
144
148
  }
145
- assert_equal(msg.merge(expected_kube_metadata), es.instance_variable_get(:@record_array)[0])
149
+ assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
146
150
  end
147
151
  end
148
152
 
149
- test 'with docker metadata, non-kubernetes' do
150
- VCR.use_cassette('non_kubernetes_docker_metadata') do
151
- msg = {
153
+ test 'with docker & kubernetes metadata but no configured api server' do
154
+ es = emit({}, '')
155
+ expected_kube_metadata = {
152
156
  docker: {
153
- id: '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459',
154
- name: '/k8s_POD.c0b903ca_fabric8-forge-controller-ymkew_default_bcde9961-f4b7-11e4-bdbf-54ee7527188d_e1f00705'
157
+ container_id: '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'
158
+ },
159
+ kubernetes: {
160
+ pod_name: 'fabric8-console-controller-98rqc',
161
+ container_name: 'fabric8-console-container',
162
+ namespace: 'default',
155
163
  }
156
- }
157
- es = emit(msg)
158
- assert_equal(msg, es.instance_variable_get(:@record_array)[0])
159
- assert_false(es.instance_variable_get(:@record_array)[0].has_key?(:kubernetes))
160
- end
164
+ }
165
+ assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
161
166
  end
162
167
 
163
- test 'without docker metadata' do
164
- VCR.use_cassette('valid_kubernetes_api_server') do
165
- msg = {'foo' => 'bar'}
166
- es = emit(msg)
167
- assert_equal(msg, es.instance_variable_get(:@record_array)[0])
168
- end
168
+
169
+
170
+ test 'with docker metadata, non-kubernetes' do
171
+ es = emit_with_tag('non-kubernetes', {}, '')
172
+ assert_false(es.instance_variable_get(:@record_array)[0].has_key?(:kubernetes))
169
173
  end
170
174
 
171
175
  test 'merges json log data' do
172
- VCR.use_cassette('valid_kubernetes_api_server') do
173
- json_log = {
174
- 'hello' => 'world'
175
- }
176
- msg = {
177
- 'log' => "#{json_log.to_json}"
178
- }
179
- es = emit(msg, '
180
- kubernetes_url https://localhost:8443
181
- verify_ssl false
182
- watch false
183
- ')
184
- assert_equal(json_log, es.instance_variable_get(:@record_array)[0])
185
- end
176
+ json_log = {
177
+ 'hello' => 'world'
178
+ }
179
+ msg = {
180
+ 'log' => "#{json_log.to_json}"
181
+ }
182
+ es = emit_with_tag('non-kubernetes', msg, '')
183
+ assert_equal(json_log, es.instance_variable_get(:@record_array)[0])
186
184
  end
187
185
  end
188
186
  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.5.0
4
+ version: 0.6.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: 2015-05-18 00:00:00.000000000 Z
11
+ date: 2015-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd