diff --git a/Kubernetes/.gitignore b/Kubernetes/.gitignore new file mode 100644 index 0000000..3ebd780 --- /dev/null +++ b/Kubernetes/.gitignore @@ -0,0 +1,7 @@ +.devspace + +.terraform +.terraform.lock.hcl +terraform.tfstate* + +app_environment.zbundle diff --git a/Kubernetes/README.md b/Kubernetes/README.md new file mode 100644 index 0000000..b1e9785 --- /dev/null +++ b/Kubernetes/README.md @@ -0,0 +1,79 @@ +# Kubernetes hosted app example + +This example demonstrates how to develop and deploy a Kubernetes hosted application alongside Enthought Edge. + +It is designed to integrate with the authentication, monitoring, logging and scaling tooling available +on Enthought-managed Kubernetes clusters, while retrieving user metadata from the upstream identity provider +(Identity/Keycloak) shared with Edge. + +## Before you begin + +Before starting, ensure you have the following installed: + +* [EDM](https://www.enthought.com/edm/), the Enthought Deployment Manager +* A local Docker installation for building container images and hosting a Kubernetes cluster (for local deployment): + * [Minikube](https://minikube.sigs.k8s.io/docs/start/) or + * [Docker Desktop](https://docs.docker.com/desktop/) +* [DevSpace](https://www.devspace.sh/docs/getting-started/installation) +* [Terraform](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) + +For this example, your `edm.yaml` file should have the public `enthought/free` and `enthought/lgpl` +repositories enabled. + +The example can be deployed and run locally or on a remote Kubernetes cluster. + +### Local deployment + +The local deployment option relies on a local Kubernetes cluster and has been tested with Minikube and with Docker Desktop's built-in Kubernetes feature. + +User metadata is passed to the application via HTTP headers. For the local deployment, we are mocking the headers +by injecting test user metadata into incoming requests via Istio. + +We recommend using Minikube for local development, since it simplifies the setup of Istio and lacks the licensing restrictions of Docker Desktop. + +#### Minikube + +1. Make sure that the Minikube CLI has been installed. +2. Start a Minikube cluster (with Istio) by running `minikube start --memory 4096 --addons="istio-provisioner,istio"` + +> [!NOTE] +> Minikube will automatically try to detect the appropriate driver for your system. If you want to use a specific driver, you can specify it with the `--driver` flag. See the [Minikube documentation](https://minikube.sigs.k8s.io/docs/start/) for more information. We have successfully tested this example with the `docker` driver, `hyper-v` driver on Windows and `hyperkit` driver on MacOS. + +#### Docker Desktop + +For Docker Desktop, you will need to perform the following steps: + +1. Make sure that Docker Desktop has been installed and is running. +2. Enable the built-in Kubernetes feature via Settings -> Kubernetes -> Enable Kubernetes. +3. Install Istio. [Istio's default profile](https://istio.io/latest/docs/setup/install/istioctl/#install-istio-using-the-default-profile) is sufficient for this example. + +### Remote deployment + +For the remote deployment, please contact the DevOps team, who will set up a namespace, networking, +Keycloak configuration and authentication middleware in an appropriate Kubernetes cluster for your use case. + +The team will also guide you through the process of adjusting the configuration of this example to work with the +remote deployment. + +Remote deployments will use the actual user metadata provided by Identity/Keycloak and therefore share a login session with Edge. + +## Quick start + +The following steps will guide you through the process of deploying the example app locally. + +1. Make sure that your Kubenetes context is pointing to the local cluster by running `devspace use context minikube` (or `devspace use context docker-desktop` if you are using Docker Desktop). + +2. Run `devspace run terraform-init` to initialize the Terraform workspace that will deploy the application resources into your local Kubernetes cluster. + +3. **Optionally**, run `devspace run create-edm-devenv` to create a development environment in EDM. This will create a new EDM environment called `edge-kubernetes-app-example` and install the required dependencies. Note that is only meant to provide a development environment for your IDE and is not required for the application to run. + +4. Run `devspace dev` to start the application in development mode. This will build the Docker image, deploy the application and set up a port-forward to access it. +You should now be able to access the application at [http://localhost:8080/k8s/default/example](http://localhost:8080/k8s/default/example). + +You can now start developing your application. The application is set up to sync changes to the source code and automatically reload. Application logs are streamed to the terminal. + +To stop the sync and the application, press `Ctrl+C` in the terminal where `devspace dev` is running. + +## Cleaning up + +To clean up the resources created by the example, run `devspace purge`. diff --git a/Kubernetes/deploy/terraform/deployments/devspace/main.tf b/Kubernetes/deploy/terraform/deployments/devspace/main.tf new file mode 100644 index 0000000..7108d6f --- /dev/null +++ b/Kubernetes/deploy/terraform/deployments/devspace/main.tf @@ -0,0 +1,55 @@ +provider "kubernetes" { + config_path = "~/.kube/config" + config_context = var.kube_context +} + +locals { + app_name = "example" + component_name = "backend" + prefix = "/k8s/default/example/" + service_port = 9000 + container_port = 9000 + + inject_headers = { + "X-Forwarded-Email" = "testuser@example.com" + "X-Forwarded-Groups" = "role:edge-kubernetes-app-example:user" + "X-Forwarded-Preferred-Username" = "testuser@example.com" + "X-Forwarded-User" = "abababab-abab-abab-abab-abababababab" + "X-Forwarded-Display-Name" = "Test User" + } +} + +module "istio_inject_headers" { + count = var.local ? 1 : 0 + + source = "../../modules/istio-inject-headers" + + app_name = local.app_name + component_name = local.component_name + container_port = local.container_port + service_port = local.service_port + + prefix = local.prefix + + namespace = var.namespace + + inject_headers = local.inject_headers +} + +module "app" { + source = "../../modules/app" + + use_nodepool = !var.local + + app_name = local.app_name + component_name = local.component_name + container_port = local.container_port + service_port = local.service_port + + prefix = local.prefix + + namespace = var.namespace + + image_name = var.image_name + image_tag = var.image_tag +} diff --git a/Kubernetes/deploy/terraform/deployments/devspace/terraform.tf b/Kubernetes/deploy/terraform/deployments/devspace/terraform.tf new file mode 100644 index 0000000..1a9b345 --- /dev/null +++ b/Kubernetes/deploy/terraform/deployments/devspace/terraform.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.27" + } + } +} diff --git a/Kubernetes/deploy/terraform/deployments/devspace/variables.tf b/Kubernetes/deploy/terraform/deployments/devspace/variables.tf new file mode 100644 index 0000000..70bb56b --- /dev/null +++ b/Kubernetes/deploy/terraform/deployments/devspace/variables.tf @@ -0,0 +1,22 @@ +variable "kube_context" { + type = string +} + +variable "namespace" { + type = string +} + +variable "image_tag" { + type = string + default = "latest" +} + +variable "image_name" { + type = string + default = "edge-kubernetes-app-example" +} + +variable "local" { + type = bool + default = true +} diff --git a/Kubernetes/deploy/terraform/modules/app/main.tf b/Kubernetes/deploy/terraform/modules/app/main.tf new file mode 100644 index 0000000..65758e7 --- /dev/null +++ b/Kubernetes/deploy/terraform/modules/app/main.tf @@ -0,0 +1,127 @@ +data "kubernetes_namespace_v1" "this" { + metadata { + name = var.namespace + } +} + +resource "kubernetes_deployment_v1" "this" { + metadata { + name = "${var.app_name}-${var.component_name}" + namespace = data.kubernetes_namespace_v1.this.metadata.0.name + labels = { + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/component" = var.component_name + } + } + + lifecycle { + ignore_changes = [ + metadata[0].annotations["devspace.sh/replicas"] + ] + } + + wait_for_rollout = false + + spec { + replicas = 1 + selector { + match_labels = { + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/component" = var.component_name + } + } + template { + metadata { + labels = { + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/component" = var.component_name + } + } + spec { + container { + name = "main" + image = "${var.image_name}:${var.image_tag}" + image_pull_policy = "IfNotPresent" + + env { + name = "PORT" + value = var.container_port + } + + env { + name = "PREFIX" + value = var.prefix + } + + resources { + requests = { + cpu = "100m" + memory = "256Mi" + } + limits = { + cpu = "100m" + memory = "256Mi" + } + } + + port { + container_port = var.container_port + name = "http" + protocol = "TCP" + } + } + + dynamic "toleration" { + for_each = var.use_nodepool ? [1] : [] + content { + key = "enthought.com/node-pool-purpose" + operator = "Equal" + value = var.namespace + effect = "NoSchedule" + } + } + + dynamic "affinity" { + for_each = var.use_nodepool ? [1] : [] + content { + node_affinity { + required_during_scheduling_ignored_during_execution { + node_selector_term { + match_expressions { + key = "enthought.com/node-pool-purpose" + operator = "In" + values = [var.namespace] + } + match_expressions { + key = "karpenter.sh/capacity-type" + operator = "In" + values = ["on-demand"] + } + } + } + } + } + } + } + } + } +} + +resource "kubernetes_service_v1" "this" { + metadata { + name = "${var.app_name}-${var.component_name}" + namespace = data.kubernetes_namespace_v1.this.metadata.0.name + } + + spec { + selector = { + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/component" = var.component_name + } + port { + port = var.service_port + target_port = var.container_port + protocol = "TCP" + } + } +} diff --git a/Kubernetes/deploy/terraform/modules/app/terraform.tf b/Kubernetes/deploy/terraform/modules/app/terraform.tf new file mode 100644 index 0000000..1a9b345 --- /dev/null +++ b/Kubernetes/deploy/terraform/modules/app/terraform.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.27" + } + } +} diff --git a/Kubernetes/deploy/terraform/modules/app/variables.tf b/Kubernetes/deploy/terraform/modules/app/variables.tf new file mode 100644 index 0000000..195970c --- /dev/null +++ b/Kubernetes/deploy/terraform/modules/app/variables.tf @@ -0,0 +1,35 @@ +variable "namespace" { + type = string +} + +variable "image_tag" { + type = string +} + +variable "image_name" { + type = string +} + +variable "prefix" { + type = string +} + +variable "use_nodepool" { + type = bool +} + +variable "app_name" { + type = string +} + +variable "component_name" { + type = string +} + +variable "service_port" { + type = number +} + +variable "container_port" { + type = number +} diff --git a/Kubernetes/deploy/terraform/modules/istio-inject-headers/istio.tf b/Kubernetes/deploy/terraform/modules/istio-inject-headers/istio.tf new file mode 100644 index 0000000..aefe4ab --- /dev/null +++ b/Kubernetes/deploy/terraform/modules/istio-inject-headers/istio.tf @@ -0,0 +1,158 @@ +resource "kubernetes_manifest" "gateway_localhost" { + manifest = { + apiVersion = "networking.istio.io/v1beta1" + kind = "Gateway" + metadata = { + name = "localhost" + namespace = "istio-system" + } + + spec = { + selector = { + istio = "ingressgateway" + } + servers = [ + { + hosts = [ + "*" + ] + port = { + name = "http" + number = 80 + protocol = "HTTP" + } + }, + ] + } + } +} + +resource "kubernetes_manifest" "telemetry_deployment" { + manifest = { + apiVersion = "telemetry.istio.io/v1alpha1" + kind = "Telemetry" + metadata = { + name = "deployment" + namespace = data.kubernetes_namespace_v1.this.metadata.0.name + } + spec = { + accessLogging = [ + { + providers = [ + { + name = "envoy" + } + ] + filter = { + expression = "response.code >= 400" + } + } + ] + } + } +} + +resource "kubernetes_manifest" "authorization_policy_allow_nothing" { + manifest = { + apiVersion = "security.istio.io/v1beta1" + kind = "AuthorizationPolicy" + metadata = { + name = "allow-nothing" + namespace = data.kubernetes_namespace_v1.this.metadata.0.name + } + spec = {} + } +} + +resource "kubernetes_manifest" "authorization_policy_backend" { + manifest = { + apiVersion = "security.istio.io/v1beta1" + kind = "AuthorizationPolicy" + metadata = { + name = "${var.app_name}-${var.component_name}" + namespace = data.kubernetes_namespace_v1.this.metadata.0.name + } + spec = { + selector = { + matchLabels = { + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/component" = var.component_name + } + } + action = "ALLOW" + rules = [ + { + from = [ + { + source = { + principals = [ + "cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account" + ] + } + } + ] + to = [ + { + operation = { + ports = [ + var.container_port + ] + } + } + ] + } + ] + } + } +} + + +resource "kubernetes_manifest" "virtualservice" { + manifest = { + apiVersion = "networking.istio.io/v1beta1" + kind = "VirtualService" + metadata = { + name = var.app_name + namespace = data.kubernetes_namespace_v1.this.metadata.0.name + } + spec = { + gateways = [ + "${kubernetes_manifest.gateway_localhost.manifest.metadata.namespace}/${kubernetes_manifest.gateway_localhost.manifest.metadata.name}" + ] + hosts = [ + "*" + ] + http = [ + { + match = [ + { + uri = { + prefix = trimsuffix(var.prefix, "/") + } + } + ] + headers = { + request = { + set = merge( + { + "X-Forwarded-For" = "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%" + }, + { for k, v in var.inject_headers : k => v } + ) + } + } + route = [ + { + destination = { + host = "${var.app_name}-${var.component_name}.${data.kubernetes_namespace_v1.this.metadata.0.name}.svc.cluster.local" + port = { + number = var.service_port + } + } + } + ] + } + ] + } + } +} diff --git a/Kubernetes/deploy/terraform/modules/istio-inject-headers/main.tf b/Kubernetes/deploy/terraform/modules/istio-inject-headers/main.tf new file mode 100644 index 0000000..1f3556b --- /dev/null +++ b/Kubernetes/deploy/terraform/modules/istio-inject-headers/main.tf @@ -0,0 +1,27 @@ +# resource "data.kubernetes_namespace_v1" "this" { +# metadata { +# name = var.namespace +# labels = { +# istio-injection = "enabled" +# } +# } +# } + +data "kubernetes_namespace_v1" "this" { + metadata { + name = var.namespace + } +} + +resource "kubernetes_labels" "this" { + api_version = "v1" + kind = "Namespace" + + metadata { + name = data.kubernetes_namespace_v1.this.metadata.0.name + } + + labels = { + istio-injection = "enabled" + } +} diff --git a/Kubernetes/deploy/terraform/modules/istio-inject-headers/terraform.tf b/Kubernetes/deploy/terraform/modules/istio-inject-headers/terraform.tf new file mode 100644 index 0000000..1a9b345 --- /dev/null +++ b/Kubernetes/deploy/terraform/modules/istio-inject-headers/terraform.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.27" + } + } +} diff --git a/Kubernetes/deploy/terraform/modules/istio-inject-headers/variables.tf b/Kubernetes/deploy/terraform/modules/istio-inject-headers/variables.tf new file mode 100644 index 0000000..dc808b4 --- /dev/null +++ b/Kubernetes/deploy/terraform/modules/istio-inject-headers/variables.tf @@ -0,0 +1,28 @@ +variable "prefix" { + type = string +} + +variable "app_name" { + type = string +} + +variable "component_name" { + type = string +} + +variable "service_port" { + type = number +} + +variable "container_port" { + type = number +} + +variable "namespace" { + type = string +} + +variable "inject_headers" { + type = map(string) + default = {} +} diff --git a/Kubernetes/devspace.yaml b/Kubernetes/devspace.yaml new file mode 100644 index 0000000..829e57f --- /dev/null +++ b/Kubernetes/devspace.yaml @@ -0,0 +1,137 @@ +version: v2beta1 +name: edge-kubernetes-app-example + +vars: + # the AWS profile is only relevant for remote deployments and can be ignored when + # using Minikube or Docker Desktop locally + DEVSPACE_AWS_PROFILE: + source: env + default: PlaceholderProfile-000000000000 + DEVSPACE_IMAGE_PREFIX: + source: env + default: "" + DEVSPACE_FLAGS: "-n ${DEVSPACE_NAME}" + EDM_APP_DEPENDENCIES: "flask setuptools gunicorn" + +localRegistry: + enabled: false + +pipelines: + build: + flags: + - name: rebuild-zbundle + run: |- + build_images --all + dev: + flags: + - name: rebuild-zbundle + run: |- + build_images --all + create_deployments --all + start_dev --all + purge: + run: |- + stop_dev --all + purge_deployments --all + +hooks: + - name: "pre-deploy-hook" + command: |- + $!( + if [ ${DEVSPACE_CONTEXT} != "docker-desktop" ] && [ ${DEVSPACE_CONTEXT} != "minikube" ]; then + echo 'aws sts get-caller-identity --profile ${DEVSPACE_AWS_PROFILE} >/dev/null 2>&1 || aws sso login' + else + echo '' + fi + ) + events: ["before:deploy"] + - name: "pre-image-build-hook" + command: |- + $!( + if ! [ -f ./src/app_environment.zbundle ] || [ $(get_flag "rebuild-zbundle") == "true" ]; then + echo 'edm bundle generate -i --version 3.8 --platform rh7-x86_64 -m 2.0 -f ./src/app_environment.zbundle ${EDM_APP_DEPENDENCIES}' + else + echo '' + fi + if [ ${DEVSPACE_CONTEXT} != "docker-desktop" ] && [ ${DEVSPACE_CONTEXT} != "minikube" ]; then + echo 'aws sts get-caller-identity --profile ${DEVSPACE_AWS_PROFILE} >/dev/null 2>&1 || aws sso login' + echo 'aws ecr get-login-password --profile ${DEVSPACE_AWS_PROFILE} | docker login --username AWS --password-stdin ${DEVSPACE_AWS_ECR_HOST}' + else + echo '' + fi + ) + events: ["before:build"] + +images: + edge-kubernetes-app-example: + image: ${DEVSPACE_IMAGE_PREFIX}edge-kubernetes-app-example + dockerfile: ./docker/Dockerfile + context: ./src + edge-kubernetes-app-example-dev: + image: ${DEVSPACE_IMAGE_PREFIX}edge-kubernetes-app-example-dev + dockerfile: ./docker/Dockerfile.devspace + context: ./src + +commands: + terraform-init: |- + cd deploy/terraform/deployments/devspace + terraform init + create-edm-devenv: |- + edm env create ${DEVSPACE_NAME} --version 3.8 + edm install -e ${DEVSPACE_NAME} -y ${EDM_APP_DEPENDENCIES} + +functions: + create_deployments: |- + cd deploy/terraform/deployments/devspace + export TF_VAR_namespace=${DEVSPACE_NAMESPACE} + export TF_VAR_kube_context=${DEVSPACE_CONTEXT} + export TF_VAR_image_name=$(get_image --only=image edge-kubernetes-app-example) + export TF_VAR_image_tag=$(get_image --only=tag edge-kubernetes-app-example) + export TF_VAR_local=$( [ ${DEVSPACE_CONTEXT} == "docker-desktop" ] || [ ${DEVSPACE_CONTEXT} == "minikube" ] && echo "true" || echo "false" ) + terraform apply -auto-approve + + purge_deployments: |- + cd deploy/terraform/deployments/devspace + export TF_VAR_namespace=${DEVSPACE_NAMESPACE} + export TF_VAR_kube_context=${DEVSPACE_CONTEXT} + terraform destroy -auto-approve + +dev: + edge-kubernetes-app-example: + labelSelector: + "app.kubernetes.io/name": "example" + "app.kubernetes.io/component": "backend" + devImage: ${runtime.images.edge-kubernetes-app-example-dev.image}:${runtime.images.edge-kubernetes-app-example-dev.tag} + command: ["/bin/sh", "-c"] + args: ["/home/app/startup-script.sh --reload"] + logs: {} + sync: + - path: src/startup-script.sh:/home/app/startup-script.sh + file: true + startContainer: true + disableDownload: true + printLogs: true + onUpload: + exec: + - command: |- + chmod +x /home/app/startup-script.sh + restartContainer: true + - path: src/main.py:/home/app/main.py + file: true + startContainer: true + disableDownload: true + printLogs: true + istio-ingressgateway: |- + $( + if [ ${DEVSPACE_CONTEXT} != "docker-desktop" ] && [ ${DEVSPACE_CONTEXT} != "minikube" ]; then + echo "null" + else + output=' + labelSelector: + istio: ingressgateway + namespace: istio-system + ports: + - port: "8080:8080"' + echo "$output" + fi + ) diff --git a/Kubernetes/docker/Dockerfile b/Kubernetes/docker/Dockerfile new file mode 100644 index 0000000..528aa2f --- /dev/null +++ b/Kubernetes/docker/Dockerfile @@ -0,0 +1,49 @@ +# This is the Dockerfile for the Enthought Edge Kubernetes app example. +# +# We use "edm-rockylinux-8" as the base image. This is a Rockylinux-8 based +# image which includes EDM. +# +# EDM dependencies for the app are brought in via a .zbundle file. This avoids +# the need to pass your EDM token and/or a custom edm.yaml into the Dockerfile. +# +# We perform a two-stage build, to avoid including the .zbundle in the layers +# of the published image. + +# First stage + +ARG BASE_IMAGE=quay.io/enthought/edm-rockylinux-8:latest + +FROM $BASE_IMAGE AS stage_one + +ARG EDGE_BUNDLE=app_environment.zbundle +COPY $EDGE_BUNDLE /tmp/app_environment.zbundle + +RUN adduser app +USER app +WORKDIR /home/app + +# Create a default EDM environment using the enthought_edge bundle +RUN edm env import -f /tmp/app_environment.zbundle edm && edm cache purge --yes + + +# Second stage + +FROM $BASE_IMAGE AS stage_two + +LABEL source="https://github.com/enthought/edge-examples/blob/main/Kubernetes/Dockerfile" + +RUN adduser app +USER app +WORKDIR /home/app + +COPY --from=stage_one --chown=app /home/app/.edm /home/app/.edm + +# Make any global changes (yum install, e.g.) in the second stage. +# Don't change the user, and in particular don't make the user "root". + +# Copy startup script and application. +COPY --chown=app ./startup-script.sh /home/app/startup-script.sh +RUN chmod +x /home/app/startup-script.sh +COPY --chown=app ./main.py /home/app/main.py + +CMD ["/home/app/startup-script.sh"] diff --git a/Kubernetes/docker/Dockerfile.devspace b/Kubernetes/docker/Dockerfile.devspace new file mode 100644 index 0000000..2a5cb29 --- /dev/null +++ b/Kubernetes/docker/Dockerfile.devspace @@ -0,0 +1,43 @@ +# This is the DevSpace Dockerfile for the Enthought Edge Kubernetes app example. +# Unlike the main Dockerfile, this does not copy any application files into the +# image, since Devspace will sync those from the local filesystem at runtime. +# +# We use "edm-rockylinux-8" as the base image. This is a Rockylinux-8 based +# image which includes EDM. +# +# EDM dependencies for the app are brought in via a .zbundle file. This avoids +# the need to pass your EDM token and/or a custom edm.yaml into the Dockerfile. +# +# We perform a two-stage build, to avoid including the .zbundle in the layers +# of the published image. + +# First stage + +ARG BASE_IMAGE=quay.io/enthought/edm-rockylinux-8:latest + +FROM $BASE_IMAGE AS stage_one + +ARG EDGE_BUNDLE=app_environment.zbundle +COPY $EDGE_BUNDLE /tmp/app_environment.zbundle + +RUN adduser app +USER app +WORKDIR /home/app + +# Create a default EDM environment using the enthought_edge bundle +RUN edm env import -f /tmp/app_environment.zbundle edm && edm cache purge --yes + + +# Second stage + +FROM $BASE_IMAGE AS stage_two + +LABEL source="https://github.com/enthought/edge-examples/blob/main/Kubernetes/Dockerfile.devspace" + +RUN adduser app && mkdir /.devspace && chown app:app /.devspace +USER app +WORKDIR /home/app + +COPY --from=stage_one --chown=app /home/app/.edm /home/app/.edm + +CMD ["sleep", "infinity"] diff --git a/Kubernetes/src/main.py b/Kubernetes/src/main.py new file mode 100644 index 0000000..7f2b044 --- /dev/null +++ b/Kubernetes/src/main.py @@ -0,0 +1,56 @@ +# Enthought product code +# +# (C) Copyright 2010-2024 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This file and its contents are confidential information and NOT open source. +# Distribution is prohibited. + +""" + Example Flask application. +""" + +import os +from flask import Flask, render_template_string, request + +PREFIX = os.environ.get("PREFIX", "/") + +app = Flask(__name__) + +@app.get(PREFIX) +def root(): + headers = dict(request.headers) + + html = """ + + + Example Flask application + + + +

Hello {{ headers.get('X-Forwarded-Display-Name', 'Unknown') }}!

+

Request Headers

+ + + + + + {% for header, value in headers.items() %} + + + + + {% endfor %} +
HeaderValue
{{ header }}{{ value }}
+ + + """ + + return render_template_string(html, headers=headers) diff --git a/Kubernetes/src/startup-script.sh b/Kubernetes/src/startup-script.sh new file mode 100644 index 0000000..1ce29a8 --- /dev/null +++ b/Kubernetes/src/startup-script.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e + +exec edm run -- gunicorn main:app -b 0.0.0.0:9000 -w 1 --access-logfile - --error-logfile - "$@"