fluent-plugin-kubernetes_sumologic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)