Jump to content

User:Brouberol

From Wikitech

Useful snippets

pcc

This is a bash function I have in my zshrc

# This function allows me to run `pcc` in my puppet repository checkout
# and have it automatically start a PCC run on the gerrit change ID
# associated with the patch change ID, without having me to copy and paste
# anything.
pcc () {
	if [[ $PWD != "${WMF_HOME}/puppet" ]]
	then
		echo "Not in the puppet root dir. Exiting."
	else
		change_id=$(git show | grep Change-Id | awk '{ print $2 }')
		gerrit_change_id=$(curl -s "https://gerrit.wikimedia.org/r/changes/${change_id}" | sed 1d | jq -r "._number")
		./utils/pcc ${gerrit_change_id}
	fi
}
prepare-commit-msg

I automatically inject this script in my git hooks to avoid having to manually type Bug: TXXXXXX in my commit messages. Note: it relies on the phabricator ticket number to appear in the git branch name.

#!/bin/sh

#
# Inspects branch name and checks if it contains a Phabricator ticket number (i.e. T123456).
# If yes, commit message will be automatically suffixed with "Bug: {num}".
#
# Useful for looking through git history and relating a commit or group of commits
# back to a user story.
#
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)


# Ensure BRANCH_NAME is not empty and is not in a detached HEAD state (i.e. rebase).
# SKIP_PREPARE_COMMIT_MSG may be used as an escape hatch to disable this hook,
# while still allowing other githooks to run.
if [ ! -z "$BRANCH_NAME" ] && [ "$BRANCH_NAME" != "HEAD" ] && [ "$SKIP_PREPARE_COMMIT_MSG" != 1 ]; then
  PREFIX_PATTERN='T[0-9]{6,7}'

  [[ $BRANCH_NAME =~ $PREFIX_PATTERN ]]

  PREFIX=${BASH_REMATCH[0]}

  PREFIX_IN_COMMIT=$(grep -c "Bug: $PREFIX" $1)

  # Ensure PREFIX exists in BRANCH_NAME and is not already present in the commit message
  if [[ -n "$PREFIX" ]] && ! [[ $PREFIX_IN_COMMIT -ge 1 ]]; then
    echo "\nBug: ${PREFIX}" >> $1
  fi

fi
How to strace the process running in a container

This allows us to run strace on a running docker container, on a Kubernetes host.

Example: strace-container mpic-staging-5f488948c-2g64g mpic-staging -e 'trace=!epoll_wait'

strace-container() {
  local pod_name=$1
  local container_name=$2
  shift
  shift
  local container_id=$(sudo docker ps | grep "${container_name}_${pod_name}" | awk '{ print $1 }')
  local container_root_pid=$(sudo docker top ${container_id} | grep -v UID | awk '{ print $2 }')
  # any additional argument will be passed to `strace`
  sudo strace -f -p ${container_root_pid} $@ 
}
How to run a command into the network namespace of a container

This function allows us to run a command available on a Kubernetes worker host, within a container network namespace. It can be useful to, say, check whether a container process can open a TCP connection to a given host/port.

Example: nsenter-container aqs-http-gateway-main-56945ccf8b-fk8tz aqs-http-gateway-main telnet 10.2.2.38 8082

nsenter-container() {
    local pod_name=$1
    local container_name=$2
    shift
    shift
    local container_id=$(sudo docker ps | grep "${container_name}_${pod_name}" | awk '{ print $1 }')
    local container_root_pid=$(sudo docker top ${container_id} | grep -v UID | awk '{ print $2 }' | head -n 1)
    # any additional will be passed to `nsenter`
    sudo nsenter --target ${container_root_pid} --net $@ 
}
How to run tcpdump on a pod IP
tcpdump-pod-ip() {
  local pod_ip=$1
  shift
  # Any additional argument is passed to `tcpdump`
  sudo tcpdump -i any host ${pod_ip} $@
}
Link a Calico Network Policy to Pods, IPs and ports

Pods "subscribe" to a Calico Network policy using a selector and a service name, allowing ingress from / egress to that service. However, all of this is hard to inspect, as it requires multiple kubectl describe calls: one to inspect the calico network policy, one to inspect the service and associated endpoints, and one to list the pods matching the selector.

The following script allows to resolve all these selectors to actual pod names and service IP/port data.

brouberol@deploy1002:~$ kube_env superset-deploy dse-k8s-eqiad
brouberol@deploy1002:~$ ./inspect_calico_networkpolicy --help
usage: inspect_calico_networkpolicy [-h] [-n NAMESPACE] [-f {plain,json}]

