Skip to content
This repository was archived by the owner on Aug 12, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ecc3976
init
davidspek Apr 8, 2022
843f036
run make generate
davidspek Apr 8, 2022
5d41478
don't have CCM manage the control plane EIP
davidspek Apr 8, 2022
bf68262
fix linting errors
davidspek Apr 8, 2022
9751345
remove comments from template
davidspek Apr 8, 2022
be61a92
Add facility to CCM config so EIPs can be created for LoadBalancer se…
davidspek Apr 8, 2022
d95b01a
pin kube-vip version
davidspek Apr 8, 2022
088bbf9
fix hardcoded project id and make kube-vip version configurable
davidspek Apr 8, 2022
a6c222c
Clean up scripting, fix awk, add ip routes for IBX datacenters
cprivitere Apr 8, 2022
40c7de0
remove caching of metadata where it isn't used
cprivitere Apr 8, 2022
b68c838
Remove echo used for debuggin
cprivitere Apr 8, 2022
a0fc851
Adding a space for readability.
cprivitere Apr 8, 2022
b05171b
Have kubeadm ignore manifests directory already existing.
cprivitere Apr 8, 2022
7d7c0b7
run make generate
davidspek Apr 8, 2022
f3f5d78
revert to non-kubevip state
cprivitere Apr 8, 2022
857424f
split kube-vip version to separate template
cprivitere Apr 8, 2022
277e4a4
run make generate
cprivitere Apr 8, 2022
d1b8a2b
Generate kube-vip template via kustomize
cprivitere Apr 8, 2022
d552aad
Add ignore preflight errors to base template as it applies to all clu…
cprivitere Apr 8, 2022
ae0ed29
Update templates to set up BGP routes in IBX datacenters, update CPEM…
cprivitere Apr 8, 2022
f1fb7a9
Further refinements to the templates. bgp sections are only for kube-…
cprivitere Apr 8, 2022
84f9d04
make EIP management configurable
davidspek Apr 11, 2022
4509155
add suggested changes
davidspek Apr 21, 2022
1e09a52
run make generate
davidspek May 2, 2022
467fadc
fix lint error
davidspek May 3, 2022
98c29cd
remove services from kube-vip config
davidspek May 4, 2022
b0cff13
Convert to having the EIP_MANAGEMENT variable as part of the packetcl…
cprivitere May 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ generate: ## Generate code

.PHONY: generate-templates
generate-templates: $(KUSTOMIZE) ## Generate cluster templates
$(KUSTOMIZE) build templates/experimental-kube-vip --load-restrictor LoadRestrictionsNone > templates/cluster-template-kube-vip.yaml
$(KUSTOMIZE) build templates/experimental-crs-cni --load-restrictor LoadRestrictionsNone > templates/cluster-template-crs-cni.yaml
$(KUSTOMIZE) build templates/addons/calico > templates/addons/calico.yaml

Expand Down
9 changes: 9 additions & 0 deletions api/v1alpha3/packetcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import (
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
)

// VIPManagerType describes if the VIP will be managed by CPEM or kube-vip
type VIPManagerType string

// PacketClusterSpec defines the desired state of PacketCluster
type PacketClusterSpec struct {
// ProjectID represents the Packet Project where this cluster will be placed into
Expand All @@ -32,6 +35,12 @@ type PacketClusterSpec struct {
// ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.
// +optional
ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"`

// VIPManager represents whether this cluster uses CPEM or kube-vip to
// manage its vip for the api server IP
// +kubebuilder:validation:Enum=CPEM;KUBE_VIP
// +kubebuilder:default:=CPEM
VIPManager VIPManagerType `json:"vipManager"`
}

// PacketClusterStatus defines the observed state of PacketCluster
Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha3/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions api/v1beta1/packetcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const (
NetworkInfrastructureReadyCondition clusterv1.ConditionType = "NetworkInfrastructureReady"
)

// VIPManagerType describes if the VIP will be managed by CPEM or kube-vip
type VIPManagerType string

// PacketClusterSpec defines the desired state of PacketCluster
type PacketClusterSpec struct {
// ProjectID represents the Packet Project where this cluster will be placed into
Expand All @@ -37,6 +40,12 @@ type PacketClusterSpec struct {
// ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.
// +optional
ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"`

// VIPManager represents whether this cluster uses CPEM or kube-vip to
// manage its vip for the api server IP
// +kubebuilder:validation:Enum=CPEM;KUBE_VIP
// +kubebuilder:default:=CPEM
VIPManager VIPManagerType `json:"vipManager"`
}

// PacketClusterStatus defines the observed state of PacketCluster
Expand Down
7 changes: 7 additions & 0 deletions api/v1beta1/packetcluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ func (c *PacketCluster) ValidateUpdate(oldRaw runtime.Object) error {
)
}

if !reflect.DeepEqual(c.Spec.VIPManager, old.Spec.VIPManager) {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "VIPManager"),
c.Spec.VIPManager, "field is immutable"),
)
}

