blob: d4a58327b0e22a0f6def9cf34741dfa9a05fd29f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 kubernetes
import (
"context"
"fmt"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
containerReasonContainerCreating = "ContainerCreating"
)
var _ DeploymentUnavailabilityReader = &deploymentUnavailabilityReader{}
// DeploymentUnavailabilityReader implementations find the reason behind a deployment failure
type DeploymentUnavailabilityReader interface {
// ReasonMessage returns the reason message in string format fetched from the culprit resource. For example, a Container status.
ReasonMessage() (string, error)
}
// DeploymentTroubleshooter creates a new DeploymentUnavailabilityReader for finding out why a deployment failed
func DeploymentTroubleshooter(client client.Client, deployment *v1.Deployment, container string) DeploymentUnavailabilityReader {
return &deploymentUnavailabilityReader{
c: client,
deployment: deployment,
container: container,
}
}
type deploymentUnavailabilityReader struct {
c client.Client
deployment *v1.Deployment
container string
}
// ReasonMessage tries to find a human-readable reason message for why the deployment is not available or in a failed state.
// This implementation fetches the given container status for this information.
// Returning an empty string means that no reason has been found in the underlying pods.
//
// See: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-states
//
// Future implementations might look in other objects for a more specific reason.
// Additionally, future work might involve returning a typed Reason, so controllers may take actions depending on what have happened.
func (d deploymentUnavailabilityReader) ReasonMessage() (string, error) {
podList := &corev1.PodList{}
// ideally we should get the latest replicaset, then the pods.
// problem is that we don't have a reliable field to get this information,
// it's in a message within the deployment status
// since this use case is only to show the deployment problem for user's troubleshooting, it's ok showing all of them.
// additionally, we are using a unique label identifier for matching
opts := &client.ListOptions{
LabelSelector: labels.SelectorFromSet(d.deployment.Spec.Selector.MatchLabels),
Namespace: d.deployment.Namespace,
}
if err := d.c.List(context.TODO(), podList, opts); err != nil {
return "", err
}
for _, pod := range podList.Items {
if pod.Status.Phase == corev1.PodRunning || pod.Status.Phase == corev1.PodSucceeded {
continue
}
for _, container := range pod.Status.ContainerStatuses {
if container.Name == d.container {
if !container.Ready {
if container.State.Waiting != nil && container.State.Waiting.Reason != containerReasonContainerCreating {
return fmt.Sprintf("ContainerNotReady: (%s) %s", container.State.Waiting.Reason, container.State.Waiting.Message), nil
}
if container.State.Terminated != nil && container.State.Terminated.ExitCode > 0 {
return fmt.Sprintf("ContainerNotReady: (%s) %s", container.State.Terminated.Reason, container.State.Terminated.Message), nil
}
}
}
}
}
return "", nil
}