diff --git a/builder/Dockerfile b/builder/Dockerfile index 0ab571e9..41d76f49 100644 --- a/builder/Dockerfile +++ b/builder/Dockerfile @@ -6,9 +6,15 @@ RUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor github.com/Azure/aks-periscope FROM alpine RUN apk --no-cache add ca-certificates +RUN apk add curl openssl bash --no-cache ADD https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/linux/amd64/kubectl /usr/local/bin/kubectl RUN chmod +x /usr/local/bin/kubectl +RUN curl -fsSl -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 +RUN chmod +x get_helm.sh +RUN ./get_helm.sh + RUN mkdir /app WORKDIR /app COPY --from=builder /app/aks-periscope . +EXPOSE 8080 CMD ["./aks-periscope"] \ No newline at end of file diff --git a/cmd/aks-periscope/aks-periscope.go b/cmd/aks-periscope/aks-periscope.go index 3a6b8576..730c8d60 100644 --- a/cmd/aks-periscope/aks-periscope.go +++ b/cmd/aks-periscope/aks-periscope.go @@ -15,6 +15,7 @@ import ( func main() { zipAndExportMode := true + //exporter := &exporter.LocalMachineExporter{} exporter := &exporter.AzureBlobExporter{} var waitgroup sync.WaitGroup @@ -23,8 +24,10 @@ func main() { log.Printf("Failed to create CRD: %+v", err) } - clusterType := strings.ToLower(os.Getenv("CLUSTER_TYPE")) + clusterType := os.Getenv("CLUSTER_TYPE") + storageAccount := os.Getenv("AZURE_BLOB_ACCOUNT_NAME") + log.Printf("Storage Account: %s", storageAccount) collectors := []interfaces.Collector{} containerLogsCollector := collector.NewContainerLogsCollector(exporter) collectors = append(collectors, containerLogsCollector) @@ -41,7 +44,15 @@ func main() { kubeletCmdCollector := collector.NewKubeletCmdCollector(exporter) systemPerfCollector := collector.NewSystemPerfCollector(exporter) - if clusterType != "connectedcluster" { + execCollector := collector.NewExecCollector(exporter) + helmCollector := collector.NewHelmCollector(exporter) + customResourceCollector := collector.NewCustomResourceCollector(exporter) + + if clusterType == "connectedcluster" { + collectors = append(collectors, helmCollector) + collectors = append(collectors, execCollector) + collectors = append(collectors, customResourceCollector) + } else { collectors = append(collectors, systemLogsCollector) collectors = append(collectors, ipTablesCollector) collectors = append(collectors, nodeLogsCollector) @@ -63,6 +74,7 @@ func main() { if err != nil { log.Printf("Collector: %s, export data failed: %+v\n", c.GetName(), err) } + waitgroup.Done() }(c) } diff --git a/deployment/aks-periscope.yaml b/deployment/aks-periscope.yaml index 360fda74..b92ca756 100644 --- a/deployment/aks-periscope.yaml +++ b/deployment/aks-periscope.yaml @@ -1,13 +1,13 @@ apiVersion: v1 kind: Namespace metadata: - name: azure-k8-periscope + name: aks-periscope --- apiVersion: v1 kind: ServiceAccount metadata: name: aks-periscope-service-account - namespace: azure-k8-periscope + namespace: aks-periscope --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -28,7 +28,7 @@ metadata: subjects: - kind: ServiceAccount name: aks-periscope-service-account - namespace: azure-k8-periscope + namespace: aks-periscope roleRef: kind: ClusterRole name: aks-periscope-role @@ -41,7 +41,7 @@ metadata: subjects: - kind: ServiceAccount name: aks-periscope-service-account - namespace: azure-k8-periscope + namespace: aks-periscope roleRef: kind: ClusterRole name: view @@ -50,18 +50,18 @@ roleRef: apiVersion: apps/v1 kind: DaemonSet metadata: - name: azure-k8-periscope - namespace: azure-k8-periscope + name: aks-periscope + namespace: aks-periscope labels: - app: azure-k8-periscope + app: aks-periscope spec: selector: matchLabels: - app: azure-k8-periscope + app: aks-periscope template: metadata: labels: - app: azure-k8-periscope + app: aks-periscope spec: serviceAccountName: aks-periscope-service-account hostPID: true @@ -80,6 +80,8 @@ spec: name: kubeobjects-config - configMapRef: name: nodelogs-config + - configMapRef: + name: clustertype-config - secretRef: name: azureblob-secret volumeMounts: @@ -102,17 +104,17 @@ apiVersion: v1 kind: Secret metadata: name: azureblob-secret - namespace: azure-k8-periscope + namespace: aks-periscope type: Opaque data: - AZURE_BLOB_ACCOUNT_NAME: Y2Nzb3BoaWV6aGFvYXJuc2FlYXN0dXMy - AZURE_BLOB_SAS_KEY: P3N2PTIwMjAtMDItMTAmc3M9YmZxdCZzcnQ9c2NvJnNwPXJ3ZGxhY3VweCZzZT0yMDIxLTA0LTIwVDAwOjA5OjM0WiZzdD0yMDIxLTA0LTE5VDE2OjA5OjM0WiZzcHI9aHR0cHMmc2lnPXZrM05FZ2ZBQm93a0RoQXJ5Rkd6b0NHJTJGRDB5SGUlMkJNYkg1SG82SmR1dmFrJTNE + AZURE_BLOB_ACCOUNT_NAME: # + AZURE_BLOB_SAS_KEY: # --- apiVersion: v1 kind: ConfigMap metadata: name: containerlogs-config - namespace: azure-k8-periscope + namespace: aks-periscope data: DIAGNOSTIC_CONTAINERLOGS_LIST: azure-arc --- @@ -120,7 +122,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: kubeobjects-config - namespace: azure-k8-periscope + namespace: aks-periscope data: DIAGNOSTIC_KUBEOBJECTS_LIST: azure-arc/pod azure-arc/service azure-arc/deployment --- @@ -128,7 +130,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: nodelogs-config - namespace: azure-k8-periscope + namespace: aks-periscope data: DIAGNOSTIC_NODELOGS_LIST: /var/log/azure/cluster-provision.log /var/log/cloud-init.log --- @@ -136,9 +138,9 @@ apiVersion: v1 kind: ConfigMap metadata: name: clustertype-config - namespace: azure-k8-periscope + namespace: aks-periscope data: - CLUSTER_TYPE: # + CLUSTER_TYPE: connectedcluster --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition @@ -167,4 +169,4 @@ spec: singular: diagnostic kind: Diagnostic shortNames: - - apd \ No newline at end of file + - apd diff --git a/integration/Dockerfile b/integration/Dockerfile new file mode 100644 index 00000000..6ced1277 --- /dev/null +++ b/integration/Dockerfile @@ -0,0 +1,25 @@ +FROM golang AS builder +RUN mkdir /app +ADD . /app +WORKDIR /app +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -mod=vendor github.com/Azure/aks-periscope/cmd/aks-periscope + +FROM alpine +RUN apk --no-cache add ca-certificates +RUN apk add curl openssl bash --no-cache +ADD https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/linux/amd64/kubectl /usr/local/bin/kubectl +RUN chmod +x /usr/local/bin/kubectl +RUN curl -fsSl -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 +RUN chmod +x get_helm.sh +RUN ./get_helm.sh + +RUN apk add --no-cache git make musl-dev go + +# Configure Go +ENV GOROOT /usr/lib/go +ENV GOPATH /go +ENV PATH /go/bin:$PATH + +RUN mkdir -p ${GOPATH}/src ${GOPATH}/bin +WORKDIR /tests +CMD CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go test ./... \ No newline at end of file diff --git a/pkg/collector/collector.go b/pkg/collector/collector.go index 471e423f..29bd57f6 100644 --- a/pkg/collector/collector.go +++ b/pkg/collector/collector.go @@ -12,6 +12,8 @@ const ( ContainerLogs //CustomResource defines CustomResource Collector Type CustomResource + //CustomResource defines CustomResource Collector Type + CustomResourceV2 //Exec defines Exec Collector Type Exec //Helm defines Helm Collector Type @@ -34,7 +36,7 @@ const ( // Name returns type name func (t Type) name() string { - return [...]string{"dns", "containerlogs", "customresource", "exec", "helm", "iptables", "kubeletcmd", "kubeobjects", "networkoutbound", "nodelogs", "systemlogs", "systemperf"}[t] + return [...]string{"dns", "containerlogs", "customresource", "customresourcev2", "exec", "helm", "iptables", "kubeletcmd", "kubeobjects", "networkoutbound", "nodelogs", "systemlogs", "systemperf"}[t] } // BaseCollector defines Base Collector diff --git a/pkg/collector/customresource_collector.go b/pkg/collector/customresource_collector.go index 588d097f..bfcd2ad9 100644 --- a/pkg/collector/customresource_collector.go +++ b/pkg/collector/customresource_collector.go @@ -1,7 +1,11 @@ package collector import ( + "path/filepath" + "strings" + "github.com/Azure/aks-periscope/pkg/interfaces" + "github.com/Azure/aks-periscope/pkg/utils" ) // CustomResourceCollector defines a CustomResources Collector struct @@ -23,5 +27,33 @@ func NewCustomResourceCollector(exporter interfaces.Exporter) *CustomResourceCol // Collect implements the interface method func (collector *CustomResourceCollector) Collect() error { + rootPath, err := utils.CreateCollectorDir(collector.GetName()) + + output, err := utils.RunCommandOnContainer("kubectl", "get", "namespace", "--output=jsonpath={.items..metadata.name}") + if err != nil { + return err + } + namespaces := strings.Split(output, " ") + for _, namespace := range namespaces { + output, err = utils.RunCommandOnContainer("kubectl", "-n", namespace, "get", "crd", "--output=jsonpath={.items..metadata.name}") + if err != nil { + return err + } + + objects := strings.Split(output, " ") + for _, object := range objects { + customResourceFile := filepath.Join(rootPath, namespace+"_"+"crd"+"_"+object) + output, err := utils.RunCommandOnContainer("kubectl", "-n", namespace, "describe", "crd", object) + if err != nil { + return err + } + err = utils.WriteToFile(customResourceFile, output) + if err != nil { + return err + } + + collector.AddToCollectorFiles(customResourceFile) + } + } return nil } diff --git a/pkg/collector/exec_collector.go b/pkg/collector/exec_collector.go index fa511fc3..11c2262a 100644 --- a/pkg/collector/exec_collector.go +++ b/pkg/collector/exec_collector.go @@ -1,7 +1,12 @@ package collector import ( + "os" + "path/filepath" + "strings" + "github.com/Azure/aks-periscope/pkg/interfaces" + "github.com/Azure/aks-periscope/pkg/utils" ) // ExecCollector defines a Exec Collector struct @@ -23,5 +28,31 @@ func NewExecCollector(exporter interfaces.Exporter) *ExecCollector { // Collect implements the interface method func (collector *ExecCollector) Collect() error { + rootPath, err := utils.CreateCollectorDir(collector.GetName()) + if err != nil { + return err + } + namespaces := strings.Fields(os.Getenv("DIAGNOSTIC_EXEC_LIST")) + for _, namespace := range namespaces { + output, err := utils.RunCommandOnContainer("kubectl", "get", "-n", namespace, "pods", "--output=jsonpath={.items..metadata.name}") + if err != nil { + return err + } + pods := strings.Split(output, " ") + + for _, pod := range pods { + execLog := filepath.Join(rootPath, namespace+"_"+pod) + output, err := utils.RunCommandOnContainer("kubectl", "-n", namespace, "exec", pod, "--", "curl", "example.com") + if err != nil { + return err + } + err = utils.WriteToFile(execLog, output) + if err != nil { + return err + } + + collector.AddToCollectorFiles(execLog) + } + } return nil } diff --git a/pkg/collector/helm_collector.go b/pkg/collector/helm_collector.go index 6ab6a9be..c2c1939a 100644 --- a/pkg/collector/helm_collector.go +++ b/pkg/collector/helm_collector.go @@ -1,7 +1,10 @@ package collector import ( + "path/filepath" + "github.com/Azure/aks-periscope/pkg/interfaces" + "github.com/Azure/aks-periscope/pkg/utils" ) // HelmCollector defines a Helm Collector struct @@ -9,7 +12,7 @@ type HelmCollector struct { BaseCollector } -var _ interfaces.Collector = &IPTablesCollector{} +var _ interfaces.Collector = &HelmCollector{} // NewHelmCollector is a constructor func NewHelmCollector(exporter interfaces.Exporter) *HelmCollector { @@ -23,5 +26,43 @@ func NewHelmCollector(exporter interfaces.Exporter) *HelmCollector { // Collect implements the interface method func (collector *HelmCollector) Collect() error { + rootPath, err := utils.CreateCollectorDir(collector.GetName()) + if err != nil { + return err + } + helmListFile := filepath.Join(rootPath, "helm_list") + output, err := utils.RunCommandOnContainer("helm", "list", "--all-namespaces") + if err != nil { + return err + } + + err = utils.WriteToFile(helmListFile, output) + if err != nil { + return err + } + + collector.AddToCollectorFiles(helmListFile) + + helmHistoryFile := filepath.Join(rootPath, collector.GetName()) + output, err = utils.RunCommandOnContainer("helm", "history", "-n", "default", "azure-arc") + if err != nil { + return err + } + err = utils.WriteToFile(helmHistoryFile, output) + if err != nil { + return err + } + + helmHistoryFile := filepath.Join(rootPath, collector.GetName()) + output, err = utils.RunCommandOnContainer("helm", "history", "-n", "default", "azure-arc") + if err != nil { + return err + } + err = utils.WriteToFile(helmHistoryFile, output) + if err != nil { + return err + } + + collector.AddToCollectorFiles(helmHistoryFile) return nil } diff --git a/pkg/diagnoser/configvalidator_diagnoser.go b/pkg/diagnoser/configvalidator_diagnoser.go new file mode 100644 index 00000000..f78d8a47 --- /dev/null +++ b/pkg/diagnoser/configvalidator_diagnoser.go @@ -0,0 +1,58 @@ +package diagnoser + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/Azure/aks-periscope/pkg/collector" + "github.com/Azure/aks-periscope/pkg/interfaces" + "github.com/Azure/aks-periscope/pkg/utils" +) + +type configValidatorDiagnosticDatum struct { + HostName string `json:"HostName"` + CRDName string `json:CRDName` +} + +// CustomValidatorDiagnoser defines a CustomValidator Diagnoser struct +type ConfigValidatorDiagnoser struct { + BaseDiagnoser + customResourceCollector *collector.CustomResourceCollector +} + +var _ interfaces.Diagnoser = &ConfigValidatorDiagnoser{} + +// NewNetworkConfigDiagnoser is a constructor +func NewConfigValidatorDiagnoser(customResourceCollector *collector.CustomResourceCollector, exporter interfaces.Exporter) *ConfigValidatorDiagnoser { + return &ConfigValidatorDiagnoser{ + BaseDiagnoser: BaseDiagnoser{ + diagnoserType: ConfigValidator, + exporter: exporter, + }, + customResourceCollector: customResourceCollector, + } +} +func (diagnoser *ConfigValidatorDiagnoser) Diagnose() error { + //hostName, err := utils.GetHostName() + rootPath, err := utils.CreateDiagnosticDir() + if err != nil { + return err + } + configValidatorDiagnosticFile := filepath.Join(rootPath, diagnoser.GetName()) + f, err := os.OpenFile(configValidatorDiagnosticFile, os.O_CREATE|os.O_WRONLY, 0644) + defer f.Close() + if err != nil { + return fmt.Errorf("Fail to open file %s: %+v", configValidatorDiagnosticFile, err) + } + + //configValidatorDiagnosticData := configValidatorDiagnosticDatum{HostName: hostName} + + diagnoser.AddToDiagnoserFiles(configValidatorDiagnosticFile) + + err = utils.WriteToCRD(configValidatorDiagnosticFile, diagnoser.GetName()) + if err != nil { + return fmt.Errorf("Fail to write file %s to CRD: %+v", configValidatorDiagnosticFile, err) + } + return nil +} diff --git a/pkg/diagnoser/diagnoser.go b/pkg/diagnoser/diagnoser.go index e0c4d8af..44d7727b 100644 --- a/pkg/diagnoser/diagnoser.go +++ b/pkg/diagnoser/diagnoser.go @@ -6,15 +6,19 @@ import "github.com/Azure/aks-periscope/pkg/interfaces" type Type int const ( + //ConfigValidator defines ConfigValidator Diagnoser Type + ConfigValidator Type = iota // NetworkConfig defines NetworkConfig Diagnoser Type - NetworkConfig Type = iota + NetworkConfig = iota // NetworkOutbound defines NetworkOutbound Diagnoser Type NetworkOutbound + //ConfigValidator defines ConfigValidator Diagnoser Type + ConfigValidator ) // Name returns type name func (t Type) name() string { - return [...]string{"networkconfig", "networkoutbound"}[t] + return [...]string{"configvalidator", "networkconfig", "networkoutbound"}[t] } // BaseDiagnoser defines Base Diagnoser diff --git a/pkg/exporter/localmachine_exporter.go b/pkg/exporter/localmachine_exporter.go new file mode 100644 index 00000000..5716af8c --- /dev/null +++ b/pkg/exporter/localmachine_exporter.go @@ -0,0 +1,87 @@ +package exporter + +import ( + "archive/zip" + "io" + "log" + "os" + + "github.com/Azure/aks-periscope/pkg/interfaces" +) + +const ( + max_Container_Name_Length = 63 +) + +// LocalMachineExporter defines an Local Machine Exporter +type LocalMachineExporter struct{} + +var _ interfaces.Exporter = &LocalMachineExporter{} + +// Export implements the interface method +func (exporter *LocalMachineExporter) Export(files []string) error { + log.Printf("system dir: %s", os.Getenv("windir")) + + output := "done.zip" + err := ZipFiles(output, files) + if err != nil { + panic(err) + } + log.Printf("Zipped File: %s", output) + return nil +} +func ZipFiles(filename string, files []string) error { + + newZipFile, err := os.Create(filename) + if err != nil { + return err + } + defer newZipFile.Close() + + zipWriter := zip.NewWriter(newZipFile) + defer zipWriter.Close() + + // Add files to zip + for _, file := range files { + log.Printf("Filename: %s", file) + if err = AddFileToZip(zipWriter, file); err != nil { + return err + } + } + return nil +} + +func AddFileToZip(zipWriter *zip.Writer, filename string) error { + + fileToZip, err := os.Open(filename) + if err != nil { + return err + } + defer fileToZip.Close() + + // Get the file information + info, err := fileToZip.Stat() + if err != nil { + return err + } + + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + // Using FileInfoHeader() above only uses the basename of the file. If we want + // to preserve the folder structure we can overwrite this with the full path. + header.Name = filename + + // Change to deflate to gain better compression + // see http://golang.org/pkg/archive/zip/#pkg-constants + header.Method = zip.Deflate + + writer, err := zipWriter.CreateHeader(header) + if err != nil { + return err + } + _, err = io.Copy(writer, fileToZip) + return err +} diff --git a/pkg/exporter/onboardingmachine_exporter.go b/pkg/exporter/onboardingmachine_exporter.go deleted file mode 100644 index 5c64ba72..00000000 --- a/pkg/exporter/onboardingmachine_exporter.go +++ /dev/null @@ -1 +0,0 @@ -package exporter diff --git a/tests/custom_resource_test.go b/tests/custom_resource_test.go new file mode 100644 index 00000000..d3435f16 --- /dev/null +++ b/tests/custom_resource_test.go @@ -0,0 +1,33 @@ +package tests + +import ( + "fmt" + "strings" + "testing" + + "github.com/Azure/aks-periscope/pkg/utils" +) + +func TestCustomResource(t *testing.T) { + output, err := utils.RunCommandOnContainer("kubectl", "get", "namespace", "--output=jsonpath={.items..metadata.name}") + if err != nil { + t.Errorf("Error: %s", err) + } + namespaces := strings.Split(output, " ") + for _, namespace := range namespaces { + output, err = utils.RunCommandOnContainer("kubectl", "-n", namespace, "get", "crd", "--output=jsonpath={.items..metadata.name}") + if err != nil { + t.Errorf("Error: %s", err) + } + + objects := strings.Split(output, " ") + for _, object := range objects { + output, err := utils.RunCommandOnContainer("kubectl", "-n", namespace, "describe", "crd", object) + if err != nil { + t.Errorf("Error: %s", err) + } else { + fmt.Printf(output) + } + } + } +} diff --git a/tests/helm_test.go b/tests/helm_test.go new file mode 100644 index 00000000..319174bc --- /dev/null +++ b/tests/helm_test.go @@ -0,0 +1,24 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/Azure/aks-periscope/pkg/utils" +) + +func TestHelm(t *testing.T) { + + output, err := utils.RunCommandOnContainer("helm", "list", "--all-namespaces") + if err != nil { + t.Errorf("Error: %s", err) + } else { + fmt.Printf(output) + } + output, err = utils.RunCommandOnContainer("helm", "history", "-n", "default", "azure-arc") + if err != nil { + t.Errorf("Error: %s", err) + } else { + fmt.Printf(output) + } +}