if len(allErrs) == 0 {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,17 @@ spec:
description: ProjectID represents the Packet Project where this cluster
will be placed into
type: string
vipManager:
default: CPEM
description: VIPManager represents whether this cluster uses CPEM
or kube-vip to manage its vip for the api server IP
enum:
- CPEM
- KUBE_VIP
type: string
required:
- projectID
- vipManager
type: object
status:
description: PacketClusterStatus defines the observed state of PacketCluster
Expand Down Expand Up @@ -134,8 +143,17 @@ spec:
description: ProjectID represents the Packet Project where this cluster
will be placed into
type: string
vipManager:
default: CPEM
description: VIPManager represents whether this cluster uses CPEM
or kube-vip to manage its vip for the api server IP
enum:
- CPEM
- KUBE_VIP
type: string
required:
- projectID
- vipManager
type: object
status:
description: PacketClusterStatus defines the observed state of PacketCluster
Expand Down
2 changes: 1 addition & 1 deletion config/default/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ patchesStrategicMerge:
- manager_pull_policy.yaml
- manager_webhook_patch.yaml
- webhookcainjection_patch.yaml
- manager_credentials_patch.yaml
- manager_credentials_config_patch.yaml

vars:
- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
Expand Down
2 changes: 2 additions & 0 deletions config/default/kustomizeconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
varReference:
- kind: Deployment
path: spec/template/spec/volumes/secret/secretName
- kind: Deployment
path: spec/template/spec/volumes/configMap/configMapName
7 changes: 7 additions & 0 deletions controllers/packetcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ func (r *PacketClusterReconciler) reconcileNormal(ctx context.Context, clusterSc
}
}

if clusterScope.PacketCluster.Spec.VIPManager == "KUBE_VIP" {
if err := r.PacketClient.EnableProjectBGP(packetCluster.Spec.ProjectID); err != nil {
log.Error(err, "error enabling bgp for project")
return ctrl.Result{}, err
}
}

clusterScope.PacketCluster.Status.Ready = true
conditions.MarkTrue(packetCluster, infrav1.NetworkInfrastructureReadyCondition)

Expand Down
54 changes: 30 additions & 24 deletions controllers/packetmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func (r *PacketMachineReconciler) PacketClusterToPacketMachines(ctx context.Cont
}
}

