blob: 2bd153bda0db8bff9ad29cda12d073eb9f7ef998 [file] [log] [blame]
// Copyright Istio Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
)
import (
"github.com/hashicorp/go-multierror"
"github.com/spf13/cobra"
"istio.io/api/annotation"
"istio.io/pkg/log"
appsv1 "k8s.io/api/apps/v1"
apierror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
)
import (
"github.com/apache/dubbo-go-pixiu/istioctl/pkg/util/handlers"
"github.com/apache/dubbo-go-pixiu/pkg/config/analysis/analyzers/util"
"github.com/apache/dubbo-go-pixiu/pkg/config/resource"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collections"
"github.com/apache/dubbo-go-pixiu/pkg/kube/inject"
)
func removeFromMeshCmd() *cobra.Command {
removeFromMeshCmd := &cobra.Command{
Use: "remove-from-mesh",
Aliases: []string{"rm"},
Short: "Remove workloads from Istio service mesh",
Long: `'istioctl experimental remove-from-mesh' restarts pods without an Istio sidecar or removes external service access configuration.
Use 'remove-from-mesh' to quickly test uninjected behavior as part of compatibility troubleshooting.
The 'add-to-mesh' command can be used to add or restore the sidecar.`,
Example: ` # Restart all productpage pods without an Istio sidecar
istioctl experimental remove-from-mesh service productpage
# Restart all details-v1 pods without an Istio sidecar
istioctl x rm service details-v1
# Restart all ratings-v1 pods without an Istio sidecar
istioctl x rm deploy ratings-v1`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("unknown resource type %q", args[0])
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
cmd.HelpFunc()(cmd, args)
return nil
},
}
removeFromMeshCmd.AddCommand(svcUnMeshifyCmd())
removeFromMeshCmd.AddCommand(deploymentUnMeshifyCmd())
removeFromMeshCmd.AddCommand(externalSvcUnMeshifyCmd())
removeFromMeshCmd.Long += "\n\n" + ExperimentalMsg
return removeFromMeshCmd
}
func deploymentUnMeshifyCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "deployment <deployment>",
Aliases: []string{"deploy", "dep"},
Short: "Remove deployment from Istio service mesh",
Long: `'istioctl experimental remove-from-mesh deployment' restarts pods with the Istio sidecar un-injected.
'remove-from-mesh' is a compatibility troubleshooting tool.`,
Example: ` # Restart all productpage-v1 pods without an Istio sidecar
istioctl experimental remove-from-mesh deployment productpage-v1
# Restart all details-v1 pods without an Istio sidecar
istioctl x remove-from-mesh deploy details-v1
# Restart all ratings-v1 pods without an Istio sidecar
istioctl x rm dep ratings-v1`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("expecting deployment name")
}
ns := handlers.HandleNamespace(namespace, defaultNamespace)
if util.IsSystemNamespace(resource.Namespace(ns)) || ns == istioNamespace {
return fmt.Errorf("namespace %s is a system namespace and has no Istio sidecar injected", ns)
}
client, err := interfaceFactory(kubeconfig)
if err != nil {
return err
}
dep, err := client.AppsV1().Deployments(ns).Get(context.TODO(), args[0], metav1.GetOptions{})
if err != nil {
if apierror.IsNotFound(err) {
return fmt.Errorf("deployment %q does not exist", args[0])
}
return err
}
writer := cmd.OutOrStdout()
deps := []appsv1.Deployment{}
deps = append(deps, *dep)
return unInjectSideCarFromDeployment(client, deps, args[0], ns, writer)
},
}
cmd.Long += "\n\n" + ExperimentalMsg
return cmd
}
func svcUnMeshifyCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "service <service>",
Aliases: []string{"svc"},
Short: "Remove Service from Istio service mesh",
Long: `'istioctl experimental remove-from-mesh service' restarts pods with the Istio sidecar un-injected.
'remove-from-mesh' is a compatibility troubleshooting tool.`,
Example: ` # Restart all productpage pods without an Istio sidecar
istioctl experimental remove-from-mesh service productpage
# Restart all details-v1 pods without an Istio sidecar
istioctl x remove-from-mesh svc details-v1
# Restart all ratings-v1 pods without an Istio sidecar
istioctl x rm svc ratings-v1`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("expecting service name")
}
ns := handlers.HandleNamespace(namespace, defaultNamespace)
if util.IsSystemNamespace(resource.Namespace(ns)) || ns == istioNamespace {
return fmt.Errorf("namespace %s is a system namespace and has no Istio sidecar injected", ns)
}
client, err := interfaceFactory(kubeconfig)
if err != nil {
return err
}
_, err = client.CoreV1().Services(ns).Get(context.TODO(), args[0], metav1.GetOptions{})
if err != nil {
if apierror.IsNotFound(err) {
return fmt.Errorf("service %q does not exist, skip", args[0])
}
return err
}
matchingDeployments, err := findDeploymentsForSvc(client, ns, args[0])
if err != nil {
return err
}
writer := cmd.OutOrStdout()
if len(matchingDeployments) == 0 {
fmt.Fprintf(writer, "No deployments found for service %s.%s\n", args[0], ns)
return nil
}
return unInjectSideCarFromDeployment(client, matchingDeployments, args[0], ns, writer)
},
}
cmd.Long += "\n\n" + ExperimentalMsg
return cmd
}
func externalSvcUnMeshifyCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "external-service <svcname>",
Aliases: []string{"es"},
Short: "Remove Service Entry and Kubernetes Service for the external service from Istio service mesh",
Long: `'istioctl experimental remove-from-mesh external-service' removes the ServiceEntry and
the Kubernetes Service for the specified external service (e.g. services running on a VM) from Istio service mesh.
The typical usage scenario is Mesh Expansion on VMs.`,
Example: ` # Remove "vmhttp" service entry rules
istioctl experimental remove-from-mesh external-service vmhttp
# Remove "vmhttp" service entry rules
istioctl x remove-from-mesh es vmhttp
# Remove "vmhttp" service entry rules
istioctl x rm es vmhttp`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("expecting external service name")
}
client, err := interfaceFactory(kubeconfig)
if err != nil {
return err
}
seClient, err := crdFactory(kubeconfig)
if err != nil {
return err
}
writer := cmd.OutOrStdout()
ns := handlers.HandleNamespace(namespace, defaultNamespace)
_, err = client.CoreV1().Services(ns).Get(context.TODO(), args[0], metav1.GetOptions{})
if err == nil {
return removeServiceOnVMFromMesh(seClient, client, ns, args[0], writer)
}
if apierror.IsNotFound(err) {
return fmt.Errorf("service %q does not exist, skip", args[0])
}
return err
},
}
cmd.Long += "\n\n" + ExperimentalMsg
return cmd
}
func unInjectSideCarFromDeployment(client kubernetes.Interface, deps []appsv1.Deployment,
svcName, svcNamespace string, writer io.Writer) error {
var errs error
name := strings.Join([]string{svcName, svcNamespace}, ".")
for _, dep := range deps {
log.Debugf("updating deployment %s.%s with Istio sidecar un-injected",
dep.Name, dep.Namespace)
res := dep.DeepCopy()
depName := strings.Join([]string{dep.Name, dep.Namespace}, ".")
sidecarInjected := false
podSpec := dep.Spec.Template.Spec.DeepCopy()
for _, c := range podSpec.Containers {
if c.Name == proxyContainerName {
sidecarInjected = true
break
}
}
if !sidecarInjected {
// The sidecar wasn't explicitly injected. (Unless there is annotation it may have been auto injected)
if val := dep.Spec.Template.Annotations[annotation.SidecarInject.Name]; strings.EqualFold(val, "false") {
fmt.Fprintf(writer, "deployment %q has no Istio sidecar injected. Skipping.\n", depName)
continue
}
}
var appProbe inject.KubeAppProbers
appProbeStr := retrieveAppProbe(podSpec.Containers)
if appProbeStr != "" {
err := json.Unmarshal([]byte(appProbeStr), &appProbe)
errs = multierror.Append(errs, err)
}
if appProbe != nil {
podSpec.Containers = restoreAppProbes(podSpec.Containers, appProbe)
}
podSpec.InitContainers = removeInjectedContainers(podSpec.InitContainers, initContainerName)
podSpec.InitContainers = removeInjectedContainers(podSpec.InitContainers, initValidationContainerName)
podSpec.InitContainers = removeInjectedContainers(podSpec.InitContainers, enableCoreDumpContainerName)
podSpec.Containers = removeInjectedContainers(podSpec.Containers, proxyContainerName)
podSpec.Volumes = removeInjectedVolumes(podSpec.Volumes, certVolumeName)
podSpec.Volumes = removeInjectedVolumes(podSpec.Volumes, dataVolumeName)
podSpec.Volumes = removeInjectedVolumes(podSpec.Volumes, envoyVolumeName)
podSpec.Volumes = removeInjectedVolumes(podSpec.Volumes, jwtTokenVolumeName)
podSpec.Volumes = removeInjectedVolumes(podSpec.Volumes, pilotCertVolumeName)
podSpec.Volumes = removeInjectedVolumes(podSpec.Volumes, podInfoVolumeName)
removeDNSConfig(podSpec.DNSConfig)
res.Spec.Template.Spec = *podSpec
// If we are in an auto-inject namespace, removing the sidecar isn't enough, we
// must prevent injection
if res.Spec.Template.Annotations == nil {
res.Spec.Template.Annotations = make(map[string]string)
}
res.Spec.Template.Annotations[annotation.SidecarInject.Name] = "false"
if _, err := client.AppsV1().Deployments(svcNamespace).Update(context.TODO(), res, metav1.UpdateOptions{}); err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to update deployment %q for service %q due to %v", depName, name, err))
continue
}
d := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: dep.Name,
Namespace: dep.Namespace,
UID: dep.UID,
OwnerReferences: dep.OwnerReferences,
},
}
if _, err := client.AppsV1().Deployments(svcNamespace).UpdateStatus(context.TODO(), d, metav1.UpdateOptions{}); err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to update deployment status %q for service %q due to %v", depName, name, err))
continue
}
fmt.Fprintf(writer, "deployment %q updated successfully with Istio sidecar un-injected.\n", depName)
}
return errs
}
// removeServiceOnVMFromMesh removes the Service Entry and K8s service for the specified external service
func removeServiceOnVMFromMesh(dynamicClient dynamic.Interface, client kubernetes.Interface, ns string,
svcName string, writer io.Writer) error {
// Pre-check Kubernetes service and service entry does not exist.
_, err := client.CoreV1().Services(ns).Get(context.TODO(), svcName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("service %q does not exist, skip", svcName)
}
serviceEntryGVR := schema.GroupVersionResource{
Group: "networking.istio.io",
Version: collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Version(),
Resource: "serviceentries",
}
_, err = dynamicClient.Resource(serviceEntryGVR).Namespace(ns).Get(context.TODO(), resourceName(svcName), metav1.GetOptions{})
if err != nil {
return fmt.Errorf("service entry %q does not exist, skip", resourceName(svcName))
}
err = client.CoreV1().Services(ns).Delete(context.TODO(), svcName, metav1.DeleteOptions{})
if err != nil {
return fmt.Errorf("failed to delete Kubernetes service %q due to %v", svcName, err)
}
name := strings.Join([]string{svcName, ns}, ".")
fmt.Fprintf(writer, "Kubernetes Service %q has been deleted for external service %q\n", name, svcName)
err = dynamicClient.Resource(serviceEntryGVR).Namespace(ns).Delete(context.TODO(), resourceName(svcName), metav1.DeleteOptions{})
if err != nil {
return fmt.Errorf("failed to delete service entry %q due to %v", resourceName(svcName), err)
}
fmt.Fprintf(writer, "Service Entry %q has been deleted for external service %q\n", resourceName(svcName), svcName)
return nil
}