diff --git a/hack/skaffold/virtual-kubelet/base.yml b/hack/skaffold/virtual-kubelet/base.yml index 9fc3d2f62..a69970a63 100644 --- a/hack/skaffold/virtual-kubelet/base.yml +++ b/hack/skaffold/virtual-kubelet/base.yml @@ -45,6 +45,13 @@ rules: - pods/status verbs: - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/vkubelet/env.go b/vkubelet/env.go index 629f041dc..28a075cb1 100644 --- a/vkubelet/env.go +++ b/vkubelet/env.go @@ -1,13 +1,48 @@ package vkubelet import ( + "context" "fmt" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/tools/record" + + "github.com/virtual-kubelet/virtual-kubelet/log" +) + +const ( + // ReasonOptionalConfigMapNotFound is the reason used in events emitted when an optional config map is not found. + ReasonOptionalConfigMapNotFound = "OptionalConfigMapNotFound" + // ReasonOptionalConfigMapKeyNotFound is the reason used in events emitted when an optional config map key is not found. + ReasonOptionalConfigMapKeyNotFound = "OptionalConfigMapKeyNotFound" + // ReasonFailedToReadOptionalConfigMap is the reason used in events emitted when an optional config map could not be read. + ReasonFailedToReadOptionalConfigMap = "FailedToReadOptionalConfigMap" + + // ReasonOptionalSecretNotFound is the reason used in events emitted when an optional secret is not found. + ReasonOptionalSecretNotFound = "OptionalSecretNotFound" + // ReasonOptionalSecretKeyNotFound is the reason used in events emitted when an optional secret key is not found. + ReasonOptionalSecretKeyNotFound = "OptionalSecretKeyNotFound" + // ReasonFailedToReadOptionalSecret is the reason used in events emitted when an optional secret could not be read. + ReasonFailedToReadOptionalSecret = "FailedToReadOptionalSecret" + + // ReasonMandatoryConfigMapNotFound is the reason used in events emitted when an mandatory config map is not found. + ReasonMandatoryConfigMapNotFound = "MandatoryConfigMapNotFound" + // ReasonMandatoryConfigMapKeyNotFound is the reason used in events emitted when an mandatory config map key is not found. + ReasonMandatoryConfigMapKeyNotFound = "MandatoryConfigMapKeyNotFound" + // ReasonFailedToReadMandatoryConfigMap is the reason used in events emitted when an mandatory config map could not be read. + ReasonFailedToReadMandatoryConfigMap = "FailedToReadMandatoryConfigMap" + + // ReasonMandatorySecretNotFound is the reason used in events emitted when an mandatory secret is not found. + ReasonMandatorySecretNotFound = "MandatorySecretNotFound" + // ReasonMandatorySecretKeyNotFound is the reason used in events emitted when an mandatory secret key is not found. + ReasonMandatorySecretKeyNotFound = "MandatorySecretKeyNotFound" + // ReasonFailedToReadMandatorySecret is the reason used in events emitted when an mandatory secret could not be read. + ReasonFailedToReadMandatorySecret = "FailedToReadMandatorySecret" ) // populateEnvironmentVariables populates Secrets and ConfigMap into environment variables -func (s *Server) populateEnvironmentVariables(pod *corev1.Pod) error { +func (s *Server) populateEnvironmentVariables(ctx context.Context, pod *corev1.Pod, recorder record.EventRecorder) error { for _, c := range pod.Spec.Containers { for i, e := range c.Env { if e.ValueFrom != nil { @@ -23,15 +58,23 @@ func (s *Server) populateEnvironmentVariables(pod *corev1.Pod) error { // We couldn't fetch the config map. // However, if the key reference is optional we should not fail. if optional { - // TODO: Emit an event (the contents of which possibly depend on whether the error is "NotFound", for clarity). + if errors.IsNotFound(err) { + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalConfigMapNotFound, "skipping optional envvar %q: configmap %q not found", e.Name, vf.Name) + } else { + log.G(ctx).Warnf("failed to read configmap %q: %v", vf.Name, err) + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadOptionalConfigMap, "skipping optional envvar %q: failed to read configmap %q", e.Name, vf.Name) + } + // Continue on to the next reference. continue } // At this point we know the key reference is mandatory. // Hence, we should return a meaningful error. if errors.IsNotFound(err) { + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatoryConfigMapNotFound, "configmap %q not found", vf.Name) return fmt.Errorf("required configmap %q not found", vf.Name) } - return fmt.Errorf("failed to fetch required configmap %q: %v", vf.Name, err) + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadMandatoryConfigMap, "failed to read configmap %q", vf.Name) + return fmt.Errorf("failed to read required configmap %q: %v", vf.Name, err) } // At this point we have successfully fetched the target config map. // We must now try to grab the requested key. @@ -43,11 +86,13 @@ func (s *Server) populateEnvironmentVariables(pod *corev1.Pod) error { // The requested key does not exist. // However, we should not fail if the key reference is optional. if optional { - // TODO: Emit an event. + // Continue on to the next reference. + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalConfigMapKeyNotFound, "skipping optional envvar %q: key %q does not exist in configmap %q", e.Name, vf.Key, vf.Name) continue } // At this point we know the key reference is mandatory. // Hence, we should fail. + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatoryConfigMapKeyNotFound, "key %q does not exist in configmap %q", vf.Key, vf.Name) return fmt.Errorf("configmap %q doesn't contain the %q key required by pod %s", vf.Name, vf.Key, pod.Name) } // Populate the environment variable and continue on to the next reference. @@ -66,15 +111,23 @@ func (s *Server) populateEnvironmentVariables(pod *corev1.Pod) error { // We couldn't fetch the secret. // However, if the key reference is optional we should not fail. if optional { - // TODO: Emit an event (the contents of which possibly depend on whether the error is "NotFound", for clarity). + if errors.IsNotFound(err) { + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalSecretNotFound, "skipping optional envvar %q: secret %q not found", e.Name, vf.Name) + } else { + log.G(ctx).Warnf("failed to read secret %q: %v", vf.Name, err) + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadOptionalSecret, "skipping optional envvar %q: failed to read secret %q", e.Name, vf.Name) + } + // Continue on to the next reference. continue } // At this point we know the key reference is mandatory. // Hence, we should return a meaningful error. if errors.IsNotFound(err) { + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatorySecretNotFound, "secret %q not found", vf.Name) return fmt.Errorf("required secret %q not found", vf.Name) } - return fmt.Errorf("failed to fetch required secret %q: %v", vf.Name, err) + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadMandatorySecret, "failed to read secret %q", vf.Name) + return fmt.Errorf("failed to read secret %q: %v", vf.Name, err) } // At this point we have successfully fetched the target secret. // We must now try to grab the requested key. @@ -86,11 +139,13 @@ func (s *Server) populateEnvironmentVariables(pod *corev1.Pod) error { // The requested key does not exist. // However, we should not fail if the key reference is optional. if optional { - // TODO: Emit an event. + // Continue on to the next reference. + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalSecretKeyNotFound, "skipping optional envvar %q: key %q does not exist in secret %q", e.Name, vf.Key, vf.Name) continue } // At this point we know the key reference is mandatory. // Hence, we should fail. + recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatorySecretKeyNotFound, "key %q does not exist in secret %q", vf.Key, vf.Name) return fmt.Errorf("secret %q doesn't contain the %q key required by pod %s", vf.Name, vf.Key, pod.Name) } // Populate the environment variable and continue on to the next reference. @@ -110,6 +165,5 @@ func (s *Server) populateEnvironmentVariables(pod *corev1.Pod) error { } } } - return nil } diff --git a/vkubelet/pod.go b/vkubelet/pod.go index b7b7d9627..788103c80 100644 --- a/vkubelet/pod.go +++ b/vkubelet/pod.go @@ -12,6 +12,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/record" "github.com/virtual-kubelet/virtual-kubelet/log" ) @@ -26,7 +27,7 @@ func addPodAttributes(span *trace.Span, pod *corev1.Pod) { ) } -func (s *Server) createOrUpdatePod(ctx context.Context, pod *corev1.Pod) error { +func (s *Server) createOrUpdatePod(ctx context.Context, pod *corev1.Pod, recorder record.EventRecorder) error { // Check if the pod is already known by the provider. // NOTE: Some providers return a non-nil error in their GetPod implementation when the pod is not found while some other don't. // Hence, we ignore the error and just act upon the pod if it is non-nil (meaning that the provider still knows about the pod). @@ -41,7 +42,7 @@ func (s *Server) createOrUpdatePod(ctx context.Context, pod *corev1.Pod) error { defer span.End() addPodAttributes(span, pod) - if err := s.populateEnvironmentVariables(pod); err != nil { + if err := s.populateEnvironmentVariables(ctx, pod, recorder); err != nil { span.SetStatus(trace.Status{Code: trace.StatusCodeInvalidArgument, Message: err.Error()}) return err } diff --git a/vkubelet/podcontroller.go b/vkubelet/podcontroller.go index 624dd50aa..6b69e5e7d 100644 --- a/vkubelet/podcontroller.go +++ b/vkubelet/podcontroller.go @@ -283,7 +283,7 @@ func (pc *PodController) syncPodInProvider(ctx context.Context, pod *corev1.Pod) } // Create or update the pod in the provider. - if err := pc.server.createOrUpdatePod(ctx, pod); err != nil { + if err := pc.server.createOrUpdatePod(ctx, pod, pc.recorder); err != nil { err := pkgerrors.Wrapf(err, "failed to sync pod %q in the provider", loggablePodName(pod)) span.SetStatus(ocstatus.FromError(err)) return err