-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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 debug with breakpoint onFailure to TaskRun Spec #3857
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ package main | |
import ( | ||
"io/ioutil" | ||
"os" | ||
"strings" | ||
"testing" | ||
"time" | ||
) | ||
|
@@ -37,7 +38,7 @@ func TestRealWaiterWaitMissingFile(t *testing.T) { | |
rw := realWaiter{} | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
err := rw.setWaitPollingInterval(testWaitPollingInterval).Wait(tmp.Name(), false) | ||
err := rw.setWaitPollingInterval(testWaitPollingInterval).Wait(tmp.Name(), false, false) | ||
if err != nil { | ||
t.Errorf("error waiting on tmp file %q", tmp.Name()) | ||
} | ||
|
@@ -60,7 +61,7 @@ func TestRealWaiterWaitWithFile(t *testing.T) { | |
rw := realWaiter{} | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
err := rw.setWaitPollingInterval(testWaitPollingInterval).Wait(tmp.Name(), false) | ||
err := rw.setWaitPollingInterval(testWaitPollingInterval).Wait(tmp.Name(), false, false) | ||
if err != nil { | ||
t.Errorf("error waiting on tmp file %q", tmp.Name()) | ||
} | ||
|
@@ -83,7 +84,7 @@ func TestRealWaiterWaitMissingContent(t *testing.T) { | |
rw := realWaiter{} | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
err := rw.setWaitPollingInterval(testWaitPollingInterval).Wait(tmp.Name(), true) | ||
err := rw.setWaitPollingInterval(testWaitPollingInterval).Wait(tmp.Name(), true, false) | ||
if err != nil { | ||
t.Errorf("error waiting on tmp file %q", tmp.Name()) | ||
} | ||
|
@@ -106,7 +107,7 @@ func TestRealWaiterWaitWithContent(t *testing.T) { | |
rw := realWaiter{} | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
err := rw.setWaitPollingInterval(testWaitPollingInterval).Wait(tmp.Name(), true) | ||
err := rw.setWaitPollingInterval(testWaitPollingInterval).Wait(tmp.Name(), true, false) | ||
if err != nil { | ||
t.Errorf("error waiting on tmp file %q", tmp.Name()) | ||
} | ||
|
@@ -122,3 +123,58 @@ func TestRealWaiterWaitWithContent(t *testing.T) { | |
t.Errorf("expected Wait() to have detected a non-zero file size by now") | ||
} | ||
} | ||
|
||
func TestRealWaiterWaitWithErrorWaitfile(t *testing.T) { | ||
tmp, err := ioutil.TempFile("", "real_waiter_test_file*.err") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cannot remove this as, while creating the tempfile we are passing |
||
if err != nil { | ||
t.Errorf("error creating temp file: %v", err) | ||
} | ||
tmpFileName := strings.Replace(tmp.Name(), ".err", "", 1) | ||
defer os.Remove(tmp.Name()) | ||
rw := realWaiter{} | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
// error of type skipError is returned after encountering a error waitfile | ||
err := rw.setWaitPollingInterval(testWaitPollingInterval).Wait(tmpFileName, false, false) | ||
if err == nil { | ||
t.Errorf("expected skipError upon encounter error waitfile") | ||
} | ||
switch typ := err.(type) { | ||
case skipError: | ||
close(doneCh) | ||
default: | ||
t.Errorf("unexpected error type %T", typ) | ||
} | ||
}() | ||
select { | ||
case <-doneCh: | ||
// Success | ||
case <-time.After(2 * testWaitPollingInterval): | ||
t.Errorf("expected Wait() to have detected a non-zero file size by now") | ||
} | ||
} | ||
|
||
func TestRealWaiterWaitWithBreakpointOnFailure(t *testing.T) { | ||
tmp, err := ioutil.TempFile("", "real_waiter_test_file*.err") | ||
if err != nil { | ||
t.Errorf("error creating temp file: %v", err) | ||
} | ||
tmpFileName := strings.Replace(tmp.Name(), ".err", "", 1) | ||
defer os.Remove(tmp.Name()) | ||
rw := realWaiter{} | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
// When breakpoint on failure is enabled skipError shouldn't be returned for a error waitfile | ||
err := rw.setWaitPollingInterval(testWaitPollingInterval).Wait(tmpFileName, false, true) | ||
if err != nil { | ||
t.Errorf("error waiting on tmp file %q", tmp.Name()) | ||
} | ||
close(doneCh) | ||
}() | ||
select { | ||
case <-doneCh: | ||
// Success | ||
case <-time.After(2 * testWaitPollingInterval): | ||
t.Errorf("expected Wait() to have detected a non-zero file size by now") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
<!-- | ||
--- | ||
linkTitle: "Debug" | ||
weight: 11 | ||
--- | ||
--> | ||
# Debug | ||
|
||
- [Overview](#overview) | ||
- [Debugging TaskRuns](#debugging-taskruns) | ||
- [Adding Breakpoints](#adding-breakpoints) | ||
- [Breakpoint on Failure](#breakpoint-on-failure) | ||
- [Failure of a Step](#failure-of-a-step) | ||
- [Halting a Step on failure](#halting-a-step-on-failure) | ||
- [Exiting breakpoint](#exiting-breakpoint) | ||
- [Debug Environment](#debug-environment) | ||
- [Mounts](#mounts) | ||
- [Debug Scripts](#debug-scripts) | ||
|
||
|
||
## Overview | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a blocker but it might be worth mentioning here that debug is behind a feature flag. |
||
|
||
`Debug` spec is used for troubleshooting and breakpointing runtime resources. This doc helps understand the inner | ||
workings of debug in Tekton. Currently only the `TaskRun` resource is supported. | ||
|
||
## Debugging TaskRuns | ||
|
||
The following provides explanation on how Debugging TaskRuns is possible through Tekton. To understand how to use | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like it's developer-focused to me. Wonder if it should live under |
||
the debug spec for TaskRuns follow the [TaskRun Debugging Documentation](taskruns.md#debugging-a-taskrun). | ||
|
||
### Breakpoint on Failure | ||
|
||
Halting a TaskRun execution on Failure of a step. | ||
|
||
#### Failure of a Step | ||
|
||
The entrypoint binary is used to manage the lifecycle of a step. Steps are aligned beforehand by the TaskRun controller | ||
allowing each step to run in a particular order. This is done using `-wait_file` and the `-post_file` flags. The former | ||
let's the entrypoint binary know that it has to wait on creation of a particular file before starting execution of the step. | ||
And the latter provides information on the step number and signal the next step on completion of the step. | ||
|
||
On success of a step, the `-post-file` is written as is, signalling the next step which would have the same argument given | ||
for `-wait_file` to resume the entrypoint process and move ahead with the step. | ||
|
||
On failure of a step, the `-post_file` is written with appending `.err` to it denoting that the previous step has failed with | ||
and error. The subsequent steps are skipped in this case as well, marking the TaskRun as a failure. | ||
|
||
#### Halting a Step on failure | ||
|
||
The failed step writes `<step-no>.err` to `/tekton/tools` and stops running completely. To be able to debug a step we would | ||
need it to continue running (not exit), not skip the next steps and signal health of the step. By disabling step skipping, | ||
stopping write of the `<step-no>.err` file and waiting on a signal by the user to disable the halt, we would be simulating a | ||
"breakpoint". | ||
|
||
In this breakpoint, which is essentially a limbo state the TaskRun finds itself in, the user can interact with the step | ||
environment using a CLI or an IDE. | ||
|
||
#### Exiting breakpoint | ||
|
||
To exit a step which has been paused upon failure, the step would wait on a file similar to `<step-no>.breakpointexit` which | ||
would unpause and exit the step container. eg: Step 0 fails and is paused. Writing `0.breakpointexit` in `/tekton/tools` | ||
would unpause and exit the step container. | ||
|
||
## Debug Environment | ||
|
||
Additional environment augmentations made available to the TaskRun Pod to aid in troubleshooting and managing step lifecycle. | ||
|
||
### Mounts | ||
|
||
`/tekton/debug/scripts` : Contains scripts which the user can run to mark the step as a success, failure or exit the breakpoint. | ||
Shared between all the containers. | ||
|
||
`/tekton/debug/info/<n>` : Contains information about the step. Single EmptyDir shared between all step containers, but renamed | ||
to reflect step number. eg: Step 0 will have `/tekton/debug/info/0`, Step 1 will have `/tekton/debug/info/1` etc. | ||
|
||
### Debug Scripts | ||
|
||
`/tekton/debug/scripts/debug-continue` : Mark the step as completed with success by writing to `/tekton/tools`. eg: User wants to exit | ||
breakpoint for failed step 0. Running this script would create `/tekton/tools/0` and `/tekton/tools/0.breakpointexit`. | ||
|
||
`/tekton/debug/scripts/debug-fail-continue` : Mark the step as completed with failure by writing to `/tekton/tools`. eg: User wants to exit | ||
breakpoint for failed step 0. Running this script would create `/tekton/tools/0.err` and `/tekton/tools/0.breakpointexit`. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,13 +23,15 @@ weight: 300 | |
- [Monitoring `Steps`](#monitoring-steps) | ||
- [Monitoring `Results`](#monitoring-results) | ||
- [Cancelling a `TaskRun`](#cancelling-a-taskrun) | ||
- [Debugging a `TaskRun`](#debugging-a-taskrun) | ||
- [Events](events.md#taskruns) | ||
- [Running a TaskRun Hermetically](hermetic.md) | ||
- [Code examples](#code-examples) | ||
- [Example `TaskRun` with a referenced `Task`](#example-taskrun-with-a-referenced-task) | ||
- [Example `TaskRun` with an embedded `Task`](#example-taskrun-with-an-embedded-task) | ||
- [Reusing a `Task`](#reusing-a-task) | ||
- [Using custom `ServiceAccount` credentials](#using-custom-serviceaccount-credentials) | ||
- [Running step containers as a non-root user](#running-step-containers-as-a-non-root-user) | ||
|
||
# Overview | ||
|
||
|
@@ -447,6 +449,43 @@ spec: | |
status: "TaskRunCancelled" | ||
``` | ||
|
||
|
||
### Debugging a `TaskRun` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again probably worth mentioning that this is an alpha feature, with a link to the "Alpha Features" section of |
||
|
||
#### Breakpoint on Failure | ||
|
||
TaskRuns can be halted on failure for troubleshooting by providing the following spec patch as seen below. | ||
|
||
```yaml | ||
spec: | ||
debug: | ||
breakpoint: ["onFailure"] | ||
``` | ||
|
||
Upon failure of a step, the TaskRun Pod execution is halted. If ths TaskRun Pod continues to run without any lifecycle | ||
change done by the user (running the debug-continue or debug-fail-continue script) the TaskRun would be subject to | ||
[TaskRunTimeout](#configuring-the-failure-timeout). | ||
During this time, the user/client can get remote shell access to the step container with a command such as the following. | ||
|
||
```bash | ||
kubectl exec -it print-date-d7tj5-pod-w5qrn -c step-print-date-human-readable | ||
``` | ||
|
||
#### Debug Environment | ||
|
||
After the user/client has access to the container environment, they can scour for any missing parts because of which | ||
their step might have failed. | ||
|
||
To control the lifecycle of the step to mark it as a success or a failure or close the breakpoint, there are scripts | ||
provided in the `/tekton/debug/scripts` directory in the container. The following are the scripts and the tasks they | ||
perform :- | ||
|
||
`debug-continue`: Mark the step as a success and exit the breakpoint. | ||
|
||
`debug-fail-continue`: Mark the step as a failure and exit the breakpoint. | ||
|
||
*More information on the inner workings of debug can be found in the [Debug documentation](debug.md)* | ||
|
||
## Code examples | ||
|
||
To better understand `TaskRuns`, study the following code examples: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we fail to read the exit code, AIUI the code as currently written will
os.Exit(0)
after logging the error (becauseexitCode
defaults to 0). Is that intentional? It might be subtle to readers, and deserve a comment.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is subtle indeed, I added the following comment
Let me know if I should add something else