-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ Add support for kube-vip #320
Changes from 22 commits
ecc3976
843f036
5d41478
bf68262
9751345
be61a92
d95b01a
088bbf9
a6c222c
40c7de0
b68c838
a0fc851
b05171b
7d7c0b7
f3f5d78
857424f
277e4a4
d1b8a2b
d552aad
ae0ed29
f1fb7a9
84f9d04
4509155
1e09a52
467fadc
98c29cd
b0cff13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
apiVersion: v1 | ||
kind: ConfigMap | ||
metadata: | ||
name: manager-config | ||
namespace: system | ||
data: | ||
EIP_MANAGEMENT: "${EIP_MANAGEMENT:=CPEM}" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,5 @@ spec: | |
envFrom: | ||
- secretRef: | ||
name: manager-api-credentials | ||
- configMapRef: | ||
name: manager-config |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -21,6 +21,7 @@ import ( | |||||
"errors" | ||||||
"fmt" | ||||||
"net/http" | ||||||
"os" | ||||||
"strings" | ||||||
"time" | ||||||
|
||||||
|
@@ -222,7 +223,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) { | ||||||
log := ctrl.LoggerFrom(ctx, "machine", machineScope.Machine.Name, "cluster", machineScope.Cluster.Name) | ||||||
log.Info("Reconciling PacketMachine") | ||||||
|
||||||
|
@@ -316,21 +317,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 the node is a control plan we need the elastic IP | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
// 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 os.Getenv("EIP_MANAGEMENT") == "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 | ||||||
} | ||||||
|
@@ -362,6 +363,13 @@ func (r *PacketMachineReconciler) reconcile(ctx context.Context, machineScope *s | |||||
machineScope.SetProviderID(dev.ID) | ||||||
machineScope.SetInstanceStatus(infrav1.PacketResourceStatus(dev.State)) | ||||||
|
||||||
if os.Getenv("EIP_MANAGEMENT") == "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...)) | ||||||
|
||||||
|
@@ -376,22 +384,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 os.Getenv("EIP_MANAGEMENT") == "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) | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ import ( | |
"net" | ||
"net/http" | ||
"os" | ||
"strconv" | ||
"strings" | ||
"text/template" | ||
|
||
|
@@ -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 ( | ||
|
@@ -230,6 +234,68 @@ 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 && bgpConfig != nil && bgpConfig.ID != "" && strings.ToLower(bgpConfig.Status) != "disabled" { | ||
return nil | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? 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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to add this nolint comment back in.