-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1253 from spidernet-io/feature/moveip
Add move(migrate) egress IP cmd
- Loading branch information
Showing
14 changed files
with
708 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Copyright 2022 Authors of spidernet-io | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
var binName = filepath.Base(os.Args[0]) | ||
|
||
// rootCmd represents the base command. | ||
var rootCmd = &cobra.Command{ | ||
Use: binName, | ||
Short: "egress gateway ctl", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
}, | ||
} | ||
|
||
// Execute adds all child commands to the root command sets flags appropriately. | ||
// This is called by main.main(). It only needs to happen once to the rootCmd. | ||
func Execute() { | ||
moveCmd.Flags().StringVarP(&egressGatewayName, "egressGatewayName", "", "", "Specify the name of the egress gateway") | ||
moveCmd.Flags().StringVarP(&vipAddress, "vip", "", "", "Specify the VIP address to MoveEgressIP") | ||
moveCmd.Flags().StringVarP(&targetNode, "targetNode", "", "", "Specify the name of the node to MoveEgressIP the VIP to") | ||
|
||
rootCmd.AddCommand(vipCmd) | ||
vipCmd.AddCommand(moveCmd) | ||
|
||
if err := rootCmd.Execute(); err != nil { | ||
fmt.Println(err) | ||
os.Exit(1) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// Copyright 2022 Authors of spidernet-io | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package cmd | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/spf13/cobra" | ||
"k8s.io/apimachinery/pkg/types" | ||
"os" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"time" | ||
|
||
egressv1 "github.com/spidernet-io/egressgateway/pkg/k8s/apis/v1beta1" | ||
"github.com/spidernet-io/egressgateway/pkg/schema" | ||
) | ||
|
||
var vipCmd = &cobra.Command{ | ||
Use: "vip", | ||
Short: "vip resources", | ||
Long: "vip resources with specified parameters.", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
_ = cmd.Help() | ||
}, | ||
} | ||
|
||
type DisplayEgressIPData struct { | ||
IPv4 string | ||
IPv6 string | ||
Node string | ||
EgressGateway string | ||
} | ||
|
||
var egressGatewayName, vipAddress, targetNode string | ||
|
||
var moveCmd = &cobra.Command{ | ||
Use: "move", | ||
Short: "move --egressGatewayName <egress-gateway-name> --vip <vip-address> --targetNode <node-name>", | ||
Long: `move --egressGatewayName <egress-gateway-name> --vip <vip-address> --targetNode <node-name>`, | ||
PreRun: func(cmd *cobra.Command, args []string) { | ||
if egressGatewayName == "" || vipAddress == "" || targetNode == "" { | ||
fmt.Println("Error: egressGatewayName, vip address, and target node name must be specified") | ||
os.Exit(1) | ||
} | ||
}, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
fmt.Printf("Moving VIP %s to node %s...\n", vipAddress, targetNode) | ||
err := MoveEgressIP(egressGatewayName, vipAddress, targetNode) | ||
if err != nil { | ||
cmd.PrintErr("Move failed: ", err) | ||
os.Exit(1) | ||
} | ||
}, | ||
} | ||
|
||
func MoveEgressIP(egressGatewayName string, vipAddress, targetNode string) error { | ||
kubeConfig, err := ctrl.GetConfig() | ||
if err != nil { | ||
return fmt.Errorf("failed to load kubeconfig: %w", err) | ||
} | ||
cli, err := client.New(kubeConfig, client.Options{Scheme: schema.GetScheme()}) | ||
if err != nil { | ||
return fmt.Errorf("failed to create client: %w", err) | ||
} | ||
|
||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||
defer cancel() | ||
|
||
egw := egressv1.EgressGateway{} | ||
err = cli.Get(ctx, types.NamespacedName{Name: egressGatewayName}, &egw) | ||
if err != nil { | ||
return fmt.Errorf("failed to get EgressGateway: %w", err) | ||
} | ||
|
||
eipPair, err := findAndRemoveEIP(&egw, vipAddress, targetNode) | ||
if err != nil { | ||
return err | ||
} | ||
if eipPair == nil { | ||
fmt.Printf("Egress IP %s is currently on node %s, with no change.\n", vipAddress, targetNode) | ||
return nil | ||
} | ||
|
||
if err := moveEIPToNode(&egw, targetNode, eipPair); err != nil { | ||
return err | ||
} | ||
if err := cli.Status().Update(ctx, &egw); err != nil { | ||
return fmt.Errorf("failed to update EgressGateway status: %w", err) | ||
} | ||
|
||
for _, p := range eipPair.Policies { | ||
if p.Namespace == "" { | ||
policy := new(egressv1.EgressClusterPolicy) | ||
err := cli.Get(ctx, types.NamespacedName{ | ||
Namespace: p.Namespace, | ||
Name: p.Name, | ||
}, policy) | ||
policy.Status.Node = targetNode | ||
if err != nil { | ||
return fmt.Errorf("failed to get EgressClusterPolicy status: %w", err) | ||
} | ||
if err := cli.Status().Update(ctx, policy); err != nil { | ||
return fmt.Errorf("failed to update EgressClusterPolicy status: %w", err) | ||
} | ||
} else { | ||
policy := new(egressv1.EgressPolicy) | ||
err := cli.Get(ctx, types.NamespacedName{ | ||
Namespace: p.Namespace, | ||
Name: p.Name, | ||
}, policy) | ||
policy.Status.Node = targetNode | ||
if err != nil { | ||
return fmt.Errorf("failed to get EgressPolicy status: %w", err) | ||
} | ||
if err := cli.Status().Update(ctx, policy); err != nil { | ||
return fmt.Errorf("failed to update EgressPolicy status: %w", err) | ||
} | ||
} | ||
} | ||
|
||
fmt.Printf("Successfully moved VIP %s to node %s\n", vipAddress, targetNode) | ||
return nil | ||
} | ||
|
||
// findAndRemoveEIP finds the EIP and removes it from the current node. | ||
// Returns the found EIP and any error that occurs. | ||
func findAndRemoveEIP(egw *egressv1.EgressGateway, vipAddress string, targetNode string) (*egressv1.Eips, error) { | ||
for nodeIndex, node := range egw.Status.NodeList { | ||
for eipIndex, eip := range node.Eips { | ||
if node.Name == targetNode { | ||
return nil, nil | ||
} | ||
|
||
if eip.IPv4 == vipAddress || eip.IPv6 == vipAddress { | ||
foundEIP := egw.Status.NodeList[nodeIndex].Eips[eipIndex] | ||
egw.Status.NodeList[nodeIndex].Eips = append( | ||
egw.Status.NodeList[nodeIndex].Eips[:eipIndex], | ||
egw.Status.NodeList[nodeIndex].Eips[eipIndex+1:]..., | ||
) | ||
return &foundEIP, nil | ||
} | ||
} | ||
} | ||
return nil, fmt.Errorf("VIP %s not found in any node of EgressGateway", vipAddress) | ||
} | ||
|
||
// moveEIPToNode MoveEgressIP an EIP to the target node within the EgressGateway. | ||
// Returns any error that occurs. | ||
func moveEIPToNode(egw *egressv1.EgressGateway, targetNode string, eipPair *egressv1.Eips) error { | ||
found := false | ||
for i, node := range egw.Status.NodeList { | ||
if node.Name == targetNode { | ||
if string(egressv1.EgressTunnelReady) != node.Status { | ||
return fmt.Errorf("target node '%s' not ready, please select a ready node", targetNode) | ||
} | ||
|
||
egw.Status.NodeList[i].Eips = append(node.Eips, *eipPair) | ||
found = true | ||
break | ||
} | ||
} | ||
if !found { | ||
return fmt.Errorf("target node '%s' not found in EgressGateway", targetNode) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Copyright 2022 Authors of spidernet-io | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package main | ||
|
||
import ( | ||
"github.com/spidernet-io/egressgateway/cmd/egctl/cmd" | ||
) | ||
|
||
func main() { | ||
cmd.Execute() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# egctl cli reference | ||
|
||
`egctl` is a command line tool for managing EgressGateway related resources. | ||
|
||
## Command Overview | ||
|
||
### vip move | ||
|
||
Move a VIP to a specified node. | ||
|
||
* `--egressGatewayName`: Specifies the name of the EgressGateway. | ||
* `--vip`: The Egress IP address you want to move. | ||
* `--targetNode`: The name of the target EgressGateway Node where the Egress IP will take effect. | ||
|
||
```shell | ||
egctl vip move --egressGatewayName <egress-gateway-name> --vip <vip-address> --targetNode <node-name> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# egctl 命令行工具说明 | ||
|
||
`egctl` 是一个命令行工具,用于管理 EgressGateway 相关资源。 | ||
|
||
## 命令概述 | ||
|
||
### vip move | ||
|
||
移动 VIP 到指定的节点。 | ||
|
||
* `--egressGatewayName`: 指定 EgressGateway 的名称。 | ||
* `--vip`: 您想要移动的 Egress IP 地址。 | ||
* `--targetNode`: Egress IP 将生效的目标 EgressGateway 节点的名称。 | ||
|
||
```shell | ||
egctl vip move --egressGatewayName <egress-gateway-name> --vip <vip-address> --targetNode <node-name> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# Migration of Egress IP Between Gateway Nodes | ||
|
||
## Use Cases | ||
|
||
* With EgressGateway, we can select multiple Nodes as EgressNodes. When a Node requires maintenance, we can manually migrate the VIP of that Node to another Node using CLI commands. | ||
* For other reasons, when it's necessary to manually move a Node's VIP to another Node. | ||
|
||
## Steps for Use | ||
|
||
We examine the definition of EgressGateway by executing `kubectl get egw egressgateway -o yaml`. | ||
|
||
```yaml | ||
apiVersion: egressgateway.spidernet.io/v1beta1 | ||
kind: EgressGateway | ||
metadata: | ||
finalizers: | ||
- egressgateway.spidernet.io/egressgateway | ||
name: egressgateway | ||
spec: | ||
ippools: | ||
ipv4: | ||
- 10.6.91.1-10.6.93.125 | ||
ipv4DefaultEIP: 10.6.92.222 | ||
nodeSelector: | ||
selector: | ||
matchLabels: | ||
egress: "true" | ||
status: | ||
ipUsage: | ||
ipv4Free: 37 | ||
ipv4Total: 637 | ||
ipv6Free: 0 | ||
ipv6Total: 0 | ||
nodeList: | ||
- name: workstation2 | ||
status: Ready | ||
- name: workstation3 | ||
status: Ready | ||
eips: | ||
- ipv4: 10.6.92.209 | ||
policies: | ||
- name: policy-1 | ||
namespace: default | ||
``` | ||
Before the migration, the Egress IP was on the workstation2 node. | ||
```shell | ||
node@workstation:~$ kubectl get egp | ||
NAME GATEWAY IPV4 IPV6 EGRESS NODE | ||
policy-1 egressgateway 10.6.92.209 workstation3 | ||
``` | ||
|
||
We migrate the Egress IP of `workstation3` to the `workstation2` Node by executing the command below. | ||
|
||
```log | ||
kubectl exec -it egressgateway-controller-86c84f4858-b6dz4 bash | ||
egctl vip move --egressGatewayName egressgateway --vip 10.6.92.209 --targetNode workstation2 | ||
Moving VIP 10.6.92.209 to node workstation2... | ||
Successfully moved VIP 10.6.92.209 to node workstation2 | ||
``` | ||
|
||
After migration, the Egress IP node has been moved to the workstation2 node. | ||
|
||
```shell | ||
node@workstation:~$ kubectl get egress | ||
NAME GATEWAY IPV4 IPV6 EGRESS NODE | ||
policy-1 egressgateway 10.6.92.209 workstation2 | ||
``` |
Oops, something went wrong.
3a71684
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.
🚀 Deployed on https://65f2758b04fec7009f840b1e--egressgateway.netlify.app