...

Source file src/github.com/linkerd/linkerd2/cli/cmd/inject.go

Documentation: github.com/linkerd/linkerd2/cli/cmd

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	jsonpatch "github.com/evanphx/json-patch"
    15  	"github.com/linkerd/linkerd2/cli/flag"
    16  	"github.com/linkerd/linkerd2/pkg/charts/linkerd2"
    17  	"github.com/linkerd/linkerd2/pkg/healthcheck"
    18  	"github.com/linkerd/linkerd2/pkg/inject"
    19  	"github.com/linkerd/linkerd2/pkg/k8s"
    20  	log "github.com/sirupsen/logrus"
    21  	"github.com/spf13/cobra"
    22  	"sigs.k8s.io/yaml"
    23  )
    24  
    25  const (
    26  	// for inject reports
    27  	hostNetworkDesc                  = "pods do not use host networking"
    28  	sidecarDesc                      = "pods do not have a 3rd party proxy or initContainer already injected"
    29  	injectDisabledDesc               = "pods are not annotated to disable injection"
    30  	unsupportedDesc                  = "at least one resource can be injected or annotated"
    31  	udpDesc                          = "pod specs do not include UDP ports"
    32  	automountServiceAccountTokenDesc = "pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled"
    33  	slash                            = "/"
    34  )
    35  
    36  type resourceTransformerInject struct {
    37  	allowNsInject       bool
    38  	injectProxy         bool
    39  	values              *linkerd2.Values
    40  	overrideAnnotations map[string]string
    41  	enableDebugSidecar  bool
    42  	closeWaitTimeout    time.Duration
    43  }
    44  
    45  func runInjectCmd(inputs []io.Reader, errWriter, outWriter io.Writer, transformer *resourceTransformerInject, output string) int {
    46  	return transformInput(inputs, errWriter, outWriter, transformer, output)
    47  }
    48  
    49  func newCmdInject() *cobra.Command {
    50  	defaults, err := linkerd2.NewValues()
    51  	if err != nil {
    52  		fmt.Fprint(os.Stderr, err.Error())
    53  		os.Exit(1)
    54  	}
    55  	flags, proxyFlagSet := makeProxyFlags(defaults)
    56  	injectFlags, injectFlagSet := makeInjectFlags(defaults)
    57  	var manualOption, enableDebugSidecar bool
    58  	var closeWaitTimeout time.Duration
    59  	var output string
    60  
    61  	cmd := &cobra.Command{
    62  		Use:   "inject [flags] CONFIG-FILE",
    63  		Short: "Add the Linkerd proxy to a Kubernetes config",
    64  		Long: `Add the Linkerd proxy to a Kubernetes config.
    65  
    66  You can inject resources contained in a single file, inside a folder and its
    67  sub-folders, or coming from stdin.`,
    68  		Example: `  # Inject all the deployments in the default namespace.
    69    kubectl get deploy -o yaml | linkerd inject - | kubectl apply -f -
    70  
    71    # Injecting a file from a remote URL
    72    linkerd inject https://url.to/yml | kubectl apply -f -
    73  
    74    # Inject all the resources inside a folder and its sub-folders.
    75    linkerd inject <folder> | kubectl apply -f -`,
    76  		RunE: func(cmd *cobra.Command, args []string) error {
    77  			if len(args) < 1 {
    78  				return fmt.Errorf("please specify a kubernetes resource file")
    79  			}
    80  
    81  			values := defaults
    82  			if !ignoreCluster {
    83  				values, err = fetchConfigs(cmd.Context())
    84  				if err != nil {
    85  					return err
    86  				}
    87  			}
    88  
    89  			baseValues, err := values.DeepCopy()
    90  			if err != nil {
    91  				return err
    92  			}
    93  			err = flag.ApplySetFlags(values, append(flags, injectFlags...))
    94  			if err != nil {
    95  				return err
    96  			}
    97  
    98  			in, err := read(args[0])
    99  			if err != nil {
   100  				return err
   101  			}
   102  
   103  			overrideAnnotations := getOverrideAnnotations(values, baseValues)
   104  
   105  			transformer := &resourceTransformerInject{
   106  				allowNsInject:       true,
   107  				injectProxy:         manualOption,
   108  				values:              values,
   109  				overrideAnnotations: overrideAnnotations,
   110  				enableDebugSidecar:  enableDebugSidecar,
   111  				closeWaitTimeout:    closeWaitTimeout,
   112  			}
   113  			exitCode := uninjectAndInject(in, stderr, stdout, transformer, output)
   114  			os.Exit(exitCode)
   115  			return nil
   116  		},
   117  	}
   118  
   119  	cmd.Flags().BoolVar(
   120  		&manualOption, "manual", manualOption,
   121  		"Include the proxy sidecar container spec in the YAML output (the auto-injector won't pick it up, so config annotations aren't supported) (default false)",
   122  	)
   123  
   124  	cmd.Flags().BoolVar(
   125  		&ignoreCluster, "ignore-cluster", false,
   126  		"Ignore the current Kubernetes cluster when checking for existing cluster configuration (default false)",
   127  	)
   128  
   129  	cmd.Flags().BoolVar(&enableDebugSidecar, "enable-debug-sidecar", enableDebugSidecar,
   130  		"Inject a debug sidecar for data plane debugging")
   131  
   132  	cmd.Flags().DurationVar(
   133  		&closeWaitTimeout, "close-wait-timeout", closeWaitTimeout,
   134  		"Sets nf_conntrack_tcp_timeout_close_wait")
   135  
   136  	cmd.Flags().StringVarP(&output, "output", "o", "yaml", "Output format, one of: json|yaml")
   137  
   138  	cmd.Flags().AddFlagSet(proxyFlagSet)
   139  	cmd.Flags().AddFlagSet(injectFlagSet)
   140  
   141  	return cmd
   142  }
   143  
   144  func uninjectAndInject(inputs []io.Reader, errWriter, outWriter io.Writer, transformer *resourceTransformerInject, output string) int {
   145  	var out bytes.Buffer
   146  	if exitCode := runUninjectSilentCmd(inputs, errWriter, &out, transformer.values, "yaml"); exitCode != 0 {
   147  		return exitCode
   148  	}
   149  	return runInjectCmd([]io.Reader{&out}, errWriter, outWriter, transformer, output)
   150  }
   151  
   152  func (rt resourceTransformerInject) transform(bytes []byte) ([]byte, []inject.Report, error) {
   153  	conf := inject.NewResourceConfig(rt.values, inject.OriginCLI, controlPlaneNamespace)
   154  
   155  	if rt.enableDebugSidecar {
   156  		conf.AppendPodAnnotation(k8s.ProxyEnableDebugAnnotation, "true")
   157  	}
   158  
   159  	if rt.closeWaitTimeout != time.Duration(0) {
   160  		conf.AppendPodAnnotation(k8s.CloseWaitTimeoutAnnotation, rt.closeWaitTimeout.String())
   161  	}
   162  
   163  	report, err := conf.ParseMetaAndYAML(bytes)
   164  	if err != nil {
   165  		return nil, nil, err
   166  	}
   167  
   168  	if conf.IsControlPlaneComponent() && !rt.injectProxy {
   169  		return nil, nil, errors.New("--manual must be set when injecting control plane components")
   170  	}
   171  
   172  	if conf.IsService() {
   173  		opaquePorts, ok := rt.overrideAnnotations[k8s.ProxyOpaquePortsAnnotation]
   174  		if ok {
   175  			annotations := map[string]string{k8s.ProxyOpaquePortsAnnotation: opaquePorts}
   176  			bytes, err = conf.AnnotateService(annotations)
   177  			report.Annotated = true
   178  		}
   179  		return bytes, []inject.Report{*report}, err
   180  	}
   181  	if rt.allowNsInject && conf.IsNamespace() {
   182  		bytes, err = conf.AnnotateNamespace(rt.overrideAnnotations)
   183  		report.Annotated = true
   184  		return bytes, []inject.Report{*report}, err
   185  	}
   186  	if conf.HasPodTemplate() && len(rt.overrideAnnotations) > 0 {
   187  		conf.AppendPodAnnotations(rt.overrideAnnotations)
   188  		report.Annotated = true
   189  	}
   190  
   191  	if ok, _ := report.Injectable(); !ok {
   192  		if errs := report.ThrowInjectError(); len(errs) > 0 {
   193  			return bytes, []inject.Report{*report}, fmt.Errorf("failed to inject %s%s%s: %w", report.Kind, slash, report.Name, concatErrors(errs, ", "))
   194  		}
   195  		return bytes, []inject.Report{*report}, nil
   196  	}
   197  
   198  	if rt.injectProxy {
   199  		// delete the inject annotation if present as its not needed in the manual case
   200  		// prevents injector from taking a different code path in the ingress mode
   201  		delete(rt.overrideAnnotations, k8s.ProxyInjectAnnotation)
   202  		conf.AppendPodAnnotation(k8s.CreatedByAnnotation, k8s.CreatedByAnnotationValue())
   203  	} else if !rt.values.Proxy.IsIngress { // Add enabled annotation only if its not ingress mode to prevent overriding the annotation
   204  		// flag the auto-injector to inject the proxy, regardless of the namespace annotation
   205  		conf.AppendPodAnnotation(k8s.ProxyInjectAnnotation, k8s.ProxyInjectEnabled)
   206  	}
   207  
   208  	patchJSON, err := conf.GetPodPatch(rt.injectProxy)
   209  	if err != nil {
   210  		return nil, nil, err
   211  	}
   212  
   213  	if len(patchJSON) == 0 {
   214  		return bytes, []inject.Report{*report}, nil
   215  	}
   216  	log.Infof("patch generated for: %s", report.ResName())
   217  	log.Debugf("patch: %s", patchJSON)
   218  	patch, err := jsonpatch.DecodePatch(patchJSON)
   219  	if err != nil {
   220  		return nil, nil, err
   221  	}
   222  	origJSON, err := yaml.YAMLToJSON(bytes)
   223  	if err != nil {
   224  		return nil, nil, err
   225  	}
   226  	injectedJSON, err := patch.Apply(origJSON)
   227  	if err != nil {
   228  		return nil, nil, err
   229  	}
   230  	injectedYAML, err := conf.JSONToYAML(injectedJSON)
   231  	if err != nil {
   232  		return nil, nil, err
   233  	}
   234  	return injectedYAML, []inject.Report{*report}, nil
   235  }
   236  
   237  func (resourceTransformerInject) generateReport(reports []inject.Report, output io.Writer) {
   238  	injected := []inject.Report{}
   239  	annotatable := false
   240  	hostNetwork := []string{}
   241  	sidecar := []string{}
   242  	udp := []string{}
   243  	injectDisabled := []string{}
   244  	automountServiceAccountTokenFalse := []string{}
   245  	warningsPrinted := verbose
   246  
   247  	for _, r := range reports {
   248  		if b, _ := r.Injectable(); b {
   249  			injected = append(injected, r)
   250  		}
   251  
   252  		if r.IsAnnotatable() {
   253  			annotatable = true
   254  		}
   255  
   256  		if r.HostNetwork {
   257  			hostNetwork = append(hostNetwork, r.ResName())
   258  			warningsPrinted = true
   259  		}
   260  
   261  		if r.Sidecar {
   262  			sidecar = append(sidecar, r.ResName())
   263  			warningsPrinted = true
   264  		}
   265  
   266  		if r.UDP {
   267  			udp = append(udp, r.ResName())
   268  			warningsPrinted = true
   269  		}
   270  
   271  		if r.InjectDisabled {
   272  			injectDisabled = append(injectDisabled, r.ResName())
   273  			warningsPrinted = true
   274  		}
   275  
   276  		if !r.AutomountServiceAccountToken {
   277  			automountServiceAccountTokenFalse = append(automountServiceAccountTokenFalse, r.ResName())
   278  			warningsPrinted = true
   279  		}
   280  	}
   281  
   282  	//
   283  	// Warnings
   284  	//
   285  
   286  	// Leading newline to separate from yaml output on stdout
   287  	output.Write([]byte("\n"))
   288  
   289  	if len(hostNetwork) > 0 {
   290  		output.Write([]byte(fmt.Sprintf("%s \"hostNetwork: true\" detected in %s\n", warnStatus, strings.Join(hostNetwork, ", "))))
   291  	} else if verbose {
   292  		output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, hostNetworkDesc)))
   293  	}
   294  
   295  	if len(sidecar) > 0 {
   296  		output.Write([]byte(fmt.Sprintf("%s known 3rd party sidecar detected in %s\n", warnStatus, strings.Join(sidecar, ", "))))
   297  	} else if verbose {
   298  		output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, sidecarDesc)))
   299  	}
   300  
   301  	if len(injectDisabled) > 0 {
   302  		output.Write([]byte(fmt.Sprintf("%s \"%s: %s\" annotation set on %s\n",
   303  			warnStatus, k8s.ProxyInjectAnnotation, k8s.ProxyInjectDisabled, strings.Join(injectDisabled, ", "))))
   304  	} else if verbose {
   305  		output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, injectDisabledDesc)))
   306  	}
   307  
   308  	if len(injected) == 0 && !annotatable {
   309  		output.Write([]byte(fmt.Sprintf("%s no supported objects found\n", warnStatus)))
   310  		warningsPrinted = true
   311  	} else if verbose {
   312  		output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, unsupportedDesc)))
   313  	}
   314  
   315  	if len(udp) > 0 {
   316  		verb := "uses"
   317  		if len(udp) > 1 {
   318  			verb = "use"
   319  		}
   320  		output.Write([]byte(fmt.Sprintf("%s %s %s \"protocol: UDP\"\n", warnStatus, strings.Join(udp, ", "), verb)))
   321  	} else if verbose {
   322  		output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, udpDesc)))
   323  	}
   324  
   325  	if len(automountServiceAccountTokenFalse) == 0 && verbose {
   326  		output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, automountServiceAccountTokenDesc)))
   327  	}
   328  
   329  	//
   330  	// Summary
   331  	//
   332  	if warningsPrinted {
   333  		output.Write([]byte("\n"))
   334  	}
   335  
   336  	for _, r := range reports {
   337  		ok, _ := r.Injectable()
   338  		if ok {
   339  			output.Write([]byte(fmt.Sprintf("%s \"%s\" injected\n", r.Kind, r.Name)))
   340  		}
   341  		if !ok && !r.Annotated {
   342  			if r.Kind != "" {
   343  				output.Write([]byte(fmt.Sprintf("%s \"%s\" skipped\n", r.Kind, r.Name)))
   344  			} else {
   345  				output.Write([]byte(fmt.Sprintln("document missing \"kind\" field, skipped")))
   346  			}
   347  		}
   348  		if !ok && r.Annotated {
   349  			output.Write([]byte(fmt.Sprintf("%s \"%s\" annotated\n", r.Kind, r.Name)))
   350  		}
   351  	}
   352  
   353  	// Trailing newline to separate from kubectl output if piping
   354  	output.Write([]byte("\n"))
   355  }
   356  
   357  func fetchConfigs(ctx context.Context) (*linkerd2.Values, error) {
   358  
   359  	hc := healthcheck.NewWithCoreChecks(&healthcheck.Options{
   360  		ControlPlaneNamespace: controlPlaneNamespace,
   361  		KubeConfig:            kubeconfigPath,
   362  		Impersonate:           impersonate,
   363  		ImpersonateGroup:      impersonateGroup,
   364  		KubeContext:           kubeContext,
   365  		APIAddr:               apiAddr,
   366  		RetryDeadline:         time.Time{},
   367  	})
   368  	hc.RunWithExitOnError()
   369  
   370  	api, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
   371  	if err != nil {
   372  		return nil, err
   373  	}
   374  
   375  	// Get the New Linkerd Configuration
   376  	_, values, err := healthcheck.FetchCurrentConfiguration(ctx, api, controlPlaneNamespace)
   377  	return values, err
   378  }
   379  
   380  // getOverrideAnnotations uses command-line overrides to update the provided configs.
   381  // the overrideAnnotations map keeps track of which configs are overridden, by
   382  // storing the corresponding annotations and values.
   383  func getOverrideAnnotations(values *linkerd2.Values, base *linkerd2.Values) map[string]string {
   384  	overrideAnnotations := make(map[string]string)
   385  
   386  	proxy := values.Proxy
   387  	baseProxy := base.Proxy
   388  	if proxy.Image.Version != baseProxy.Image.Version {
   389  		overrideAnnotations[k8s.ProxyVersionOverrideAnnotation] = proxy.Image.Version
   390  	}
   391  
   392  	if values.ProxyInit.IgnoreInboundPorts != base.ProxyInit.IgnoreInboundPorts {
   393  		overrideAnnotations[k8s.ProxyIgnoreInboundPortsAnnotation] = values.ProxyInit.IgnoreInboundPorts
   394  	}
   395  	if values.ProxyInit.IgnoreOutboundPorts != base.ProxyInit.IgnoreOutboundPorts {
   396  		overrideAnnotations[k8s.ProxyIgnoreOutboundPortsAnnotation] = values.ProxyInit.IgnoreOutboundPorts
   397  	}
   398  
   399  	if proxy.Ports.Admin != baseProxy.Ports.Admin {
   400  		overrideAnnotations[k8s.ProxyAdminPortAnnotation] = fmt.Sprintf("%d", proxy.Ports.Admin)
   401  	}
   402  	if proxy.Ports.Control != baseProxy.Ports.Control {
   403  		overrideAnnotations[k8s.ProxyControlPortAnnotation] = fmt.Sprintf("%d", proxy.Ports.Control)
   404  	}
   405  	if proxy.Ports.Inbound != baseProxy.Ports.Inbound {
   406  		overrideAnnotations[k8s.ProxyInboundPortAnnotation] = fmt.Sprintf("%d", proxy.Ports.Inbound)
   407  	}
   408  	if proxy.Ports.Outbound != baseProxy.Ports.Outbound {
   409  		overrideAnnotations[k8s.ProxyOutboundPortAnnotation] = fmt.Sprintf("%d", proxy.Ports.Outbound)
   410  	}
   411  	if proxy.OpaquePorts != baseProxy.OpaquePorts {
   412  		overrideAnnotations[k8s.ProxyOpaquePortsAnnotation] = proxy.OpaquePorts
   413  	}
   414  
   415  	if proxy.Image.Name != baseProxy.Image.Name {
   416  		overrideAnnotations[k8s.ProxyImageAnnotation] = proxy.Image.Name
   417  	}
   418  	if values.ProxyInit.Image.Name != base.ProxyInit.Image.Name {
   419  		overrideAnnotations[k8s.ProxyInitImageAnnotation] = values.ProxyInit.Image.Name
   420  	}
   421  	if values.DebugContainer.Image.Name != base.DebugContainer.Image.Name {
   422  		overrideAnnotations[k8s.DebugImageAnnotation] = values.DebugContainer.Image.Name
   423  	}
   424  
   425  	if values.ProxyInit.Image.Version != base.ProxyInit.Image.Version {
   426  		overrideAnnotations[k8s.ProxyInitImageVersionAnnotation] = values.ProxyInit.Image.Version
   427  	}
   428  
   429  	if values.DebugContainer.Image.Version != base.DebugContainer.Image.Version {
   430  		overrideAnnotations[k8s.DebugImageVersionAnnotation] = values.DebugContainer.Image.Version
   431  	}
   432  
   433  	if proxy.Image.PullPolicy != baseProxy.Image.PullPolicy {
   434  		overrideAnnotations[k8s.ProxyImagePullPolicyAnnotation] = proxy.Image.PullPolicy
   435  	}
   436  
   437  	if proxy.UID != baseProxy.UID {
   438  		overrideAnnotations[k8s.ProxyUIDAnnotation] = strconv.FormatInt(proxy.UID, 10)
   439  	}
   440  
   441  	if proxy.GID >= 0 && (baseProxy.GID < 0 || proxy.GID != baseProxy.GID) {
   442  		overrideAnnotations[k8s.ProxyGIDAnnotation] = strconv.FormatInt(proxy.GID, 10)
   443  	}
   444  
   445  	if proxy.LogLevel != baseProxy.LogLevel {
   446  		overrideAnnotations[k8s.ProxyLogLevelAnnotation] = proxy.LogLevel
   447  	}
   448  
   449  	if proxy.LogFormat != baseProxy.LogFormat {
   450  		overrideAnnotations[k8s.ProxyLogFormatAnnotation] = proxy.LogFormat
   451  	}
   452  
   453  	if proxy.RequireIdentityOnInboundPorts != baseProxy.RequireIdentityOnInboundPorts {
   454  		overrideAnnotations[k8s.ProxyRequireIdentityOnInboundPortsAnnotation] = proxy.RequireIdentityOnInboundPorts
   455  	}
   456  
   457  	if proxy.EnableExternalProfiles != baseProxy.EnableExternalProfiles {
   458  		overrideAnnotations[k8s.ProxyEnableExternalProfilesAnnotation] = strconv.FormatBool(proxy.EnableExternalProfiles)
   459  	}
   460  
   461  	if proxy.IsIngress != baseProxy.IsIngress {
   462  		overrideAnnotations[k8s.ProxyInjectAnnotation] = k8s.ProxyInjectIngress
   463  	}
   464  
   465  	if proxy.Resources.CPU.Request != baseProxy.Resources.CPU.Request {
   466  		overrideAnnotations[k8s.ProxyCPURequestAnnotation] = proxy.Resources.CPU.Request
   467  	}
   468  	if proxy.Resources.CPU.Limit != baseProxy.Resources.CPU.Limit {
   469  		overrideAnnotations[k8s.ProxyCPULimitAnnotation] = proxy.Resources.CPU.Limit
   470  	}
   471  	if proxy.Resources.Memory.Request != baseProxy.Resources.Memory.Request {
   472  		overrideAnnotations[k8s.ProxyMemoryRequestAnnotation] = proxy.Resources.Memory.Request
   473  	}
   474  	if proxy.Resources.Memory.Limit != baseProxy.Resources.Memory.Limit {
   475  		overrideAnnotations[k8s.ProxyMemoryLimitAnnotation] = proxy.Resources.Memory.Limit
   476  	}
   477  	if proxy.WaitBeforeExitSeconds != baseProxy.WaitBeforeExitSeconds {
   478  		overrideAnnotations[k8s.ProxyWaitBeforeExitSecondsAnnotation] = uintToString(proxy.WaitBeforeExitSeconds)
   479  	}
   480  
   481  	if proxy.Await != baseProxy.Await {
   482  		if proxy.Await {
   483  			overrideAnnotations[k8s.ProxyAwait] = k8s.Enabled
   484  		} else {
   485  			overrideAnnotations[k8s.ProxyAwait] = k8s.Disabled
   486  		}
   487  	}
   488  
   489  	if proxy.DefaultInboundPolicy != baseProxy.DefaultInboundPolicy {
   490  		overrideAnnotations[k8s.ProxyDefaultInboundPolicyAnnotation] = proxy.DefaultInboundPolicy
   491  	}
   492  
   493  	if proxy.AccessLog != baseProxy.AccessLog {
   494  		overrideAnnotations[k8s.ProxyAccessLogAnnotation] = proxy.AccessLog
   495  	}
   496  
   497  	if proxy.ShutdownGracePeriod != baseProxy.ShutdownGracePeriod {
   498  		overrideAnnotations[k8s.ProxyShutdownGracePeriodAnnotation] = proxy.ShutdownGracePeriod
   499  	}
   500  
   501  	if proxy.NativeSidecar != baseProxy.NativeSidecar {
   502  		overrideAnnotations[k8s.ProxyEnableNativeSidecarAnnotation] = strconv.FormatBool(proxy.NativeSidecar)
   503  	}
   504  
   505  	return overrideAnnotations
   506  }
   507  
   508  func uintToString(v uint64) string {
   509  	return strconv.FormatUint(v, 10)
   510  }
   511  

View as plain text