Skip to content

Can't deploy metallb - apparmor issue #5543

Description

@wallyworld

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions