Repository: kubernetes-sigs/security-profiles-operator
Pull request #478 (2021-06-09 07:38:50)

Add localhost deployment patch
#### What type of PR is this?

/kind documentation

#### What this PR does / why we need it:
This little helper can be used if the operator image has been built
locally on the via podman by running `sudo make image`. After that, the
patch can be applied to be able to run `kubectl apply -f
deploy/operator.yaml`.
#### Which issue(s) this PR fixes:

<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.

Fixes #

or

None
-->
None
#### Does this PR have test?
None
<!--
If tests aren't applicable just write N/A.
-->

#### Special notes for your reviewer:
None
#### Does this PR introduce a user-facing change?

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->

```release-note
None
```


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/478#" title="Author self-approved">saschagrunert</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/master/OWNERS)~~ [saschagrunert]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
k8s-ci-robot: @saschagrunert: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 254bf7322219b8920ccc1aa604f54d38cc714f3d | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/478/pull-security-profiles-operator-test-e2e/1402530614703296512) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=478). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaschagrunert). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
saschagrunert: /test pull-security-profiles-operator-test-e2e
saschagrunert: /test pull-security-profiles-operator-test-e2e
k8s-ci-robot: @saschagrunert: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 254bf7322219b8920ccc1aa604f54d38cc714f3d | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/478/pull-security-profiles-operator-test-e2e/1402537917129166848) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=478). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaschagrunert). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
saschagrunert: /retest

