From ef6ae9ecf4163441fbe3bcf1bcc9e04d0402389f Mon Sep 17 00:00:00 2001 From: Rohan Chakravarthy Date: Wed, 25 Jul 2018 12:37:26 -0700 Subject: [PATCH] Plumb through log analytics values (#274) * plumb through log analytics values * add option to specify a log analytics file as well * use secret for log analytics --- .gitignore | 3 ++ .../virtual-kubelet/templates/deployment.yaml | 4 ++ charts/virtual-kubelet/templates/secrets.yaml | 3 ++ charts/virtual-kubelet/values.yaml | 6 ++- providers/azure/aci.go | 21 ++++++++++ providers/azure/client/README.md | 26 +++++++++++++ providers/azure/client/aci/analytics.go | 39 +++++++++++++++++++ providers/azure/client/aci/analytics_test.go | 38 ++++++++++++++++++ providers/azure/client/aci/client_test.go | 22 +---------- 9 files changed, 141 insertions(+), 21 deletions(-) create mode 100644 providers/azure/client/aci/analytics.go create mode 100644 providers/azure/client/aci/analytics_test.go diff --git a/.gitignore b/.gitignore index c69176432..7ef1ffcf7 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ bin/ # Test credentials file credentials.json +# Test loganalytics file +loganalytics.json + # VS Code files .vscode/ diff --git a/charts/virtual-kubelet/templates/deployment.yaml b/charts/virtual-kubelet/templates/deployment.yaml index 41ba5b766..046b86a2f 100644 --- a/charts/virtual-kubelet/templates/deployment.yaml +++ b/charts/virtual-kubelet/templates/deployment.yaml @@ -26,6 +26,10 @@ spec: value: /etc/virtual-kubelet/cert.pem - name: APISERVER_KEY_LOCATION value: /etc/virtual-kubelet/key.pem + {{ if .Values.loganalytics.enabled }} + - name: LOG_ANALYTICS_AUTH_LOCATION + value: /etc/virtual-kubelet/loganalytics.json + {{ end }} - name: VKUBELET_POD_IP valueFrom: fieldRef: diff --git a/charts/virtual-kubelet/templates/secrets.yaml b/charts/virtual-kubelet/templates/secrets.yaml index c3eb8c84c..bb25d6d74 100644 --- a/charts/virtual-kubelet/templates/secrets.yaml +++ b/charts/virtual-kubelet/templates/secrets.yaml @@ -7,3 +7,6 @@ data: credentials.json: {{ printf "{ \"clientId\": \"%s\", \"clientSecret\": \"%s\", \"subscriptionId\": \"%s\", \"tenantId\": \"%s\", \"activeDirectoryEndpointUrl\": \"https://login.microsoftonline.com/\", \"resourceManagerEndpointUrl\": \"https://management.azure.com/\", \"activeDirectoryGraphResourceId\": \"https://graph.windows.net/\", \"sqlManagementEndpointUrl\": \"database.windows.net\", \"galleryEndpointUrl\": \"https://gallery.azure.com/\", \"managementEndpointUrl\": \"https://management.core.windows.net/\" }" (default "MISSING" .Values.env.azureClientId) (default "MISSING" .Values.env.azureClientKey) (default "MISSING" .Values.env.azureSubscriptionId) (default "MISSING" .Values.env.azureTenantId) | b64enc | quote }} cert.pem: {{ (default "TUlTU0lORw==" .Values.env.apiserverCert) | quote }} key.pem: {{ (default "TUlTU0lORw==" .Values.env.apiserverKey) | quote }} + {{ if .Values.loganalytics.enabled }} + loganalytics.json: {{ printf "{\"workspaceID\": \"%s\",\"workspaceKey\": \"%s\"}" (required "workspaceID is required for loganalytics" .Values.loganalytics.workspaceID ) (required "workspaceKey is required for loganalytics" .Values.loganalytics.workspaceKey ) }} + {{ end }} diff --git a/charts/virtual-kubelet/values.yaml b/charts/virtual-kubelet/values.yaml index b0426297a..b1e04a6da 100644 --- a/charts/virtual-kubelet/values.yaml +++ b/charts/virtual-kubelet/values.yaml @@ -14,7 +14,11 @@ env: nodeOsType: apiserverCert: apiserverKey: - monitoredNamespace: + monitoredNamespace: +loganalytics: + enabled: false + workspaceID: + workspaceKey: # Install Default RBAC roles and bindings rbac: diff --git a/providers/azure/aci.go b/providers/azure/aci.go index e085045e0..58b533979 100644 --- a/providers/azure/aci.go +++ b/providers/azure/aci.go @@ -44,6 +44,7 @@ type ACIProvider struct { pods string internalIP string daemonEndpointPort int32 + diagnostics *aci.ContainerGroupDiagnostics } // AuthConfig is the secret returned from an ImageRegistryCredential @@ -155,6 +156,25 @@ func NewACIProvider(config string, rm *manager.ResourceManager, nodeName, operat return nil, err } + // If the log analytics file has been specified, load workspace credentials from the file + if logAnalyticsAuthFile := os.Getenv("LOG_ANALYTICS_AUTH_LOCATION"); logAnalyticsAuthFile != "" { + p.diagnostics, err = aci.NewContainerGroupDiagnosticsFromFile(logAnalyticsAuthFile) + if err != nil { + return nil, err + } + } + + // If we have both the log analytics workspace id and key, add them to the provider + // Environment variables overwrite the values provided in the file + if logAnalyticsID := os.Getenv("LOG_ANALYTICS_ID"); logAnalyticsID != "" { + if logAnalyticsKey := os.Getenv("LOG_ANALYTICS_KEY"); logAnalyticsKey != "" { + p.diagnostics, err = aci.NewContainerGroupDiagnostics(logAnalyticsID, logAnalyticsKey) + if err != nil { + return nil, err + } + } + } + if rg := os.Getenv("ACI_RESOURCE_GROUP"); rg != "" { p.resourceGroup = rg } @@ -227,6 +247,7 @@ func (p *ACIProvider) CreatePod(pod *v1.Pod) error { containerGroup.ContainerGroupProperties.Containers = containers containerGroup.ContainerGroupProperties.Volumes = volumes containerGroup.ContainerGroupProperties.ImageRegistryCredentials = creds + containerGroup.ContainerGroupProperties.Diagnostics = p.diagnostics filterServiceAccountSecretVolume(p.operatingSystem, &containerGroup) diff --git a/providers/azure/client/README.md b/providers/azure/client/README.md index 23943b52e..e07b7f2bd 100644 --- a/providers/azure/client/README.md +++ b/providers/azure/client/README.md @@ -38,3 +38,29 @@ The file looks like this, in case you want to create it yourself: "managementEndpointUrl": "https://management.core.windows.net/" } ``` + + +## Log Analytics support + +Log Analytics is supported through environment variables: +- `LOG_ANALYTICS_KEY` +- `LOG_ANALYTICS_ID` + +You can also specify a file with these values and specify the path to it in the `LOG_ANALYTICS_AUTH_LOCATION`: + +``` bash +export LOG_ANALYTICS_AUTH_LOCATION=/secure/location/loganalytics.json +``` + +``` powershell +$env:LOG_ANALYTICS_AUTH_LOCATION= "/secure/location/loganalytics.json" +``` + +The file should look like this: + +``` json +{ + "workspaceID": "", + "workspaceKey": "" +} +``` \ No newline at end of file diff --git a/providers/azure/client/aci/analytics.go b/providers/azure/client/aci/analytics.go new file mode 100644 index 000000000..0b1f00f3f --- /dev/null +++ b/providers/azure/client/aci/analytics.go @@ -0,0 +1,39 @@ +package aci + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" +) + +func NewContainerGroupDiagnostics(logAnalyticsID, logAnalyticsKey string) (*ContainerGroupDiagnostics, error) { + + if logAnalyticsID == "" || logAnalyticsKey == "" { + return nil, errors.New("Log Analytics configuration requires both the workspace ID and Key") + } + + return &ContainerGroupDiagnostics{ + LogAnalytics: &LogAnalyticsWorkspace{ + WorkspaceID: logAnalyticsID, + WorkspaceKey: logAnalyticsKey, + }, + }, nil +} + +func NewContainerGroupDiagnosticsFromFile(filepath string) (*ContainerGroupDiagnostics, error) { + + analyticsdata, err := ioutil.ReadFile(filepath) + if err != nil { + return nil, fmt.Errorf("Reading Log Analytics Auth file %q failed: %v", filepath, err) + } + // Unmarshal the log analytics file. + var law LogAnalyticsWorkspace + if err := json.Unmarshal(analyticsdata, &law); err != nil { + return nil, err + } + + return &ContainerGroupDiagnostics{ + LogAnalytics: &law, + }, nil +} diff --git a/providers/azure/client/aci/analytics_test.go b/providers/azure/client/aci/analytics_test.go new file mode 100644 index 000000000..fef69988d --- /dev/null +++ b/providers/azure/client/aci/analytics_test.go @@ -0,0 +1,38 @@ +package aci + +import ( + "io/ioutil" + "os" + "testing" +) + +func TestLogAnalyticsFileParsingSuccess(t *testing.T) { + diagnostics, err := NewContainerGroupDiagnosticsFromFile("../../../../loganalytics.json") + if err != nil { + t.Fatal(err) + } + + if diagnostics == nil || diagnostics.LogAnalytics == nil { + t.Fatalf("Unexpected nil diagnostics. Log Analytics file not parsed correctly") + } + + if diagnostics.LogAnalytics.WorkspaceID == "" || diagnostics.LogAnalytics.WorkspaceKey == "" { + t.Fatalf("Unexpected empty analytics authentication credentials. Log Analytics file not parsed correctly") + } +} + +func TestLogAnalyticsFileParsingFailure(t *testing.T) { + tempFile, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + _, err = NewContainerGroupDiagnosticsFromFile(tempFile.Name()) + + // Cleaup + tempFile.Close() + os.Remove(tempFile.Name()) + + if err == nil { + t.Fatalf("Expected parsing an empty Log Analytics auth file to fail, but there were no errors") + } +} diff --git a/providers/azure/client/aci/client_test.go b/providers/azure/client/aci/client_test.go index fb918cde5..0fcaf5227 100644 --- a/providers/azure/client/aci/client_test.go +++ b/providers/azure/client/aci/client_test.go @@ -1,9 +1,6 @@ package aci import ( - "encoding/json" - "fmt" - "io/ioutil" "log" "os" "strings" @@ -363,21 +360,8 @@ func TestCreateContainerGroupWithReadinessProbe(t *testing.T) { } } -func logAnalyticsWorkspaceFromFile(filepath string) (*LogAnalyticsWorkspace, error) { - analyticsdata, err := ioutil.ReadFile(filepath) - if err != nil { - return nil, fmt.Errorf("Reading LogAnalyticsWorkspace file %q failed: %v", filepath, err) - } - // Unmarshal the log analytics file. - var law LogAnalyticsWorkspace - if err := json.Unmarshal(analyticsdata, &law); err != nil { - return nil, err - } - return &law, nil -} - func TestCreateContainerGroupWithLogAnalytics(t *testing.T) { - law, err := logAnalyticsWorkspaceFromFile("../../../../loganalytics.json") + diagnostics, err := NewContainerGroupDiagnosticsFromFile("../../../../loganalytics.json") if err != nil { t.Fatal(err) } @@ -411,9 +395,7 @@ func TestCreateContainerGroupWithLogAnalytics(t *testing.T) { }, }, }, - Diagnostics: &ContainerGroupDiagnostics{ - LogAnalytics: law, - }, + Diagnostics: diagnostics, }, }) if err != nil {