Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add debugging support for Skaffold: skaffold debug #1702

Merged
merged 66 commits into from
Mar 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
b6a592f
Initial hack at `skaffold debug`:
briandealwis Jan 26, 2019
51827d6
Make kubectl transforms be configurable
briandealwis Jan 31, 2019
01354ab
Disable watching for `skaffold debug`
briandealwis Feb 7, 2019
9e553d1
Merge remote-tracking branch 'origin/master' into HEAD
briandealwis Feb 7, 2019
d548123
Add some infrastructure for guessing artifact type
briandealwis Feb 7, 2019
003a268
Add support for retrieving image configurations at build-time
briandealwis Feb 9, 2019
2909234
Workaround google/go-containerregistry#351
briandealwis Feb 14, 2019
e4d6521
Use auth when fetching image
briandealwis Feb 14, 2019
96c641b
doc improvements
briandealwis Feb 14, 2019
e28df85
separate jvm debugging; go types fixes
briandealwis Feb 14, 2019
292ed1a
rewrite using k8s api objects to avoid typing errors
briandealwis Feb 15, 2019
54225a1
Merge remote-tracking branch 'origin/master' into HEAD
briandealwis Feb 15, 2019
8bb0a8c
Avoid port conflicts
briandealwis Feb 20, 2019
ab72e71
pickup parameters from existing jdwp configuration
briandealwis Feb 22, 2019
a1b4caa
Initial support for NodeJS
briandealwis Feb 26, 2019
143a75c
merge with HEAD
briandealwis Feb 26, 2019
60161ef
Support nodemon too
briandealwis Feb 27, 2019
eca494e
Fix variable scope
briandealwis Feb 27, 2019
a524fa4
address initial review comments
briandealwis Feb 28, 2019
d4a31ee
Refactor debug code to debugging package
briandealwis Mar 1, 2019
0b6c56f
separate skaffold-specific pieces
briandealwis Mar 1, 2019
b984d32
rejig file names
briandealwis Mar 1, 2019
0863254
Move envAsMap to debug and toss unused helper functions
briandealwis Mar 4, 2019
9aaa4c5
Move TestFindArtifact to debug_test
briandealwis Mar 4, 2019
1a83a0a
fixup copyrights
briandealwis Mar 4, 2019
704729f
Add support for other k8s object types
briandealwis Mar 6, 2019
8d6c705
Add support for PodLists too
briandealwis Mar 6, 2019
6f8318d
make linter happy
briandealwis Mar 6, 2019
bead5d0
Merge with HEAD
briandealwis Mar 6, 2019
a258068
gofmt -s
briandealwis Mar 6, 2019
63d6598
Rename CheckEqual -> CheckDeepEqualWithOptions
briandealwis Mar 7, 2019
0aee98f
slight tweak; regen doc
briandealwis Mar 8, 2019
864a839
Merge remote-tracking branch 'origin/master' into HEAD
briandealwis Mar 11, 2019
6f4caa9
Merge with HEAD and fixup
briandealwis Mar 12, 2019
2060611
Get rid of ParseName workaround
briandealwis Mar 12, 2019
3fe99af
Initial hack at docs
briandealwis Mar 12, 2019
a15bc08
Merge remote-tracking branch 'origin/master' into HEAD
briandealwis Mar 12, 2019
0c1f588
move watch-related options back to `dev`
briandealwis Mar 12, 2019
883cf50
made debug go last
briandealwis Mar 14, 2019
ddff55b
Merge remote-tracking branch 'origin/master' into HEAD
briandealwis Mar 14, 2019
0defe2c
why does this keep disappearing
briandealwis Mar 14, 2019
f903b9c
make test happy
briandealwis Mar 14, 2019
21f3e1d
fix up doc
briandealwis Mar 15, 2019
48f1092
simplify debug() by delegating to dev()
briandealwis Mar 15, 2019
77edbc0
Change builder API to return Artifact
briandealwis Mar 16, 2019
8a039c2
more review nits
briandealwis Mar 20, 2019
f9c0fdc
Use testutil.CheckDeepEqual instead of CheckDeepEqualWithOptions
briandealwis Mar 20, 2019
96d2a04
Add e2e tests for ApplyDebuggingTransforms
briandealwis Mar 20, 2019
dec62cf
Merge remote-tracking branch 'origin/master' into HEAD
briandealwis Mar 20, 2019
1cf5434
Fixups
briandealwis Mar 20, 2019
b3655f0
Merge with HEAD; revert goimports on pkg/skaffold/event/proto/skaffol…
briandealwis Mar 21, 2019
ff0e805
update doc for --rpc-http-port
briandealwis Mar 21, 2019
d70cb48
Merge remote-tracking branch 'origin/master' into HEAD
briandealwis Mar 25, 2019
1dfcacb
Pick up doc fix for #1863
briandealwis Mar 25, 2019
48ef8e9
clarify todo
briandealwis Mar 26, 2019
6a92d51
formatting
briandealwis Mar 26, 2019
7fc4a67
move pkg/skaffold/debugging -> pkg/skaffold/debug
briandealwis Mar 26, 2019
2aa9e30
transform{nodejs,jvm} -> transform_{nodejs,jvm}
briandealwis Mar 26, 2019
f7cc3ae
no helm or kustomize
briandealwis Mar 26, 2019
6b8f8cb
inspectSpec doc comment
briandealwis Mar 26, 2019
17d6065
rewrite and simplify allocatePort
briandealwis Mar 26, 2019
9f9338a
extract default debug ports to constants
briandealwis Mar 26, 2019
c727bc9
Try retrieving image configuration from local docker, and then remote…
briandealwis Mar 26, 2019
80ff077
back out unnecessary build API changes
briandealwis Mar 26, 2019
3be4334
Use pkg/skaffold/docker to fetch image configuration
briandealwis Mar 26, 2019
a5e2859
goimports
briandealwis Mar 26, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/skaffold/app/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func NewSkaffoldCommand(out, err io.Writer) *cobra.Command {
rootCmd.AddCommand(NewCmdVersion(out))
rootCmd.AddCommand(NewCmdRun(out))
rootCmd.AddCommand(NewCmdDev(out))
rootCmd.AddCommand(NewCmdDebug(out))
rootCmd.AddCommand(NewCmdBuild(out))
rootCmd.AddCommand(NewCmdDeploy(out))
rootCmd.AddCommand(NewCmdDelete(out))
Expand Down Expand Up @@ -158,6 +159,14 @@ func AddRunDevFlags(cmd *cobra.Command) {
cmd.Flags().StringVarP(&opts.CacheFile, "cache-file", "", "", "Specify the location of the cache file (default $HOME/.skaffold/cache)")
}

func AddDevDebugFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&opts.TailDev, "tail", true, "Stream logs from deployed objects")
cmd.Flags().BoolVar(&opts.Cleanup, "cleanup", true, "Delete deployments after dev mode is interrupted")
briandealwis marked this conversation as resolved.
Show resolved Hide resolved
cmd.Flags().BoolVar(&opts.PortForward, "port-forward", true, "Port-forward exposed container ports within pods")
briandealwis marked this conversation as resolved.
Show resolved Hide resolved
cmd.Flags().StringArrayVarP(&opts.CustomLabels, "label", "l", nil, "Add custom labels to deployed objects. Set multiple times for multiple labels")
briandealwis marked this conversation as resolved.
Show resolved Hide resolved
cmd.Flags().BoolVar(&opts.ExperimentalGUI, "experimental-gui", false, "Experimental Graphical User Interface")
balopat marked this conversation as resolved.
Show resolved Hide resolved
}

