jsonpath for nested arrays in kubectl get - kubectl

I am trying to get the resource limits & requests for Kubernetes pods. I am attempting to output to a comma delimited row that lists the namespace, pod name, container name and then the mem & CPU limits/requests for each container. Running into issues when there's multiple containers per pod.
The closest I've been able to get is this which will print out a single row for each pod. If there are multiple containers, they are listed in separate "columns" in the same row.
kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{#.metadata.namespace}{","}{#.metadata.name}{","}{range .spec.containers[*]}{.name}{","}{#.resources.requests.cpu}{","}{#.resources.requests.memory}{","}{#.resources.limits.cpu}{","}{#.resources.limits.memory}{","}{end}{"\n"}{end}'
The output looks like this:
kube-system,metrics-server-5f8d84558d-g926z,metrics-server-vpa,5m,30Mi,100m,300Mi,metrics-server,46m,63Mi,46m,63Mi,
What I would like to see is something like this:
kube-system,metrics-server-5f8d84558d-g926z,metrics-server-vpa,5m,30Mi,100m,300Mi,
kube-system,metrics-server-5f8d84558d-g926z,metrics-server,46m,63Mi,46m,63Mi,
Appreciate any assistance. Thanks.

I think (don't know that) you can't using only kubectl's (limited) JSONPath.
There's a UNIX principle that each tool should do one thing well:
kubectl does Kubernetes stuff well and can output JSON
jq does JSON processing well.
If you're willing to use another tool:
FILTER='
.items[]
|.metadata as $meta
|.spec.containers[]
|.name as $name
|.resources.requests as $requests
|.resources.limits as $limits
|[
$meta.namespace,
$meta.name,$name,
$requests.cpu,
$requests.memory,
$limits.cpu,
$limits.memory
]
|#csv
'
kubectl get pods \
--all-namespaces \
--output=json \
| jq -r "${FILTER}"
Explanation:
For each items (i.e. each Pod)
Set the variable meta to the (Pod's) metadata content
For each containers (i.e. each Container)
Set the variable name as the (Container's) name
Set the variable requests as the (Container's Resources') requests
Set the variable limits as the (Container's Resources') limits
Create an array ([...]) by reassembling the relevant pieces
Output the arrays as comma-delimited
On a cluster:
"monitoring","prometheus-adapter-59df95d9f5-kg4hc","prometheus-adapter",,,,
"monitoring","prometheus-adapter-59df95d9f5-j6rbx","prometheus-adapter",,,,
"monitoring","prometheus-operator-7775c66ccf-45z2f","prometheus-operator","100m","100Mi","200m","200Mi"
"monitoring","prometheus-operator-7775c66ccf-45z2f","kube-rbac-proxy","10m","20Mi","20m","40Mi"
"monitoring","node-exporter-7cf4m","node-exporter","102m","180Mi","250m","180Mi"
"monitoring","node-exporter-7cf4m","kube-rbac-proxy","10m","20Mi","20m","40Mi"
"monitoring","kube-state-metrics-76f6cb7996-hdxcb","kube-state-metrics","10m","190Mi","100m","250Mi"
"monitoring","kube-state-metrics-76f6cb7996-hdxcb","kube-rbac-proxy-main","20m","20Mi","40m","40Mi"
"monitoring","kube-state-metrics-76f6cb7996-hdxcb","kube-rbac-proxy-self","10m","20Mi","20m","40Mi"
"monitoring","blackbox-exporter-55c457d5fb-x6hwj","blackbox-exporter","10m","20Mi","20m","40Mi"
"monitoring","blackbox-exporter-55c457d5fb-x6hwj","module-configmap-reloader","10m","20Mi","20m","40Mi"
"monitoring","blackbox-exporter-55c457d5fb-x6hwj","kube-rbac-proxy","10m","20Mi","20m","40Mi"
"monitoring","grafana-6dd5b5f65-6jwq8","grafana","100m","100Mi","200m","200Mi"
"monitoring","alertmanager-main-0","alertmanager","4m","100Mi","100m","100Mi"
"monitoring","alertmanager-main-0","config-reloader","100m","50Mi","100m","50Mi"
"kube-system","coredns-7f9c69c78c-2zx4h","coredns","100m","70Mi",,"170Mi"
"monitoring","prometheus-k8s-0","prometheus",,"400Mi",,
"monitoring","prometheus-k8s-0","config-reloader","100m","50Mi","100m","50Mi"
"kube-system","calico-kube-controllers-5f7575cc96-6tf8x","calico-kube-controllers",,,,
"kube-system","calico-node-m78xm","calico-node","250m",,,

Here is one way to obtain the output natively using kubectl via the go-template output format. jsonpath is not the right tool(maybe doable) for this requirement; perhaps piping to jq or go-template is the appropriate solution.
kubectl get pod -o go-template='{{- range $index, $element := .items -}}
{{- range $container, $status := $element.spec.containers -}}
{{- printf "%s,%s,%s,%s,%s,%s,%s\n" $element.metadata.namespace $element.metadata.name $status.name (or $status.resources.requests.cpu "" ) (or $status.resources.requests.memory "") (or $status.resources.limits.memory "") (or $status.resources.limits.cpu "") -}}
{{- end -}}
{{- end -}}'

Related

How to set variable inline in gitlab-ci.yaml based on regex matching?

I am trying to create a variable in gitlab-ci.yaml based on the name of the branch.
Suppose I am pushing to a branch named 3.2.7
Here is the situation:
include:
- template: "Workflows/Branch-Pipelines.gitlab-ci.yml"
variables:
PRODUCTION_BRANCH: "master"
STAGING_BRANCH: (\d)\.(\d)\.(\d)
.deploy_rules:
rules:
- if: '$CI_COMMIT_BRANCH =~ /$STAGING_BRANCH/'
variables:
SERVER_PORT: 3007 # TODO: should be 300d ; d is the second digit
I want to generate 3002 inline using regex matching.
How can I do this?
I have done some research and seems I have to use sed but I am not sure if it is the best way to do it and how to do it.
TO MAKE THE PROBLEM SIMPLER
include:
- template: "Workflows/Branch-Pipelines.gitlab-ci.yml"
variables:
TEST_VAR: sed -E 's/(\d)\.(\d)\.(\d)/300\2/gm;t;d' <<< $CI_COMMIT_BRANCH
stages:
- temp
temp:
stage: temp
script:
- echo $TEST_VAR
Should be echoing 3002 but it is echoing sed -E 's/(\d)\.(\d)\.(\d)/300\2/gm;t;d' <<< 3.2.7
You can't use variables in the regex pattern. You just have to write the regex verbatim, it cannot be directly parameterized. You also cannot use sed or other Linux utilities in variables: or other parts of your yaml. You're bound to the limitations of YAML specification and features provided by GitLab.
However, there is an option available to you that will fit your stated use case.
Dynamic variables
TEST_VAR: sed -E 's/(\d).(\d).(\d)/300\2/gm;t;d' <<< $CI_COMMIT_BRANCH
While you can't use sed or other utilities directly in variables: declarations, you can use dotenv artifacts via artifacts:reports:dotenv to set variables dynamically.
For example, a job can use sed or whatever other utilities you like to create variables which will be used by the rest of the pipeline.
stages:
- temp
create_variables:
stage: .pre
script:
- TEST_VAR="$(sed -E 's/(\d)\.(\d)\.(\d)/300\2/gm;t;d' <<< ${CI_COMMIT_BRANCH})"
- echo "TEST_VAR=${TEST_VAR}" >> dotenv.txt
artifacts:
reports:
dotenv: dotenv.txt
temp:
stage: temp
script:
- echo $TEST_VAR
Here, the .pre stage is used, which is a special stage that is always ordered before every other stage. The dotenv artifact from the create_variables job will dynamically create variables for the jobs in subsequent stages that receive the artifact.

Format gcloud compute instances list to get single metadata value

As part of some GCP admin automation I'm trying to run a gcloud compute instances list command to return a few instance properties, one of which is a single metadata property. I cannot find in the documentation how to return only a single metadata property.
This is what I would think is correct based on the doc, but I don't get any metadata properties returned...
gcloud compute instances list --filter="name~^my-machine.*-type" --zones=zone1,zone2,zone3 --format="json(name,metadata.items.MY_VALUE)"
How can I return a single metadata value?
Eesh... this was not obvious ;-)
KEY=...
gcloud compute instances list \
--project=${PROJECT} \
--format="value(metadata.items.extract("${KEY}"))"
See extract
I'm not sure why it works.
In my case:
gcloud compute instances list \
--project=${PROJECT} \
--format="value(metadata.items)"
Yields:
{'key': 'gce-container-declaration', 'value': "..."};
{'key': 'google-logging-enabled', 'value': 'true'}
NOTE a semi-colon separated list of JSON objects
So, metadata.items appears to be a list of JSON objects {"key": $KEY, "value": $VALUE} and I think this why you can't walk down through the values using something ... metadata.items.key["google-logging-enabled"] or similar.
When I initially looked using YAML, this wasn't obvious and I think, even though the YAML looks flat, the items are embedded and the --format=yaml is doing something clever:
gcloud compute instances list \
--project=${PROJECT} \
--format="yaml(metadata.items)"
---
metadata:
items:
- key: gce-container-declaration
value: |-
...
- key: google-logging-enabled
value: 'true'
But:
gcloud compute instances list \
--project=${PROJECT} \
--format="value(metadata.items.extract("gce-container-declaration"))"
Yields:
spec:
containers:
- name: instance-1
image: ...
stdin: false
tty: false
restartPolicy: Always

How to get all pods without jobs

Is it possible to retrieve all pods without taking jobs?
kubectl get pods
pod1 1/1 Running 1 28d
pod2 1/1 Running 1 28d
pods3 0/1 Completed 0 30m
pod4 0/1 Completed 0 30m
I don't want to see jobs, but only the other pod.
I don't want to fetch them basing on "Running State" because I would like to verify if all deployment I am trying to install are "deployed".
Basing on that I wanted to use the following command, but it is fetching also jobs I am trying to exclude:
kubectl wait --for=condition=Ready pods --all --timeout=600s
Add a special label (e.g. kind=pod) to your job pods. Then use kubectl get pods -l kind!=pod.
If using a bit of scripting is OK...this one-liner should return the names of all "non-Jobs" pods in all namespaces:
for p in `kubectl get pods --all-namespaces -o=jsonpath="{range .items[*]}{.metadata.name}{';'}{.metadata.ownerReferences[?(#.kind != 'Job')].name}{'\n'}{end}"`; do v_owner_name=$(echo $p | cut -d';' -f2); if [ ! -z "$v_owner_name" ]; then v_pod_name=$(echo $p | cut -d';' -f1); echo $v_pod_name; fi; done
Using the above as a foundation, the following aims to return all "non-Jobs" pods in Ready status:
for p in `kubectl get pods --all-namespaces -o=jsonpath="{range .items[*]}{.metadata.name}{';'}{'Ready='}{.status.conditions[?(#.type == 'Ready')].status}{';'}{.metadata.ownerReferences[?(#.kind != 'Job')].name}{'\n'}{end}"`; do v_owner_name=$(echo $p | cut -d';' -f3); if [ ! -z "$v_owner_name" ]; then v_pod_name=$(echo $p | cut -d';' -f1,2); echo $v_pod_name; fi; done
This doc explains (arguably - to some degree) the JSONPath support in kubectl.
If your question is -
I would like to verify if all deployment I am trying to install are
"deployed"
Then this is not the right way of checking Pods status in Kubernetes. Please check the replicas and readyReplicas for your deployment.
kubectl get deployment <deployment-Name> -ojson | jq -r '.status | { desired: .replicas, ready: .readyReplicas }'
Output:-
{
"desired": 1,
"ready": 1
}
Here I am using jq (It's very handy) utility to parse the stuff

How to use if-else condition on gitlabci

How to use if else condition inside the gitlab-CI.
I have below code:
deploy-dev:
image: testimage
environment: dev
tags:
- kubectl
script:
- kubectl apply -f demo1 --record=true
- kubectl apply -f demo2 --record=true
Now I want to add a condition something like this
script:
- (if [ "$flag" == "true" ]; then kubectl apply -f demo1 --record=true; else kubectl apply -f demo2 --record=true);
Could someone provide the correct syntax for the same? Is there any documentation for the conditions (if-else, for loop) in gitlabci?
Hereunder three syntax options for that kind of statement. From gitlab-ci documentation :
Using shell variable
deploy-dev:
image: testimage
environment: dev
tags:
- kubectl
script:
- if [ "$flag" == "true" ]; then MODULE="demo1"; else MODULE="demo2"; fi
- kubectl apply -f ${MODULE} --record=true
Using shell variable with yaml multiline block
deploy-dev:
image: testimage
environment: dev
tags:
- kubectl
script:
- >
if [ "$flag" == "true" ]; then
kubectl apply -f demo1 --record=true
else
kubectl apply -f demo2 --record=true
fi
Using gitlab rules
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: never
- if: '$CI_PIPELINE_SOURCE == "push"'
when: never
- when: always
Using gitlab templates and variables
demo1-deploy-dev:
extends: .deploy-dev
only:
variables: [ $flag == "true" ]
variables:
MODULE: demo1
demo2-deploy-dev:
extends: .deploy-dev
only:
variables: [ $flag == "false" ]
variables:
MODULE: demo2
.deploy-dev:
image: testimage
environment: dev
tags:
- kubectl
script:
- kubectl apply -f ${MODULE} --record=true
Note that with GitLab 13.3 (August 2020), there is an improvement to the if-else rule syntax:
CI/CD rules:if support logical expressions with parentheses
If you use the rules keyword with if clauses, it’s now even more powerful, with support for bracketed expressions evaluated by the pipeline processor.
You can use more complex and efficient AND (&&) / OR (||) expressions, making your pipelines rules more logical, powerful, and easier to manage.
See Documentation and Issue.
And, with GitLab 13.8 (January 2021)
Support variables for pipeline rules
Previously, the rules keyword was limited in scope and only determined if a job should be included or excluded from pipelines. In this release, you can now decide if certain conditions are met and subsequently override variables in jobs, providing you with more flexibility when configuring your pipelines.
See Documentation and Issue.
With GitLab 13.12 (May 2021):
Support variables in CI/CD pipeline 'workflow:rules'
Previously, the rules keyword was limited in scope and only determined if a job should be included or excluded from pipelines. In 13.8, we added the ability to use the variables keyword with rules to set variable values in a job based on which rule matched.
In this release we’ve extended this ability to workflow: rules, so you can set variable values for the whole pipeline if certain conditions match.
This helps you make your pipelines even more flexible.
See Documentation and Issue.
I think you need to just add a semicolon and closing "fi" at the end.
I couldn't find a link to documentation.
script:
- (if [ "$flag" == "true" ]; then kubectl apply -f demo1 --record=true; else kubectl apply -f demo2 --record=true; fi);
In addition, in the case of a multiline block if you want or need to preserve line breaks you can use the pipe character:
script: |
if [ "$flag" == "true" ]; then
kubectl apply -f demo1 --record=true
else
kubectl apply -f demo2 --record=true
fi
To go deeper, visit https://yaml-multiline.info/
You may consider checking rules
It allows for a list of individual rule objects to be evaluated in order, until one matches and dynamically provides attributes to the job.
Available rule clauses include:
if (similar to only:variables)
changes (same as only:changes)
exists
Example:
job:
script: "echo Hello, Rules!"
rules:
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
when: always
- if: '$VAR =~ /pattern/'
when: manual
- when: on_success
This worked for me when using powershell based gitlab scripts:
script:
- 'if ($flag -eq "true") { kubectl apply -f demo1 --record=true; } else { kubectl apply -f demo2 --record=true; }'

telegraf - exec plugin - aws ec2 ebs volumen info - metric parsing error, reason: [missing fields] or Errors encountered: [ invalid number]

Machine - CentOS 7.2 or Ubuntu 14.04/16.xx
Telegraf version: 1.0.1
Python version: 2.7.5
Telegraf supports an INPUT plugin named: exec. First please see EXAMPLE 2 in the README doc there. I can't use JSON format as it only consumes Numeric values for metrics. As per the docs:
If using JSON, only numeric values are parsed and turned into floats. Booleans and strings will be ignored.
So, the idea is simple, you specify a script in exec plugin section, which should spit some meaningful info(in either JSON -or- influx data format in my case as I have some metrics which contains non-numeric values) which you would want to catch/show somewhere in a cool dashboard like for example Wavefront Dashboard shown here:
:
Basically one can use these metrics, tags, sources from where these metrics are coming from to find out various info about memory, cpu, disk, networking, other meaningful info and also create alerts using those if something unwanted happens.
OK, I came up with this python script available here:
#!/usr/bin/python
# sudo pip install boto3 if you don't have it on your machine.
import boto3
def generate(key, value):
"""
Creates a nicely formatted Key(Value) item for output
"""
return '{}="{}"'.format(key, value)
#return '{}={}'.format(key, value)
def main():
ec2 = boto3.resource('ec2', region_name="us-west-2")
volumes = ec2.volumes.all()
for vol in volumes:
# You don't need to wrap everything in `str` unless it is not a string
# By default most things will come back as a string
# unless they are very obviously not (complex, date time, etc)
# but since we are printing these (and formatting them into strings)
# the cast to string will be implicit and we don't need to make it
# explicit
# vol is already a fully returned volume you are essentially DOUBLING
# your API calls when you do this
#iv = ec2.Volume(vol.id)
output_parts = [
# Volume level details
generate('create_time', vol.create_time),
generate('availability_zone', vol.availability_zone),
generate('volume_id', vol.volume_id),
generate('volume_type', vol.volume_type),
generate('state', vol.state),
generate('size', vol.size),
generate('iops', vol.iops),
generate('encrypted', vol.encrypted),
generate('snapshot_id', vol.snapshot_id),
generate('kms_key_id', vol.kms_key_id),
]
for _ in vol.attachments:
# Will get any attachments and since it is a list
# we should write this to handle MULTIPLE attachments
output_parts.extend([
generate('InstanceId', _.get('InstanceId')),
generate('InstanceVolumeState', _.get('State')),
generate('DeleteOnTermination', _.get('DeleteOnTermination')),
generate('Device', _.get('Device')),
])
# only process when there are tags to process
if vol.tags:
for _ in vol.tags:
# Get all of the tags
output_parts.extend([
generate(_.get('Key'), _.get('Value')),
])
# output everything at once..
print ','.join(output_parts)
if __name__ == '__main__':
main()
This script will talk to AWS EC2 EBS volumes and outputs all values it can find (usually what you see in AWS EC2 EBS volume console) and format that info into a meaningful CSV format which I'm redirecting to a .csv log file.
We don't want to run the python script all the time (AWS API limits / cost factor).
So, once the .csv file is created, I created this small shell script which I'll set in Telegraf's exec plugin's section.
Shell script /tmp/aws-vol-info.sh set in Telegraf exec plugin is:
#!/bin/bash
cat /tmp/aws-vol-info.csv
Telegraf configuration file created using exec plugin (/etc/telegraf/telegraf.d/exec-plugin-aws-info.conf):
#--- https://github.com/influxdata/telegraf/tree/master/plugins/inputs/exec
[[inputs.exec]]
commands = ["/tmp/aws-vol-info.sh"]
## Timeout for each command to complete.
timeout = "5s"
# Data format to consume.
# NOTE json only reads numerical measurements, strings and booleans are ignored.
data_format = "influx"
name_suffix = "_telegraf_execplugin"
I tweaked the .py (Python script for generate function) to generate the following three type of output formats (.csv file) and wanted to test how telegraf would handle this data before I enable the config file (/etc/telegraf/telegraf.d/catch-aws-ebs-info.conf) and restart telegraf service.
Format 1: (with double quotes " wrapped for every value)
create_time="2017-01-09 23:24:29.428000+00:00",availability_zone="us-east-2b",volume_id="vol-058e1d47dgh721121",volume_type="gp2",state="in-use",size="8",iops="100",encrypted="False",snapshot_id="snap-06h1h1b91bh662avn",kms_key_id="None",InstanceId="i-0jjb1boop26f42f50",InstanceVolumeState="attached",DeleteOnTermination="True",Device="/dev/sda1",Name="[company-2b-app90] secondary",hostname="company-2b-app90-i-0jjb1boop26f42f50",high_availability="1",mirror="secondary",cluster="company",autoscale="true",role="app"
Testing telegraf configuration on the telegraf directory gives me the following error.
Command: $ telegraf --config-directory=/etc/telegraf --test --input-filter=exec
[vagrant#myvagrant ~] $ telegraf --config-directory=/etc/telegraf --test --input-filter=exec
2017/03/10 00:37:48 I! Using config file: /etc/telegraf/telegraf.conf
* Plugin: inputs.exec, Collection 1
2017-03-10T00:37:48Z E! Errors encountered: [ metric parsing error, reason: [invalid field format], buffer: [create_time="2017-01-09 23:24:29.428000+00:00",availability_zone="us-east-2b",volume_id="vol-058e1d47dgh721121",volume_type="gp2",state="in-use",size="8",iops="100",encrypted="False",snapshot_id="snap-06h1h1b91bh662avn",kms_key_id="None",InstanceId="i-0jjb1boop26f42f50",InstanceVolumeState="attached",DeleteOnTermination="True",Device="/dev/sda1",Name="[company-2b-app90] secondary",hostname="company-2b-app90-i-0jjb1boop26f42f50",high_availability="1",mirror="secondary",cluster="company",autoscale="true",role="app"], index: [372]]
[vagrant#myvagrant ~] $
Format 2: (without any " double quotes)
create_time=2017-01-09 23:24:29.428000+00:00,availability_zone=us-east-2b,volume_id=vol-058e1d47dgh721121,volume_type=gp2,state=in-use,size=8,iops=100,encrypted=False,snapshot_id=snap-06h1h1b91bh662avn,kms_key_id=None,InstanceId=i-0jjb1boop26f42f50,InstanceVolumeState=attached,DeleteOnTermination=True,Device=/dev/sda1,Name=[company-2b-app90] secondary,hostname=company-2b-app90-i-0jjb1boop26f42f50,high_availability=1,mirror=secondary,cluster=company,autoscale=true,role=app
Getting same error while testing Telegraf's configuration for exec plugin:
2017/03/10 00:45:01 I! Using config file: /etc/telegraf/telegraf.conf
* Plugin: inputs.exec, Collection 1
2017-03-10T00:45:01Z E! Errors encountered: [ metric parsing error, reason: [invalid value], buffer: [create_time=2017-01-09 23:24:29.428000+00:00,availability_zone=us-east-2b,volume_id=vol-058e1d47dgh721121,volume_type=gp2,state=in-use,size=8,iops=100,encrypted=False,snapshot_id=snap-06h1h1b91bh662avn,kms_key_id=None,InstanceId=i-0jjb1boop26f42f50,InstanceVolumeState=attached,DeleteOnTermination=True,Device=/dev/sda1,Name=[company-2b-app90] secondary,hostname=company-2b-app90-i-0jjb1boop26f42f50,high_availability=1,mirror=secondary,cluster=company,autoscale=true,role=app], index: [63]]
Format 3: (this format doesn't have any " double quote and space character in the values). Substituted space with _ character.
create_time=2017-01-09_23:24:29.428000+00:00,availability_zone=us-east-2b,volume_id=vol-058e1d47dgh721121,volume_type=gp2,state=in-use,size=8,iops=100,encrypted=False,snapshot_id=snap-06h1h1b91bh662avn,kms_key_id=None,InstanceId=i-0jjb1boop26f42f50,InstanceVolumeState=attached,DeleteOnTermination=True,Device=/dev/sda1,Name=[company-2b-app90]_secondary,hostname=company-2b-app90-i-0jjb1boop26f42f50,high_availability=1,mirror=secondary,cluster=company,autoscale=true,role=app
Still didn't work, getting same error:
[vagrant#myvagrant ~] $ telegraf --config-directory=/etc/telegraf --test --input-filter=exec
2017/03/10 00:50:30 I! Using config file: /etc/telegraf/telegraf.conf
* Plugin: inputs.exec, Collection 1
2017-03-10T00:50:30Z E! Errors encountered: [ metric parsing error, reason: [missing fields], buffer: [create_time=2017-01-09_23:24:29.428000+00:00,availability_zone=us-east-2b,volume_id=vol-058e1d47dgh721121,volume_type=gp2,state=in-use,size=8,iops=100,encrypted=False,snapshot_id=snap-06h1h1b91bh662avn,kms_key_id=None,InstanceId=i-0jjb1boop26f42f50,InstanceVolumeState=attached,DeleteOnTermination=True,Device=/dev/sda1,Name=[company-2b-app90]_secondary,hostname=company-2b-app90-i-0jjb1boop26f42f50,high_availability=1,mirror=secondary,cluster=company,autoscale=true,role=app], index: [476]]
Format 4: If I follow influx line protocol as per this page: https://docs.influxdata.com/influxdb/v1.2/write_protocols/line_protocol_tutorial/
awsebs,Name=[company-2b-app90]_secondary,hostname=company-2b-app90-i-0jjb1boop26f42f50,high_availability=1,mirror=secondary,cluster=company,autoscale=true,role=app create_time=2017-01-09_23:24:29.428000+00:00,availability_zone=us-east-2b,volume_id=vol-058e1d47dgh721121,volume_type=gp2,state=in-use,size=8,iops=100,encrypted=False,snapshot_id=snap-06h1h1b91bh662avn,kms_key_id=None,InstanceId=i-0jjb1boop26f42f50,InstanceVolumeState=attached,DeleteOnTermination=True,Device=/dev/sda1
I'm getting this error:
[vagrant#myvagrant ~] $ telegraf --config-directory=/etc/telegraf --test --input-filter=exec
2017/03/10 02:34:30 I! Using config file: /etc/telegraf/telegraf.conf
* Plugin: inputs.exec, Collection 1
2017-03-10T02:34:30Z E! Errors encountered: [ invalid number]
HOW can I get rid of this error and get telegraf to work with exec plugin (which runs the .sh script)?
Other Info:
Python script will run once/twice per day (via cron) and telegraf will run every 1 minute (to run exec plugin - which runs .sh script - which will cat the .csv file so that telegraf can consume it in influx data format).
https://galaxy.ansible.com/wavefrontHQ/wavefront-ansible/
https://github.com/influxdata/telegraf/issues/2525
It seems like the rules are very strict, I should have looked more closely.
Syntax of the output of any program that you can to consume MUST match or follow INFLUX LINE PROTOCOL format shown below and also all the RULES which comes with it.
For ex:
weather,location=us-midwest temperature=82 1465839830100400200
| -------------------- -------------- |
| | | |
| | | |
+-----------+--------+-+---------+-+---------+
|measurement|,tag_set| |field_set| |timestamp|
+-----------+--------+-+---------+-+---------+
You can read more about what's measurement, tag, field and optional(timestamp) here: https://docs.influxdata.com/influxdb/v1.2/write_protocols/line_protocol_tutorial/
Important rules are:
1) There must be a , and no space between measurement and tag set.
2) There must be a space between tag set and field set.
3) For tag keys, tag values, and field keys always use a backslash character \ to escape if you want to escape any character in measurement name, tag or field set name and their values!
4) You can't escape \ with \
5) Line Protocol handles emojis with no problem :)
6) TAG / TAG set (tags comma separated) in OPTIONAL
7) FIELD / FIELD set (fields, comma separated) - At least ONE is required per line.
8) TIMESTAMP (last value shown in the format) is OPTIONAL.
9) VERY IMPORTANT QUOTING rules are below:
a) Never double or single quote the timestamp. It’s not valid Line Protocol. '123123131312313' or "1231313213131" won't work if that # is valid.
b) Never single quote field values (even if they’re strings!). It’s also not valid Line Protocol. i.e. fieldname='giga' won't work.
c) Do not double or single quote measurement names, tag keys, tag values, and field keys. NOTE: THIS does say !!! tag values !!!! so careful.
d) Do not double quote field values that are ONLY in floats, integers, or booleans format, otherwise InfluxDB will assume that those values are strings.
e) Do double quote field values that are strings.
f) AND the MOST IMPORTANT one (which will save you from getting BALD): If a FIELD value is set without double quote / i.e. you think it's an integer value or float in one line (for ex: anyone will say fields size or iops) and in some other lines (anywhere in the file that telegraf will read/parse using exec plugin) if you have a non-integer value set (i.e. a String), then you'll get the following error message Errors encountered: [ invalid number error.
So to fix it, the RULE is, if any possible FIELD value for a FIELD key is a string, then you MUST make sure to use " to wrap it (in every lines), it doesn't matter whether it has value 1, 200 or 1.5 in some lines (for ex: iops can be 1, 5) and in some other lines that value (iops can be None).
Error message: Errors encountered: [ invalid number
[vagrant#myvagrant ~] $ telegraf --config-directory=/etc/telegraf --test --input-filter=exec
2017/03/10 11:13:18 I! Using config file: /etc/telegraf/telegraf.conf
* Plugin: inputs.exec, Collection 1
2017-03-10T11:13:18Z E! Errors encountered: [ invalid number metric parsing error, reason: [invalid field format], buffer: [awsebsvol,host=myvagrant ], index: [25]]
So, after all this learning, it's clear that first I was missing the Influx Line protocol format and ALSO the RULES!!
Now, my output that I want my python script to generate should be like this (acc. to the INFLUX LINE PROTOCOL). You can just change the .sh file and use sed "s/^/awsec2ebs,/" or also do sed "s/^/awsec2ebs,sourcehost=$(hostname) /" (note: the space before the closing sed / character) and then you can have " around any key=value pair. I did change .py file to not use " for size and iops fields.
Anyways, if the output is something like this:
awsec2ebs,volume_id=vol-058e1d47dgh721121 create_time="2017-01-09 23:24:29.428000+00:00",availability_zone="us-east-2b",volume_type="gp2",state="in-use",size="8",iops="100",encrypted="False",snapshot_id="snap-06h1h1b91bh662avn",kms_key_id="None",InstanceId="i-0jjb1boop26f42f50",InstanceVolumeState="attached",DeleteOnTermination="True",Device="/dev/sda1",Name="[company-2b-app90] secondary",hostname="company-2b-app90-i-0jjb1boop26f42f50",high_availability="1",mirror="secondary",cluster="company",autoscale="true",role="app"
In the above final working solution, I created a measurement named awsec2ebs then gave , between this measurement and tag key volume_id and for tag value, I did NOT use any ' or " quotes and then I gave a space character (as I just wanted only one tag for now otherwise you can have more tag using command separated way and following the rules) between tag set and field set.
Finally ran the command:
$ telegraf --config-directory=/etc/telegraf --test --input-filter=exec which worked like a shenzi!
2017/03/10 03:33:54 I! Using config file: /etc/telegraf/telegraf.conf
* Plugin: inputs.exec, Collection 1
> awsec2ebs_telegraf_execplugin,volume_id=vol-058e1d47dgh721121,host=myvagrant volume_type="gp2",iops="100",kms_key_id="None",role="app",size="8",encrypted="False",InstanceId="i-0jjb1boop26f42f50",InstanceVolumeState="attached",Name="[company-2b-app90] secondary",snapshot_id="snap-06h1h1b91bh662avn",DeleteOnTermination="True",mirror="secondary",cluster="company",autoscale="true",high_availability="1",create_time="2017-01-09 23:24:29.428000+00:00",availability_zone="us-east-2b",state="in-use",Device="/dev/sda1",hostname="company-2b-app90-i-0jjb1boop26f42f50" 1489116835000000000
[vagrant#myvagrant ~] $ echo $?
0
In the above example, size is the only field which will always be a number/numeric value, so we don't need to wrap it with " but it's up to you. Recall the MOST IMPORTANT rule.. above and the error it generates.
So final python file is:
#!/usr/bin/python
#Do `sudo pip install boto3` first
import boto3
def generate(key, value, qs, qe):
"""
Creates a nicely formatted Key(Value) item for output
"""
return '{}={}{}{}'.format(key, qs, value, qe)
def main():
ec2 = boto3.resource('ec2', region_name="us-west-2")
volumes = ec2.volumes.all()
for vol in volumes:
# You don't need to wrap everything in `str` unless it is not a string
# By default most things will come back as a string
# unless they are very obviously not (complex, date time, etc)
# but since we are printing these (and formatting them into strings)
# the cast to string will be implicit and we don't need to make it
# explicit
# vol is already a fully returned volume you are essentially DOUBLING
# your API calls when you do this
#iv = ec2.Volume(vol.id)
output_parts = [
# Volume level details
generate('volume_id', vol.volume_id, '"', '"'),
generate('create_time', vol.create_time, '"', '"'),
generate('availability_zone', vol.availability_zone, '"', '"'),
generate('volume_type', vol.volume_type, '"', '"'),
generate('state', vol.state, '"', '"'),
generate('size', vol.size, '', ''),
#The following vol.iops variable can be a number or None so you must wrap it with double quotes otherwise "invalid number" error will come.
generate('iops', vol.iops, '"', '"'),
generate('encrypted', vol.encrypted, '"', '"'),
generate('snapshot_id', vol.snapshot_id, '"', '"'),
generate('kms_key_id', vol.kms_key_id, '"', '"'),
]
for _ in vol.attachments:
# Will get any attachments and since it is a list
# we should write this to handle MULTIPLE attachments
output_parts.extend([
generate('InstanceId', _.get('InstanceId'), '"', '"'),
generate('InstanceVolumeState', _.get('State'), '"', '"'),
generate('DeleteOnTermination', _.get('DeleteOnTermination'), '"', '"'),
generate('Device', _.get('Device'), '"', '"'),
])
# only process when there are tags to process
if vol.tags:
for _ in vol.tags:
# Get all of the tags
output_parts.extend([
generate(_.get('Key'), _.get('Value'), '"', '"'),
])
# output everything at once..
print ','.join(output_parts)
if __name__ == '__main__':
main()
Final aws-vol-info.sh is:
#!/bin/bash
cat aws-vol-info.csv | sed "s/^/awsebsvol,host=`hostname|head -1|sed "s/[ \t][ \t]*/_/g"` /"
Final telegraf exec plugin config file is (/etc/telegraf/telegraf.d/exec-plugin-aws-info.conf) give any name with .conf:
#--- https://github.com/influxdata/telegraf/tree/master/plugins/inputs/exec
[[inputs.exec]]
commands = ["/some/valid/path/where/csvfileexists/aws-vol-info.sh"]
## Timeout for each command to complete.
timeout = "5s"
# Data format to consume.
# NOTE json only reads numerical measurements, strings and booleans are ignored.
data_format = "influx"
name_suffix = "_telegraf_exec"
Run: and everything will work now!
$ telegraf --config-directory=/etc/telegraf --test --input-filter=exec