Inspect the Calico NetworkPolicy objects in a given namespace and link each of
them to the set of attached Pods.

optional arguments:
  -h, --help            show this help message and exit
  -n NAMESPACE, --namespace NAMESPACE
                        The namespace to inspect (default: the current one)
  -f {plain,json}, --format {plain,json}
                        Output format (default=plain)
brouberol@deploy1002:~$ ./inspect_calico_networkpolicy
[+] NetworkPolicy superset-staging-egress-external-services-cas
    Pods:
    - superset-staging-8678649994-grb8z
    Service cas-idp -> ips=208.80.153.12, 208.80.154.80, 2620:0:860:1:208:80:153:12, 2620:0:861:3:208:80:154:80, port=TCP/443
[+] NetworkPolicy superset-staging-egress-external-services-druid
    Pods:
    - superset-staging-8678649994-grb8z
    Service druid-analytics -> ips=10.64.21.11, 10.64.36.101, 10.64.5.17, 10.64.5.36, 10.64.53.13, 2620:0:861:104:10:64:5:17, 2620:0:861:104:10:64:5:36, 2620:0:861:105:10:64:21:11, 2620:0:861:106:10:64:36:101, 2620:0:861:108:10:64:53:13, port=TCP/8081,TCP/8082,TCP/8083
    Service druid-public -> ips=10.64.131.9, 10.64.132.12, 10.64.135.9, 10.64.16.171, 10.64.48.227, 2620:0:861:102:10:64:16:171, 2620:0:861:107:10:64:48:227, 2620:0:861:10a:10:64:131:9, 2620:0:861:10b:10:64:132:12, 2620:0:861:10e:10:64:135:9, port=TCP/8081,TCP/8082,TCP/8083
[+] NetworkPolicy superset-staging-egress-external-services-kerberos
    Pods:
    - superset-staging-8678649994-grb8z
    Service kerberos-kdc -> ips=10.192.48.190, 10.64.0.112, 2620:0:860:104:10:192:48:190, 2620:0:861:101:10:64:0:112, port=UDP/88,TCP/88
[+] NetworkPolicy superset-staging-egress-external-services-presto
    Pods:
    - superset-staging-8678649994-grb8z
    Service presto-analytics -> ips=10.64.138.7, 10.64.142.6, 2620:0:861:100:10:64:138:7, 2620:0:861:114:10:64:142:6, port=TCP/8280,TCP/8281
brouberol@deploy1002:~$ ./inspect_calico_networkpolicy --format json
{
  "superset-staging-egress-external-services-cas": {
    "pods": [
      "superset-staging-8678649994-grb8z"
    ],
    "services": [
      {
        "name": "cas-idp",
        "ips": [
          "208.80.153.12",
          "208.80.154.80",
          "2620:0:860:1:208:80:153:12",
          "2620:0:861:3:208:80:154:80"
        ],
        "ports": [
          "TCP/443"
        ]
      }
    ]
  },
  "superset-staging-egress-external-services-druid": {
    "pods": [
      "superset-staging-8678649994-grb8z"
    ],
    "services": [
      {
        "name": "druid-analytics",
        "ips": [
          "10.64.21.11",
          "10.64.36.101",
          "10.64.5.17",
          "10.64.5.36",
          "10.64.53.13",
          "2620:0:861:104:10:64:5:17",
          "2620:0:861:104:10:64:5:36",
          "2620:0:861:105:10:64:21:11",
          "2620:0:861:106:10:64:36:101",
          "2620:0:861:108:10:64:53:13"
        ],
        "ports": [
          "TCP/8081",
          "TCP/8082",
          "TCP/8083"
        ]
      },
      {
        "name": "druid-public",
        "ips": [
          "10.64.131.9",
          "10.64.132.12",
          "10.64.135.9",
          "10.64.16.171",
          "10.64.48.227",
          "2620:0:861:102:10:64:16:171",
          "2620:0:861:107:10:64:48:227",
          "2620:0:861:10a:10:64:131:9",
          "2620:0:861:10b:10:64:132:12",
          "2620:0:861:10e:10:64:135:9"
        ],
        "ports": [
          "TCP/8081",
          "TCP/8082",
          "TCP/8083"
        ]
      }
    ]
  },
  "superset-staging-egress-external-services-kerberos": {
    "pods": [
      "superset-staging-8678649994-grb8z"
    ],
    "services": [
      {
        "name": "kerberos-kdc",
        "ips": [
          "10.192.48.190",
          "10.64.0.112",
          "2620:0:860:104:10:192:48:190",
          "2620:0:861:101:10:64:0:112"
        ],
        "ports": [
          "UDP/88",
          "TCP/88"
        ]
      }
    ]
  },
  "superset-staging-egress-external-services-presto": {
    "pods": [
      "superset-staging-8678649994-grb8z"
    ],
    "services": [
      {
        "name": "presto-analytics",
        "ips": [
          "10.64.138.7",
          "10.64.142.6",
          "2620:0:861:100:10:64:138:7",
          "2620:0:861:114:10:64:142:6"
        ],
        "ports": [
          "TCP/8280",
          "TCP/8281"
        ]
      }
    ]
  }
}
#!/usr/bin/env python3

