mirror of https://github.com/linkerd/linkerd2.git
373 lines
12 KiB
Go
373 lines
12 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/linkerd/linkerd2/cli/flag"
|
|
charts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
|
|
l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
|
|
"github.com/linkerd/linkerd2/pkg/healthcheck"
|
|
"github.com/linkerd/linkerd2/pkg/k8s"
|
|
"github.com/linkerd/linkerd2/pkg/tls"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
corev1 "k8s.io/api/core/v1"
|
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"sigs.k8s.io/yaml"
|
|
)
|
|
|
|
const (
|
|
controlPlaneMessage = "Don't forget to run `linkerd upgrade control-plane`!"
|
|
failMessage = "For troubleshooting help, visit: https://linkerd.io/upgrade/#troubleshooting\n"
|
|
trustRootChangeMessage = "Rotating the trust anchors will affect existing proxies\nSee https://linkerd.io/2/tasks/rotating_identity_certificates/ for more information"
|
|
)
|
|
|
|
var (
|
|
addOnOverwrite bool
|
|
manifests string
|
|
force bool
|
|
)
|
|
|
|
/* The upgrade commands all follow the same flow:
|
|
* 1. Load default values from the Linkerd2 chart
|
|
* 2. Update the values with stored overrides
|
|
* 3. Apply flags to further modify the values
|
|
* 4. Render the chart using those values
|
|
*
|
|
* The individual commands (upgrade, upgrade config, and upgrade control-plane)
|
|
* differ in which flags are available to each, what pre-check validations
|
|
* are done, and which subset of the chart is rendered.
|
|
*/
|
|
|
|
// newCmdUpgradeConfig is a subcommand for `linkerd upgrade config`
|
|
func newCmdUpgradeConfig(values *l5dcharts.Values) *cobra.Command {
|
|
allStageFlags, allStageFlagSet := makeAllStageFlags(values)
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "config [flags]",
|
|
Args: cobra.NoArgs,
|
|
Short: "Output Kubernetes cluster-wide resources to upgrade an existing Linkerd",
|
|
Long: `Output Kubernetes cluster-wide resources to upgrade an existing Linkerd.
|
|
|
|
Note that this command should be followed by "linkerd upgrade control-plane".`,
|
|
Example: ` # Default upgrade.
|
|
linkerd upgrade config | kubectl apply -f -`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
k, err := k8sClient(manifests)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return upgradeRunE(cmd.Context(), k, allStageFlags, configStage)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().AddFlagSet(allStageFlagSet)
|
|
|
|
return cmd
|
|
}
|
|
|
|
// newCmdUpgradeControlPlane is a subcommand for `linkerd upgrade control-plane`
|
|
func newCmdUpgradeControlPlane(values *l5dcharts.Values) *cobra.Command {
|
|
allStageFlags, allStageFlagSet := makeAllStageFlags(values)
|
|
installUpgradeFlags, installUpgradeFlagSet, err := makeInstallUpgradeFlags(values)
|
|
if err != nil {
|
|
fmt.Fprint(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
proxyFlags, proxyFlagSet := makeProxyFlags(values)
|
|
|
|
flags := flattenFlags(allStageFlags, installUpgradeFlags, proxyFlags)
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "control-plane [flags]",
|
|
Args: cobra.NoArgs,
|
|
Short: "Output Kubernetes control plane resources to upgrade an existing Linkerd",
|
|
Long: `Output Kubernetes control plane resources to upgrade an existing Linkerd.
|
|
|
|
Note that the default flag values for this command come from the Linkerd control
|
|
plane. The default values displayed in the Flags section below only apply to the
|
|
install command. It should be run after "linkerd upgrade config".`,
|
|
Example: ` # Default upgrade.
|
|
linkerd upgrade control-plane | kubectl apply -f -`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
k, err := k8sClient(manifests)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return upgradeRunE(cmd.Context(), k, flags, controlPlaneStage)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().AddFlagSet(allStageFlagSet)
|
|
cmd.Flags().AddFlagSet(installUpgradeFlagSet)
|
|
cmd.Flags().AddFlagSet(proxyFlagSet)
|
|
|
|
return cmd
|
|
}
|
|
|
|
func newCmdUpgrade() *cobra.Command {
|
|
values, err := l5dcharts.NewValues(false)
|
|
if err != nil {
|
|
fmt.Fprint(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
allStageFlags, allStageFlagSet := makeAllStageFlags(values)
|
|
installUpgradeFlags, installUpgradeFlagSet, err := makeInstallUpgradeFlags(values)
|
|
if err != nil {
|
|
fmt.Fprint(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
proxyFlags, proxyFlagSet := makeProxyFlags(values)
|
|
flags := flattenFlags(allStageFlags, installUpgradeFlags, proxyFlags)
|
|
|
|
upgradeFlagSet := makeUpgradeFlags()
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "upgrade [flags]",
|
|
Args: cobra.NoArgs,
|
|
Short: "Output Kubernetes configs to upgrade an existing Linkerd control plane",
|
|
Long: `Output Kubernetes configs to upgrade an existing Linkerd control plane.
|
|
|
|
Note that the default flag values for this command come from the Linkerd control
|
|
plane. The default values displayed in the Flags section below only apply to the
|
|
install command.`,
|
|
|
|
Example: ` # Default upgrade.
|
|
linkerd upgrade | kubectl apply --prune -l linkerd.io/control-plane-ns=linkerd -f -
|
|
|
|
# Similar to install, upgrade may also be broken up into two stages, by user
|
|
# privilege.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
k, err := k8sClient(manifests)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return upgradeRunE(cmd.Context(), k, flags, "")
|
|
},
|
|
}
|
|
|
|
cmd.Flags().AddFlagSet(allStageFlagSet)
|
|
cmd.Flags().AddFlagSet(installUpgradeFlagSet)
|
|
cmd.Flags().AddFlagSet(proxyFlagSet)
|
|
cmd.PersistentFlags().AddFlagSet(upgradeFlagSet)
|
|
|
|
cmd.AddCommand(newCmdUpgradeConfig(values))
|
|
cmd.AddCommand(newCmdUpgradeControlPlane(values))
|
|
|
|
return cmd
|
|
}
|
|
|
|
func k8sClient(manifestsFile string) (*k8s.KubernetesAPI, error) {
|
|
// We need a Kubernetes client to fetch configs and issuer secrets.
|
|
var k *k8s.KubernetesAPI
|
|
var err error
|
|
if manifestsFile != "" {
|
|
readers, err := read(manifestsFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to parse manifests from %s: %s", manifestsFile, err)
|
|
}
|
|
|
|
k, err = k8s.NewFakeAPIFromManifests(readers)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to parse Kubernetes objects from manifest %s: %s", manifestsFile, err)
|
|
}
|
|
} else {
|
|
k, err = k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to create a kubernetes client: %s", err)
|
|
}
|
|
}
|
|
return k, nil
|
|
}
|
|
|
|
// makeUpgradeFlags returns a FlagSet of flags that are only accessible at upgrade-time
|
|
// and not at install-time. These flags do not configure the Values used to
|
|
// render the chart but instead modify the behavior of the upgrade command itself.
|
|
// They are not persisted in any way.
|
|
func makeUpgradeFlags() *pflag.FlagSet {
|
|
upgradeFlags := pflag.NewFlagSet("upgrade-only", pflag.ExitOnError)
|
|
|
|
upgradeFlags.StringVar(
|
|
&manifests, "from-manifests", "",
|
|
"Read config from a Linkerd install YAML rather than from Kubernetes",
|
|
)
|
|
upgradeFlags.BoolVar(
|
|
&force, "force", false,
|
|
"Force upgrade operation even when issuer certificate does not work with the trust anchors of all proxies",
|
|
)
|
|
upgradeFlags.BoolVar(
|
|
&addOnOverwrite, "addon-overwrite", false,
|
|
"Overwrite add-on configuration instead of loading the existing config (or reset to defaults if no new config is specified)",
|
|
)
|
|
return upgradeFlags
|
|
}
|
|
|
|
func upgradeRunE(ctx context.Context, k *k8s.KubernetesAPI, flags []flag.Flag, stage string) error {
|
|
|
|
buf, err := upgrade(ctx, k, flags, stage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, flag := range flags {
|
|
if flag.Name() == "identity-trust-anchors-file" && flag.IsSet() {
|
|
fmt.Fprintf(os.Stderr, "\n%s %s\n\n", warnStatus, trustRootChangeMessage)
|
|
}
|
|
}
|
|
if stage == configStage {
|
|
fmt.Fprintf(os.Stderr, "%s\n\n", controlPlaneMessage)
|
|
}
|
|
|
|
buf.WriteTo(os.Stdout)
|
|
|
|
return nil
|
|
}
|
|
|
|
func upgrade(ctx context.Context, k *k8s.KubernetesAPI, flags []flag.Flag, stage string) (bytes.Buffer, error) {
|
|
values, err := loadStoredValues(ctx, k)
|
|
if err != nil {
|
|
return bytes.Buffer{}, err
|
|
}
|
|
// If there is no linkerd-config-overrides secret, assume we are upgrading
|
|
// from a verion of Linkerd prior to the introduction of this secret. In
|
|
// this case we load the values from the legacy linkerd-config configmap.
|
|
if values == nil {
|
|
values, err = loadStoredValuesLegacy(ctx, k)
|
|
if err != nil {
|
|
return bytes.Buffer{}, err
|
|
}
|
|
}
|
|
|
|
if addOnOverwrite {
|
|
err = clearAddonOverrides(values)
|
|
if err != nil {
|
|
return bytes.Buffer{}, err
|
|
}
|
|
}
|
|
|
|
err = flag.ApplySetFlags(values, flags)
|
|
if err != nil {
|
|
return bytes.Buffer{}, err
|
|
}
|
|
|
|
if values.Identity.Issuer.Scheme == string(corev1.SecretTypeTLS) {
|
|
for _, flag := range flags {
|
|
if (flag.Name() == "identity-issuer-certificate-file" || flag.Name() == "identity-issuer-key-file") && flag.IsSet() {
|
|
return bytes.Buffer{}, errors.New("cannot update issuer certificates if you are using external cert management solution")
|
|
}
|
|
}
|
|
}
|
|
|
|
err = validateValues(ctx, k, values)
|
|
if err != nil {
|
|
return bytes.Buffer{}, err
|
|
}
|
|
if !force && values.Identity.Issuer.Scheme == k8s.IdentityIssuerSchemeLinkerd {
|
|
err = ensureIssuerCertWorksWithAllProxies(ctx, k, values)
|
|
if err != nil {
|
|
return bytes.Buffer{}, err
|
|
}
|
|
}
|
|
|
|
// rendering to a buffer and printing full contents of buffer after
|
|
// render is complete, to ensure that okStatus prints separately
|
|
var buf bytes.Buffer
|
|
if err = render(&buf, values, stage); err != nil {
|
|
upgradeErrorf("Could not render upgrade configuration: %s", err)
|
|
}
|
|
|
|
return buf, nil
|
|
}
|
|
|
|
func loadStoredValues(ctx context.Context, k *k8s.KubernetesAPI) (*charts.Values, error) {
|
|
// Load the default values from the chart.
|
|
values, err := charts.NewValues(false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load the stored overrides from the linkerd-config-overrides secret.
|
|
secret, err := k.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, "linkerd-config-overrides", metav1.GetOptions{})
|
|
if kerrors.IsNotFound(err) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bytes, ok := secret.Data["linkerd-config-overrides"]
|
|
if !ok {
|
|
return nil, errors.New("secret/linkerd-config-overrides is missing linkerd-config-overrides data")
|
|
}
|
|
|
|
// Unmarshal the overrides directly onto the values. This has the effect
|
|
// of merging the two with the overrides taking priority.
|
|
err = yaml.Unmarshal(bytes, values)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return values, nil
|
|
}
|
|
|
|
// upgradeErrorf prints the error message and quits the upgrade process
|
|
func upgradeErrorf(format string, a ...interface{}) {
|
|
template := fmt.Sprintf("%s %s\n%s\n", failStatus, format, failMessage)
|
|
fmt.Fprintf(os.Stderr, template, a...)
|
|
os.Exit(1)
|
|
}
|
|
|
|
func ensureIssuerCertWorksWithAllProxies(ctx context.Context, k *k8s.KubernetesAPI, values *l5dcharts.Values) error {
|
|
cred, err := tls.ValidateAndCreateCreds(
|
|
values.Identity.Issuer.TLS.CrtPEM,
|
|
values.Identity.Issuer.TLS.KeyPEM,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
meshedPods, err := healthcheck.GetMeshedPodsIdentityData(ctx, k, "")
|
|
var problematicPods []string
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, pod := range meshedPods {
|
|
anchors, err := tls.DecodePEMCertPool(pod.Anchors)
|
|
|
|
if anchors != nil {
|
|
err = cred.Verify(anchors, "", time.Time{})
|
|
}
|
|
|
|
if err != nil {
|
|
problematicPods = append(problematicPods, fmt.Sprintf("* %s/%s", pod.Namespace, pod.Name))
|
|
}
|
|
}
|
|
|
|
if len(problematicPods) > 0 {
|
|
errorMessageHeader := "You are attempting to use an issuer certificate which does not validate against the trust anchors of the following pods:"
|
|
errorMessageFooter := "These pods do not have the current trust bundle and must be restarted. Use the --force flag to proceed anyway (this will likely prevent those pods from sending or receiving traffic)."
|
|
return fmt.Errorf("%s\n\t%s\n%s", errorMessageHeader, strings.Join(problematicPods, "\n\t"), errorMessageFooter)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func clearAddonOverrides(values *l5dcharts.Values) error {
|
|
defaults, err := l5dcharts.NewValues(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
values.Grafana = defaults.Grafana
|
|
values.Prometheus = defaults.Prometheus
|
|
values.Tracing = defaults.Tracing
|
|
return nil
|
|
}
|