diff --git a/.gitignore b/.gitignore index 1e1573a..c0fab31 100644 --- a/.gitignore +++ b/.gitignore @@ -288,3 +288,5 @@ __pycache__/ *.odx.cs *.xsd.cs .DS_Store + +**/output \ No newline at end of file diff --git a/samples/Definitions/Client-ServiceAccount.yaml b/samples/Definitions/Client-ServiceAccount.yaml new file mode 100644 index 0000000..28f02dc --- /dev/null +++ b/samples/Definitions/Client-ServiceAccount.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: orleansclient + namespace: kubetest +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: orleansclient +rules: + - apiGroups: + - orleans.dot.net + resources: + - silos + - clusterversions + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: orleansclient +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: orleansclient +subjects: + - kind: ServiceAccount + name: orleansclient + namespace: kubetest diff --git a/samples/Definitions/Client.yaml b/samples/Definitions/Client.yaml new file mode 100644 index 0000000..f6ac25a --- /dev/null +++ b/samples/Definitions/Client.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: orleans-client + labels: + app: kubeclient +spec: + replicas: 1 + selector: + matchLabels: + app: kubeclient + template: + metadata: + labels: + app: kubeclient + spec: + serviceAccountName: orleansclient + containers: + - name: orleansclient + image: kubeclient:latest + imagePullPolicy: Never diff --git a/samples/Definitions/Gateway.yaml b/samples/Definitions/Gateway.yaml new file mode 100644 index 0000000..911402f --- /dev/null +++ b/samples/Definitions/Gateway.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: orleans-gateway + labels: + app: kubegateway +spec: + replicas: 2 + selector: + matchLabels: + app: kubegateway + template: + metadata: + labels: + app: kubegateway + spec: + serviceAccountName: orleanssilo + containers: + - name: orleanssilo + image: kubegateway:latest + imagePullPolicy: Never \ No newline at end of file diff --git a/samples/Definitions/Silo-ServiceAccount.yaml b/samples/Definitions/Silo-ServiceAccount.yaml new file mode 100644 index 0000000..6216f84 --- /dev/null +++ b/samples/Definitions/Silo-ServiceAccount.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: orleanssilo + namespace: kubetest +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: orleanssilo +rules: + - apiGroups: + - orleans.dot.net + resources: + - silos + - clusterversions + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: orleanssilo +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: orleanssilo +subjects: + - kind: ServiceAccount + name: orleanssilo + namespace: kubetest diff --git a/samples/Definitions/Silo.yaml b/samples/Definitions/Silo.yaml new file mode 100644 index 0000000..7c7e729 --- /dev/null +++ b/samples/Definitions/Silo.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: orleans-silo + labels: + app: kubesilo +spec: + replicas: 1 + selector: + matchLabels: + app: kubesilo + template: + metadata: + labels: + app: kubesilo + spec: + serviceAccountName: orleanssilo + containers: + - name: orleanssilo + image: kubesilo:latest + imagePullPolicy: Never diff --git a/samples/README.md b/samples/README.md index f1851c0..d3d3434 100644 --- a/samples/README.md +++ b/samples/README.md @@ -1,30 +1,64 @@ -# Orleans on Kubernetes - Samples - -This directory contains 3 projects to show a practical example of Orleans running on top of Kubernetes using `Orleans.Clustering.Kubernetes` package: - -1. `KubeClient` -> Regular Orleans Client. -2. `KubeSiloHost` - Orleans silo host. -3. `KubeGatewayHost` - An Orleans silo which has the gateway enabled. - -> NOTE: The reason the gateway and non-gateway silos are on different projects, is just to illustrate that you don't have to expose all your pods to outside world in case you don't want it. It is not a requirement and all silos can be gateways if you want it. - -## Pre-requisites - -1. Docker -2. Kubernetes -3. .Net Core 2.0 SDK - -## Running it - -To run it first create the Kubernetes `namespace` you will use to host the sample deployments with `kubectl create namespace `. Take a note of the `` used so it can be used later on. - -Each project contains a regular `Dockerfile` which must be built to a Docker image just like a regular docker application. - -1. Publish each project using `dotnet publish -c Release -o PublishOutput` -2. From the `PublishOutput` directory of each project, build the image with `docker build -t : .` and replace `` and `` with the respective project name (i.e `kubesilo`, `kubehost`, `kubeclient` for the name and `latest` for the tag) or whatever name you want. -3. For each project run `kubectl run --image=: --namespace= --image-pull-policy=Never`. First the silo, later the gateway then the client. Be sure to replace the `` tags with the values used on previous steps. - -> NOTE: The reason we use `--image-pull-policy=Never` for this sample is just so Kubernetes doesn't try to pull the image from Docker Hub. - - - +# Orleans on Kubernetes - Samples + +This directory contains 3 projects to show a practical example of Orleans running on top of Kubernetes using `Orleans.Clustering.Kubernetes` package: + +1. `KubeClient` -> Regular Orleans Client. +2. `KubeSiloHost` - Orleans silo host. +3. `KubeGatewayHost` - An Orleans silo which has the gateway enabled. + +> NOTE: The reason the gateway and non-gateway silos are on different projects, is just to illustrate that you don't have to expose all your pods to outside world in case you don't want it. It is not a requirement and all silos can be gateways if you want it. + +## Pre-requisites + +1. Docker +2. Kubernetes +3. .Net Core 3.1 SDK + +## Running it + +To run it first create the Kubernetes `namespace` you will use to host the sample deployments with `kubectl create namespace `. Take a note of the `` used so it can be used later on. + +From the `samples/` directory, run the following commands to publish the .Net Core applications: + +1. `dotnet publish -c Release KubeClient -o output/KubeClient` +2. `dotnet publish -c Release KubeGatewayHost -o output/KubeGatewayHost` +3. `dotnet publish -c Release KubeSiloHost -o output/KubeSiloHost` + +Each project contains a regular `Dockerfile` which must be built to a Docker image just like a regular docker application. + +To build the images, run the following commands from the `samples/` directory: + +1. `docker build -f output/KubeClient/Dockerfile -t kubeclient output/KubeClient` +2. `docker build -f output/KubeGatewayHost/Dockerfile -t kubegateway output/KubeGatewayHost` +3. `docker build -f output/KubeSiloHost/Dockerfile -t kubesilo output/KubeSiloHost` + +Now you have 3 images containing the 3 sample projects built on your local Docker image repository. + +In order for the provider to work properly, the CRD files must be deployed. This must be done once per Kubernetes cluster regardless of how many Orleans clusters/deployments on it. + +From the `samples/` directory, run the following command: + +1. `kubectl apply -f ../src/Orleans.Clustering.Kubernetes/Definitions/ClusterVersionCRD.yaml` +2. `kubectl apply -f ../src/Orleans.Clustering.Kubernetes/Definitions/SiloEntryCRD.yaml` + +Now you need to make sure the pods can create objects using those deifnitions. To do that, the pods must run under a service account which has access to Kubernetes APIs under the scope of those objects. To deploy the samples service accounts run the following: + +1. `kubectl apply -f ./Definitions/Silo-ServiceAccount.yaml --namespace ` +2. `kubectl apply -f ./Definitions/Client-ServiceAccount.yaml --namespace ` + +Those definitions create the a Kubernetes ClusterRole with permissions to read for the client, and read/write for the silos and also bind it to the service accounts you just created. You can modify the service account names and the way you create them on your production envinronment but be aware of the permissions required in order for this to work. + +Now you have all the assets deployed to your Kubernetes cluster and all you need is to create the Deployment objects with the following commands: + +1. `kubectl apply -f ./Definitions/Silo.yaml --namespace ` +2. `kubectl apply -f ./Definitions/Gateway.yaml --namespace ` +3. `kubectl apply -f ./Definitions/Client.yaml --namespace ` + +You are all set! You can use commands like `kubectl get pods --namespace ` and you will see the client, silo and gateway pods listed on it. + +To inspect the cluster objects deployed to kubertes with `kubectl get silos --namespace -o yaml` or `kubectl get clusterversions --namespace -o yaml` and that will return Orleans cluster membership objects in YAML (you can change to `-o json` if you like to). + +Enjoy! + + + diff --git a/src/Orleans.Clustering.Kubernetes/ClusteringExtensions.cs b/src/Orleans.Clustering.Kubernetes/ClusteringExtensions.cs index f3efa84..a291509 100644 --- a/src/Orleans.Clustering.Kubernetes/ClusteringExtensions.cs +++ b/src/Orleans.Clustering.Kubernetes/ClusteringExtensions.cs @@ -64,7 +64,10 @@ public static IClientBuilder UseKubeGatewayListProvider(this IClientBuilder buil return builder.ConfigureServices((ctx, services) => { services.AddOptions(); - services.Configure(configureOptions); + if (configureOptions != null) + { + services.Configure(configureOptions); + } KubernetesClientConfiguration config = default; diff --git a/src/Orleans.Clustering.Kubernetes/KubeGatewayListProvider.cs b/src/Orleans.Clustering.Kubernetes/KubeGatewayListProvider.cs index d2ef54a..5d53233 100644 --- a/src/Orleans.Clustering.Kubernetes/KubeGatewayListProvider.cs +++ b/src/Orleans.Clustering.Kubernetes/KubeGatewayListProvider.cs @@ -62,9 +62,10 @@ public async Task> GetGateways() } } - public async Task InitializeGatewayListProvider() + public Task InitializeGatewayListProvider() { - this._namespace = await this.GetNamespace(); + this._namespace = this.GetNamespace(); + return Task.CompletedTask; } private static Uri ConvertToGatewayUri(SiloEntity gateway) @@ -73,29 +74,16 @@ private static Uri ConvertToGatewayUri(SiloEntity gateway) return address.ToGatewayUri(); } - private async ValueTask GetNamespace() + private string GetNamespace() { if (!string.IsNullOrWhiteSpace(this._kubeGatewayOptions.Namespace)) return this._kubeGatewayOptions.Namespace; var namespaceFilePath = Path.Combine(Constants.SERVICE_ACCOUNT_PATH, Constants.SERVICE_ACCOUNT_NAMESPACE_FILENAME); - if (File.Exists(namespaceFilePath)) - { - using var sourceStream = new FileStream(namespaceFilePath, - FileMode.Open, FileAccess.Read, FileShare.Read, - bufferSize: 4096, useAsync: true); - - var sb = new StringBuilder(); + if (!File.Exists(namespaceFilePath)) return Constants.ORLEANS_NAMESPACE; - byte[] buffer = new byte[0x1000]; - int numRead; - while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0) - { - string text = Encoding.Unicode.GetString(buffer, 0, numRead); - sb.Append(text); - } + var ns = File.ReadAllText(namespaceFilePath); - return sb.ToString(); - } + if (!string.IsNullOrWhiteSpace(ns)) return ns; this._logger?.LogWarning( "Namespace file {namespaceFilePath} wasn't found. Are we running in a pod? If you are running unit tests outside a pod, please create the test namespace '{namespace}'.", diff --git a/src/Orleans.Clustering.Kubernetes/KubeMembershipTable.cs b/src/Orleans.Clustering.Kubernetes/KubeMembershipTable.cs index a241ce0..014622d 100644 --- a/src/Orleans.Clustering.Kubernetes/KubeMembershipTable.cs +++ b/src/Orleans.Clustering.Kubernetes/KubeMembershipTable.cs @@ -36,7 +36,8 @@ public KubeMembershipTable(ILoggerFactory loggerFactory, IOptions GetNamespace() + private string GetNamespace() { var namespaceFilePath = Path.Combine(Constants.SERVICE_ACCOUNT_PATH, Constants.SERVICE_ACCOUNT_NAMESPACE_FILENAME); - if (File.Exists(namespaceFilePath)) - { - using var sourceStream = new FileStream(namespaceFilePath, - FileMode.Open, FileAccess.Read, FileShare.Read, - bufferSize: 4096, useAsync: true); + if (!File.Exists(namespaceFilePath)) return Constants.ORLEANS_NAMESPACE; - var sb = new StringBuilder(); + var ns = File.ReadAllText(namespaceFilePath); - byte[] buffer = new byte[0x1000]; - int numRead; - while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0) - { - string text = Encoding.Unicode.GetString(buffer, 0, numRead); - sb.Append(text); - } - - return sb.ToString(); - } + if (!string.IsNullOrWhiteSpace(ns)) return ns; this._logger?.LogWarning( "Namespace file {namespaceFilePath} wasn't found. Are we running in a pod? If you are running unit tests outside a pod, please create the test namespace '{namespace}'.",