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

Encode artifact image-name and container WORKDIR in container debug info #3564

Merged
merged 1 commit into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 12 additions & 2 deletions docs/content/en/docs/workflows/debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,21 @@ describing the debug configurations for the pod's containers (linebreaks for rea
```

For example the following annotation indicates that the container named `web` is a Go application
that is being debugged by a headless Delve session on port `56268`:
that is being debugged by a headless Delve session on port `56268` (linebreaks for readability):
```
debug.cloud.google.com/config={"web":{"dlv":56268,"runtime":"go"}}
debug.cloud.google.com/config={
"web":{
"artifactName":"gcr.io/random/image",
"runtime":"go",
"ports":{"dlv":56268},
"workingDir":"/some/path"}}
```

`artifactName` is the corresponding artifact's name in the `skaffold.yaml`.
`runtime` is the language runtime detected.
`ports` is a list of debug ports keyed by the language runtime debugging protocol.
`workingDir` is the working directory (if not an empty string).

Some language runtimes require additional support files to enable debugging.
For these languages, a special set of [runtime-specific images](https://github.com/GoogleContainerTools/container-debug-support)
are configured as _init-containers_ to populate a shared-volume that is mounted into
Expand Down
14 changes: 14 additions & 0 deletions pkg/skaffold/debug/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ var (
}
)

// ContainerDebugConfiguration captures debugging information for a specific container
type ContainerDebugConfiguration struct {
// ArtifactImage is the image reference used in the skaffold.yaml
ArtifactImage string `json:"artifactImage,omitempty"`
// Runtime represents the underlying language runtime (`go`, `jvm`, `nodejs`, `python`)
Runtime string `json:"runtime,omitempty"`
// WorkingDir is the working directory in the image configuration; may be empty
WorkingDir string `json:"workingDir,omitempty"`
// Ports is the list of debugging ports, keyed by protocol type
Ports map[string]uint32 `json:"ports,omitempty"`
}

// ApplyDebuggingTransforms applies language-platform-specific transforms to a list of manifests.
func ApplyDebuggingTransforms(l kubectl.ManifestList, builds []build.Artifact, insecureRegistries map[string]bool) (kubectl.ManifestList, error) {
ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -115,10 +127,12 @@ func retrieveImageConfiguration(ctx context.Context, artifact *build.Artifact, i
config := manifest.Config
logrus.Debugf("Retrieved local image configuration for %v: %v", artifact.Tag, config)
return imageConfiguration{
name: artifact.ImageName,
env: envAsMap(config.Env),
entrypoint: config.Entrypoint,
arguments: config.Cmd,
labels: config.Labels,
workingDir: config.WorkingDir,
}, nil
}

Expand Down
57 changes: 48 additions & 9 deletions pkg/skaffold/debug/debug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package debug

import (
"strings"
"testing"

v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -114,14 +115,14 @@ func (t testTransformer) RuntimeSupportImage() string {
return ""
}

func (t testTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) map[string]interface{} {
func (t testTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) *ContainerDebugConfiguration {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you return a value rather than a pointer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a vestige prior to introducing IsApplicable(). I'll do that as a separate PR if that's ok.

port := portAlloc(9999)
container.Ports = append(container.Ports, v1.ContainerPort{Name: "test", ContainerPort: port})

testEnv := v1.EnvVar{Name: "KEY", Value: "value"}
container.Env = append(container.Env, testEnv)

return map[string]interface{}{"key": "value"}
return &ContainerDebugConfiguration{Runtime: "test"}
}

func TestApplyDebuggingTransforms(t *testing.T) {
Expand Down Expand Up @@ -149,7 +150,7 @@ spec:
kind: Pod
metadata:
annotations:
debug.cloud.google.com/config: '{"example":{"key":"value"}}'
debug.cloud.google.com/config: '{"example":{"runtime":"test"}}'
creationTimestamp: null
name: pod
spec:
Expand Down Expand Up @@ -200,7 +201,7 @@ spec:
template:
metadata:
annotations:
debug.cloud.google.com/config: '{"example":{"key":"value"}}'
debug.cloud.google.com/config: '{"example":{"runtime":"test"}}'
creationTimestamp: null
labels:
app: debug-app
Expand Down Expand Up @@ -252,7 +253,7 @@ spec:
template:
metadata:
annotations:
debug.cloud.google.com/config: '{"example":{"key":"value"}}'
debug.cloud.google.com/config: '{"example":{"runtime":"test"}}'
creationTimestamp: null
labels:
app: debug-app
Expand Down Expand Up @@ -307,7 +308,7 @@ spec:
template:
metadata:
annotations:
debug.cloud.google.com/config: '{"example":{"key":"value"}}'
debug.cloud.google.com/config: '{"example":{"runtime":"test"}}'
creationTimestamp: null
labels:
app: debug-app
Expand Down Expand Up @@ -359,7 +360,7 @@ spec:
template:
metadata:
annotations:
debug.cloud.google.com/config: '{"example":{"key":"value"}}'
debug.cloud.google.com/config: '{"example":{"runtime":"test"}}'
creationTimestamp: null
labels:
app: debug-app
Expand Down Expand Up @@ -414,7 +415,7 @@ spec:
template:
metadata:
annotations:
debug.cloud.google.com/config: '{"example":{"key":"value"}}'
debug.cloud.google.com/config: '{"example":{"runtime":"test"}}'
creationTimestamp: null
labels:
app: debug-app
Expand Down Expand Up @@ -464,7 +465,7 @@ spec:
template:
metadata:
annotations:
debug.cloud.google.com/config: '{"example":{"key":"value"}}'
debug.cloud.google.com/config: '{"example":{"runtime":"test"}}'
creationTimestamp: null
labels:
app: debug-app
Expand Down Expand Up @@ -518,3 +519,41 @@ spec:
})
}
}

func TestWorkingDir(t *testing.T) {
defer func(c []containerTransformer) { containerTransforms = c }(containerTransforms)
containerTransforms = append(containerTransforms, testTransformer{})

pod := &v1.Pod{
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.Version, Kind: "Pod"},
ObjectMeta: metav1.ObjectMeta{Name: "podname"},
Spec: v1.PodSpec{Containers: []v1.Container{{Name: "name1", Image: "image1"}}}}

retriever := func(image string) (imageConfiguration, error) {
return imageConfiguration{workingDir: "/a/dir"}, nil
}

result := transformManifest(pod, retriever)
testutil.CheckDeepEqual(t, true, result)
debugConfig := pod.ObjectMeta.Annotations["debug.cloud.google.com/config"]
testutil.CheckDeepEqual(t, true, strings.Contains(debugConfig, `"workingDir":"/a/dir"`))
}

func TestArtifactImage(t *testing.T) {
defer func(c []containerTransformer) { containerTransforms = c }(containerTransforms)
containerTransforms = append(containerTransforms, testTransformer{})

pod := &v1.Pod{
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.Version, Kind: "Pod"},
ObjectMeta: metav1.ObjectMeta{Name: "podname"},
Spec: v1.PodSpec{Containers: []v1.Container{{Name: "name1", Image: "image1"}}}}

retriever := func(image string) (imageConfiguration, error) {
return imageConfiguration{name: "gcr.io/random/image"}, nil
}

result := transformManifest(pod, retriever)
testutil.CheckDeepEqual(t, true, result)
debugConfig := pod.ObjectMeta.Annotations["debug.cloud.google.com/config"]
testutil.CheckDeepEqual(t, true, strings.Contains(debugConfig, `"artifactImage":"gcr.io/random/image"`))
}
21 changes: 14 additions & 7 deletions pkg/skaffold/debug/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,17 @@ type portAllocator func(int32) int32
// configurationRetriever retrieves an container image configuration
type configurationRetriever func(string) (imageConfiguration, error)

// imageConfiguration captures information from a docker/oci image configuration
// imageConfiguration captures information from a docker/oci image configuration.
// It also includes a "name", usually containing the corresponding artifact `name` from `skaffold.yaml`.
type imageConfiguration struct {
// name is the corresponding artifact's name
name string

labels map[string]string
env map[string]string
entrypoint []string
arguments []string
workingDir string
}

// containerTransformer transforms a container definition
Expand All @@ -87,8 +92,8 @@ type containerTransformer interface {
// RuntimeSupportImage returns the associated duct-tape helper image required or empty string
RuntimeSupportImage() string

// Apply configures a container definition for debugging, returning a simple map describing the debug configuration details or `nil` if it could not be done
Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) map[string]interface{}
// Apply configures a container definition for debugging, returning the debug configuration details or `nil` if it could not be done
Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) *ContainerDebugConfiguration
}

// debuggingSupportVolume is the name of the volume used to hold language runtime debugging support files
Expand Down Expand Up @@ -159,7 +164,7 @@ func transformPodSpec(metadata *metav1.ObjectMeta, podSpec *v1.PodSpec, retrieve
return allocatePort(podSpec, desiredPort)
}
// map of containers -> debugging configuration maps; k8s ensures that a pod's containers are uniquely named
configurations := make(map[string]map[string]interface{})
configurations := make(map[string]ContainerDebugConfiguration)
// the container images that require debugging support files
var containersRequiringSupport []*v1.Container
// the set of image IDs required to provide debugging support files
Expand All @@ -173,7 +178,9 @@ func transformPodSpec(metadata *metav1.ObjectMeta, podSpec *v1.PodSpec, retrieve
}
// requiredImage, if not empty, is the image ID providing the debugging support files
if configuration, requiredImage, err := transformContainer(container, imageConfig, portAlloc); err == nil {
configurations[container.Name] = configuration
configuration.ArtifactImage = imageConfig.name
configuration.WorkingDir = imageConfig.workingDir
configurations[container.Name] = *configuration
if len(requiredImage) > 0 {
logrus.Infof("%q requires debugging support image %q", container.Name, requiredImage)
containersRequiringSupport = append(containersRequiringSupport, container)
Expand Down Expand Up @@ -257,7 +264,7 @@ func isPortAvailable(podSpec *v1.PodSpec, port int32) bool {
// transformContainer rewrites the container definition to enable debugging.
// Returns a debugging configuration description with associated language runtime support
// container image, or an error if the rewrite was unsuccessful.
func transformContainer(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (map[string]interface{}, string, error) {
func transformContainer(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (*ContainerDebugConfiguration, string, error) {
// update image configuration values with those set in the k8s manifest
for _, envVar := range container.Env {
// FIXME handle ValueFrom?
Expand All @@ -282,7 +289,7 @@ func transformContainer(container *v1.Container, config imageConfiguration, port
return nil, "", errors.Errorf("unable to determine runtime for %q", container.Name)
}

func encodeConfigurations(configurations map[string]map[string]interface{}) string {
func encodeConfigurations(configurations map[string]ContainerDebugConfiguration) string {
bytes, err := json.Marshal(configurations)
if err != nil {
return ""
Expand Down
8 changes: 4 additions & 4 deletions pkg/skaffold/debug/transform_go.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (t dlvTransformer) RuntimeSupportImage() string {

// Apply configures a container definition for Go with Delve.
// Returns a simple map describing the debug configuration details.
func (t dlvTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) map[string]interface{} {
func (t dlvTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) *ContainerDebugConfiguration {
logrus.Infof("Configuring %q for Go/Delve debugging", container.Name)

// try to find existing `dlv` command
Expand All @@ -103,9 +103,9 @@ func (t dlvTransformer) Apply(container *v1.Container, config imageConfiguration

container.Ports = exposePort(container.Ports, "dlv", int32(spec.port))

return map[string]interface{}{
"runtime": "go",
"dlv": spec.port,
return &ContainerDebugConfiguration{
Runtime: "go",
Ports: map[string]uint32{"dlv": uint32(spec.port)},
}
}

Expand Down
16 changes: 8 additions & 8 deletions pkg/skaffold/debug/transform_go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ func TestTransformManifestDelve(t *testing.T) {
true,
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"dlv":56268,"runtime":"go"}}`},
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"runtime":"go","ports":{"dlv":56268}}}`},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Expand Down Expand Up @@ -278,7 +278,7 @@ func TestTransformManifestDelve(t *testing.T) {
Replicas: int32p(1),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"dlv":56268,"runtime":"go"}}`},
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"runtime":"go","ports":{"dlv":56268}}}`},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Expand Down Expand Up @@ -321,7 +321,7 @@ func TestTransformManifestDelve(t *testing.T) {
Replicas: int32p(1),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"dlv":56268,"runtime":"go"}}`},
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"runtime":"go","ports":{"dlv":56268}}}`},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Expand Down Expand Up @@ -364,7 +364,7 @@ func TestTransformManifestDelve(t *testing.T) {
Replicas: int32p(1),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"dlv":56268,"runtime":"go"}}`},
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"runtime":"go","ports":{"dlv":56268}}}`},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Expand Down Expand Up @@ -405,7 +405,7 @@ func TestTransformManifestDelve(t *testing.T) {
Spec: appsv1.DaemonSetSpec{
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"dlv":56268,"runtime":"go"}}`},
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"runtime":"go","ports":{"dlv":56268}}}`},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Expand Down Expand Up @@ -446,7 +446,7 @@ func TestTransformManifestDelve(t *testing.T) {
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"dlv":56268,"runtime":"go"}}`},
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"runtime":"go","ports":{"dlv":56268}}}`},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Expand Down Expand Up @@ -489,7 +489,7 @@ func TestTransformManifestDelve(t *testing.T) {
Replicas: int32p(1),
Template: &v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"dlv":56268,"runtime":"go"}}`},
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"runtime":"go","ports":{"dlv":56268}}}`},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Expand Down Expand Up @@ -542,7 +542,7 @@ func TestTransformManifestDelve(t *testing.T) {
}},
{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"dlv":56268,"runtime":"go"}}`},
Annotations: map[string]string{"debug.cloud.google.com/config": `{"test":{"runtime":"go","ports":{"dlv":56268}}}`},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Expand Down
8 changes: 4 additions & 4 deletions pkg/skaffold/debug/transform_jvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (t jdwpTransformer) RuntimeSupportImage() string {

// Apply configures a container definition for JVM debugging.
// Returns a simple map describing the debug configuration details.
func (t jdwpTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) map[string]interface{} {
func (t jdwpTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) *ContainerDebugConfiguration {
logrus.Infof("Configuring %q for JVM debugging", container.Name)
// try to find existing JAVA_TOOL_OPTIONS or jdwp command argument
spec := retrieveJdwpSpec(config)
Expand All @@ -89,9 +89,9 @@ func (t jdwpTransformer) Apply(container *v1.Container, config imageConfiguratio

container.Ports = exposePort(container.Ports, "jdwp", port)

return map[string]interface{}{
"runtime": "jvm",
"jdwp": port,
return &ContainerDebugConfiguration{
Runtime: "jvm",
Ports: map[string]uint32{"jdwp": uint32(port)},
}
}

Expand Down
Loading