Summary
To my recollection this was working a few weeks ago when I last tried it.
Maybe I'm holding it wrong? Or maybe there's a legitimate issue?
It seems there's an apparmor permission problem.
NB: the exact same steps work with the classic microk8s snap.
I installed microk8s 1.36-strict and attempted to deploy metallb
sudo microk8s kubectl apply -f "metallb.io_configurationstates.yaml"
Where the yaml file is
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.19.0
name: configurationstates.metallb.io
spec:
group: metallb.io
names:
kind: ConfigurationState
listKind: ConfigurationStateList
plural: configurationstates
singular: configurationstate
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.result
name: Result
type: string
- jsonPath: .status.errorSummary
name: ErrorSummary
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1beta1
schema:
openAPIV3Schema:
description: |-
ConfigurationState is a status-only CRD that reports configuration validation results from MetalLB components.
Labels:
- metallb.io/component-type: "controller" or "speaker"
- metallb.io/node-name: node name (only for speaker)
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
status:
description: ConfigurationStateStatus defines the observed state of ConfigurationState.
properties:
conditions:
description: Conditions contains the status conditions from the reconcilers
running in this component.
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
errorSummary:
description: |-
ErrorSummary contains the aggregated error messages from reconciliation failures.
This field is empty when Result is "Valid".
type: string
result:
description: Result indicates the configuration validation result.
enum:
- Valid
- Invalid
- Unknown
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}
However, the relevant pods keep bouncing due to an apparmor permission denied error
sudo microk8s kubectl -n metallb-system get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
controller-f59dc4bc7-ckhr7 0/1 Running 288 133m 10.1.78.105 wallyworld <none> <none>
speaker-r7hv4 0/1 Running 288 133m 10.0.0.17 wallyworld <none> <none>
sudo microk8s kubectl -n metallb-system describe pod/controller-f59dc4bc7-ckhr7
Name: controller-f59dc4bc7-ckhr7
Namespace: metallb-system
Priority: 0
Service Account: controller
Node: wallyworld/10.0.0.17
Start Time: Tue, 09 Jun 2026 15:28:37 +1000
Labels: app=metallb
component=controller
pod-template-hash=f59dc4bc7
Annotations: cni.projectcalico.org/containerID: 5d112472fb6b0569b0224c5bb28378f5b141ebe9574843e8267512beaae647b1
cni.projectcalico.org/podIP: 10.1.78.125/32
cni.projectcalico.org/podIPs: 10.1.78.125/32
prometheus.io/port: 7472
prometheus.io/scrape: true
Status: Running
IP: 10.1.78.125
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SandboxChanged 6m54s kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 6m53s kubelet spec.containers{controller}: Container image "quay.io/metallb/controller:v0.15.3" already present on machine and can be accessed by the pod
Normal Created 6m53s kubelet spec.containers{controller}: Container created
Normal Started 6m53s kubelet spec.containers{controller}: Container started
Normal SandboxChanged 6m40s kubelet Pod sandbox changed, it will be killed and re-created.
...
the above repeats ^^^^^
sudo dmesg -T | grep -i "apparmor.*signal.*rtmin" | tail -n 10
[Tue Jun 9 17:42:20 2026] audit: type=1400 audit(1780990874.051:330650): apparmor="DENIED" operation="signal" class="signal" profile="cri-containerd.apparmor.d" pid=3802870 comm="runc:[2:INIT]" requested_mask="send" denied_mask="send" signal=rtmin+1 peer="snap.microk8s.daemon-containerd"
[Tue Jun 9 17:42:20 2026] audit: type=1400 audit(1780990874.051:330651): apparmor="DENIED" operation="signal" class="signal" profile="cri-containerd.apparmor.d" pid=3802870 comm="runc:[2:INIT]" requested_mask="send" denied_mask="send" signal=rtmin+1 peer="snap.microk8s.daemon-containerd"
[Tue Jun 9 17:42:20 2026] audit: type=1400 audit(1780990874.051:330652): apparmor="DENIED" operation="signal" class="signal" profile="cri-containerd.apparmor.d" pid=3802870 comm="runc:[2:INIT]" requested_mask="send" denied_mask="send" signal=rtmin+1 peer="snap.microk8s.daemon-containerd"
[Tue Jun 9 17:42:20 2026] audit: type=1400 audit(1780990874.051:330653): apparmor="DENIED" operation="signal" class="signal" profile="cri-containerd.apparmor.d" pid=3802870 comm="runc:[2:INIT]" requested_mask="send" denied_mask="send" signal=rtmin+1 peer="snap.microk8s.daemon-containerd"
[Tue Jun 9 17:42:20 2026] audit: type=1400 audit(1780990874.051:330654): apparmor="DENIED" operation="signal" class="signal" profile="cri-containerd.apparmor.d" pid=3802870 comm="runc:[2:INIT]" requested_mask="send" denied_mask="send" signal=rtmin+1 peer="snap.microk8s.daemon-containerd"
[Tue Jun 9 17:42:20 2026] audit: type=1400 audit(1780990874.051:330655): apparmor="DENIED" operation="signal" class="signal" profile="cri-containerd.apparmor.d" pid=3802870 comm="runc:[2:INIT]" requested_mask="send" denied_mask="send" signal=rtmin+1 peer="snap.microk8s.daemon-containerd"
[Tue Jun 9 17:42:20 2026] audit: type=1400 audit(1780990874.051:330656): apparmor="DENIED" operation="signal" class="signal" profile="cri-containerd.apparmor.d" pid=3802870 comm="runc:[2:INIT]" requested_mask="send" denied_mask="send" signal=rtmin+1 peer="snap.microk8s.daemon-containerd"
[Tue Jun 9 17:42:20 2026] audit: type=1400 audit(1780990874.051:330657): apparmor="DENIED" operation="signal" class="signal" profile="cri-containerd.apparmor.d" pid=3802870 comm="runc:[2:INIT]" requested_mask="send" denied_mask="send" signal=rtmin+1 peer="snap.microk8s.daemon-containerd"
[Tue Jun 9 17:42:20 2026] audit: type=1400 audit(1780990874.051:330658): apparmor="DENIED" operation="signal" class="signal" profile="cri-containerd.apparmor.d" pid=3802870 comm="runc:[2:INIT]" requested_mask="send" denied_mask="send" signal=rtmin+1 peer="snap.microk8s.daemon-containerd"
[Tue Jun 9 17:42:20 2026] audit: type=1400 audit(1780990874.051:330659): apparmor="DENIED" operation="signal" class="signal" profile="cri-containerd.apparmor.d" pid=3802870 comm="runc:[2:INIT]" requested_mask="send" denied_mask="send" signal=rtmin+1 peer="snap.microk8s.daemon-containerd"
Introspection Report
inspection-report-20260609_181307.tar.gz
Can you suggest a fix?
Not sure if this is valid?
Claude clams:
- MicroK8s bundles
cri-containerd.apparmor.d rules in microk8s-resources/containerd-profile
- snapd generates
snap.microk8s.daemon-containerd from the docker-support interface
Each side contains only half of the required signal policy:
cri-containerd.apparmor.d can receive from snap.microk8s.daemon-containerd, but not send to it
snap.microk8s.daemon-containerd can send to cri-containerd.apparmor.d, but not receive from it
As a result, SIGRTMIN+1 is denied in both directions and containers never complete initialization.
Summary
To my recollection this was working a few weeks ago when I last tried it.
Maybe I'm holding it wrong? Or maybe there's a legitimate issue?
It seems there's an apparmor permission problem.
NB: the exact same steps work with the classic microk8s snap.
I installed microk8s 1.36-strict and attempted to deploy metallb
sudo microk8s kubectl apply -f "metallb.io_configurationstates.yaml"Where the yaml file is
However, the relevant pods keep bouncing due to an apparmor permission denied error
Introspection Report
inspection-report-20260609_181307.tar.gz
Can you suggest a fix?
Not sure if this is valid?
Claude clams:
cri-containerd.apparmor.drules inmicrok8s-resources/containerd-profilesnap.microk8s.daemon-containerdfrom thedocker-supportinterfaceEach side contains only half of the required signal policy:
cri-containerd.apparmor.dcan receive fromsnap.microk8s.daemon-containerd, but not send to itsnap.microk8s.daemon-containerdcan send tocri-containerd.apparmor.d, but not receive from itAs a result,
SIGRTMIN+1is denied in both directions and containers never complete initialization.