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

Add new "external" app example #90

Merged
merged 20 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
7 changes: 7 additions & 0 deletions Kubernetes/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.devspace

.terraform
.terraform.lock.hcl
terraform.tfstate*

app_environment.zbundle
79 changes: 79 additions & 0 deletions Kubernetes/README.md
Original file line number Diff line number Diff line change
@@ -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`.
55 changes: 55 additions & 0 deletions Kubernetes/deploy/terraform/deployments/devspace/main.tf
Original file line number Diff line number Diff line change
@@ -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" = "[email protected]"
"X-Forwarded-Groups" = "role:edge-kubernetes-app-example:user"
"X-Forwarded-Preferred-Username" = "[email protected]"
"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
}
10 changes: 10 additions & 0 deletions Kubernetes/deploy/terraform/deployments/devspace/terraform.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.0"

required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.27"
}
}
}
22 changes: 22 additions & 0 deletions Kubernetes/deploy/terraform/deployments/devspace/variables.tf
Original file line number Diff line number Diff line change
@@ -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
}
127 changes: 127 additions & 0 deletions Kubernetes/deploy/terraform/modules/app/main.tf
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
10 changes: 10 additions & 0 deletions Kubernetes/deploy/terraform/modules/app/terraform.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.0"

required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.27"
}
}
}
35 changes: 35 additions & 0 deletions Kubernetes/deploy/terraform/modules/app/variables.tf
Original file line number Diff line number Diff line change
@@ -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
}
Loading
Loading