fluent-plugin-kubernetes_sumologic 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +101 -0
- data/Dockerfile +70 -0
- data/Gemfile +9 -0
- data/LICENSE +201 -0
- data/README.md +545 -0
- data/Rakefile +11 -0
- data/ci/build.sh +39 -0
- data/conf.d/file/source.containers.conf +64 -0
- data/conf.d/file/source.docker.conf +20 -0
- data/conf.d/file/source.kubernetes.conf +158 -0
- data/conf.d/out.sumo.conf +13 -0
- data/conf.d/systemd/source.containers.conf +47 -0
- data/conf.d/systemd/source.systemd.conf +1088 -0
- data/daemonset/nonrbac/fluentd.yaml +51 -0
- data/daemonset/rbac/fluentd.yaml +90 -0
- data/entrypoint.sh +16 -0
- data/etc/fluent.file.conf +13 -0
- data/etc/fluent.systemd.conf +13 -0
- data/fluent-plugin-kubernetes_sumologic.gemspec +29 -0
- data/lib/fluent/plugin/filter_kubernetes_sumologic.rb +201 -0
- data/screenshots/container.png +0 -0
- data/screenshots/docker.png +0 -0
- data/screenshots/kubelet.png +0 -0
- data/screenshots/kubernetes.png +0 -0
- data/test/helper.rb +16 -0
- data/test/plugin/test_filter_kubernetes_sumologic.rb +1473 -0
- metadata +161 -0
@@ -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)
|