Merge pull request #99375 from ehashman/probe-kep-2238

Add Probe-level terminationGracePeriodSeconds
This commit is contained in:
Kubernetes Prow Robot 2021-03-11 23:10:18 -08:00 committed by GitHub
commit faa5c8ccd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 18088 additions and 17260 deletions

View File

@ -8957,7 +8957,7 @@
"type": "string"
},
"terminationGracePeriodSeconds": {
"description": "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.",
"description": "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.",
"format": "int64",
"type": "integer"
},
@ -9249,6 +9249,11 @@
"$ref": "#/definitions/io.k8s.api.core.v1.TCPSocketAction",
"description": "TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported"
},
"terminationGracePeriodSeconds": {
"description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate.",
"format": "int64",
"type": "integer"
},
"timeoutSeconds": {
"description": "Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes",
"format": "int32",

View File

@ -551,6 +551,20 @@ func dropDisabledFields(
})
}
if !utilfeature.DefaultFeatureGate.Enabled(features.ProbeTerminationGracePeriod) && !probeGracePeriodInUse(oldPodSpec) {
// Set pod-level terminationGracePeriodSeconds to nil if the feature is disabled and it is not used
VisitContainers(podSpec, AllContainers, func(c *api.Container, containerType ContainerType) bool {
if c.LivenessProbe != nil {
c.LivenessProbe.TerminationGracePeriodSeconds = nil
}
if c.StartupProbe != nil {
c.StartupProbe.TerminationGracePeriodSeconds = nil
}
// cannot be set for readiness probes
return true
})
}
dropDisabledFSGroupFields(podSpec, oldPodSpec)
if !utilfeature.DefaultFeatureGate.Enabled(features.PodOverhead) && !overheadInUse(oldPodSpec) {
@ -811,6 +825,27 @@ func subpathExprInUse(podSpec *api.PodSpec) bool {
return inUse
}
// probeGracePeriodInUse returns true if the pod spec is non-nil and has a probe that makes use
// of the probe-level terminationGracePeriodSeconds feature
func probeGracePeriodInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {
return false
}
var inUse bool
VisitContainers(podSpec, AllContainers, func(c *api.Container, containerType ContainerType) bool {
// cannot be set for readiness probes
if (c.LivenessProbe != nil && c.LivenessProbe.TerminationGracePeriodSeconds != nil) ||
(c.StartupProbe != nil && c.StartupProbe.TerminationGracePeriodSeconds != nil) {
inUse = true
return false
}
return true
})
return inUse
}
// csiInUse returns true if any pod's spec include inline CSI volumes.
func csiInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {

View File

@ -1140,6 +1140,103 @@ func TestDropSubPathExpr(t *testing.T) {
}
}
func TestDropProbeGracePeriod(t *testing.T) {
gracePeriod := int64(10)
probe := api.Probe{TerminationGracePeriodSeconds: &gracePeriod}
podWithProbeGracePeriod := func() *api.Pod {
return &api.Pod{
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyNever,
Containers: []api.Container{{Name: "container1", Image: "testimage", LivenessProbe: &probe, StartupProbe: &probe}},
},
}
}
podWithoutProbeGracePeriod := func() *api.Pod {
p := podWithProbeGracePeriod()
p.Spec.Containers[0].LivenessProbe.TerminationGracePeriodSeconds = nil
p.Spec.Containers[0].StartupProbe.TerminationGracePeriodSeconds = nil
return p
}
podInfo := []struct {
description string
hasGracePeriod bool
pod func() *api.Pod
}{
{
description: "has probe-level terminationGracePeriod",
hasGracePeriod: true,
pod: podWithProbeGracePeriod,
},
{
description: "does not have probe-level terminationGracePeriod",
hasGracePeriod: false,
pod: podWithoutProbeGracePeriod,
},
{
description: "only has liveness probe-level terminationGracePeriod",
hasGracePeriod: true,
pod: func() *api.Pod {
p := podWithProbeGracePeriod()
p.Spec.Containers[0].StartupProbe.TerminationGracePeriodSeconds = nil
return p
},
},
{
description: "is nil",
hasGracePeriod: false,
pod: func() *api.Pod { return nil },
},
}
enabled := true
for _, oldPodInfo := range podInfo {
for _, newPodInfo := range podInfo {
oldPodHasGracePeriod, oldPod := oldPodInfo.hasGracePeriod, oldPodInfo.pod()
newPodHasGracePeriod, newPod := newPodInfo.hasGracePeriod, newPodInfo.pod()
if newPod == nil {
continue
}
t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) {
var oldPodSpec *api.PodSpec
if oldPod != nil {
oldPodSpec = &oldPod.Spec
}
dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil)
// old pod should never be changed
if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) {
t.Errorf("old pod changed: %v", diff.ObjectReflectDiff(oldPod, oldPodInfo.pod()))
}
switch {
case enabled || oldPodHasGracePeriod:
// new pod should not be changed if the feature is enabled, or if the old pod had subpaths
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
t.Errorf("new pod changed: %v", diff.ObjectReflectDiff(newPod, newPodInfo.pod()))
}
case newPodHasGracePeriod:
// new pod should be changed
if reflect.DeepEqual(newPod, newPodInfo.pod()) {
t.Errorf("new pod was not changed")
}
// new pod should not have subpaths
if !reflect.DeepEqual(newPod, podWithoutProbeGracePeriod()) {
t.Errorf("new pod had probe-level terminationGracePeriod: %v", diff.ObjectReflectDiff(newPod, podWithoutProbeGracePeriod()))
}
default:
// new pod should not need to be changed
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
t.Errorf("new pod changed: %v", diff.ObjectReflectDiff(newPod, newPodInfo.pod()))
}
}
})
}
}
}
// helper creates a podStatus with list of PodIPs
func makePodStatus(podIPs []api.PodIP) *api.PodStatus {
return &api.PodStatus{

View File

@ -2020,6 +2020,17 @@ type Probe struct {
// Minimum consecutive failures for the probe to be considered failed after having succeeded.
// +optional
FailureThreshold int32
// Optional duration in seconds the pod needs to terminate gracefully upon probe failure.
// The grace period is the duration in seconds after the processes running in the pod are sent
// a termination signal and the time when the processes are forcibly halted with a kill signal.
// Set this value longer than the expected cleanup time for your process.
// If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this
// value overrides the value provided by the pod spec.
// Value must be non-negative integer. The value zero indicates stop immediately via
// the kill signal (no opportunity to shut down).
// This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate.
// +optional
TerminationGracePeriodSeconds *int64
}
// PullPolicy describes a policy for if/when to pull a container image
@ -2719,7 +2730,8 @@ type PodSpec struct {
// +optional
RestartPolicy RestartPolicy
// Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request.
// Value must be non-negative integer. The value zero indicates delete immediately.
// Value must be non-negative integer. The value zero indicates stop immediately via the kill
// signal (no opportunity to shut down).
// If this value is nil, the default grace period will be used instead.
// The grace period is the duration in seconds after the processes running in the pod are sent
// a termination signal and the time when the processes are forcibly halted with a kill signal.

View File

@ -6467,6 +6467,7 @@ func autoConvert_v1_Probe_To_core_Probe(in *v1.Probe, out *core.Probe, s convers
out.PeriodSeconds = in.PeriodSeconds
out.SuccessThreshold = in.SuccessThreshold
out.FailureThreshold = in.FailureThreshold
out.TerminationGracePeriodSeconds = (*int64)(unsafe.Pointer(in.TerminationGracePeriodSeconds))
return nil
}
@ -6484,6 +6485,7 @@ func autoConvert_core_Probe_To_v1_Probe(in *core.Probe, out *v1.Probe, s convers
out.PeriodSeconds = in.PeriodSeconds
out.SuccessThreshold = in.SuccessThreshold
out.FailureThreshold = in.FailureThreshold
out.TerminationGracePeriodSeconds = (*int64)(unsafe.Pointer(in.TerminationGracePeriodSeconds))
return nil
}

View File

@ -2859,6 +2859,11 @@ func validateContainers(containers []core.Container, isInitContainers bool, volu
allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, idxPath.Child("lifecycle"))...)
}
allErrs = append(allErrs, validateProbe(ctr.LivenessProbe, idxPath.Child("livenessProbe"))...)
// Readiness-specific validation
if ctr.ReadinessProbe != nil && ctr.ReadinessProbe.TerminationGracePeriodSeconds != nil {
allErrs = append(allErrs, field.Invalid(idxPath.Child("readinessProbe", "terminationGracePeriodSeconds"), ctr.ReadinessProbe.TerminationGracePeriodSeconds, "must not be set for readinessProbes"))
}
allErrs = append(allErrs, validateProbe(ctr.StartupProbe, idxPath.Child("startupProbe"))...)
// Liveness-specific validation
if ctr.LivenessProbe != nil && ctr.LivenessProbe.SuccessThreshold != 1 {
allErrs = append(allErrs, field.Invalid(idxPath.Child("livenessProbe", "successThreshold"), ctr.LivenessProbe.SuccessThreshold, "must be 1"))

View File

@ -6284,6 +6284,20 @@ func TestValidateContainers(t *testing.T) {
TerminationMessagePolicy: "File",
},
},
"invalid readiness probe, terminationGracePeriodSeconds set.": {
{
Name: "life-123",
Image: "image",
ReadinessProbe: &core.Probe{
Handler: core.Handler{
TCPSocket: &core.TCPSocketAction{},
},
TerminationGracePeriodSeconds: utilpointer.Int64Ptr(10),
},
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"invalid liveness probe, no tcp socket port.": {
{
Name: "life-123",

View File

@ -4192,6 +4192,11 @@ func (in *PreferredSchedulingTerm) DeepCopy() *PreferredSchedulingTerm {
func (in *Probe) DeepCopyInto(out *Probe) {
*out = *in
in.Handler.DeepCopyInto(&out.Handler)
if in.TerminationGracePeriodSeconds != nil {
in, out := &in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds
*out = new(int64)
**out = **in
}
return
}

View File

@ -696,6 +696,12 @@ const (
// Enables topology aware hints for EndpointSlices
TopologyAwareHints featuregate.Feature = "TopologyAwareHints"
// owner: @ehashman
// alpha: v1.21
//
// Allows user to override pod-level terminationGracePeriod for probes
ProbeTerminationGracePeriod featuregate.Feature = "ProbeTerminationGracePeriod"
// owner: @ahg-g
// alpha: v1.21
//
@ -850,6 +856,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
MixedProtocolLBService: {Default: false, PreRelease: featuregate.Alpha},
VolumeCapacityPriority: {Default: false, PreRelease: featuregate.Alpha},
PreferNominatedNode: {Default: false, PreRelease: featuregate.Alpha},
ProbeTerminationGracePeriod: {Default: false, PreRelease: featuregate.Alpha},
RunAsGroup: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
PodDeletionCost: {Default: false, PreRelease: featuregate.Alpha},
TopologyAwareHints: {Default: false, PreRelease: featuregate.Alpha},

View File

@ -227,7 +227,7 @@ func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandb
msg, handlerErr := m.runner.Run(kubeContainerID, pod, container, container.Lifecycle.PostStart)
if handlerErr != nil {
m.recordContainerEvent(pod, container, kubeContainerID.ID, v1.EventTypeWarning, events.FailedPostStartHook, msg)
if err := m.killContainer(pod, kubeContainerID, container.Name, "FailedPostStartHook", nil); err != nil {
if err := m.killContainer(pod, kubeContainerID, container.Name, "FailedPostStartHook", reasonFailedPostStartHook, nil); err != nil {
klog.ErrorS(fmt.Errorf("%s: %v", ErrPostStartHook, handlerErr), "Failed to kill container", "pod", klog.KObj(pod),
"podUID", pod.UID, "containerName", container.Name, "containerID", kubeContainerID.String())
}
@ -596,7 +596,7 @@ func (m *kubeGenericRuntimeManager) restoreSpecsFromContainerLabels(containerID
// killContainer kills a container through the following steps:
// * Run the pre-stop lifecycle hooks (if applicable).
// * Stop the container.
func (m *kubeGenericRuntimeManager) killContainer(pod *v1.Pod, containerID kubecontainer.ContainerID, containerName string, message string, gracePeriodOverride *int64) error {
func (m *kubeGenericRuntimeManager) killContainer(pod *v1.Pod, containerID kubecontainer.ContainerID, containerName string, message string, reason containerKillReason, gracePeriodOverride *int64) error {
var containerSpec *v1.Container
if pod != nil {
if containerSpec = kubecontainer.GetContainerSpec(pod, containerName); containerSpec == nil {
@ -619,6 +619,19 @@ func (m *kubeGenericRuntimeManager) killContainer(pod *v1.Pod, containerID kubec
gracePeriod = *pod.DeletionGracePeriodSeconds
case pod.Spec.TerminationGracePeriodSeconds != nil:
gracePeriod = *pod.Spec.TerminationGracePeriodSeconds
if utilfeature.DefaultFeatureGate.Enabled(features.ProbeTerminationGracePeriod) {
switch reason {
case reasonStartupProbe:
if containerSpec.StartupProbe != nil && containerSpec.StartupProbe.TerminationGracePeriodSeconds != nil {
gracePeriod = *containerSpec.StartupProbe.TerminationGracePeriodSeconds
}
case reasonLivenessProbe:
if containerSpec.LivenessProbe != nil && containerSpec.LivenessProbe.TerminationGracePeriodSeconds != nil {
gracePeriod = *containerSpec.LivenessProbe.TerminationGracePeriodSeconds
}
}
}
}
if len(message) == 0 {
@ -672,7 +685,7 @@ func (m *kubeGenericRuntimeManager) killContainersWithSyncResult(pod *v1.Pod, ru
defer wg.Done()
killContainerResult := kubecontainer.NewSyncResult(kubecontainer.KillContainer, container.Name)
if err := m.killContainer(pod, container.ID, container.Name, "", gracePeriodOverride); err != nil {
if err := m.killContainer(pod, container.ID, container.Name, "", reasonUnknown, gracePeriodOverride); err != nil {
killContainerResult.Fail(kubecontainer.ErrKillContainer, err.Error())
klog.ErrorS(err, "Kill container failed", "pod", klog.KObj(pod), "podUID", pod.UID,
"containerName", container.Name, "containerID", container.ID)

View File

@ -120,7 +120,7 @@ func TestKillContainer(t *testing.T) {
}
for _, test := range tests {
err := m.killContainer(test.pod, test.containerID, test.containerName, test.reason, &test.gracePeriodOverride)
err := m.killContainer(test.pod, test.containerID, test.containerName, test.reason, "", &test.gracePeriodOverride)
if test.succeed != (err == nil) {
t.Errorf("%s: expected %v, got %v (%v)", test.caseName, test.succeed, (err == nil), err)
}
@ -290,7 +290,7 @@ func TestLifeCycleHook(t *testing.T) {
// Configured and works as expected
t.Run("PreStop-CMDExec", func(t *testing.T) {
testPod.Spec.Containers[0].Lifecycle = cmdLifeCycle
m.killContainer(testPod, cID, "foo", "testKill", &gracePeriod)
m.killContainer(testPod, cID, "foo", "testKill", "", &gracePeriod)
if fakeRunner.Cmd[0] != cmdLifeCycle.PreStop.Exec.Command[0] {
t.Errorf("CMD Prestop hook was not invoked")
}
@ -300,7 +300,7 @@ func TestLifeCycleHook(t *testing.T) {
t.Run("PreStop-HTTPGet", func(t *testing.T) {
defer func() { fakeHTTP.url = "" }()
testPod.Spec.Containers[0].Lifecycle = httpLifeCycle
m.killContainer(testPod, cID, "foo", "testKill", &gracePeriod)
m.killContainer(testPod, cID, "foo", "testKill", "", &gracePeriod)
if !strings.Contains(fakeHTTP.url, httpLifeCycle.PreStop.HTTPGet.Host) {
t.Errorf("HTTP Prestop hook was not invoked")
@ -314,7 +314,7 @@ func TestLifeCycleHook(t *testing.T) {
testPod.DeletionGracePeriodSeconds = &gracePeriodLocal
testPod.Spec.TerminationGracePeriodSeconds = &gracePeriodLocal
m.killContainer(testPod, cID, "foo", "testKill", &gracePeriodLocal)
m.killContainer(testPod, cID, "foo", "testKill", "", &gracePeriodLocal)
if strings.Contains(fakeHTTP.url, httpLifeCycle.PreStop.HTTPGet.Host) {
t.Errorf("HTTP Should not execute when gracePeriod is 0")

View File

@ -137,7 +137,7 @@ func (cgc *containerGC) removeOldestN(containers []containerGCInfo, toRemove int
ID: containers[i].id,
}
message := "Container is in unknown state, try killing it before removal"
if err := cgc.manager.killContainer(nil, id, containers[i].name, message, nil); err != nil {
if err := cgc.manager.killContainer(nil, id, containers[i].name, message, reasonUnknown, nil); err != nil {
klog.Errorf("Failed to stop container %q: %v", containers[i].id, err)
continue
}

View File

@ -403,6 +403,16 @@ func (m *kubeGenericRuntimeManager) GetPods(all bool) ([]*kubecontainer.Pod, err
return result, nil
}
// containerKillReason explains what killed a given container
type containerKillReason string
const (
reasonStartupProbe containerKillReason = "StartupProbe"
reasonLivenessProbe containerKillReason = "LivenessProbe"
reasonFailedPostStartHook containerKillReason = "FailedPostStartHook"
reasonUnknown containerKillReason = "Unknown"
)
// containerToKillInfo contains necessary information to kill a container.
type containerToKillInfo struct {
// The spec of the container.
@ -411,6 +421,9 @@ type containerToKillInfo struct {
name string
// The message indicates why the container will be killed.
message string
// The reason is a clearer source of info on why a container will be killed
// TODO: replace message with reason?
reason containerKillReason
}
// podActions keeps information what to do for a pod.
@ -582,6 +595,7 @@ func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *ku
container: next,
message: fmt.Sprintf("Init container is in %q state, try killing it before restart",
initLastStatus.State),
reason: reasonUnknown,
}
}
changes.NextInitContainerToStart = next
@ -623,6 +637,7 @@ func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *ku
container: &pod.Spec.Containers[idx],
message: fmt.Sprintf("Container is in %q state, try killing it before restart",
containerStatus.State),
reason: reasonUnknown,
}
}
}
@ -630,6 +645,7 @@ func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *ku
}
// The container is running, but kill the container if any of the following condition is met.
var message string
var reason containerKillReason
restart := shouldRestartOnFailure(pod)
if _, _, changed := containerChanged(&container, containerStatus); changed {
message = fmt.Sprintf("Container %s definition changed", container.Name)
@ -639,9 +655,11 @@ func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *ku
} else if liveness, found := m.livenessManager.Get(containerStatus.ID); found && liveness == proberesults.Failure {
// If the container failed the liveness probe, we should kill it.
message = fmt.Sprintf("Container %s failed liveness probe", container.Name)
reason = reasonLivenessProbe
} else if startup, found := m.startupManager.Get(containerStatus.ID); found && startup == proberesults.Failure {
// If the container failed the startup probe, we should kill it.
message = fmt.Sprintf("Container %s failed startup probe", container.Name)
reason = reasonStartupProbe
} else {
// Keep the container.
keepCount++
@ -660,6 +678,7 @@ func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *ku
name: containerStatus.Name,
container: &pod.Spec.Containers[idx],
message: message,
reason: reason,
}
klog.V(2).InfoS("Message for Container of pod", "containerName", container.Name, "containerStatusID", containerStatus.ID, "pod", klog.KObj(pod), "containerMessage", message)
}
@ -720,7 +739,7 @@ func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, podStatus *kubecontaine
klog.V(3).InfoS("Killing unwanted container for pod", "containerName", containerInfo.name, "containerID", containerID, "pod", klog.KObj(pod))
killContainerResult := kubecontainer.NewSyncResult(kubecontainer.KillContainer, containerInfo.name)
result.AddSyncResult(killContainerResult)
if err := m.killContainer(pod, containerID, containerInfo.name, containerInfo.message, nil); err != nil {
if err := m.killContainer(pod, containerID, containerInfo.name, containerInfo.message, containerInfo.reason, nil); err != nil {
killContainerResult.Fail(kubecontainer.ErrKillContainer, err.Error())
klog.ErrorS(err, "killContainer for pod failed", "containerName", containerInfo.name, "containerID", containerID, "pod", klog.KObj(pod))
return

View File

@ -1002,9 +1002,10 @@ func getKillMapWithInitContainers(pod *v1.Pod, status *kubecontainer.PodStatus,
func verifyActions(t *testing.T, expected, actual *podActions, desc string) {
if actual.ContainersToKill != nil {
// Clear the message field since we don't need to verify the message.
// Clear the message and reason fields since we don't need to verify them.
for k, info := range actual.ContainersToKill {
info.message = ""
info.reason = ""
actual.ContainersToKill[k] = info
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3421,7 +3421,8 @@ message PodSpec {
optional string restartPolicy = 3;
// Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request.
// Value must be non-negative integer. The value zero indicates delete immediately.
// Value must be non-negative integer. The value zero indicates stop immediately via
// the kill signal (no opportunity to shut down).
// If this value is nil, the default grace period will be used instead.
// The grace period is the duration in seconds after the processes running in the pod are sent
// a termination signal and the time when the processes are forcibly halted with a kill signal.
@ -3883,6 +3884,18 @@ message Probe {
// Defaults to 3. Minimum value is 1.
// +optional
optional int32 failureThreshold = 6;
// Optional duration in seconds the pod needs to terminate gracefully upon probe failure.
// The grace period is the duration in seconds after the processes running in the pod are sent
// a termination signal and the time when the processes are forcibly halted with a kill signal.
// Set this value longer than the expected cleanup time for your process.
// If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this
// value overrides the value provided by the pod spec.
// Value must be non-negative integer. The value zero indicates stop immediately via
// the kill signal (no opportunity to shut down).
// This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate.
// +optional
optional int64 terminationGracePeriodSeconds = 7;
}
// Represents a projected volume source

View File

@ -2117,6 +2117,17 @@ type Probe struct {
// Defaults to 3. Minimum value is 1.
// +optional
FailureThreshold int32 `json:"failureThreshold,omitempty" protobuf:"varint,6,opt,name=failureThreshold"`
// Optional duration in seconds the pod needs to terminate gracefully upon probe failure.
// The grace period is the duration in seconds after the processes running in the pod are sent
// a termination signal and the time when the processes are forcibly halted with a kill signal.
// Set this value longer than the expected cleanup time for your process.
// If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this
// value overrides the value provided by the pod spec.
// Value must be non-negative integer. The value zero indicates stop immediately via
// the kill signal (no opportunity to shut down).
// This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate.
// +optional
TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty" protobuf:"varint,7,opt,name=terminationGracePeriodSeconds"`
}
// PullPolicy describes a policy for if/when to pull a container image
@ -2968,7 +2979,8 @@ type PodSpec struct {
// +optional
RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" protobuf:"bytes,3,opt,name=restartPolicy,casttype=RestartPolicy"`
// Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request.
// Value must be non-negative integer. The value zero indicates delete immediately.
// Value must be non-negative integer. The value zero indicates stop immediately via
// the kill signal (no opportunity to shut down).
// If this value is nil, the default grace period will be used instead.
// The grace period is the duration in seconds after the processes running in the pod are sent
// a termination signal and the time when the processes are forcibly halted with a kill signal.

View File

@ -1627,7 +1627,7 @@ var map_PodSpec = map[string]string{
"containers": "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.",
"ephemeralContainers": "List of ephemeral containers run in this pod. Ephemeral containers may be run in an existing pod to perform user-initiated actions such as debugging. This list cannot be specified when creating a pod, and it cannot be modified by updating the pod spec. In order to add an ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource. This field is alpha-level and is only honored by servers that enable the EphemeralContainers feature.",
"restartPolicy": "Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy",
"terminationGracePeriodSeconds": "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.",
"terminationGracePeriodSeconds": "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.",
"activeDeadlineSeconds": "Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.",
"dnsPolicy": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'.",
"nodeSelector": "NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/",
@ -1777,12 +1777,13 @@ func (PreferredSchedulingTerm) SwaggerDoc() map[string]string {
}
var map_Probe = map[string]string{
"": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.",
"initialDelaySeconds": "Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes",
"timeoutSeconds": "Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes",
"periodSeconds": "How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.",
"successThreshold": "Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1.",
"failureThreshold": "Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1.",
"": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.",
"initialDelaySeconds": "Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes",
"timeoutSeconds": "Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes",
"periodSeconds": "How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.",
"successThreshold": "Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1.",
"failureThreshold": "Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1.",
"terminationGracePeriodSeconds": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate.",
}
func (Probe) SwaggerDoc() map[string]string {

View File

@ -4190,6 +4190,11 @@ func (in *PreferredSchedulingTerm) DeepCopy() *PreferredSchedulingTerm {
func (in *Probe) DeepCopyInto(out *Probe) {
*out = *in
in.Handler.DeepCopyInto(&out.Handler)
if in.TerminationGracePeriodSeconds != nil {
in, out := &in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds
*out = new(int64)
**out = **in
}
return
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -148,7 +148,8 @@
"timeoutSeconds": 1563658126,
"periodSeconds": -1771047449,
"successThreshold": -1280107919,
"failureThreshold": -54954325
"failureThreshold": -54954325,
"terminationGracePeriodSeconds": 8559948711650432497
},
"readinessProbe": {
"exec": {
@ -158,138 +159,141 @@
},
"httpGet": {
"path": "53",
"port": "54",
"host": "55",
"scheme": "OŖ樅尷",
"port": -1395989138,
"host": "54",
"scheme": "斎AO6ĴC浔Ű壝ž",
"httpHeaders": [
{
"name": "56",
"value": "57"
"name": "55",
"value": "56"
}
]
},
"tcpSocket": {
"port": 2136826132,
"host": "58"
"port": 180803110,
"host": "57"
},
"initialDelaySeconds": 819364842,
"timeoutSeconds": 933484239,
"periodSeconds": -983896210,
"successThreshold": 552512122,
"failureThreshold": -833209928
"initialDelaySeconds": -2014231015,
"timeoutSeconds": 1488277679,
"periodSeconds": -1679907303,
"successThreshold": -1051545416,
"failureThreshold": 1305372099,
"terminationGracePeriodSeconds": -1220632347188845753
},
"startupProbe": {
"exec": {
"command": [
"59"
"58"
]
},
"httpGet": {
"path": "60",
"port": 180803110,
"host": "61",
"scheme": "ņ錕?øēƺ",
"path": "59",
"port": 1229400382,
"host": "60",
"scheme": "3ƆìQ喞艋涽託仭",
"httpHeaders": [
{
"name": "62",
"value": "63"
"name": "61",
"value": "62"
}
]
},
"tcpSocket": {
"port": "64",
"host": "65"
"port": "63",
"host": "64"
},
"initialDelaySeconds": -816398166,
"timeoutSeconds": 1229400382,
"periodSeconds": -1583208879,
"successThreshold": 1088264954,
"failureThreshold": 13573196
"initialDelaySeconds": 2076966617,
"timeoutSeconds": 202362764,
"periodSeconds": -560446848,
"successThreshold": -1098992377,
"failureThreshold": -1009864962,
"terminationGracePeriodSeconds": 2618170937706035036
},
"lifecycle": {
"postStart": {
"exec": {
"command": [
"66"
"65"
]
},
"httpGet": {
"path": "67",
"port": -1293912096,
"host": "68",
"scheme": "託仭",
"path": "66",
"port": -503563033,
"host": "67",
"scheme": "趕ã/鈱$-议}ȧ外ĺ稥氹Ç|¶鎚¡ ",
"httpHeaders": [
{
"name": "69",
"value": "70"
"name": "68",
"value": "69"
}
]
},
"tcpSocket": {
"port": "71",
"host": "72"
"port": "70",
"host": "71"
}
},
"preStop": {
"exec": {
"command": [
"73"
"72"
]
},
"httpGet": {
"path": "74",
"port": "75",
"host": "76",
"scheme": "鴜Ł%Ũ",
"path": "73",
"port": 991085362,
"host": "74",
"scheme": "磩窮秳ķ蟒苾h^樅燴壩卄",
"httpHeaders": [
{
"name": "77",
"value": "78"
"name": "75",
"value": "76"
}
]
},
"tcpSocket": {
"port": "79",
"host": "80"
"port": -479087071,
"host": "77"
}
}
},
"terminationMessagePath": "81",
"terminationMessagePolicy": "Ņ£",
"terminationMessagePath": "78",
"terminationMessagePolicy": "?讦ĭÐ",
"imagePullPolicy": "/C龷ȪÆl殛瓷雼浢Ü礽绅",
"securityContext": {
"capabilities": {
"add": [
"2Ō¾\\ĒP鄸靇杧ž"
"\"ŵw^Ü郀叚Fi皬择,Q"
],
"drop": [
"娲瘹ɭȊɚɎ(dɅ囥糷磩窮秳ķ蟒苾h^"
"ȸ{+"
]
},
"privileged": false,
"seLinuxOptions": {
"user": "82",
"role": "83",
"type": "84",
"level": "85"
"user": "79",
"role": "80",
"type": "81",
"level": "82"
},
"windowsOptions": {
"gmsaCredentialSpecName": "86",
"gmsaCredentialSpec": "87",
"runAsUserName": "88"
"gmsaCredentialSpecName": "83",
"gmsaCredentialSpec": "84",
"runAsUserName": "85"
},
"runAsUser": 4491726672505793472,
"runAsGroup": -5441351197948631872,
"runAsNonRoot": true,
"runAsUser": -1466062763730980131,
"runAsGroup": 8360795821384820753,
"runAsNonRoot": false,
"readOnlyRootFilesystem": true,
"allowPrivilegeEscalation": true,
"procMount": "ĭÐl恕ɍȇ廄裭4懙",
"procMount": "Ƙq/",
"seccompProfile": {
"type": "嵒ƫS捕ɷD¡轫n",
"localhostProfile": "89"
"type": " u衲\u003c¿燥",
"localhostProfile": "86"
}
},
"stdin": true,
"targetContainerName": "90"
"tty": true,
"targetContainerName": "87"
}
]
}

View File

@ -32,37 +32,38 @@ ephemeralContainers:
name: "28"
optional: false
image: "20"
imagePullPolicy: /C龷ȪÆl殛瓷雼浢Ü礽绅
lifecycle:
postStart:
exec:
command:
- "66"
- "65"
httpGet:
host: "68"
host: "67"
httpHeaders:
- name: "69"
value: "70"
path: "67"
port: -1293912096
scheme: 託仭
- name: "68"
value: "69"
path: "66"
port: -503563033
scheme: '趕ã/鈱$-议}ȧ外ĺ稥氹Ç|¶鎚¡ '
tcpSocket:
host: "72"
port: "71"
host: "71"
port: "70"
preStop:
exec:
command:
- "73"
- "72"
httpGet:
host: "76"
host: "74"
httpHeaders:
- name: "77"
value: "78"
path: "74"
port: "75"
scheme: 鴜Ł%Ũ
- name: "75"
value: "76"
path: "73"
port: 991085362
scheme: 磩窮秳ķ蟒苾h^樅燴壩卄
tcpSocket:
host: "80"
port: "79"
host: "77"
port: -479087071
livenessProbe:
exec:
command:
@ -82,6 +83,7 @@ ephemeralContainers:
tcpSocket:
host: "51"
port: 1366345526
terminationGracePeriodSeconds: 8559948711650432497
timeoutSeconds: 1563658126
name: "19"
ports:
@ -93,22 +95,23 @@ ephemeralContainers:
exec:
command:
- "52"
failureThreshold: -833209928
failureThreshold: 1305372099
httpGet:
host: "55"
host: "54"
httpHeaders:
- name: "56"
value: "57"
- name: "55"
value: "56"
path: "53"
port: "54"
scheme: OŖ樅尷
initialDelaySeconds: 819364842
periodSeconds: -983896210
successThreshold: 552512122
port: -1395989138
scheme: 斎AO6ĴC浔Ű壝ž
initialDelaySeconds: -2014231015
periodSeconds: -1679907303
successThreshold: -1051545416
tcpSocket:
host: "58"
port: 2136826132
timeoutSeconds: 933484239
host: "57"
port: 180803110
terminationGracePeriodSeconds: -1220632347188845753
timeoutSeconds: 1488277679
resources:
limits:
V夸eɑ: "420"
@ -118,51 +121,52 @@ ephemeralContainers:
allowPrivilegeEscalation: true
capabilities:
add:
- 2Ō¾\ĒP鄸靇杧ž
- '"ŵw^Ü郀叚Fi皬择,Q'
drop:
- 娲瘹ɭȊɚɎ(dɅ囥糷磩窮秳ķ蟒苾h^
- ȸ{+
privileged: false
procMount: ĭÐl恕ɍȇ廄裭4懙
procMount: Ƙq/
readOnlyRootFilesystem: true
runAsGroup: -5441351197948631872
runAsNonRoot: true
runAsUser: 4491726672505793472
runAsGroup: 8360795821384820753
runAsNonRoot: false
runAsUser: -1466062763730980131
seLinuxOptions:
level: "85"
role: "83"
type: "84"
user: "82"
level: "82"
role: "80"
type: "81"
user: "79"
seccompProfile:
localhostProfile: "89"
type: 嵒ƫS捕ɷD¡轫n
localhostProfile: "86"
type: ' u衲<¿燥'
windowsOptions:
gmsaCredentialSpec: "87"
gmsaCredentialSpecName: "86"
runAsUserName: "88"
gmsaCredentialSpec: "84"
gmsaCredentialSpecName: "83"
runAsUserName: "85"
startupProbe:
exec:
command:
- "59"
failureThreshold: 13573196
- "58"
failureThreshold: -1009864962
httpGet:
host: "61"
host: "60"
httpHeaders:
- name: "62"
value: "63"
path: "60"
port: 180803110
scheme: ņ錕?øēƺ
initialDelaySeconds: -816398166
periodSeconds: -1583208879
successThreshold: 1088264954
- name: "61"
value: "62"
path: "59"
port: 1229400382
scheme: 3ƆìQ喞艋涽託仭
initialDelaySeconds: 2076966617
periodSeconds: -560446848
successThreshold: -1098992377
tcpSocket:
host: "65"
port: "64"
timeoutSeconds: 1229400382
stdin: true
targetContainerName: "90"
terminationMessagePath: "81"
terminationMessagePolicy: ң
host: "64"
port: "63"
terminationGracePeriodSeconds: 2618170937706035036
timeoutSeconds: 202362764
targetContainerName: "87"
terminationMessagePath: "78"
terminationMessagePolicy: ?讦ĭÐ
tty: true
volumeDevices:
- devicePath: "44"
name: "43"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -21,12 +21,13 @@ package v1
// ProbeApplyConfiguration represents an declarative configuration of the Probe type for use
// with apply.
type ProbeApplyConfiguration struct {
HandlerApplyConfiguration `json:",inline"`
InitialDelaySeconds *int32 `json:"initialDelaySeconds,omitempty"`
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`
PeriodSeconds *int32 `json:"periodSeconds,omitempty"`
SuccessThreshold *int32 `json:"successThreshold,omitempty"`
FailureThreshold *int32 `json:"failureThreshold,omitempty"`
HandlerApplyConfiguration `json:",inline"`
InitialDelaySeconds *int32 `json:"initialDelaySeconds,omitempty"`
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`
PeriodSeconds *int32 `json:"periodSeconds,omitempty"`
SuccessThreshold *int32 `json:"successThreshold,omitempty"`
FailureThreshold *int32 `json:"failureThreshold,omitempty"`
TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"`
}
// ProbeApplyConfiguration constructs an declarative configuration of the Probe type for use with
@ -98,3 +99,11 @@ func (b *ProbeApplyConfiguration) WithFailureThreshold(value int32) *ProbeApplyC
b.FailureThreshold = &value
return b
}
// WithTerminationGracePeriodSeconds sets the TerminationGracePeriodSeconds field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the TerminationGracePeriodSeconds field is set to the value of the last call.
func (b *ProbeApplyConfiguration) WithTerminationGracePeriodSeconds(value int64) *ProbeApplyConfiguration {
b.TerminationGracePeriodSeconds = &value
return b
}

View File

@ -5514,6 +5514,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: tcpSocket
type:
namedType: io.k8s.api.core.v1.TCPSocketAction
- name: terminationGracePeriodSeconds
type:
scalar: numeric
- name: timeoutSeconds
type:
scalar: numeric

View File

@ -420,6 +420,67 @@ var _ = SIGDescribe("Probing container", func() {
framework.Failf("Pod became ready in %v, more than 5s after startupProbe succeeded. It means that the delay readiness probes were not initiated immediately after startup finished.", readyIn)
}
})
/*
Release: v1.21
Testname: Set terminationGracePeriodSeconds for livenessProbe
Description: A pod with a long terminationGracePeriod is created with a shorter livenessProbe-level terminationGracePeriodSeconds. We confirm the shorter termination period is used.
*/
ginkgo.It("should override timeoutGracePeriodSeconds when LivenessProbe field is set [Feature:ProbeTerminationGracePeriod]", func() {
pod := e2epod.NewAgnhostPod(f.Namespace.Name, "liveness-override-"+string(uuid.NewUUID()), nil, nil, nil, "/bin/sh", "-c", "sleep 1000")
longGracePeriod := int64(500)
pod.Spec.TerminationGracePeriodSeconds = &longGracePeriod
// probe will fail since pod has no http endpoints
shortGracePeriod := int64(5)
pod.Spec.Containers[0].LivenessProbe = &v1.Probe{
Handler: v1.Handler{
HTTPGet: &v1.HTTPGetAction{
Path: "/healthz",
Port: intstr.FromInt(8080),
},
},
InitialDelaySeconds: 10,
FailureThreshold: 1,
TerminationGracePeriodSeconds: &shortGracePeriod,
}
// 10s delay + 10s period + 5s grace period = 25s < 30s << pod-level timeout 500
RunLivenessTest(f, pod, 1, time.Second*30)
})
/*
Release: v1.21
Testname: Set terminationGracePeriodSeconds for startupProbe
Description: A pod with a long terminationGracePeriod is created with a shorter startupProbe-level terminationGracePeriodSeconds. We confirm the shorter termination period is used.
*/
ginkgo.It("should override timeoutGracePeriodSeconds when StartupProbe field is set [Feature:ProbeTerminationGracePeriod]", func() {
pod := e2epod.NewAgnhostPod(f.Namespace.Name, "startup-override-"+string(uuid.NewUUID()), nil, nil, nil, "/bin/sh", "-c", "sleep 1000")
longGracePeriod := int64(500)
pod.Spec.TerminationGracePeriodSeconds = &longGracePeriod
// startup probe will fail since pod will sleep for 1000s before becoming ready
shortGracePeriod := int64(5)
pod.Spec.Containers[0].StartupProbe = &v1.Probe{
Handler: execHandler([]string{"/bin/cat", "/tmp/startup"}),
InitialDelaySeconds: 10,
FailureThreshold: 1,
TerminationGracePeriodSeconds: &shortGracePeriod,
}
// liveness probe always succeeds
pod.Spec.Containers[0].LivenessProbe = &v1.Probe{
Handler: v1.Handler{
Exec: &v1.ExecAction{
Command: []string{"/bin/true"},
},
},
InitialDelaySeconds: 15,
FailureThreshold: 1,
}
// 10s delay + 10s period + 5s grace period = 25s < 30s << pod-level timeout 500
RunLivenessTest(f, pod, 1, time.Second*30)
})
})
// GetContainerStartedTime returns the time when the given container started and error if any