"""
Inspect the Calico NetworkPolicy objects in a given namespace and link
each of them to the set of attached pod names, service IP and ports.
"""

import argparse
import subprocess
import json
import sys

from collections import defaultdict
from typing import Dict, Optional, List


WARNING = '\033[93m'
ENDC = '\033[0m'


def parse_args():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "-n",
        "--namespace",
        help="The namespace to inspect (default: the current one)",
        type=str,
    )
    parser.add_argument(
        "-f",
        "--format",
        help="Output format (default=plain)",
        choices=("plain", "json"),
        default="plain",
    )
    return parser.parse_args()


def warn(msg: str):
    sys.stderr.write(f"{WARNING}{msg}{ENDC}\n")


def kubectl_get(
    resource_type: str,
    resource_name: Optional[str] = None,
    label_selector: Optional[str] = None,
    namespace: Optional[str] = None,
) -> List:
    cmd = ["kubectl", "get", resource_type]
    if label_selector:
        cmd.extend(["-l", label_selector])
    if namespace:
        cmd.extend(["-n", namespace])
    cmd.extend(["-o", "json"])
    if resource_name:
        cmd.append(resource_name)
    returncmd = subprocess.run(cmd, capture_output=True)
    try:
        data = json.loads(returncmd.stdout)
    except json.decoder.JSONDecodeError:
        return {} if resource_name else []
    if resource_name:
        return data
    return data["items"]


def calico_selector_to_label_selector(calico_selector: str) -> str:
    label_selector = []
    tokens = calico_selector.split(" && ")
    for token in tokens:
        selector = token.replace(" == ", "=")
        label_selector.append(selector.replace("'", ""))
    return ",".join(label_selector)


def main():
    output = defaultdict(lambda: defaultdict(dict))
    warnings = 0
    args = parse_args()

    def log(msg):
        if args.format == "plain":
            print(msg)

    network_policies = kubectl_get(
        "networkpolicies.crd.projectcalico.org", namespace=args.namespace
    )
    for network_policy in network_policies:
        network_policy_name = network_policy["metadata"]["name"]
        namespace = network_policy["metadata"]["annotations"][
            "meta.helm.sh/release-namespace"
        ]
        selector = network_policy["spec"]["selector"]
        label_selector = calico_selector_to_label_selector(selector)
        log(f"[+] NetworkPolicy {network_policy_name}")

        pods = kubectl_get(
            "pod", label_selector=label_selector, namespace=namespace
        )
        output[network_policy_name]["pods"] = [pod["metadata"]["name"] for pod in pods]
        if not pods:
            log("    No pods found")
        else:
            log("    Pods:")
            for pod in pods:
                log(f"    - {pod['metadata']['name']}")


        egress_destinations = network_policy["spec"]["egress"]
        output[network_policy_name]['services'] = []
        for egress_destination in egress_destinations:
            egress_service = egress_destination["destination"]["services"]
            egress_service_endpoints = kubectl_get(
                "endpoints",
                egress_service["name"],
                namespace=egress_service["namespace"],
            )
            if not egress_service_endpoints:
                warn(f"The Service {egress_service['name']} does not exist. The network policy won't have any effect.")
                warnings += 1
                continue

            ips = [
                addr["ip"]
                for addr in egress_service_endpoints["subsets"][0]["addresses"]
            ]
            ports = [
                f"{port['protocol']}/{port['port']}"
                for port in egress_service_endpoints["subsets"][0]["ports"]
            ]
            log(
                f"    Service {egress_service['name']} -> ips={', '.join(ips)}, port={','.join(ports)}"
            )
            service = {
                'name': egress_service['name'],
                'ips': ips,
                'ports': ports
            }
            output[network_policy_name]['services'].append(service)


    if args.format == "json":
        print(json.dumps(output, indent=2))
    sys.exit(0 if not warnings else 1)

if __name__ == "__main__":
    main()