Last active
May 21, 2022 23:10
-
-
Save NathanielWroblewski/57246a40405b0e22f15d92bcdf334cf6 to your computer and use it in GitHub Desktop.
View k8s selector chains from ingress -> service -> pod
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/local/bin/bash | |
#title :selch | |
#desc :Summarizes k8s selector chains from ingress to service to pod | |
#author :Nathaniel Wroblewski (nathanielwroblewski) | |
#created :2022-05-21 | |
#version :0.0.1 | |
#usage :bash ./selch | |
#environment :GNU bash, version 5.1.16(1)-release | |
#dependencies :kubectl (v1.22.5), jq-1.6, sed, xargs, column | |
#=============================================================================== | |
set -o noglob | |
set -o errexit | |
set -o errtrace | |
set -o nounset | |
set -o pipefail | |
IFS=$'\n' | |
main () { | |
# Fetch data from k8s | |
local services_csv_=$(k8s::ingresses.get_services) | |
local deployments_csv_=$(k8s::services.get_deployments) | |
local selectors_csv_=$(k8s::deployments.get_selectors) | |
# Format responses | |
local -a services_=(${services_csv_//;/$IFS}) | |
local -a deployments_=(${deployments_csv_//;/$IFS}) | |
local -a selectors_=(${selectors_csv_//;/$IFS}) | |
local -a edges_=(${services_[*]} ${deployments_[*]} ${selectors_[*]}) | |
local roots_=($(echo "${services_[*]}" | enums::map enums::first)) | |
# Traverse DAGs | |
local -A graph=$(dag::from_edges "${edges_[*]}") | |
dag::dfs $(utils::rep graph) "${roots_[*]}" | column -t -s' ' | |
} | |
# Returns an adjacency matrix representation of a directed, acyclic graph given | |
# a list of edges. | |
dag::from_edges () { | |
local -a edges_=( $1 ) | |
local -A dag_ | |
for pair_ in ${edges_[*]}; do | |
local -a tuple_=(${pair_//,/$IFS}) | |
local src_=${tuple_[0]} | |
local dst_=${tuple_[1]:=null} | |
dag_[$src_]=$dst_ | |
done; | |
utils::rep dag_ | |
} | |
# Depth-first searches a DAG, and prints the paths of traversal | |
dag::dfs () { | |
local -A dag_=$1; shift | |
local -a stack_=( $@ ) | |
local -a path_ | |
while [[ $(enums::length ${stack_[*]}) -gt 0 ]]; do | |
local vertex_=$(stack::peek stack_) | |
local next_=${dag_[$vertex_]:-} | |
stack::push path_ $vertex_ | |
unset stack_[-1] | |
if utils::is_null $next_; then | |
utils::render_path "${path_[*]}" | |
path_=() | |
else | |
stack::push stack_ $next_ | |
fi | |
done | |
} | |
# Pushes an element onto a stack | |
stack::push () { | |
local -n stx_=$1; shift | |
stx_+=( $@ ) | |
} | |
# Peek at the top element of a stack | |
stack::peek () { | |
local -n stx_=$1; shift | |
local ref_=${2:-} | |
printf ${ref_:+-v$IFS$ref_} ${stx_[-1]} | |
} | |
# Render each step in the path from root to leaf | |
utils::render_path () { | |
echo "${1//$IFS/ -> }" | |
} | |
# Removes leading, trailing, and internal whitepace | |
utils::trim () { | |
local ref_=${2:-} | |
printf ${ref_:+-v$IFS$ref_} ${1//[[:space:]]} | |
} | |
# Removes verbose, k8s internal selectors | |
utils::scrub () { | |
sed -E 's/(app|statefulset).kubernetes.io\///g' | \ | |
sed -E 's/managed-by-Helm--//g' | \ | |
xargs | sed -E 's/[[:space:]]/;/g' | |
} | |
# Checks for null or blank | |
utils::is_null () { | |
[[ -z ${1:-} || ${1:=null} == "null" ]] | |
} | |
# Serializes associative arrays | |
utils::rep () { | |
local expression="^declare -[a|A] [^=]+=(.*)$" | |
[[ $(declare -p $1) =~ $expression ]] && echo ${BASH_REMATCH[1]} | |
} | |
# Returns the head of an array | |
enums::first () { | |
local -a tuple_=(${1//,/$IFS}) | |
echo "${tuple_[0]}" | |
} | |
# Returns the length of the given array | |
enums::length () { | |
local array_=( ${1:-} ) | |
echo ${#array_[*]} | |
} | |
# Iterates over every element of an array and applies a function | |
enums::map () { | |
local fn=$1 | |
local arg | |
while read -r arg; do | |
$fn $arg; | |
done;: | |
} | |
# Fetches the ingress-service pairs from k8s | |
k8s::ingresses.get_services () { | |
utils::trim $(kubectl get ingresses --all-namespaces -ojsonpath="\ | |
{range .items[*]}\ | |
ingress={@.metadata.namespace}/{@.metadata.name},\ | |
service={@.metadata.namespace}/{@.spec.rules[].http.paths[].backend.service.name};\ | |
{end}") | |
} | |
# Fetches the service-pod pairs from k8s | |
k8s::services.get_deployments () { | |
utils::trim $(kubectl get services --all-namespaces -ojson | \ | |
jq -r '.items[] | [ | |
"service=", .metadata.namespace, "/", .metadata.name, ",", | |
"selector=", .metadata.namespace, "/", | |
(.spec.selector // {"":""} | | |
[to_entries[] | [.key, .value] | join("-")] | | |
join("--") | |
) | |
] | join("")' | utils::scrub) | |
} | |
# Fetches the pod selectors from k8s | |
k8s::deployments.get_selectors () { | |
utils::trim $(kubectl get deployments --all-namespaces -ojson | \ | |
jq -r '.items[] | [ | |
"selector=", .metadata.namespace, "/", | |
(.spec.selector.matchLabels // {"":""} | | |
[to_entries[] | [.key, .value] | join("-")] | | |
join("--") | |
) | |
] | join("")' | utils::scrub) | |
} | |
main $@ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment