From 3776492c568cb0e8f35c22591815d5928815233c Mon Sep 17 00:00:00 2001 From: Julien Pinsonneau Date: Mon, 14 Oct 2024 12:51:20 +0200 Subject: [PATCH 1/4] run in background --- Dockerfile | 9 + cmd/flow_capture.go | 31 ++- cmd/packet_capture.go | 12 +- cmd/root.go | 50 ++++- cmd/root_test.go | 2 +- commands/netobserv | 51 ++++- e2e/script_test.go | 2 +- res/service-account.yml | 28 +-- scripts/functions.sh | 479 ++++++++++++++++++++++------------------ 9 files changed, 410 insertions(+), 254 deletions(-) diff --git a/Dockerfile b/Dockerfile index bd5dc97d..a928856b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,15 @@ RUN mkdir -p output # Create final image from ubi + built binary and command FROM --platform=$TARGETPLATFORM registry.access.redhat.com/ubi9/ubi:9.4 WORKDIR / + +# Install oc to allow collector to run commands +RUN set -x; \ + OC_TAR_URL="https://mirror.openshift.com/pub/openshift-v4/$(uname -m)/clients/ocp/latest/openshift-client-linux.tar.gz" && \ + curl -L -q -o /tmp/oc.tar.gz "$OC_TAR_URL" && \ + tar -C /usr/bin/ -xvf /tmp/oc.tar.gz oc && \ + ln -sf /usr/bin/oc /usr/bin/kubectl && \ + rm -f /tmp/oc.tar.gz + COPY --from=builder /opt/app-root/build . COPY --from=builder --chown=65532:65532 /opt/app-root/output /output USER 65532:65532 diff --git a/cmd/flow_capture.go b/cmd/flow_capture.go index a30ee1b1..ddd4c06a 100644 --- a/cmd/flow_capture.go +++ b/cmd/flow_capture.go @@ -147,16 +147,20 @@ func runFlowCaptureOnAddr(port int, filename string) { // terminate capture if max bytes reached totalBytes = totalBytes + int64(bytes) if totalBytes > maxBytes { - log.Infof("Capture reached %s, exiting now...", sizestr.ToString(maxBytes)) - return + if exit := onLimitReached(); exit { + log.Infof("Capture reached %s, exiting now...", sizestr.ToString(maxBytes)) + return + } } // terminate capture if max time reached now := currentTime() duration := now.Sub(startupTime) if int(duration) > int(maxTime) { - log.Infof("Capture reached %s, exiting now...", maxTime) - return + if exit := onLimitReached(); exit { + log.Infof("Capture reached %s, exiting now...", sizestr.ToString(maxBytes)) + return + } } captureStarted = true @@ -241,7 +245,7 @@ func toSize(fieldName string) int { func updateTable() { // don't refresh terminal too often to avoid blinking now := currentTime() - if int(now.Sub(lastRefresh)) > int(maxRefreshRate) { + if !captureEnded && int(now.Sub(lastRefresh)) > int(maxRefreshRate) { lastRefresh = now resetTerminal() @@ -251,12 +255,18 @@ func updateTable() { fmt.Printf("Log level: %s ", logLevel) fmt.Printf("Duration: %s ", duration.Round(time.Second)) fmt.Printf("Capture size: %s\n", sizestr.ToString(totalBytes)) - if len(strings.TrimSpace(filter)) > 0 { - fmt.Printf("Filters: %s\n", filter) + if len(strings.TrimSpace(options)) > 0 { + fmt.Printf("Options: %s\n", options) + } + if strings.Contains(options, "background=true") { + fmt.Printf("Showing last: %d\n", flowsToShow) + fmt.Printf("Display: %s\n", strings.Join(display, ",")) + fmt.Printf("Enrichment: %s\n", strings.Join(enrichement, ",")) + } else { + fmt.Printf("Showing last: %d Use Up / Down keyboard arrows to increase / decrease limit\n", flowsToShow) + fmt.Printf("Display: %s Use Left / Right keyboard arrows to cycle views\n", strings.Join(display, ",")) + fmt.Printf("Enrichment: %s Use Page Up / Page Down keyboard keys to cycle enrichment scopes\n", strings.Join(enrichement, ",")) } - fmt.Printf("Showing last: %d Use Up / Down keyboard arrows to increase / decrease limit\n", flowsToShow) - fmt.Printf("Display: %s Use Left / Right keyboard arrows to cycle views\n", strings.Join(display, ",")) - fmt.Printf("Enrichment: %s Use Page Up / Page Down keyboard keys to cycle enrichment scopes\n", strings.Join(enrichement, ",")) } if slices.Contains(display, rawDisplay) { @@ -393,7 +403,6 @@ func updateTable() { fmt.Printf("Type anything to filter incoming flows in view\n") } } - } } diff --git a/cmd/packet_capture.go b/cmd/packet_capture.go index 706fd3e5..6cec6a8f 100644 --- a/cmd/packet_capture.go +++ b/cmd/packet_capture.go @@ -157,8 +157,10 @@ func runPacketCaptureOnAddr(port int, filename string) { // terminate capture if max bytes reached totalBytes = totalBytes + int64(len(fp.GenericMap.Value)) if totalBytes > maxBytes { - log.Infof("Capture reached %s, exiting now...", sizestr.ToString(maxBytes)) - return + if exit := onLimitReached(); exit { + log.Infof("Capture reached %s, exiting now...", sizestr.ToString(maxBytes)) + return + } } totalPackets = totalPackets + 1 @@ -166,8 +168,10 @@ func runPacketCaptureOnAddr(port int, filename string) { now := currentTime() duration := now.Sub(startupTime) if int(duration) > int(maxTime) { - log.Infof("Capture reached %s, exiting now...", maxTime) - return + if exit := onLimitReached(); exit { + log.Infof("Capture reached %s, exiting now...", maxTime) + return + } } captureStarted = true diff --git a/cmd/root.go b/cmd/root.go index c0990de0..6e55b004 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -24,7 +24,7 @@ var ( logLevel string ports []int nodes []string - filter string + options string maxTime time.Duration maxBytes int64 @@ -56,6 +56,7 @@ var ( captureType = "Flow" outputBuffer *bytes.Buffer captureStarted = false + captureEnded = false stopReceived = false keyboardError = "" ) @@ -71,7 +72,7 @@ func init() { rootCmd.PersistentFlags().StringVarP(&logLevel, "loglevel", "l", "info", "Log level") rootCmd.PersistentFlags().IntSliceVarP(&ports, "ports", "", []int{9999}, "TCP ports to listen") rootCmd.PersistentFlags().StringSliceVarP(&nodes, "nodes", "", []string{""}, "Node names per port (optionnal)") - rootCmd.PersistentFlags().StringVarP(&filter, "filter", "", "", "Filter(s)") + rootCmd.PersistentFlags().StringVarP(&options, "options", "", "", "Options(s)") rootCmd.PersistentFlags().DurationVarP(&maxTime, "maxtime", "", 5*time.Minute, "Maximum capture time") rootCmd.PersistentFlags().Int64VarP(&maxBytes, "maxbytes", "", 50000000, "Maximum capture bytes") @@ -100,10 +101,22 @@ func initConfig() { log.Fatalf("specified nodes names doesn't match ports length") } - log.Infof("Running network-observability-cli\nLog level: %s\nFilter(s): %s", logLevel, filter) + printBanner() + log.Infof("Log level: %s\nOption(s): %s", logLevel, options) showKernelVersion() } +func printBanner() { + fmt.Print(` +------------------------------------------------------------------------ + _ _ _ _ ___ _ ___ + | \| |___| |_ ___| |__ ___ ___ _ ___ __ / __| | |_ _| + | .' / -_) _/ _ \ '_ (_-/dev/null || which kubectl 2>/dev/null ) -K8S_CLI_BIN=$( basename "${K8S_CLI_BIN_PATH}" ) +K8S_CLI_BIN_PATH=$(which oc 2>/dev/null || which kubectl 2>/dev/null) +K8S_CLI_BIN=$(basename "${K8S_CLI_BIN_PATH}") # eBPF agent image to use agentImg="quay.io/netobserv/netobserv-ebpf-agent:main" @@ -64,16 +69,25 @@ function loadYAMLs() { } function clusterIsReady() { - # use oc whoami as connectivity check by default and fallback to kubectl get all if needed - K8S_CLI_CONNECTIVITY="${K8S_CLI_BIN} whoami" - if [ "${K8S_CLI_BIN}" = "kubectl" ]; then - K8S_CLI_CONNECTIVITY="${K8S_CLI_BIN} get all" - fi - if ${K8S_CLI_CONNECTIVITY} 2>&1 || ${K8S_CLI_BIN} cluster-info | grep -q "Kubernetes control plane"; then - return 0 - else - return 1 - fi + # use oc whoami as connectivity check by default and fallback to kubectl get all if needed + K8S_CLI_CONNECTIVITY="${K8S_CLI_BIN} whoami" + if [ "${K8S_CLI_BIN}" = "kubectl" ]; then + K8S_CLI_CONNECTIVITY="${K8S_CLI_BIN} get all" + fi + if ${K8S_CLI_CONNECTIVITY} 2>&1 || ${K8S_CLI_BIN} cluster-info | grep -q "Kubernetes control plane"; then + return 0 + else + return 1 + fi +} + +function namespaceFound() { + # ensure namespace doesn't exist, else we should not override content + if ${K8S_CLI_BIN} get namespace netobserv-cli --ignore-not-found=true | grep -q "netobserv-cli"; then + return 0 + else + return 1 + fi } FLOWS_MANIFEST_FILE="flow-capture.yml" @@ -94,6 +108,12 @@ function setup { exit 1 fi + if namespaceFound; then + printf "netobserv-cli namespace already exists. Ensure someone else is not running another capture on this cluster. Else use 'oc netobserv cleanup' to remove the namespace first.\n" >&2 + skipCleanup="true" + exit 1 + fi + # load yaml files loadYAMLs @@ -111,51 +131,83 @@ function setup { shift echo "creating flow-capture agents:" if [[ ! -d ${MANIFEST_OUTPUT_PATH} ]]; then - mkdir -p ${MANIFEST_OUTPUT_PATH} > /dev/null + mkdir -p ${MANIFEST_OUTPUT_PATH} >/dev/null fi manifest="${MANIFEST_OUTPUT_PATH}/${FLOWS_MANIFEST_FILE}" - echo "${flowAgentYAML}" > ${manifest} + echo "${flowAgentYAML}" >${manifest} options="$*" check_args_and_apply "$options" "$manifest" "flows" elif [ "$1" = "packets" ]; then shift echo "creating packet-capture agents" if [[ ! -d ${MANIFEST_OUTPUT_PATH} ]]; then - mkdir -p ${MANIFEST_OUTPUT_PATH} > /dev/null + mkdir -p ${MANIFEST_OUTPUT_PATH} >/dev/null fi manifest="${MANIFEST_OUTPUT_PATH}/${PACKETS_MANIFEST_FILE}" - echo "${packetAgentYAML}" > ${manifest} + echo "${packetAgentYAML}" >${manifest} options="$*" check_args_and_apply "$options" "$manifest" "packets" fi } +function follow { + ${K8S_CLI_BIN} logs collector -n netobserv-cli -f +} + function copyOutput { echo "Copying collector output files..." mkdir -p ./output ${K8S_CLI_BIN} cp -n netobserv-cli collector:output ./output } +function deleteDaemonset { + printf "\nDeleting daemonset... " + ${K8S_CLI_BIN} delete daemonset netobserv-cli -n netobserv-cli --ignore-not-found=true +} + +function deletePod { + printf "\nDeleting pod... " + ${K8S_CLI_BIN} delete pod collector -n netobserv-cli --ignore-not-found=true +} + +function deleteNamespace { + printf "\nDeleting namespace... " + ${K8S_CLI_BIN} delete namespace netobserv-cli --ignore-not-found=true +} + function cleanup { + if [[ "$runBackground" == "true" || "$skipCleanup" == "true" ]]; then + return + fi + # shellcheck disable=SC2034 if clusterIsReady; then if [ "$captureStarted" = false ]; then - echo "Can't copy since capture didn't start" + echo "Copy skipped" elif [[ "$isE2E" = true || "$copy" = true ]]; then copyOutput elif [ "$copy" = "prompt" ]; then while true; do - read -rp "Copy the capture output locally ?" yn - case $yn in - [Yy]* ) copyOutput; break;; - [Nn]* ) echo "copy skipped"; break;; - * ) echo "Please answer yes or no.";; - esac + read -rp "Copy the capture output locally ?" yn + case $yn in + [Yy]*) + copyOutput + break + ;; + [Nn]*) + echo "copy skipped" + break + ;; + *) echo "Please answer yes or no." ;; + esac done fi - printf "\nCleaning up... " - ${K8S_CLI_BIN} delete namespace netobserv-cli + printf "\nCleaning up..." + deleteDaemonset + deletePod + deleteNamespace + printf "\n" else echo "Cleanup namespace skipped" return @@ -167,6 +219,7 @@ function common_usage { echo " --log-level: components logs (default: info)" echo " --max-time: maximum capture time (default: 5m)" echo " --max-bytes: maximum capture bytes (default: 50000000 = 50MB)" + echo " --background: run in background (default: false)" echo " --copy: copy the output files locally (default: prompt)" # filters echo " --direction: filter direction (default: n/a)" @@ -279,11 +332,11 @@ function edit_manifest() { yq e --inplace ".spec.template.spec.containers[0].env[] |= select(.name==\"FILTER_ACTION\").value|=\"$2\"" "$3" ;; "filter_tcp_flags") - yq e --inplace ".spec.template.spec.containers[0].env[] |= select(.name==\"FILTER_TCP_FLAGS\").value|=\"$2\"" "$3" - ;; - "filter_pkt_drops") - yq e --inplace ".spec.template.spec.containers[0].env[] |= select(.name==\"FILTER_DROPS\").value|=\"$2\"" "$3" - ;; + yq e --inplace ".spec.template.spec.containers[0].env[] |= select(.name==\"FILTER_TCP_FLAGS\").value|=\"$2\"" "$3" + ;; + "filter_pkt_drops") + yq e --inplace ".spec.template.spec.containers[0].env[] |= select(.name==\"FILTER_DROPS\").value|=\"$2\"" "$3" + ;; "log_level") yq e --inplace ".spec.template.spec.containers[0].env[] |= select(.name==\"LOG_LEVEL\").value|=\"$2\"" "$3" ;; @@ -295,183 +348,191 @@ function edit_manifest() { #$2: manifest #$3: either flows or packets function check_args_and_apply() { - # Iterate through the command-line arguments - for option in $1; do - key="${option%%=*}" - value="${option#*=}" - case "$key" in - --copy) # Copy or skip without prompt - if [[ "$value" == "true" || "$value" == "false" || "$value" == "prompt" ]]; then - echo "param: $key, param_value: $value" - copy="$value" - else - echo "invalid value for --copy" - fi - ;; - --interfaces) # Interfaces - edit_manifest "interfaces" "$value" "$2" - ;; - --enable_pktdrop) # Enable packet drop - if [[ "$3" == "flows" ]]; then - if [[ "$value" == "true" || "$value" == "false" ]]; then - edit_manifest "pktdrop_enable" "$value" "$2" - else - echo "invalid value for --enable_pktdrop" - fi - else - echo "--enable_pktdrop is invalid option for packets" - exit 1 - fi - ;; - --enable_dns) # Enable DNS - if [[ "$3" == "flows" ]]; then - if [[ "$value" == "true" || "$value" == "false" ]]; then - edit_manifest "dns_enable" "$value" "$2" - else - echo "invalid value for --enable_dns" - fi - else - echo "--enable_dns is invalid option for packets" - exit 1 - fi - ;; - --enable_rtt) # Enable RTT - if [[ "$3" == "flows" ]]; then - if [[ "$value" == "true" || "$value" == "false" ]]; then - edit_manifest "rtt_enable" "$value" "$2" - else - echo "invalid value for --enable_rtt" - fi - else - echo "--enable_rtt is invalid option for packets" - exit 1 - fi - ;; - --enable_network_events) # Enable Network events monitoring - if [[ "$3" == "flows" ]]; then - if [[ "$value" == "true" || "$value" == "false" ]]; then - edit_manifest "network_events_enable" "$value" "$2" - else - echo "invalid value for --enable_network_events" - fi - else - echo "--enable_network_events is invalid option for packets" - exit 1 - fi - ;; - --enable_filter) # Enable flow filter - if [[ "$3" == "flows" ]]; then - if [[ "$value" == "true" || "$value" == "false" ]]; then - edit_manifest "filter_enable" "$value" "$2" - else - echo "invalid value for --enable_filter" - fi - else - echo "--enable_filter is invalid option for packets" - exit 1 - fi - ;; - --direction) # Configure filter direction - if [[ "$value" == "Ingress" || "$value" == "Egress" ]]; then - edit_manifest "filter_direction" "$value" "$2" - else - echo "invalid value for --direction" - fi - ;; - --cidr) # Configure flow CIDR - edit_manifest "filter_cidr" "$value" "$2" - ;; - --protocol) # Configure filter protocol - if [[ "$value" == "TCP" || "$value" == "UDP" || "$value" == "SCTP" || "$value" == "ICMP" || "$value" == "ICMPv6" ]]; then - edit_manifest "filter_protocol" "$value" "$2" - else - echo "invalid value for --protocol" - fi - ;; - --sport) # Configure filter source port - edit_manifest "filter_sport" "$value" "$2" - ;; - --dport) # Configure filter destination port - edit_manifest "filter_dport" "$value" "$2" - ;; - --port) # Configure filter port - edit_manifest "filter_port" "$value" "$2" - ;; - --sport_range) # Configure filter source port range - edit_manifest "filter_sport_range" "$value" "$2" - ;; - --dport_range) # Configure filter destination port range - edit_manifest "filter_dport_range" "$value" "$2" - ;; - --port_range) # Configure filter port range - edit_manifest "filter_port_range" "$value" "$2" - ;; - --sports) # Configure filter source two ports using "," - edit_manifest "filter_sports" "$value" "$2" - ;; - --dports) # Configure filter destination two ports using "," - edit_manifest "filter_dports" "$value" "$2" - ;; - --ports) # Configure filter on two ports usig "," can either be srcport or dstport - edit_manifest "filter_ports" "$value" "$2" - ;; - --tcp_flags) # Configure filter TCP flags - if [[ "$value" == "SYN" || "$value" == "SYN-ACK" || "$value" == "ACK" || "$value" == "FIN" || "$value" == "RST" || "$value" == "FIN-ACK" || "$value" == "RST-ACK" || "$value" == "PSH" || "$value" == "URG" || "$value" == "ECE" || "$value" == "CWR" ]]; then - edit_manifest "filter_tcp_flags" "$value" "$2" - else - echo "invalid value for --tcp_flags" - fi - ;; - --drops) # Filter packet drops - if [[ "$value" == "true" || "$value" == "false" ]]; then - edit_manifest "filter_pkt_drops" "$value" "$2" - else - echo "invalid value for --drops" - fi - ;; - --icmp_type) # ICMP type - edit_manifest "filter_icmp_type" "$value" "$2" - ;; - --icmp_code) # ICMP code - edit_manifest "filter_icmp_code" "$value" "$2" - ;; - --peer_ip) # Peer IP - edit_manifest "filter_peer_ip" "$value" "$2" - ;; - --action) # Filter action - if [[ "$value" == "Accept" || "$value" == "Reject" ]]; then - edit_manifest "filter_action" "$value" "$2" - else - echo "invalid value for --action" - fi - ;; - --log-level) # Log level - if [[ "$value" == "trace" || "$value" == "debug" || "$value" == "info" || "$value" == "warn" || "$value" == "error" || "$value" == "fatal" || "$value" == "panic" ]]; then - edit_manifest "log_level" "$value" "$2" - logLevel=$value - filter=${filter/$key=$logLevel/} - else - echo "invalid value for --action" - fi - ;; - --max-time) # Max time - echo "param: $key, param_value: $value" - maxTime=$value - filter=${filter/$key=$maxTime/} - ;; - --max-bytes) # Max bytes - echo "param: $key, param_value: $value" - maxBytes=$value - filter=${filter/$key=$maxBytes/} - ;; - *) # Invalid option - echo "Invalid option: $key" >&2 - exit 1 - ;; - esac - done + # Iterate through the command-line arguments + for option in $1; do + key="${option%%=*}" + value="${option#*=}" + case "$key" in + --background) # Run command in background + if [[ "$value" == "true" || "$value" == "false" ]]; then + echo "param: $key, param_value: $value" + runBackground="$value" + else + echo "invalid value for --background" + fi + ;; + --copy) # Copy or skip without prompt + if [[ "$value" == "true" || "$value" == "false" || "$value" == "prompt" ]]; then + echo "param: $key, param_value: $value" + copy="$value" + else + echo "invalid value for --copy" + fi + ;; + --interfaces) # Interfaces + edit_manifest "interfaces" "$value" "$2" + ;; + --enable_pktdrop) # Enable packet drop + if [[ "$3" == "flows" ]]; then + if [[ "$value" == "true" || "$value" == "false" ]]; then + edit_manifest "pktdrop_enable" "$value" "$2" + else + echo "invalid value for --enable_pktdrop" + fi + else + echo "--enable_pktdrop is invalid option for packets" + exit 1 + fi + ;; + --enable_dns) # Enable DNS + if [[ "$3" == "flows" ]]; then + if [[ "$value" == "true" || "$value" == "false" ]]; then + edit_manifest "dns_enable" "$value" "$2" + else + echo "invalid value for --enable_dns" + fi + else + echo "--enable_dns is invalid option for packets" + exit 1 + fi + ;; + --enable_rtt) # Enable RTT + if [[ "$3" == "flows" ]]; then + if [[ "$value" == "true" || "$value" == "false" ]]; then + edit_manifest "rtt_enable" "$value" "$2" + else + echo "invalid value for --enable_rtt" + fi + else + echo "--enable_rtt is invalid option for packets" + exit 1 + fi + ;; + --enable_network_events) # Enable Network events monitoring + if [[ "$3" == "flows" ]]; then + if [[ "$value" == "true" || "$value" == "false" ]]; then + edit_manifest "network_events_enable" "$value" "$2" + else + echo "invalid value for --enable_network_events" + fi + else + echo "--enable_network_events is invalid option for packets" + exit 1 + fi + ;; + --enable_filter) # Enable flow filter + if [[ "$3" == "flows" ]]; then + if [[ "$value" == "true" || "$value" == "false" ]]; then + edit_manifest "filter_enable" "$value" "$2" + else + echo "invalid value for --enable_filter" + fi + else + echo "--enable_filter is invalid option for packets" + exit 1 + fi + ;; + --direction) # Configure filter direction + if [[ "$value" == "Ingress" || "$value" == "Egress" ]]; then + edit_manifest "filter_direction" "$value" "$2" + else + echo "invalid value for --direction" + fi + ;; + --cidr) # Configure flow CIDR + edit_manifest "filter_cidr" "$value" "$2" + ;; + --protocol) # Configure filter protocol + if [[ "$value" == "TCP" || "$value" == "UDP" || "$value" == "SCTP" || "$value" == "ICMP" || "$value" == "ICMPv6" ]]; then + edit_manifest "filter_protocol" "$value" "$2" + else + echo "invalid value for --protocol" + fi + ;; + --sport) # Configure filter source port + edit_manifest "filter_sport" "$value" "$2" + ;; + --dport) # Configure filter destination port + edit_manifest "filter_dport" "$value" "$2" + ;; + --port) # Configure filter port + edit_manifest "filter_port" "$value" "$2" + ;; + --sport_range) # Configure filter source port range + edit_manifest "filter_sport_range" "$value" "$2" + ;; + --dport_range) # Configure filter destination port range + edit_manifest "filter_dport_range" "$value" "$2" + ;; + --port_range) # Configure filter port range + edit_manifest "filter_port_range" "$value" "$2" + ;; + --sports) # Configure filter source two ports using "," + edit_manifest "filter_sports" "$value" "$2" + ;; + --dports) # Configure filter destination two ports using "," + edit_manifest "filter_dports" "$value" "$2" + ;; + --ports) # Configure filter on two ports usig "," can either be srcport or dstport + edit_manifest "filter_ports" "$value" "$2" + ;; + --tcp_flags) # Configure filter TCP flags + if [[ "$value" == "SYN" || "$value" == "SYN-ACK" || "$value" == "ACK" || "$value" == "FIN" || "$value" == "RST" || "$value" == "FIN-ACK" || "$value" == "RST-ACK" || "$value" == "PSH" || "$value" == "URG" || "$value" == "ECE" || "$value" == "CWR" ]]; then + edit_manifest "filter_tcp_flags" "$value" "$2" + else + echo "invalid value for --tcp_flags" + fi + ;; + --drops) # Filter packet drops + if [[ "$value" == "true" || "$value" == "false" ]]; then + edit_manifest "filter_pkt_drops" "$value" "$2" + else + echo "invalid value for --drops" + fi + ;; + --icmp_type) # ICMP type + edit_manifest "filter_icmp_type" "$value" "$2" + ;; + --icmp_code) # ICMP code + edit_manifest "filter_icmp_code" "$value" "$2" + ;; + --peer_ip) # Peer IP + edit_manifest "filter_peer_ip" "$value" "$2" + ;; + --action) # Filter action + if [[ "$value" == "Accept" || "$value" == "Reject" ]]; then + edit_manifest "filter_action" "$value" "$2" + else + echo "invalid value for --action" + fi + ;; + --log-level) # Log level + if [[ "$value" == "trace" || "$value" == "debug" || "$value" == "info" || "$value" == "warn" || "$value" == "error" || "$value" == "fatal" || "$value" == "panic" ]]; then + edit_manifest "log_level" "$value" "$2" + logLevel=$value + filter=${filter/$key=$logLevel/} + else + echo "invalid value for --action" + fi + ;; + --max-time) # Max time + echo "param: $key, param_value: $value" + maxTime=$value + filter=${filter/$key=$maxTime/} + ;; + --max-bytes) # Max bytes + echo "param: $key, param_value: $value" + maxBytes=$value + filter=${filter/$key=$maxBytes/} + ;; + *) # Invalid option + echo "Invalid option: $key" >&2 + exit 1 + ;; + esac + done - ${K8S_CLI_BIN} apply -f "$2" - ${K8S_CLI_BIN} rollout status daemonset netobserv-cli -n netobserv-cli --timeout 60s - rm -rf ${MANIFEST_OUTPUT_PATH} -} \ No newline at end of file + ${K8S_CLI_BIN} apply -f "$2" + ${K8S_CLI_BIN} rollout status daemonset netobserv-cli -n netobserv-cli --timeout 60s + rm -rf ${MANIFEST_OUTPUT_PATH} +} From 65b14a338e9743e14347580d738815ecf92af728 Mon Sep 17 00:00:00 2001 From: Julien Pinsonneau Date: Mon, 14 Oct 2024 09:27:57 +0200 Subject: [PATCH 2/4] run in background --- scripts/functions.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/functions.sh b/scripts/functions.sh index 136dc5d7..c375d7a6 100755 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -90,6 +90,12 @@ function namespaceFound() { fi } +function namespaceFound() { + # ensure namespace doesn't exist, else we should not override content + found="$(oc get namespace netobserv-cli --ignore-not-found=true | grep -q 'Active')" + return "$found" +} + FLOWS_MANIFEST_FILE="flow-capture.yml" PACKETS_MANIFEST_FILE="packet-capture.yml" MANIFEST_OUTPUT_PATH="tmp" From 3f62151810b960c2c0601ea301f3ac241d2274fc Mon Sep 17 00:00:00 2001 From: Julien Pinsonneau Date: Mon, 14 Oct 2024 13:39:50 +0200 Subject: [PATCH 3/4] allow custom namespace --- commands/netobserv | 14 ++++++++++--- res/collector-service.yml | 2 +- res/flow-capture.yml | 4 ++-- res/namespace.yml | 2 +- res/packet-capture.yml | 4 ++-- res/service-account.yml | 8 ++++---- scripts/functions.sh | 42 +++++++++++++++++++++++++-------------- 7 files changed, 48 insertions(+), 28 deletions(-) diff --git a/commands/netobserv b/commands/netobserv index ad5403f2..eaf98cd2 100755 --- a/commands/netobserv +++ b/commands/netobserv @@ -15,6 +15,14 @@ if [ -z "${runBackground+x}" ]; then runBackground="false"; fi # options such as filters, background etc options="" +# namespace for this run +namespace="netobserv-cli" + +if [ -n "$NETOBSERV_NAMESPACE" ]; then + echo "using custom namespace $NETOBSERV_NAMESPACE" + namespace="$NETOBSERV_NAMESPACE" +fi + # CLI image to use img="quay.io/netobserv/network-observability-cli:main" @@ -129,7 +137,7 @@ fi echo "Running network-observability-cli get-$command... " ${K8S_CLI_BIN} run \ - -n netobserv-cli \ + -n $namespace \ collector \ --image=$img\ --image-pull-policy='Always' \ @@ -138,14 +146,14 @@ ${K8S_CLI_BIN} run \ --command -- $runCommand ${K8S_CLI_BIN} wait \ - -n netobserv-cli \ + -n $namespace \ --for=condition=Ready pod/collector || exit 1 captureStarted=true if [ -n "${execCommand}" ]; then ${K8S_CLI_BIN} exec -i --tty \ - -n netobserv-cli \ + -n $namespace \ collector \ -- $execCommand else diff --git a/res/collector-service.yml b/res/collector-service.yml index 528af885..a1259b65 100644 --- a/res/collector-service.yml +++ b/res/collector-service.yml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: name: collector - namespace: netobserv-cli + namespace: "{{NAMESPACE}}" spec: selector: run: collector diff --git a/res/flow-capture.yml b/res/flow-capture.yml index abe07fce..d158a28d 100644 --- a/res/flow-capture.yml +++ b/res/flow-capture.yml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: DaemonSet metadata: name: netobserv-cli - namespace: netobserv-cli + namespace: "{{NAMESPACE}}" labels: app: netobserv-cli spec: @@ -131,7 +131,7 @@ spec: "write":{ "type":"grpc", "grpc":{ - "targetHost":"collector.netobserv-cli.svc.cluster.local", + "targetHost":"{{TARGET_HOST}}", "targetPort":9999 } } diff --git a/res/namespace.yml b/res/namespace.yml index c7f44339..c76213d4 100644 --- a/res/namespace.yml +++ b/res/namespace.yml @@ -1,7 +1,7 @@ kind: Namespace apiVersion: v1 metadata: - name: netobserv-cli + name: "{{NAME}}" labels: app: netobserv pod-security.kubernetes.io/enforce: privileged diff --git a/res/packet-capture.yml b/res/packet-capture.yml index 3b63904c..190bd34b 100644 --- a/res/packet-capture.yml +++ b/res/packet-capture.yml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: DaemonSet metadata: name: netobserv-cli - namespace: netobserv-cli + namespace: "{{NAMESPACE}}" labels: app: netobserv-cli spec: @@ -115,7 +115,7 @@ spec: "write":{ "type":"grpc", "grpc":{ - "targetHost":"collector.netobserv-cli.svc.cluster.local", + "targetHost":"{{TARGET_HOST}}", "targetPort":9999 } } diff --git a/res/service-account.yml b/res/service-account.yml index e2459ebb..13de255a 100644 --- a/res/service-account.yml +++ b/res/service-account.yml @@ -2,13 +2,13 @@ apiVersion: v1 kind: ServiceAccount metadata: name: netobserv-cli - namespace: netobserv-cli + namespace: "{{NAMESPACE}}" --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: netobserv-cli - namespace: netobserv-cli + namespace: "{{NAMESPACE}}" rules: # allow running in privileged - apiGroups: @@ -53,11 +53,11 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: netobserv-cli - namespace: netobserv-cli + namespace: "{{NAMESPACE}}" subjects: - kind: ServiceAccount name: netobserv-cli - namespace: netobserv-cli + namespace: "{{NAMESPACE}}" roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole diff --git a/scripts/functions.sh b/scripts/functions.sh index c375d7a6..3b689362 100755 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -21,6 +21,17 @@ skipCleanup=false K8S_CLI_BIN_PATH=$(which oc 2>/dev/null || which kubectl 2>/dev/null) K8S_CLI_BIN=$(basename "${K8S_CLI_BIN_PATH}") +# namespace for this run +namespace="netobserv-cli" + +if [ -n "$NETOBSERV_NAMESPACE" ]; then + echo "using custom namespace $NETOBSERV_NAMESPACE" + namespace="$NETOBSERV_NAMESPACE" +fi + +# collector target host +targetHost="collector.$namespace.svc.cluster.local" + # eBPF agent image to use agentImg="quay.io/netobserv/netobserv-ebpf-agent:main" @@ -36,6 +47,7 @@ function loadYAMLs() { if [ -f ./res/namespace.yml ]; then namespaceYAML="$(cat ./res/namespace.yml)" fi + namespaceYAML="${namespaceYAML/"{{NAME}}"/${namespace}}" saYAML=' saYAMLContent @@ -43,6 +55,7 @@ function loadYAMLs() { if [ -f ./res/service-account.yml ]; then saYAML="$(cat ./res/service-account.yml)" fi + saYAML="${saYAML//"{{NAMESPACE}}"/${namespace}}" flowAgentYAML=' flowAgentYAMLContent @@ -50,6 +63,8 @@ function loadYAMLs() { if [ -f ./res/flow-capture.yml ]; then flowAgentYAML="$(cat ./res/flow-capture.yml)" fi + flowAgentYAML="${flowAgentYAML/"{{NAMESPACE}}"/${namespace}}" + flowAgentYAML="${flowAgentYAML/"{{TARGET_HOST}}"/${targetHost}}" flowAgentYAML="${flowAgentYAML/"{{AGENT_IMAGE_URL}}"/${agentImg}}" packetAgentYAML=' @@ -58,6 +73,8 @@ function loadYAMLs() { if [ -f ./res/packet-capture.yml ]; then packetAgentYAML="$(cat ./res/packet-capture.yml)" fi + packetAgentYAML="${packetAgentYAML/"{{NAMESPACE}}"/${namespace}}" + packetAgentYAML="${packetAgentYAML/"{{TARGET_HOST}}"/${targetHost}}" packetAgentYAML="${packetAgentYAML/"{{AGENT_IMAGE_URL}}"/${agentImg}}" collectorServiceYAML=' @@ -66,6 +83,7 @@ function loadYAMLs() { if [ -f ./res/collector-service.yml ]; then collectorServiceYAML="$(cat ./res/collector-service.yml)" fi + collectorServiceYAML="${collectorServiceYAML/"{{NAMESPACE}}"/${namespace}}" } function clusterIsReady() { @@ -83,19 +101,13 @@ function clusterIsReady() { function namespaceFound() { # ensure namespace doesn't exist, else we should not override content - if ${K8S_CLI_BIN} get namespace netobserv-cli --ignore-not-found=true | grep -q "netobserv-cli"; then + if ${K8S_CLI_BIN} get namespace "$namespace" --ignore-not-found=true | grep -q "$namespace"; then return 0 else return 1 fi } -function namespaceFound() { - # ensure namespace doesn't exist, else we should not override content - found="$(oc get namespace netobserv-cli --ignore-not-found=true | grep -q 'Active')" - return "$found" -} - FLOWS_MANIFEST_FILE="flow-capture.yml" PACKETS_MANIFEST_FILE="packet-capture.yml" MANIFEST_OUTPUT_PATH="tmp" @@ -115,7 +127,7 @@ function setup { fi if namespaceFound; then - printf "netobserv-cli namespace already exists. Ensure someone else is not running another capture on this cluster. Else use 'oc netobserv cleanup' to remove the namespace first.\n" >&2 + printf "%s namespace already exists. Ensure someone else is not running another capture on this cluster. Else use 'oc netobserv cleanup' to remove the namespace first.\n" "$namespace" >&2 skipCleanup="true" exit 1 fi @@ -124,7 +136,7 @@ function setup { loadYAMLs # apply yamls - echo "creating netobserv-cli namespace" + echo "creating $namespace namespace" echo "$namespaceYAML" | ${K8S_CLI_BIN} apply -f - echo "creating service account" @@ -157,28 +169,28 @@ function setup { } function follow { - ${K8S_CLI_BIN} logs collector -n netobserv-cli -f + ${K8S_CLI_BIN} logs collector -n "$namespace" -f } function copyOutput { echo "Copying collector output files..." mkdir -p ./output - ${K8S_CLI_BIN} cp -n netobserv-cli collector:output ./output + ${K8S_CLI_BIN} cp -n "$namespace" collector:output ./output } function deleteDaemonset { printf "\nDeleting daemonset... " - ${K8S_CLI_BIN} delete daemonset netobserv-cli -n netobserv-cli --ignore-not-found=true + ${K8S_CLI_BIN} delete daemonset netobserv-cli -n "$namespace" --ignore-not-found=true } function deletePod { printf "\nDeleting pod... " - ${K8S_CLI_BIN} delete pod collector -n netobserv-cli --ignore-not-found=true + ${K8S_CLI_BIN} delete pod collector -n "$namespace" --ignore-not-found=true } function deleteNamespace { printf "\nDeleting namespace... " - ${K8S_CLI_BIN} delete namespace netobserv-cli --ignore-not-found=true + ${K8S_CLI_BIN} delete namespace "$namespace" --ignore-not-found=true } function cleanup { @@ -539,6 +551,6 @@ function check_args_and_apply() { done ${K8S_CLI_BIN} apply -f "$2" - ${K8S_CLI_BIN} rollout status daemonset netobserv-cli -n netobserv-cli --timeout 60s + ${K8S_CLI_BIN} rollout status daemonset netobserv-cli -n "$namespace" --timeout 60s rm -rf ${MANIFEST_OUTPUT_PATH} } From e98fbe6088a566820c58234ecfc7cf39a14b8803 Mon Sep 17 00:00:00 2001 From: Julien Pinsonneau Date: Mon, 21 Oct 2024 12:05:51 +0200 Subject: [PATCH 4/4] install tar if needed --- Dockerfile | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index a928856b..4c73888f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,12 @@ COPY .mk/ .mk/ # Build collector RUN GOARCH=$TARGETARCH make compile +# Install oc to allow collector to run commands +RUN set -x; \ + OC_TAR_URL="https://mirror.openshift.com/pub/openshift-v4/$(uname -m)/clients/ocp/latest/openshift-client-linux.tar.gz" && \ + curl -L -q -o /tmp/oc.tar.gz "$OC_TAR_URL" && \ + tar -C /tmp -xvf /tmp/oc.tar.gz oc kubectl + # Embedd commands in case users want to pull it from collector image RUN USER=netobserv VERSION=main make oc-commands @@ -36,15 +42,10 @@ RUN mkdir -p output FROM --platform=$TARGETPLATFORM registry.access.redhat.com/ubi9/ubi:9.4 WORKDIR / -# Install oc to allow collector to run commands -RUN set -x; \ - OC_TAR_URL="https://mirror.openshift.com/pub/openshift-v4/$(uname -m)/clients/ocp/latest/openshift-client-linux.tar.gz" && \ - curl -L -q -o /tmp/oc.tar.gz "$OC_TAR_URL" && \ - tar -C /usr/bin/ -xvf /tmp/oc.tar.gz oc && \ - ln -sf /usr/bin/oc /usr/bin/kubectl && \ - rm -f /tmp/oc.tar.gz - COPY --from=builder /opt/app-root/build . +COPY --from=builder /opt/app-root/build . +COPY --from=builder /tmp/oc /usr/bin/oc +COPY --from=builder /tmp/kubectl /usr/bin/kubectl COPY --from=builder --chown=65532:65532 /opt/app-root/output /output USER 65532:65532