func SetUpLogs(out io.Writer, level string) error {
logrus.SetOutput(out)
lvl, err := logrus.ParseLevel(v)
Expand Down
53 changes: 53 additions & 0 deletions cmd/skaffold/app/cmd/debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
Copyright 2019 The Skaffold Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cmd

import (
"io"

debugging "github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy"
"github.com/spf13/cobra"
)

// NewCmdDebug describes the CLI command to run a pipeline in debug mode.
func NewCmdDebug(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "debug",
Short: "Runs a pipeline file in debug mode",
Long: "Similar to `dev`, but configures the pipeline for debugging.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return debug(out)
},
}
AddRunDevFlags(cmd)
AddDevDebugFlags(cmd)
return cmd
}

func debug(out io.Writer) error {
briandealwis marked this conversation as resolved.
Show resolved Hide resolved
// HACK: disable watcher to prevent redeploying changed containers during debugging
// TODO: enable file-sync but avoid redeploys of artifacts being debugged
if len(opts.TargetImages) == 0 {
briandealwis marked this conversation as resolved.
Show resolved Hide resolved
opts.TargetImages = []string{"none"}
}

deploy.AddManifestTransform(debugging.ApplyDebuggingTransforms)

return dev(out, opts.ExperimentalGUI)
}
7 changes: 1 addition & 6 deletions cmd/skaffold/app/cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,10 @@ func NewCmdDev(out io.Writer) *cobra.Command {
},
}
AddRunDevFlags(cmd)
cmd.Flags().BoolVar(&opts.TailDev, "tail", true, "Stream logs from deployed objects")
AddDevDebugFlags(cmd)
cmd.Flags().StringVar(&opts.Trigger, "trigger", "polling", "How are changes detected? (polling, manual or notify)")
cmd.Flags().BoolVar(&opts.Cleanup, "cleanup", true, "Delete deployments after dev mode is interrupted")
cmd.Flags().StringArrayVarP(&opts.TargetImages, "watch-image", "w", nil, "Choose which artifacts to watch. Artifacts with image names that contain the expression will be watched only. Default is to watch sources for all artifacts")
cmd.Flags().IntVarP(&opts.WatchPollInterval, "watch-poll-interval", "i", 1000, "Interval (in ms) between two checks for file changes")
cmd.Flags().BoolVar(&opts.PortForward, "port-forward", true, "Port-forward exposed container ports within pods")
cmd.Flags().StringArrayVarP(&opts.CustomLabels, "label", "l", nil, "Add custom labels to deployed objects. Set multiple times for multiple labels")
cmd.Flags().BoolVar(&opts.ExperimentalGUI, "experimental-gui", false, "Experimental Graphical User Interface")

