Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

New Kubernetes cluster on GCP, Azure or AWS

This guide will walk through the process of adding a new cluster to our terraform configuration.

You can find out more about terraform in Terraform and their documentation.

Cluster Design

This guide will assume you have already followed the guidance in Cluster design considerations to select the appropriate infrastructure.

Prerequisites

AWS
Google Cloud
Azure
Jetstream2
  1. Install kubectl, helm, sops, etc.

    In Setting up your local environment to work on this repo you find instructions on how to setup sops to encrypt and decrypt files.

  2. Install aws

    Verify install and version with aws --version. You should have at least version 2.

  3. Install or upgrade eksctl

    Mac users with homebrew can run brew install eksctl.

    Verify install and version with eksctl version. You should have the latest version of this CLI.

  4. Install jsonnet

    Mac users with homebrew can run brew install jsonnet.

    Verify install and version with jsonnet --version.

Create a new cluster

Setup credentials

AWS
Google Cloud
Azure
Jetstream2

Depending on whether this project is using AWS SSO or not, you can use the following links to figure out how to authenticate to this project from your terminal.

Generate cluster files

We automatically generate the files required to setup a new cluster:

AWS
Google Cloud
Azure
Jetstream2
  • A .jsonnet file for use with eksctl

  • A .tfvars terraform variables file that will setup most of the non EKS infrastructure.

  • The cluster config directory in ./config/cluster/<new-cluster>

  • The cluster.yaml config file

  • The support values file support.values.yaml

  • The the support credentials encrypted file enc-support.values.yaml

You can generate these with:

AWS
Google Cloud
Azure
Jestream2
export CLUSTER_NAME=<cluster-name>
export CLUSTER_REGION=<cluster-region-like ca-central-1>
export ACCOUNT_ID=<the 12 digit aws account id>
deployer generate dedicated-cluster aws --cluster-name=$CLUSTER_NAME --cluster-region=$CLUSTER_REGION --account-id=$ACCOUNT_ID

Create and render an eksctl config file

We use an eksctl config file in YAML to specify how our cluster should be built. Since it can get repetitive, we use jsonnet to declaratively specify this config. You can find the .jsonnet files for the current clusters in the eksctl/ directory.

The previous step should’ve created a baseline .jsonnet file you can modify as you like. The eksctl docs have a reference for all the possible options. You’d want to make sure to change at least the following:

  • Region / Zone - make sure you are creating your cluster in the correct region and verify the suggested zones 1a, 1b, and 1c actually are available in that region.

    # a command to list availability zones, for example
    # ca-central-1 doesn't have 1c, but 1d instead
    aws ec2 describe-availability-zones --region=$CLUSTER_REGION
  • Size of nodes in instancegroups, for both notebook nodes and dask nodes. In particular, make sure you have enough quota to launch these instances in your selected regions.

  • Kubernetes version - older .jsonnet files might be on older versions, but you should pick a newer version when you create a new cluster.

Once you have a .jsonnet file, you can render it into a config file that eksctl can read.

jsonnet $CLUSTER_NAME.jsonnet > $CLUSTER_NAME.eksctl.yaml

Create the cluster

Now you’re ready to create the cluster!

eksctl create cluster --config-file=$CLUSTER_NAME.eksctl.yaml 

This might take a few minutes.

If any errors are reported in the config (there is a schema validation step), fix it in the .jsonnet file, re-render the config, and try again.

Once it is done, you can test access to the new cluster with kubectl, after getting credentials via:

aws eks update-kubeconfig --name=$CLUSTER_NAME --region=$CLUSTER_REGION

kubectl should be able to find your cluster now! kubectl get node should show you at least one core node running.

Specify hubspot_deal_id

We want to match every cluster we deploy to a particular contract that we have to run it for a specific time. We manage contracts on Hubspot, and each contract is associated with a “Deal”. We specify the id of this deal for each Cluster under metadata.2i2c.hubspot_deal_id, and it must be specified when creating the cluster. The new hub request issue should have a Hubspot deal URL, and you can determine the deal ID by either:

  1. Opening the URL, logging into hubspot and looking in the sidebar

  2. Manually just look at the URL - if the URL of the deal looks like https://app-na2.hubspot.com/contacts/242496330/record/0-3/96602996427, the deal ID is the last integer, that comes after 0-3.

Add GPU nodegroup if needed

If this cluster is going to have GPUs, you should edit the generated jsonnet file to include a GPU nodegroups.

Initialising Terraform

Our default terraform state is located centrally in our two-eye-two-see-org GCP project, therefore you must authenticate gcloud to your @2i2c.org account before initialising terraform. The terraform state includes all cloud providers, not just GCP.

gcloud auth application-default login

Then you can change into the terraform subdirectory for the appropriate cloud provider and initialise terraform.

AWS
Google Cloud
Azure
Jetstream2

Our AWS terraform code is now used to deploy supporting infrastructure for the EKS cluster, including:

  • An IAM identity account for use with our CI/CD system

  • EBS volumes for use with jupyterhub-home-nfs

  • Optionally, setup a shared database

  • Optionally, setup user buckets

