From e71cb611c0797e7459fda571a19f76724f9fab5d Mon Sep 17 00:00:00 2001 From: Mitch Kelley Date: Fri, 3 May 2019 13:13:14 -0400 Subject: [PATCH] support process specification (#193) * introduce multi-process example app * use v0.0.2 images * changelog * wip: introduce failing test * add optional timeout for cli wrapper * refactor test setup * implement feature, test passing * collided with a stale ns, reduce likelihood * gen docs * make docs in build --- Gopkg.lock | 2 +- Makefile | 2 + changelog/v0.5.13/multi-process.yaml | 4 + contrib/condition/multi_process/.gitignore | 1 + contrib/condition/multi_process/Makefile | 32 ++++++++ contrib/condition/multi_process/README.md | 69 ++++++++++++++++ contrib/condition/multi_process/call_app.sh | 5 ++ contrib/condition/multi_process/main.go | 32 ++++++++ .../condition/multi_process/multi.dockerfile | 6 ++ contrib/condition/multi_process/multi.yaml | 20 +++++ .../condition/multi_process/single.dockerfile | 5 ++ contrib/condition/multi_process/single.yaml | 20 +++++ docs/cli/squashctl.md | 2 + docs/cli/squashctl_completion.md | 2 + docs/cli/squashctl_deploy.md | 2 + docs/cli/squashctl_deploy_demo.md | 2 + docs/cli/squashctl_deploy_squash.md | 2 + docs/cli/squashctl_squash.md | 2 + docs/cli/squashctl_squash_delete.md | 2 + docs/cli/squashctl_squash_status.md | 2 + docs/cli/squashctl_utils.md | 3 + .../cli/squashctl_utils_delete-attachments.md | 2 + .../cli/squashctl_utils_delete-permissions.md | 2 + docs/cli/squashctl_utils_delete-planks.md | 2 + docs/cli/squashctl_utils_list-attachments.md | 2 + .../cli/squashctl_utils_register-resources.md | 48 ++++++++++++ pkg/config/squash.go | 1 + pkg/plank/container.go | 32 +++++++- pkg/squashctl/app.go | 3 +- test/e2e/multi_process_test.go | 63 +++++++++++++++ test/e2e/session_test.go | 78 ++++++++++++++----- test/testutils/utils.go | 46 +++++++++-- 32 files changed, 466 insertions(+), 30 deletions(-) create mode 100644 changelog/v0.5.13/multi-process.yaml create mode 100644 contrib/condition/multi_process/.gitignore create mode 100644 contrib/condition/multi_process/Makefile create mode 100644 contrib/condition/multi_process/README.md create mode 100755 contrib/condition/multi_process/call_app.sh create mode 100644 contrib/condition/multi_process/main.go create mode 100644 contrib/condition/multi_process/multi.dockerfile create mode 100644 contrib/condition/multi_process/multi.yaml create mode 100644 contrib/condition/multi_process/single.dockerfile create mode 100644 contrib/condition/multi_process/single.yaml create mode 100644 docs/cli/squashctl_utils_register-resources.md create mode 100644 test/e2e/multi_process_test.go diff --git a/Gopkg.lock b/Gopkg.lock index 8715970..b285f03 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1466,6 +1466,7 @@ analyzer-version = 1 input-imports = [ "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes", + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2", "github.com/davecgh/go-spew/spew", "github.com/derekparker/delve/service/rpc1", "github.com/gogo/protobuf/gogoproto", @@ -1535,7 +1536,6 @@ "k8s.io/apimachinery/pkg/runtime/schema", "k8s.io/apimachinery/pkg/util/intstr", "k8s.io/client-go/kubernetes", - "k8s.io/client-go/kubernetes/scheme", "k8s.io/client-go/plugin/pkg/client/auth", "k8s.io/client-go/plugin/pkg/client/auth/gcp", "k8s.io/client-go/rest", diff --git a/Makefile b/Makefile index 55468c3..381706e 100644 --- a/Makefile +++ b/Makefile @@ -100,6 +100,8 @@ clean: generatecode: must mkdir -p $(OUTPUT_DIR) go run cmd/generate-code/main.go + rm docs/cli/squashctl* + go run cmd/generate-docs/main.go gofmt -w ci cmd pkg test goimports -w ci cmd pkg test diff --git a/changelog/v0.5.13/multi-process.yaml b/changelog/v0.5.13/multi-process.yaml new file mode 100644 index 0000000..c296d3d --- /dev/null +++ b/changelog/v0.5.13/multi-process.yaml @@ -0,0 +1,4 @@ +description: +- type: NEW_FEATURE + description: Option to pass a process selector, rather than default to PID 1 + issueLink: https://github.com/solo-io/squash/issues/173 diff --git a/contrib/condition/multi_process/.gitignore b/contrib/condition/multi_process/.gitignore new file mode 100644 index 0000000..c4fff02 --- /dev/null +++ b/contrib/condition/multi_process/.gitignore @@ -0,0 +1 @@ +_out/ diff --git a/contrib/condition/multi_process/Makefile b/contrib/condition/multi_process/Makefile new file mode 100644 index 0000000..47edc28 --- /dev/null +++ b/contrib/condition/multi_process/Makefile @@ -0,0 +1,32 @@ +IMAGE_TAG ?= dev +CONTAINER_REPO_ORG ?= docker.io/soloio + +## These are the images created by this makefile +MULTI_SPEC := $(CONTAINER_REPO_ORG)/multi_process:$(IMAGE_TAG) +SINGLE_SPEC := $(CONTAINER_REPO_ORG)/multi_process_base:$(IMAGE_TAG) + +ROOTDIR := $(shell pwd) +OUTPUT_DIR := $(ROOTDIR)/_output + +.PHONY: all +all: push-single push-multi + +.PHONY: compile +compile: + GOOS=linux go build -gcflags "-N -l" -o $(OUTPUT_DIR)/sample_app main.go + +.PHONY: build-multi +build-multi: compile + docker build -t $(MULTI_SPEC) -f multi.dockerfile . + +.PHONY: push-multi +push-multi: build-multi + docker push $(MULTI_SPEC) + +.PHONY: build-single +build-single: compile + docker build -t $(SINGLE_SPEC) -f single.dockerfile . + +.PHONY: push-single +push-single: build-single + docker push $(SINGLE_SPEC) diff --git a/contrib/condition/multi_process/README.md b/contrib/condition/multi_process/README.md new file mode 100644 index 0000000..a26005e --- /dev/null +++ b/contrib/condition/multi_process/README.md @@ -0,0 +1,69 @@ +# Debug arbitrary container PIDs with Squash + +At one point, Squash assumed that a user's debug target was completely described by the selection of: namespace, pod name, container. Squash assumed that the user wanted to debug the first process in the container. This is a reasonable assumption, since a popular container usage pattern is to specify one process per container. However it may be useful to run multiple processes in a single container. In order for squash to debug an arbitrary process, it needs to be told how to choose among the available processes. + +# Demonstration of the properties of multi-process containers + +This directory includes files needed to build and deploy a sample app as the first process in one container and the second process in a separate container. + +## Build and push the containers + +*This step is not needed, as the container images are already available with the values shown below. If you change these values you will need to update the manifests similarly.* + +```bash +export CONTAINER_REPO_ORG = docker.io/soloio +IMAGE_TAG=v0.0.2 make all +``` + +## Deploy the containers + +We will deploy our sample containers in their own pods: + +```bash +kubectl apply -f single.yaml +kubectl apply -f multi.yaml +``` + +## Inspect the images + +Note that the container with a single process features our app in PID 1 + +```bash +k exec -it squash-demo-multiprocess-base-6c746c8595-kpsvt -- /bin/s +h +/app # ls +sample_app +/app # ps +PID USER TIME COMMAND + 1 root 0:00 ./sample_app + 19 root 0:00 /bin/sh + 26 root 0:00 ps +``` + +However, for our multi-process container, our app is not PID 1 + +```bash +k exec -it squash-demo-multiprocess-5fbdcd96cf-k9bzw -- /bin/sh +/app # ls +call_app.sh sample_app +/app # ps +PID USER TIME COMMAND + 1 root 0:00 {call_app.sh} /bin/sh ./call_app.sh + 7 root 0:00 ./sample_app + 20 root 0:00 /bin/sh + 27 root 0:00 ps +``` + +## Debug the processes with squash + +You can debug the single process container without passing any flags. Squash will use the first PID by default. This works fine with our single process example. + +```bash +squashctl # follow interactive prompt to choose target debug container +``` + +To debug a multi-process container, you need to specify a process-identifier string. Squash will look for processes whos invocation comand matches the string provided. + +```bash +squashctl --process sample_app # matches with case-insensitive regex +``` diff --git a/contrib/condition/multi_process/call_app.sh b/contrib/condition/multi_process/call_app.sh new file mode 100755 index 0000000..35ea423 --- /dev/null +++ b/contrib/condition/multi_process/call_app.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +echo running the app from a bash script + +./sample_app \ No newline at end of file diff --git a/contrib/condition/multi_process/main.go b/contrib/condition/multi_process/main.go new file mode 100644 index 0000000..d8685d6 --- /dev/null +++ b/contrib/condition/multi_process/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" +) + +var ServiceToCall = "example-service2" + +func main() { + + fmt.Println("starting app") + potentialservice2 := os.Getenv("SERVICE2_URL") + if potentialservice2 != "" { + ServiceToCall = potentialservice2 + } + + http.HandleFunc("/calc", handler) + http.HandleFunc("/", view) + + log.Fatal(http.ListenAndServe(":8080", nil)) +} + +func view(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello") +} + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello again") +} diff --git a/contrib/condition/multi_process/multi.dockerfile b/contrib/condition/multi_process/multi.dockerfile new file mode 100644 index 0000000..b9662c7 --- /dev/null +++ b/contrib/condition/multi_process/multi.dockerfile @@ -0,0 +1,6 @@ +# calls a go process from a shell script, making it the second PID +FROM alpine +WORKDIR /app +ADD _output/sample_app /app +ADD call_app.sh /app +ENTRYPOINT ./call_app.sh diff --git a/contrib/condition/multi_process/multi.yaml b/contrib/condition/multi_process/multi.yaml new file mode 100644 index 0000000..4ed9149 --- /dev/null +++ b/contrib/condition/multi_process/multi.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + name: squash-demo-multiprocess +spec: + replicas: 1 + selector: + matchLabels: + app: squash-demo-multiprocess + template: + metadata: + labels: + app: squash-demo-multiprocess + spec: + containers: + - name: squash-demo-multiprocess + image: soloio/multi_process:v0.0.2 + ports: + - containerPort: 8080 + protocol: TCP diff --git a/contrib/condition/multi_process/single.dockerfile b/contrib/condition/multi_process/single.dockerfile new file mode 100644 index 0000000..14a2516 --- /dev/null +++ b/contrib/condition/multi_process/single.dockerfile @@ -0,0 +1,5 @@ +# calls the go process only, making it the first PID +FROM alpine +WORKDIR /app +ADD _output/sample_app /app +ENTRYPOINT ./sample_app diff --git a/contrib/condition/multi_process/single.yaml b/contrib/condition/multi_process/single.yaml new file mode 100644 index 0000000..df7d75d --- /dev/null +++ b/contrib/condition/multi_process/single.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + name: squash-demo-multiprocess-base +spec: + replicas: 1 + selector: + matchLabels: + app: squash-demo-multiprocess-base + template: + metadata: + labels: + app: squash-demo-multiprocess-base + spec: + containers: + - name: squash-demo-multiprocess-base + image: soloio/multi_process_base:v0.0.2 + ports: + - containerPort: 8080 + protocol: TCP diff --git a/docs/cli/squashctl.md b/docs/cli/squashctl.md index b4d961f..022774f 100644 --- a/docs/cli/squashctl.md +++ b/docs/cli/squashctl.md @@ -22,6 +22,7 @@ squashctl [flags] ### Options ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -36,6 +37,7 @@ squashctl [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_completion.md b/docs/cli/squashctl_completion.md index 7d62705..eb0d52a 100644 --- a/docs/cli/squashctl_completion.md +++ b/docs/cli/squashctl_completion.md @@ -55,6 +55,7 @@ squashctl completion SHELL [flags] ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -68,6 +69,7 @@ squashctl completion SHELL [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_deploy.md b/docs/cli/squashctl_deploy.md index b5e0fe5..fcf0c37 100644 --- a/docs/cli/squashctl_deploy.md +++ b/docs/cli/squashctl_deploy.md @@ -19,6 +19,7 @@ deploy squash or a demo microservice ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -32,6 +33,7 @@ deploy squash or a demo microservice --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_deploy_demo.md b/docs/cli/squashctl_deploy_demo.md index 44ab63b..f28bae5 100644 --- a/docs/cli/squashctl_deploy_demo.md +++ b/docs/cli/squashctl_deploy_demo.md @@ -26,6 +26,7 @@ squashctl deploy demo [flags] ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -39,6 +40,7 @@ squashctl deploy demo [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_deploy_squash.md b/docs/cli/squashctl_deploy_squash.md index 8bce2a0..58171ae 100644 --- a/docs/cli/squashctl_deploy_squash.md +++ b/docs/cli/squashctl_deploy_squash.md @@ -24,6 +24,7 @@ squashctl deploy squash [flags] ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -37,6 +38,7 @@ squashctl deploy squash [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_squash.md b/docs/cli/squashctl_squash.md index c5433e0..5c39574 100644 --- a/docs/cli/squashctl_squash.md +++ b/docs/cli/squashctl_squash.md @@ -31,6 +31,7 @@ squashctl squash [flags] ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -44,6 +45,7 @@ squashctl squash [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_squash_delete.md b/docs/cli/squashctl_squash_delete.md index 604a797..1f3b8e9 100644 --- a/docs/cli/squashctl_squash_delete.md +++ b/docs/cli/squashctl_squash_delete.md @@ -23,6 +23,7 @@ squashctl squash delete [flags] ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -36,6 +37,7 @@ squashctl squash delete [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_squash_status.md b/docs/cli/squashctl_squash_status.md index f694ecb..518d343 100644 --- a/docs/cli/squashctl_squash_status.md +++ b/docs/cli/squashctl_squash_status.md @@ -23,6 +23,7 @@ squashctl squash status [flags] ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -36,6 +37,7 @@ squashctl squash status [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_utils.md b/docs/cli/squashctl_utils.md index 1ea58e7..9b791f1 100644 --- a/docs/cli/squashctl_utils.md +++ b/docs/cli/squashctl_utils.md @@ -29,6 +29,7 @@ squash utils list-attachments ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -42,6 +43,7 @@ squash utils list-attachments --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` @@ -53,4 +55,5 @@ squash utils list-attachments * [squashctl utils delete-permissions](../squashctl_utils_delete-permissions) - remove all service accounts, roles, and role bindings created by Squash. * [squashctl utils delete-planks](../squashctl_utils_delete-planks) - remove all plank debugger pods created by Squash. * [squashctl utils list-attachments](../squashctl_utils_list-attachments) - list all existing debug attachments +* [squashctl utils register-resources](../squashctl_utils_register-resources) - register the custom resource definitions (CRDs) needed by squash diff --git a/docs/cli/squashctl_utils_delete-attachments.md b/docs/cli/squashctl_utils_delete-attachments.md index aed2d76..84e1e3d 100644 --- a/docs/cli/squashctl_utils_delete-attachments.md +++ b/docs/cli/squashctl_utils_delete-attachments.md @@ -23,6 +23,7 @@ squashctl utils delete-attachments [flags] ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -36,6 +37,7 @@ squashctl utils delete-attachments [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_utils_delete-permissions.md b/docs/cli/squashctl_utils_delete-permissions.md index 396253a..3a90434 100644 --- a/docs/cli/squashctl_utils_delete-permissions.md +++ b/docs/cli/squashctl_utils_delete-permissions.md @@ -23,6 +23,7 @@ squashctl utils delete-permissions [flags] ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -36,6 +37,7 @@ squashctl utils delete-permissions [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_utils_delete-planks.md b/docs/cli/squashctl_utils_delete-planks.md index 31d3e31..95bd66a 100644 --- a/docs/cli/squashctl_utils_delete-planks.md +++ b/docs/cli/squashctl_utils_delete-planks.md @@ -23,6 +23,7 @@ squashctl utils delete-planks [flags] ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -36,6 +37,7 @@ squashctl utils delete-planks [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_utils_list-attachments.md b/docs/cli/squashctl_utils_list-attachments.md index ef3647e..a25f39d 100644 --- a/docs/cli/squashctl_utils_list-attachments.md +++ b/docs/cli/squashctl_utils_list-attachments.md @@ -23,6 +23,7 @@ squashctl utils list-attachments [flags] ### Options inherited from parent commands ``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) --container string Container to debug --container-repo string debug container repo to use (default "soloio") --container-version string debug container version to use (default "mkdev") @@ -36,6 +37,7 @@ squashctl utils list-attachments [flags] --no-guess-debugger don't auto detect debugger to use --no-guess-pod don't auto detect pod to use --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") --timeout int timeout in seconds to wait for debug pod to be ready (default 300) ``` diff --git a/docs/cli/squashctl_utils_register-resources.md b/docs/cli/squashctl_utils_register-resources.md new file mode 100644 index 0000000..c5d64e1 --- /dev/null +++ b/docs/cli/squashctl_utils_register-resources.md @@ -0,0 +1,48 @@ +--- +title: "squashctl utils register-resources" +weight: 5 +--- +## squashctl utils register-resources + +register the custom resource definitions (CRDs) needed by squash + +### Synopsis + +register the custom resource definitions (CRDs) needed by squash + +``` +squashctl utils register-resources [flags] +``` + +### Options + +``` + -h, --help help for register-resources +``` + +### Options inherited from parent commands + +``` + --config string optional, path to squash config (defaults to ~/.squash/config.yaml) + --container string Container to debug + --container-repo string debug container repo to use (default "soloio") + --container-version string debug container version to use (default "mkdev") + --crisock string The path to the CRI socket (default "/var/run/dockershim.sock") + --debugger string Debugger to use + --json output json format + --localport int local port to use to connect to debugger (defaults to random free port) + --machine machine mode input and output + --namespace string Namespace to debug + --no-clean don't clean temporary pod when existing + --no-guess-debugger don't auto detect debugger to use + --no-guess-pod don't auto detect pod to use + --pod string Pod to debug + --process-match string optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process. + --squash-namespace string the namespace where squash resources will be deployed (default: squash-debugger) (default "squash-debugger") + --timeout int timeout in seconds to wait for debug pod to be ready (default 300) +``` + +### SEE ALSO + +* [squashctl utils](../squashctl_utils) - call various squash utils + diff --git a/pkg/config/squash.go b/pkg/config/squash.go index 9a3d997..48ef75c 100644 --- a/pkg/config/squash.go +++ b/pkg/config/squash.go @@ -40,6 +40,7 @@ type Squash struct { Container string Machine bool DebugServerAddress string + ProcessName string CRISock string diff --git a/pkg/plank/container.go b/pkg/plank/container.go index dc5b9da..a0a46ae 100644 --- a/pkg/plank/container.go +++ b/pkg/plank/container.go @@ -3,6 +3,12 @@ package plank import ( "context" "fmt" + "regexp" + "strings" + + "github.com/pkg/errors" + v1 "github.com/solo-io/squash/pkg/api/v1" + "github.com/solo-io/squash/pkg/utils" "github.com/solo-io/squash/pkg/platforms" "github.com/solo-io/squash/pkg/platforms/kubernetes" @@ -31,8 +37,32 @@ func Debug(ctx context.Context) error { return err } - pid := info.Pids[0] + pid, err := getPid(&cfg.Attachment, info) + if err != nil { + return err + } fmt.Println("about to serve") return startDebugging(cfg, pid) } + +func getPid(da *v1.DebugAttachment, info *platforms.ContainerInfo) (int, error) { + if da.ProcessName == "" { + return info.Pids[0], nil + } + reg, err := regexp.Compile(strings.ToLower(da.ProcessName)) + if err != nil { + return 0, errors.Wrapf(err, "unable to match process name, invalid match specification") + } + for _, pid := range info.Pids { + cmdLines, err := utils.GetCmdArgsByPid(pid) + if err != nil { + return 0, errors.Wrapf(err, "could not get command line for pid %v", pid) + } + preparedCmdLine := strings.ToLower(strings.Join(cmdLines, "")) + if reg.MatchString(preparedCmdLine) { + return pid, nil + } + } + return 0, errors.Wrapf(err, "could not find a command line matching %v", da.ProcessName) +} diff --git a/pkg/squashctl/app.go b/pkg/squashctl/app.go index 7cb85b0..0ea5e13 100644 --- a/pkg/squashctl/app.go +++ b/pkg/squashctl/app.go @@ -111,6 +111,7 @@ func applySquashFlags(cfg *config.Squash, f *pflag.FlagSet) { f.StringVar(&cfg.Container, "container", "", "Container to debug") f.StringVar(&cfg.CRISock, "crisock", "/var/run/dockershim.sock", "The path to the CRI socket") f.StringVar(&cfg.SquashNamespace, "squash-namespace", sqOpts.SquashNamespace, fmt.Sprintf("the namespace where squash resources will be deployed (default: %v)", options.SquashNamespace)) + f.StringVar(&cfg.ProcessName, "process-match", "", "optional, if passed, Squash will try to find a process in the target container that matches (regex, case-insensitive) this string. Otherwise Squash chooses the first process.") } func initializeOptions(o *Options) { @@ -227,7 +228,7 @@ func (o *Options) writeDebugAttachment() error { dbge.Container.Image, dbge.Pod.Name, so.Container, - "", + so.ProcessName, so.Debugger) return nil diff --git a/test/e2e/multi_process_test.go b/test/e2e/multi_process_test.go new file mode 100644 index 0000000..7070f4e --- /dev/null +++ b/test/e2e/multi_process_test.go @@ -0,0 +1,63 @@ +package e2e_test + +import ( + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + gotestutils "github.com/solo-io/go-utils/testutils" + "github.com/solo-io/squash/test/testutils" + "k8s.io/client-go/kubernetes" +) + +// To verify that this test does what you expect on a simple container, set verifySelfMode=false +// Suggestion: run this function twice in adjacent It() blocks, use the same args, except set verifySelfMode=true for +// the first call, and false for the second call +func multiProcessTest(cs *kubernetes.Clientset, appNamespace, plankNamespace, processMatch string, verifySelfMode bool) { + installFile := "../../contrib/condition/multi_process/multi.yaml" + labelSelector := "app=squash-demo-multiprocess" + if verifySelfMode { + installFile = "../../contrib/condition/multi_process/single.yaml" + labelSelector = "app=squash-demo-multiprocess-base" + } + applyOut, err := gotestutils.KubectlOut("apply", "-f", installFile, "-n", appNamespace) + Expect(err).NotTo(HaveOccurred()) + _, err = fmt.Fprintf(GinkgoWriter, applyOut) + Expect(err).NotTo(HaveOccurred()) + appName, err := waitForPodByLabel(cs, appNamespace, labelSelector) + Expect(err).NotTo(HaveOccurred()) + By("should attach a dlv debugger") + + By("starting debug session") + // give it enough time to pull the image, but don't be as lenient as squashctl itself since the test environments + // should be able to pull all images in less than one minute + timeLimitSeconds := 100 + dbgStr, err := testutils.SquashctlOutWithTimeout(testutils.MachineDebugArgs(testConditions, + "dlv", + appNamespace, + appName, + plankNamespace, + "", + processMatch), &timeLimitSeconds) + Expect(err).NotTo(HaveOccurred()) + + By("should have created the required permissions") + err = ensurePlankPermissionsWereCreated(cs, plankNamespace) + Expect(err).NotTo(HaveOccurred()) + validateMachineDebugOutput(dbgStr) + + By("should speak with dlv") + ensureDLVServerIsLive(dbgStr) + + By("should list expected resources after debug session initiated") + attachmentList, err := testutils.SquashctlOut("utils list-attachments") + Expect(err).NotTo(HaveOccurred()) + validateUtilsListDebugAttachments(attachmentList, 1) + + By("utils delete-planks should not delete non-plank pods") + err = testutils.Squashctl(fmt.Sprintf("utils delete-planks --squash-namespace %v", plankNamespace)) + Expect(err).NotTo(HaveOccurred()) + appPods := mustGetActivePlankNsPods(cs, appNamespace) + Expect(len(appPods)).To(Equal(1)) + Expect(appPods[0].Name).To(Equal(appName)) +} diff --git a/test/e2e/session_test.go b/test/e2e/session_test.go index 3cae7f6..97448ce 100644 --- a/test/e2e/session_test.go +++ b/test/e2e/session_test.go @@ -40,8 +40,6 @@ var _ = Describe("Single debug mode", func() { var ( testNamespace string testPlankNamespace string - goPodName string - javaPodName string cs *kubernetes.Clientset logCtx context.Context cancelFunc context.CancelFunc @@ -56,8 +54,8 @@ var _ = Describe("Single debug mode", func() { } }() - testNamespace = fmt.Sprintf("testsquash-demos-%v", rand.Intn(1000)) - testPlankNamespace = fmt.Sprintf("testsquash-planks-%v", rand.Intn(1000)) + testNamespace = fmt.Sprintf("testsquash-demos-%v", rand.Intn(100000)) + testPlankNamespace = fmt.Sprintf("testsquash-planks-%v", rand.Intn(100000)) By("should create a demo namespace") _, err := cs.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}) Expect(err).NotTo(HaveOccurred()) @@ -71,22 +69,12 @@ var _ = Describe("Single debug mode", func() { // Run delete before testing to ensure there are no lingering artifacts By("should list no resources after delete") - err = testutils.Squashctl("utils delete-attachments") + _, err = testutils.SquashctlOut("utils delete-attachments") Expect(err).NotTo(HaveOccurred()) str, err := testutils.SquashctlOut("utils list-attachments") Expect(err).NotTo(HaveOccurred()) - validateUtilsListDebugAttachments(str, 0) - By("should deploy a demo app") - err = testutils.Squashctl(fmt.Sprintf("deploy demo --demo-id %v --demo-namespace1 %v --demo-namespace2 %v", "go-java", - testNamespace, - testPlankNamespace)) - Expect(err).NotTo(HaveOccurred()) - By("should find the demo deployment") - goPodName, err = waitForPod(cs, testNamespace, "example-service1") - Expect(err).NotTo(HaveOccurred()) - javaPodName, err = waitForPod(cs, testPlankNamespace, "example-service2-java") - Expect(err).NotTo(HaveOccurred()) + validateUtilsListDebugAttachments(str, 0) }) AfterEach(func() { @@ -103,8 +91,9 @@ var _ = Describe("Single debug mode", func() { }) It("Should create a debug session", func() { + goPodName, javaPodName := installSquashBuiltInDemoApps(cs, testNamespace, testPlankNamespace) By("should attach a dlv debugger") - dbgStr, err := testutils.SquashctlOut(testutils.MachineDebugArgs(testConditions, "dlv", testNamespace, goPodName, testPlankNamespace, "")) + dbgStr, err := testutils.SquashctlOut(testutils.MachineDebugArgs(testConditions, "dlv", testNamespace, goPodName, testPlankNamespace, "", "")) Expect(err).NotTo(HaveOccurred()) By("should have created the required permissions") @@ -133,6 +122,7 @@ var _ = Describe("Single debug mode", func() { }) It("Should create a debug session - secure mode", func() { + goPodName, javaPodName := installSquashBuiltInDemoApps(cs, testNamespace, testPlankNamespace) configFile := "secure_mode_test_config.yaml" err := testutils.Squashctl(fmt.Sprintf("deploy squash --squash-namespace %v --container-repo %v --container-version %v --config %v", testPlankNamespace, @@ -148,7 +138,7 @@ var _ = Describe("Single debug mode", func() { Expect(err).NotTo(HaveOccurred()) By("should attach a dlv debugger") - dbgStr, err := testutils.SquashctlOut(testutils.MachineDebugArgs(testConditions, "dlv", testNamespace, goPodName, testPlankNamespace, configFile)) + dbgStr, err := testutils.SquashctlOut(testutils.MachineDebugArgs(testConditions, "dlv", testNamespace, goPodName, testPlankNamespace, configFile, "")) Expect(err).NotTo(HaveOccurred()) validateMachineDebugOutput(dbgStr) @@ -176,6 +166,15 @@ var _ = Describe("Single debug mode", func() { Expect(len(plankNsPods)).To(Equal(2)) Expect(plankNsPods[0].Name).To(Equal(javaPodName)) }) + + It("Should debug specific process - default, single-process case", func() { + multiProcessTest(cs, testNamespace, testPlankNamespace, "", true) + }) + + It("Should debug specific process - multi-process case", func() { + processName := "sample_app" + multiProcessTest(cs, testNamespace, testPlankNamespace, processName, false) + }) }) func waitForPod(cs *kubernetes.Clientset, testNamespace, deploymentName string) (string, error) { @@ -195,6 +194,26 @@ func waitForPod(cs *kubernetes.Clientset, testNamespace, deploymentName string) return "", fmt.Errorf("Pod for deployment %v not found", deploymentName) } +func waitForPodByLabel(cs *kubernetes.Clientset, testNamespace, labelSelector string) (string, error) { + // this can be slow, pulls image for the first time - should store demo images in cache if possible + timeLimit := 100 + timeStepSleepDuration := time.Second + for i := 0; i < timeLimit; i++ { + pods, err := cs.CoreV1().Pods(testNamespace).List(metav1.ListOptions{LabelSelector: labelSelector}) + if err != nil { + return "", err + } + if len(pods.Items) == 1 { + return pods.Items[0].Name, nil + } + if len(pods.Items) > 1 { + return "", fmt.Errorf("label selector %v returned %v matches (one expected)", labelSelector, len(pods.Items)) + } + time.Sleep(timeStepSleepDuration) + } + return "", fmt.Errorf("Pod with label selector %v not found", labelSelector) +} + func findPod(pods *v1.PodList, deploymentName string) (string, bool) { for _, pod := range pods.Items { if pod.Spec.Containers[0].Name == deploymentName && podReady(pod) { @@ -322,7 +341,8 @@ func podsMustInclude(pods []v1.Pod, name string) { foundPod = true } } - ExpectWithOffset(1, foundPod).To(BeTrue()) + By(fmt.Sprintf("looking for pod name: %v", name)) + Expect(foundPod).To(BeTrue()) } // This util does two things, you may only need one of them: @@ -425,3 +445,23 @@ func dumpLogsBackground(ctx context.Context, onFailOnly bool) error { } return nil } + +//func applyManifest(filepath, ns string) { +// _, err := helmchart.RenderManifests(context.Background(), filepath, "", "", ns, "") +// Expect(err).NotTo(HaveOccurred()) +//} + +func installSquashBuiltInDemoApps(cs *kubernetes.Clientset, appNamespace, plankNamespace string) (string, string) { + By("should deploy a demo app") + err := testutils.Squashctl(fmt.Sprintf("deploy demo --demo-id %v --demo-namespace1 %v --demo-namespace2 %v", "go-java", + appNamespace, + plankNamespace)) + Expect(err).NotTo(HaveOccurred()) + + By("should find the demo deployment") + goPodName, err := waitForPod(cs, appNamespace, "example-service1") + Expect(err).NotTo(HaveOccurred()) + javaPodName, err := waitForPod(cs, plankNamespace, "example-service2-java") + Expect(err).NotTo(HaveOccurred()) + return goPodName, javaPodName +} diff --git a/test/testutils/utils.go b/test/testutils/utils.go index 0df5602..aff785e 100644 --- a/test/testutils/utils.go +++ b/test/testutils/utils.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "strings" + "time" "github.com/pkg/errors" @@ -68,20 +69,50 @@ func Squashctl(args string) error { return app.Execute() } func SquashctlOut(args string) (string, error) { + return SquashctlOutWithTimeout(args, nil) +} +func SquashctlOutWithTimeout(args string, timeout *int) (string, error) { + timeLimit := 600 // default to 10 minute timeout + if timeout != nil { + timeLimit = *timeout + } stdOut := os.Stdout r, w, err := os.Pipe() if err != nil { return "", err } os.Stdout = w + // back to normal state + restore := func() { + _ = w.Close() + os.Stdout = stdOut // restoring the real stdout + } + // have to do this in case it exits by timeout in order to preserve the fail handler output + // (another reason to use the corecli lib, it writes to buffers during tests, not stdout, so you don't need to + // risk losing your error messages) + defer restore() app, err := squashctl.App("test") if err != nil { return "", err } app.SetArgs(strings.Split(args, " ")) - err = app.Execute() + errC := make(chan error) + go func() { + errC <- app.Execute() + }() + t := time.NewTimer(time.Duration(timeLimit) * time.Second) + + select { + case exErr := <-errC: + if exErr != nil { + return "", exErr + } + break + case <-t.C: + return "", fmt.Errorf("timeout during squashctl call") + } outC := make(chan string) // copy the output in a separate goroutine so printing can't block indefinitely @@ -91,11 +122,9 @@ func SquashctlOut(args string) (string, error) { outC <- buf.String() }() - // back to normal state - _ = w.Close() - os.Stdout = stdOut // restoring the real stdout - out := <-outC + restore() + out := <-outC return strings.TrimSuffix(out, "\n"), nil } @@ -104,13 +133,14 @@ func Curl(args string) ([]byte, error) { return curl.CombinedOutput() } -func MachineDebugArgs(tc TestConditions, debugger, ns, podName, squashNamespace, configFile string) string { - return fmt.Sprintf(`--debugger %v --machine --namespace %v --pod %v --container-version %v --container-repo %v --squash-namespace %v --config %v`, +func MachineDebugArgs(tc TestConditions, debugger, ns, podName, squashNamespace, configFile, processMatcher string) string { + return fmt.Sprintf(`--debugger %v --machine --namespace %v --pod %v --container-version %v --container-repo %v --squash-namespace %v --config %v --process-match %v`, debugger, ns, podName, tc.PlankImageTag, tc.PlankImageRepo, squashNamespace, - configFile) + configFile, + processMatcher) }