Skip to content

Commit

Permalink
Add Kubernetes support to Docker sample (#5493)
Browse files Browse the repository at this point in the history
* Add Kubernetes support to Docker sample

* adding cpu/ram limits + statefulsets info
  • Loading branch information
dgkanatsios authored and benjaminpetit committed Oct 10, 2019
1 parent 61c89e5 commit cb37ca6
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 0 deletions.
1 change: 1 addition & 0 deletions Samples/2.0/docker-aspnet-core/API/API.csproj
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
<ServerGarbageCollection>false</ServerGarbageCollection> <!-- https://blog.markvincze.com/troubleshooting-high-memory-usage-with-asp-net-core-on-kubernetes/ -->
</PropertyGroup>

<ItemGroup>
Expand Down
47 changes: 47 additions & 0 deletions Samples/2.0/docker-aspnet-core/Makefile
@@ -0,0 +1,47 @@
# a Makefile to test this sample on Kubernetes
# you can test it locally (e.g. using Docker for Windows/Mac) or remotely, on your cloud provider of choice
# do not forget to set Azure Storage connection string on Silo/Program.cs and API/Startup.cs

# your Docker registry (we suppose you have logged in and have push access)
REGISTRY ?= docker.io/dgkanatsios
# the context of your local Kubernetes cluster
LOCAL_K8S_CLUSTER=docker-for-desktop
# the contect of your remote Kubernetes cluster
REMOTE_K8S_CLUSTER=aksorleans
# version/tag of the images that will be pushed to Docker Hub
VERSION=0.0.14

API_PROJECT_NAME=orleans-api
SILO_PROJECT_NAME=orleans-silo

# tag of the images that will be pushed for local development
TAG?=$(shell git rev-list HEAD --max-count=1 --abbrev-commit)

buildlocal:
docker build -f ./Silo/Dockerfile -t $(SILO_PROJECT_NAME):$(TAG) .
docker build -f ./API/Dockerfile -t $(API_PROJECT_NAME):$(TAG) .
docker system prune -f
deploylocal: uselocalcontext
kubectl config use-context $(LOCAL_K8S_CLUSTER)
sed -e 's/orleans-silo-image/$(SILO_PROJECT_NAME):$(TAG)/' -e 's/orleans-api-image/$(API_PROJECT_NAME):$(TAG)/' deploy.yaml | kubectl apply -f -
cleandeploylocal: uselocalcontext clean
uselocalcontext:
kubectl config use-context $(LOCAL_K8S_CLUSTER)

buildremote:
docker build -f ./Silo/Dockerfile -t $(REGISTRY)/$(SILO_PROJECT_NAME):$(VERSION) .
docker build -f ./API/Dockerfile -t $(REGISTRY)/$(API_PROJECT_NAME):$(VERSION) .
docker system prune -f
pushremote:
docker push $(REGISTRY)/$(SILO_PROJECT_NAME):$(VERSION)
docker push $(REGISTRY)/$(API_PROJECT_NAME):$(VERSION)
deployremote: useremotecontext
sed -e 's,orleans-silo-image,$(REGISTRY)/$(SILO_PROJECT_NAME):$(VERSION),' -e 's,orleans-api-image,$(REGISTRY)/$(API_PROJECT_NAME):$(VERSION),' deploy.yaml | kubectl apply -f -
# after a few minutes, you can do a `kubectl get service` to get the Public IP endpoint
cleandeployremote: useremotecontext clean
useremotecontext:
kubectl config use-context $(REMOTE_K8S_CLUSTER)

clean:
kubectl delete deployment $(SILO_PROJECT_NAME) $(API_PROJECT_NAME)
kubectl delete service $(API_PROJECT_NAME)
92 changes: 92 additions & 0 deletions Samples/2.0/docker-aspnet-core/README.md
@@ -0,0 +1,92 @@
# Docker - Kubernetes - ASP.NET Core

## Description

This sample creates two Docker containers, one that creates the Silo that hosts `ValueGrain` instances (you can find it in the `Grains/ValueGrain.cs` file) and a ASP.NET Core app (which acts like a client to this Silo) called `API` and resides in the `API` folder. User can access the API app and create/update `ValueGrain` instances.
These two containers can be tested using [Docker Compose](https://docs.docker.com/compose/) or in a [Kubernetes](https://www.kubernetes.io) cluster.

## Before running the sample

Before running the sample, you should

- Edit `API/startup.cs` and `Silo/Program.cs` and add your Azure Storage Connection string. However, bear in mind that in production environments the recommended way to set configuration information is via environment variables
- Edit `Makefile` and modify the `REGISTRY`, `LOCAL_K8S_CLUSTER` and `REMOTE_K8S_CLUSTER` with the appropriate values for your setup

## Docker compose

To build and run the sample using [Docker Compose](https://docs.docker.com/compose/) run:

```bash
docker-compose up
```

Then, on another command prompt, you can do `docker ps` to find out the port the the API service is listening.

```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9af86b6a00fa api "dotnet API.dll" 31 seconds ago Up 26 seconds 0.0.0.0:32771->80/tcp docker-aspnet-core_api_1
3e71db40b80e silo "dotnet Silo.dll" 31 seconds ago Up 27 seconds docker-aspnet-core_silo_1
```

As you can see, API is listening to port *32771* (your port will be different, of course). Now, you can use your browser and navigate to `http://localhost:32771` to test the app.

To stop the sample you can use `Ctrl-C`, whereas to delete resources you should run:

```bash
docker-compose down
```

## Kubernetes

We've added a `Makefile` as well as a `deploy.yaml` file with the necessary commands to run the solution on a Kubernetes cluster.

### Local Kubernetes cluster

You can run the sample on your local Kubernetes cluster. We have tested this with [Docker for Windows](https://docs.docker.com/docker-for-windows/) but it should also work with [Docker for Mac](https://docs.docker.com/docker-for-mac/) or [Minikube](https://kubernetes.io/docs/setup/minikube/) as well. To run it on local cluster, use a Linux shell or [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) on Windows and run:

```bash
make buildlocal
make deploylocal
```

Use your browser and navigate to `http://localhost:8888` to test the app.

If you want to remove resources, use:

```bash
make cleandeploylocal
```

### Remote Kubernetes cluster

To run the sample on your remote Kubernetes cluster, use:

```bash
make buildremote
make pushremote
make deployremote
# if you want to do it in one step, you can use make buildremote pushremote deployremote
```

You can use `kubectl get svc` to get the Public IP for the API [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/), so you can connect via web browser. Alternatively, you can do `kubectl port-forward service/orleans-api 8888:8888` to create a tunnel between your machine and the remote Service. This way, you can access it via web browser at `http://localhost:8888`.

If you want to remove remote resources, use:

```bash
make cleandeployremote
```

### Using StatefulSets

Some advice (courtesy of [James Sukhabut](https://github.com/jsukhabut)) on using [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/) to run the Silos.

StatefulSets are shutdown, scaled, or upgraded in order (ie. 4,3,2,1,0). When you try to upgrade your silo with a new deployment, if you use stateless deployment you have 5 brand new silos so it will take some time for them to get into a quorum. Using StatefulSets, first instance 4 gets shutdown and upgraded, then instance 3, then 2, 1, and 0. This technique can avoid downtime on upgrades.

Regarding Orleans clients on Kubernetes, you can set the Silo as a Kubernetes Service with ClusterIP=None. When this is done the silo DNS name (e.g. mysilo.mynamespace.svc.cluster.local) will have a multi-IP response containing all the IPs of the Silo Pods. You can use a custom DnsNameGatewayListProvider class at the client that resolves the name to IP. If the Silo scales up or down the client will easily see the updated list. This can be much faster compared to the client reading Azure tables because the rows may contain Silos that are gone but not marked as dead yet. Kubernetes will always return the active silo (IP). You can find some code in [this Pull Request](https://github.com/dotnet/orleans/pull/4301)

### Additional stuff

- If you have issues with Silos not being able to get activated when you re-deploy/update your Deployments, try deleting all rows from the Azure Table Storage membership table.
- We're using `imagePullPolicy: Always` so you would not need to increment the `VERSION` variable on the Makefile, but be aware that you may have to, just in case. Oh, and don't use `latest` in Production ([source](https://kubernetes.io/docs/concepts/configuration/overview/#container-images)).
- If you would like to use Kubernetes to store Membership information (via Custom Resource Definitions), check out [this](https://github.com/OrleansContrib/Orleans.Clustering.Kubernetes) project.
- We deactivated Server Garbage Collection on the API and Silo containers (check the corresponding .csproj files) because of [this](https://blog.markvincze.com/troubleshooting-high-memory-usage-with-asp-net-core-on-kubernetes/).
1 change: 1 addition & 0 deletions Samples/2.0/docker-aspnet-core/Silo/Silo.csproj
Expand Up @@ -5,6 +5,7 @@
<ApplicationIcon />
<OutputType>Exe</OutputType>
<StartupObject />
<ServerGarbageCollection>false</ServerGarbageCollection> <!-- https://blog.markvincze.com/troubleshooting-high-memory-usage-with-asp-net-core-on-kubernetes/ -->
</PropertyGroup>

<ItemGroup>
Expand Down
75 changes: 75 additions & 0 deletions Samples/2.0/docker-aspnet-core/deploy.yaml
@@ -0,0 +1,75 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: orleans-api
name: orleans-api
spec:
replicas: 1
selector:
matchLabels:
run: orleans-api
strategy: {}
template:
metadata:
labels:
run: orleans-api
spec:
containers:
- image: orleans-api-image # image name updated by Makefile
name: orleans-api
imagePullPolicy: Always
ports:
- containerPort: 80
resources:
limits:
memory: "256Mi"
cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
labels:
run: orleans-api
name: orleans-api
spec:
ports:
- port: 8888
protocol: TCP
targetPort: 80
selector:
run: orleans-api
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: orleans-silo
name: orleans-silo
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
replicas: 2
selector:
matchLabels:
run: orleans-silo
template:
metadata:
labels:
run: orleans-silo
spec:
containers:
- image: orleans-silo-image # image name updated by Makefile
name: orleans-silo
imagePullPolicy: Always
ports:
- containerPort: 11111
- containerPort: 30000
resources:
limits:
memory: "256Mi"
cpu: "250m"

0 comments on commit cb37ca6

Please sign in to comment.