Visualize
We can build some GraphViz diagrams that help up to understand better some configurations on the server. This can be done automatically from bash scripts.
First, let’s install GraphViz:
apt install graphviz
1. Port forwarding
This script builds a graph that shows port forwarding on the server. Usually the open ports:
-
go to the host (the ports on the public zone of
firewalld) -
are forwarded to an Incus container
-
are forwarded to a Docker container
Script: visualize/gv-ports.sh
#!/bin/bash
main() {
local file_gv=${1:-/dev/stdout}
# get container name from ip
declare -A container_name
for container in $(get_containers); do
ip=$(get_ip $container)
container_name[$ip]=$container
done
print_gv_file $file_gv
}
get_containers() {
incus ls -f csv -c ns \
| grep RUNNING \
| sed 's/,RUNNING//'
}
get_ip() {
local container=$1
if [[ -z $container ]]; then
ip route get 8.8.8.8 | head -1 | sed 's/.*src //' | cut -d' ' -f1
else
incus exec $container -- \
ip route get 8.8.8.8 | head -1 | sed 's/.*src //' | cut -d' ' -f1
fi
}
print_gv_file() {
local file_gv=${1:-/dev/stdout}
cat <<EOF > $file_gv
digraph {
graph [
rankdir = LR
ranksep = 1.5
concentrate = true
fontname = system
fontcolor = brown
]
// all the ports that are accessable in the server
subgraph ports {
graph [
cluster = true
//label = "Ports"
style = "rounded, dotted"
]
node [
fillcolor = lightyellow
style = "filled"
fontname = system
fontcolor = navy
]
// ports that are forwarded to the host itself
// actually they are the ports that are open in the firewall
subgraph host_ports {
graph [
cluster = true
style = "rounded"
color = darkgreen
bgcolor = lightgreen
label = "firewall-cmd --list-ports"
]
$(host_port_nodes)
}
// ports that are forwarded to the docker containers
subgraph docker_ports {
graph [
cluster = true
label = "docker ports"
style = "rounded"
bgcolor = lightblue
color = steelblue
]
$(docker_port_nodes)
}
// ports that are forwarded to the incus containers
// with the command: 'incus network forward ...'
subgraph incus_ports {
graph [
cluster = true
label = "incus network forward"
style = "rounded"
bgcolor = pink
color = orangered
]
$(incus_port_nodes)
}
}
Host [
label = "Host: $(hostname -f)"
margin = 0.2
shape = box
peripheries = 2
style = "filled, rounded"
color = red
fillcolor = orange
fontname = system
fontcolor = navy
]
// docker containers
subgraph docker_containers {
graph [
cluster = true
label = "Docker"
style = "rounded"
]
node [
shape = record
style = "filled, rounded"
color = blue
fillcolor = deepskyblue
fontname = system
fontcolor = navy
]
$(docker_container_nodes)
}
// incus containers
subgraph incus_containers {
graph [
cluster = true
label = "Incus"
style = "rounded"
]
node [
shape = record
style = "filled, rounded"
color = orange
fillcolor = yellow
fontname = system
fontcolor = navy
]
$(incus_container_nodes)
}
edge [
dir = back
arrowtail = odiamondinvempty
]
$(host_edges)
$(docker_edges)
$(incus_edges)
graph [
labelloc = t
//labeljust = r
fontcolor = red
label = "Port forwarding"
]
}
EOF
}
print_port_node() {
local protocol=$1
local port=$2
cat <<EOF
"$protocol/$port" [ label = <$protocol <br/> <b>$port</b>> ];
EOF
}
host_port_nodes() {
echo
local ports=$(firewall-cmd --list-ports --zone=public)
for p in $ports; do
protocol=$(echo $p | cut -d'/' -f2)
port=$(echo $p | cut -d'/' -f1)
print_port_node $protocol $port
done
}
host_edges() {
echo
local ports=$(firewall-cmd --list-ports --zone=public)
for p in $ports; do
protocol=$(echo $p | cut -d'/' -f2)
port=$(echo $p | cut -d'/' -f1)
cat <<EOF
"$protocol/$port" -> Host;
EOF
done
}
docker_port_nodes() {
echo
docker container ls -a --format "{{.Names}} {{.Ports}}" \
| grep 0.0.0.0 \
| sed -e 's/,//g' \
| tr ' ' "\n" \
| grep 0.0.0.0 \
| cut -d: -f2 \
| \
while read line; do
port=$(echo $line | cut -d'-' -f1)
protocol=$(echo $line | cut -d'/' -f2)
print_port_node $protocol $port
done
}
docker_container_nodes() {
echo
docker container ls -a --format "{{.Names}} {{.Ports}}" \
| grep 0.0.0.0 \
| cut -d' ' -f1 \
| \
while read name; do
echo " \"$name\";"
done
}
docker_edges() {
echo
docker container ls -a --format "{{.Names}} {{.Ports}}" \
| grep 0.0.0.0 \
| sed -e 's/,//g' \
| \
while read line; do
container=$(echo $line | cut -d' ' -f1)
echo $line | tr ' ' "\n" | grep 0.0.0.0 | cut -d: -f2 | \
while read line1; do
port=$(echo $line1 | cut -d'-' -f1)
protocol=$(echo $line1 | cut -d'/' -f2)
target=$(echo $line1 | cut -d'>' -f2 | cut -d'/' -f1)
[[ $target == $port ]] && target=''
cat <<EOF
"$protocol/$port" -> "$container" [ label = "$target" ];
EOF
done
done
}
incus_port_nodes() {
echo
get_forward_lines | \
while read line; do
eval "$line"
target_port=${target_port:-$listen_port}
#echo $description $protocol $listen_port $target_port $target_address
print_port_node $protocol $listen_port
done
}
get_forward_lines() {
local listen_address=$(get_ip)
incus network forward show incusbr0 $listen_address \
| sed -e '/^description:/d' \
-e '/^config:/d' \
-e '/^ports:/d' \
-e '/^listen_address:/d' \
-e '/^location:/d' \
| tr -d '\n\r' \
| sed -e 's/- description:/\ndescription:/g' \
| sed -e 1d \
| sed -e 's/: /=/g'
}
incus_container_nodes() {
echo
for container in $(get_containers); do
ip=$(get_ip $container)
cat <<EOF
$container [ label = "$container|<ip> $ip" ];
EOF
done
}
incus_edges() {
echo
get_forward_lines | \
while read line; do
eval "$line"
#echo $description $protocol $listen_port $target_port $target_address
container=${container_name[$target_address]}
cat <<EOF
"$protocol/$listen_port" -> $container:ip [ label = "$target_port" ];
EOF
done
}
### call main
main "$@"
To build the graph do:
./gv-ports.sh ports.gv
dot -Tpng ports.gv -o ports.png
2. Web domains
The web domains hosted on the server are handled by
sniproxy and
revproxy. The first one,
sniproxy, redirects some of the domains to Incus containers, and the
rest to revproxy (which is based on nginx). Then, revproxy
redirects these domains to docker dontainers.
Script: visualize/gv-domains.sh
#!/bin/bash
main() {
local file_gv=${1:-/dev/stdout}
# get container name from ip
declare -A container_name
for container in $(get_containers); do
ip=$(get_ip $container)
container_name[$ip]=$container
done
print_gv_file $file_gv
}
get_containers() {
incus ls -f csv -c ns \
| grep RUNNING \
| sed 's/,RUNNING//'
}
get_ip() {
local container=$1
if [[ -z $container ]]; then
ip route get 8.8.8.8 | head -1 | sed 's/.*src //' | cut -d' ' -f1
else
incus exec $container -- \
ip route get 8.8.8.8 | head -1 | sed 's/.*src //' | cut -d' ' -f1
fi
}
print_gv_file() {
local file_gv=${1:-/dev/stdout}
cat <<EOF > $file_gv
digraph {
graph [
rankdir = LR
ranksep = 1.5
concentrate = true
fontname = system
fontcolor = brown
]
node [
shape = box
]
// domain entries of sniproxy
subgraph sniproxy_domains {
graph [
cluster = true
label = sniproxy
color = darkgreen
bgcolor = lightgreen
style = "rounded, dotted"
]
node [
fillcolor = lightyellow
style = "filled"
fontname = system
fontcolor = navy
]
$(sniproxy_domains)
}
// domain entries of revproxy
subgraph revproxy_domains {
graph [
cluster = true
color = darkgreen
bgcolor = lightgreen
style = "rounded, dotted"
]
node [
fillcolor = lightyellow
style = "filled"
fontname = system
fontcolor = navy
]
$(revproxy_domains)
}
revproxy [
label = "revproxy"
margin = 0.2
shape = box
peripheries = 2
style = "filled, rounded"
color = blue
fillcolor = deepskyblue
fontname = system
fontcolor = navy
]
node [
shape = record
style = "filled, rounded"
color = orange
fillcolor = yellow
fontname = system
fontcolor = navy
]
$(incus_container_nodes)
edge [
arrowhead = none
]
$(sniproxy_edges)
$(revproxy_edges)
graph [
labelloc = t
//labeljust = r
fontcolor = red
label = "Domains"
]
}
EOF
}
sniproxy_domains() {
echo
cat /var/ds/sniproxy/etc/sniproxy.conf \
| sed -e '1,/table/d' -e '/}/d' -e '/# /d' -e '/^ *$/d' \
| \
while read line; do
domain=$(echo "$line" | cut -d' ' -f1)
echo " \"$domain\";"
done
}
revproxy_domains() {
echo
for domain in $(ds @revproxy domains-ls); do
echo " \"$domain\";"
done
}
incus_container_nodes() {
echo
for container in $(get_containers); do
ip=$(get_ip $container)
cat <<EOF
$container [ label = "$container|<ip> $ip" ];
EOF
done
}
sniproxy_edges() {
echo
cat /var/ds/sniproxy/etc/sniproxy.conf \
| sed -e '1,/table/d' -e '/}/d' -e '/# /d' -e '/^ *$/d' \
| \
while read line; do
domain=$(echo "$line" | xargs | cut -d' ' -f1)
target=$(echo "$line" | xargs | cut -d' ' -f2)
container=${container_name[$target]}
[[ -z $container ]] && container=$target
echo " \"$domain\" -> \"$container\";"
done
}
revproxy_edges() {
echo
for domain in $(ds @revproxy domains-ls); do
echo " revproxy -> \"$domain\";"
done
}
### call main
main "$@"
To build the graph do:
./gv-domains.sh domains.gv
dot -Tpng domains.gv -o domains.png