Skip to content

AWS EKS Deployment

This is an example deployment of CogStack in AWS. It will create publically accessible services, so is not suitable for production deployment.

The recommended deployment in AWS is based on using Kubernetes through AWS EKS.

This example will create a AWS EKS cluster, setup any necessary config, deploy CogStack to the cluster, and test that it is available.

Usage

Deployment through terraform is carried out through two terraform commands, to handle the sequencing issues between making a k8s cluster and using it in AWS.

Requirements

  • Terraform - Install Terraform
  • AWS Credentials for an account that can create and destroy resources.

1. Get the configuration files

All you need to do is get the Terraform files that have been preconfigured for this example (the ZIP contains every deployment-examples tree; use the aws-kubernetes folder for this guide).

Download all deployment examples (ZIP)

Alternatively you can view the file contents here:

eks-cluster terraform files

This terraform configuration will create a new AWS EKS cluster.

# Portions of this code adapted from tha Amazon AWS terraform-aws-eks example module (Apache 2.0):
# https://github.com/terraform-aws-modules/terraform-aws-eks/blob/v21.0.4/examples/eks-auto-mode/README.md#module_eks

data "aws_availability_zones" "available" {
  # Exclude local zones
  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required"]
  }
}

locals {
  name               = "ex-${basename(path.cwd)}"
  kubernetes_version = "1.33"
  region             = "eu-west-1"

  vpc_cidr = "10.0.0.0/16"
  azs      = slice(data.aws_availability_zones.available.names, 0, 3)

  tags = {
    Test       = local.name
    GithubRepo = "cogstack-devops"
    GithubOrg  = "CogStack"
  }
}

################################################################################
# EKS Module
################################################################################

module "eks" {
  source                 = "terraform-aws-modules/eks/aws"
  version                = "21.0.4"
  name                   = local.name
  kubernetes_version     = local.kubernetes_version
  endpoint_public_access = true

  enable_cluster_creator_admin_permissions = true

  compute_config = {
    enabled    = true
    node_pools = ["general-purpose"]
  }

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  tags = local.tags
}

################################################################################
# Supporting Resources
################################################################################

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 6.0"

  name = local.name
  cidr = local.vpc_cidr

  azs             = local.azs
  private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
  public_subnets  = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)]
  intra_subnets   = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 52)]

  enable_nat_gateway = true
  single_nat_gateway = true

  public_subnet_tags = {
    "kubernetes.io/role/elb" = 1
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = 1
  }

  tags = local.tags
}
resource "null_resource" "copy_kubeconfig" {
  depends_on = [module.eks, module.vpc]

  provisioner "local-exec" {
    # Extract the kubeconfig file using the AWS CLI. Save it as a local file 
    command = <<EOT
aws eks update-kubeconfig --name ${module.eks.cluster_name} --kubeconfig ${local.kubeconfig_file}
EOT
  }
}
################################################################################
# Cluster
################################################################################

output "cluster_arn" {
  description = "The Amazon Resource Name (ARN) of the cluster"
  value       = module.eks.cluster_arn
}

output "cluster_certificate_authority_data" {
  description = "Base64 encoded certificate data required to communicate with the cluster"
  value       = module.eks.cluster_certificate_authority_data
}

output "cluster_endpoint" {
  description = "Endpoint for your Kubernetes API server"
  value       = module.eks.cluster_endpoint
}

output "cluster_id" {
  description = "The ID of the EKS cluster. Note: currently a value is returned only for local EKS clusters created on Outposts"
  value       = module.eks.cluster_id
}

output "cluster_name" {
  description = "The name of the EKS cluster"
  value       = module.eks.cluster_name
}

### Custom outputs

output "kubeconfig_file" {
  value       = abspath(local.kubeconfig_file)
  description = "Path to the generated KUBECONFIG file used to connect to kubernetes"
}
terraform {
  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0.0"
    }
  }
}
provider "aws" {
}
locals {
  kubeconfig_file = "${path.module}/.build/aws-kubeconfig.yaml"
}

kubernetes-deployment terraform files

This terraform configuration will use the helm plugin to run services in kubernetes.

