github.com/tencentyun/consul@v1.4.5/command/connect/proxy/proxy.go (about)

     1  package proxy
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"net"
     9  	"net/http"
    10  	_ "net/http/pprof" // Expose pprof if configured
    11  	"os"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  
    16  	proxyAgent "github.com/hashicorp/consul/agent/proxyprocess"
    17  	"github.com/hashicorp/consul/api"
    18  	"github.com/hashicorp/consul/command/flags"
    19  	proxyImpl "github.com/hashicorp/consul/connect/proxy"
    20  
    21  	"github.com/hashicorp/consul/logger"
    22  	"github.com/hashicorp/logutils"
    23  	"github.com/mitchellh/cli"
    24  )
    25  
    26  func New(ui cli.Ui, shutdownCh <-chan struct{}) *cmd {
    27  	ui = &cli.PrefixedUi{
    28  		OutputPrefix: "==> ",
    29  		InfoPrefix:   "    ",
    30  		ErrorPrefix:  "==> ",
    31  		Ui:           ui,
    32  	}
    33  
    34  	c := &cmd{UI: ui, shutdownCh: shutdownCh}
    35  	c.init()
    36  	return c
    37  }
    38  
    39  type cmd struct {
    40  	UI    cli.Ui
    41  	flags *flag.FlagSet
    42  	http  *flags.HTTPFlags
    43  	help  string
    44  
    45  	shutdownCh <-chan struct{}
    46  
    47  	logFilter *logutils.LevelFilter
    48  	logOutput io.Writer
    49  	logger    *log.Logger
    50  
    51  	// flags
    52  	logLevel    string
    53  	cfgFile     string
    54  	proxyID     string
    55  	sidecarFor  string
    56  	pprofAddr   string
    57  	service     string
    58  	serviceAddr string
    59  	upstreams   map[string]proxyImpl.UpstreamConfig
    60  	listen      string
    61  	register    bool
    62  	registerId  string
    63  
    64  	// test flags
    65  	testNoStart bool // don't start the proxy, just exit 0
    66  }
    67  
    68  func (c *cmd) init() {
    69  	c.flags = flag.NewFlagSet("", flag.ContinueOnError)
    70  
    71  	c.flags.StringVar(&c.proxyID, "proxy-id", "",
    72  		"The proxy's ID on the local agent.")
    73  
    74  	c.flags.StringVar(&c.sidecarFor, "sidecar-for", "",
    75  		"The ID of a service instance on the local agent that this proxy should "+
    76  			"become a sidecar for. It requires that the proxy service is registered "+
    77  			"with the agent as a connect-proxy with Proxy.DestinationServiceID set "+
    78  			"to this value. If more than one such proxy is registered it will fail.")
    79  
    80  	c.flags.StringVar(&c.logLevel, "log-level", "INFO",
    81  		"Specifies the log level.")
    82  
    83  	c.flags.StringVar(&c.pprofAddr, "pprof-addr", "",
    84  		"Enable debugging via pprof. Providing a host:port (or just ':port') "+
    85  			"enables profiling HTTP endpoints on that address.")
    86  
    87  	c.flags.StringVar(&c.service, "service", "",
    88  		"Name of the service this proxy is representing.")
    89  
    90  	c.flags.Var((*FlagUpstreams)(&c.upstreams), "upstream",
    91  		"Upstream service to support connecting to. The format should be "+
    92  			"'name:addr', such as 'db:8181'. This will make 'db' available "+
    93  			"on port 8181. This can be repeated multiple times.")
    94  
    95  	c.flags.StringVar(&c.serviceAddr, "service-addr", "",
    96  		"Address of the local service to proxy. Only useful if -listen "+
    97  			"and -service are both set.")
    98  
    99  	c.flags.StringVar(&c.listen, "listen", "",
   100  		"Address to listen for inbound connections to the proxied service. "+
   101  			"Must be specified with -service and -service-addr.")
   102  
   103  	c.flags.BoolVar(&c.register, "register", false,
   104  		"Self-register with the local Consul agent. Only useful with "+
   105  			"-listen.")
   106  
   107  	c.flags.StringVar(&c.registerId, "register-id", "",
   108  		"ID suffix for the service. Use this to disambiguate with other proxies.")
   109  
   110  	c.http = &flags.HTTPFlags{}
   111  	flags.Merge(c.flags, c.http.ClientFlags())
   112  	flags.Merge(c.flags, c.http.ServerFlags())
   113  	c.help = flags.Usage(help, c.flags)
   114  }
   115  
   116  func (c *cmd) Run(args []string) int {
   117  	if err := c.flags.Parse(args); err != nil {
   118  		return 1
   119  	}
   120  	if len(c.flags.Args()) > 0 {
   121  		c.UI.Error(fmt.Sprintf("Should have no non-flag arguments."))
   122  		return 1
   123  	}
   124  
   125  	// Load the proxy ID and token from env vars if they're set
   126  	if c.proxyID == "" {
   127  		c.proxyID = os.Getenv(proxyAgent.EnvProxyID)
   128  	}
   129  	if c.sidecarFor == "" {
   130  		c.sidecarFor = os.Getenv(proxyAgent.EnvSidecarFor)
   131  	}
   132  	if c.http.Token() == "" {
   133  		c.http.SetToken(os.Getenv(proxyAgent.EnvProxyToken))
   134  	}
   135  
   136  	// Setup the log outputs
   137  	logConfig := &logger.Config{
   138  		LogLevel: c.logLevel,
   139  	}
   140  	logFilter, logGate, _, logOutput, ok := logger.Setup(logConfig, c.UI)
   141  	if !ok {
   142  		return 1
   143  	}
   144  	c.logFilter = logFilter
   145  	c.logOutput = logOutput
   146  	c.logger = log.New(logOutput, "", log.LstdFlags)
   147  
   148  	// Enable Pprof if needed
   149  	if c.pprofAddr != "" {
   150  		go func() {
   151  			c.UI.Output(fmt.Sprintf("Starting pprof HTTP endpoints on "+
   152  				"http://%s/debug/pprof", c.pprofAddr))
   153  			log.Fatal(http.ListenAndServe(c.pprofAddr, nil))
   154  		}()
   155  	}
   156  
   157  	// Setup Consul client
   158  	client, err := c.http.APIClient()
   159  	if err != nil {
   160  		c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
   161  		return 1
   162  	}
   163  
   164  	// Output this first since the config watcher below will output
   165  	// other information.
   166  	c.UI.Output("Consul Connect proxy starting...")
   167  
   168  	// Get the proper configuration watcher
   169  	cfgWatcher, err := c.configWatcher(client)
   170  	if err != nil {
   171  		c.UI.Error(fmt.Sprintf("Error preparing configuration: %s", err))
   172  		return 1
   173  	}
   174  
   175  	p, err := proxyImpl.New(client, cfgWatcher, c.logger)
   176  	if err != nil {
   177  		c.UI.Error(fmt.Sprintf("Failed initializing proxy: %s", err))
   178  		return 1
   179  	}
   180  
   181  	// Hook the shutdownCh up to close the proxy
   182  	go func() {
   183  		<-c.shutdownCh
   184  		p.Close()
   185  	}()
   186  
   187  	// Register the service if we requested it
   188  	if c.register {
   189  		monitor, err := c.registerMonitor(client)
   190  		if err != nil {
   191  			c.UI.Error(fmt.Sprintf("Failed initializing registration: %s", err))
   192  			return 1
   193  		}
   194  
   195  		go monitor.Run()
   196  		defer monitor.Close()
   197  	}
   198  
   199  	c.UI.Info("")
   200  	c.UI.Output("Log data will now stream in as it occurs:\n")
   201  	logGate.Flush()
   202  
   203  	// Run the proxy unless our tests require we don't
   204  	if !c.testNoStart {
   205  		if err := p.Serve(); err != nil {
   206  			c.UI.Error(fmt.Sprintf("Failed running proxy: %s", err))
   207  		}
   208  	}
   209  
   210  	c.UI.Output("Consul Connect proxy shutdown")
   211  	return 0
   212  }
   213  
   214  func (c *cmd) lookupProxyIDForSidecar(client *api.Client) (string, error) {
   215  	return LookupProxyIDForSidecar(client, c.sidecarFor)
   216  }
   217  
   218  // LookupProxyIDForSidecar finds candidate local proxy registrations that are a
   219  // sidecar for the given service. It will return an ID if and only if there is
   220  // exactly one registered connect proxy with `Proxy.DestinationServiceID` set to
   221  // the specified service ID.
   222  //
   223  // This is exported to share it with the connect envoy command.
   224  func LookupProxyIDForSidecar(client *api.Client, sidecarFor string) (string, error) {
   225  	svcs, err := client.Agent().Services()
   226  	if err != nil {
   227  		return "", fmt.Errorf("Failed looking up sidecar proxy info for %s: %s",
   228  			sidecarFor, err)
   229  	}
   230  
   231  	var proxyIDs []string
   232  	for _, svc := range svcs {
   233  		if svc.Kind == api.ServiceKindConnectProxy && svc.Proxy != nil &&
   234  			strings.ToLower(svc.Proxy.DestinationServiceID) == sidecarFor {
   235  			proxyIDs = append(proxyIDs, svc.ID)
   236  		}
   237  	}
   238  
   239  	if len(proxyIDs) == 0 {
   240  		return "", fmt.Errorf("No sidecar proxy registered for %s", sidecarFor)
   241  	}
   242  	if len(proxyIDs) > 1 {
   243  		return "", fmt.Errorf("More than one sidecar proxy registered for %s.\n"+
   244  			"    Start proxy with -proxy-id and one of the following IDs: %s",
   245  			sidecarFor, strings.Join(proxyIDs, ", "))
   246  	}
   247  	return proxyIDs[0], nil
   248  }
   249  
   250  func (c *cmd) configWatcher(client *api.Client) (proxyImpl.ConfigWatcher, error) {
   251  	// Use the configured proxy ID
   252  	if c.proxyID != "" {
   253  		c.UI.Info("Configuration mode: Agent API")
   254  		c.UI.Info(fmt.Sprintf("          Proxy ID: %s", c.proxyID))
   255  		return proxyImpl.NewAgentConfigWatcher(client, c.proxyID, c.logger)
   256  	}
   257  
   258  	if c.sidecarFor != "" {
   259  		// Running as a sidecar, we need to find the proxy-id for the requested
   260  		// service
   261  		var err error
   262  		c.proxyID, err = c.lookupProxyIDForSidecar(client)
   263  		if err != nil {
   264  			return nil, err
   265  		}
   266  
   267  		c.UI.Info("Configuration mode: Agent API")
   268  		c.UI.Info(fmt.Sprintf("    Sidecar for ID: %s", c.sidecarFor))
   269  		c.UI.Info(fmt.Sprintf("          Proxy ID: %s", c.proxyID))
   270  		return proxyImpl.NewAgentConfigWatcher(client, c.proxyID, c.logger)
   271  	}
   272  
   273  	// Otherwise, we're representing a manually specified service.
   274  	if c.service == "" {
   275  		return nil, fmt.Errorf(
   276  			"-service or -proxy-id must be specified so that proxy can " +
   277  				"configure itself.")
   278  	}
   279  
   280  	c.UI.Info("Configuration mode: Flags")
   281  	c.UI.Info(fmt.Sprintf("           Service: %s", c.service))
   282  
   283  	// Convert our upstreams to a slice of configurations. We do this
   284  	// deterministically by alphabetizing the upstream keys. We do this so
   285  	// that tests can compare the upstream values.
   286  	upstreamKeys := make([]string, 0, len(c.upstreams))
   287  	for k := range c.upstreams {
   288  		upstreamKeys = append(upstreamKeys, k)
   289  	}
   290  	sort.Strings(upstreamKeys)
   291  	upstreams := make([]proxyImpl.UpstreamConfig, 0, len(c.upstreams))
   292  	for _, k := range upstreamKeys {
   293  		config := c.upstreams[k]
   294  
   295  		c.UI.Info(fmt.Sprintf(
   296  			"          Upstream: %s => %s:%d",
   297  			k, config.LocalBindAddress, config.LocalBindPort))
   298  		upstreams = append(upstreams, config)
   299  	}
   300  
   301  	// Parse out our listener if we have one
   302  	var listener proxyImpl.PublicListenerConfig
   303  	if c.listen != "" {
   304  		host, port, err := c.listenParts()
   305  		if err != nil {
   306  			return nil, err
   307  		}
   308  
   309  		if c.serviceAddr == "" {
   310  			return nil, fmt.Errorf(
   311  				"-service-addr must be specified with -listen so the proxy " +
   312  					"knows the backend service address.")
   313  		}
   314  
   315  		c.UI.Info(fmt.Sprintf("   Public listener: %s:%d => %s", host, port, c.serviceAddr))
   316  		listener.BindAddress = host
   317  		listener.BindPort = port
   318  		listener.LocalServiceAddress = c.serviceAddr
   319  	} else {
   320  		c.UI.Info(fmt.Sprintf("   Public listener: Disabled"))
   321  	}
   322  
   323  	return proxyImpl.NewStaticConfigWatcher(&proxyImpl.Config{
   324  		ProxiedServiceName: c.service,
   325  		PublicListener:     listener,
   326  		Upstreams:          upstreams,
   327  	}), nil
   328  }
   329  
   330  // registerMonitor returns the registration monitor ready to be started.
   331  func (c *cmd) registerMonitor(client *api.Client) (*RegisterMonitor, error) {
   332  	if c.service == "" || c.listen == "" {
   333  		return nil, fmt.Errorf("-register may only be specified with -service and -listen")
   334  	}
   335  
   336  	host, port, err := c.listenParts()
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  
   341  	m := NewRegisterMonitor()
   342  	m.Logger = c.logger
   343  	m.Client = client
   344  	m.Service = c.service
   345  	m.IDSuffix = c.registerId
   346  	m.LocalAddress = host
   347  	m.LocalPort = port
   348  	return m, nil
   349  }
   350  
   351  // listenParts returns the host and port parts of the -listen flag. The
   352  // -listen flag must be non-empty prior to calling this.
   353  func (c *cmd) listenParts() (string, int, error) {
   354  	host, portRaw, err := net.SplitHostPort(c.listen)
   355  	if err != nil {
   356  		return "", 0, err
   357  	}
   358  
   359  	port, err := strconv.ParseInt(portRaw, 0, 0)
   360  	if err != nil {
   361  		return "", 0, err
   362  	}
   363  
   364  	return host, int(port), nil
   365  }
   366  
   367  func (c *cmd) Synopsis() string {
   368  	return synopsis
   369  }
   370  
   371  func (c *cmd) Help() string {
   372  	return c.help
   373  }
   374  
   375  const synopsis = "Runs a Consul Connect proxy"
   376  const help = `
   377  Usage: consul connect proxy [options]
   378  
   379    Starts a Consul Connect proxy and runs until an interrupt is received.
   380    The proxy can be used to accept inbound connections for a service,
   381    wrap outbound connections to upstream services, or both. This enables
   382    a non-Connect-aware application to use Connect.
   383  
   384    The proxy requires service:write permissions for the service it represents.
   385    The token may be passed via the CLI or the CONSUL_TOKEN environment
   386    variable.
   387  
   388    Consul can automatically start and manage this proxy by specifying the
   389    "proxy" configuration within your service definition.
   390  
   391    The example below shows how to start a local proxy for establishing outbound
   392    connections to "db" representing the frontend service. Once running, any
   393    process that creates a TCP connection to the specified port (8181) will
   394    establish a mutual TLS connection to "db" identified as "frontend".
   395  
   396      $ consul connect proxy -service frontend -upstream db:8181
   397  
   398    The next example starts a local proxy that also accepts inbound connections
   399    on port 8443, authorizes the connection, then proxies it to port 8080:
   400  
   401      $ consul connect proxy \
   402          -service frontend \
   403          -service-addr 127.0.0.1:8080 \
   404          -listen ':8443'
   405  
   406    A proxy can accept both inbound connections as well as proxy to upstream
   407    services by specifying both the "-listen" and "-upstream" flags.
   408  
   409  `