mirror of https://github.com/linkerd/linkerd2.git
253 lines
9.9 KiB
Go
253 lines
9.9 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
pb "github.com/linkerd/linkerd2/controller/gen/public"
|
|
"github.com/linkerd/linkerd2/pkg/healthcheck"
|
|
"github.com/linkerd/linkerd2/pkg/version"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
k8sResource "k8s.io/apimachinery/pkg/api/resource"
|
|
)
|
|
|
|
const (
|
|
defaultNamespace = "linkerd"
|
|
|
|
lineWidth = 80
|
|
okStatus = "[ok]"
|
|
warnStatus = "[warn]"
|
|
)
|
|
|
|
var controlPlaneNamespace string
|
|
var apiAddr string // An empty value means "use the Kubernetes configuration"
|
|
var kubeconfigPath string
|
|
var kubeContext string
|
|
var verbose bool
|
|
|
|
var (
|
|
// These regexs are not as strict as they could be, but are a quick and dirty
|
|
// sanity check against illegal characters.
|
|
alphaNumDash = regexp.MustCompile("^[a-zA-Z0-9-]+$")
|
|
alphaNumDashDot = regexp.MustCompile("^[\\.a-zA-Z0-9-]+$")
|
|
alphaNumDashDotSlash = regexp.MustCompile("^[\\./a-zA-Z0-9-]+$")
|
|
)
|
|
|
|
var RootCmd = &cobra.Command{
|
|
Use: "linkerd",
|
|
Short: "linkerd manages the Linkerd service mesh",
|
|
Long: `linkerd manages the Linkerd service mesh.`,
|
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
|
// enable / disable logging
|
|
if verbose {
|
|
log.SetLevel(log.DebugLevel)
|
|
} else {
|
|
log.SetLevel(log.PanicLevel)
|
|
}
|
|
|
|
controlPlaneNamespaceFromEnv := os.Getenv("LINKERD_NAMESPACE")
|
|
if controlPlaneNamespace == defaultNamespace && controlPlaneNamespaceFromEnv != "" {
|
|
controlPlaneNamespace = controlPlaneNamespaceFromEnv
|
|
}
|
|
|
|
if !alphaNumDash.MatchString(controlPlaneNamespace) {
|
|
return fmt.Errorf("%s is not a valid namespace", controlPlaneNamespace)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
RootCmd.PersistentFlags().StringVarP(&controlPlaneNamespace, "linkerd-namespace", "l", defaultNamespace, "Namespace in which Linkerd is installed [$LINKERD_NAMESPACE]")
|
|
RootCmd.PersistentFlags().StringVar(&kubeconfigPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests")
|
|
RootCmd.PersistentFlags().StringVar(&kubeContext, "context", "", "Name of the kubeconfig context to use")
|
|
RootCmd.PersistentFlags().StringVar(&apiAddr, "api-addr", "", "Override kubeconfig and communicate directly with the control plane at host:port (mostly for testing)")
|
|
RootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Turn on debug logging")
|
|
|
|
RootCmd.AddCommand(newCmdCheck())
|
|
RootCmd.AddCommand(newCmdCompletion())
|
|
RootCmd.AddCommand(newCmdDashboard())
|
|
RootCmd.AddCommand(newCmdGet())
|
|
RootCmd.AddCommand(newCmdInject())
|
|
RootCmd.AddCommand(newCmdInstall())
|
|
RootCmd.AddCommand(newCmdStat())
|
|
RootCmd.AddCommand(newCmdTap())
|
|
RootCmd.AddCommand(newCmdTop())
|
|
RootCmd.AddCommand(newCmdVersion())
|
|
}
|
|
|
|
// validatedPublicAPIClient builds a new public API client and executes status
|
|
// checks to determine if the client can successfully connect to the API. If the
|
|
// checks fail, then CLI will print an error and exit. If the shouldRetry param
|
|
// is specified, then the CLI will print a message to stderr and retry.
|
|
func validatedPublicAPIClient(retryDeadline time.Time) pb.ApiClient {
|
|
checks := []healthcheck.Checks{
|
|
healthcheck.KubernetesAPIChecks,
|
|
healthcheck.LinkerdAPIChecks,
|
|
}
|
|
|
|
hc := healthcheck.NewHealthChecker(checks, &healthcheck.HealthCheckOptions{
|
|
ControlPlaneNamespace: controlPlaneNamespace,
|
|
KubeConfig: kubeconfigPath,
|
|
KubeContext: kubeContext,
|
|
APIAddr: apiAddr,
|
|
RetryDeadline: retryDeadline,
|
|
})
|
|
|
|
exitOnError := func(result *healthcheck.CheckResult) {
|
|
if result.Retry {
|
|
fmt.Fprintln(os.Stderr, "Waiting for control plane to become available")
|
|
return
|
|
}
|
|
|
|
if result.Err != nil {
|
|
var msg string
|
|
switch result.Category {
|
|
case healthcheck.KubernetesAPICategory:
|
|
msg = "Cannot connect to Kubernetes"
|
|
case healthcheck.LinkerdAPICategory:
|
|
msg = "Cannot connect to Linkerd"
|
|
}
|
|
fmt.Fprintf(os.Stderr, "%s: %s\n", msg, result.Err)
|
|
|
|
checkCmd := "linkerd check"
|
|
if controlPlaneNamespace != defaultNamespace {
|
|
checkCmd += fmt.Sprintf(" --linkerd-namespace %s", controlPlaneNamespace)
|
|
}
|
|
fmt.Fprintf(os.Stderr, "Validate the install with: %s\n", checkCmd)
|
|
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
hc.RunChecks(exitOnError)
|
|
return hc.PublicAPIClient()
|
|
}
|
|
|
|
type proxyConfigOptions struct {
|
|
linkerdVersion string
|
|
proxyImage string
|
|
initImage string
|
|
dockerRegistry string
|
|
imagePullPolicy string
|
|
inboundPort uint
|
|
outboundPort uint
|
|
ignoreInboundPorts []uint
|
|
ignoreOutboundPorts []uint
|
|
proxyUID int64
|
|
proxyLogLevel string
|
|
proxyBindTimeout string
|
|
proxyAPIPort uint
|
|
proxyControlPort uint
|
|
proxyMetricsPort uint
|
|
proxyCpuRequest string
|
|
proxyMemoryRequest string
|
|
proxyOutboundCapacity map[string]uint
|
|
tls string
|
|
}
|
|
|
|
const (
|
|
optionalTLS = "optional"
|
|
defaultDockerRegistry = "gcr.io/linkerd-io"
|
|
)
|
|
|
|
func newProxyConfigOptions() *proxyConfigOptions {
|
|
return &proxyConfigOptions{
|
|
linkerdVersion: version.Version,
|
|
proxyImage: defaultDockerRegistry + "/proxy",
|
|
initImage: defaultDockerRegistry + "/proxy-init",
|
|
dockerRegistry: defaultDockerRegistry,
|
|
imagePullPolicy: "IfNotPresent",
|
|
inboundPort: 4143,
|
|
outboundPort: 4140,
|
|
ignoreInboundPorts: nil,
|
|
ignoreOutboundPorts: nil,
|
|
proxyUID: 2102,
|
|
proxyLogLevel: "warn,linkerd2_proxy=info",
|
|
proxyBindTimeout: "10s",
|
|
proxyAPIPort: 8086,
|
|
proxyControlPort: 4190,
|
|
proxyMetricsPort: 4191,
|
|
proxyOutboundCapacity: map[string]uint{},
|
|
proxyCpuRequest: "",
|
|
proxyMemoryRequest: "",
|
|
tls: "",
|
|
}
|
|
}
|
|
|
|
func (options *proxyConfigOptions) validate() error {
|
|
if !alphaNumDashDot.MatchString(options.linkerdVersion) {
|
|
return fmt.Errorf("%s is not a valid version", options.linkerdVersion)
|
|
}
|
|
|
|
if !alphaNumDashDotSlash.MatchString(options.dockerRegistry) {
|
|
return fmt.Errorf("%s is not a valid Docker registry", options.dockerRegistry)
|
|
}
|
|
|
|
if options.imagePullPolicy != "Always" && options.imagePullPolicy != "IfNotPresent" && options.imagePullPolicy != "Never" {
|
|
return fmt.Errorf("--image-pull-policy must be one of: Always, IfNotPresent, Never")
|
|
}
|
|
|
|
if _, err := time.ParseDuration(options.proxyBindTimeout); err != nil {
|
|
return fmt.Errorf("Invalid duration '%s' for --proxy-bind-timeout flag", options.proxyBindTimeout)
|
|
}
|
|
|
|
if options.proxyCpuRequest != "" {
|
|
if _, err := k8sResource.ParseQuantity(options.proxyCpuRequest); err != nil {
|
|
return fmt.Errorf("Invalid cpu request '%s' for --proxy-cpu flag", options.proxyCpuRequest)
|
|
}
|
|
}
|
|
|
|
if options.proxyMemoryRequest != "" {
|
|
if _, err := k8sResource.ParseQuantity(options.proxyMemoryRequest); err != nil {
|
|
return fmt.Errorf("Invalid memory request '%s' for --proxy-memory flag", options.proxyMemoryRequest)
|
|
}
|
|
}
|
|
|
|
if options.tls != "" && options.tls != optionalTLS {
|
|
return fmt.Errorf("--tls must be blank or set to \"%s\"", optionalTLS)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (options *proxyConfigOptions) enableTLS() bool {
|
|
return options.tls == optionalTLS
|
|
}
|
|
|
|
func (options *proxyConfigOptions) taggedProxyImage() string {
|
|
image := strings.Replace(options.proxyImage, defaultDockerRegistry, options.dockerRegistry, 1)
|
|
return fmt.Sprintf("%s:%s", image, options.linkerdVersion)
|
|
}
|
|
|
|
func (options *proxyConfigOptions) taggedProxyInitImage() string {
|
|
image := strings.Replace(options.initImage, defaultDockerRegistry, options.dockerRegistry, 1)
|
|
return fmt.Sprintf("%s:%s", image, options.linkerdVersion)
|
|
}
|
|
|
|
func addProxyConfigFlags(cmd *cobra.Command, options *proxyConfigOptions) {
|
|
cmd.PersistentFlags().StringVarP(&options.linkerdVersion, "linkerd-version", "v", options.linkerdVersion, "Tag to be used for Linkerd images")
|
|
cmd.PersistentFlags().StringVar(&options.initImage, "init-image", options.initImage, "Linkerd init container image name")
|
|
cmd.PersistentFlags().StringVar(&options.proxyImage, "proxy-image", options.proxyImage, "Linkerd proxy container image name")
|
|
cmd.PersistentFlags().StringVar(&options.dockerRegistry, "registry", options.dockerRegistry, "Docker registry to pull images from")
|
|
cmd.PersistentFlags().StringVar(&options.imagePullPolicy, "image-pull-policy", options.imagePullPolicy, "Docker image pull policy")
|
|
cmd.PersistentFlags().Int64Var(&options.proxyUID, "proxy-uid", options.proxyUID, "Run the proxy under this user ID")
|
|
cmd.PersistentFlags().StringVar(&options.proxyLogLevel, "proxy-log-level", options.proxyLogLevel, "Log level for the proxy")
|
|
cmd.PersistentFlags().StringVar(&options.proxyBindTimeout, "proxy-bind-timeout", options.proxyBindTimeout, "Timeout the proxy will use")
|
|
cmd.PersistentFlags().UintVar(&options.inboundPort, "inbound-port", options.inboundPort, "Proxy port to use for inbound traffic")
|
|
cmd.PersistentFlags().UintVar(&options.outboundPort, "outbound-port", options.outboundPort, "Proxy port to use for outbound traffic")
|
|
cmd.PersistentFlags().UintVar(&options.proxyAPIPort, "api-port", options.proxyAPIPort, "Port where the Linkerd controller is running")
|
|
cmd.PersistentFlags().UintVar(&options.proxyControlPort, "control-port", options.proxyControlPort, "Proxy port to use for control")
|
|
cmd.PersistentFlags().UintVar(&options.proxyMetricsPort, "metrics-port", options.proxyMetricsPort, "Proxy port to serve metrics on")
|
|
cmd.PersistentFlags().StringVar(&options.tls, "tls", options.tls, "Enable TLS; valid settings: \"optional\"")
|
|
cmd.PersistentFlags().StringVar(&options.proxyCpuRequest, "proxy-cpu", options.proxyCpuRequest, "Amount of CPU units that the proxy sidecar requests")
|
|
cmd.PersistentFlags().StringVar(&options.proxyMemoryRequest, "proxy-memory", options.proxyMemoryRequest, "Amount of Memory that the proxy sidecar requests")
|
|
cmd.PersistentFlags().UintSliceVar(&options.ignoreInboundPorts, "skip-inbound-ports", options.ignoreInboundPorts, "Ports that should skip the proxy and send directly to the application")
|
|
cmd.PersistentFlags().UintSliceVar(&options.ignoreOutboundPorts, "skip-outbound-ports", options.ignoreOutboundPorts, "Outbound ports that should skip the proxy")
|
|
}
|