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¶
Install
kubectl,helm,sops, etc.In Setting up your local environment to work on this repo you find instructions on how to setup
sopsto encrypt and decrypt files.Install
awsVerify install and version with
aws --version. You should have at least version 2.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.Install
jsonnetMac users with homebrew can run
brew install jsonnet.Verify install and version with
jsonnet --version.
Install
kubectl,helm,sops, etc.In Setting up your local environment to work on this repo you find instructions on how to setup
sopsto encrypt and decrypt files.
Install
kubectl,helm,sops, etc.In Setting up your local environment to work on this repo you find instructions on how to setup
sopsto encrypt and decrypt files.
Install
kubectl,helm,sopsand pip installpython-openstackclientandpython-magnumclientIn Setting up your local environment to work on this repo you find instructions on how to setup
sopsto encrypt and decrypt files.
Create a new cluster¶
Setup credentials¶
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.
N/A
N/A
You will need to generate Jetstream2 application credentials that the cli client will use to authenticate against the desired Jetstream2 allocation.
There is a comprehensive guide on how to generate the credentials, and export them as environment variables through sourcing an openrc.sh file. It is important to note that when creating the application credentials you must give them UNRESTRICTED access by ticking the corresponding box and also select all roles available to you in the ROLES box.
Go to https://
js2 .jetstream -cloud .org/ and follow the guide at https:// cvw .cac .cornell .edu /jetstreamapi /cli /openrc, but keep in mind the UNRESTRICTEDpart as that’s not covered in the guide.After exporting the variables in the openrc.sh file, make sure you have access by running:
openstack coe cluster list
Generate cluster files¶
We automatically generate the files required to setup a new cluster:
A
.jsonnetfile for use witheksctlA
.tfvarsterraform variables file that will setup most of the non EKS infrastructure.The cluster config directory in
./config/cluster/<new-cluster>The
cluster.yamlconfig fileThe support values file
support.values.yamlThe the support credentials encrypted file
enc-support.values.yaml
A
.tfvarsfile for use withterraformThe cluster config directory in
./config/cluster/<new-cluster>A sample
cluster.yamlconfig fileThe support values file
support.values.yamlThe the support credentials encrypted file
enc-support.values.yaml
You can generate these with:
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_IDCreate 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_REGIONSize 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
.jsonnetfiles 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.yamlCreate 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_REGIONkubectl should be able to find your cluster now! kubectl get node should show
you at least one core node running.
export CLUSTER_NAME=<cluster-name>
export CLUSTER_REGION=<cluster-region-like ca-central-1>
export PROJECT_ID=<gcp-project-id>deployer generate dedicated-cluster gcp --cluster-name=$CLUSTER_NAME --project-id=$PROJECT_ID --cluster-region=$CLUSTER_REGIONAn automated deployer command doesn’t exist yet, these files need to be manually generated. The minimum inputs this file requires are:
subscription_id: Azure subscription ID to create resources in. Should be the id, rather than display name of the project.resourcegroup_name: The name of the Resource Group to be created by terraform, where the cluster and other resources will be deployed into.global_container_registry_name: The name of an Azure Container Registry to be created by terraform to use for our image. This must be unique across all of Azure. You can use the following Azure CLI command to check your desired name is available:az acr check-name --name ACR_NAME --output tableglobal_storage_account_name: The name of a storage account to be created by terraform to use for Azure File Storage. This must be unique across all of Azure. You can use the following Azure CLI command to check your desired name is available:az storage account check-name --name STORAGE_ACCOUNT_NAME --output tablessh_pub_key: The public half of an SSH key that will be authorised to login to nodes.
See the variables file for other inputs this file can take and their descriptions.
We recommend the following conventions using lowercase:
{CLUSTER_NAME}hubregistryfor container registries{CLUSTER_NAME}hubstoragefor storage accounts
This increases the probability that we won’t take up a namespace that may be required by the Hub Community, for example, in cases where we are deploying to Azure subscriptions not owned/managed by 2i2c.
Example .tfvars file:
subscription_id = "my-awesome-subscription-id"
resourcegroup_name = "my-awesome-resource-group"
global_container_registry_name = "myawesomehubregistry"
global_storage_account_name = "myawesomestorageaccount"
ssh_pub_key = "ssh-rsa my-public-ssh-key"An automated deployer command doesn’t exist yet, these files need to be manually generated. The minimum inputs this file requires are:
prefix: A prefix that will be added to all the cluster-specific resources. Changing this will force the recreation of all resources.notebook_nodes: A list of nodebook nodes that will be created in the cluster. Themachine_typeshould be one of the Jetstream2 flavors.notebook_nodes = { "m3.medium" : { min : 1, max : 100, # 8 CPU, 30 RAM # https://docs.jetstream-cloud.org/general/instance-flavors/#jetstream2-cpu machine_type : "m3.medium", labels = { "hub.jupyter.org/node-purpose" = "user", "k8s.dask.org/node-purpose" = "scheduler", } }, }
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:
Opening the URL, logging into hubspot and looking in the sidebar
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 loginThen you can change into the terraform subdirectory for the appropriate cloud provider and initialise terraform.
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-nfsOptionally, 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 initcd terraform/gcp
terraform initcd terraform/azure
terraform initcd terraform/openstack
terraform initCreating 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_NAMESetting 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 showPlan your changes with the terraform plan command, passing the .tfvars file as a variable file.
terraform plan -var-file=projects/$CLUSTER_NAME.tfvarsCheck 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.tfvarsCongratulations, 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.
First, make sure you are in the right terraform directory:
AWSGoogle CloudAzureJetstream2cd terraform/awscd terraform/gcpcd terraform/azurecd terraform/openstackCheck you are still in the correct terraform workspace
terraform workspace showIf you need to change, you can do so as follows
terraform workspace list # List all available workspaces terraform workspace select WORKSPACE_NAMEFetch credentials for automatic deployment
Create the directory if it doesn’t exist already:
mkdir -p ../../config/clusters/$CLUSTER_NAMEAWSGoogle CloudAzureJetstream2terraform output -raw continuous_deployer_creds > ../../config/clusters/$CLUSTER_NAME/deployer-credentials.secret.jsonterraform output -raw ci_deployer_key > ../../config/clusters/$CLUSTER_NAME/deployer-credentials.secret.jsonterraform output -raw kubeconfig > ../../config/clusters/$CLUSTER_NAME/deployer-credentials.secret.yamlTo access the cluster using kubectl we need to get the kubeconfig with:
openstack coe cluster config <cluster-name> && \ mv ./config ../../config/clusters/$CLUSTER_NAME/deployer-credentials.secret.yamlThis command will generate a file named config in the cwd with the configuration. The --force flag will overwrite this file if it already exists.
Encrypt the kubeconfig file using
sops:sops --output ../../config/clusters/$CLUSTER_NAME/enc-deployer-credentials.secret.yaml --encrypt ../../config/clusters/$CLUSTER_NAME/deployer-credentials.secret.yamlThen encrypt the key using
sops.sops --output ../../config/clusters/$CLUSTER_NAME/enc-deployer-credentials.secret.json --encrypt ../../config/clusters/$CLUSTER_NAME/deployer-credentials.secret.jsonThis key can now be committed to the
infrastructurerepo and used to deploy and manage hubs hosted on that cluster.Double check to make sure that the
config/clusters/$CLUSTER_NAME/enc-deployer-credentials.secret.jsonfile is actually encrypted bysopsbefore 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:
A cluster.yaml file should already have been generated as part of Generate cluster files.
A cluster.yaml file should already have been generated as part of Generate cluster files.
Billing information¶
For projects where we are paying the cloud bill & then passing costs through, you need to fill
in information under gcp.billing.bigquery and set gcp.billing.paid_by_us to true. Partnerships
should be able to tell you if we are doing cloud costs pass through or not.
Going to the Billing Tab on Google Cloud Console
Make sure the correct project is selected in the top bar. You might have to select the ‘All’ tab in the project chooser if you do not see the project right away.
Click ‘Go to billing account’
In the default view (Overview) that opens, you can find the value for
billing_idin the right sidebar, under “Billing Account”. It should be of the formXXXXXX-XXXXXX-XXXXXX.Select “Billing export” on the left navigation bar, and you will find the values for
projectanddatasetunder “Detailed cost usage”.If “Detailed cost usage” is not set up, you should enable it
name: <cluster-name> # This should also match the name of the folder: config/clusters/$CLUSTER_NAME
provider: kubeconfig
kubeconfig:
# The location of the *encrypted* key we exported from terraform
file: enc-deployer-credentials.secret.yamlname: <cluster-name> # This should also match the name of the folder: config/clusters/$CLUSTER_NAME
provider: azure
azure:
# The location of the *encrypted* key we exported from terraform
key: enc-deployer-credentials.secret.json
# The name of the cluster *as it appears in the Azure Portal*! Sometimes our
# terraform code adjusts the contents of the 'name' field, so double check this.
cluster: <cluster-name>
# The name of the resource group the cluster has been deployed into. This is
# the same as the resourcegroup_name variable in the .tfvars file.
resource_group: <resource-group-name>name: <cluster-name> # This should also match the name of the folder: config/clusters/$CLUSTER_NAME
provider: kubeconfig
kubeconfig:
# The location of the *encrypted* key we exported from terraform
file: enc-deployer-credentials.secret.yamlCommit this file to the repo.
Access¶
Grant the deployer’s IAM user cluster access¶
We need to grant the freshly created deployer IAM user access to the kubernetes cluster.
As this requires passing in some parameters that match the created cluster, we have a
terraform outputthat can give you the exact command to run.
terraform output -raw eksctl_iam_commandRun the
eksctl create iamidentitymappingcommand returned byterraform 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:mastersTest the access by running:
deployer use-cluster-credentials $CLUSTER_NAMEand running:
kubectl get nodeIt 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:mastersThis 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_REGIONThis 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.
Test deployer access by running:
deployer use-cluster-credentials $CLUSTER_NAMEand running:
kubectl get nodeIt should show you the provisioned node on the cluster if everything works out ok.
Test deployer access by running:
deployer use-cluster-credentials $CLUSTER_NAMEand running:
kubectl get nodeIt should show you the provisioned node on the cluster if everything works out ok.
Test deployer access by running:
deployer use-cluster-credentials $CLUSTER_NAMEand running:
kubectl get nodeIt should show you the provisioned node on the cluster if everything works out ok.