Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add collector to collect tidb information in kubernetes cluster #4689

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

LDFLAGS = $(shell ./hack/version.sh)

GOVER_MAJOR := $(shell go version | sed -E -e "s/.*go([0-9]+)[.]([0-9]+).*/\1/")
Expand Down Expand Up @@ -46,7 +47,7 @@ else
docker build --tag "${DOCKER_REPO}/tidb-backup-manager:${IMAGE_TAG}" --build-arg=TARGETARCH=$(GOARCH) images/tidb-backup-manager
endif

build: controller-manager scheduler discovery admission-webhook backup-manager
build: controller-manager scheduler discovery admission-webhook backup-manager collector

controller-manager:
ifeq ($(E2E),y)
Expand Down Expand Up @@ -83,6 +84,13 @@ else
$(GO_BUILD) -ldflags '$(LDFLAGS)' -o images/tidb-backup-manager/bin/$(GOARCH)/tidb-backup-manager cmd/backup-manager/main.go
endif

collector:
ifeq ($(E2E),y)
$(GO_TEST) -ldflags '$(LDFLAGS)' -c -o images/tidb-operator/bin/collector ./cmd/collector
else
$(GO_BUILD) -ldflags '$(LDFLAGS)' -o images/tidb-operator/bin/$(GOARCH)/collector cmd/collector/main.go
endif

ifeq ($(NO_BUILD),y)
backup-docker:
@echo "NO_BUILD=y, skip build for $@"
Expand Down
42 changes: 42 additions & 0 deletions cmd/collector/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Collector - a tool for collecting tidb related information in a kubernetes cluster.

Collector is a tool for collecting tidb related kubernetes information in your own cluster.
To run the tool, you need a kubeconfig to access the cluster and a config file.

## Configuration

The configuration file for collector specifies the resources to be collected.
The collector will collect following information:
1. Pod information
2. Event information
3. Advanced statefulset information
4. Persistent volume information
5. Persistent volume claim information.
6. Service information
7. TidbCluster information
8. StatefulSet information
9. ConfigMap information

There're global configuration and per-resource configuration.
User can specify a namespace in global config, and override that in per-resource config.
For each resource, user can specify whether skip the collection for the resource (disable), the namespace for the resource and the label selector for the resource.

For instance, user can use the following config to collect information in `demo` namespace, enforce label selector for pods and skip the advanced stateful set.
```yaml
namespace: demo
asts:
disable: true
pod:
labels:
env: dev
```

## Execution

To execute the tool, user need to specify the following flags:
1. `--kubeconfig`: the kubeconfig for the kubernetes cluster to be collected
2. `--config`: the config file for info collection
3. `--output`: the output path for the collection

The tool will collect the infornation and genrate a zip file.
User can review the contents for the collection before sharing it.
145 changes: 145 additions & 0 deletions cmd/collector/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright 2022 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"flag"
"fmt"
"io/ioutil"
"path/filepath"

"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/pingcap/tidb-operator/cmd/collector/pkg/collect"
"github.com/pingcap/tidb-operator/cmd/collector/pkg/config"
"github.com/pingcap/tidb-operator/cmd/collector/pkg/dump"
)

func main() {
var (
kubeconfig string
configPath string
outputPath string
)
flag.StringVar(&kubeconfig, "kubeconfig", "", "kubeconfig path")
flag.StringVar(&configPath, "config", "", "config path")
flag.StringVar(&outputPath, "output", "", "output path")
flag.Parse()

cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
panic(err)
}

cli, err := client.New(cfg, client.Options{Scheme: collect.GetScheme()})
if err != nil {
panic(err)
}

collectorCfg := config.CollectorConfig{}
configContents, err := ioutil.ReadFile(configPath)
if err != nil {
panic(err)
}
err = yaml.Unmarshal(configContents, &collectorCfg)
if err != nil {
panic(err)
}

// TODO: make encoder and dumper configurable
var (
// encoderType encode.Encoding = encode.YAML
encoding = "yaml"
dumperType dump.Dumping = dump.Zip
dumper dump.Dumper
collectors []collect.Collector
printer printers.ResourcePrinter
)

switch dumperType {
case dump.Zip:
dumper, err = dump.NewZipDumper(outputPath)
if err != nil {
panic(err)
}
}

switch encoding {
case "yaml":
printer = &printers.TypeSetterPrinter{
Delegate: &printers.YAMLPrinter{},
Typer: collect.GetScheme(),
}
}
// Set types for the objects
printer, err = printers.NewTypeSetter(collect.GetScheme()).WrapToPrinter(printer, nil)
if err != nil {
panic(err)
}

