1 package cmd
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "os"
8 "time"
9
10 pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
11 "github.com/linkerd/linkerd2/pkg/healthcheck"
12 "github.com/linkerd/linkerd2/pkg/k8s"
13 "github.com/linkerd/linkerd2/pkg/version"
14 "github.com/spf13/cobra"
15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16 )
17
18 const (
19
20
21 JaegerExtensionName = "jaeger"
22
23
24
25 JaegerLegacyExtension = "linkerd-jaeger"
26
27
28 linkerdJaegerExtensionCheck healthcheck.CategoryID = "linkerd-jaeger"
29 )
30
31 var (
32 jaegerNamespace string
33 )
34
35 type checkOptions struct {
36 wait time.Duration
37 output string
38 proxy bool
39 namespace string
40 }
41
42 func jaegerCategory(hc *healthcheck.HealthChecker) *healthcheck.Category {
43
44 checkers := []healthcheck.Checker{}
45
46 checkers = append(checkers,
47 *healthcheck.NewChecker("linkerd-jaeger extension Namespace exists").
48 WithHintAnchor("l5d-jaeger-ns-exists").
49 Fatal().
50 WithCheck(func(ctx context.Context) error {
51
52 ns, err := hc.KubeAPIClient().GetNamespaceWithExtensionLabel(ctx, JaegerExtensionName)
53 if err != nil {
54 return err
55 }
56 jaegerNamespace = ns.Name
57 return nil
58 }))
59
60 checkers = append(checkers,
61 *healthcheck.NewChecker("jaeger extension pods are injected").
62 WithHintAnchor("l5d-jaeger-pods-injection").
63 Warning().
64 WithCheck(func(ctx context.Context) error {
65
66 pods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, jaegerNamespace)
67 if err != nil {
68 return err
69 }
70 return healthcheck.CheckIfDataPlanePodsExist(pods)
71 }))
72
73 checkers = append(checkers,
74 *healthcheck.NewChecker("jaeger injector pods are running").
75 WithHintAnchor("l5d-jaeger-pods-running").
76 Warning().
77 WithRetryDeadline(hc.RetryDeadline).
78 SurfaceErrorOnRetry().
79 WithCheck(func(ctx context.Context) error {
80 podList, err := hc.KubeAPIClient().CoreV1().Pods(jaegerNamespace).List(ctx, metav1.ListOptions{
81 LabelSelector: fmt.Sprintf("%s=%s", k8s.LinkerdExtensionLabel, JaegerExtensionName),
82 })
83 if err != nil {
84 return err
85 }
86
87
88 err = healthcheck.CheckForPods(podList.Items, []string{"jaeger-injector"})
89 if err != nil {
90 return err
91 }
92
93 return healthcheck.CheckPodsRunning(podList.Items, jaegerNamespace)
94 }))
95
96 checkers = append(checkers,
97 *healthcheck.NewChecker("jaeger extension proxies are healthy").
98 WithHintAnchor("l5d-jaeger-proxy-healthy").
99 Warning().
100 WithRetryDeadline(hc.RetryDeadline).
101 SurfaceErrorOnRetry().
102 WithCheck(func(ctx context.Context) error {
103 return hc.CheckProxyHealth(ctx, hc.ControlPlaneNamespace, jaegerNamespace)
104 }))
105
106 checkers = append(checkers,
107 *healthcheck.NewChecker("jaeger extension proxies are up-to-date").
108 WithHintAnchor("l5d-jaeger-proxy-cp-version").
109 Warning().
110 WithCheck(func(ctx context.Context) error {
111 var err error
112 if hc.VersionOverride != "" {
113 hc.LatestVersions, err = version.NewChannels(hc.VersionOverride)
114 } else {
115 uuid := "unknown"
116 if hc.UUID() != "" {
117 uuid = hc.UUID()
118 }
119 hc.LatestVersions, err = version.GetLatestVersions(ctx, uuid, "cli")
120 }
121 if err != nil {
122 return err
123 }
124
125 pods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, jaegerNamespace)
126 if err != nil {
127 return err
128 }
129
130 return hc.CheckProxyVersionsUpToDate(pods)
131 }))
132
133 checkers = append(checkers,
134 *healthcheck.NewChecker("jaeger extension proxies and cli versions match").
135 WithHintAnchor("l5d-jaeger-proxy-cli-version").
136 Warning().
137 WithCheck(func(ctx context.Context) error {
138 pods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, jaegerNamespace)
139 if err != nil {
140 return err
141 }
142
143 return healthcheck.CheckIfProxyVersionsMatchWithCLI(pods)
144 }))
145
146 return healthcheck.NewCategory(linkerdJaegerExtensionCheck, checkers, true)
147 }
148
149 func newCheckOptions() *checkOptions {
150 return &checkOptions{
151 wait: 300 * time.Second,
152 output: healthcheck.TableOutput,
153 }
154 }
155
156 func (options *checkOptions) validate() error {
157 if options.output != healthcheck.TableOutput && options.output != healthcheck.JSONOutput && options.output != healthcheck.ShortOutput {
158 return fmt.Errorf("Invalid output type '%s'. Supported output types are: %s, %s, %s", options.output, healthcheck.JSONOutput, healthcheck.TableOutput, healthcheck.ShortOutput)
159 }
160 return nil
161 }
162
163
164 func NewCmdCheck() *cobra.Command {
165 options := newCheckOptions()
166 cmd := &cobra.Command{
167 Use: "check [flags]",
168 Args: cobra.NoArgs,
169 Short: "Check the Jaeger extension for potential problems",
170 Long: `Check the Jaeger extension for potential problems.
171
172 The check command will perform a series of checks to validate that the Jaeger
173 extension is configured correctly. If the command encounters a failure it will
174 print additional information about the failure and exit with a non-zero exit
175 code.`,
176 Example: ` # Check that the Jaeger extension is up and running
177 linkerd jaeger check`,
178 RunE: func(cmd *cobra.Command, args []string) error {
179 return configureAndRunChecks(stdout, stderr, options)
180 },
181 }
182
183 cmd.Flags().StringVarP(&options.output, "output", "o", options.output, "Output format. One of: table, json, short")
184 cmd.Flags().DurationVar(&options.wait, "wait", options.wait, "Maximum allowed time for all tests to pass")
185 cmd.Flags().BoolVar(&options.proxy, "proxy", options.proxy, "Also run data-plane checks, to determine if the data plane is healthy")
186 cmd.Flags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace to use for --proxy checks (default: all namespaces)")
187
188 pkgcmd.ConfigureNamespaceFlagCompletion(
189 cmd, []string{"namespace"},
190 kubeconfigPath, impersonate, impersonateGroup, kubeContext)
191 pkgcmd.ConfigureOutputFlagCompletion(cmd)
192
193 return cmd
194 }
195
196 func configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions) error {
197 err := options.validate()
198 if err != nil {
199 return fmt.Errorf("Validation error when executing check command: %w", err)
200 }
201
202 hc := healthcheck.NewHealthChecker([]healthcheck.CategoryID{}, &healthcheck.Options{
203 ControlPlaneNamespace: controlPlaneNamespace,
204 KubeConfig: kubeconfigPath,
205 KubeContext: kubeContext,
206 Impersonate: impersonate,
207 ImpersonateGroup: impersonateGroup,
208 APIAddr: apiAddr,
209 RetryDeadline: time.Now().Add(options.wait),
210 DataPlaneNamespace: options.namespace,
211 })
212
213 err = hc.InitializeKubeAPIClient()
214 if err != nil {
215 fmt.Fprintf(werr, "Error initializing k8s API client: %s\n", err)
216 os.Exit(1)
217 }
218
219 hc.AppendCategories(jaegerCategory(hc))
220
221 success, warning := healthcheck.RunChecks(wout, werr, hc, options.output)
222 healthcheck.PrintChecksResult(wout, options.output, success, warning)
223
224 if !success {
225 os.Exit(1)
226 }
227
228 return nil
229 }
230
View as plain text