Skip to content

Commit 7a9dd31

Browse files
vknabelrobertvolkmannGerrit91
authored
Use KubeVIP Load Balancing for Control Planes. (#87)
Co-authored-by: Robert Volkmann <[email protected]> Co-authored-by: Gerrit Schwerthelm <[email protected]>
1 parent fb54439 commit 7a9dd31

File tree

6 files changed

+96
-35
lines changed

6 files changed

+96
-35
lines changed

DEVELOPMENT.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ make push-to-capi-lab
2121
Before creating a cluster some manual steps are required beforehand: you need to allocate a node network and a firewall.
2222

2323
```bash
24-
make -C capi-lab node-network firewall
24+
make -C capi-lab node-network firewall control-plane-ip
2525
```
2626

2727
A basic cluster configuration that relies on `config/clusterctl-templates/cluster-template.yaml` and uses the aforementioned node network can be generated and applied to the management cluster using a make target.
@@ -218,6 +218,7 @@ export METAL_API_URL=
218218
export METAL_PARTITION=
219219
export METAL_PROJECT_ID=
220220
export METAL_NODE_NETWORK_ID=
221+
export CONTROL_PLANE_IP=
221222

222223
export FIREWALL_MACHINE_IMAGE=
223224
export FIREWALL_MACHINE_SIZE=
@@ -246,6 +247,7 @@ Create firewall if needed:
246247
```bash
247248
metalctl project create --name $project_name --tenant $tenant_name --description "Cluster API test project"
248249
metalctl network allocate --description "Node network for $CLUSTER_NAME" --name $CLUSTER_NAME --project $METAL_PROJECT_ID --partition $METAL_PARTITION
250+
metalctl network ip create --network internet --project $METAL_PROJECT_ID --name "$CLUSTER_NAME-vip" --type static -o template --template "{{ .ipaddress }}"
249251
metalctl firewall create --description "Firewall for $CLUSTER_NAME cluster" --name firewall-$CLUSTER_NAME --hostname firewall-$CLUSTER_NAME --project $METAL_PROJECT_ID --partition $METAL_PARTITION --image $FIREWALL_MACHINE_IMAGE --size $FIREWALL_MACHINE_SIZE --firewall-rules-file $repo_path/config/target-cluster/firewall-rules.yaml --networks internet,$METAL_NODE_NETWORK_ID
250252
```
251253

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,19 @@ clusterctl init --infrastructure metal-stack
5454
5555
A node network needs to be created.
5656
```bash
57+
export CLUSTER_NAME=<cluster-name>
5758
export METAL_PARTITION=<partition>
5859
export METAL_PROJECT_ID=<project-id>
59-
metalctl network allocate --description "<description>" --name <name> --project $METAL_PROJECT_ID --partition $METAL_PARTITION
60+
metalctl network allocate --description "Node network for $CLUSTER_NAME" --name $CLUSTER_NAME --project $METAL_PROJECT_ID --partition $METAL_PARTITION
6061

6162
# export environment variable for use in the next steps
62-
export METAL_NODE_NETWORK_ID=$(metalctl network list --name <name> -o template --template '{{ .id }}')
63+
export METAL_NODE_NETWORK_ID=$(metalctl network list --name $CLUSTER_NAME -o template --template '{{ .id }}')
64+
```
65+
66+
Allocate a VIP for the control plane.
67+
68+
```bash
69+
export CONTROL_PLANE_IP=$(metalctl network ip create --network internet --project $METAL_PROJECT_ID --name "$CLUSTER_NAME-vip" --type static -o template --template "{{ .ipaddress }}")
6370
```
6471

6572
A firewall needs to be created with appropriate firewall rules. An example can be found at [firewall-rules.yaml](config/target-cluster/firewall-rules.yaml).
@@ -68,14 +75,14 @@ A firewall needs to be created with appropriate firewall rules. An example can b
6875
export FIREWALL_MACHINE_IMAGE=<firewall-image>
6976
export FIREWALL_MACHINE_SIZE=<machine-size>
7077

71-
metalctl firewall create --description <description> --name <name> --hostname <hostname> --project $METAL_PROJECT_ID --partition $METAL_PARTITION --image $FIREWALL_MACHINE_IMAGE --size $FIREWALL_MACHINE_SIZE --firewall-rules-file=<rules.yaml> --networks internet,$METAL_NODE_NETWORK_ID
78+
metalctl firewall create --description "Firewall for $CLUSTER_NAME" --name "$CLUSTER_NAME-fw" --hostname "$CLUSTER_NAME-fw" --project $METAL_PROJECT_ID --partition $METAL_PARTITION --image $FIREWALL_MACHINE_IMAGE --size $FIREWALL_MACHINE_SIZE --firewall-rules-file=<rules.yaml> --networks internet,$METAL_NODE_NETWORK_ID
7279
```
7380

7481
For your first cluster, it is advised to start with our generated template. Ensure that the namespaced cluster name is unique within the metal stack project.
7582

7683
```bash
7784
# display required environment variables
78-
clusterctl generate cluster <cluster-name> --infrastructure metal-stack --list-variables
85+
clusterctl generate cluster $CLUSTER_NAME --infrastructure metal-stack --list-variables
7986

8087
# set additional environment variables
8188
export CONTROL_PLANE_MACHINE_IMAGE=<machine-image>
@@ -84,7 +91,7 @@ export WORKER_MACHINE_IMAGE=<machine-image>
8491
export WORKER_MACHINE_SIZE=<machine-size>
8592

8693
# generate manifest
87-
clusterctl generate cluster <cluster-name> --kubernetes-version v1.30.6 --infrastructure metal-stack
94+
clusterctl generate cluster $CLUSTER_NAME --kubernetes-version v1.30.6 --infrastructure metal-stack
8895
```
8996

9097
Apply the generated manifest from the `clusterctl` output.
@@ -179,10 +186,10 @@ EOF
179186

180187
### I need to know the Control Plane IP address in advance. Can I provide a static IP address in advance?
181188

182-
Yes, simply create a static IP address and set it to `metalstackcluster/<name>.spec.controlPlaneIP`.
189+
Yes, simply create a static IP address and set it to `metalstackcluster/$CLUSTER_NAME.spec.controlPlaneIP`.
183190

184191
```bash
185-
metalctl network ip create --name <name> --project $METAL_PROJECT_ID --type static
192+
metalctl network ip create --name $CLUSTER_NAME-vip --project $METAL_PROJECT_ID --type static
186193
```
187194

188195
### I'd like to have a specific Pod CIDR. How can I achieve this?

capi-lab/Makefile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ METALCTL_HMAC_AUTH_TYPE=Metal-Edit
1616
METAL_PARTITION ?= mini-lab
1717
METAL_PROJECT_ID ?= 00000000-0000-0000-0000-000000000001
1818

19-
CONTROL_PLANE_MACHINE_IMAGE ?= ubuntu-24.4
19+
CONTROL_PLANE_MACHINE_IMAGE ?= ubuntu-24.0-k8s-1.31.6
2020
CONTROL_PLANE_MACHINE_SIZE ?= v1-small-x86
21-
WORKER_MACHINE_IMAGE ?= ubuntu-24.4
21+
WORKER_MACHINE_IMAGE ?= ubuntu-24.0-k8s-1.31.6
2222
WORKER_MACHINE_SIZE ?= v1-small-x86
2323

2424
IMG ?= ghcr.io/metal-stack/cluster-api-metal-stack-controller:latest
@@ -60,9 +60,14 @@ firewall:
6060
node-network:
6161
metalctl network allocate --description "node network for metal-test cluster" --name metal-test --project 00000000-0000-0000-0000-000000000001 --partition mini-lab
6262

63+
.PHONY: control-plane-ip
64+
control-plane-ip:
65+
metalctl network ip create --network internet-mini-lab --project $(METAL_PROJECT_ID) --name "$(CLUSTER_NAME)-vip" --type static -o template --template "{{ .ipaddress }}"
66+
6367
.PHONY: apply-sample-cluster
6468
apply-sample-cluster:
6569
$(eval METAL_NODE_NETWORK_ID = $(shell metalctl network list --name metal-test -o template --template '{{ .id }}'))
70+
$(eval CONTROL_PLANE_IP = $(shell metalctl network ip list --name "$(CLUSTER_NAME)-vip" -o template --template '{{ .id }}'))
6671
clusterctl generate cluster metal-test \
6772
--kubeconfig=$(KUBECONFIG) \
6873
--worker-machine-count 1 \
@@ -74,6 +79,7 @@ apply-sample-cluster:
7479
.PHONY: delete-sample-cluster
7580
delete-sample-cluster:
7681
$(eval METAL_NODE_NETWORK_ID = $(shell metalctl network list --name metal-test -o template --template '{{ .id }}'))
82+
$(eval CONTROL_PLANE_IP = $(shell metalctl network ip list --name "$(CLUSTER_NAME)-vip" -o template --template '{{ .id }}'))
7783
clusterctl generate cluster metal-test \
7884
--kubeconfig=$(KUBECONFIG) \
7985
--worker-machine-count 1 \
@@ -91,4 +97,5 @@ mtu-fix:
9197
deploy-metal-ccm:
9298
$(eval METAL_CLUSTER_ID = $(shell kubectl get metalstackclusters.infrastructure.cluster.x-k8s.io metal-test -ojsonpath='{.metadata.uid}'))
9399
$(eval METAL_NODE_NETWORK_ID = $(shell metalctl network list --name metal-test -o template --template '{{ .id }}'))
100+
$(eval CONTROL_PLANE_IP = $(shell metalctl network ip list --name "$(CLUSTER_NAME)-vip" -o template --template '{{ .id }}'))
94101
cat metal-ccm.yaml | envsubst | kubectl --kubeconfig=.capms-cluster-kubeconfig.yaml apply -f -

config/clusterctl-templates/cluster-template.yaml

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ spec:
2828
projectID: ${METAL_PROJECT_ID}
2929
partition: ${METAL_PARTITION}
3030
nodeNetworkID: ${METAL_NODE_NETWORK_ID}
31+
controlPlaneIP: ${CONTROL_PLANE_IP}
3132
---
3233
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
3334
kind: MetalStackMachineTemplate
@@ -80,10 +81,75 @@ spec:
8081
kubeletExtraArgs:
8182
cloud-provider: external
8283
joinConfiguration:
83-
controlPlane: {}
84+
controlPlane:
85+
localAPIEndpoint:
86+
advertiseAddress: 127.0.0.1
87+
bindPort: 443
8488
nodeRegistration:
8589
kubeletExtraArgs:
8690
cloud-provider: external
91+
files:
92+
- path: /etc/kubernetes/manifests/kubevip.yaml
93+
owner: root:root
94+
permissions: "0644"
95+
content: |
96+
apiVersion: v1
97+
kind: Pod
98+
metadata:
99+
name: kube-vip
100+
namespace: kube-system
101+
spec:
102+
containers:
103+
- args:
104+
- manager
105+
env:
106+
- name: vip_arp
107+
value: "false"
108+
- name: port
109+
value: "443"
110+
- name: vip_interface
111+
value: lo
112+
- name: cp_enable
113+
value: "true"
114+
- name: cp_namespace
115+
value: kube-system
116+
- name: bgp_enable
117+
value: "true"
118+
- name: bgp_routerid
119+
value: 127.0.0.1
120+
- name: bgp_as
121+
value: "METAL_MACHINE_ASN"
122+
- name: bgp_peeraddress
123+
value: 127.0.0.1
124+
- name: bgp_peerpass
125+
- name: bgp_peeras
126+
value: "METAL_MACHINE_ASN"
127+
- name: address
128+
value: ${CONTROL_PLANE_IP}
129+
image: ghcr.io/kube-vip/kube-vip:v0.8.10
130+
imagePullPolicy: IfNotPresent
131+
name: kube-vip
132+
resources: {}
133+
securityContext:
134+
capabilities:
135+
add:
136+
- NET_ADMIN
137+
- NET_RAW
138+
drop:
139+
- ALL
140+
volumeMounts:
141+
- mountPath: /etc/kubernetes/admin.conf
142+
name: kubeconfig
143+
hostAliases:
144+
- hostnames:
145+
- kubernetes
146+
ip: 127.0.0.1
147+
hostNetwork: true
148+
volumes:
149+
- hostPath:
150+
path: /etc/kubernetes/admin.conf
151+
name: kubeconfig
152+
87153
---
88154
apiVersion: cluster.x-k8s.io/v1beta1
89155
kind: MachineDeployment

config/clusterctl-templates/example_variables.rc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export FIREWALL_MACHINE_SIZE=v1-small-x86
1010
export FIREWALL_MACHINE_IMAGE=
1111
export FIREWALL_NETWORKS=[internet]
1212

13+
export CONTROL_PLANE_IP=203.0.113.130
1314
export CONTROL_PLANE_MACHINE_SIZE=v1-small-x86
1415
export CONTROL_PLANE_MACHINE_IMAGE=ubuntu-24.4
1516

internal/controller/metalstackmachine_controller.go

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ import (
4646
"github.com/go-logr/logr"
4747
"github.com/metal-stack/cluster-api-provider-metal-stack/api/v1alpha1"
4848
metalgo "github.com/metal-stack/metal-go"
49-
ipmodels "github.com/metal-stack/metal-go/api/client/ip"
5049
metalmachine "github.com/metal-stack/metal-go/api/client/machine"
5150
"github.com/metal-stack/metal-go/api/models"
5251
"github.com/metal-stack/metal-lib/pkg/pointer"
@@ -79,15 +78,8 @@ type machineReconciler struct {
7978
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=metalstackmachines/finalizers,verbs=update
8079
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
8180

82-
// Reconcile is part of the main kubernetes reconciliation loop which aims to
83-
// move the current state of the cluster closer to the desired state.
84-
// TODO(user): Modify the Reconcile function to compare the state specified by
85-
// the MetalStackMachine object against the actual cluster state, and then
86-
// perform operations to make the cluster state reflect the state specified by
87-
// the user.
88-
//
89-
// For more details, check Reconcile and its Result here:
90-
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
81+
// Reconile reconciles a MetalStackMachine object.
82+
// Creates, updates and deletes the actual metalstack infra machine entities.
9183
func (r *MetalStackMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
9284
var (
9385
log = ctrllog.FromContext(ctx)
@@ -440,20 +432,6 @@ func (r *machineReconciler) create() (*models.V1MachineResponse, error) {
440432
}
441433
)
442434

443-
if util.IsControlPlaneMachine(r.clusterMachine) {
444-
ips = append(ips, r.infraCluster.Spec.ControlPlaneEndpoint.Host)
445-
446-
resp, err := r.metalClient.IP().FindIP(ipmodels.NewFindIPParams().WithID(r.infraCluster.Spec.ControlPlaneEndpoint.Host).WithContext(r.ctx), nil)
447-
if err != nil {
448-
return nil, fmt.Errorf("unable to lookup control plane ip: %w", err)
449-
}
450-
451-
nws = append(nws, &models.V1MachineAllocationNetwork{
452-
Autoacquire: ptr.To(false),
453-
Networkid: resp.Payload.Networkid,
454-
})
455-
}
456-
457435
resp, err := r.metalClient.Machine().AllocateMachine(metalmachine.NewAllocateMachineParamsWithContext(r.ctx).WithBody(&models.V1MachineAllocateRequest{
458436
Partitionid: &r.infraCluster.Spec.Partition,
459437
Projectid: &r.infraCluster.Spec.ProjectID,

0 commit comments

Comments
 (0)