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.
- 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)
|