diff --git a/.gitignore b/.gitignore
index daa30a3f752706bce3bc36b34a7aab39ee5484d4..e7dff43ac58af825e6105802266a6918f9c56e0c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
 README.html
+id_*
+*.pem
+*.key
\ No newline at end of file
diff --git a/Ansible/ansible.cfg b/Ansible/ansible.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..ffb9bb537e268c6ca6c4dab2f33147ec3abe547f
--- /dev/null
+++ b/Ansible/ansible.cfg
@@ -0,0 +1,7 @@
+[defaults]
+inventory = hosts.yml
+remote_user = terraform
+private_key_file = keys/id_ed25519
+host_key_checking = false
+deprecation_warnings = false
+interpreter_python = auto_silent
diff --git a/Ansible/hosts.yml b/Ansible/hosts.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ad77f8d33bc7e5efcb66dcd1d2b0a025721ffccb
--- /dev/null
+++ b/Ansible/hosts.yml
@@ -0,0 +1,11 @@
+all:
+  hosts:
+    testserver:
+      ansible_ssh_host: '<your-VM-IP>'
+
+    # aliases
+    tfserver:
+      testserver:
+
+    project-web-sso:
+      testserver:
diff --git a/Ansible/keys/README.md b/Ansible/keys/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3e6fdee4341b9ed6ff2eed7383080e2c4d07c241
--- /dev/null
+++ b/Ansible/keys/README.md
@@ -0,0 +1,2 @@
+Files dropped here with names "id_*" won't be committed -- see the top-level
+.gitignore file.
diff --git a/Ansible/playbooks/files/kind-config.yaml b/Ansible/playbooks/files/kind-config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..06b30293897715800a88bf470042eb7a3399d35a
--- /dev/null
+++ b/Ansible/playbooks/files/kind-config.yaml
@@ -0,0 +1,31 @@
+kind: Cluster
+apiVersion: kind.x-k8s.io/v1alpha4
+# patch the generated kubeadm config with some extra settings
+kubeadmConfigPatches:
+- |
+  apiVersion: kubelet.config.k8s.io/v1beta1
+  kind: KubeletConfiguration
+  evictionHard:
+    nodefs.available: "0%"
+# patch it further using a JSON 6902 patch
+kubeadmConfigPatchesJSON6902:
+- group: kubeadm.k8s.io
+  version: v1beta3
+  kind: ClusterConfiguration
+  patch: |
+    - op: add
+      path: /apiServer/certSANs/-
+      value: my-hostname
+
+# Comment in this to fix the API port so that you can use kubectl via an SSH
+# tunnel started from the management machine with:
+#   $ ssh -L 6443:localhost:6443 <remote-kube-host>
+# References: <https://github.com/kubernetes-sigs/kind/issues/3417#issuecomment-1806231832>
+networking:
+  apiServerPort: 6443
+
+nodes:
+- role: control-plane
+- role: worker
+  labels:
+    application: hepia-bachelor-web-sso
diff --git a/Ansible/playbooks/files/lb-deployment.yaml b/Ansible/playbooks/files/lb-deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a05ea071992ce109bafc0c3417e8076158eef5f1
--- /dev/null
+++ b/Ansible/playbooks/files/lb-deployment.yaml
@@ -0,0 +1,41 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: http-echo
+spec:
+  replicas: 2
+  selector:
+    matchLabels:
+      app: http-echo
+  template:
+    metadata:
+      labels:
+        app: http-echo
+    spec:
+      nodeSelector:
+        kubernetes.io/hostname: kind-worker  # Schedule pods on one worker node
+      containers:
+      - name: http-echo
+        image: hashicorp/http-echo
+        args:
+        - >-
+          -text=Hello from Kubernetes! My IP is $(POD_IP)
+        env:
+        - name: POD_IP
+          valueFrom:
+            fieldRef:
+              fieldPath: status.podIP
+        ports:
+        - containerPort: 5678
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: loadbalancer
+spec:
+  type: LoadBalancer
+  selector:
+    app: http-echo
+  ports:
+  - port: 80
+    targetPort: 5678
diff --git a/Ansible/playbooks/files/metallb-native.yaml b/Ansible/playbooks/files/metallb-native.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..de6ca2118e31793c516e01d7999d0baf4b708fae
--- /dev/null
+++ b/Ansible/playbooks/files/metallb-native.yaml
@@ -0,0 +1,2042 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  labels:
+    pod-security.kubernetes.io/audit: privileged
+    pod-security.kubernetes.io/enforce: privileged
+    pod-security.kubernetes.io/warn: privileged
+  name: metallb-system
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: addresspools.metallb.io
+spec:
+  conversion:
+    strategy: Webhook
+    webhook:
+      clientConfig:
+        caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlGWlRDQ0EwMmdBd0lCQWdJVU5GRW1XcTM3MVpKdGkrMmlSQzk1WmpBV1MxZ3dEUVlKS29aSWh2Y05BUUVMDQpCUUF3UWpFTE1Ba0dBMVVFQmhNQ1dGZ3hGVEFUQmdOVkJBY01ERVJsWm1GMWJIUWdRMmwwZVRFY01Cb0dBMVVFDQpDZ3dUUkdWbVlYVnNkQ0JEYjIxd1lXNTVJRXgwWkRBZUZ3MHlNakEzTVRrd09UTXlNek5hRncweU1qQTRNVGd3DQpPVE15TXpOYU1FSXhDekFKQmdOVkJBWVRBbGhZTVJVd0V3WURWUVFIREF4RVpXWmhkV3gwSUVOcGRIa3hIREFhDQpCZ05WQkFvTUUwUmxabUYxYkhRZ1EyOXRjR0Z1ZVNCTWRHUXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDDQpEd0F3Z2dJS0FvSUNBUUNxVFpxMWZRcC9vYkdlenhES0o3OVB3Ny94azJwellualNzMlkzb1ZYSm5sRmM4YjVlDQpma2ZZQnY2bndscW1keW5PL2phWFBaQmRQSS82aFdOUDBkdVhadEtWU0NCUUpyZzEyOGNXb3F0MGNTN3pLb1VpDQpvcU1tQ0QvRXVBeFFNZjhRZDF2c1gvVllkZ0poVTZBRXJLZEpIaXpFOUJtUkNkTDBGMW1OVW55Rk82UnRtWFZUDQpidkxsTDVYeTc2R0FaQVBLOFB4aVlDa0NtbDdxN0VnTWNiOXlLWldCYmlxQ3VkTXE5TGJLNmdKNzF6YkZnSXV4DQo1L1pXK2JraTB2RlplWk9ZODUxb1psckFUNzJvMDI4NHNTWW9uN0pHZVZkY3NoUnh5R1VpSFpSTzdkaXZVTDVTDQpmM2JmSDFYbWY1ZDQzT0NWTWRuUUV2NWVaOG8zeWVLa3ZrbkZQUGVJMU9BbjdGbDlFRVNNR2dhOGFaSG1URSttDQpsLzlMSmdDYjBnQmtPT0M0WnV4bWh2aERKV1EzWnJCS3pMQlNUZXN0NWlLNVlwcXRWVVk2THRyRW9FelVTK1lsDQpwWndXY2VQWHlHeHM5ZURsR3lNVmQraW15Y3NTU1UvVno2Mmx6MnZCS21NTXBkYldDQWhud0RsRTVqU2dyMjRRDQp0eGNXLys2N3d5KzhuQlI3UXdqVTFITndVRjBzeERWdEwrZ1NHVERnSEVZSlhZelYvT05zMy94TkpoVFNPSkxNDQpoeXNVdyttaGdackdhbUdXcHVIVU1DUitvTWJzMTc1UkcrQjJnUFFHVytPTjJnUTRyOXN2b0ZBNHBBQm8xd1dLDQpRYjRhY3pmeVVscElBOVFoSmFsZEY3S3dPSHVlV3gwRUNrNXg0T2tvVDBvWVp0dzFiR0JjRGtaSmF3SURBUUFCDQpvMU13VVRBZEJnTlZIUTRFRmdRVW90UlNIUm9IWTEyRFZ4R0NCdEhpb1g2ZmVFQXdId1lEVlIwakJCZ3dGb0FVDQpvdFJTSFJvSFkxMkRWeEdDQnRIaW9YNmZlRUF3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCDQpBUXNGQUFPQ0FnRUFSbkpsWWRjMTFHd0VxWnh6RDF2R3BDR2pDN2VWTlQ3aVY1d3IybXlybHdPYi9aUWFEa0xYDQpvVStaOVVXT1VlSXJTdzUydDdmQUpvVVAwSm5iYkMveVIrU1lqUGhvUXNiVHduOTc2ZldBWTduM3FMOXhCd1Y0DQphek41OXNjeUp0dlhMeUtOL2N5ak1ReDRLajBIMFg0bWJ6bzVZNUtzWWtYVU0vOEFPdWZMcEd0S1NGVGgrSEFDDQpab1Q5YnZHS25adnNHd0tYZFF0Wnh0akhaUjVqK3U3ZGtQOTJBT051RFNabS8rWVV4b2tBK09JbzdSR3BwSHNXDQo1ZTdNY0FTVXRtb1FORXd6dVFoVkJaRWQ1OGtKYjUrV0VWbGNzanlXNnRTbzErZ25tTWNqR1BsMWgxR2hVbjV4DQpFY0lWRnBIWXM5YWo1NmpBSjk1MVQvZjhMaWxmTlVnanBLQ0c1bnl0SUt3emxhOHNtdGlPdm1UNEpYbXBwSkI2DQo4bmdHRVluVjUrUTYwWFJ2OEhSSGp1VG9CRHVhaERrVDA2R1JGODU1d09FR2V4bkZpMXZYWUxLVllWb1V2MXRKDQo4dVdUR1pwNllDSVJldlBqbzg5ZytWTlJSaVFYUThJd0dybXE5c0RoVTlqTjA0SjdVL1RvRDFpNHE3VnlsRUc5DQorV1VGNkNLaEdBeTJIaEhwVncyTGFoOS9lUzdZMUZ1YURrWmhPZG1laG1BOCtqdHNZamJadnR5Mm1SWlF0UUZzDQpUU1VUUjREbUR2bVVPRVRmeStpRHdzK2RkWXVNTnJGeVVYV2dkMnpBQU4ydVl1UHFGY2pRcFNPODFzVTJTU3R3DQoxVzAyeUtYOGJEYmZFdjBzbUh3UzliQnFlSGo5NEM1Mjg0YXpsdTBmaUdpTm1OUEM4ckJLRmhBPQ0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==
+        service:
+          name: webhook-service
+          namespace: metallb-system
+          path: /convert
+      conversionReviewVersions:
+      - v1alpha1
+      - v1beta1
+  group: metallb.io
+  names:
+    kind: AddressPool
+    listKind: AddressPoolList
+    plural: addresspools
+    singular: addresspool
+  scope: Namespaced
+  versions:
+  - deprecated: true
+    deprecationWarning: metallb.io v1alpha1 AddressPool is deprecated
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: AddressPool is the Schema for the addresspools API.
+        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
+          spec:
+            description: AddressPoolSpec defines the desired state of AddressPool.
+            properties:
+              addresses:
+                description: A list of IP address ranges over which MetalLB has authority.
+                  You can list multiple ranges in a single pool, they will all share
+                  the same settings. Each range can be either a CIDR prefix, or an
+                  explicit start-end range of IPs.
+                items:
+                  type: string
+                type: array
+              autoAssign:
+                default: true
+                description: AutoAssign flag used to prevent MetallB from automatic
+                  allocation for a pool.
+                type: boolean
+              bgpAdvertisements:
+                description: When an IP is allocated from this pool, how should it
+                  be translated into BGP announcements?
+                items:
+                  properties:
+                    aggregationLength:
+                      default: 32
+                      description: The aggregation-length advertisement option lets
+                        you “roll up” the /32s into a larger prefix.
+                      format: int32
+                      minimum: 1
+                      type: integer
+                    aggregationLengthV6:
+                      default: 128
+                      description: Optional, defaults to 128 (i.e. no aggregation)
+                        if not specified.
+                      format: int32
+                      type: integer
+                    communities:
+                      description: BGP communities
+                      items:
+                        type: string
+                      type: array
+                    localPref:
+                      description: BGP LOCAL_PREF attribute which is used by BGP best
+                        path algorithm, Path with higher localpref is preferred over
+                        one with lower localpref.
+                      format: int32
+                      type: integer
+                  type: object
+                type: array
+              protocol:
+                description: Protocol can be used to select how the announcement is
+                  done.
+                enum:
+                - layer2
+                - bgp
+                type: string
+            required:
+            - addresses
+            - protocol
+            type: object
+          status:
+            description: AddressPoolStatus defines the observed state of AddressPool.
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: false
+    subresources:
+      status: {}
+  - deprecated: true
+    deprecationWarning: metallb.io v1beta1 AddressPool is deprecated, consider using
+      IPAddressPool
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: AddressPool represents a pool of IP addresses that can be allocated
+          to LoadBalancer services. AddressPool is deprecated and being replaced by
+          IPAddressPool.
+        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
+          spec:
+            description: AddressPoolSpec defines the desired state of AddressPool.
+            properties:
+              addresses:
+                description: A list of IP address ranges over which MetalLB has authority.
+                  You can list multiple ranges in a single pool, they will all share
+                  the same settings. Each range can be either a CIDR prefix, or an
+                  explicit start-end range of IPs.
+                items:
+                  type: string
+                type: array
+              autoAssign:
+                default: true
+                description: AutoAssign flag used to prevent MetallB from automatic
+                  allocation for a pool.
+                type: boolean
+              bgpAdvertisements:
+                description: Drives how an IP allocated from this pool should translated
+                  into BGP announcements.
+                items:
+                  properties:
+                    aggregationLength:
+                      default: 32
+                      description: The aggregation-length advertisement option lets
+                        you “roll up” the /32s into a larger prefix.
+                      format: int32
+                      minimum: 1
+                      type: integer
+                    aggregationLengthV6:
+                      default: 128
+                      description: Optional, defaults to 128 (i.e. no aggregation)
+                        if not specified.
+                      format: int32
+                      type: integer
+                    communities:
+                      description: BGP communities to be associated with the given
+                        advertisement.
+                      items:
+                        type: string
+                      type: array
+                    localPref:
+                      description: BGP LOCAL_PREF attribute which is used by BGP best
+                        path algorithm, Path with higher localpref is preferred over
+                        one with lower localpref.
+                      format: int32
+                      type: integer
+                  type: object
+                type: array
+              protocol:
+                description: Protocol can be used to select how the announcement is
+                  done.
+                enum:
+                - layer2
+                - bgp
+                type: string
+            required:
+            - addresses
+            - protocol
+            type: object
+          status:
+            description: AddressPoolStatus defines the observed state of AddressPool.
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: bfdprofiles.metallb.io
+spec:
+  group: metallb.io
+  names:
+    kind: BFDProfile
+    listKind: BFDProfileList
+    plural: bfdprofiles
+    singular: bfdprofile
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.passiveMode
+      name: Passive Mode
+      type: boolean
+    - jsonPath: .spec.transmitInterval
+      name: Transmit Interval
+      type: integer
+    - jsonPath: .spec.receiveInterval
+      name: Receive Interval
+      type: integer
+    - jsonPath: .spec.detectMultiplier
+      name: Multiplier
+      type: integer
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: BFDProfile represents the settings of the bfd session that can
+          be optionally associated with a BGP session.
+        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
+          spec:
+            description: BFDProfileSpec defines the desired state of BFDProfile.
+            properties:
+              detectMultiplier:
+                description: Configures the detection multiplier to determine packet
+                  loss. The remote transmission interval will be multiplied by this
+                  value to determine the connection loss detection timer.
+                format: int32
+                maximum: 255
+                minimum: 2
+                type: integer
+              echoInterval:
+                description: Configures the minimal echo receive transmission interval
+                  that this system is capable of handling in milliseconds. Defaults
+                  to 50ms
+                format: int32
+                maximum: 60000
+                minimum: 10
+                type: integer
+              echoMode:
+                description: Enables or disables the echo transmission mode. This
+                  mode is disabled by default, and not supported on multi hops setups.
+                type: boolean
+              minimumTtl:
+                description: 'For multi hop sessions only: configure the minimum expected
+                  TTL for an incoming BFD control packet.'
+                format: int32
+                maximum: 254
+                minimum: 1
+                type: integer
+              passiveMode:
+                description: 'Mark session as passive: a passive session will not
+                  attempt to start the connection and will wait for control packets
+                  from peer before it begins replying.'
+                type: boolean
+              receiveInterval:
+                description: The minimum interval that this system is capable of receiving
+                  control packets in milliseconds. Defaults to 300ms.
+                format: int32
+                maximum: 60000
+                minimum: 10
+                type: integer
+              transmitInterval:
+                description: The minimum transmission interval (less jitter) that
+                  this system wants to use to send BFD control packets in milliseconds.
+                  Defaults to 300ms
+                format: int32
+                maximum: 60000
+                minimum: 10
+                type: integer
+            type: object
+          status:
+            description: BFDProfileStatus defines the observed state of BFDProfile.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: bgpadvertisements.metallb.io
+spec:
+  group: metallb.io
+  names:
+    kind: BGPAdvertisement
+    listKind: BGPAdvertisementList
+    plural: bgpadvertisements
+    singular: bgpadvertisement
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.ipAddressPools
+      name: IPAddressPools
+      type: string
+    - jsonPath: .spec.ipAddressPoolSelectors
+      name: IPAddressPool Selectors
+      type: string
+    - jsonPath: .spec.peers
+      name: Peers
+      type: string
+    - jsonPath: .spec.nodeSelectors
+      name: Node Selectors
+      priority: 10
+      type: string
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: BGPAdvertisement allows to advertise the IPs coming from the
+          selected IPAddressPools via BGP, setting the parameters of the BGP Advertisement.
+        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
+          spec:
+            description: BGPAdvertisementSpec defines the desired state of BGPAdvertisement.
+            properties:
+              aggregationLength:
+                default: 32
+                description: The aggregation-length advertisement option lets you
+                  “roll up” the /32s into a larger prefix. Defaults to 32. Works for
+                  IPv4 addresses.
+                format: int32
+                minimum: 1
+                type: integer
+              aggregationLengthV6:
+                default: 128
+                description: The aggregation-length advertisement option lets you
+                  “roll up” the /128s into a larger prefix. Defaults to 128. Works
+                  for IPv6 addresses.
+                format: int32
+                type: integer
+              communities:
+                description: The BGP communities to be associated with the announcement.
+                  Each item can be a standard community of the form 1234:1234, a large
+                  community of the form large:1234:1234:1234 or the name of an alias
+                  defined in the Community CRD.
+                items:
+                  type: string
+                type: array
+              ipAddressPoolSelectors:
+                description: A selector for the IPAddressPools which would get advertised
+                  via this advertisement. If no IPAddressPool is selected by this
+                  or by the list, the advertisement is applied to all the IPAddressPools.
+                items:
+                  description: A label selector is a label query over a set of resources.
+                    The result of matchLabels and matchExpressions are ANDed. An empty
+                    label selector matches all objects. A null label selector matches
+                    no objects.
+                  properties:
+                    matchExpressions:
+                      description: matchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: A label selector requirement is a selector that
+                          contains values, a key, and an operator that relates the
+                          key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: operator represents a key's relationship
+                              to a set of values. Valid operators are In, NotIn, Exists
+                              and DoesNotExist.
+                            type: string
+                          values:
+                            description: values is an array of string values. If the
+                              operator is In or NotIn, the values array must be non-empty.
+                              If the operator is Exists or DoesNotExist, the values
+                              array must be empty. This array is replaced during a
+                              strategic merge patch.
+                            items:
+                              type: string
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      description: matchLabels is a map of {key,value} pairs. A single
+                        {key,value} in the matchLabels map is equivalent to an element
+                        of matchExpressions, whose key field is "key", the operator
+                        is "In", and the values array contains only "value". The requirements
+                        are ANDed.
+                      type: object
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+              ipAddressPools:
+                description: The list of IPAddressPools to advertise via this advertisement,
+                  selected by name.
+                items:
+                  type: string
+                type: array
+              localPref:
+                description: The BGP LOCAL_PREF attribute which is used by BGP best
+                  path algorithm, Path with higher localpref is preferred over one
+                  with lower localpref.
+                format: int32
+                type: integer
+              nodeSelectors:
+                description: NodeSelectors allows to limit the nodes to announce as
+                  next hops for the LoadBalancer IP. When empty, all the nodes having  are
+                  announced as next hops.
+                items:
+                  description: A label selector is a label query over a set of resources.
+                    The result of matchLabels and matchExpressions are ANDed. An empty
+                    label selector matches all objects. A null label selector matches
+                    no objects.
+                  properties:
+                    matchExpressions:
+                      description: matchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: A label selector requirement is a selector that
+                          contains values, a key, and an operator that relates the
+                          key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: operator represents a key's relationship
+                              to a set of values. Valid operators are In, NotIn, Exists
+                              and DoesNotExist.
+                            type: string
+                          values:
+                            description: values is an array of string values. If the
+                              operator is In or NotIn, the values array must be non-empty.
+                              If the operator is Exists or DoesNotExist, the values
+                              array must be empty. This array is replaced during a
+                              strategic merge patch.
+                            items:
+                              type: string
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      description: matchLabels is a map of {key,value} pairs. A single
+                        {key,value} in the matchLabels map is equivalent to an element
+                        of matchExpressions, whose key field is "key", the operator
+                        is "In", and the values array contains only "value". The requirements
+                        are ANDed.
+                      type: object
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+              peers:
+                description: Peers limits the bgppeer to advertise the ips of the
+                  selected pools to. When empty, the loadbalancer IP is announced
+                  to all the BGPPeers configured.
+                items:
+                  type: string
+                type: array
+            type: object
+          status:
+            description: BGPAdvertisementStatus defines the observed state of BGPAdvertisement.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: bgppeers.metallb.io
+spec:
+  conversion:
+    strategy: Webhook
+    webhook:
+      clientConfig:
+        caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlGWlRDQ0EwMmdBd0lCQWdJVU5GRW1XcTM3MVpKdGkrMmlSQzk1WmpBV1MxZ3dEUVlKS29aSWh2Y05BUUVMDQpCUUF3UWpFTE1Ba0dBMVVFQmhNQ1dGZ3hGVEFUQmdOVkJBY01ERVJsWm1GMWJIUWdRMmwwZVRFY01Cb0dBMVVFDQpDZ3dUUkdWbVlYVnNkQ0JEYjIxd1lXNTVJRXgwWkRBZUZ3MHlNakEzTVRrd09UTXlNek5hRncweU1qQTRNVGd3DQpPVE15TXpOYU1FSXhDekFKQmdOVkJBWVRBbGhZTVJVd0V3WURWUVFIREF4RVpXWmhkV3gwSUVOcGRIa3hIREFhDQpCZ05WQkFvTUUwUmxabUYxYkhRZ1EyOXRjR0Z1ZVNCTWRHUXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDDQpEd0F3Z2dJS0FvSUNBUUNxVFpxMWZRcC9vYkdlenhES0o3OVB3Ny94azJwellualNzMlkzb1ZYSm5sRmM4YjVlDQpma2ZZQnY2bndscW1keW5PL2phWFBaQmRQSS82aFdOUDBkdVhadEtWU0NCUUpyZzEyOGNXb3F0MGNTN3pLb1VpDQpvcU1tQ0QvRXVBeFFNZjhRZDF2c1gvVllkZ0poVTZBRXJLZEpIaXpFOUJtUkNkTDBGMW1OVW55Rk82UnRtWFZUDQpidkxsTDVYeTc2R0FaQVBLOFB4aVlDa0NtbDdxN0VnTWNiOXlLWldCYmlxQ3VkTXE5TGJLNmdKNzF6YkZnSXV4DQo1L1pXK2JraTB2RlplWk9ZODUxb1psckFUNzJvMDI4NHNTWW9uN0pHZVZkY3NoUnh5R1VpSFpSTzdkaXZVTDVTDQpmM2JmSDFYbWY1ZDQzT0NWTWRuUUV2NWVaOG8zeWVLa3ZrbkZQUGVJMU9BbjdGbDlFRVNNR2dhOGFaSG1URSttDQpsLzlMSmdDYjBnQmtPT0M0WnV4bWh2aERKV1EzWnJCS3pMQlNUZXN0NWlLNVlwcXRWVVk2THRyRW9FelVTK1lsDQpwWndXY2VQWHlHeHM5ZURsR3lNVmQraW15Y3NTU1UvVno2Mmx6MnZCS21NTXBkYldDQWhud0RsRTVqU2dyMjRRDQp0eGNXLys2N3d5KzhuQlI3UXdqVTFITndVRjBzeERWdEwrZ1NHVERnSEVZSlhZelYvT05zMy94TkpoVFNPSkxNDQpoeXNVdyttaGdackdhbUdXcHVIVU1DUitvTWJzMTc1UkcrQjJnUFFHVytPTjJnUTRyOXN2b0ZBNHBBQm8xd1dLDQpRYjRhY3pmeVVscElBOVFoSmFsZEY3S3dPSHVlV3gwRUNrNXg0T2tvVDBvWVp0dzFiR0JjRGtaSmF3SURBUUFCDQpvMU13VVRBZEJnTlZIUTRFRmdRVW90UlNIUm9IWTEyRFZ4R0NCdEhpb1g2ZmVFQXdId1lEVlIwakJCZ3dGb0FVDQpvdFJTSFJvSFkxMkRWeEdDQnRIaW9YNmZlRUF3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCDQpBUXNGQUFPQ0FnRUFSbkpsWWRjMTFHd0VxWnh6RDF2R3BDR2pDN2VWTlQ3aVY1d3IybXlybHdPYi9aUWFEa0xYDQpvVStaOVVXT1VlSXJTdzUydDdmQUpvVVAwSm5iYkMveVIrU1lqUGhvUXNiVHduOTc2ZldBWTduM3FMOXhCd1Y0DQphek41OXNjeUp0dlhMeUtOL2N5ak1ReDRLajBIMFg0bWJ6bzVZNUtzWWtYVU0vOEFPdWZMcEd0S1NGVGgrSEFDDQpab1Q5YnZHS25adnNHd0tYZFF0Wnh0akhaUjVqK3U3ZGtQOTJBT051RFNabS8rWVV4b2tBK09JbzdSR3BwSHNXDQo1ZTdNY0FTVXRtb1FORXd6dVFoVkJaRWQ1OGtKYjUrV0VWbGNzanlXNnRTbzErZ25tTWNqR1BsMWgxR2hVbjV4DQpFY0lWRnBIWXM5YWo1NmpBSjk1MVQvZjhMaWxmTlVnanBLQ0c1bnl0SUt3emxhOHNtdGlPdm1UNEpYbXBwSkI2DQo4bmdHRVluVjUrUTYwWFJ2OEhSSGp1VG9CRHVhaERrVDA2R1JGODU1d09FR2V4bkZpMXZYWUxLVllWb1V2MXRKDQo4dVdUR1pwNllDSVJldlBqbzg5ZytWTlJSaVFYUThJd0dybXE5c0RoVTlqTjA0SjdVL1RvRDFpNHE3VnlsRUc5DQorV1VGNkNLaEdBeTJIaEhwVncyTGFoOS9lUzdZMUZ1YURrWmhPZG1laG1BOCtqdHNZamJadnR5Mm1SWlF0UUZzDQpUU1VUUjREbUR2bVVPRVRmeStpRHdzK2RkWXVNTnJGeVVYV2dkMnpBQU4ydVl1UHFGY2pRcFNPODFzVTJTU3R3DQoxVzAyeUtYOGJEYmZFdjBzbUh3UzliQnFlSGo5NEM1Mjg0YXpsdTBmaUdpTm1OUEM4ckJLRmhBPQ0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==
+        service:
+          name: webhook-service
+          namespace: metallb-system
+          path: /convert
+      conversionReviewVersions:
+      - v1beta1
+      - v1beta2
+  group: metallb.io
+  names:
+    kind: BGPPeer
+    listKind: BGPPeerList
+    plural: bgppeers
+    singular: bgppeer
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.peerAddress
+      name: Address
+      type: string
+    - jsonPath: .spec.peerASN
+      name: ASN
+      type: string
+    - jsonPath: .spec.bfdProfile
+      name: BFD Profile
+      type: string
+    - jsonPath: .spec.ebgpMultiHop
+      name: Multi Hops
+      type: string
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: BGPPeer is the Schema for the peers API.
+        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
+          spec:
+            description: BGPPeerSpec defines the desired state of Peer.
+            properties:
+              bfdProfile:
+                type: string
+              ebgpMultiHop:
+                description: EBGP peer is multi-hops away
+                type: boolean
+              holdTime:
+                description: Requested BGP hold time, per RFC4271.
+                type: string
+              keepaliveTime:
+                description: Requested BGP keepalive time, per RFC4271.
+                type: string
+              myASN:
+                description: AS number to use for the local end of the session.
+                format: int32
+                maximum: 4294967295
+                minimum: 0
+                type: integer
+              nodeSelectors:
+                description: Only connect to this peer on nodes that match one of
+                  these selectors.
+                items:
+                  properties:
+                    matchExpressions:
+                      items:
+                        properties:
+                          key:
+                            type: string
+                          operator:
+                            type: string
+                          values:
+                            items:
+                              type: string
+                            minItems: 1
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        - values
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      type: object
+                  type: object
+                type: array
+              password:
+                description: Authentication password for routers enforcing TCP MD5
+                  authenticated sessions
+                type: string
+              peerASN:
+                description: AS number to expect from the remote end of the session.
+                format: int32
+                maximum: 4294967295
+                minimum: 0
+                type: integer
+              peerAddress:
+                description: Address to dial when establishing the session.
+                type: string
+              peerPort:
+                description: Port to dial when establishing the session.
+                maximum: 16384
+                minimum: 0
+                type: integer
+              routerID:
+                description: BGP router ID to advertise to the peer
+                type: string
+              sourceAddress:
+                description: Source address to use when establishing the session.
+                type: string
+            required:
+            - myASN
+            - peerASN
+            - peerAddress
+            type: object
+          status:
+            description: BGPPeerStatus defines the observed state of Peer.
+            type: object
+        type: object
+    served: true
+    storage: false
+    subresources:
+      status: {}
+  - additionalPrinterColumns:
+    - jsonPath: .spec.peerAddress
+      name: Address
+      type: string
+    - jsonPath: .spec.peerASN
+      name: ASN
+      type: string
+    - jsonPath: .spec.bfdProfile
+      name: BFD Profile
+      type: string
+    - jsonPath: .spec.ebgpMultiHop
+      name: Multi Hops
+      type: string
+    name: v1beta2
+    schema:
+      openAPIV3Schema:
+        description: BGPPeer is the Schema for the peers API.
+        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
+          spec:
+            description: BGPPeerSpec defines the desired state of Peer.
+            properties:
+              bfdProfile:
+                description: The name of the BFD Profile to be used for the BFD session
+                  associated to the BGP session. If not set, the BFD session won't
+                  be set up.
+                type: string
+              ebgpMultiHop:
+                description: To set if the BGPPeer is multi-hops away. Needed for
+                  FRR mode only.
+                type: boolean
+              holdTime:
+                description: Requested BGP hold time, per RFC4271.
+                type: string
+              keepaliveTime:
+                description: Requested BGP keepalive time, per RFC4271.
+                type: string
+              myASN:
+                description: AS number to use for the local end of the session.
+                format: int32
+                maximum: 4294967295
+                minimum: 0
+                type: integer
+              nodeSelectors:
+                description: Only connect to this peer on nodes that match one of
+                  these selectors.
+                items:
+                  description: A label selector is a label query over a set of resources.
+                    The result of matchLabels and matchExpressions are ANDed. An empty
+                    label selector matches all objects. A null label selector matches
+                    no objects.
+                  properties:
+                    matchExpressions:
+                      description: matchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: A label selector requirement is a selector that
+                          contains values, a key, and an operator that relates the
+                          key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: operator represents a key's relationship
+                              to a set of values. Valid operators are In, NotIn, Exists
+                              and DoesNotExist.
+                            type: string
+                          values:
+                            description: values is an array of string values. If the
+                              operator is In or NotIn, the values array must be non-empty.
+                              If the operator is Exists or DoesNotExist, the values
+                              array must be empty. This array is replaced during a
+                              strategic merge patch.
+                            items:
+                              type: string
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      description: matchLabels is a map of {key,value} pairs. A single
+                        {key,value} in the matchLabels map is equivalent to an element
+                        of matchExpressions, whose key field is "key", the operator
+                        is "In", and the values array contains only "value". The requirements
+                        are ANDed.
+                      type: object
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+              password:
+                description: Authentication password for routers enforcing TCP MD5
+                  authenticated sessions
+                type: string
+              passwordSecret:
+                description: passwordSecret is name of the authentication secret for
+                  BGP Peer. the secret must be of type "kubernetes.io/basic-auth",
+                  and created in the same namespace as the MetalLB deployment. The
+                  password is stored in the secret as the key "password".
+                properties:
+                  name:
+                    description: name is unique within a namespace to reference a
+                      secret resource.
+                    type: string
+                  namespace:
+                    description: namespace defines the space within which the secret
+                      name must be unique.
+                    type: string
+                type: object
+                x-kubernetes-map-type: atomic
+              peerASN:
+                description: AS number to expect from the remote end of the session.
+                format: int32
+                maximum: 4294967295
+                minimum: 0
+                type: integer
+              peerAddress:
+                description: Address to dial when establishing the session.
+                type: string
+              peerPort:
+                default: 179
+                description: Port to dial when establishing the session.
+                maximum: 16384
+                minimum: 0
+                type: integer
+              routerID:
+                description: BGP router ID to advertise to the peer
+                type: string
+              sourceAddress:
+                description: Source address to use when establishing the session.
+                type: string
+              vrf:
+                description: To set if we want to peer with the BGPPeer using an interface
+                  belonging to a host vrf
+                type: string
+            required:
+            - myASN
+            - peerASN
+            - peerAddress
+            type: object
+          status:
+            description: BGPPeerStatus defines the observed state of Peer.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: communities.metallb.io
+spec:
+  group: metallb.io
+  names:
+    kind: Community
+    listKind: CommunityList
+    plural: communities
+    singular: community
+  scope: Namespaced
+  versions:
+  - name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: Community is a collection of aliases for communities. Users can
+          define named aliases to be used in the BGPPeer CRD.
+        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
+          spec:
+            description: CommunitySpec defines the desired state of Community.
+            properties:
+              communities:
+                items:
+                  properties:
+                    name:
+                      description: The name of the alias for the community.
+                      type: string
+                    value:
+                      description: The BGP community value corresponding to the given
+                        name. Can be a standard community of the form 1234:1234 or
+                        a large community of the form large:1234:1234:1234.
+                      type: string
+                  type: object
+                type: array
+            type: object
+          status:
+            description: CommunityStatus defines the observed state of Community.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: ipaddresspools.metallb.io
+spec:
+  group: metallb.io
+  names:
+    kind: IPAddressPool
+    listKind: IPAddressPoolList
+    plural: ipaddresspools
+    singular: ipaddresspool
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.autoAssign
+      name: Auto Assign
+      type: boolean
+    - jsonPath: .spec.avoidBuggyIPs
+      name: Avoid Buggy IPs
+      type: boolean
+    - jsonPath: .spec.addresses
+      name: Addresses
+      type: string
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: IPAddressPool represents a pool of IP addresses that can be allocated
+          to LoadBalancer services.
+        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
+          spec:
+            description: IPAddressPoolSpec defines the desired state of IPAddressPool.
+            properties:
+              addresses:
+                description: A list of IP address ranges over which MetalLB has authority.
+                  You can list multiple ranges in a single pool, they will all share
+                  the same settings. Each range can be either a CIDR prefix, or an
+                  explicit start-end range of IPs.
+                items:
+                  type: string
+                type: array
+              autoAssign:
+                default: true
+                description: AutoAssign flag used to prevent MetallB from automatic
+                  allocation for a pool.
+                type: boolean
+              avoidBuggyIPs:
+                default: false
+                description: AvoidBuggyIPs prevents addresses ending with .0 and .255
+                  to be used by a pool.
+                type: boolean
+              serviceAllocation:
+                description: AllocateTo makes ip pool allocation to specific namespace
+                  and/or service. The controller will use the pool with lowest value
+                  of priority in case of multiple matches. A pool with no priority
+                  set will be used only if the pools with priority can't be used.
+                  If multiple matching IPAddressPools are available it will check
+                  for the availability of IPs sorting the matching IPAddressPools
+                  by priority, starting from the highest to the lowest. If multiple
+                  IPAddressPools have the same priority, choice will be random.
+                properties:
+                  namespaceSelectors:
+                    description: NamespaceSelectors list of label selectors to select
+                      namespace(s) for ip pool, an alternative to using namespace
+                      list.
+                    items:
+                      description: A label selector is a label query over a set of
+                        resources. The result of matchLabels and matchExpressions
+                        are ANDed. An empty label selector matches all objects. A
+                        null label selector matches no objects.
+                      properties:
+                        matchExpressions:
+                          description: matchExpressions is a list of label selector
+                            requirements. The requirements are ANDed.
+                          items:
+                            description: A label selector requirement is a selector
+                              that contains values, a key, and an operator that relates
+                              the key and values.
+                            properties:
+                              key:
+                                description: key is the label key that the selector
+                                  applies to.
+                                type: string
+                              operator:
+                                description: operator represents a key's relationship
+                                  to a set of values. Valid operators are In, NotIn,
+                                  Exists and DoesNotExist.
+                                type: string
+                              values:
+                                description: values is an array of string values.
+                                  If the operator is In or NotIn, the values array
+                                  must be non-empty. If the operator is Exists or
+                                  DoesNotExist, the values array must be empty. This
+                                  array is replaced during a strategic merge patch.
+                                items:
+                                  type: string
+                                type: array
+                            required:
+                            - key
+                            - operator
+                            type: object
+                          type: array
+                        matchLabels:
+                          additionalProperties:
+                            type: string
+                          description: matchLabels is a map of {key,value} pairs.
+                            A single {key,value} in the matchLabels map is equivalent
+                            to an element of matchExpressions, whose key field is
+                            "key", the operator is "In", and the values array contains
+                            only "value". The requirements are ANDed.
+                          type: object
+                      type: object
+                      x-kubernetes-map-type: atomic
+                    type: array
+                  namespaces:
+                    description: Namespaces list of namespace(s) on which ip pool
+                      can be attached.
+                    items:
+                      type: string
+                    type: array
+                  priority:
+                    description: Priority priority given for ip pool while ip allocation
+                      on a service.
+                    type: integer
+                  serviceSelectors:
+                    description: ServiceSelectors list of label selector to select
+                      service(s) for which ip pool can be used for ip allocation.
+                    items:
+                      description: A label selector is a label query over a set of
+                        resources. The result of matchLabels and matchExpressions
+                        are ANDed. An empty label selector matches all objects. A
+                        null label selector matches no objects.
+                      properties:
+                        matchExpressions:
+                          description: matchExpressions is a list of label selector
+                            requirements. The requirements are ANDed.
+                          items:
+                            description: A label selector requirement is a selector
+                              that contains values, a key, and an operator that relates
+                              the key and values.
+                            properties:
+                              key:
+                                description: key is the label key that the selector
+                                  applies to.
+                                type: string
+                              operator:
+                                description: operator represents a key's relationship
+                                  to a set of values. Valid operators are In, NotIn,
+                                  Exists and DoesNotExist.
+                                type: string
+                              values:
+                                description: values is an array of string values.
+                                  If the operator is In or NotIn, the values array
+                                  must be non-empty. If the operator is Exists or
+                                  DoesNotExist, the values array must be empty. This
+                                  array is replaced during a strategic merge patch.
+                                items:
+                                  type: string
+                                type: array
+                            required:
+                            - key
+                            - operator
+                            type: object
+                          type: array
+                        matchLabels:
+                          additionalProperties:
+                            type: string
+                          description: matchLabels is a map of {key,value} pairs.
+                            A single {key,value} in the matchLabels map is equivalent
+                            to an element of matchExpressions, whose key field is
+                            "key", the operator is "In", and the values array contains
+                            only "value". The requirements are ANDed.
+                          type: object
+                      type: object
+                      x-kubernetes-map-type: atomic
+                    type: array
+                type: object
+            required:
+            - addresses
+            type: object
+          status:
+            description: IPAddressPoolStatus defines the observed state of IPAddressPool.
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: l2advertisements.metallb.io
+spec:
+  group: metallb.io
+  names:
+    kind: L2Advertisement
+    listKind: L2AdvertisementList
+    plural: l2advertisements
+    singular: l2advertisement
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.ipAddressPools
+      name: IPAddressPools
+      type: string
+    - jsonPath: .spec.ipAddressPoolSelectors
+      name: IPAddressPool Selectors
+      type: string
+    - jsonPath: .spec.interfaces
+      name: Interfaces
+      type: string
+    - jsonPath: .spec.nodeSelectors
+      name: Node Selectors
+      priority: 10
+      type: string
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: L2Advertisement allows to advertise the LoadBalancer IPs provided
+          by the selected pools via L2.
+        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
+          spec:
+            description: L2AdvertisementSpec defines the desired state of L2Advertisement.
+            properties:
+              interfaces:
+                description: A list of interfaces to announce from. The LB IP will
+                  be announced only from these interfaces. If the field is not set,
+                  we advertise from all the interfaces on the host.
+                items:
+                  type: string
+                type: array
+              ipAddressPoolSelectors:
+                description: A selector for the IPAddressPools which would get advertised
+                  via this advertisement. If no IPAddressPool is selected by this
+                  or by the list, the advertisement is applied to all the IPAddressPools.
+                items:
+                  description: A label selector is a label query over a set of resources.
+                    The result of matchLabels and matchExpressions are ANDed. An empty
+                    label selector matches all objects. A null label selector matches
+                    no objects.
+                  properties:
+                    matchExpressions:
+                      description: matchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: A label selector requirement is a selector that
+                          contains values, a key, and an operator that relates the
+                          key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: operator represents a key's relationship
+                              to a set of values. Valid operators are In, NotIn, Exists
+                              and DoesNotExist.
+                            type: string
+                          values:
+                            description: values is an array of string values. If the
+                              operator is In or NotIn, the values array must be non-empty.
+                              If the operator is Exists or DoesNotExist, the values
+                              array must be empty. This array is replaced during a
+                              strategic merge patch.
+                            items:
+                              type: string
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      description: matchLabels is a map of {key,value} pairs. A single
+                        {key,value} in the matchLabels map is equivalent to an element
+                        of matchExpressions, whose key field is "key", the operator
+                        is "In", and the values array contains only "value". The requirements
+                        are ANDed.
+                      type: object
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+              ipAddressPools:
+                description: The list of IPAddressPools to advertise via this advertisement,
+                  selected by name.
+                items:
+                  type: string
+                type: array
+              nodeSelectors:
+                description: NodeSelectors allows to limit the nodes to announce as
+                  next hops for the LoadBalancer IP. When empty, all the nodes having  are
+                  announced as next hops.
+                items:
+                  description: A label selector is a label query over a set of resources.
+                    The result of matchLabels and matchExpressions are ANDed. An empty
+                    label selector matches all objects. A null label selector matches
+                    no objects.
+                  properties:
+                    matchExpressions:
+                      description: matchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: A label selector requirement is a selector that
+                          contains values, a key, and an operator that relates the
+                          key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: operator represents a key's relationship
+                              to a set of values. Valid operators are In, NotIn, Exists
+                              and DoesNotExist.
+                            type: string
+                          values:
+                            description: values is an array of string values. If the
+                              operator is In or NotIn, the values array must be non-empty.
+                              If the operator is Exists or DoesNotExist, the values
+                              array must be empty. This array is replaced during a
+                              strategic merge patch.
+                            items:
+                              type: string
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      description: matchLabels is a map of {key,value} pairs. A single
+                        {key,value} in the matchLabels map is equivalent to an element
+                        of matchExpressions, whose key field is "key", the operator
+                        is "In", and the values array contains only "value". The requirements
+                        are ANDed.
+                      type: object
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+            type: object
+          status:
+            description: L2AdvertisementStatus defines the observed state of L2Advertisement.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  labels:
+    app: metallb
+  name: controller
+  namespace: metallb-system
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  labels:
+    app: metallb
+  name: speaker
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  labels:
+    app: metallb
+  name: controller
+  namespace: metallb-system
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - ""
+  resourceNames:
+  - memberlist
+  resources:
+  - secrets
+  verbs:
+  - list
+- apiGroups:
+  - apps
+  resourceNames:
+  - controller
+  resources:
+  - deployments
+  verbs:
+  - get
+- apiGroups:
+  - metallb.io
+  resources:
+  - bgppeers
+  verbs:
+  - get
+  - list
+- apiGroups:
+  - metallb.io
+  resources:
+  - addresspools
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - bfdprofiles
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - ipaddresspools
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - bgpadvertisements
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - l2advertisements
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - communities
+  verbs:
+  - get
+  - list
+  - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  labels:
+    app: metallb
+  name: pod-lister
+  namespace: metallb-system
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  verbs:
+  - list
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - configmaps
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - addresspools
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - bfdprofiles
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - bgppeers
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - l2advertisements
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - bgpadvertisements
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - ipaddresspools
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - communities
+  verbs:
+  - get
+  - list
+  - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app: metallb
+  name: metallb-system:controller
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - services
+  - namespaces
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - nodes
+  verbs:
+  - list
+- apiGroups:
+  - ""
+  resources:
+  - services/status
+  verbs:
+  - update
+- apiGroups:
+  - ""
+  resources:
+  - events
+  verbs:
+  - create
+  - patch
+- apiGroups:
+  - policy
+  resourceNames:
+  - controller
+  resources:
+  - podsecuritypolicies
+  verbs:
+  - use
+- apiGroups:
+  - admissionregistration.k8s.io
+  resourceNames:
+  - metallb-webhook-configuration
+  resources:
+  - validatingwebhookconfigurations
+  - mutatingwebhookconfigurations
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - admissionregistration.k8s.io
+  resources:
+  - validatingwebhookconfigurations
+  - mutatingwebhookconfigurations
+  verbs:
+  - list
+  - watch
+- apiGroups:
+  - apiextensions.k8s.io
+  resourceNames:
+  - addresspools.metallb.io
+  - bfdprofiles.metallb.io
+  - bgpadvertisements.metallb.io
+  - bgppeers.metallb.io
+  - ipaddresspools.metallb.io
+  - l2advertisements.metallb.io
+  - communities.metallb.io
+  resources:
+  - customresourcedefinitions
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - apiextensions.k8s.io
+  resources:
+  - customresourcedefinitions
+  verbs:
+  - list
+  - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app: metallb
+  name: metallb-system:speaker
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - services
+  - endpoints
+  - nodes
+  - namespaces
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - discovery.k8s.io
+  resources:
+  - endpointslices
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - events
+  verbs:
+  - create
+  - patch
+- apiGroups:
+  - policy
+  resourceNames:
+  - speaker
+  resources:
+  - podsecuritypolicies
+  verbs:
+  - use
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  labels:
+    app: metallb
+  name: controller
+  namespace: metallb-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: controller
+subjects:
+- kind: ServiceAccount
+  name: controller
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  labels:
+    app: metallb
+  name: pod-lister
+  namespace: metallb-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: pod-lister
+subjects:
+- kind: ServiceAccount
+  name: speaker
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app: metallb
+  name: metallb-system:controller
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: metallb-system:controller
+subjects:
+- kind: ServiceAccount
+  name: controller
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app: metallb
+  name: metallb-system:speaker
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: metallb-system:speaker
+subjects:
+- kind: ServiceAccount
+  name: speaker
+  namespace: metallb-system
+---
+apiVersion: v1
+data:
+  excludel2.yaml: |
+    announcedInterfacesToExclude: ["docker.*", "cbr.*", "dummy.*", "virbr.*", "lxcbr.*", "veth.*", "lo", "^cali.*", "^tunl.*", "flannel.*", "kube-ipvs.*", "cni.*", "^nodelocaldns.*"]
+kind: ConfigMap
+metadata:
+  name: metallb-excludel2
+  namespace: metallb-system
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: webhook-server-cert
+  namespace: metallb-system
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: webhook-service
+  namespace: metallb-system
+spec:
+  ports:
+  - port: 443
+    targetPort: 9443
+  selector:
+    component: controller
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app: metallb
+    component: controller
+  name: controller
+  namespace: metallb-system
+spec:
+  revisionHistoryLimit: 3
+  selector:
+    matchLabels:
+      app: metallb
+      component: controller
+  template:
+    metadata:
+      annotations:
+        prometheus.io/port: "7472"
+        prometheus.io/scrape: "true"
+      labels:
+        app: metallb
+        component: controller
+    spec:
+      containers:
+      - args:
+        - --port=7472
+        - --log-level=info
+        env:
+        - name: METALLB_ML_SECRET_NAME
+          value: memberlist
+        - name: METALLB_DEPLOYMENT
+          value: controller
+        image: quay.io/metallb/controller:v0.13.10
+        livenessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /metrics
+            port: monitoring
+          initialDelaySeconds: 10
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        name: controller
+        ports:
+        - containerPort: 7472
+          name: monitoring
+        - containerPort: 9443
+          name: webhook-server
+          protocol: TCP
+        readinessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /metrics
+            port: monitoring
+          initialDelaySeconds: 10
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - all
+          readOnlyRootFilesystem: true
+        volumeMounts:
+        - mountPath: /tmp/k8s-webhook-server/serving-certs
+          name: cert
+          readOnly: true
+      nodeSelector:
+        kubernetes.io/os: linux
+      securityContext:
+        fsGroup: 65534
+        runAsNonRoot: true
+        runAsUser: 65534
+      serviceAccountName: controller
+      terminationGracePeriodSeconds: 0
+      volumes:
+      - name: cert
+        secret:
+          defaultMode: 420
+          secretName: webhook-server-cert
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  labels:
+    app: metallb
+    component: speaker
+  name: speaker
+  namespace: metallb-system
+spec:
+  selector:
+    matchLabels:
+      app: metallb
+      component: speaker
+  template:
+    metadata:
+      annotations:
+        prometheus.io/port: "7472"
+        prometheus.io/scrape: "true"
+      labels:
+        app: metallb
+        component: speaker
+    spec:
+      containers:
+      - args:
+        - --port=7472
+        - --log-level=info
+        env:
+        - name: METALLB_NODE_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: spec.nodeName
+        - name: METALLB_HOST
+          valueFrom:
+            fieldRef:
+              fieldPath: status.hostIP
+        - name: METALLB_ML_BIND_ADDR
+          valueFrom:
+            fieldRef:
+              fieldPath: status.podIP
+        - name: METALLB_ML_LABELS
+          value: app=metallb,component=speaker
+        - name: METALLB_ML_SECRET_KEY_PATH
+          value: /etc/ml_secret_key
+        image: quay.io/metallb/speaker:v0.13.10
+        livenessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /metrics
+            port: monitoring
+          initialDelaySeconds: 10
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        name: speaker
+        ports:
+        - containerPort: 7472
+          name: monitoring
+        - containerPort: 7946
+          name: memberlist-tcp
+        - containerPort: 7946
+          name: memberlist-udp
+          protocol: UDP
+        readinessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /metrics
+            port: monitoring
+          initialDelaySeconds: 10
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            add:
+            - NET_RAW
+            drop:
+            - ALL
+          readOnlyRootFilesystem: true
+        volumeMounts:
+        - mountPath: /etc/ml_secret_key
+          name: memberlist
+          readOnly: true
+        - mountPath: /etc/metallb
+          name: metallb-excludel2
+          readOnly: true
+      hostNetwork: true
+      nodeSelector:
+        kubernetes.io/os: linux
+      serviceAccountName: speaker
+      terminationGracePeriodSeconds: 2
+      tolerations:
+      - effect: NoSchedule
+        key: node-role.kubernetes.io/master
+        operator: Exists
+      - effect: NoSchedule
+        key: node-role.kubernetes.io/control-plane
+        operator: Exists
+      volumes:
+      - name: memberlist
+        secret:
+          defaultMode: 420
+          secretName: memberlist
+      - configMap:
+          defaultMode: 256
+          name: metallb-excludel2
+        name: metallb-excludel2
+---
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  creationTimestamp: null
+  name: metallb-webhook-configuration
+webhooks:
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta2-bgppeer
+  failurePolicy: Fail
+  name: bgppeersvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta2
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - bgppeers
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-addresspool
+  failurePolicy: Fail
+  name: addresspoolvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - addresspools
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-bfdprofile
+  failurePolicy: Fail
+  name: bfdprofilevalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - DELETE
+    resources:
+    - bfdprofiles
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-bgpadvertisement
+  failurePolicy: Fail
+  name: bgpadvertisementvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - bgpadvertisements
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-community
+  failurePolicy: Fail
+  name: communityvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - communities
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-ipaddresspool
+  failurePolicy: Fail
+  name: ipaddresspoolvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - ipaddresspools
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-l2advertisement
+  failurePolicy: Fail
+  name: l2advertisementvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - l2advertisements
+  sideEffects: None
diff --git a/Ansible/playbooks/files/metallb.yaml b/Ansible/playbooks/files/metallb.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8169310a7df160ed6db40c0faafd9e26bb3c9037
--- /dev/null
+++ b/Ansible/playbooks/files/metallb.yaml
@@ -0,0 +1,16 @@
+# Layer 2 configuration
+# <https://metallb.universe.tf/configuration/#layer-2-configuration>
+apiVersion: metallb.io/v1beta1
+kind: IPAddressPool
+metadata:
+  name: default
+  namespace: metallb-system
+spec:
+  addresses:
+    - 172.18.0.1/24  # Adjust this range based on your Docker network
+---
+apiVersion: metallb.io/v1beta1
+kind: L2Advertisement
+metadata:
+  name: default
+  namespace: metallb-system
diff --git a/Ansible/playbooks/kind-metallb.yml b/Ansible/playbooks/kind-metallb.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/Ansible/playbooks/kind-metallb.yml
@@ -0,0 +1 @@
+---
diff --git a/Application/backend/app.log b/Application/backend/app.log
deleted file mode 100644
index 9cee2c841a0fc8ba39b7c0cb3cba513b8df98ec7..0000000000000000000000000000000000000000
--- a/Application/backend/app.log
+++ /dev/null
@@ -1,164 +0,0 @@
-2024-12-02 12:10:55,122 - __main__ - INFO - Starting Flask application...
-2024-12-02 12:11:05,697 - __main__ - INFO - Starting Flask application...
-2024-12-02 12:11:05,701 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- * Running on all addresses (0.0.0.0)
- * Running on http://127.0.0.1:8000
- * Running on http://192.168.1.93:8000
-2024-12-02 12:11:05,701 - werkzeug - INFO - Press CTRL+C to quit
-2024-12-02 12:11:50,693 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:11:50] "OPTIONS /enroll HTTP/1.1" 200 -
-2024-12-02 12:11:50,703 - __main__ - INFO - Enrollment attempt for email: test@gmail.com
-2024-12-02 12:11:51,392 - __main__ - ERROR - Unexpected error: An error occurred (InvalidAccessKeyId) when calling the PutObject operation: Unknown
-2024-12-02 12:11:51,396 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:11:51] "POST /enroll HTTP/1.1" 500 -
-2024-12-02 12:19:27,242 - __main__ - INFO - Starting Flask application...
-2024-12-02 12:19:27,246 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- * Running on all addresses (0.0.0.0)
- * Running on http://127.0.0.1:8000
- * Running on http://192.168.1.93:8000
-2024-12-02 12:19:27,246 - werkzeug - INFO - Press CTRL+C to quit
-2024-12-02 12:19:42,112 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:19:42] "OPTIONS /enroll HTTP/1.1" 200 -
-2024-12-02 12:19:42,115 - __main__ - INFO - Enrollment attempt for email: test@gmail.com
-2024-12-02 12:19:42,700 - __main__ - ERROR - Unexpected error: An error occurred (InvalidAccessKeyId) when calling the PutObject operation: Unknown
-2024-12-02 12:19:42,702 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:19:42] "POST /enroll HTTP/1.1" 500 -
-2024-12-02 12:20:19,205 - __main__ - INFO - AWS_ACCESS_KEY_ID: AKIAVEKYIBTQEJB2XSNM
-2024-12-02 12:20:19,205 - __main__ - INFO - AWS_SECRET_ACCESS_KEY: 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
-2024-12-02 12:20:19,205 - __main__ - INFO - AWS_ENDPOINT_URL: https://os.zhdk.cloud.switch.ch
-2024-12-02 12:20:19,205 - __main__ - INFO - S3_BUCKET_NAME: cloud-bach-proj
-2024-12-02 12:20:19,272 - __main__ - INFO - Starting Flask application...
-2024-12-02 12:20:19,274 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- * Running on all addresses (0.0.0.0)
- * Running on http://127.0.0.1:8000
- * Running on http://192.168.1.93:8000
-2024-12-02 12:20:19,274 - werkzeug - INFO - Press CTRL+C to quit
-2024-12-02 12:21:40,705 - __main__ - INFO - SWTICH_ACCESS_KEY_ID: None
-2024-12-02 12:21:40,705 - __main__ - INFO - SWITCH_SECRET_ACCESS_KEY: None
-2024-12-02 12:21:40,705 - __main__ - INFO - SWTICH_ENDPOINT_URL: None
-2024-12-02 12:21:40,705 - __main__ - INFO - S3_BUCKET_NAME: cloud-bach-proj
-2024-12-02 12:21:40,713 - botocore.credentials - INFO - Found credentials in environment variables.
-2024-12-02 12:21:40,766 - botocore.configprovider - INFO - Found endpoint for s3 via: environment_global.
-2024-12-02 12:21:40,770 - __main__ - INFO - Starting Flask application...
-2024-12-02 12:21:40,773 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- * Running on all addresses (0.0.0.0)
- * Running on http://127.0.0.1:8000
- * Running on http://192.168.1.93:8000
-2024-12-02 12:21:40,773 - werkzeug - INFO - Press CTRL+C to quit
-2024-12-02 12:21:51,332 - __main__ - INFO - SWTICH_ACCESS_KEY_ID: None
-2024-12-02 12:21:51,332 - __main__ - INFO - SWITCH_SECRET_ACCESS_KEY: None
-2024-12-02 12:21:51,332 - __main__ - INFO - SWTICH_ENDPOINT_URL: None
-2024-12-02 12:21:51,332 - __main__ - INFO - S3_BUCKET_NAME: cloud-bach-proj
-2024-12-02 12:21:51,340 - botocore.credentials - INFO - Found credentials in environment variables.
-2024-12-02 12:21:51,389 - botocore.configprovider - INFO - Found endpoint for s3 via: environment_global.
-2024-12-02 12:21:51,394 - __main__ - INFO - Starting Flask application...
-2024-12-02 12:21:51,396 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- * Running on all addresses (0.0.0.0)
- * Running on http://127.0.0.1:8000
- * Running on http://192.168.1.93:8000
-2024-12-02 12:21:51,396 - werkzeug - INFO - Press CTRL+C to quit
-2024-12-02 12:23:04,117 - __main__ - INFO - SWTICH_ACCESS_KEY_ID: None
-2024-12-02 12:23:04,117 - __main__ - INFO - SWITCH_SECRET_ACCESS_KEY: None
-2024-12-02 12:23:04,117 - __main__ - INFO - SWTICH_ENDPOINT_URL: None
-2024-12-02 12:23:04,117 - __main__ - INFO - S3_BUCKET_NAME: None
-2024-12-02 12:23:04,126 - botocore.credentials - INFO - Found credentials in environment variables.
-2024-12-02 12:23:04,196 - __main__ - CRITICAL - S3_BUCKET_NAME environment variable is not set
-2024-12-02 12:53:11,151 - __main__ - INFO - SWTICH_ACCESS_KEY_ID: 4406dbe746a24614a9bc8f7ec864e59f
-2024-12-02 12:53:11,151 - __main__ - INFO - SWITCH_SECRET_ACCESS_KEY: cec6e60954b24a51923fe5aaea9fbb3b
-2024-12-02 12:53:11,151 - __main__ - INFO - SWTICH_ENDPOINT_URL: https://os.zhdk.cloud.switch.ch
-2024-12-02 12:53:11,151 - __main__ - INFO - S3_BUCKET_NAME: cloud-bach-proj
-2024-12-02 12:53:11,385 - __main__ - INFO - Starting Flask application...
-2024-12-02 12:53:11,391 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- * Running on all addresses (0.0.0.0)
- * Running on http://127.0.0.1:8000
- * Running on http://192.168.1.93:8000
-2024-12-02 12:53:11,391 - werkzeug - INFO - Press CTRL+C to quit
-2024-12-02 12:53:19,651 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:53:19] "OPTIONS /enroll HTTP/1.1" 200 -
-2024-12-02 12:53:19,656 - __main__ - INFO - Enrollment attempt for email: test@gmail.com
-2024-12-02 12:53:20,645 - __main__ - ERROR - Unexpected error: An error occurred (NoSuchBucket) when calling the PutObject operation: Unknown
-2024-12-02 12:53:20,647 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:53:20] "POST /enroll HTTP/1.1" 500 -
-2024-12-02 12:54:00,835 - __main__ - INFO - SWTICH_ACCESS_KEY_ID: 4406dbe746a24614a9bc8f7ec864e59f
-2024-12-02 12:54:00,835 - __main__ - INFO - SWITCH_SECRET_ACCESS_KEY: cec6e60954b24a51923fe5aaea9fbb3b
-2024-12-02 12:54:00,835 - __main__ - INFO - SWTICH_ENDPOINT_URL: https://os.zhdk.cloud.switch.ch
-2024-12-02 12:54:00,835 - __main__ - INFO - S3_BUCKET_NAME: cloud-bach-proj
-2024-12-02 12:54:00,923 - __main__ - INFO - Starting Flask application...
-2024-12-02 12:54:01,360 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- * Running on all addresses (0.0.0.0)
- * Running on http://127.0.0.1:8000
- * Running on http://192.168.1.93:8000
-2024-12-02 12:54:01,361 - werkzeug - INFO - Press CTRL+C to quit
-2024-12-02 12:54:09,932 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:54:09] "OPTIONS /enroll HTTP/1.1" 200 -
-2024-12-02 12:54:09,937 - __main__ - INFO - Enrollment attempt for email: test@gmail.com
-2024-12-02 12:54:10,203 - __main__ - INFO - Successfully enrolled user: test@gmail.com
-2024-12-02 12:54:10,205 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:54:10] "POST /enroll HTTP/1.1" 200 -
-2024-12-02 12:54:19,954 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:54:19] "OPTIONS /login HTTP/1.1" 200 -
-2024-12-02 12:54:19,960 - __main__ - INFO - Login attempt for email: test@gmail.com
-2024-12-02 12:54:20,282 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:54:20] "POST /login HTTP/1.1" 200 -
-2024-12-02 12:54:27,064 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:54:27] "OPTIONS /logout HTTP/1.1" 200 -
-2024-12-02 12:54:27,069 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:54:27] "POST /logout HTTP/1.1" 400 -
-2024-12-02 12:55:40,966 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:55:40] "OPTIONS /logout HTTP/1.1" 200 -
-2024-12-02 12:55:40,971 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:55:40] "POST /logout HTTP/1.1" 400 -
-2024-12-02 12:55:46,985 - __main__ - INFO - SWTICH_ACCESS_KEY_ID: 4406dbe746a24614a9bc8f7ec864e59f
-2024-12-02 12:55:46,985 - __main__ - INFO - SWITCH_SECRET_ACCESS_KEY: cec6e60954b24a51923fe5aaea9fbb3b
-2024-12-02 12:55:46,985 - __main__ - INFO - SWTICH_ENDPOINT_URL: https://os.zhdk.cloud.switch.ch
-2024-12-02 12:55:46,985 - __main__ - INFO - S3_BUCKET_NAME: cloud-bach-proj
-2024-12-02 12:55:47,082 - __main__ - INFO - Starting Flask application...
-2024-12-02 12:55:47,580 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- * Running on all addresses (0.0.0.0)
- * Running on http://127.0.0.1:8000
- * Running on http://192.168.1.93:8000
-2024-12-02 12:55:47,580 - werkzeug - INFO - Press CTRL+C to quit
-2024-12-02 12:55:53,216 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:55:53] "OPTIONS /login HTTP/1.1" 200 -
-2024-12-02 12:55:53,221 - __main__ - INFO - Login attempt for email: test@gmail.com
-2024-12-02 12:55:53,396 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:55:53] "POST /login HTTP/1.1" 200 -
-2024-12-02 12:55:54,335 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:55:54] "OPTIONS /logout HTTP/1.1" 200 -
-2024-12-02 12:55:54,340 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:55:54] "POST /logout HTTP/1.1" 400 -
-2024-12-02 12:56:23,774 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:56:23] "OPTIONS /logout HTTP/1.1" 200 -
-2024-12-02 12:56:23,780 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:56:23] "POST /logout HTTP/1.1" 400 -
-2024-12-02 12:56:30,635 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:56:30] "OPTIONS /login HTTP/1.1" 200 -
-2024-12-02 12:56:30,641 - __main__ - INFO - Login attempt for email: test@gmail.com
-2024-12-02 12:56:31,075 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:56:31] "POST /login HTTP/1.1" 200 -
-2024-12-02 12:56:32,425 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:56:32] "OPTIONS /logout HTTP/1.1" 200 -
-2024-12-02 12:56:32,430 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:56:32] "POST /logout HTTP/1.1" 400 -
-2024-12-02 12:57:02,178 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:57:02] "OPTIONS /login HTTP/1.1" 200 -
-2024-12-02 12:57:02,184 - __main__ - INFO - Login attempt for email: test@gmail.com
-2024-12-02 12:57:02,825 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:57:02] "POST /login HTTP/1.1" 200 -
-2024-12-02 12:57:04,463 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:57:04] "OPTIONS /logout HTTP/1.1" 200 -
-2024-12-02 12:57:05,167 - werkzeug - INFO - 127.0.0.1 - - [02/Dec/2024 12:57:05] "POST /logout HTTP/1.1" 200 -
-2024-12-09 09:10:26,299 - __main__ - INFO - SWTICH_ACCESS_KEY_ID: 4406dbe746a24614a9bc8f7ec864e59f
-2024-12-09 09:10:26,299 - __main__ - INFO - SWITCH_SECRET_ACCESS_KEY: cec6e60954b24a51923fe5aaea9fbb3b
-2024-12-09 09:10:26,299 - __main__ - INFO - SWTICH_ENDPOINT_URL: https://os.zhdk.cloud.switch.ch
-2024-12-09 09:10:26,299 - __main__ - INFO - S3_BUCKET_NAME: cloud-bach-proj
-2024-12-09 09:10:26,615 - __main__ - INFO - Starting Flask application...
-2024-12-09 09:10:27,453 - werkzeug - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- * Running on all addresses (0.0.0.0)
- * Running on http://127.0.0.1:8000
- * Running on http://192.168.1.159:8000
-2024-12-09 09:10:27,453 - werkzeug - INFO - Press CTRL+C to quit
-2024-12-09 09:20:42,041 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:20:42] "OPTIONS /enroll HTTP/1.1" 200 -
-2024-12-09 09:20:42,045 - __main__ - INFO - Enrollment attempt for email: sapos@hotmail.com
-2024-12-09 09:20:43,477 - __main__ - INFO - Successfully enrolled user: sapos@hotmail.com
-2024-12-09 09:20:43,478 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:20:43] "POST /enroll HTTP/1.1" 200 -
-2024-12-09 09:20:52,022 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:20:52] "OPTIONS /login HTTP/1.1" 200 -
-2024-12-09 09:20:52,026 - __main__ - INFO - Login attempt for email: sapos@hotmail.com
-2024-12-09 09:20:52,208 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:20:52] "POST /login HTTP/1.1" 401 -
-2024-12-09 09:20:56,219 - __main__ - INFO - Login attempt for email: sapos@hotmail.com
-2024-12-09 09:20:56,909 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:20:56] "POST /login HTTP/1.1" 200 -
-2024-12-09 09:21:21,262 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:21:21] "OPTIONS /logout HTTP/1.1" 200 -
-2024-12-09 09:21:21,692 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:21:21] "POST /logout HTTP/1.1" 200 -
-2024-12-09 09:21:29,789 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:21:29] "OPTIONS /enroll HTTP/1.1" 200 -
-2024-12-09 09:21:29,793 - __main__ - INFO - Enrollment attempt for email: fran.abm94@gmail.com
-2024-12-09 09:21:30,036 - __main__ - INFO - Successfully enrolled user: fran.abm94@gmail.com
-2024-12-09 09:21:30,037 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:21:30] "POST /enroll HTTP/1.1" 200 -
-2024-12-09 09:21:54,239 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:21:54] "OPTIONS /login HTTP/1.1" 200 -
-2024-12-09 09:21:54,242 - __main__ - INFO - Login attempt for email: fran.abm94@gmail.com
-2024-12-09 09:21:54,689 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:21:54] "POST /login HTTP/1.1" 200 -
-2024-12-09 09:21:56,036 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:21:56] "OPTIONS /logout HTTP/1.1" 200 -
-2024-12-09 09:21:56,455 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:21:56] "POST /logout HTTP/1.1" 200 -
-2024-12-09 09:21:58,042 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:21:58] "OPTIONS /enroll HTTP/1.1" 200 -
-2024-12-09 09:21:58,044 - __main__ - INFO - Enrollment attempt for email: fran.abm94@gmail.com
-2024-12-09 09:21:58,125 - __main__ - INFO - Enrollment failed - user already exists: fran.abm94@gmail.com
-2024-12-09 09:21:58,126 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:21:58] "POST /enroll HTTP/1.1" 409 -
-2024-12-09 09:22:05,692 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:22:05] "OPTIONS /enroll HTTP/1.1" 200 -
-2024-12-09 09:22:05,694 - __main__ - INFO - Enrollment attempt for email: fran.abm@gmail.com
-2024-12-09 09:22:06,199 - __main__ - INFO - Successfully enrolled user: fran.abm@gmail.com
-2024-12-09 09:22:06,200 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:22:06] "POST /enroll HTTP/1.1" 200 -
-2024-12-09 09:22:16,204 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:22:16] "OPTIONS /login HTTP/1.1" 200 -
-2024-12-09 09:22:16,205 - __main__ - INFO - Login attempt for email: fran.abm@gmail.com
-2024-12-09 09:22:16,590 - werkzeug - INFO - 127.0.0.1 - - [09/Dec/2024 09:22:16] "POST /login HTTP/1.1" 200 -
diff --git a/README.md b/README.md
index 057e6944755161b5fa9b9be36374ee567e82f178..439c7ab2f43a0377608132077350d4fec78da41d 100644
--- a/README.md
+++ b/README.md
@@ -20,47 +20,77 @@ S3 storage for the enrollment and session data.
 
 ## Architecture
 
-The system architecture is composed of 3 tiers:
-  1. The **front-end** that provides a log-in Web GUI (languages: HTML5, JS)
-     and sends incoming authentication requests to the back-end.
-  2. The **back-end** (languages: Python/Flask) that receives
-     and handles authentication requests from the front-end. All the session
-     logic is implemented here in a CRUD-like fashion: session objects are
-     stored in an S3-compatible storage.
+The system's 3-tier architecture is microservice-based:
+
+  1. The replicated **front-end** tier implements:
+      * a login Web GUI composed of several *views* (languages: HTML5, JS) and
+      * associated logic (languages: Python/Flask) which sends incoming
+        authentication requests to the back-end and returns the results to the
+        views.  The ingress/egress point is a load balancer that exposes an
+        external IP address.
+  2. The **back-end** tier (languages: Python/Flask) tier receives and handles
+     authentication requests from the front-end. All the enrollment and
+     session logic is implemented here in a CRUD-like fashion: corresponding
+     objects are stored in an S3-compatible storage.
   3. The **storage** tier is a standard S3-like object storage which can be
      accessed only by the back-end.
 
-:construction: **TO-DO**: add API diagrams: components, activity.
+![Application's architecture and deployment](app_arch_depl.png)
+*Application's architecture and deployment schema*
+
+
+### Infrastructure  and deployment
+
+The front-end replicas and the back-end are deployed in separate containers
+hosted by a Kubernetes-based MetalLB service deployment. The service, on its
+turn, is hosted by a 2-node KinD cluster installed on a single VM
+infrastructure.
 
 
 ### Front-end
 
-:construction: Web portal with REST-based functions, written in HTML5
-and JavaScript. The service routes are:
+Web portal with REST-based functions, written in HTML5, JavaScript (views) and
+Python/Flask (main logic). All related files are in directory `frontend/`.
 
-  * `enroll`: subscribe to the system with credentials
-  * `unenroll`: delete subscription credentials
+The REST service *routes* are:
+
+  * `enroll`: sign-up (subscribe) to the system with credentials
+  * `unenroll`: delete subscription credentials (remove account)
   * `login`: authenticate with e-mail and, if needed (first login), password
   * `logout`: de-authenticate by removing the current session
 
-:construction: **TO-DO**: add API prototypes.
+The following files in directory `views/` handle the client-side workflow.
+
+  * `index.html` provides just two buttons:
+      * "Login" linked to the view `login.html`
+      * "Sign up" (enroll) linked to the view `signup.html`
+  * `signup.html` provides a form with with input fields "e-mail" and
+    "password" and is linked to the `dashboard.html` view
+  * `login.html` provides a form with input fields "e-mail" and "password" and
+    is linked to the `dashboard.html` view.
+  * `dashboard.html` provides two buttons "Logout" and "Remove account" (unenroll)
 
-Input fields: *e-mail* and *password*.
+The file `main.py` handles the REST logic by conveying all requests to the
+back-end.
 
-Buttons: 4, named as the routes above.
+:bulb: **This part does not requires adaptations.**
 
 
 ### Back-end
 
-:construction: Session management subsystem written in Python/Flask
+The single file `backend/main.py` (Python/Flask) implements the enrollment and
+session management logic by handling REST requests coming from the
+front-end. The corresponding objects are managed in a CRUD-like fashion
+in/from a single S3 storage bucket.
 
-:tools: This part requires some development by the students.
+:tools: **This part requires some development.** See the details in the
+boilerplate `backend/main.py`.
 
 
 ### Storage
 
-:construction: S3-like object storage composed of 1 buckets with two directories: one for
-*enrollment* data, one for *session* data.
+This is a single S3-like buckets with two directories: one for *enrollment*
+data, one for *session* data.
 
 Objects shall be written as JSON data based on the following proposed schema.
 
@@ -139,6 +169,7 @@ Minimum schema (you're free to extend it):
 Example data for object named `foo@bar.com`:
 ``` json
 {
+	"client": "192.168.1.2",
 	"timestamp": 1733330967
 }
 ```
@@ -169,10 +200,10 @@ nothing else is recorded by the back-end.
 
 #### Enroll
 
-A new user subscribes to the system via the `enroll` function:
+A *new* user subscribes to the system via the `enroll` function:
 
   1. **User** provides enrollment data (*e-mail* and *password*) via the the
-     front-end.
+     front-end's `signup` view.
   2. **Front-end** sends enrollment data to the back-end.
   3. **Back-end** verifies enrollment data:
       - IF user exists THEN returns 'KO:ALREADY_ENROLLED'
@@ -184,24 +215,26 @@ A new user subscribes to the system via the `enroll` function:
 
 #### Unenroll
 
-An enrolled unsubscribes from the system with the `unenroll` function:
+An *enrolled* user unsubscribes from the system (removes their account) with
+the `unenroll` function:
 
-  1. **User** provides enrollment data (*e-mail* and *password*) via the the
-     front-end.
+  1. **User** provides enrollment *e-mail* via the the front-end's `dashboard`
+     view.
   2. **Front-end** sends enrollment data to the back-end.
   3. **Back-end** verifies enrollment data:
       - IF user does not exists THEN returns 'KO:NO\_SUCH\_USER'
       - ELSE
           1. Removes enrollment data and any active sessions from the storage
           2. Returns 'OK:UNENROLLED' to the front-end
-  4. **Front-end** receives response from the back-end and shows it to the user.
+  4. **Front-end** receives response from the back-end and shows it to the
+     user.
 
 
 #### Login
 
 An enrolled user authenticates to the system with the `login` function:
 
-  1. **User** provides *e-mail* via the the front-end.
+  1. **User** provides *e-mail* via the the front-end's view `login`.
   2. **Front-end** sends *e-mail* to the back-end.
   3. **Back-end** verifies the *e-mail*:
       - IF user does not exists THEN returns 'KO:NO\_SUCH\_USER'
@@ -227,7 +260,7 @@ An enrolled user authenticates to the system with the `login` function:
 
 An enrolled user deauthenticates to the system with the `logout` function:
 
-  1. **User** provides *e-mail* via the the front-end.
+  1. **User** provides *e-mail* via the the front-end's view `dashboard`.
   2. **Front-end** sends *e-mail* to the back-end.
   3. **Back-end** verifies the *e-mail*:
       - IF user does not exists THEN returns 'KO:NO\_SUCH\_USER'
@@ -247,7 +280,7 @@ is composed of
   * A single VM featuring:
       * Source image: A Debian 12 Bookworm
       * Flavor: 2 vCPUs, 4GB RAM, 40GB root disk -- no extra volume needed
-      * A KinD installation
+      * A KinD/Kubectl installation
   * One S3 bucket.
 
 The infrastructure (computing instance + S3 storage) shall be provisioned via
@@ -255,52 +288,86 @@ The infrastructure (computing instance + S3 storage) shall be provisioned via
 
 :bulb: References:
   * Terraform: https://registry.terraform.io/providers/hashicorp/aws/latest/docs
-  * :question: what else?
 
 
 ## Service deployment
 
-The service is deployed on a 2-pods microservice hosted by a two-node KinD
-cluster, with a single MetalLB load-balancer entry point, as done with the
+The service shall be deployed on a 3-pods K8s microservice hosted by a
+two-node KinD cluster, with a single MetalLB load-balancer entry point, as
+done with the
 [Lab-K8s](https://gitedu.hesge.ch/lsds/teaching/bachelor/cloud-and-deployment/lab-k8s).
+The front-end shall be replicated over 2 pods. The 3rd pod shall host the
+back-end.
 
 The whole software stack, apart from the KinD package, shall be deployed via
-**Ansible**. Of course, instead of the dummy `http-echo` app, a different
-Docker image shall be used -- :construction: see the [project's Docker file
-boilerplate](provide-link-please). This image shall be rebuilt after any
-modification to the application code. The application shall be redeployed
-whenever its image is updated.
+**Ansible**. Of course, instead of the dummy `http-echo` app, two different
+Docker images shall be used -- :construction: see the [project's Docker file
+boilerplate](provide-link-please): one for the front-end, the other for the
+back-end, both hosted in the [Docker Hub registry](https://hub.docker.com/) --
+you shall create a personal public repository. **We trust you, please, do not
+cheat!**
+
+The front-end image does not need to be rebuilt, unless you want to implement
+some client-side (HTML/JS) bonuses.
+
+The back-end image shall be rebuilt after any modification to the application
+code.
+
+The whole stack shall be redeployed whenever any of its images are updated.
 
 
 ## Tasks
 
 :construction: **To be finalized**
 
+:warning: Please respect the file layout provided by this repository!
+
 You shall:
 
-0. Fork this repository.
-1. Complete the Python back-end file(s) in folder `Application/back-end.py`.
-2. Rebuild the application Docker image, and store it (somewhere) --
-   **(:question: TO-DO - We should provide instructions + Dockerfile:
-    - Build image on the student's workstation
-    - What's better: push to Dockerhub vs to scp to VM + import?
-    )**. This task shall be automated via Ansible -- see below.
-3. Complete your Terraform files from the version you developed in
+1. Fork this repository.
+2. Complete the Python back-end file(s) in folder
+   `Application/backend/main.py`.
+3. Rebuild the application back-end Docker image, and push it to your public
+   Docker Hub repositry -- **(:question: TO-DO - We should provide
+   instructions)**. This task shall be automated via Ansible -- see below.
+4. Complete your Terraform files from the version you developed in
    [Lab-Terraform](https://gitedu.hesge.ch/lsds/teaching/bachelor/cloud-and-deployment/lab-terraform/-/blob/main/SwitchEngines/README.md)
    up to Task #8. Your recipe shall handle only the provisioning of the VM
    plus an S3 storage bucket -- no KinD/Kubectl package installation. Commit
-   your recipe files (included Cloud-init) and  in directory `Terraform/`.
-4. Complete your Ansible playbook, starting from the version you developed in
+   your recipe files (included Cloud-init) and in directory `Terraform/`.
+5. Complete your Ansible playbook, starting from the version you developed in
    [Lab-Ansible](https://gitedu.hesge.ch/lsds/teaching/bachelor/cloud-and-deployment/lab-ansible)
-   Task #10, to:
-    - expose the application portal IP (e.g., load-balancer IP) to the
+   Task #10, to (commit all realted files in directory `Ansible/`):
+    - expose the application portal's IP (i.e, the load-balancer's) to the
       Internet via `socat` or other mechanism of your choice;
-    - :question: **(TO-DO: What's better? Local or registry [Docker])?**
-      rebuild and transfer/download the application image to your VM instance.
-   Commit these files in directory `Ansible/`.
+    - rebuild and push the application images to your Docker Hub
+      repository. These shall be [`local_action`
+      tasks.](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_delegation.html)
+
+:bulb: References:
+  * Build and push Docker images: https://docs.docker.com/get-started/introduction/build-and-push-first-image/
+  * Ansible playbook delegation:
+    https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_delegation.html
+
+
+### Bonuses
+
+You will get bonus for any of the following improvements.
+
+  * Ask for the password when removing an account (unenroll) -- extra
+    safety. +0.2 points.
+  * Use password hashing in the back-end -- extra security. +0.1 points.
+  * Handle session expiration after a configurable amount of time (in
+    minutes). You can use an extra enrollment view's parameter
+    `expiration_time`. +0.3 points.
+  * Handle multiple sessions started from different browsers, e.g., private
+    navigation tab/window. +0.4 points.
+  * Support temporary disconnection via client session data stored in a Web
+    cookie (without the password) -- this requires some sort of [cryptographic
+    "nonce"](https://en.wikipedia.org/wiki/Cryptographic_nonce). +0.5 points.
 
 
-### Tests
+### Testing
 
 The following tests shall be passed by your implementation:
 
diff --git a/Terraform/conf/cloud-init.packages.yaml b/Terraform/conf/cloud-init.packages.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3caed8137c5ce7139e91dd788f2fa206ea29aea0
--- /dev/null
+++ b/Terraform/conf/cloud-init.packages.yaml
@@ -0,0 +1,20 @@
+#cloud-config
+---
+
+# package_update: true
+# package_upgrade: true
+
+groups:
+  - docker
+
+system_info:
+  default_user:
+    groups: [docker]
+
+# add any basic packages here:
+packages:
+  - curl
+  - nano
+  - ripgrep
+  - docker.io
+  - bash-completion
diff --git a/Terraform/conf/cloud-init.users.yaml b/Terraform/conf/cloud-init.users.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b675f4098514f5d4c57585c1e95b29083ce38020
--- /dev/null
+++ b/Terraform/conf/cloud-init.users.yaml
@@ -0,0 +1,15 @@
+#cloud-config
+---
+groups:
+  - terraform
+
+system_info:
+  default_user:
+    name: terraform
+    gecos: terraform
+    primary_group: terraform
+    groups: [users, admin, sudo]
+    shell: /bin/bash
+    sudo: ALL=(ALL) NOPASSWD:ALL
+    ssh_authorized_keys:
+      - <your-ssh-ed25519-or-RSA-public-key>
diff --git a/Terraform/main.tf b/Terraform/main.tf
new file mode 100644
index 0000000000000000000000000000000000000000..20bacf6fbbbed311d6c0c8ccd930c82ce0223741
--- /dev/null
+++ b/Terraform/main.tf
@@ -0,0 +1 @@
+# main.tf
diff --git a/Terraform/outputs.tf b/Terraform/outputs.tf
new file mode 100644
index 0000000000000000000000000000000000000000..237252c761f173f3ed90534c1afd40bf30bee3bd
--- /dev/null
+++ b/Terraform/outputs.tf
@@ -0,0 +1 @@
+# outputs.tf
diff --git a/Terraform/variables.tf b/Terraform/variables.tf
new file mode 100644
index 0000000000000000000000000000000000000000..57bf62f7a78dc418a07075d6ceb1e598bd20b883
--- /dev/null
+++ b/Terraform/variables.tf
@@ -0,0 +1 @@
+# variables.tf
diff --git a/app_arch_depl.png b/app_arch_depl.png
new file mode 100644
index 0000000000000000000000000000000000000000..422710ea26038ceb87e88f4eb4a97b102bba2863
Binary files /dev/null and b/app_arch_depl.png differ