Skip to content

Instantly share code, notes, and snippets.

@redmcg
Last active October 2, 2024 15:41
Show Gist options
  • Save redmcg/60cfff7bca6f32969188008ad4a44c9a to your computer and use it in GitHub Desktop.
Save redmcg/60cfff7bca6f32969188008ad4a44c9a to your computer and use it in GitHub Desktop.
Bash script to show k8s PVC usage
#!/usr/bin/env bash
NODESAPI=/api/v1/nodes
function getNodes() {
kubectl get --raw $NODESAPI | jq -r '.items[].metadata.name'
}
function getPVCs() {
jq -s '[flatten | .[].pods[].volume[]? | select(has("pvcRef")) | '\
'{name: .pvcRef.name, capacityBytes, usedBytes, availableBytes, '\
'percentageUsed: (.usedBytes / .capacityBytes * 100)}] | sort_by(.name)'
}
function column() {
awk '{ for (i = 1; i <= NF; i++) { d[NR, i] = $i; w[i] = length($i) > w[i] ? length($i) : w[i] } } '\
'END { for (i = 1; i <= NR; i++) { printf("%-*s", w[1], d[i, 1]); for (j = 2; j <= NF; j++ ) { printf("%*s", w[j] + 1, d[i, j]) } print "" } }'
}
function defaultFormat() {
awk 'BEGIN { print "PVC 1K-blocks Used Available Use%" } '\
'{$2 = $2/1024; $3 = $3/1024; $4 = $4/1024; $5 = sprintf("%.0f%%",$5); print $0}'
}
function humanFormat() {
awk 'BEGIN { print "PVC Size Used Avail Use%" } '\
'{$5 = sprintf("%.0f%%",$5); printf("%s ", $1); system(sprintf("numfmt --to=iec %s %s %s | sed '\''N;N;s/\\n/ /g'\'' | tr -d \\\\n", $2, $3, $4)); print " " $5 }'
}
function format() {
jq -r '.[] | "\(.name) \(.capacityBytes) \(.usedBytes) \(.availableBytes) \(.percentageUsed)"' |
$format | column
}
if [ "$1" == "-h" ]; then
format=humanFormat
else
format=defaultFormat
fi
for node in $(getNodes); do
kubectl get --raw $NODESAPI/$node/proxy/stats/summary
done | getPVCs | format
@web-dev-passion
Copy link

Works like a charm, thank you very much :)

@vsadanala
Copy link

Awesome, love this script
my customer requested me exactly this output, thank you very much for sharing

Could you please help me to add "NAMESPACE" column as well along with "PVC Capacity Used Available Used%" in your script.

Thank you once again!

@redmcg
Copy link
Author

redmcg commented Feb 16, 2023

Thanks for the kind words.

I'd love to help, but unfortunately the only k8s cluster I have access to at the moment is minikube with the default storage provisioner; which doesn't provide the metrics required by this script.

The only guidance I can provide is that because the physical volume itself is not namespaced, it would probably require additional calls to find the pvc and from there the namespace could be extracted.

@mattwelke
Copy link

I'd love to help, but unfortunately the only k8s cluster I have access to at the moment is...

I wonder if it's a good idea to take this script and use it as the beginning of a new open source CLI tool (or, use it as inspiration for adding a feature to an existing CLI tool if one exists). This use case seems so popular. People like to be able to scan through all the data they've stored in their clusters' disks and learn more about the disks.

@redmcg
Copy link
Author

redmcg commented Feb 17, 2023

kubectl has support for plugins, which can be managed via krew (a kubectl plugin itself). It looks like a plugin that could be used in place of this script has already been created:
https://github.com/yashbhutwala/kubectl-df-pv

@mattwelke
Copy link

That's neat. That plugin's README says it's only compatible with GKE right now though. This script's approach is just pure Kubernetes, right?

@redmcg
Copy link
Author

redmcg commented Feb 18, 2023

Yeah, I'm using the kubernates rest api. But I took a quick look at the code for kubectl-df-pv, and it looks like it's using the same approach. I couldn't see anything that suggested this script would provide a greater level of support; but testing would confirm.

@vsadanala
Copy link

vsadanala commented Mar 26, 2023

@redmcg
I have managed to add namespace column to your existing code

kubectl get --raw $NODESAPI/$node/proxy/stats/summary|jq -s '[flatten | .[].pods[].volume[]? | select(has("pvcRef")) | ''{namespace: .pvcRef.namespace, name: .pvcRef.name, capacityBytes, usedBytes, availableBytes, ''percentageUsed: (.usedBytes / .capacityBytes * 100)}] | sort_by(.namespace)'|jq -r '.[] | "(.namespace) (.name) (.capacityBytes) (.usedBytes) (.availableBytes) (.percentageUsed)"'|awk '{$3 = $3/(102410241024); $4 = $4/(10241024); $5= $5/(10241024*1024); $6 = sprintf("%.0f%%",$6); print $0}'


Namsespace PVC Capacity Used Available Used%

Thank you @redmcg

@redmcg
Copy link
Author

redmcg commented Mar 27, 2023

Good work @vsadanala ! And thanks for sharing.

@dandvc
Copy link

dandvc commented Jul 18, 2023

Nice Work ! It's very useful !! TKS
image

@thibault-ketterer
Copy link

thibault-ketterer commented Feb 7, 2024

nice ! thanks

I would had this > 50% usage

bash kubedf -h |tr -d '%' |awk '$NF > 50' | sed -e 's/$/%/'

@hamardk
Copy link

hamardk commented May 29, 2024

Hi,
I just added an option to list the pvc information filtered with a namespace based on the script above:

#!/usr/bin/env bash

function getNodes() {
kubectl get --raw=/api/v1/nodes | jq -r '.items[].metadata.name'
}

function getPVCs() {
jq -s '[flatten | .[].pods[].volume[]? | select(has("pvcRef")) | '
'{namespace: .pvcRef.namespace, name: .pvcRef.name, capacityBytes, usedBytes, availableBytes, '
'percentageUsed: (.usedBytes / .capacityBytes * 100)}] | sort_by(.namespace)'
}

function column() {
awk '{ for (i = 1; i <= NF; i++) { d[NR, i] = $i; w[i] = length($i) > w[i] ? length($i) : w[i] } } '
'END { for (i = 1; i <= NR; i++) { printf("%-*s", w[1], d[i, 1]); for (j = 2; j <= NF; j++ ) { printf("%*s", w[j] + 1, d[i, j]) } print "" } }'
}

function defaultFormat() {
awk 'BEGIN { print "Namespace PVC 1K-blocks Used Available Use%" } '
'{$3 = $3/1024; $4 = $4/1024; $5 = $5/1024; $6 = sprintf("%.0f%%",$6); print $0}'
}

function humanFormat() {
awk 'BEGIN { print "Namespace PVC Size Used Avail Use%" } '
'{$6 = sprintf("%.0f%%",$6); printf("%s ", $1); printf("%s ", $2); system(sprintf("numfmt --to=iec %s %s %s | sed '''N;N;s/\n/ /g''' | tr -d \\n", $3, $4, $5)); print " " $6 }'
}

function format() {
jq '.[] | "(.namespace) (.name) (.capacityBytes) (.usedBytes) (.availableBytes) (.percentageUsed)"' |
sed 's/^"|"$//g' |
$format | column
}

function get_pvc_info(){
local _format=${1:-h} && shift
local namespace=${1:-""}
format=humanFormat

[[ "${_format,,}" != "h" ]] && format=defaultFormat

table=$(for node in $(getNodes); do
kubectl get --raw=/api/v1/nodes/${node}/proxy/stats/summary
done | getPVCs | format)
header=$(echo -e "${table}" | head -1)
echo -e "${header}"
[[ -n "${namespace}" ]] && echo -e "${table}" | grep -w "^${namespace} " || echo -e "${table}"
}

get_pvc_info "$@"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment