feat: split connect into connect and proxy

This commit is contained in:
fengcaiwen
2023-03-10 18:54:01 +08:00
parent 0d7f78f8ae
commit caeaab9ba2
10 changed files with 184 additions and 54 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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
View 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
}

View File

@@ -29,6 +29,7 @@ func NewKubeVPNCommand() *cobra.Command {
Message: "Client Commands:",
Commands: []*cobra.Command{
CmdConnect(factory),
CmdProxy(factory),
CmdDev(factory),
CmdReset(factory),
CmdUpgrade(factory),

View File

@@ -37,6 +37,7 @@ type Options struct {
Workload string
Factory cmdutil.Factory
ContainerName string
NoProxy bool
// docker options
Platform string

View File

@@ -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) {

View File

@@ -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") {

View File

@@ -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().