зеркало из https://github.com/Azure/kubectl-aks.git
Merge pull request #1 from Azure/jose/initial-run-and-connectivity
Add commands to run commands and check cluster connectivity
This commit is contained in:
Коммит
dfc3eaae64
|
@ -4,6 +4,8 @@
|
|||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
/kubectl-az
|
||||
/kubectl-az-*-*
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
@ -11,5 +13,3 @@
|
|||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
GOHOSTOS ?= $(shell go env GOHOSTOS)
|
||||
GOHOSTARCH ?= $(shell go env GOHOSTARCH)
|
||||
|
||||
TAG := `git describe --tags --always`
|
||||
VERSION :=
|
||||
|
||||
# Adds a '-dirty' suffix to version string if there are uncommitted changes
|
||||
changes := $(shell git status --porcelain)
|
||||
ifeq ($(changes),)
|
||||
VERSION := $(TAG)
|
||||
else
|
||||
VERSION := $(TAG)-dirty
|
||||
endif
|
||||
|
||||
LDFLAGS := "-X github.com/Azure/kubectl-az/cmd.version=$(VERSION) -extldflags '-static'"
|
||||
|
||||
.DEFAULT_GOAL := kubectl-az
|
||||
|
||||
KUBECTL_AZ_TARGETS = \
|
||||
kubectl-az-linux-amd64 \
|
||||
kubectl-az-linux-arm64 \
|
||||
kubectl-az-darwin-amd64 \
|
||||
kubectl-az-darwin-arm64 \
|
||||
kubectl-az-windows-amd64
|
||||
|
||||
.PHONY: list-kubectl-az-targets
|
||||
list-kubectl-az-targets:
|
||||
@echo $(KUBECTL_AZ_TARGETS)
|
||||
|
||||
.PHONY: kubectl-az-all
|
||||
kubectl-az-all: $(KUBECTL_AZ_TARGETS)
|
||||
|
||||
.PHONY: kubectl-az
|
||||
kubectl-az: kubectl-az-$(GOHOSTOS)-$(GOHOSTARCH)
|
||||
mv kubectl-az-$(GOHOSTOS)-$(GOHOSTARCH) kubectl-az
|
||||
|
||||
# make does not allow implicit rules (with '%') to be phony so let's use
|
||||
# the 'phony_explicit' dependency to make implicit rules inherit the phony
|
||||
# attribute
|
||||
.PHONY: phony_explicit
|
||||
phony_explicit:
|
||||
|
||||
.PHONY: kubectl-az-%
|
||||
kubectl-az-%: phony_explicit
|
||||
export GO111MODULE=on CGO_ENABLED=0 && \
|
||||
export GOOS=$(shell echo $* |cut -f1 -d-) GOARCH=$(shell echo $* |cut -f2 -d-) && \
|
||||
go build -ldflags $(LDFLAGS) \
|
||||
-o kubectl-az-$${GOOS}-$${GOARCH} \
|
||||
github.com/Azure/kubectl-az
|
||||
|
||||
.PHONY: install
|
||||
install: kubectl-az
|
||||
mkdir -p ~/.local/bin/
|
||||
cp kubectl-az ~/.local/bin/
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f kubectl-az
|
||||
|
||||
.PHONY: cleanall
|
||||
cleanall: clean
|
||||
rm -f $(KUBECTL_AZ_TARGETS)
|
108
README.md
108
README.md
|
@ -1,33 +1,97 @@
|
|||
# Project
|
||||
# Microsoft Azure CLI kubectl plugin
|
||||
|
||||
> This repo has been populated by an initial template to help get you started. Please
|
||||
> make sure to update the content to build a great experience for community-building.
|
||||
`kubectl-az` is a set of commands used to troubleshoot Kubernetes clusters in
|
||||
Azure.
|
||||
|
||||
As the maintainer of this project, please make a few updates:
|
||||
Going through the following documentation will help you to understand each
|
||||
available command and which one is the most suitable for your case:
|
||||
|
||||
- Improving this README.MD file to provide a great experience
|
||||
- Updating SUPPORT.MD with content about this project's support experience
|
||||
- Understanding the security reporting process in SECURITY.MD
|
||||
- Remove this section from the README
|
||||
- [run-command](docs/run-command.md)
|
||||
- [check-apiserver-connectivity](docs/check-apiserver-connectivity.md)
|
||||
|
||||
Consider `kubectl-az` expects the cluster to use virtual machine scale sets.
|
||||
And, commands that allow using `--node` flag requires the Kubernetes API server
|
||||
to up and running because it is used to retrieve the VMSS instance information
|
||||
of nodes.
|
||||
|
||||
However, in case of issues with the Kubernetes API server, we can retrieve the
|
||||
VMSS instance information from the [Azure portal](https://portal.azure.com/) and
|
||||
pass it to the commands using the `--id` flag or separately with the
|
||||
`--subscription`, `--node-resource-group`, `--vmss` and `--instance-id` flags.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/Azure/kubectl-az.git
|
||||
$ cd kubectl-az
|
||||
# Build and copy the resulting binary in $HOME/.local/bin/
|
||||
$ make install
|
||||
```
|
||||
|
||||
Notice it requires Go version 1.17.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
$ kubectl az --help
|
||||
Microsoft Azure CLI kubectl plugin
|
||||
|
||||
Usage:
|
||||
kubectl-az [command]
|
||||
|
||||
Available Commands:
|
||||
check-apiserver-connectivity Check connectivity between the nodes and the Kubernetes API Server
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
help Help about any command
|
||||
run-command Run a command in a node
|
||||
version Show version
|
||||
|
||||
Flags:
|
||||
-h, --help help for kubectl-az
|
||||
|
||||
Use "kubectl-az [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
It is necessary to sign in to Azure to run any `kubectl-az` command. To do so,
|
||||
you can use any authentication method provided by the [Azure
|
||||
CLI](https://github.com/Azure/azure-cli/) using the `az login` command; see
|
||||
further details
|
||||
[here](https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli).
|
||||
However, if you do not have the Azure CLI or have not signed in yet,
|
||||
`kubectl-az` will open the default browser and load the Azure sign-in page where
|
||||
you need to authenticate.
|
||||
|
||||
## Future
|
||||
|
||||
- `kubectl-az` does not store the access token as Azure CLI does. Therefore,
|
||||
unless you sign in using Azure CLI, `kubectl-az` will request you to
|
||||
authenticate with the default web browser at every execution.
|
||||
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
|
||||
This project welcomes contributions and suggestions. Most contributions require
|
||||
you to agree to a Contributor License Agreement (CLA) declaring that you have
|
||||
the right to, and actually do, grant us the rights to use your contribution. For
|
||||
details, visit https://cla.opensource.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
When you submit a pull request, a CLA bot will automatically determine whether
|
||||
you need to provide a CLA and decorate the PR appropriately (e.g., status check,
|
||||
comment). Simply follow the instructions provided by the bot. You will only need
|
||||
to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
This project has adopted the [Microsoft Open Source Code of
|
||||
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
|
||||
see the [Code of Conduct
|
||||
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
||||
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
|
||||
questions or comments.
|
||||
|
||||
## Trademarks
|
||||
|
||||
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
|
||||
trademarks or logos is subject to and must follow
|
||||
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
||||
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
|
||||
Any use of third-party trademarks or logos are subject to those third-party's policies.
|
||||
This project may contain trademarks or logos for projects, products, or
|
||||
services. Authorized use of Microsoft trademarks or logos is subject to and must
|
||||
follow [Microsoft's Trademark & Brand
|
||||
Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
||||
Use of Microsoft trademarks or logos in modified versions of this project must
|
||||
not cause confusion or imply Microsoft sponsorship. Any use of third-party
|
||||
trademarks or logos are subject to those third-party's policies.
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/kubectl-az/cmd/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var connCheckVM utils.VirtualMachineScaleSetVM
|
||||
|
||||
var connCheckCmd = &cobra.Command{
|
||||
Use: "check-apiserver-connectivity",
|
||||
Short: "Check connectivity between the nodes and the Kubernetes API Server",
|
||||
RunE: connCheckCmdRun,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.AddNodeFlags(connCheckCmd, &connCheckVM)
|
||||
utils.AddCommonFlags(connCheckCmd, &commonFlags)
|
||||
rootCmd.AddCommand(connCheckCmd)
|
||||
}
|
||||
|
||||
func connCheckCmdRun(cmd *cobra.Command, args []string) error {
|
||||
cred, err := utils.GetCredentials()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
// Check connectivity by executing "kubectl version" on the node. This
|
||||
// command will try to contact the API server to get the Kubernetes version
|
||||
// it is running. Use only the return value of the command, tough.
|
||||
command := "kubectl --kubeconfig /var/lib/kubelet/kubeconfig version > /dev/null; echo $?"
|
||||
res, err := utils.RunCommand(cmd.Context(), cred, &connCheckVM, &command, commonFlags.Verbose)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run command that checks connectivity: %w", err)
|
||||
}
|
||||
|
||||
// Extract stdout and stderr from response.
|
||||
// Expected format: "[stdout]<text>[stderr]<text>"
|
||||
split := regexp.MustCompile(`(\[(stdout|stderr)\])`).Split(res, -1)
|
||||
if len(split) != 3 {
|
||||
return fmt.Errorf("couldn't parse response message:\n%s", res)
|
||||
}
|
||||
stdOutput := strings.TrimSpace(split[1])
|
||||
stdError := strings.TrimSpace(split[2])
|
||||
|
||||
// The stdout should contain the returned value of "kubectl version":
|
||||
// 0 (succeeded), otherwise (failure)
|
||||
ret, err := strconv.Atoi(stdOutput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't parse stdout of response message:\n%s", res)
|
||||
}
|
||||
if ret != 0 {
|
||||
fmt.Printf("\nConnectivity check: failed with returned value %d: %s\n",
|
||||
ret, stdError)
|
||||
|
||||
// Force the binary to return an exit code != 0 (forwarding command's
|
||||
// return value). Useful if it is used in scripts.
|
||||
os.Exit(ret)
|
||||
}
|
||||
|
||||
fmt.Println("\nConnectivity check: succeeded")
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Azure/kubectl-az/cmd/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Common flags for all subcommands
|
||||
var commonFlags utils.CommonFlags
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "kubectl-az",
|
||||
Short: "Microsoft Azure CLI kubectl plugin",
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/kubectl-az/cmd/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
command string
|
||||
runCommandVM utils.VirtualMachineScaleSetVM
|
||||
)
|
||||
|
||||
var runCommandCmd = &cobra.Command{
|
||||
Use: "run-command",
|
||||
Short: "Run a command in a node",
|
||||
RunE: runCommandCmdRun,
|
||||
SilenceUsage: true,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("usage: %s <command>", cmd.CommandPath())
|
||||
}
|
||||
command = args[0]
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.AddNodeFlags(runCommandCmd, &runCommandVM)
|
||||
utils.AddCommonFlags(runCommandCmd, &commonFlags)
|
||||
rootCmd.AddCommand(runCommandCmd)
|
||||
}
|
||||
|
||||
func runCommandCmdRun(cmd *cobra.Command, args []string) error {
|
||||
cred, err := utils.GetCredentials()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
res, err := utils.RunCommand(cmd.Context(), cred, &runCommandVM, &command, commonFlags.Verbose)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run command: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s", res)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
)
|
||||
|
||||
// Further details about authentication:
|
||||
// https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity
|
||||
func GetCredentials() (*azidentity.ChainedTokenCredential, error) {
|
||||
azCLI, err := azidentity.NewAzureCLICredential(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating default authentication chain: %w", err)
|
||||
}
|
||||
|
||||
// Fallback if users didn't get already authenticated using the Azure CLI
|
||||
inBrowser, err := azidentity.NewInteractiveBrowserCredential(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating interactive authentication chain: %w", err)
|
||||
}
|
||||
|
||||
// Methods will be tried in that specific order: (1) Azure CLI (2) Interactive
|
||||
chain, err := azidentity.NewChainedTokenCredential([]azcore.TokenCredential{azCLI, inBrowser}, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating credential chain: %w", err)
|
||||
}
|
||||
|
||||
return chain, nil
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CommonFlags contains CLI flags common for all subcommands
|
||||
type CommonFlags struct {
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func AddCommonFlags(command *cobra.Command, flags *CommonFlags) {
|
||||
command.PersistentFlags().BoolVarP(
|
||||
&flags.Verbose,
|
||||
"verbose", "v",
|
||||
false,
|
||||
"Verbose output.",
|
||||
)
|
||||
}
|
||||
|
||||
// Every command that allows user to specify the node name has three options:
|
||||
// (1) Provide the kubernetes node name
|
||||
// (2) Provide the VMMS instance information (--subscription, --node-resource-group, --vmss and --instance-id)
|
||||
// (3) Provide Resource ID (/subscriptions/mySubID/resourceGroups/myRG/providers/myProvider/virtualMachineScaleSets/myVMSS/virtualMachines/myInsID)
|
||||
func AddNodeFlags(command *cobra.Command, vm *VirtualMachineScaleSetVM) {
|
||||
var (
|
||||
node string
|
||||
subscriptionID string
|
||||
nodeResourceGroup string
|
||||
vmScaleSet string
|
||||
instanceID string
|
||||
resourceID string
|
||||
)
|
||||
|
||||
command.PersistentFlags().StringVarP(
|
||||
&node,
|
||||
"node", "",
|
||||
"",
|
||||
"Kubernetes node name.",
|
||||
)
|
||||
command.PersistentFlags().StringVarP(
|
||||
&subscriptionID,
|
||||
"subscription", "",
|
||||
"",
|
||||
"Subscription ID.",
|
||||
)
|
||||
command.PersistentFlags().StringVarP(
|
||||
&nodeResourceGroup,
|
||||
"node-resource-group", "",
|
||||
"",
|
||||
"Node resource group name.",
|
||||
)
|
||||
command.PersistentFlags().StringVarP(
|
||||
&vmScaleSet,
|
||||
"vmss", "",
|
||||
"",
|
||||
"Virtual machine scale set name.",
|
||||
)
|
||||
command.PersistentFlags().StringVarP(
|
||||
&instanceID,
|
||||
"instance-id", "",
|
||||
"",
|
||||
"VM scale set instance ID.",
|
||||
)
|
||||
command.PersistentFlags().StringVarP(
|
||||
&resourceID,
|
||||
"id", "",
|
||||
"",
|
||||
`Resource ID containing all information of the VMSS instance using format:
|
||||
e.g. /subscriptions/mySubID/resourceGroups/myRG/providers/myProvider/virtualMachineScaleSets/myVMSS/virtualMachines/myInsID.
|
||||
Notice it is not case sensitive.`,
|
||||
)
|
||||
|
||||
command.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
if node != "" {
|
||||
if resourceID != "" {
|
||||
return errors.New("specify either --node or --id but not both")
|
||||
}
|
||||
|
||||
var err error
|
||||
resourceID, err = GetNodeResourceID(context.TODO(), node)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve Azure resource ID of node %s from API server: %w",
|
||||
node, err)
|
||||
}
|
||||
}
|
||||
|
||||
if subscriptionID != "" && nodeResourceGroup != "" && vmScaleSet != "" && instanceID != "" {
|
||||
if resourceID != "" {
|
||||
return errors.New("do not provide VMMS instance information (--subscription, --node-resource-group, --vmss and --instance-id) when --node or --id were provided")
|
||||
}
|
||||
|
||||
vm.SubscriptionID = subscriptionID
|
||||
vm.NodeResourceGroup = nodeResourceGroup
|
||||
vm.VMScaleSet = vmScaleSet
|
||||
vm.InstanceID = instanceID
|
||||
} else if resourceID != "" {
|
||||
if err := ParseVMSSResourceID(resourceID, vm); err != nil {
|
||||
return fmt.Errorf("failed to parse resource id: %w", err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("specify either --node or --id or VMMS instance information (--subscription, --node-resource-group, --vmss and --instance-id)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/kinvolk/inspektor-gadget/pkg/k8sutil"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
var KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
|
||||
|
||||
// GetNodeResourceID retrieve the Azure resource ID of a given node. In other
|
||||
// words, the resource ID of the VM scale set instance. It returns format:
|
||||
// /subscriptions/mySubID/resourceGroups/myRG/providers/myProvider/virtualMachineScaleSets/myVMSS/virtualMachines/myInsID
|
||||
func GetNodeResourceID(ctx context.Context, nodeName string) (string, error) {
|
||||
client, err := k8sutil.NewClientsetFromConfigFlags(KubernetesConfigFlags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nodeRes, err := client.CoreV1().Nodes().Get(ctx, nodeName, metaV1.GetOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimPrefix(nodeRes.Spec.ProviderID, "azure://"), nil
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
)
|
||||
|
||||
type VirtualMachineScaleSetVM struct {
|
||||
SubscriptionID string
|
||||
NodeResourceGroup string
|
||||
VMScaleSet string
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
// ParseVMSSResourceID extracts elements from a given VMSS resource ID with format:
|
||||
// /subscriptions/mySubID/resourceGroups/myRG/providers/myProvider/virtualMachineScaleSets/myVMSS/virtualMachines/myInsID
|
||||
func ParseVMSSResourceID(id string, vm *VirtualMachineScaleSetVM) error {
|
||||
const expectedItems int = 5
|
||||
|
||||
// This allows us to make resource ID (--id) option not case sentitive
|
||||
id = strings.ToLower(id)
|
||||
|
||||
// Required because fmt.Sscanf expects space-separated values
|
||||
idWithSpaces := strings.TrimSpace(strings.Replace(id, "/", " ", -1))
|
||||
|
||||
// We don't need the provider but fmt.Sscanf does not support "%*s" operator
|
||||
// to read but prevent conversion. Therefore, read it and don't use it.
|
||||
var provider string
|
||||
|
||||
n, err := fmt.Sscanf(idWithSpaces, "subscriptions %s resourcegroups %s providers %s virtualmachinescalesets %s virtualmachines %s",
|
||||
&vm.SubscriptionID, &vm.NodeResourceGroup, &provider, &vm.VMScaleSet, &vm.InstanceID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing provider ID %s: %w", id, err)
|
||||
}
|
||||
if n != expectedItems {
|
||||
return fmt.Errorf("%d values retrieved while expecting %d when parsing id %s",
|
||||
n, expectedItems, id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunCommand(
|
||||
ctx context.Context,
|
||||
cred azcore.TokenCredential,
|
||||
vm *VirtualMachineScaleSetVM,
|
||||
command *string,
|
||||
verbose bool,
|
||||
) (
|
||||
string,
|
||||
error,
|
||||
) {
|
||||
const (
|
||||
commandID = "RunShellScript"
|
||||
initialDelay = 15 * time.Second
|
||||
pollingFreq = 2 * time.Second
|
||||
)
|
||||
|
||||
client := armcompute.NewVirtualMachineScaleSetVMsClient(vm.SubscriptionID, cred, nil)
|
||||
|
||||
script := []*string{command}
|
||||
runCommand := armcompute.RunCommandInput{
|
||||
CommandID: to.StringPtr(commandID),
|
||||
Script: script,
|
||||
}
|
||||
|
||||
if verbose {
|
||||
b, _ := json.MarshalIndent(vm, "", " ")
|
||||
fmt.Printf("Command: %s\nVirtual Machine Scale Set VM:\n%s\n\n", *command, string(b))
|
||||
}
|
||||
|
||||
poller, err := client.BeginRunCommand(ctx, vm.NodeResourceGroup,
|
||||
vm.VMScaleSet, vm.InstanceID, runCommand, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("couldn't begin running command: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Running...")
|
||||
|
||||
res, err := poller.PollUntilDone(ctx, pollingFreq)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error polling command response: %w", err)
|
||||
}
|
||||
|
||||
if verbose {
|
||||
b, _ := json.MarshalIndent(res, "", " ")
|
||||
fmt.Printf("\nResponse:\n%s\n", string(b))
|
||||
}
|
||||
|
||||
// TODO: Is it possible to have multiple values after using PollUntilDone()?
|
||||
if len(res.Value) == 0 || res.Value[0] == nil {
|
||||
return "", errors.New("no response received after command execution")
|
||||
}
|
||||
val := res.Value[0]
|
||||
|
||||
// TODO: Isn't there a constant in the SDK to compare this?
|
||||
if to.String(val.Code) != "ProvisioningState/succeeded" {
|
||||
b, _ := json.MarshalIndent(res, "", " ")
|
||||
return "", fmt.Errorf("command execution didn't succeed:\n%s", string(b))
|
||||
}
|
||||
|
||||
// Expected format: "Enable succeeded: \n<text>"
|
||||
return strings.TrimPrefix(to.String(val.Message), "Enable succeeded: \n"), nil
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseVMSSResourceID(t *testing.T) {
|
||||
type test struct {
|
||||
description string
|
||||
id string
|
||||
expectedResult VirtualMachineScaleSetVM
|
||||
expectedError bool
|
||||
}
|
||||
|
||||
table := []test{
|
||||
// KO tests
|
||||
{
|
||||
description: "From empty id",
|
||||
id: "",
|
||||
expectedResult: VirtualMachineScaleSetVM{},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
description: "Unexpected format",
|
||||
id: "subscriptionsmysubid",
|
||||
expectedResult: VirtualMachineScaleSetVM{},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
description: "Unexpected separator",
|
||||
id: "subscriptions-mysubid",
|
||||
expectedResult: VirtualMachineScaleSetVM{},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
description: "Incomplete format",
|
||||
id: "/subscriptions/mysubid",
|
||||
expectedResult: VirtualMachineScaleSetVM{
|
||||
SubscriptionID: "mysubid",
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
// OK test
|
||||
{
|
||||
description: "Correct format",
|
||||
id: "/subscriptions/mysubid/resourcegroups/myrd/providers/myprovider/virtualmachinescalesets/myvmss/virtualmachines/myinsid",
|
||||
expectedResult: VirtualMachineScaleSetVM{
|
||||
SubscriptionID: "mysubid",
|
||||
NodeResourceGroup: "myrd",
|
||||
VMScaleSet: "myvmss",
|
||||
InstanceID: "myinsid",
|
||||
},
|
||||
expectedError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, entry := range table {
|
||||
result := VirtualMachineScaleSetVM{}
|
||||
err := ParseVMSSResourceID(entry.id, &result)
|
||||
errorOcurred := err != nil
|
||||
if errorOcurred != entry.expectedError || entry.expectedResult != result {
|
||||
t.Fatalf("Failed test %q: result %+v (error %t - %s) vs expected %+v (error %t)",
|
||||
entry.description, result, errorOcurred, err, entry.expectedResult, entry.expectedError)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// This variable is used by the "version" command and is set during build
|
||||
var version = "undefined"
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show version",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(version)
|
||||
},
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
# Check Connectivity
|
||||
|
||||
We can use `check-connectivity` to verify that nodes can communicate with the
|
||||
Kubernetes API server:
|
||||
|
||||
```bash
|
||||
$ kubectl get nodes
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
aks-agentpool-27170680-vmss000000 Ready agent 11d v1.22.4
|
||||
aks-agentpool-27170680-vmss000001 Ready agent 11d v1.22.4
|
||||
aks-agentpool-27170680-vmss000002 Ready agent 11d v1.22.4
|
||||
|
||||
$ kubectl az check-apiserver-connectivity --node aks-agentpool-27170680-vmss000000
|
||||
Running...
|
||||
|
||||
Connectivity check: succeeded
|
||||
```
|
||||
|
||||
Or we could also pass directly the VMSS instance information:
|
||||
|
||||
```bash
|
||||
$ kubectl az check-apiserver-connectivity --id "/subscriptions/$SUBSCRIPTION/resourceGroups/$NODERESOURCEGROUP/providers/Microsoft.Compute/virtualMachineScaleSets/$VMSS/virtualmachines/$INSTANCEID"
|
||||
```
|
||||
|
||||
```bash
|
||||
$ kubectl az check-apiserver-connectivity --subscription $SUBSCRIPTION --node-resource-group $NODERESOURCEGROUP --vmss $VMSS --instance-id $INSTANCEID
|
||||
```
|
||||
|
||||
The `check-connectivity` command verifies the connectivity between the nodes and
|
||||
the API server by executing the command `kubectl version` from the node itself.
|
||||
This command will try to contact the API server to get the Kubernetes version it
|
||||
is running, which is enough to verify the connectivity. We have to consider that
|
||||
`kubectl` uses the URL of the API server available in the `kubeconfig` file and
|
||||
not directly the IP address. It means that this connectivity check requires the
|
||||
DNS to be working correctly to succeed.
|
||||
|
||||
We can use the flag `-v`/`--verbose` to have further details about the command
|
||||
that is being executed in the nodes to check connectivity:
|
||||
|
||||
```bash
|
||||
$ kubectl az check-apiserver-connectivity --node aks-agentpool-27170680-vmss000001 -v
|
||||
Command: kubectl --kubeconfig /var/lib/kubelet/kubeconfig version > /dev/null; echo $?
|
||||
Virtual Machine Scale Set VM:
|
||||
{
|
||||
"SubscriptionID": "MySub",
|
||||
"NodeResourceGroup": "MyNodeRG",
|
||||
"VMScaleSet": "MyVMSS",
|
||||
"InstanceID": "X"
|
||||
}
|
||||
|
||||
Running...
|
||||
|
||||
Response:
|
||||
{
|
||||
"value": [
|
||||
{
|
||||
"code": "ProvisioningState/succeeded",
|
||||
"displayStatus": "Provisioning succeeded",
|
||||
"level": "Info",
|
||||
"message": "Enable succeeded: \n[stdout]\n0\n\n[stderr]\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Connectivity check: succeeded
|
||||
```
|
||||
|
||||
Given that the `check-connectivity` command checks the connectivity by running a
|
||||
command on the nodes, all the
|
||||
[restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/run-command#restrictions)
|
||||
of running scripts in an Azure Linux VM also apply here.
|
|
@ -0,0 +1,54 @@
|
|||
# Run Command
|
||||
|
||||
We can use `run-command` to execute a command on one of the cluster nodes:
|
||||
|
||||
```bash
|
||||
$ kubectl get nodes
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
aks-agentpool-27170680-vmss000000 Ready agent 11d v1.22.4
|
||||
aks-agentpool-27170680-vmss000001 Ready agent 11d v1.22.4
|
||||
aks-agentpool-27170680-vmss000002 Ready agent 11d v1.22.4
|
||||
|
||||
$ kubectl az run-command "ip route" --node aks-agentpool-27170680-vmss000000
|
||||
Running...
|
||||
|
||||
[stdout]
|
||||
default via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.4 metric 100
|
||||
10.240.0.0/16 dev eth0 proto kernel scope link src 10.240.0.4
|
||||
10.244.2.2 dev calic38a36632c7 scope link
|
||||
10.244.2.6 dev cali0b155bb80e7 scope link
|
||||
10.244.2.7 dev cali997a02e57a6 scope link
|
||||
10.244.2.8 dev calia2f1486fcb5 scope link
|
||||
10.244.2.9 dev cali221544885dd scope link
|
||||
10.244.2.10 dev cali8913de1b395 scope link
|
||||
10.244.2.14 dev cali8eecb1f59c6 scope link
|
||||
168.63.129.16 via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.4 metric 100
|
||||
169.254.169.254 via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.4 metric 100
|
||||
|
||||
[stderr]
|
||||
```
|
||||
|
||||
Another example when requested command prints information to the standard error:
|
||||
```
|
||||
$ kubectl az run-command "cat non-existent-file" --node aks-agentpool-27170680-vmss000000
|
||||
Running...
|
||||
|
||||
[stdout]
|
||||
|
||||
[stderr]
|
||||
cat: non-existent-file: No such file or directory
|
||||
```
|
||||
|
||||
Or we could also pass directly the VMSS instance information:
|
||||
|
||||
```bash
|
||||
$ kubectl az run-command "ip route" --id "/subscriptions/$SUBSCRIPTION/resourceGroups/$NODERESOURCEGROUP/providers/Microsoft.Compute/virtualMachineScaleSets/$VMSS/virtualmachines/$INSTANCEID"
|
||||
```
|
||||
|
||||
```bash
|
||||
$ kubectl az run-command "ip route" --subscription $SUBSCRIPTION --node-resource-group $NODERESOURCEGROUP --vmss $VMSS --instance-id $INSTANCEID
|
||||
```
|
||||
|
||||
Take into account that all the
|
||||
[restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/run-command#restrictions)
|
||||
of running scripts in an Azure Linux VM also apply here.
|
|
@ -0,0 +1,80 @@
|
|||
module github.com/Azure/kubectl-az
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.3.0
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0
|
||||
github.com/kinvolk/inspektor-gadget v0.4.2
|
||||
github.com/spf13/cobra v1.3.0
|
||||
k8s.io/apimachinery v0.23.3
|
||||
k8s.io/cli-runtime v0.23.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go v61.1.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-logr/logr v1.2.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/api v0.23.3 // indirect
|
||||
k8s.io/client-go v0.23.3 // indirect
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.10.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/Azure/kubectl-az/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
Загрузка…
Ссылка в новой задаче