provider "helm" {
  kubernetes = {
    config_path = var.kubeconfig_file
  }
}

provider "kubernetes" {
  config_path = var.kubeconfig_file
}

resource "kubernetes_ingress_class" "application_load_balancer" {
  # An Application Load Balancer  is required in order to create public URLs. 
  # The load balancer controller creates instances of the ALB when it sees ingress objects with the right annotations.
  metadata {
    name = "alb"
    labels = {
      "app.kubernetes.io/name" = "LoadBalancerController"
    }
  }
  spec {
    controller = "eks.amazonaws.com/alb"
  }
}


locals {
  services = {
    medcat_service = {
      path_prefix = "medcat-service"
    }
  }
}
module "cogstack_helm_services" {
  depends_on            = [kubernetes_ingress_class.application_load_balancer]
source                = "github.com/CogStack/cogstack-platform-toolkit//deployment/terraform/modules/cogstack-helm-services?ref=terraform-modules-v0.1.0"
  medcat_service_values = <<EOT
ingress:
    annotations:
        alb.ingress.kubernetes.io/scheme: internet-facing
        alb.ingress.kubernetes.io/target-type: ip
        alb.ingress.kubernetes.io/healthcheck-path: /api/health/live
    className: alb
    http:
    - paths:
        - path: /${local.services.medcat_service.path_prefix}
          pathType: Prefix
EOT
}
locals {
  medcat_ingress_address = module.cogstack_helm_services.service_urls.medcat_service.ip_address
}
output "service_urls" {
  value = {
    medcat_service = {
      example_curl = "curl http://${local.medcat_ingress_address}/${local.services.medcat_service.path_prefix}/api/info"
      example_ui   = "http://${local.medcat_ingress_address}/${local.services.medcat_service.path_prefix}/docs"
    }
  }
  description = "Public URls to call services on"
}
variable "kubeconfig_file" {
  description = "Path to the generated KUBECONFIG file used to connect to kubernetes"
}

2. Add required secrets for your environment

This readme uses environment variables for access:

  1. See the .env.example file for the required details.
  2. Create a file .env with those fields set for your account.
  3. Execute source .env to set those environment variables

If desired, see the official documentation for other ways to provide AWS credentials https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration

3. Run Terraform

Terraform is run on two modules for AWS, so we will run one terraform apply in one folder, then another terraform apply in a second folder.

Initial provisioning takes around 15 minutes.

# Set AWS credentials 
source .env

# Create AWS EKS infra
cd eks-cluster
terraform init
terraform apply --auto-approve

AWS_KUBECONFIG=$(terraform output -raw kubeconfig_file)

# Deploy services to kubernetes
cd ../kubernetes-deployment
export TF_VAR_kubeconfig_file=$AWS_KUBECONFIG
terraform init
terraform apply --auto-approve

4. Accessing the CogStack Platform

Once the deployment is complete and all services are running, you can access the CogStack platform and its components using the following URLs:

terraform output service_urls

Optional - Destroy

You can destroy the infra to save costs when it wont be used for a long time.

Do note that there is an initial cost every time the EKS infrastructure is created, looks to be around $0.50 at time of writing.

cd ../kubernetes-deployment
terraform destroy

cd ../eks-cluster
terraform destroy

Optionally use the K8s cluster as normal with the CLI

After setting up the cluster, it is possible to interact directly with it using the kubectl CLI

The requirement is to get the KUBECONFIG file created by the terraform apply.

# Get KUBECONFIG
cd eks-cluster
AWS_KUBECONFIG=$(terraform output -raw kubeconfig_file)

# SET KUBECONFIG
export KUBECONFIG=${AWS_KUBECONFIG}

Note - alternatively you could use the AWS CLI to set your kubeconfig using aws eks update-kubeconfig --name $(terraform output -raw cluster_name).

You can then interact with kubernetes via the CLI

# Run Medcat service
helm install my-medcat oci://registry-1.docker.io/cogstacksystems/medcat-service-helm --wait --timeout 10m0s

# Create the ingress
kubectl apply -f resources/ingress-medcat-service.yaml
# Find public url
kubectl get ingress