func (r *PacketMachineReconciler) reconcile(ctx context.Context, machineScope *scope.MachineScope) (ctrl.Result, error) { //nolint:gocyclo
func (r *PacketMachineReconciler) reconcile(ctx context.Context, machineScope *scope.MachineScope) (ctrl.Result, error) { //nolint:gocyclo,maintidx
log := ctrl.LoggerFrom(ctx, "machine", machineScope.Machine.Name, "cluster", machineScope.Cluster.Name)
log.Info("Reconciling PacketMachine")

Expand Down Expand Up @@ -316,21 +316,21 @@ func (r *PacketMachineReconciler) reconcile(ctx context.Context, machineScope *s
ExtraTags: packet.DefaultCreateTags(machineScope.Namespace(), machineScope.Machine.Name, machineScope.Cluster.Name),
}

// TODO: see if this can be removed with kube-vip in place
// when the node is a control plan we should check if the elastic ip
// for this cluster is not assigned. If it is free we can prepare the
// current node to use it.
// when a node is a control plane node we need the elastic IP
// to template out the kube-vip deployment
if machineScope.IsControlPlane() {
controlPlaneEndpoint, _ = r.PacketClient.GetIPByClusterIdentifier(
machineScope.Cluster.Namespace,
machineScope.Cluster.Name,
machineScope.PacketCluster.Spec.ProjectID)
if len(controlPlaneEndpoint.Assignments) == 0 {
a := corev1.NodeAddress{
Type: corev1.NodeExternalIP,
Address: controlPlaneEndpoint.Address,
if machineScope.PacketCluster.Spec.VIPManager == "CPEM" {
if len(controlPlaneEndpoint.Assignments) == 0 {
a := corev1.NodeAddress{
Type: corev1.NodeExternalIP,
Address: controlPlaneEndpoint.Address,
}
addrs = append(addrs, a)
}
addrs = append(addrs, a)
}
createDeviceReq.ControlPlaneEndpoint = controlPlaneEndpoint.Address
}
Expand Down Expand Up @@ -362,6 +362,13 @@ func (r *PacketMachineReconciler) reconcile(ctx context.Context, machineScope *s
machineScope.SetProviderID(dev.ID)
machineScope.SetInstanceStatus(infrav1.PacketResourceStatus(dev.State))

if machineScope.PacketCluster.Spec.VIPManager == "KUBE_VIP" {
if err := r.PacketClient.EnsureNodeBGPEnabled(dev.ID); err != nil {
// Do not treat an error enabling bgp on machine as fatal
return ctrl.Result{RequeueAfter: time.Second * 20}, fmt.Errorf("failed to enable bpg on machine %s: %w", machineScope.Name(), err)
}
}

deviceAddr := r.PacketClient.GetDeviceAddresses(dev)
machineScope.SetAddresses(append(addrs, deviceAddr...))

Expand All @@ -376,22 +383,21 @@ func (r *PacketMachineReconciler) reconcile(ctx context.Context, machineScope *s
case infrav1.PacketResourceStatusRunning:
log.Info("Machine instance is active", "instance-id", machineScope.GetInstanceID())

// TODO: see if this can be removed with kube-vip in place
// This logic is here because an elastic ip can be assigned only an
// active node. It needs to be a control plane and the IP should not be
// assigned to anything at this point.
controlPlaneEndpoint, _ = r.PacketClient.GetIPByClusterIdentifier(
machineScope.Cluster.Namespace,
machineScope.Cluster.Name,
machineScope.PacketCluster.Spec.ProjectID)
if len(controlPlaneEndpoint.Assignments) == 0 && machineScope.IsControlPlane() {
if _, _, err := r.PacketClient.DeviceIPs.Assign(dev.ID, &packngo.AddressStruct{
Address: controlPlaneEndpoint.Address,
}); err != nil {
log.Error(err, "err assigining elastic ip to control plane. retrying...")
return ctrl.Result{RequeueAfter: time.Second * 20}, nil
if machineScope.PacketCluster.Spec.VIPManager == "CPEM" {
controlPlaneEndpoint, _ = r.PacketClient.GetIPByClusterIdentifier(
machineScope.Cluster.Namespace,
machineScope.Cluster.Name,
machineScope.PacketCluster.Spec.ProjectID)
if len(controlPlaneEndpoint.Assignments) == 0 && machineScope.IsControlPlane() {
if _, _, err := r.PacketClient.DeviceIPs.Assign(dev.ID, &packngo.AddressStruct{
Address: controlPlaneEndpoint.Address,
}); err != nil {
log.Error(err, "err assigining elastic ip to control plane. retrying...")
return ctrl.Result{RequeueAfter: time.Second * 20}, nil
}
}
}

machineScope.SetReady()
conditions.MarkTrue(machineScope.PacketMachine, infrav1.DeviceReadyCondition)

Expand Down
68 changes: 68 additions & 0 deletions pkg/cloud/packet/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"net"
"net/http"
"os"
"strconv"
"strings"
"text/template"

Expand All @@ -38,6 +39,9 @@ const (
apiTokenVarName = "PACKET_API_KEY" //nolint:gosec
clientName = "CAPP-v1beta1"
ipxeOS = "custom_ipxe"
envVarLocalASN = "METAL_LOCAL_ASN"
envVarBGPPass = "METAL_BGP_PASS" //nolint:gosec
DefaultLocalASN = 65000
)

var (
Expand Down Expand Up @@ -230,6 +234,70 @@ func (p *Client) CreateIP(namespace, clusterName, projectID, facility string) (n
return ip, nil
}

// enableBGP enable bgp on the project
func (p *Client) EnableProjectBGP(projectID string) error {
// first check if it is enabled before trying to create it
bgpConfig, _, err := p.BGPConfig.Get(projectID, &packngo.GetOptions{})
// if we already have a config, just return
// we need some extra handling logic because the API always returns 200, even if
// not BGP config is in place.
// We treat it as valid config already exists only if ALL of the above is true:
// - no error
// - bgpConfig struct exists
// - bgpConfig struct has non-blank ID
// - bgpConfig struct does not have Status=="disabled"
if err != nil {
return err
} else if bgpConfig != nil && bgpConfig.ID != "" && strings.ToLower(bgpConfig.Status) != "disabled" {
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will we do if the BGPConfig Get request fails? Invalid project or token, timeout, API availability issues?
Let's handle err != nil here. We should log at the very least.

If BGP can not be enabled, we can return that error and this error will cascade through the reconciliation loop and we will await the next reconciliation loop to attempt to enable BGP. That sounds good. The log messages will be helpful if the resource can not resolve because of this.


// get the local ASN
localASN := os.Getenv(envVarLocalASN)
var outLocalASN int
switch {
case localASN != "":
localASNNo, err := strconv.Atoi(localASN)
if err != nil {
return fmt.Errorf("env var %s must be a number, was %s: %w", envVarLocalASN, localASN, err)
}
outLocalASN = localASNNo
default:
outLocalASN = DefaultLocalASN
}

var outBGPPass string
bgpPass := os.Getenv(envVarBGPPass)
if bgpPass != "" {
outBGPPass = bgpPass
}

// we did not have a valid one, so create it
req := packngo.CreateBGPConfigRequest{
Asn: outLocalASN,
Md5: outBGPPass,
DeploymentType: "local",
UseCase: "kubernetes-load-balancer",
}
_, err = p.BGPConfig.Create(projectID, req)
return err
}

// ensureNodeBGPEnabled check if the node has bgp enabled, and set it if it does not
func (p *Client) EnsureNodeBGPEnabled(id string) error {
// fortunately, this is idempotent, so just create
req := packngo.CreateBGPSessionRequest{
AddressFamily: "ipv4",
}
_, response, err := p.BGPSessions.Create(id, req)
// if we already had one, then we can ignore the error
// this really should be a 409, but 422 is what is returned
if response.StatusCode == 422 && strings.Contains(fmt.Sprintf("%s", err), "already has session") {
err = nil
}
return err
}

func (p *Client) GetIPByClusterIdentifier(namespace, name, projectID string) (packngo.IPAddressReservation, error) {
var err error
var reservedIP packngo.IPAddressReservation
Expand Down
10 changes: 5 additions & 5 deletions templates/addons/calico.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2497,7 +2497,7 @@ spec:
value: node
- name: DATASTORE_TYPE
value: kubernetes
image: quay.io/calico/kube-controllers:v3.20.4
image: quay.io/calico/kube-controllers:v3.20.5
livenessProbe:
exec:
command:
Expand Down Expand Up @@ -2603,7 +2603,7 @@ spec:
- configMapRef:
name: kubernetes-services-endpoint
optional: true
image: quay.io/calico/node:v3.20.4
image: quay.io/calico/node:v3.20.5
lifecycle:
preStop:
exec:
Expand Down Expand Up @@ -2677,7 +2677,7 @@ spec:
- configMapRef:
name: kubernetes-services-endpoint
optional: true
image: quay.io/calico/cni:v3.20.4
image: quay.io/calico/cni:v3.20.5
name: upgrade-ipam
securityContext:
privileged: true
Expand Down Expand Up @@ -2711,7 +2711,7 @@ spec:
- configMapRef:
name: kubernetes-services-endpoint
optional: true
image: quay.io/calico/cni:v3.20.4
image: quay.io/calico/cni:v3.20.5
name: install-cni
securityContext:
privileged: true
Expand All @@ -2720,7 +2720,7 @@ spec:
name: cni-bin-dir
- mountPath: /host/etc/cni/net.d
name: cni-net-dir
- image: quay.io/calico/pod2daemon-flexvol:v3.20.4
- image: quay.io/calico/pod2daemon-flexvol:v3.20.5
name: flexvol-driver
securityContext:
privileged: true
Expand Down
Loading