mirror of
https://github.com/kubenetworks/kubevpn.git
synced 2025-10-05 07:16:54 +08:00
feat: split connect into connect and proxy
This commit is contained in:
@@ -152,7 +152,7 @@ reviews ClusterIP 172.27.255.155 <none> 9080/TCP 9m6s app=
|
||||
### Reverse proxy
|
||||
|
||||
```shell
|
||||
➜ ~ kubevpn connect --workloads deployment/productpage
|
||||
➜ ~ kubevpn proxy deployment/productpage
|
||||
got cidr from cache
|
||||
traffic manager not exist, try to create it...
|
||||
pod [kubevpn-traffic-manager] status is Running
|
||||
@@ -207,7 +207,7 @@ Hello world!%
|
||||
Support HTTP, GRPC and WebSocket etc. with specific header `"a: 1"` will route to your local machine
|
||||
|
||||
```shell
|
||||
➜ ~ kubevpn connect --workloads=deployment/productpage --headers a=1
|
||||
➜ ~ kubevpn proxy deployment/productpage --headers a=1
|
||||
got cidr from cache
|
||||
traffic manager not exist, try to create it...
|
||||
pod [kubevpn-traffic-manager] status is Running
|
||||
|
@@ -150,7 +150,7 @@ reviews ClusterIP 172.27.255.155 <none> 9080/TCP 9m6s app=
|
||||
### 反向代理
|
||||
|
||||
```shell
|
||||
➜ ~ kubevpn connect --workloads deployment/productpage
|
||||
➜ ~ kubevpn proxy deployment/productpage
|
||||
got cidr from cache
|
||||
traffic manager not exist, try to create it...
|
||||
pod [kubevpn-traffic-manager] status is Running
|
||||
@@ -205,7 +205,7 @@ Hello world!%
|
||||
支持 HTTP, GRPC 和 WebSocket 等, 携带了指定 header `"a: 1"` 的流量,将会路由到本地
|
||||
|
||||
```shell
|
||||
➜ ~ kubevpn connect --workloads=deployment/productpage --headers a=1
|
||||
➜ ~ kubevpn proxy deployment/productpage --headers a=1
|
||||
got cidr from cache
|
||||
traffic manager not exist, try to create it...
|
||||
pod [kubevpn-traffic-manager] status is Running
|
||||
|
@@ -31,15 +31,6 @@ func CmdConnect(f cmdutil.Factory) *cobra.Command {
|
||||
# Connect to k8s cluster network
|
||||
kubevpn connect
|
||||
|
||||
# Reverse proxy
|
||||
- reverse deployment
|
||||
kubevpn connect --workloads=deployment/productpage
|
||||
- reverse service
|
||||
kubevpn connect --workloads=service/productpage
|
||||
|
||||
# Reverse proxy with mesh, traffic with header a=1, will hit local PC, otherwise no effect
|
||||
kubevpn connect --workloads=service/productpage --headers a=1
|
||||
|
||||
# Connect to api-server behind of bastion host or ssh jump host
|
||||
kubevpn connect --ssh-addr 192.168.1.100:22 --ssh-username root --ssh-keyfile /Users/naison/.ssh/ssh.pem
|
||||
|
||||
@@ -67,7 +58,6 @@ func CmdConnect(f cmdutil.Factory) *cobra.Command {
|
||||
if err := connect.InitClient(f); err != nil {
|
||||
return err
|
||||
}
|
||||
connect.PreCheckResource()
|
||||
if err := connect.DoConnect(); err != nil {
|
||||
log.Errorln(err)
|
||||
handler.Cleanup(syscall.SIGQUIT)
|
||||
@@ -81,8 +71,6 @@ func CmdConnect(f cmdutil.Factory) *cobra.Command {
|
||||
select {}
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringArrayVar(&connect.Workloads, "workloads", []string{}, "Kubernetes workloads, special which workloads you want to proxy it to local PC, If not special, just connect to cluster network, like: pods/tomcat, deployment/nginx, replicaset/tomcat etc")
|
||||
cmd.Flags().StringToStringVarP(&connect.Headers, "headers", "H", map[string]string{}, "Traffic with special headers with reverse it to local PC, you should startup your service after reverse workloads successfully, If not special, redirect all traffic to local PC, format is k=v, like: k1=v1,k2=v2")
|
||||
cmd.Flags().BoolVar(&config.Debug, "debug", false, "enable debug mode or not, true or false")
|
||||
cmd.Flags().StringVar(&config.Image, "image", config.Image, "use this image to startup container")
|
||||
|
||||
|
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
@@ -13,7 +12,6 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/util/retry"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/completion"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
@@ -35,6 +33,7 @@ func CmdDev(f cmdutil.Factory) *cobra.Command {
|
||||
Env: opts.NewListOpts(nil),
|
||||
Volumes: opts.NewListOpts(nil),
|
||||
ExtraHosts: opts.NewListOpts(nil),
|
||||
NoProxy: false,
|
||||
}
|
||||
var sshConf = util.SshConfig{}
|
||||
cmd := &cobra.Command{
|
||||
@@ -45,6 +44,7 @@ func CmdDev(f cmdutil.Factory) *cobra.Command {
|
||||
# Dev reverse proxy
|
||||
- reverse deployment
|
||||
kubevpn dev deployment/productpage
|
||||
|
||||
- reverse service
|
||||
kubevpn dev service/productpage
|
||||
|
||||
@@ -75,11 +75,9 @@ func CmdDev(f cmdutil.Factory) *cobra.Command {
|
||||
return handler.SshJump(sshConf, cmd.Flags())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
devOptions.Workload = args[0]
|
||||
|
||||
connect := handler.ConnectOptions{
|
||||
Headers: devOptions.Headers,
|
||||
Workloads: []string{devOptions.Workload},
|
||||
Workloads: args,
|
||||
}
|
||||
|
||||
if devOptions.ParentContainer != "" {
|
||||
@@ -103,7 +101,26 @@ func CmdDev(f cmdutil.Factory) *cobra.Command {
|
||||
if err := connect.InitClient(f); err != nil {
|
||||
return err
|
||||
}
|
||||
connect.PreCheckResource()
|
||||
err2 := connect.PreCheckResource()
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
|
||||
if len(connect.Workloads) > 1 {
|
||||
return fmt.Errorf("can only dev one workloads at same time, workloads: %v", connect.Workloads)
|
||||
}
|
||||
if len(connect.Workloads) < 1 {
|
||||
return fmt.Errorf("you must provide resource to dev, workloads : %v is invaild", connect.Workloads)
|
||||
}
|
||||
|
||||
devOptions.Workload = connect.Workloads[0]
|
||||
// if no-proxy is true, not needs to intercept traffic
|
||||
if devOptions.NoProxy {
|
||||
if len(connect.Headers) != 0 {
|
||||
return fmt.Errorf("not needs to provide headers if is no-proxy mode")
|
||||
}
|
||||
connect.Workloads = []string{}
|
||||
}
|
||||
defer func() {
|
||||
handler.Cleanup(syscall.SIGQUIT)
|
||||
select {}
|
||||
@@ -119,37 +136,11 @@ func CmdDev(f cmdutil.Factory) *cobra.Command {
|
||||
}
|
||||
return err
|
||||
},
|
||||
PostRun: func(*cobra.Command, []string) {
|
||||
if util.IsWindows() {
|
||||
err := retry.OnError(retry.DefaultRetry, func(err error) bool {
|
||||
return err != nil
|
||||
}, func() error {
|
||||
return driver.UninstallWireGuardTunDriver()
|
||||
})
|
||||
if err != nil {
|
||||
var wd string
|
||||
wd, err = os.Getwd()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filename := filepath.Join(wd, "wintun.dll")
|
||||
var temp *os.File
|
||||
if temp, err = os.CreateTemp("", ""); err != nil {
|
||||
return
|
||||
}
|
||||
if err = temp.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = os.Rename(filename, temp.Name()); err != nil {
|
||||
log.Debugln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringToStringVarP(&devOptions.Headers, "headers", "H", map[string]string{}, "Traffic with special headers with reverse it to local PC, you should startup your service after reverse workloads successfully, If not special, redirect all traffic to local PC, format is k=v, like: k1=v1,k2=v2")
|
||||
cmd.Flags().BoolVar(&config.Debug, "debug", false, "enable debug mode or not, true or false")
|
||||
cmd.Flags().StringVar(&config.Image, "image", config.Image, "use this image to startup container")
|
||||
cmd.Flags().BoolVar(&devOptions.NoProxy, "no-proxy", false, "Whether proxy remote workloads traffic into local or not, true: just startup container on local without inject containers to intercept traffic, false: intercept traffic and forward to local")
|
||||
cmdutil.AddContainerVarFlags(cmd, &devOptions.ContainerName, devOptions.ContainerName)
|
||||
cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc("container", completion.ContainerCompletionFunc(f)))
|
||||
|
||||
|
114
cmd/kubevpn/cmds/proxy.go
Normal file
114
cmd/kubevpn/cmds/proxy.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
defaultlog "log"
|
||||
"net/http"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
utilcomp "k8s.io/kubectl/pkg/util/completion"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
|
||||
"github.com/wencaiwulue/kubevpn/pkg/config"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/driver"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/handler"
|
||||
"github.com/wencaiwulue/kubevpn/pkg/util"
|
||||
)
|
||||
|
||||
func CmdProxy(f cmdutil.Factory) *cobra.Command {
|
||||
var connect = handler.ConnectOptions{}
|
||||
var sshConf = util.SshConfig{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "proxy",
|
||||
Short: i18n.T("Connect to kubernetes cluster network, or proxy kubernetes workloads inbound traffic into local PC"),
|
||||
Long: templates.LongDesc(i18n.T(`Connect to kubernetes cluster network, or proxy kubernetes workloads inbound traffic into local PC`)),
|
||||
Example: templates.Examples(i18n.T(`
|
||||
# Reverse proxy
|
||||
- proxy deployment
|
||||
kubevpn proxy deployment/productpage
|
||||
|
||||
- proxy service
|
||||
kubevpn proxy service/productpage
|
||||
|
||||
- proxy multiple workloads
|
||||
kubevpn proxy deployment/authors deployment/productpage
|
||||
or
|
||||
kubevpn proxy deployment authors productpage
|
||||
|
||||
# Reverse proxy with mesh, traffic with header a=1, will hit local PC, otherwise no effect
|
||||
kubevpn proxy service/productpage --headers a=1
|
||||
|
||||
# Connect to api-server behind of bastion host or ssh jump host and proxy kubernetes resource traffic into local PC
|
||||
kubevpn proxy --ssh-addr 192.168.1.100:22 --ssh-username root --ssh-keyfile /Users/naison/.ssh/ssh.pem service/productpage --headers a=1
|
||||
|
||||
# it also support ProxyJump, like
|
||||
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────────┐
|
||||
│ pc ├────►│ ssh1 ├────►│ ssh2 ├────►│ ssh3 ├─────►... ─────► │ api-server │
|
||||
└──────┘ └──────┘ └──────┘ └──────┘ └────────────┘
|
||||
kubevpn proxy service/productpage --ssh-alias <alias> --headers a=1
|
||||
|
||||
`)),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
if !util.IsAdmin() {
|
||||
util.RunWithElevated()
|
||||
os.Exit(0)
|
||||
}
|
||||
go http.ListenAndServe("localhost:6060", nil)
|
||||
util.InitLogger(config.Debug)
|
||||
defaultlog.Default().SetOutput(io.Discard)
|
||||
if util.IsWindows() {
|
||||
driver.InstallWireGuardTunDriver()
|
||||
}
|
||||
return handler.SshJump(sshConf, cmd.Flags())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := connect.InitClient(f); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintf(os.Stdout, "You must specify the type of resource to proxy. %s\n\n", cmdutil.SuggestAPIResources("kubevpn"))
|
||||
fullCmdName := cmd.Parent().CommandPath()
|
||||
usageString := "Required resource not specified."
|
||||
if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") {
|
||||
usageString = fmt.Sprintf("%s\nUse \"%s explain <resource>\" for a detailed description of that resource (e.g. %[2]s explain pods).", usageString, fullCmdName)
|
||||
}
|
||||
return cmdutil.UsageErrorf(cmd, usageString)
|
||||
}
|
||||
connect.Workloads = args
|
||||
err := connect.PreCheckResource()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = connect.DoConnect(); err != nil {
|
||||
log.Errorln(err)
|
||||
handler.Cleanup(syscall.SIGQUIT)
|
||||
} else {
|
||||
fmt.Println()
|
||||
fmt.Println(`---------------------------------------------------------------------------`)
|
||||
fmt.Println(` Now you can access resources in the kubernetes cluster, enjoy it :) `)
|
||||
fmt.Println(`---------------------------------------------------------------------------`)
|
||||
fmt.Println()
|
||||
}
|
||||
select {}
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringToStringVarP(&connect.Headers, "headers", "H", map[string]string{}, "Traffic with special headers with reverse it to local PC, you should startup your service after reverse workloads successfully, If not special, redirect all traffic to local PC, format is k=v, like: k1=v1,k2=v2")
|
||||
cmd.Flags().BoolVar(&config.Debug, "debug", false, "Enable debug mode or not, true or false")
|
||||
cmd.Flags().StringVar(&config.Image, "image", config.Image, "Use this image to startup container")
|
||||
|
||||
// for ssh jumper host
|
||||
cmd.Flags().StringVar(&sshConf.Addr, "ssh-addr", "", "Optional ssh jump server address to dial as <hostname>:<port>, eg: 127.0.0.1:22")
|
||||
cmd.Flags().StringVar(&sshConf.User, "ssh-username", "", "Optional username for ssh jump server")
|
||||
cmd.Flags().StringVar(&sshConf.Password, "ssh-password", "", "Optional password for ssh jump server")
|
||||
cmd.Flags().StringVar(&sshConf.Keyfile, "ssh-keyfile", "", "Optional file with private key for SSH authentication")
|
||||
cmd.Flags().StringVar(&sshConf.ConfigAlias, "ssh-alias", "", "Optional config alias with ~/.ssh/config for SSH authentication")
|
||||
|
||||
cmd.ValidArgsFunction = utilcomp.ResourceTypeAndNameCompletionFunc(f)
|
||||
return cmd
|
||||
}
|
@@ -29,6 +29,7 @@ func NewKubeVPNCommand() *cobra.Command {
|
||||
Message: "Client Commands:",
|
||||
Commands: []*cobra.Command{
|
||||
CmdConnect(factory),
|
||||
CmdProxy(factory),
|
||||
CmdDev(factory),
|
||||
CmdReset(factory),
|
||||
CmdUpgrade(factory),
|
||||
|
@@ -37,6 +37,7 @@ type Options struct {
|
||||
Workload string
|
||||
Factory cmdutil.Factory
|
||||
ContainerName string
|
||||
NoProxy bool
|
||||
|
||||
// docker options
|
||||
Platform string
|
||||
|
@@ -564,7 +564,17 @@ func SshJump(conf util.SshConfig, flags *pflag.FlagSet) (err error) {
|
||||
// pods without controller
|
||||
// pod/productpage-without-controller --> pod/productpage-without-controller
|
||||
// service/productpage-without-pod --> controller/controllerName
|
||||
func (c *ConnectOptions) PreCheckResource() {
|
||||
func (c *ConnectOptions) PreCheckResource() error {
|
||||
list, err := util.GetUnstructuredObjectList(c.factory, c.Namespace, c.Workloads)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var resources []string
|
||||
for _, info := range list {
|
||||
resources = append(resources, fmt.Sprintf("%s/%s", info.Mapping.GroupVersionKind.GroupKind().String(), info.Name))
|
||||
}
|
||||
c.Workloads = resources
|
||||
|
||||
// normal workloads, like pod with controller, deployments, statefulset, replicaset etc...
|
||||
for i, workload := range c.Workloads {
|
||||
ownerReference, err := util.GetTopOwnerReference(c.factory, c.Namespace, workload)
|
||||
@@ -576,7 +586,7 @@ func (c *ConnectOptions) PreCheckResource() {
|
||||
for i, workload := range c.Workloads {
|
||||
object, err := util.GetUnstructuredObject(c.factory, c.Namespace, workload)
|
||||
if err != nil {
|
||||
continue
|
||||
return err
|
||||
}
|
||||
if object.Mapping.Resource.Resource != "services" {
|
||||
continue
|
||||
@@ -606,11 +616,12 @@ func (c *ConnectOptions) PreCheckResource() {
|
||||
}
|
||||
// only a single service, not support it yet
|
||||
if controller.Len() == 0 {
|
||||
log.Fatalf("Not support resources: %s", workload)
|
||||
return fmt.Errorf("not support resources: %s", workload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConnectOptions) GetRunningPodList() ([]v1.Pod, error) {
|
||||
|
@@ -264,7 +264,7 @@ func server(port int) {
|
||||
|
||||
func kubevpnConnect(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Hour)
|
||||
cmd := exec.CommandContext(context.Background(), "kubevpn", "connect", "--debug", "--workloads", "deployments/reviews")
|
||||
cmd := exec.CommandContext(context.Background(), "kubevpn", "proxy", "deployments/reviews", "--debug")
|
||||
go func() {
|
||||
var checker = func(log string) {
|
||||
if strings.Contains(log, "dns service ok") {
|
||||
|
@@ -251,11 +251,35 @@ func GetUnstructuredObject(f cmdutil.Factory, namespace string, workloads string
|
||||
return nil, err
|
||||
}
|
||||
if len(infos) == 0 {
|
||||
return nil, errors.New("Not found")
|
||||
return nil, fmt.Errorf("not found workloads %s", workloads)
|
||||
}
|
||||
return infos[0], err
|
||||
}
|
||||
|
||||
func GetUnstructuredObjectList(f cmdutil.Factory, namespace string, workloads []string) ([]*runtimeresource.Info, error) {
|
||||
do := f.NewBuilder().
|
||||
Unstructured().
|
||||
NamespaceParam(namespace).DefaultNamespace().AllNamespaces(false).
|
||||
ResourceTypeOrNameArgs(true, workloads...).
|
||||
ContinueOnError().
|
||||
Latest().
|
||||
Flatten().
|
||||
TransformRequests(func(req *rest.Request) { req.Param("includeObject", "Object") }).
|
||||
Do()
|
||||
if err := do.Err(); err != nil {
|
||||
log.Warn(err)
|
||||
return nil, err
|
||||
}
|
||||
infos, err := do.Infos()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(infos) == 0 {
|
||||
return nil, fmt.Errorf("not found resource %v", workloads)
|
||||
}
|
||||
return infos, err
|
||||
}
|
||||
|
||||
func GetUnstructuredObjectBySelector(f cmdutil.Factory, namespace string, selector string) ([]*runtimeresource.Info, error) {
|
||||
do := f.NewBuilder().
|
||||
Unstructured().
|
||||
|
Reference in New Issue
Block a user