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

[K8s Operator] Documentation and easier branding setup #495

Merged
merged 1 commit into from
Apr 21, 2020
Merged
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
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ This repository offers a wide collection of **ASP.NET Core** Health Check packag

## HealthChecks UI and Kubernetes

- Kubernetes Operator (Documentation in progress)
- [Kubernetes Operator](#UI-Kubernetes-Operator)
- [Kubernetes automatic services discovery](#UI-Kubernetes-automatic-services-discovery)

## HealthChecks and Devops
Expand Down Expand Up @@ -497,6 +497,12 @@ services.AddHealthChecksUI(setupSettings: setup =>

```

## UI Kubernetes Operator

If you are running your workloads in kubernetes, you can benefit from it and have your healthchecks environment ready and monitoring in seconds.

You can get for information in our [HealthChecks Operator docs](./doc/k8s-operator.md)

## UI Kubernetes automatic services discovery

<!-- ![k8s-discovery](./doc/images/k8s-discovery-service.png) -->
Expand Down
2 changes: 1 addition & 1 deletion build/dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,6 @@
<HealthCheckCloudFirestore>3.1.1</HealthCheckCloudFirestore>
<HealthCheckIoTHub>3.1.1</HealthCheckIoTHub>
<HealthCheckIbmMQ>3.1.1</HealthCheckIbmMQ>
<HealthChecksUIK8sOperator>3.1.0-beta.6</HealthChecksUIK8sOperator>
<HealthChecksUIK8sOperator>3.1.0-beta.7</HealthChecksUIK8sOperator>
</PropertyGroup>
</Project>
5 changes: 1 addition & 4 deletions deploy/operator/crd/healthcheck-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@ spec:
image:
type: string
imagePullPolicy:
type: string
stylesheetPath:
type: string
pattern: ^[^\/][^.]+$
type: string
stylesheetContent:
type: string
serviceAnnotations:
Expand Down
Binary file added doc/images/ui-branding-2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
239 changes: 239 additions & 0 deletions doc/k8s-operator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# Health Checks Kubernetes Operator

This repository, offers a Kubernetes Operator that automatically deploys and configures the HealthChecks UI in k8s and also monitors labeled services that expose healthchecks in a given namespace.

The operator also has a namespaced service watcher that can detect new services and report them back to the UI and also tracks services modifications and removals so the UI always represents the latest desired state.

## Installing HealthChecks Operator

### Intalling with the installer tool

We offer a installer tool for windows and linux, that will install all necessary resources to have your operator up and running in seconds in the healthchecks namespace.

You can download the differente releases from this links:

- [Windows Release](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/raw/master/deploy/operator/installer/releases/operator-installer-win.exe)
- [Linux Release](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/raw/master/deploy/operator/installer/releases/operator-installer-linux)

Note: If you execute the tool with --delete parameters, a resource cleanup will be triggered and all the operator related resources including the crd will be removed.

### Installing from definition files

You can deploy healthchecks operator in your cluster by cloning the repository and applying the yaml definition files:

- Apply the custom resource definition: [healthcheck-crd](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/blob/master/deploy/operator/crd/healthcheck-crd.yaml)

`kubectl apply -f deploy/operator/crd/healthcheck-crd.yaml`

- Apply the rest of definition yamls

`kubectl apply -f ./deploy/operator`

## Creating a HealthCheck Resource

The [HealthCheck operator definition](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/blob/master/deploy/operator/crd/healthcheck-crd.yaml), has the following fields to be configured:

### Required fields

| Field | Description |
| ------------- | :--------------------------------------------------------------------------------- |
| name | Name of the healthcheck resource |
| servicesLabel | The label the operator service watcher will use to detected healthchecks endpoints |

### Optional fields

| Field | Description | Default |
| --------------------- | :------------------------------------------------------------------- | ---------------------------------------------------- |
| serviceType | How the UI should be published (ClusterIP, LoadBalancer or NodePort) | ClusterIP |
| portNumber | What port will be used to expose the UI service | 80 |
| uiPath | Location where the UI frontend will be served | /healthchecks |
| healthChecksPath | Path where the UI will collect health from endpoints | /health (Can be overriden with a service annotation) |
| healthChecksScheme | Scheme to be used to collect health from endpoints | http (Can be overriden with a service annotation) |
| image | Image to be used by the UI | xabarilcoding/healthchecksui:latest |
| imagePullPolicy | Deployment image pull policy | Always |
| stylesheetContent | css content used to brand the UI | none |
| serviceAnnotations | name / value array to use custom annotations in UI service | none |
| deploymentAnnotations | name / value array to use custom annotations in UI Deployment | none |

## Sample HealthChecks Operator Tutorial

Let's start by creating a demo namespace:

`kubectl create ns lande`

And then, create a healthchecks-ui.yaml file with the following contents:

```yaml
apiVersion: "aspnetcore.ui/v1"
kind: HealthCheck
metadata:
name: healthchecks-ui
namespace: demo
spec:
name: healthchecks-ui
servicesLabel: HealthChecks
serviceType: LoadBalancer
stylesheetContent: >
:root {
--primaryColor: #2a3950;
--secondaryColor: #f4f4f4;
--bgMenuActive: #e1b015;
--bgButton: #e1b015;
--logoImageUrl: url('https://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/WoW_icon.svg/1200px-WoW_icon.svg.png');
--bgAside: var(--primaryColor);
}
serviceAnnotations:
- name: service.beta.kubernetes.io/azure-load-balancer-internal
value: "true"
```

Let's apply the HealthCheck definition with the created file:

`kubectl apply -f healthchecks-ui.yaml`

You can now check your created HealthCheck resource using:

`kubectl get healthcheck -n demo`

### Operator controller

Once you apply a HealthCheck kind resource, the operator will automatically create some resources in the namespace. The UI deployment and service to expose the dashboard, a secret that enables secure communication from the operator to the UI service, and an optional configmap volume source depending if you configured the stylesheetContent branding.

All this resources are created using OwnerReferences so that means if you delete the HealthCheck resource, all the child resources will be automatically purged with it.

The operator also starts services watchers per namespace that will monitor creation, modification of labels and annotations and also services themselves. If a monitored application is added or removed and has the HealthChecks label annotation, the UI will automatically react to this events.

## Deploying an Application with HealthChecks to be monitored automatically

The next step is creating an AspNetCore core uses some HealthChecks. We are going to use a sample app that is already deployed in dockerhub (carloslanderas/hc-website) using the following code:

```csharp
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace hc_website
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services
.AddRouting()
.AddHealthChecks()
.AddProcessAllocatedMemoryHealthCheck(maximumMegabytesAllocated: 50)
.AddCheck("self", () => HealthCheckResult.Healthy());
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health", new HealthCheckOptions
{
Predicate = r => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
});
}
}
}

