fluent-plugin-kubernetes_sumologic 0.0.1

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.
@@ -0,0 +1,51 @@
1
+ apiVersion: extensions/v1beta1
2
+ kind: DaemonSet
3
+ metadata:
4
+ name: fluentd-sumologic
5
+ labels:
6
+ app: fluentd-sumologic
7
+ version: v1
8
+ spec:
9
+ template:
10
+ metadata:
11
+ labels:
12
+ name: fluentd-sumologic
13
+ spec:
14
+ volumes:
15
+ - name: pos-files
16
+ hostPath:
17
+ path: /var/run/fluentd-pos
18
+ type: ""
19
+ - name: host-logs
20
+ hostPath:
21
+ path: /var/log/
22
+ - name: docker-logs
23
+ hostPath:
24
+ path: /var/lib/docker
25
+ containers:
26
+ - image: sumologic/fluentd-kubernetes-sumologic:latest
27
+ name: fluentd
28
+ imagePullPolicy: Always
29
+ volumeMounts:
30
+ - name: host-logs
31
+ mountPath: /mnt/log/
32
+ readOnly: true
33
+ - name: host-logs
34
+ mountPath: /var/log/
35
+ readOnly: true
36
+ - name: docker-logs
37
+ mountPath: /var/lib/docker/
38
+ readOnly: true
39
+ - name: pos-files
40
+ mountPath: /mnt/pos/
41
+ env:
42
+ - name: COLLECTOR_URL
43
+ valueFrom:
44
+ secretKeyRef:
45
+ name: sumologic
46
+ key: collector-url
47
+ tolerations:
48
+ - operator: "Exists"
49
+ - effect: "NoSchedule"
50
+ key: "node-role.kubernetes.io/master"
51
+
@@ -0,0 +1,90 @@
1
+ ---
2
+ apiVersion: v1
3
+ kind: ServiceAccount
4
+ metadata:
5
+ name: fluentd
6
+
7
+ ---
8
+ apiVersion: rbac.authorization.k8s.io/v1beta1
9
+ kind: ClusterRole
10
+ metadata:
11
+ name: fluentd
12
+ rules:
13
+ - apiGroups:
14
+ - ""
15
+ resources:
16
+ - namespaces
17
+ - pods
18
+ verbs:
19
+ - get
20
+ - list
21
+ - watch
22
+
23
+ ---
24
+ kind: ClusterRoleBinding
25
+ apiVersion: rbac.authorization.k8s.io/v1beta1
26
+ metadata:
27
+ name: fluentd
28
+ roleRef:
29
+ kind: ClusterRole
30
+ name: fluentd
31
+ apiGroup: rbac.authorization.k8s.io
32
+ subjects:
33
+ - kind: ServiceAccount
34
+ name: fluentd
35
+ # This namespace setting will limit fluentd to watching/listing/getting pods in the default namespace. If you want it to be able to log your kube-system namespace as well, comment the line out.
36
+ namespace: default
37
+
38
+ ---
39
+ apiVersion: extensions/v1beta1
40
+ kind: DaemonSet
41
+ metadata:
42
+ name: fluentd-sumologic
43
+ labels:
44
+ app: fluentd-sumologic
45
+ version: v1
46
+ spec:
47
+ template:
48
+ metadata:
49
+ labels:
50
+ name: fluentd-sumologic
51
+ spec:
52
+ serviceAccountName: fluentd
53
+ volumes:
54
+ - name: pos-files
55
+ hostPath:
56
+ path: /var/run/fluentd-pos
57
+ type: ""
58
+ - name: host-logs
59
+ hostPath:
60
+ path: /var/log/
61
+ - name: docker-logs
62
+ hostPath:
63
+ path: /var/lib/docker
64
+ containers:
65
+ - image: sumologic/fluentd-kubernetes-sumologic:latest
66
+ name: fluentd
67
+ imagePullPolicy: Always
68
+ volumeMounts:
69
+ - name: host-logs
70
+ mountPath: /mnt/log/
71
+ readOnly: true
72
+ - name: host-logs
73
+ mountPath: /var/log/
74
+ readOnly: true
75
+ - name: docker-logs
76
+ mountPath: /var/lib/docker/
77
+ readOnly: true
78
+ - name: pos-files
79
+ mountPath: /mnt/pos/
80
+ env:
81
+ - name: COLLECTOR_URL
82
+ valueFrom:
83
+ secretKeyRef:
84
+ name: sumologic
85
+ key: collector-url
86
+ tolerations:
87
+ #- operator: "Exists"
88
+ - effect: "NoSchedule"
89
+ key: "node-role.kubernetes.io/master"
90
+
data/entrypoint.sh ADDED
@@ -0,0 +1,16 @@
1
+ #!/bin/bash
2
+ if [ $# -gt 0 ] && [ "${1:0:1}" != "-" ]; then
3
+ exec $@
4
+ else
5
+ cd `dirname $0`
6
+
7
+ if [ $FLUENTD_SOURCE != file ] && [ $FLUENTD_SOURCE != systemd ]; then
8
+ echo "Unknown source '$FLUENTD_SOURCE'"
9
+ if [ -e /dev/termination-log ]; then
10
+ echo "Unknown source '$FLUENTD_SOURCE'" >/dev/termination-log
11
+ fi
12
+ exit 1
13
+ fi
14
+
15
+ exec fluentd -c /fluentd/etc/fluent.$FLUENTD_SOURCE.conf -p /fluentd/plugins $FLUENTD_OPT
16
+ fi
@@ -0,0 +1,13 @@
1
+ <match containers.**.fluentd**>
2
+ @type null
3
+ </match>
4
+
5
+ <source>
6
+ @type monitor_agent
7
+ bind 0.0.0.0
8
+ port 24220
9
+ </source>
10
+
11
+ @include /fluentd/conf.d/file/source.*.conf
12
+ @include /fluentd/conf.d/user/*.conf
13
+ @include /fluentd/conf.d/out.sumo.conf
@@ -0,0 +1,13 @@
1
+ <match containers.**.fluentd**>
2
+ @type null
3
+ </match>
4
+
5
+ <source>
6
+ @type monitor_agent
7
+ bind 0.0.0.0
8
+ port 24220
9
+ </source>
10
+
11
+ @include /fluentd/conf.d/systemd/source.*.conf
12
+ @include /fluentd/conf.d/user/*.conf
13
+ @include /fluentd/conf.d/out.sumo.conf
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "fluent-plugin-kubernetes_sumologic"
7
+ gem.version = "0.0.1"
8
+ gem.authors = ["Frank Reno"]
9
+ gem.email = ["frank.reno@me.com"]
10
+ gem.description = %q{FluentD plugin to extract logs from Kubernetes clusters, enrich and ship to Sumo logic.}
11
+ gem.summary = %q{FluentD plugin to extract logs from Kubernetes clusters, enrich and ship to Sumo logic.}
12
+ gem.homepage = "https://github.com/SumoLogic/fluentd-kubernetes-sumologic"
13
+ gem.license = "Apache-2.0"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.has_rdoc = false
20
+
21
+ gem.required_ruby_version = '>= 2.0.0'
22
+
23
+ gem.add_development_dependency "bundler", "~> 2"
24
+ gem.add_development_dependency "rake"
25
+ gem.add_development_dependency 'test-unit', '~> 3.1.0'
26
+ gem.add_development_dependency "codecov", ">= 0.1.10"
27
+ gem.add_runtime_dependency "fluentd", ">= 0.14.12"
28
+ gem.add_runtime_dependency 'httpclient', '~> 2.8.0'
29
+ end
@@ -0,0 +1,201 @@
1
+ require "fluent/filter"
2
+
3
+ module Fluent::Plugin
4
+ class SumoContainerOutput < Fluent::Plugin::Filter
5
+ # Register type
6
+ Fluent::Plugin.register_filter("kubernetes_sumologic", self)
7
+
8
+ config_param :kubernetes_meta, :bool, :default => true
9
+ config_param :kubernetes_meta_reduce, :bool, :default => false
10
+ config_param :source_category, :string, :default => "%{namespace}/%{pod_name}"
11
+ config_param :source_category_replace_dash, :string, :default => "/"
12
+ config_param :source_category_prefix, :string, :default => "kubernetes/"
13
+ config_param :source_name, :string, :default => "%{namespace}.%{pod}.%{container}"
14
+ config_param :log_format, :string, :default => "json"
15
+ config_param :source_host, :string, :default => ""
16
+ config_param :exclude_container_regex, :string, :default => ""
17
+ config_param :exclude_facility_regex, :string, :default => ""
18
+ config_param :exclude_host_regex, :string, :default => ""
19
+ config_param :exclude_namespace_regex, :string, :default => ""
20
+ config_param :exclude_pod_regex, :string, :default => ""
21
+ config_param :exclude_priority_regex, :string, :default => ""
22
+ config_param :exclude_unit_regex, :string, :default => ""
23
+ config_param :add_stream, :bool, :default => true
24
+ config_param :add_time, :bool, :default => true
25
+
26
+ def configure(conf)
27
+ super
28
+ end
29
+
30
+ def is_number?(string)
31
+ true if Float(string) rescue false
32
+ end
33
+
34
+ def sanitize_pod_name(k8s_metadata)
35
+ # Strip out dynamic bits from pod name.
36
+ # NOTE: Kubernetes deployments append a template hash.
37
+ # At the moment this can be in 3 different forms:
38
+ # 1) pre-1.8: numeric in pod_template_hash and pod_parts[-2]
39
+ # 2) 1.8-1.11: numeric in pod_template_hash, hash in pod_parts[-2]
40
+ # 3) post-1.11: hash in pod_template_hash and pod_parts[-2]
41
+
42
+ pod_parts = k8s_metadata[:pod].split("-")
43
+ pod_template_hash = k8s_metadata[:"label:pod-template-hash"]
44
+ if (pod_template_hash == pod_parts[-2] ||
45
+ to_hash(pod_template_hash) == pod_parts[-2])
46
+ k8s_metadata[:pod_name] = pod_parts[0..-3].join("-")
47
+ else
48
+ k8s_metadata[:pod_name] = pod_parts[0..-2].join("-")
49
+ end
50
+ end
51
+
52
+ def to_hash(pod_template_hash)
53
+ # Convert the pod_template_hash to an alphanumeric string using the same logic Kubernetes
54
+ # uses at https://github.com/kubernetes/apimachinery/blob/18a5ff3097b4b189511742e39151a153ee16988b/pkg/util/rand/rand.go#L119
55
+ alphanums = "bcdfghjklmnpqrstvwxz2456789"
56
+ pod_template_hash.each_byte.map { |i| alphanums[i.to_i % alphanums.length] }.join("")
57
+ end
58
+
59
+ def filter(tag, time, record)
60
+ # Set the sumo metadata fields
61
+ sumo_metadata = record["_sumo_metadata"] || {}
62
+ record["_sumo_metadata"] = sumo_metadata
63
+ sumo_metadata[:log_format] = @log_format
64
+ sumo_metadata[:host] = @source_host if @source_host
65
+ sumo_metadata[:source] = @source_name if @source_name
66
+
67
+ unless @source_category.nil?
68
+ sumo_metadata[:category] = @source_category.dup
69
+ unless @source_category_prefix.nil?
70
+ sumo_metadata[:category].prepend(@source_category_prefix)
71
+ end
72
+ end
73
+
74
+ if record.key?("_SYSTEMD_UNIT") and not record.fetch("_SYSTEMD_UNIT").nil?
75
+ unless @exclude_unit_regex.empty?
76
+ if Regexp.compile(@exclude_unit_regex).match(record["_SYSTEMD_UNIT"])
77
+ return nil
78
+ end
79
+ end
80
+
81
+ unless @exclude_facility_regex.empty?
82
+ if Regexp.compile(@exclude_facility_regex).match(record["SYSLOG_FACILITY"])
83
+ return nil
84
+ end
85
+ end
86
+
87
+ unless @exclude_priority_regex.empty?
88
+ if Regexp.compile(@exclude_priority_regex).match(record["PRIORITY"])
89
+ return nil
90
+ end
91
+ end
92
+
93
+ unless @exclude_host_regex.empty?
94
+ if Regexp.compile(@exclude_host_regex).match(record["_HOSTNAME"])
95
+ return nil
96
+ end
97
+ end
98
+ end
99
+
100
+ # Allow fields to be overridden by annotations
101
+ if record.key?("kubernetes") and not record.fetch("kubernetes").nil?
102
+ # Clone kubernetes hash so we don't override the cache
103
+ kubernetes = record["kubernetes"].clone
104
+ k8s_metadata = {
105
+ :namespace => kubernetes["namespace_name"],
106
+ :pod => kubernetes["pod_name"],
107
+ :pod_id => kubernetes['pod_id'],
108
+ :container => kubernetes["container_name"],
109
+ :source_host => kubernetes["host"],
110
+ }
111
+
112
+
113
+ if kubernetes.has_key? "labels"
114
+ kubernetes["labels"].each { |k, v| k8s_metadata["label:#{k}".to_sym] = v }
115
+ end
116
+ k8s_metadata.default = "undefined"
117
+
118
+ annotations = kubernetes.fetch("annotations", {})
119
+ if annotations["sumologic.com/include"] == "true"
120
+ include = true
121
+ else
122
+ include = false
123
+ end
124
+
125
+ unless @exclude_namespace_regex.empty?
126
+ if Regexp.compile(@exclude_namespace_regex).match(k8s_metadata[:namespace]) and not include
127
+ return nil
128
+ end
129
+ end
130
+
131
+ unless @exclude_pod_regex.empty?
132
+ if Regexp.compile(@exclude_pod_regex).match(k8s_metadata[:pod]) and not include
133
+ return nil
134
+ end
135
+ end
136
+
137
+ unless @exclude_container_regex.empty?
138
+ if Regexp.compile(@exclude_container_regex).match(k8s_metadata[:container]) and not include
139
+ return nil
140
+ end
141
+ end
142
+
143
+ unless @exclude_host_regex.empty?
144
+ if Regexp.compile(@exclude_host_regex).match(k8s_metadata[:source_host]) and not include
145
+ return nil
146
+ end
147
+ end
148
+
149
+ sanitize_pod_name(k8s_metadata)
150
+
151
+ if annotations["sumologic.com/exclude"] == "true"
152
+ return nil
153
+ end
154
+
155
+ sumo_metadata[:log_format] = annotations["sumologic.com/format"] if annotations["sumologic.com/format"]
156
+
157
+ if annotations["sumologic.com/sourceHost"].nil?
158
+ sumo_metadata[:host] = sumo_metadata[:host] % k8s_metadata
159
+ else
160
+ sumo_metadata[:host] = annotations["sumologic.com/sourceHost"] % k8s_metadata
161
+ end
162
+
163
+ if annotations["sumologic.com/sourceName"].nil?
164
+ sumo_metadata[:source] = sumo_metadata[:source] % k8s_metadata
165
+ else
166
+ sumo_metadata[:source] = annotations["sumologic.com/sourceName"] % k8s_metadata
167
+ end
168
+
169
+ if annotations["sumologic.com/sourceCategory"].nil?
170
+ sumo_metadata[:category] = sumo_metadata[:category] % k8s_metadata
171
+ else
172
+ sumo_metadata[:category] = (annotations["sumologic.com/sourceCategory"] % k8s_metadata).prepend(@source_category_prefix)
173
+ end
174
+ sumo_metadata[:category].gsub!("-", @source_category_replace_dash)
175
+
176
+ # Strip kubernetes metadata from json if disabled
177
+ if annotations["sumologic.com/kubernetes_meta"] == "false" || !@kubernetes_meta
178
+ record.delete("docker")
179
+ record.delete("kubernetes")
180
+ end
181
+ if annotations["sumologic.com/kubernetes_meta_reduce"] == "true" || annotations["sumologic.com/kubernetes_meta_reduce"].nil? && @kubernetes_meta_reduce == true
182
+ record.delete("docker")
183
+ record["kubernetes"].delete("pod_id")
184
+ record["kubernetes"].delete("namespace_id")
185
+ record["kubernetes"].delete("labels")
186
+ record["kubernetes"].delete("master_url")
187
+ record["kubernetes"].delete("annotations")
188
+ end
189
+ if @add_stream == false
190
+ record.delete("stream")
191
+ end
192
+ if @add_time == false
193
+ record.delete("time")
194
+ end
195
+ # Strip sumologic.com annotations
196
+ kubernetes.delete("annotations") if annotations
197
+ end
198
+ record
199
+ end
200
+ end
201
+ end
Binary file
Binary file
Binary file
Binary file
data/test/helper.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "simplecov"
2
+ SimpleCov.start
3
+
4
+ if ENV["CI"] == "true"
5
+ require "codecov"
6
+ SimpleCov.formatter = SimpleCov::Formatter::Codecov
7
+ end
8
+
9
+ $LOAD_PATH.unshift(File.expand_path("../../", __FILE__))
10
+ require "test-unit"
11
+ require "fluent/test"
12
+ require "fluent/test/driver/filter"
13
+ require "fluent/test/helpers"
14
+
15
+ Test::Unit::TestCase.include(Fluent::Test::Helpers)
16
+ Test::Unit::TestCase.extend(Fluent::Test::Helpers)