forked from RedisLabs/redis-enterprise-k8s-docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
log_collector.py
executable file
·281 lines (226 loc) · 8.8 KB
/
log_collector.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#!/usr/bin/env python
# Redis Enterprise Cluster log collector script.
# Creates a directory with output of kubectl for several API objects and for pods logs
# unless pass a -n parameter will run on current namespace. Run with -h to see options
import argparse
import yaml
import logging
import os
import re
import subprocess
import sys
import tarfile
import time
from collections import OrderedDict
import shutil
import json
logger = logging.getLogger("log collector")
TIME_FORMAT = time.strftime("%Y%m%d-%H%M%S")
api_resources = [
"RedisEnterpriseCluster",
"StatefulSet",
"Deployment",
"Services",
"ConfigMap",
"Routes",
"Ingress",
]
def make_dir(directory):
if not os.path.exists(directory):
# noinspection PyBroadException
try:
os.mkdir(directory)
except:
logger.exception("Could not create directory %s - exiting", directory)
sys.exit()
def run(namespace, output_dir):
if not namespace:
namespace = get_namespace_from_config()
if not output_dir:
output_dir_name = "redis_enterprise_k8s_debug_info_{}".format(TIME_FORMAT)
output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), output_dir_name)
make_dir(output_dir)
get_redis_enterprise_debug_info(namespace, output_dir)
collect_cluster_info(output_dir)
collect_resources_list(output_dir)
collect_events(namespace, output_dir)
collect_api_resources(namespace, output_dir)
collect_pods_logs(namespace, output_dir)
archive_files(output_dir, output_dir_name)
logger.info("Finished Redis Enterprise log collector")
def get_redis_enterprise_debug_info(namespace, output_dir):
"""
Connects to an RS cluster node, creates and copies debug info package from the pod
"""
pod_names = get_pod_names(namespace, selector='redis.io/role=node')
if not pod_names:
logger.warning("Cannot find redis enterprise pod")
return
pod_name = pod_names[0]
cmd = "kubectl -n {} exec {} /opt/redislabs/bin/rladmin cluster debug_info path /tmp".format(namespace, pod_name)
rc, out = run_shell_command(cmd)
if "Downloading complete" not in out:
logger.warning("Failed running rladmin command in pod: {}".format(out))
return
# get the debug file name
match = re.search(r'File (/tmp/(.*\.gz))', out)
if match:
debug_file_path = match.group(1)
debug_file_name = match.group(2)
logger.info("debug info created on pod {} in path {}".format(pod_name, debug_file_path))
else:
logger.warning(
"Failed to extract debug info name from output - "
"Skipping collecting Redis Enterprise cluster debug info".format(out))
return
# copy package from RS pod
output_path = os.path.join(output_dir, debug_file_name)
cmd = "kubectl -n {} cp {}:{} {}".format(namespace, pod_name, debug_file_path, output_path)
rc, out = run_shell_command(cmd)
if rc:
logger.warning(
"Failed to debug info from pod to output directory".format(out))
return
logger.info("Collected Redis Enterprise cluster debug package")
def collect_resources_list(output_dir):
"""
Prints the output of kubectl get all to a file
"""
collect_helper(output_dir, cmd="kubectl get all", file_name="resources_list", resource_name="resources list")
def collect_cluster_info(output_dir):
"""
Prints the output of kubectl cluster-info to a file
"""
collect_helper(output_dir, cmd="kubectl cluster-info", file_name="cluster_info", resource_name="cluster-info")
def collect_events(namespace, output_dir):
"""
Prints the output of kubectl cluster-info to a file
"""
# events need -n parameter in kubectl
if not namespace:
logger.warning("Cannot collect events without namespace - skipping events collection")
return
cmd = "kubectl get events -n {}".format(namespace)
collect_helper(output_dir, cmd=cmd, file_name="events", resource_name="events")
def collect_api_resources(namespace, output_dir):
"""
Creates file for each of the API resources with the output of kubectl get <resource> -o yaml
"""
logger.info("Collecting API resources:")
resources_out = OrderedDict()
for resource in api_resources:
output = run_kubectl_get(namespace, resource)
if output:
resources_out[resource] = run_kubectl_get(namespace, resource)
logger.info(" + {}".format(resource))
for entry, out in resources_out.items():
with open(os.path.join(output_dir, "{}.yaml".format(entry)), "w+") as fp:
fp.write(out)
def collect_pods_logs(namespace, output_dir):
"""
Collects all the pods logs from given namespace
"""
logger.info("Collecting pods' logs:")
logs_dir = os.path.join(output_dir, "pods")
make_dir(logs_dir)
pods = get_pod_names(namespace)
if not pods:
logger.warning("Could not get pods list - skipping pods logs collection")
return
for pod in pods:
cmd = "kubectl logs -n {} {} --all-containers=true".format(namespace, pod)
with open(os.path.join(logs_dir, "{}.log".format(pod)), "w+") as fp:
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
# read one line a time - we do not want to read large files to memory
line = native_string(p.stdout.readline())
if line:
fp.write(line)
else:
break
logger.info(" + {}".format(pod))
def archive_files(output_dir, output_dir_name):
file_name = output_dir + ".tar.gz"
with tarfile.open(file_name, "w|gz") as tar:
tar.add(output_dir, arcname=output_dir_name)
logger.info("Archived files into {}".format(file_name))
try:
shutil.rmtree(output_dir)
except OSError as e:
logger.warning("Failed to delete directory after archiving: %s", e)
def get_pod_names(namespace, selector=""):
"""
Returns list of pods names
"""
if selector:
selector = '--selector="{}"'.format(selector)
cmd = 'kubectl get pod -n {} {} -o json '.format(namespace, selector)
rc, out = run_shell_command(cmd)
if rc:
logger.warning("Failed to get pod names: {}".format(out))
return
pods_json = json.loads(out)
return [pod['metadata']['name'] for pod in pods_json['items']]
def get_namespace_from_config():
"""
Returns the namespace from current context if one is set OW None
"""
# find namespace from config file
cmd = "kubectl config view -o yaml"
rc, out = run_shell_command(cmd)
if rc:
return
config = yaml.safe_load(out)
current_context = config.get('current-context')
if not current_context:
return
for context in config.get('contexts', []):
if context['name'] == current_context:
if context['context'].get("namespace"):
return context['context']["namespace"]
break
def collect_helper(output_dir, cmd, file_name, resource_name):
"""
Runs command, write output to file_name, logs the resource_name
"""
rc, out = run_shell_command(cmd)
if rc:
logger.warning("Error when running {}: {}".format(cmd, out))
return
path = os.path.join(output_dir, file_name)
with open(path, "w+") as fp:
fp.write(out)
logger.info("Collected {}".format(resource_name))
def native_string(x):
return x if isinstance(x, str) else x.decode('utf-8', 'replace')
def run_shell_command(cmd):
"""
Returns a tuple of the shell exit code, output
"""
try:
output = subprocess.check_output(
cmd,
shell=True,
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as ex:
logger.warning("Failed in shell command: {}, output: {}".format(cmd, ex.output))
return ex.returncode, ex.output
return 0, native_string(output)
def run_kubectl_get(namespace, resource_type):
"""
Runs kubectl get command
"""
cmd = "kubectl get -n {} {} -o yaml".format(namespace, resource_type)
rc, out = run_shell_command(cmd)
if rc == 0:
return out
logger.warning("Failed to get {} resource: {}".format(resource_type, out))
if __name__ == "__main__":
logger.setLevel(logging.INFO)
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s')
parser = argparse.ArgumentParser(description='Redis Enterprise K8s log collector')
parser.add_argument('-n', '--namespace', action="store", type=str)
parser.add_argument('-o', '--output_dir', action="store", type=str)
results = parser.parse_args()
logger.info("Started Redis Enterprise k8s log collector")
run(results.namespace, results.output_dir)