collectors = config.ConfigCollectors(cli, collectorCfg)

err = dumpFiles(collect.MergedCollectors(collectors...), dumper, printer, encoding)
if err != nil {
panic(err)
}
}

// dumpFiles dumps the encoded contents to dumper.
func dumpFiles(c collect.Collector, dumper dump.Dumper, printer printers.ResourcePrinter, encoding string) error {
defer dumper.Close()()

objCh, err := c.Objects()
if err != nil {
return err
}

for obj := range objCh {
// TODO: remove hack after https://github.com/kubernetes/kubernetes/issues/3030 is resolved
gvks, _, err := collect.GetScheme().ObjectKinds(obj)
if err != nil {
return err
}
gvk := schema.GroupVersionKind{}
for _, g := range gvks {
if g.Empty() {
continue
}
gvk = g
}
kind, ns, name := gvk.Kind, obj.GetNamespace(), obj.GetName()
path := fmt.Sprintf("%s.%s", filepath.Join(kind, ns, name), encoding)
writer, err := dumper.Open(path)
if err != nil {
return err
}

// omit managed fields
if a, err := meta.Accessor(obj); err == nil {
a.SetManagedFields(nil)
}

err = printer.PrintObj(obj, writer)
if err != nil {
return err
}
}

return nil
}
61 changes: 61 additions & 0 deletions cmd/collector/pkg/collect/asts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2022 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package collect

import (
"context"
"sync"

"sigs.k8s.io/controller-runtime/pkg/client"

asapps "github.com/pingcap/advanced-statefulset/client/apis/apps/v1"
)

type AdvancedStatefulSet struct {
*BaseCollector
}

var _ Collector = (*AdvancedStatefulSet)(nil)

func (p *AdvancedStatefulSet) Objects() (<-chan client.Object, error) {
list := &asapps.StatefulSetList{}
err := p.Reader.List(context.Background(), list, p.opts...)
if err != nil {
return nil, err
}

ch := make(chan client.Object)
go func() {
for _, obj := range list.Items {
ch <- &obj // nolint: gosec
}
close(ch)
}()
return ch, nil
}

func NewASTSCollector(cli client.Reader) Collector {
addAsappsV1Scheme()
return &AdvancedStatefulSet{
BaseCollector: NewBaseCollector(cli),
}
}

var asappsv1Scheme sync.Once

func addAsappsV1Scheme() {
asappsv1Scheme.Do(func() {
asapps.AddToScheme(scheme)
})
}
59 changes: 59 additions & 0 deletions cmd/collector/pkg/collect/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2022 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package collect

import (
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// BaseCollector is a base collector that implements the Collector interface.
// It's used to host common objects required by all collectors.
type BaseCollector struct {
// Reader is a read-only client for retrieving objects information.
client.Reader
// opts is a set of customized options for listing information.
opts []client.ListOption
}

var _ Collector = (*BaseCollector)(nil)

func (*BaseCollector) Objects() (<-chan client.Object, error) {
panic("not implemented")
}

// NewBaseCollector returns an instance of the BaseCollector.
func NewBaseCollector(cli client.Reader) *BaseCollector {

return &BaseCollector{
Reader: cli,
opts: []client.ListOption{},
}
}

// WithNamespace add option to base collector to select resources from specific
// namespace.
func (b *BaseCollector) WithNamespace(ns string) Collector {
b.opts = append(b.opts, (client.InNamespace)(ns))
return b
}

// WithLabel add option to base collector to select resources with specific
// labels.
func (b *BaseCollector) WithLabel(label map[string]string) Collector {
b.opts = append(b.opts, client.MatchingLabelsSelector{
Selector: (labels.Set)(label).AsSelector(),
})
return b
}
51 changes: 51 additions & 0 deletions cmd/collector/pkg/collect/configmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2022 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package collect

import (
"context"

corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type ConfigMap struct {
*BaseCollector
}

var _ Collector = (*ConfigMap)(nil)

func (p *ConfigMap) Objects() (<-chan client.Object, error) {
list := &corev1.ConfigMapList{}
err := p.Reader.List(context.Background(), list, p.opts...)
if err != nil {
return nil, err
}

ch := make(chan client.Object)
go func() {
for _, obj := range list.Items {
ch <- &obj // nolint: gosec
}
close(ch)
}()
return ch, nil
}

func NewConfigMapCollector(cli client.Reader) Collector {
addCorev1Scheme()
return &ConfigMap{
BaseCollector: NewBaseCollector(cli),
}
}
Loading