Patch:
diff --git a/cmd/security-profiles-operator/main.go b/cmd/security-profiles-operator/main.go
index b72b21891..fcb2874b8 100644
--- a/cmd/security-profiles-operator/main.go
+++ b/cmd/security-profiles-operator/main.go
@@ -325,8 +325,7 @@ func runDaemon(ctx *cli.Context) error {
 
 func runLogEnricher(ctx *cli.Context) error {
 	printInfo("log-enricher")
-	enricher.Run(ctrl.Log.WithName("log-enricher"))
-	return nil
+	return enricher.Run(ctrl.Log.WithName("log-enricher"))
 }
 
 func runWebhook(ctx *cli.Context) error {
diff --git a/hack/deploy-localhost.patch b/hack/deploy-localhost.patch
new file mode 100644
index 000000000..9a05e3b43
--- /dev/null
+++ b/hack/deploy-localhost.patch
@@ -0,0 +1,44 @@
+diff --git a/deploy/operator.yaml b/deploy/operator.yaml
+index fd8bd11..07c664e 100644
+--- a/deploy/operator.yaml
++++ b/deploy/operator.yaml
+@@ -1351,7 +1351,7 @@ metadata:
+   name: security-profiles-operator
+   namespace: security-profiles-operator
+ spec:
+-  replicas: 3
++  replicas: 1
+   selector:
+     matchLabels:
+       app: security-profiles-operator
+@@ -1374,8 +1374,8 @@ spec:
+           valueFrom:
+             fieldRef:
+               fieldPath: metadata.namespace
+-        image: gcr.io/k8s-staging-sp-operator/security-profiles-operator:latest
+-        imagePullPolicy: Always
++        image: localhost/security-profiles-operator:latest
++        imagePullPolicy: IfNotPresent
+         name: security-profiles-operator
+         securityContext:
+           allowPrivilegeEscalation: false
+@@ -1400,7 +1400,7 @@ metadata:
+   name: security-profiles-operator-webhook
+   namespace: security-profiles-operator
+ spec:
+-  replicas: 3
++  replicas: 1
+   selector:
+     matchLabels:
+       app: security-profiles-operator
+@@ -1417,8 +1417,8 @@ spec:
+       containers:
+       - args:
+         - webhook
+-        image: gcr.io/k8s-staging-sp-operator/security-profiles-operator:latest
+-        imagePullPolicy: Always
++        image: localhost/security-profiles-operator:latest
++        imagePullPolicy: IfNotPresent
+         name: security-profiles-operator
+         ports:
+         - containerPort: 9443
diff --git a/installation-usage.md b/installation-usage.md
index 79a1ceae4..4e8a983a1 100644
--- a/installation-usage.md
+++ b/installation-usage.md
@@ -473,6 +473,8 @@ additional metrics are provided by the daemon, which are always prefixed with
 | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------- |
 | `seccomp_profile_total`       | `operation={delete,update}`                                                                                                                                                                                | Counter | Amount of seccomp profile operations. |
 | `seccomp_profile_error_total` | `reason={`<br>`SeccompNotSupportedOnNode,`<br>`InvalidSeccompProfile,`<br>`CannotSaveSeccompProfile,`<br>`CannotRemoveSeccompProfile,`<br>`CannotUpdateSeccompProfile,`<br>`CannotUpdateNodeStatus`<br>`}` | Counter | Amount of seccomp profile errors.     |
+| `selinux_profile_total`       | `operation={delete,update}`                                                                                                                                                                                | Counter | Amount of selinux profile operations. |
+| `selinux_profile_error_total` | `reason={`<br>`CannotSaveSelinuxPolicy,`<br>`CannotUpdatePolicyStatus,`<br>`CannotRemoveSelinuxPolicy,`<br>`CannotContactSelinuxd,`<br>`CannotWritePolicyFile,`<br>`CannotGetPolicyStatus`<br>`}` | Counter | Amount of selinux profile errors.     |
 
 ## Automatic ServiceMonitor deployment
 
diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go
index a8f0d86a3..fe2c8cdbf 100644
--- a/internal/pkg/config/config.go
+++ b/internal/pkg/config/config.go
@@ -64,6 +64,9 @@ const (
 
 	// HealthProbePort is the port where the liveness probe will be served.
 	HealthProbePort = 8085
+
+	// EnricherLogFile is the path to the kernel messages log file used for the enricher.
+	EnricherLogFile = "/var/log/spo.log"
 )
 
 // ProfileRecordingOutputPath is the path where the recorded profiles will be
diff --git a/internal/pkg/daemon/enricher/container.go b/internal/pkg/daemon/enricher/container.go
index b44c9c60d..68ce9edc0 100644
--- a/internal/pkg/daemon/enricher/container.go
+++ b/internal/pkg/daemon/enricher/container.go
@@ -25,6 +25,7 @@ import (
 	"regexp"
 	"strings"
 
+	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
@@ -86,7 +87,7 @@ func containerIDRaw(containerID string) (string, error) {
 	return "", errors.Wrap(errUnsupportedContainerRuntime, containerID)
 }
 
-func getContainerID(processID int) string {
+func getContainerID(logger logr.Logger, processID int) string {
 	cgroupFile := fmt.Sprintf("/proc/%d/cgroup", processID)
 	file, err := os.Open(filepath.Clean(cgroupFile))
 	if err != nil {
diff --git a/internal/pkg/daemon/enricher/main.go b/internal/pkg/daemon/enricher/enricher.go
similarity index 77%
rename from internal/pkg/daemon/enricher/main.go
rename to internal/pkg/daemon/enricher/enricher.go
index e321dde69..5238d18e2 100644
--- a/internal/pkg/daemon/enricher/main.go
+++ b/internal/pkg/daemon/enricher/enricher.go
@@ -25,35 +25,32 @@ import (
 
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
-)
 
-var (
-	logFile = "/var/log/spo.log"
-	logger  logr.Logger
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/config"
 )
 
 // Run the log-enricher to scrap audit logs and enrich them with
 // Kubernetes data (namespace, pod and container).
-func Run(l logr.Logger) {
-	logger = l
-	nodeName := os.Getenv("NODE_NAME")
+func Run(logger logr.Logger) error {
+	nodeName := os.Getenv(config.NodeNameEnvKey)
 	if nodeName == "" {
-		logger.Error(nil, "'NODE_NAME' environment variable not found")
-		os.Exit(1)
+		err := errors.Errorf("%s environment variable not set", config.NodeNameEnvKey)
+		logger.Error(err, "unable to run enricher")
+		return err
 	}
 
-	logger.V(1).Info("starting log-exporter on node: ", nodeName)
+	logger.V(1).Info("Starting log-enricher on node: " + nodeName)
 
 	auditLines := make(chan string)
 	tailErrors := make(chan error)
-	go tailFile(logFile, auditLines, tailErrors)
+	go tailFile(config.EnricherLogFile, auditLines, tailErrors)
 
 	for {
 		var line string
 		select {
 		case err := <-tailErrors:
 			logger.Error(err, "tail audit log")
-			os.Exit(1)
+			return errors.Wrap(err, "failed to tail")
 		case line = <-auditLines:
 		}
 
@@ -63,14 +60,14 @@ func Run(l logr.Logger) {
 
 		auditLine, err := extractAuditLine(line)
 		if err != nil {
-			fmt.Printf("extract seccomp details from audit line: %v\n", err)
+			logger.Error(err, "extract seccomp details from audit line")
 		}
 
 		if auditLine.SystemCallID == 0 {
 			continue
 		}
 
-		cID := getContainerID(auditLine.ProcessID)
+		cID := getContainerID(logger, auditLine.ProcessID)
 		containers, err := getNodeContainers(nodeName)
 		c, found := containers[cID]
 
@@ -80,10 +77,10 @@ func Run(l logr.Logger) {
 		}
 
 		name := systemCalls[auditLine.SystemCallID]
-		fmt.Printf("audit(%s) type=%s node=%s pid=%d ns=%s pod=%s c=%s exe=%s scid=%d scname=%s\n",
+		logger.Info(fmt.Sprintf("audit(%s) type=%s node=%s pid=%d ns=%s pod=%s c=%s exe=%s scid=%d scname=%s\n",
 			auditLine.TimestampID, auditLine.Type, nodeName,
 			auditLine.ProcessID, c.Namespace, c.PodName,
-			c.ContainerName, auditLine.Executable, auditLine.SystemCallID, name)
+			c.ContainerName, auditLine.Executable, auditLine.SystemCallID, name))
 	}
 }
 
diff --git a/internal/pkg/daemon/metrics/metrics.go b/internal/pkg/daemon/metrics/metrics.go
index 25300a707..0819fad91 100644
--- a/internal/pkg/daemon/metrics/metrics.go
+++ b/internal/pkg/daemon/metrics/metrics.go
@@ -33,13 +33,15 @@ import (
 // +kubebuilder:rbac:groups=authorization.k8s.io,resources=subjectaccessreviews,verbs=create
 
 const (
-	metricNamespace                      = "security_profiles_operator"
-	metricNameSeccompProfile             = "seccomp_profile_total"
-	metricNameSeccompProfileError        = "seccomp_profile_error_total"
-	metricLabelValueSeccompProfileUpdate = "update"
-	metricLabelValueSeccompProfileDelete = "delete"
-	metricLabelOperation                 = "operation"
-	metricsLabelReason                   = "reason"
+	metricNamespace               = "security_profiles_operator"
+	metricNameSeccompProfile      = "seccomp_profile_total"
+	metricNameSelinuxProfile      = "selinux_profile_total"
+	metricNameSeccompProfileError = "seccomp_profile_error_total"
+	metricNameSelinuxProfileError = "selinux_profile_error_total"
+	metricLabelValueProfileUpdate = "update"
+	metricLabelValueProfileDelete = "delete"
+	metricLabelOperation          = "operation"
+	metricsLabelReason            = "reason"
 
 	// HandlerPath is the default path for serving metrics.
 	HandlerPath = "/metrics-spod"
@@ -51,6 +53,8 @@ type Metrics struct {
 	log                       logr.Logger
 	metricSeccompProfile      *prometheus.CounterVec
 	metricSeccompProfileError *prometheus.CounterVec
+	metricSelinuxProfile      *prometheus.CounterVec
+	metricSelinuxProfileError *prometheus.CounterVec
 }
 
 // New returns a new Metrics instance.
@@ -74,6 +78,22 @@ func New() *Metrics {
 			},
 			[]string{metricsLabelReason},
 		),
+		metricSelinuxProfile: prometheus.NewCounterVec(
+			prometheus.CounterOpts{
+				Name:      metricNameSelinuxProfile,
+				Namespace: metricNamespace,
+				Help:      "Counter about selinux profile operations.",
+			},
+			[]string{metricLabelOperation},
+		),
+		metricSelinuxProfileError: prometheus.NewCounterVec(
+			prometheus.CounterOpts{
+				Name:      metricNameSelinuxProfileError,
+				Namespace: metricNamespace,
+				Help:      "Counter about selinux profile errors.",
+			},
+			[]string{metricsLabelReason},
+		),
 	}
 }
 
@@ -82,6 +102,8 @@ func (m *Metrics) Register() error {
 	for name, collector := range map[string]prometheus.Collector{
 		metricNameSeccompProfile:      m.metricSeccompProfile,
 		metricNameSeccompProfileError: m.metricSeccompProfileError,
+		metricNameSelinuxProfile:      m.metricSelinuxProfile,
+		metricNameSelinuxProfileError: m.metricSelinuxProfileError,
 	} {
 		m.log.Info(fmt.Sprintf("Registering metric: %s", name))
 		if err := m.impl.Register(collector); err != nil {
@@ -101,13 +123,13 @@ func (m *Metrics) Handler() http.Handler {
 // IncSeccompProfileUpdate increments the seccomp profile update counter.
 func (m *Metrics) IncSeccompProfileUpdate() {
 	m.metricSeccompProfile.
-		WithLabelValues(metricLabelValueSeccompProfileUpdate).Inc()
+		WithLabelValues(metricLabelValueProfileUpdate).Inc()
 }
 
 // IncSeccompProfileDelete increments the seccomp profile deletion counter.
 func (m *Metrics) IncSeccompProfileDelete() {
 	m.metricSeccompProfile.
-		WithLabelValues(metricLabelValueSeccompProfileDelete).Inc()
+		WithLabelValues(metricLabelValueProfileDelete).Inc()
 }
 
 // IncSeccompProfileError increments the seccomp profile error counter for the
@@ -115,3 +137,21 @@ func (m *Metrics) IncSeccompProfileDelete() {
 func (m *Metrics) IncSeccompProfileError(reason event.Reason) {
 	m.metricSeccompProfileError.WithLabelValues(string(reason)).Inc()
 }
+
+// IncSelinuxProfileUpdate increments the selinux profile update counter.
+func (m *Metrics) IncSelinuxProfileUpdate() {
+	m.metricSelinuxProfile.
+		WithLabelValues(metricLabelValueProfileUpdate).Inc()
+}
+
+// IncSelinuxProfileDelete increments the selinux profile deletion counter.
+func (m *Metrics) IncSelinuxProfileDelete() {
+	m.metricSelinuxProfile.
+		WithLabelValues(metricLabelValueProfileDelete).Inc()
+}
+
+// IncSelinuxProfileError increments the selinux profile error counter for the
+// provided reason.
+func (m *Metrics) IncSelinuxProfileError(reason event.Reason) {
+	m.metricSelinuxProfileError.WithLabelValues(string(reason)).Inc()
+}
diff --git a/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go b/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go
index c480c30f0..db16a20ab 100644
--- a/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go
+++ b/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go
@@ -43,6 +43,7 @@ import (
 	selinuxprofilev1alpha1 "sigs.k8s.io/security-profiles-operator/api/selinuxprofile/v1alpha1"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/config"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/controller"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/metrics"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/manager/spod/bindata"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/nodestatus"
 )
@@ -101,6 +102,7 @@ type ReconcileSP struct {
 	scheme         *runtime.Scheme
 	policyTemplate *template.Template
 	record         event.Recorder
+	metrics        *metrics.Metrics
 }
 
 // Name returns the name of the controller.
@@ -163,6 +165,7 @@ func (r *ReconcileSP) Reconcile(_ context.Context, request reconcile.Request) (r
 
 	if err := nodeStatus.SetNodeStatus(context.TODO(), statusv1alpha1.ProfileStateTerminating); err != nil {
 		reqLogger.Error(err, "cannot update SELinux profile status")
+		r.metrics.IncSelinuxProfileError(reasonCannotUpdatePolicyStatus)
 		r.record.Event(instance, event.Warning(reasonCannotUpdatePolicyStatus, err))
 		return reconcile.Result{}, errors.Wrap(err, "updating status for deleted SELinux profile")
 	}
@@ -177,12 +180,14 @@ func (r *ReconcileSP) Reconcile(_ context.Context, request reconcile.Request) (r
 
 	res, err := r.reconcileDeletePolicy(instance, nodeStatus, reqLogger)
 	if res.Requeue || err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotRemovePolicy)
 		r.record.Event(instance, event.Warning(reasonCannotRemovePolicy, err))
 		return res, err
 	}
 
 	if err := nodeStatus.Remove(context.TODO(), r.client); err != nil {
 		reqLogger.Error(err, "cannot remove finalizer from SELinux profile")
+		r.metrics.IncSelinuxProfileError(reasonCannotUpdatePolicyStatus)
 		r.record.Event(instance, event.Warning(reasonCannotUpdatePolicyStatus, err))
 		return ctrl.Result{}, errors.Wrap(err, "deleting finalizer for deleted SELinux profile")
 	}
@@ -195,6 +200,7 @@ func (r *ReconcileSP) reconcilePolicy(
 ) (reconcile.Result, error) {
 	selinuxdReady, err := isSelinuxdReady()
 	if err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotContactSelinuxd)
 		r.record.Event(sp, event.Warning(reasonCannotContactSelinuxd, err))
 		return reconcile.Result{}, errors.Wrap(err, "contacting selinuxd")
 	}
@@ -206,6 +212,7 @@ func (r *ReconcileSP) reconcilePolicy(
 
 	err = r.reconcilePolicyFile(sp, l)
 	if err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotWritePolicyFile)
 		r.record.Event(sp, event.Warning(reasonCannotWritePolicyFile, err))
 		return reconcile.Result{}, errors.Wrap(err, "Creating policy file")
 	}
@@ -213,12 +220,14 @@ func (r *ReconcileSP) reconcilePolicy(
 	l.Info("Checking if policy deployed", "policyName", sp.Name)
 	polStatus, err := getPolicyStatus(sp)
 	if err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotGetPolicyStatus)
 		r.record.Event(sp, event.Warning(reasonCannotGetPolicyStatus, err))
 		return reconcile.Result{}, errors.Wrap(err, "Looking up policy status")
 	}
 
 	if polStatus == nil {
 		if err := nodeStatus.SetNodeStatus(context.TODO(), statusv1alpha1.ProfileStateInProgress); err != nil {
+			r.metrics.IncSelinuxProfileError(reasonCannotUpdatePolicyStatus)
 			r.record.Event(sp, event.Warning(reasonCannotUpdatePolicyStatus, err))
 			return reconcile.Result{}, errors.Wrap(err, "setting node status to in progress")
 		}
@@ -231,16 +240,19 @@ func (r *ReconcileSP) reconcilePolicy(
 	case installedStatus:
 		polState = statusv1alpha1.ProfileStateInstalled
 		evstr := fmt.Sprintf("Successfully saved profile to disk on %s", os.Getenv(config.NodeNameEnvKey))
+		r.metrics.IncSelinuxProfileUpdate()
 		r.record.Event(sp, event.Normal(reasonInstalledPolicy, evstr))
 	case failedStatus:
 		polState = statusv1alpha1.ProfileStateError
-		evstr := fmt.Sprintf("Successfully saved profile to disk on %s", os.Getenv(config.NodeNameEnvKey))
+		evstr := fmt.Sprintf("Failed to save profile to disk on %s", os.Getenv(config.NodeNameEnvKey))
+		r.metrics.IncSelinuxProfileError(reasonCannotInstallPolicy)
 		r.record.Event(sp, event.Warning(reasonCannotInstallPolicy, errors.New(evstr)))
 	}
 
 	l.Info("Policy deployed", "status", polState)
 
 	if err := nodeStatus.SetNodeStatus(context.TODO(), polState); err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotUpdatePolicyStatus)
 		r.record.Event(sp, event.Warning(reasonCannotUpdatePolicyStatus, err))
 		return reconcile.Result{}, errors.Wrap(err, "setting profile status")
 	}
@@ -309,6 +321,7 @@ func (r *ReconcileSP) reconcileDeletePolicy(
 	l.Info("Checking if policy is removed", "policyName", sp.Name)
 	polStatus, err := getPolicyStatus(sp)
 	if err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotGetPolicyStatus)
 		return reconcile.Result{}, errors.Wrap(err, "Looking up policy status")
 	}
 
@@ -319,13 +332,15 @@ func (r *ReconcileSP) reconcileDeletePolicy(
 			return reconcile.Result{Requeue: true}, nil
 		case failedStatus:
 			if err := nodeStatus.SetNodeStatus(context.TODO(), statusv1alpha1.ProfileStateError); err != nil {
-				return reconcile.Result{}, errors.Wrap(err, "Updating SELinux policy with installation success")
+				r.metrics.IncSelinuxProfileError(reasonCannotRemovePolicy)
+				return reconcile.Result{}, errors.Wrap(err, "Updating SELinux policy with installation")
 			}
 
 			return reconcile.Result{}, nil
 		}
 	}
 
+	r.metrics.IncSelinuxProfileDelete()
 	l.Info("Policy removed")
 	return reconcile.Result{}, nil
 }
diff --git a/internal/pkg/daemon/selinuxprofile/setup.go b/internal/pkg/daemon/selinuxprofile/setup.go
index dc69b8f5a..c2bb9df35 100644
--- a/internal/pkg/daemon/selinuxprofile/setup.go
+++ b/internal/pkg/daemon/selinuxprofile/setup.go
@@ -47,6 +47,7 @@ func (r *ReconcileSP) Setup(
 	r.scheme = mgr.GetScheme()
 	r.policyTemplate = tmpl
 	r.record = event.NewAPIRecorder(mgr.GetEventRecorderFor("selinuxprofile"))
+	r.metrics = met
 
 	// Register the regular reconciler to manage SelinuxProfiles
 	return ctrl.NewControllerManagedBy(mgr).
diff --git a/internal/pkg/manager/spod/bindata/spod.go b/internal/pkg/manager/spod/bindata/spod.go
index 10408a9f9..c0ff29e12 100644
--- a/internal/pkg/manager/spod/bindata/spod.go
+++ b/internal/pkg/manager/spod/bindata/spod.go
@@ -47,7 +47,6 @@ const (
 	SelinuxdPrivateDir   = "/var/run/selinuxd"
 	SelinuxdSocketPath   = SelinuxdPrivateDir + "/selinuxd.sock"
 	SelinuxdDBPath       = SelinuxdPrivateDir + "/selinuxd.db"
-	varLogSpoPath        = "/var/log/spo.log"
 	MetricsImage         = "quay.io/brancz/kube-rbac-proxy:v0.9.0"
 )
 
@@ -361,7 +360,7 @@ semodule -i /opt/spo-profiles/selinuxd.cil
 						VolumeMounts: []v1.VolumeMount{
 							{
 								Name:      "host-varlogspo-volume",
-								MountPath: varLogSpoPath,
+								MountPath: config.EnricherLogFile,
 								ReadOnly:  true,
 							},
 						},
@@ -387,11 +386,10 @@ semodule -i /opt/spo-profiles/selinuxd.cil
 						},
 						Env: []v1.EnvVar{
 							{
-								Name: "NODE_NAME",
+								Name: config.NodeNameEnvKey,
 								ValueFrom: &v1.EnvVarSource{
 									FieldRef: &v1.ObjectFieldSelector{
-										APIVersion: "v1",
-										FieldPath:  "spec.nodeName",
+										FieldPath: "spec.nodeName",
 									},
 								},
 							},
@@ -524,7 +522,7 @@ semodule -i /opt/spo-profiles/selinuxd.cil
 						Name: "host-varlogspo-volume",
 						VolumeSource: v1.VolumeSource{
 							HostPath: &v1.HostPathVolumeSource{
-								Path: varLogSpoPath,
+								Path: config.EnricherLogFile,
 								Type: &hostPathFile,
 							},
 						},
diff --git a/internal/pkg/manager/spod/spod_controller.go b/internal/pkg/manager/spod/spod_controller.go
index 24f243be1..b2ec1d4bb 100644
--- a/internal/pkg/manager/spod/spod_controller.go
+++ b/internal/pkg/manager/spod/spod_controller.go
@@ -405,10 +405,10 @@ func (r *ReconcileSPOd) getConfiguredSPOd(
 
 	// Log enricher parameters
 	if cfg.Spec.EnableLogEnricher {
+		r.baseSPOd.Spec.Template.Spec.Containers[2].Image = image
 		templateSpec.Containers = append(
 			templateSpec.Containers,
 			r.baseSPOd.Spec.Template.Spec.Containers[2])
-		templateSpec.Containers[2].Image = image
 
 		// HostPID is only required for the log-enricher
 		// and is used to access cgroup files to map Process IDs to Pod IDs


Test patch:
diff --git a/internal/pkg/daemon/metrics/metrics_test.go b/internal/pkg/daemon/metrics/metrics_test.go
index 9ef0aea62..dbca1f975 100644
--- a/internal/pkg/daemon/metrics/metrics_test.go
+++ b/internal/pkg/daemon/metrics/metrics_test.go
@@ -82,7 +82,7 @@ func TestSeccompProfile(t *testing.T) {
 				m.IncSeccompProfileUpdate()
 			},
 			then: func(m *Metrics) {
-				ctr, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueSeccompProfileUpdate)
+				ctr, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueProfileUpdate)
 				require.Nil(t, err)
 				require.Equal(t, 1, getMetricValue(ctr))
 			},
@@ -92,7 +92,7 @@ func TestSeccompProfile(t *testing.T) {
 				m.IncSeccompProfileDelete()
 			},
 			then: func(m *Metrics) {
-				ctr, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueSeccompProfileDelete)
+				ctr, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueProfileDelete)
 				require.Nil(t, err)
 				require.Equal(t, 1, getMetricValue(ctr))
 			},
@@ -106,11 +106,49 @@ func TestSeccompProfile(t *testing.T) {
 				m.IncSeccompProfileDelete()
 			},
 			then: func(m *Metrics) {
-				ctrUpdate, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueSeccompProfileUpdate)
+				ctrUpdate, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueProfileUpdate)
 				require.Nil(t, err)
 				require.Equal(t, 3, getMetricValue(ctrUpdate))
 
-				ctrDelete, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueSeccompProfileDelete)
+				ctrDelete, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueProfileDelete)
+				require.Nil(t, err)
+				require.Equal(t, 2, getMetricValue(ctrDelete))
+			},
+		},
+		{ // Selinux single update
+			when: func(m *Metrics) {
+				m.IncSelinuxProfileUpdate()
+			},
+			then: func(m *Metrics) {
+				ctr, err := m.metricSelinuxProfile.GetMetricWithLabelValues(metricLabelValueProfileUpdate)
+				require.Nil(t, err)
+				require.Equal(t, 1, getMetricValue(ctr))
+			},
+		},
+		{ // Selinux single delete
+			when: func(m *Metrics) {
+				m.IncSelinuxProfileDelete()
+			},
+			then: func(m *Metrics) {
+				ctr, err := m.metricSelinuxProfile.GetMetricWithLabelValues(metricLabelValueProfileDelete)
+				require.Nil(t, err)
+				require.Equal(t, 1, getMetricValue(ctr))
+			},
+		},
+		{ // Selinux multiple update and delete
+			when: func(m *Metrics) {
+				m.IncSelinuxProfileUpdate()
+				m.IncSelinuxProfileUpdate()
+				m.IncSelinuxProfileDelete()
+				m.IncSelinuxProfileUpdate()
+				m.IncSelinuxProfileDelete()
+			},
+			then: func(m *Metrics) {
+				ctrUpdate, err := m.metricSelinuxProfile.GetMetricWithLabelValues(metricLabelValueProfileUpdate)
+				require.Nil(t, err)
+				require.Equal(t, 3, getMetricValue(ctrUpdate))
+
+				ctrDelete, err := m.metricSelinuxProfile.GetMetricWithLabelValues(metricLabelValueProfileDelete)
 				require.Nil(t, err)
 				require.Equal(t, 2, getMetricValue(ctrDelete))
 			},
diff --git a/test/e2e_test.go b/test/e2e_test.go
index 905eba44c..22615b16b 100644
--- a/test/e2e_test.go
+++ b/test/e2e_test.go
@@ -80,7 +80,7 @@ func (e *e2e) TestSecurityProfilesOperator() {
 		},
 		{
 			"Seccomp: Metrics",
-			e.testCaseMetrics,
+			e.testCaseSeccompMetrics,
 		},
 		{
 			"Seccomp: Re-deploy the operator",
@@ -94,6 +94,10 @@ func (e *e2e) TestSecurityProfilesOperator() {
 			"SELinux: base case (install policy, run pod and delete)",
 			e.testCaseSelinuxBaseUsage,
 		},
+		{
+			"SELinux: Metrics (update, delete)",
+			e.testCaseSelinuxMetrics,
+		},
 		{
 			"SPOD: Update SELinux flag",
 			e.testCaseSPODUpdateSelinux,
diff --git a/test/suite_test.go b/test/suite_test.go
index a36aff077..a3ae45e43 100644
--- a/test/suite_test.go
+++ b/test/suite_test.go
@@ -452,7 +452,7 @@ func (e *e2e) logf(format string, a ...interface{}) {
 	e.logger.Info(fmt.Sprintf(format, a...))
 }
 
-func (e *e2e) selinuxtOnlyTestCase() {
+func (e *e2e) selinuxOnlyTestCase() {
 	if !e.selinuxEnabled {
 		e.T().Skip("Skipping SELinux-related test")
 	}
diff --git a/test/tc_metrics_test.go b/test/tc_metrics_test.go
index a32f42e51..45cba4fe6 100644
--- a/test/tc_metrics_test.go
+++ b/test/tc_metrics_test.go
@@ -23,15 +23,18 @@ import (
 	"strings"
 )
 
-func (e *e2e) testCaseMetrics(nodes []string) {
+const (
+	curlCMD     = "curl -ks -H \"Authorization: Bearer `cat /var/run/secrets/kubernetes.io/serviceaccount/token`\" "
+	metricsURL  = "https://metrics.security-profiles-operator/"
+	curlSpodCMD = curlCMD + metricsURL + "metrics-spod"
+	curlCtrlCMD = curlCMD + metricsURL + "metrics"
+	profileName = "metrics-profile"
+)
+
+func (e *e2e) testCaseSeccompMetrics(nodes []string) {
 	e.seccompOnlyTestCase()
 
 	const (
-		curlCMD         = "curl -ks -H \"Authorization: Bearer `cat /var/run/secrets/kubernetes.io/serviceaccount/token`\" "
-		metricsURL      = "https://metrics.security-profiles-operator/"
-		curlSpodCMD     = curlCMD + metricsURL + "metrics-spod"
-		curlCtrlCMD     = curlCMD + metricsURL + "metrics"
-		profileName     = "metrics-profile"
 		operationDelete = `security_profiles_operator_seccomp_profile_total{operation="delete"}`
 		operationUpdate = `security_profiles_operator_seccomp_profile_total{operation="update"}`
 	)
@@ -74,6 +77,46 @@ spec:
 	e.GreaterOrEqual(newMetricUpdates, metricUpdates)
 }
 
+func (e *e2e) testCaseSelinuxMetrics(nodes []string) {
+	e.selinuxOnlyTestCase()
+
+	const (
+		operationDelete = `security_profiles_operator_selinux_profile_total{operation="delete"}`
+		operationUpdate = `security_profiles_operator_selinux_profile_total{operation="update"}`
+	)
+
+	e.logf("Retrieving spo metrics for getting assertions")
+	output := e.kubectlRunOperatorNS("pod", "--", "bash", "-c", curlSpodCMD)
+	metricDeletions := e.parseMetric(output, operationDelete)
+	metricUpdates := e.parseMetric(output, operationUpdate)
+
+	e.logf("Creating test errorlogger policy")
+	cleanup := e.writeAndCreate(errorloggerPolicy, "errorlogger-policy.yml")
+	defer cleanup()
+	e.logf("Waiting for profile to be reconciled")
+	e.kubectl("wait", "--timeout", defaultSelinuxOpTimeout,
+		"--for", "condition=ready", "selinuxprofile", "errorlogger")
+
+	rawPolicyName := e.getSELinuxPolicyName("errorlogger")
+	e.logf("assert errorlogger policy is installed")
+	e.assertSelinuxPolicyIsInstalled(nodes, rawPolicyName, maxNodeIterations, sleepBetweenIterations)
+
+	e.logf("Deleting errorlogger profile")
+	e.kubectl("delete", "selinuxprofile", "errorlogger")
+	e.logf("assert errorlogger policy was removed")
+	e.assertSelinuxPolicyIsInstalled(nodes, rawPolicyName, maxNodeIterations, sleepBetweenIterations)
+
+	e.logf("Retrieving spo metrics for validation")
+	outputSpod := e.kubectlRunOperatorNS("pod", "--", "bash", "-c", curlSpodCMD)
+	e.Contains(outputSpod, "promhttp_metric_handler_requests_total")
+
+	e.logf("Asserting metrics values")
+	newMetricDeletions := e.parseMetric(outputSpod, operationDelete)
+	newMetricUpdates := e.parseMetric(outputSpod, operationUpdate)
+	e.GreaterOrEqual(newMetricDeletions, metricDeletions)
+	e.GreaterOrEqual(newMetricUpdates, metricUpdates)
+}
+
 func (e *e2e) parseMetric(content, metric string) int {
 	scanner := bufio.NewScanner(strings.NewReader(content))
 	for scanner.Scan() {
diff --git a/test/tc_selinux_base_usage_test.go b/test/tc_selinux_base_usage_test.go
index fadbf7db3..e8efdf8b7 100644
--- a/test/tc_selinux_base_usage_test.go
+++ b/test/tc_selinux_base_usage_test.go
@@ -22,14 +22,24 @@ import (
 	"time"
 )
 
-func (e *e2e) testCaseSelinuxBaseUsage(nodes []string) {
-	e.selinuxtOnlyTestCase()
-
-	const maxNodeIterations = 3
-	const sleepBetweenIterations = 5 * time.Second
+const (
+	maxNodeIterations      = 3
+	sleepBetweenIterations = 5 * time.Second
+	errorloggerPolicy      = `
+apiVersion: security-profiles-operator.x-k8s.io/v1alpha1
+kind: SelinuxProfile
+metadata:
+  name: errorlogger
+spec:
+  policy: |
+    (blockinherit container)
+    (allow process var_log_t ( dir ( open read getattr lock search ioctl add_name remove_name write )))
+    (allow process var_log_t ( file ( getattr read write append ioctl lock map open create  )))
+    (allow process var_log_t ( sock_file ( getattr read write append open  )))
+`
 
 	// nolint:lll
-	const podWithPolicyFmt = `
+	podWithPolicyFmt = `
 apiVersion: v1
 kind: Pod
 metadata:
@@ -53,19 +63,10 @@ spec:
       path: /var/log
       type: Directory
 `
+)
 
-	const errorloggerPolicy = `
-apiVersion: security-profiles-operator.x-k8s.io/v1alpha1
-kind: SelinuxProfile
-metadata:
-  name: errorlogger
-spec:
-  policy: |
-    (blockinherit container)
-    (allow process var_log_t ( dir ( open read getattr lock search ioctl add_name remove_name write ))) 
-    (allow process var_log_t ( file ( getattr read write append ioctl lock map open create  ))) 
-    (allow process var_log_t ( sock_file ( getattr read write append open  ))) 
-`
+func (e *e2e) testCaseSelinuxBaseUsage(nodes []string) {
+	e.selinuxOnlyTestCase()
 
 	e.logf("The 'errorlogger' workload should be able to use SELinux policy")
 
@@ -79,28 +80,7 @@ spec:
 	rawPolicyName := e.getSELinuxPolicyName("errorlogger")
 
 	e.logf("assert policy is installed")
-
-	for i := 0; i < maxNodeIterations; i++ {
-		var missingPolName string
-
-		for _, node := range nodes {
-			policiesRaw := e.execNode(node, "semodule", "-l")
-			if !e.sliceContainsString(strings.Split(policiesRaw, "\n"), rawPolicyName) {
-				missingPolName = node
-				break
-			}
-		}
-
-		if missingPolName != "" {
-			if i == maxNodeIterations-1 {
-				e.Failf("The SelinuxProfile errorlogger wasn't found in the %s node with the name %s",
-					missingPolName, rawPolicyName)
-			} else {
-				e.logf("the policy was stil present, trying again")
-				time.Sleep(sleepBetweenIterations)
-			}
-		}
-	}
+	e.assertSelinuxPolicyIsInstalled(nodes, rawPolicyName, maxNodeIterations, sleepBetweenIterations)
 
 	e.logf("creating workload")
 
@@ -125,23 +105,52 @@ spec:
 	e.kubectl("delete", "selinuxprofile", "errorlogger")
 
 	e.logf("assert policy was removed")
-	for i := 0; i < maxNodeIterations; i++ {
-		var nodeHasPolName string
+	e.assertSelinuxPolicyIsRemoved(nodes, rawPolicyName, maxNodeIterations, sleepBetweenIterations)
+}
+
+func (e *e2e) assertSelinuxPolicyIsInstalled(nodes []string, policy string, nodeIterations int, sleep time.Duration) {
+	for i := 0; i < nodeIterations; i++ {
+		var missingPolName string
+
+		for _, node := range nodes {
+			policiesRaw := e.execNode(node, "semodule", "-l")
+			if !e.sliceContainsString(strings.Split(policiesRaw, "\n"), policy) {
+				missingPolName = node
+				break
+			}
+		}
+
+		if missingPolName != "" {
+			if i == nodeIterations-1 {
+				e.Failf("The SelinuxProfile errorlogger wasn't found in the %s node with the name %s",
+					missingPolName, policy)
+			} else {
+				e.logf("the policy was stil present, trying again")
+				time.Sleep(sleep)
+			}
+		}
+	}
+}
+
+func (e *e2e) assertSelinuxPolicyIsRemoved(nodes []string, policy string, nodeIterations int, sleep time.Duration) {
+	for i := 0; i < nodeIterations; i++ {
+		var missingPolName string
 
 		for _, node := range nodes {
 			policiesRaw := e.execNode(node, "semodule", "-l")
-			if e.sliceContainsString(strings.Split(policiesRaw, "\n"), rawPolicyName) {
-				nodeHasPolName = node
+			if e.sliceContainsString(strings.Split(policiesRaw, "\n"), policy) {
+				missingPolName = node
 				break
 			}
 		}
 
-		if nodeHasPolName != "" {
-			if i == maxNodeIterations-1 {
-				e.Failf("The SelinuxProfile errorlogger should have been removed from %s node", nodeHasPolName)
+		if missingPolName != "" {
+			if i == nodeIterations-1 {
+				e.Failf("The SelinuxProfile errorlogger was found in the %s node with the name %s",
+					missingPolName, policy)
 			} else {
 				e.logf("the policy was stil present, trying again")
-				time.Sleep(sleepBetweenIterations)
+				time.Sleep(sleep)
 			}
 		}
 	}
diff --git a/test/tc_selinux_sanity_check_test.go b/test/tc_selinux_sanity_check_test.go
index b6e1ac937..7340f888d 100644
--- a/test/tc_selinux_sanity_check_test.go
+++ b/test/tc_selinux_sanity_check_test.go
@@ -17,7 +17,7 @@ limitations under the License.
 package e2e_test
 
 func (e *e2e) testCaseSelinuxSanityCheck([]string) {
-	e.selinuxtOnlyTestCase()
+	e.selinuxOnlyTestCase()
 
 	const podWithoutPolicy = `
 apiVersion: v1
diff --git a/test/tc_spod_update_selinux_test.go b/test/tc_spod_update_selinux_test.go
index 3ef9292c1..36a8946a7 100644
--- a/test/tc_spod_update_selinux_test.go
+++ b/test/tc_spod_update_selinux_test.go
@@ -19,7 +19,7 @@ package e2e_test
 import "time"
 
 func (e *e2e) testCaseSPODUpdateSelinux(nodes []string) {
-	e.selinuxtOnlyTestCase()
+	e.selinuxOnlyTestCase()
 
 	e.logf("assert selinux is enabled in the spod object")
 	selinuxEnabledInSPODObj := e.kubectlOperatorNS("get", "spod", "spod", "-o", "jsonpath={.spec.enableSelinux}")