The steps above will have created a default .tfvars file. This file can either be used as-is or edited to enable the optional features listed above.

Initialise terraform for use with AWS:

cd terraform/aws
terraform init

Creating a new terraform workspace

We use terraform workspaces so that the state of one .tfvars file does not influence another. Create a new workspace with the below command, and again give it the same name as the .tfvars filename, $CLUSTER_NAME.

terraform workspace new $CLUSTER_NAME

Setting up Budget Alerts

Follow the instructions in Setting up Budget Alerts to determine if and how you should setup budget alerts.

You can learn more about our budget alerts in Cloud Billing Budget Alerts.

Plan and Apply Changes

First, make sure you are in the new workspace that you just created.

terraform workspace show

Plan your changes with the terraform plan command, passing the .tfvars file as a variable file.

terraform plan -var-file=projects/$CLUSTER_NAME.tfvars

Check over the output of this command to ensure nothing is being created/deleted that you didn’t expect. Copy-paste the plan into your open Pull Request so a fellow 2i2c engineer can double check it too.

If you’re both satisfied with the plan, merge the Pull Request and apply the changes to deploy the cluster.

terraform apply -var-file=projects/$CLUSTER_NAME.tfvars

Congratulations, you’ve just deployed a new cluster!

Exporting and Encrypting the Cluster Access Credentials

In the previous step, we will have created an IAM user with just enough permissions for automatic deployment of hubs from CI/CD. Since these credentials are checked-in to our git repository and made public, they should have least amount of permissions possible.

To begin deploying and operating hubs on your new cluster, we need to export these credentials, encrypt them using sops, and store them in the secrets directory of the infrastructure repo.

  1. First, make sure you are in the right terraform directory:

    AWS
    Google Cloud
    Azure
    Jetstream2
    cd terraform/aws
  2. Check you are still in the correct terraform workspace

    terraform workspace show

    If you need to change, you can do so as follows

    terraform workspace list  # List all available workspaces
    terraform workspace select WORKSPACE_NAME
  3. Fetch credentials for automatic deployment

    Create the directory if it doesn’t exist already:

    mkdir -p ../../config/clusters/$CLUSTER_NAME
    AWS
    Google Cloud
    Azure
    Jetstream2
    terraform output -raw continuous_deployer_creds > ../../config/clusters/$CLUSTER_NAME/deployer-credentials.secret.json
  4. Then encrypt the key using sops.

    sops --output ../../config/clusters/$CLUSTER_NAME/enc-deployer-credentials.secret.json --encrypt ../../config/clusters/$CLUSTER_NAME/deployer-credentials.secret.json

    This key can now be committed to the infrastructure repo and used to deploy and manage hubs hosted on that cluster.

  5. Double check to make sure that the config/clusters/$CLUSTER_NAME/enc-deployer-credentials.secret.json file is actually encrypted by sops before checking it in to the git repo. Otherwise this can be a serious security leak!

    cat ../../config/clusters/$CLUSTER_NAME/enc-deployer-credentials.secret.json

Create a cluster.yaml file

Create a cluster.yaml file under the config/cluster/$CLUSTER_NAME> folder and populate it with the following info:

AWS
Google Cloud
Azure (kubeconfig)
Azure (Service Principal)
Jetstream2 (kubeconfig)

A cluster.yaml file should already have been generated as part of Generate cluster files.

Commit this file to the repo.

Access

AWS
Google Cloud
Azure
Jetstream2

Grant the deployer’s IAM user cluster access

We need to grant the freshly created deployer IAM user access to the kubernetes cluster.

  1. As this requires passing in some parameters that match the created cluster, we have a terraform output that can give you the exact command to run.

terraform output -raw eksctl_iam_command
  1. Run the eksctl create iamidentitymapping command returned by terraform output. That should give the continuous deployer user access.

The command should look like this:

eksctl create iamidentitymapping \
    --cluster $CLUSTER_NAME \
    --region $CLUSTER_REGION \
    --arn arn:aws:iam::<aws-account-id>:user/hub-continuous-deployer \
    --username hub-continuous-deployer \
    --group system:masters

Test the access by running:

deployer use-cluster-credentials $CLUSTER_NAME

and running:

kubectl get node

It should show you the provisioned node on the cluster if everything works out ok.

Grant cluster access to other users

Find the usernames of the 2i2c engineers on this particular AWS account, and run the following command to give them access using the deprecated system active in parallel to the newer system with access entries:

eksctl create iamidentitymapping \
   --cluster $CLUSTER_NAME \
   --region $CLUSTER_REGION \
   --arn arn:aws:iam::<aws-account-id>:user/<iam-user-name> \
   --username <iam-user-name> \
   --group system:masters

This gives all the users full access to the entire kubernetes cluster. After this step is done, they can fetch local config with:

aws eks update-kubeconfig --name=$CLUSTER_NAME --region=$CLUSTER_REGION

This should eventually be converted to use an IAM Role instead, so we need not give each individual user access, but just grant access to the role - and users can modify them as they wish. It should also eventually be converted to use access entries instead of the legacy system active in parallel.