-
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
Graceful sidecar support #936
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 |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package main | ||
|
||
import ( | ||
"io/ioutil" | ||
"os" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestRealWaiterWaitMissingFile(t *testing.T) { | ||
// Create a temp file and then immediately delete it to get | ||
// a legitimate tmp path and ensure the file doesnt exist | ||
// prior to testing Wait(). | ||
tmp, err := ioutil.TempFile("", "real_waiter_test_file") | ||
if err != nil { | ||
t.Errorf("error creating temp file: %v", err) | ||
} | ||
os.Remove(tmp.Name()) | ||
rw := RealWaiter{} | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
err := rw.Wait(tmp.Name(), false) | ||
if err != nil { | ||
t.Errorf("error waiting on tmp file %q", tmp.Name()) | ||
} | ||
close(doneCh) | ||
}() | ||
select { | ||
case <-doneCh: | ||
t.Errorf("did not expect Wait() to have detected a file at path %q", tmp.Name()) | ||
case <-time.After(2 * waitPollingInterval): | ||
// Success | ||
} | ||
} | ||
|
||
func TestRealWaiterWaitWithFile(t *testing.T) { | ||
tmp, err := ioutil.TempFile("", "real_waiter_test_file") | ||
if err != nil { | ||
t.Errorf("error creating temp file: %v", err) | ||
} | ||
defer os.Remove(tmp.Name()) | ||
rw := RealWaiter{} | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
err := rw.Wait(tmp.Name(), false) | ||
if err != nil { | ||
t.Errorf("error waiting on tmp file %q", tmp.Name()) | ||
} | ||
close(doneCh) | ||
}() | ||
select { | ||
case <-doneCh: | ||
// Success | ||
case <-time.After(2 * waitPollingInterval): | ||
t.Errorf("expected Wait() to have detected the file's existence by now") | ||
} | ||
} | ||
|
||
func TestRealWaiterWaitMissingContent(t *testing.T) { | ||
tmp, err := ioutil.TempFile("", "real_waiter_test_file") | ||
if err != nil { | ||
t.Errorf("error creating temp file: %v", err) | ||
} | ||
defer os.Remove(tmp.Name()) | ||
rw := RealWaiter{} | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
err := rw.Wait(tmp.Name(), true) | ||
if err != nil { | ||
t.Errorf("error waiting on tmp file %q", tmp.Name()) | ||
} | ||
close(doneCh) | ||
}() | ||
select { | ||
case <-doneCh: | ||
t.Errorf("no data was written to tmp file, did not expect Wait() to have detected a non-zero file size and returned") | ||
case <-time.After(2 * waitPollingInterval): | ||
// Success | ||
} | ||
} | ||
|
||
func TestRealWaiterWaitWithContent(t *testing.T) { | ||
tmp, err := ioutil.TempFile("", "real_waiter_test_file") | ||
if err != nil { | ||
t.Errorf("error creating temp file: %v", err) | ||
} | ||
defer os.Remove(tmp.Name()) | ||
rw := RealWaiter{} | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
err := rw.Wait(tmp.Name(), true) | ||
if err != nil { | ||
t.Errorf("error waiting on tmp file %q", tmp.Name()) | ||
} | ||
close(doneCh) | ||
}() | ||
if err := ioutil.WriteFile(tmp.Name(), []byte("😺"), 0700); err != nil { | ||
t.Errorf("error writing content to temp file: %v", err) | ||
} | ||
select { | ||
case <-doneCh: | ||
// Success | ||
case <-time.After(2 * waitPollingInterval): | ||
t.Errorf("expected Wait() to have detected a non-zero file size by now") | ||
} | ||
} | ||
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. beautiful!! 😭 🤩 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,12 @@ See the License for the specific language governing permissions and | |
limitations under the License. | ||
*/ | ||
|
||
// The nop command is a no-op, it simply prints a message and exits. Nop | ||
// is used to stop sidecar containers in TaskRun Pods. When a Task's Steps | ||
// are complete any sidecars running alongside the Step containers need | ||
// to be terminated. Whatever image the sidecars are running is replaced | ||
// with nop and the sidecar quickly exits. | ||
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. nice! |
||
|
||
package main | ||
|
||
import "fmt" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ A `TaskRun` runs until all `steps` have completed or until a failure occurs. | |
- [Steps](#steps) | ||
- [Cancelling a TaskRun](#cancelling-a-taskrun) | ||
- [Examples](#examples) | ||
- [Sidecars](#sidecars) | ||
- [Logs](logs.md) | ||
|
||
--- | ||
|
@@ -544,6 +545,29 @@ of the `Task` resource object. | |
For examples and more information about specifying service accounts, see the | ||
[`ServiceAccount`](./auth.md) reference topic. | ||
|
||
## Sidecars | ||
|
||
A well-established pattern in Kubernetes is that of the "sidecar" - a | ||
container which runs alongside your workloads to provide ancillary support. | ||
Typical examples of the sidecar pattern are logging daemons, services to | ||
update files on a shared volume, and network proxies. | ||
|
||
Tekton doesn't provide a mechanism to specify sidecars for Task steps | ||
but it's still possible for sidecars to be added to your Pods: | ||
[Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/) | ||
provide cluster admins a mechanism to inject sidecar containers as Pods launch. | ||
As a concrete example this is one possible method [used by Istio](https://istio.io/docs/setup/kubernetes/additional-setup/sidecar-injection/#automatic-sidecar-injection) | ||
to inject an envoy proxy in to pods so that they can be included as part of | ||
Istio's service mesh. | ||
|
||
Tekton will happily work with sidecars injected into a TaskRun's | ||
pods but the behaviour is a bit nuanced: When TaskRun's steps are complete | ||
any sidecar containers running inside the Pod will be terminated. In | ||
order to terminate the sidecars they will be restarted with a new | ||
"nop" image that quickly exits. The result will be that your TaskRun's | ||
Pod will include the sidecar container with a Retry Count of 1 and | ||
with a different container image than you might be expecting. | ||
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. 👍 nice docs! :D |
||
|
||
--- | ||
|
||
Except as otherwise noted, the content of this page is licensed under the | ||
|
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.
I'd love to see some/any unit tests for this if possible 😇
(i think what i'd do is take the bulk of the
Wait
function, put it in a separate function - probably in pkg/entrypoint/entrypointer.go - and test that)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.
I've added unit tests for the
RealWaiter
type. I wasn't entirely sure how best to break this out into theentrypoint
package so I've just added them initially alongsidemain.go
in amain_test.go
. Happy to move them elsewhere but might need a bit more guidance on how best to separate things out.