return cmd
}

Expand Down
1 change: 1 addition & 0 deletions docs/content/en/docs/how-tos/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ weight: 30
| [Port forwarding](/docs/how-tos/portforward) | Port forwarding from pods |
| [Profiles](/docs/how-tos/profiles) | Define configurations for different contexts |
| [Templated fields](/docs/how-tos/templating) | Adjust configuration with environment variables |
| [Debugging (alpha)](/docs/how-tos/debug) | Enabling debugging of apps as deployed to a Kubernetes cluster |
48 changes: 48 additions & 0 deletions docs/content/en/docs/how-tos/debug/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: "Debugging with Skaffold"
linkTitle: "Debugging"
weight: 100
---

This page describes `skaffold debug`, a zero-configuration solution for
setting up containers for debugging on a Kubernetes cluster.

{{< alert title="Note" >}}
This functionality is in an alpha state and may change without warning.
{{< /alert >}}

## Debugging with Skaffold

`skaffold debug` acts like `skaffold dev`, but it configures containers in pods
for debugging as required for each container's runtime technology.
The associated debugging ports are exposed and labelled and port-forwarded to the
local machine. Helper metadata is also added to allow IDEs to detect the debugging
configuration parameters.

## How it works

`skaffold debug` examines the built artifacts to determine the underlying runtime technology
(currently supported: Java and NodeJS). Any Kubernetes manifest that references these
artifacts are transformed to enable the runtime technology's debugging functions:

- a JDWP agent is configured for Java applications,
- the Chrome DevTools inspector is configured for NodeJS applications.

`skaffold debug` uses a set of heuristics to identify the runtime technology.
The Kubernetes manifests are transformed on-the-fly such that the on-disk
representations are untouched.

## Limitations

`skaffold debug` has some limitations:
briandealwis marked this conversation as resolved.
Show resolved Hide resolved

- Only the `kubectl` deployer is supported at the moment: the Helm and Kustomize
deployers are not yet available.
- Only JVM and NodeJS applications are supported:
- JVM applications are configured using the `JAVA_TOOL_OPTIONS` environment variable
which causes extra debugging output on launch.
- NodeJS applications must be launched using `node` or `nodemon`
- File watching is disabled for all artifacts, regardless of whether
the artifact could be configured for debugging.

Support for additional language runtimes will be forthcoming.
51 changes: 51 additions & 0 deletions docs/content/en/docs/references/cli/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,57 @@ Env vars:
* `SKAFFOLD_GLOBAL` (same as `--global`)
* `SKAFFOLD_KUBE_CONTEXT` (same as `--kube-context`)

### skaffold debug

Runs a pipeline file in debug mode

```
Usage:
skaffold debug

Flags:
--cache-artifacts Set to true to enable caching of artifacts.
--cache-file string Specify the location of the cache file (default $HOME/.skaffold/cache)
--cleanup Delete deployments after dev mode is interrupted (default true)
-d, --default-repo string Default repository value (overrides global config)
--enable-rpc skaffold dev Enable gRPC for exposing Skaffold events (true by default for skaffold dev)
--experimental-gui Experimental Graphical User Interface
-f, --filename string Filename or URL to the pipeline file (default "skaffold.yaml")
-l, --label stringArray Add custom labels to deployed objects. Set multiple times for multiple labels
-n, --namespace string Run deployments in the specified namespace
--port-forward Port-forward exposed container ports within pods (default true)
-p, --profile stringArray Activate profiles by name
--rpc-http-port int tcp port to expose event REST API over HTTP (default 50052)
--rpc-port int tcp port to expose event API (default 50051)
--skip-tests Whether to skip the tests after building
--tail Stream logs from deployed objects (default true)
--toot Emit a terminal beep after the deploy is complete

Global Flags:
--color int Specify the default output color in ANSI escape codes (default 34)
-v, --verbosity string Log level (debug, info, warn, error, fatal, panic) (default "warning")


```
Env vars:

* `SKAFFOLD_CACHE_ARTIFACTS` (same as `--cache-artifacts`)
* `SKAFFOLD_CACHE_FILE` (same as `--cache-file`)
* `SKAFFOLD_CLEANUP` (same as `--cleanup`)
* `SKAFFOLD_DEFAULT_REPO` (same as `--default-repo`)
* `SKAFFOLD_ENABLE_RPC` (same as `--enable-rpc`)
* `SKAFFOLD_EXPERIMENTAL_GUI` (same as `--experimental-gui`)
* `SKAFFOLD_FILENAME` (same as `--filename`)
* `SKAFFOLD_LABEL` (same as `--label`)
* `SKAFFOLD_NAMESPACE` (same as `--namespace`)
* `SKAFFOLD_PORT_FORWARD` (same as `--port-forward`)
* `SKAFFOLD_PROFILE` (same as `--profile`)
* `SKAFFOLD_RPC_HTTP_PORT` (same as `--rpc-http-port`)
* `SKAFFOLD_RPC_PORT` (same as `--rpc-port`)
* `SKAFFOLD_SKIP_TESTS` (same as `--skip-tests`)
* `SKAFFOLD_TAIL` (same as `--tail`)
* `SKAFFOLD_TOOT` (same as `--toot`)

### skaffold delete

Delete the deployed resources
Expand Down
132 changes: 132 additions & 0 deletions pkg/skaffold/debug/debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
Copyright 2019 The Skaffold Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package debug

import (
"bufio"
"bytes"
"context"
"strings"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"

"k8s.io/apimachinery/pkg/runtime"
serializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/client-go/kubernetes/scheme"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kubectl"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
)

var (
decodeFromYaml = scheme.Codecs.UniversalDeserializer().Decode
encodeAsYaml = func(o runtime.Object) ([]byte, error) {
s := serializer.NewYAMLSerializer(serializer.DefaultMetaFactory, scheme.Scheme, scheme.Scheme)
var b bytes.Buffer
w := bufio.NewWriter(&b)
if err := s.Encode(o, w); err != nil {
return nil, err
}
w.Flush()
return b.Bytes(), nil
}
)

// ApplyDebuggingTransforms applies language-platform-specific transforms to a list of manifests.
func ApplyDebuggingTransforms(l kubectl.ManifestList, builds []build.Artifact) (kubectl.ManifestList, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

retriever := func(image string) (imageConfiguration, error) {
if artifact := findArtifact(image, builds); artifact != nil {
return retrieveImageConfiguration(ctx, artifact)
}
return imageConfiguration{}, errors.Errorf("no build artifact for [%q]", image)
}
return applyDebuggingTransforms(l, retriever)
}

func applyDebuggingTransforms(l kubectl.ManifestList, retriever configurationRetriever) (kubectl.ManifestList, error) {
var updated kubectl.ManifestList
for _, manifest := range l {
obj, _, err := decodeFromYaml(manifest, nil, nil)
if err != nil {
return nil, errors.Wrap(err, "reading kubernetes YAML")
}

if transformManifest(obj, retriever) {
manifest, err = encodeAsYaml(obj)
if err != nil {
return nil, errors.Wrap(err, "marshalling yaml")
}
if logrus.IsLevelEnabled(logrus.DebugLevel) {
logrus.Debugln("Applied debugging transform:\n", string(manifest))
}
}
updated = append(updated, manifest)
}

return updated, nil
}

// findArtifact finds the corresponding artifact for the given image
func findArtifact(image string, builds []build.Artifact) *build.Artifact {
for _, artifact := range builds {
if image == artifact.ImageName || image == artifact.Tag {
logrus.Debugf("Found artifact for image [%s]", image)
return &artifact
}
}
return nil
}

// retrieveImageConfiguration retrieves the image container configuration for
// the given build artifact
func retrieveImageConfiguration(ctx context.Context, artifact *build.Artifact) (imageConfiguration, error) {
apiClient, err := docker.NewAPIClient()
if err != nil {
return imageConfiguration{}, errors.Wrap(err, "could not connect to local docker daemon")
}

// the apiClient will go to the remote registry if local docker daemon is not available
manifest, err := apiClient.ConfigFile(ctx, artifact.Tag)
if err != nil {
logrus.Debugf("Error retrieving image manifest for %v: %v", artifact.Tag, err)
return imageConfiguration{}, errors.Wrapf(err, "retrieving image config for %q", artifact.Tag)
}

config := manifest.Config
logrus.Debugf("Retrieved local image configuration for %v: %v", artifact.Tag, config)
return imageConfiguration{
env: envAsMap(config.Env),
entrypoint: config.Entrypoint,
arguments: config.Cmd,
labels: config.Labels,
}, nil
}

// envAsMap turns an array of environment "NAME=value" strings into a map
func envAsMap(env []string) map[string]string {
result := make(map[string]string)
for _, pair := range env {
s := strings.SplitN(pair, "=", 2)
result[s[0]] = s[1]
}
return result
}
Loading