```

The next step, is deploying this application using a deployment yaml definition like this:

```yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: hc-website-deploy
namespace: demo
labels:
app: hc-website
spec:
selector:
matchLabels:
app: hc-website
replicas: 1
template:
metadata:
labels:
app: hc-website
spec:
containers:
- name: hc-website
image: carloslanderas/hc-website
imagePullPolicy: Always
ports:
- name: http
containerPort: 80
env:
- name: Logging__LogLevel__Default
value: Debug
- name: Logging__LogLevel__Microsoft
value: Warning
- name: Logging__LogLevel__hc_website
value: Debug
---
kind: Service
apiVersion: v1
metadata:
name: hc-website-svc
namespace: demo
labels:
HealthChecks: enabled
spec:
selector:
app: hc-website
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 80
```

Note the HealthChecks : enabled label to tell the operator this is a healthchecked application:

```yaml
labels:
HealthChecks: enabled
```

Create a `deployment.yaml` file with above contents and apply it:

`kubectl apply -f deployment.yaml`

## Service Annotations

You can use `HealthChecksPath` and `HealthChecksScheme` annotations in HealthChecks services to override the default Path and Scheme crd definition.

## Accessing the UI service

Once you applied the [HealthCheck](#Sample-HealthCheck-definition) definition, the operator has automatically created some resources to setup your UI service and you can now access the healthchecks dashboard after inspecting it's address:

`kubectl get svc -n demo`

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

hc-website-svc ClusterIP 10.0.145.224 <none> 80/TCP 16h

healthchecks-ui-svc LoadBalancer 10.0.20.73 **51.138.24.168** 80:31343/TCP 16h

Your UI endpoint will be listening in http://51.138.24.168/healthchecks and the labeled service should appear automatically.

![HealthChecksUIBranding](./images/ui-branding-2.jpg)
2 changes: 2 additions & 0 deletions src/HealthChecks.UI.K8s.Operator/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ internal class Constants
public const string DefaultScheme = "http";
public const string DefaultHealthPath = "health";
public const string PushServiceAuthKey = "key";
public const string StylesPath = "css";
public const string StyleSheetName = "styles";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public class HealthCheckResourceSpec
public string HealthChecksScheme { get; set; }
public string Image { get; set; }
public string ImagePullPolicy { get; set; }
public string StylesheetPath { get; set; }
public string StylesheetContent { get; set; }
public List<NameValueObject> ServiceAnnotations { get; set; } = new List<NameValueObject>();
public List<NameValueObject> DeploymentAnnotations { get; set; } = new List<NameValueObject>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static V1OwnerReference CreateOwnerReference(this HealthCheckResource res
}

public static bool HasBrandingConfigured(this HealthCheckResource resource) =>
resource.Spec.StylesheetContent.NotEmpty() && resource.Spec.StylesheetPath.NotEmpty();
resource.Spec.StylesheetContent.NotEmpty();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,11 @@ public async Task Delete(HealthCheckResource resource)
}
public V1ConfigMap Build(HealthCheckResource resource)
{
string path = resource.Spec.StylesheetPath;

if(path.Contains(SPLIT_CHAR))
{
path = path.Split(SPLIT_CHAR)[^1];
}

return new V1ConfigMap
{
BinaryData = new Dictionary<string, byte[]>
{
[path] = Encoding.UTF8.GetBytes(resource.Spec.StylesheetContent)
[Constants.StyleSheetName] = Encoding.UTF8.GetBytes(resource.Spec.StylesheetContent)
},
Metadata = new V1ObjectMeta
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,11 @@ public V1Deployment Build(HealthCheckResource resource)
if (specification.Volumes == null) specification.Volumes = new List<V1Volume>();
if (container.VolumeMounts == null) container.VolumeMounts = new List<V1VolumeMount>();

specification.Volumes.Add(new V1Volume(name: "healthchecks-volume",
specification.Volumes.Add(new V1Volume(name: volumeName,
configMap: new V1ConfigMapVolumeSource(name: $"{resource.Spec.Name}-config")));

var mountPath = resource.Spec.StylesheetPath.Split('/')[0];

container.Env.Add(new V1EnvVar("ui_stylesheet", resource.Spec.StylesheetPath));
container.VolumeMounts.Add(new V1VolumeMount($"app/{mountPath}", volumeName));
container.Env.Add(new V1EnvVar("ui_stylesheet", $"{Constants.StylesPath}/{Constants.StyleSheetName}"));
container.VolumeMounts.Add(new V1VolumeMount($"/app/{Constants.StylesPath}", volumeName));
}

return new V1Deployment(metadata: metadata, spec: spec);
Expand Down
25 changes: 16 additions & 9 deletions src/HealthChecks.UI.K8s.Operator/Operator/HealthChecksOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ public Task StopAsync(CancellationToken cancellationToken)
{
_diagnostics.OperatorShuttingDown();
_operatorCts.Cancel();
if(_watcher != null && _watcher.Watching)

if (_watcher != null && _watcher.Watching)
{
_watcher.Dispose();
}

_serviceWatcher.Dispose();
_channel.Writer.Complete();

return Task.CompletedTask;
}

Expand Down Expand Up @@ -101,15 +101,22 @@ private async Task OperatorListener()
{
while (_channel.Reader.TryRead(out ResourceWatch item))
{
if (item.EventType == WatchEventType.Added)
try
{
await _controller.DeployAsync(item.Resource);
await _serviceWatcher.Watch(item.Resource, _operatorCts.Token);
if (item.EventType == WatchEventType.Added)
{
await _controller.DeployAsync(item.Resource);
await _serviceWatcher.Watch(item.Resource, _operatorCts.Token);
}
else if (item.EventType == WatchEventType.Deleted)
{
await _controller.DeleteDeploymentAsync(item.Resource);
_serviceWatcher.Stopwatch(item.Resource);
}
}
else if (item.EventType == WatchEventType.Deleted)
catch (Exception ex)
{
await _controller.DeleteDeploymentAsync(item.Resource);
_serviceWatcher.Stopwatch(item.Resource);
_diagnostics.OperatorThrow(ex);
}
}
}
Expand Down