Introduction

Developers are often overwhelmed by infrastructure configuration such as Terraform, or learning the intricacies of a cloud provider like AWS, as well as needing to learn how to operate and deploy to a container orchestrator with complex YAML manifests such as Kubernetes. Score and Humanitec help solve this issue with workload-centric development.

With workload-centric development developers only need to write a simple Workload Specification with Score. This outlines the configuration schema of the Workload, including dependent resources and other properties. Humanitec can use this schema to contextually inject values depending on the environment.

Humanitec also gives platform engineers more control over how infrastructure and workloads are deployed and where, as well as creating Role Based Authentication (RBAC) to partition the business into logical segments where developers work together.

Overview

In this article, we will focus on the developer workflow, showing how to write a simple Workload Specification file (Score) and deploy it to two different environments. We will also illustrate how this Workload uses placeholders so a single spec can be used for all environments.

Prerequisites

Before starting this tutorial, you’ll need:

  • A Humanitec account (if you don’t have an account yet, explore the full functionality of Humanitec and test it 45 days for free)
  • score-humanitec installed on your computer.
  • A POSIX-Compliant Bash terminal (otherwise you may need to slightly change the commands below)
  • curl
  • jq

Step 1: Create the working directory

Create your working directory and go into it.

mkdir ~/score-humanitec-tutorial ; cd ~/score-humanitec-tutorial
Copy

Step 2: Populate config values

Populate the Humanitec configuration variables for your deployment, these environment variables will be used in all the commands that interact with Humanitec in your shell.

In order to get the Humanitec App and Token, go into the UI and click here where it says Organization settings:

Then click on Generate new token under API Tokens:

The name of the Organization in our case above is “happy-learning”. In your case, it will be whatever you set up when you sign up for your Humanitec free trial.

Fill in the information in the variables below and export them with this commands on your terminal:

export HUMANITEC_APP="score-humanitec-tutorial"
Copy
export HUMANITEC_TOKEN="enter your Humanitec token here"
Copy
export HUMANITEC_ORG="enter your Humanitec org here"
Copy

Step 3: Create a Humanitec App

Apps are how Humanitec groups your Workloads. If you have multiple Workloads that need to interact with one another it makes sense to deploy them within the same app. An App would typically be created by a platform engineer and handed down to a developer to work with, but in our demo environment, we will create it ourselves.

Create a Humanitec App:

curl -sw '%{http_code}' \
--request POST "https://api.humanitec.io/orgs/${HUMANITEC_ORG}/apps" \
--header "Authorization: Bearer ${HUMANITEC_TOKEN}" \
--header "Content-Type: application/json" \
--data-raw '{
  "id": "'"${HUMANITEC_APP}"'",
  "name": "'"${HUMANITEC_APP}"'"
}' | jq
Copy

A successful output should look something like this:

{
  "id": "score-humanitec-tutorial",
  "name": "score-humanitec-tutorial",
  "created_at": "2023-01-19T12:40:34.730534873Z",
  "created_by": "s-781798f9-e2e6-456e-814c-768ea737ebc6",
  "envs": [
    {
      "id": "development",
      "name": "Development",
      "type": "development"
    }
  ]
}
201
Copy

Step 4: Create a value for the workload

Shared app values are very useful to store secrets and configuration values that can be referenced by your workloads with Placeholders.

In this step, we will create a value called greeting_name.

In the Humanitec UI, go into the app you just created and click on App settings:

Click on Add Variables

Add a key named greeting_name and insert any value of your choice. Click on Add when you are done.

Step 5: Create workload Spec (Score) file

Developers usually need to deal with Kubernetes manifests and sometimes infrastructure configuration. The advantage with this approach is that you define everything simply with one Workload Specification (Score).

Generate the humanitec.score.yaml file:

cat <<- "EOF" > humanitec.score.yaml
apiVersion: score.dev/v1b1
metadata:
  name: backend
containers:
  backend:
    image: postgres:alpine
    command: ["/bin/sh"]
    args: ["-c", "sleep 5; while psql $$CONNECTION_STRING -c \"SELECT version()\"; do echo : Hey $${GREETING_NAME}, connecting to DB $${CONNECTION_STRING} was successful!; sleep 10; done"]
    variables:
      CONNECTION_STRING: postgresql://${resources.psql-db.username}:${resources.psql-db.password}@${resources.psql-db.host}:${resources.psql-db.port}/${resources.psql-db.name}
      GREETING_NAME: ${resources.env.greeting_name} 
resources:
  env:
    type: environment
    properties:
      greeting_name:
        type: string
        default: Jimmy
  psql-db:
    type: postgres
    properties:
      host:
        default: psql-db
      port:
        default: 5432
      name:
        default: psql-db
      username:
        default: jimmy
      password:
        default: defaultpass
EOF
Copy

humanitec.score.yaml

You will notice we added some defaults for our resource psql-db. These will not be used by Humanitec though. These are only used when translating this Workload for other platforms like Helm or docker-compose.

In fact, you could even rewrite the above to this and it would still work just the same.

cat <<- "EOF" > humanitec-nodefaults.score.yaml
apiVersion: score.dev/v1b1
metadata:
  name: backend
containers:
  backend:
    image: postgres:alpine
    command: ["/bin/sh"]
    args: ["-c", "sleep 5; while psql $$CONNECTION_STRING -c \"SELECT version()\"; do echo : Hey $${GREETING_NAME}, connecting to DB $${CONNECTION_STRING} was successful!; sleep 10; done"]
    variables:
      CONNECTION_STRING: postgresql://${resources.psql-db.username}:${resources.psql-db.password}@${resources.psql-db.host}:${resources.psql-db.port}/${resources.psql-db.name}
      GREETING_NAME: ${resources.env.greeting_name} 
resources:
  env:
    type: environment
    properties:
      greeting_name:
        type: string
        default: Jimmy
  psql-db:
    type: postgres
    properties:
      host:
      port:
      name:
      username:
      password:
EOF
Copy

This is because Humanitec will use placeholders to fetch this information from the resources it creates for your Workload as we will see later.

Whenever you define your resources in Score, Humanitec aims to create or resolve whatever is specified there:

  • In the case of env, it will seek a value named greeting_name which you created in a previous step.
  • In the case of psql-db it will use a resource definition to match you with a postgres type of database.  The resource definition describes how Humanitec provisions the resources for you, this means either creating or assigning you to an existing resource. In this case, we are using the default resource definition that comes with your Humanitec account.

If you look at the variables within the containers section we are referring to the resources via placeholders, this will dynamically populate the correct values regardless of the environment you find yourself in.

That’s the power of Dynamic Configuration Management, you only need to define one Workload Specification and it will work on all your environments.

Step 6: Deploy the Workload

In this step, we will be deploying a Workload by applying a Delta. A Delta describes the changes that need to be applied to a Set. Applying this Delta will deploy your Workload to a development environment the same command can be used for other environments as well by changing the --env flag (the environment will need to be created in the console beforehand).

score-humanitec delta --org $HUMANITEC_ORG  --app $HUMANITEC_APP --env development --token $HUMANITEC_TOKEN --file humanitec.score.yaml --deploy
Copy

A successful run will show the following output:

{
    "id": "ca0ba54caa5f6b36bc797a1d61f65badc6cf1425",
    "metadata": {
      "env_id": "development",
      "name": "Auto-generated (SCORE)",
      "url": "https://app.humanitec.io/orgs/happy-learning/apps/score-humanitec-tutorial/envs/development/draft/ca0ba54caa5f6b36bc797a1d61f65badc6cf1425",
      "created_by": "s-781798f9-e2e6-456e-814c-768ea737ebc6",
      "created_at": "2023-01-17T12:21:15.760845172Z",
      "last_modified_at": "2023-01-17T12:21:15.760845172Z"
    },
    "modules": {
      "add": {
        "backend": {
          "externals": {
            "psql-db": {
              "type": "postgres"
            }
          },
          "profile": "humanitec/default-module",
          "spec": {
            "containers": {
              "backend": {
                "args": [
                  "-c",
                  "sleep 5; while psql $$CONNECTION_STRING -c \"SELECT version()\"; do echo : Hey $${GREETING_NAME}, connecting to DB $${CONNECTION_STRING} was successful!; sleep 10; done"
                ],
                "command": [
                  "/bin/sh"
                ],
                "id": "backend",
                "image": "postgres:alpine",
                "variables": {
                  "CONNECTION_STRING": "postgresql://${externals.psql-db.username}:${externals.psql-db.password}@${externals.psql-db.host}:${externals.psql-db.port}/${externals.psql-db.name}",
                  "GREETING_NAME": "${values.greeting_name}"
                }
              }
            }
          }
        }
      }
    }
  }
Copy

Pay close attention to this Delta, especially the CONNECTION_STRING and GREETING_NAME variables and notice how they have been interpolated to Placeholders. Using Placeholders means that Humanitec can also fetch the correct output value for your workload.

Using Placeholders helps you create Workload Specifications as an agnostic configuration schema that can be used across environments.

Step 7: Verify your deployment to the development environment

Here we will go into the Humanitec UI to view our Workload running.

You should see your deployment running in the UI:

Click on the Workload backend.

You will see your Workload and the log output for the DB that was created:

Step 8: Create another environment

We will now create another environment to illustrate how one workload specification can be applied to multiple environments.

Click on plus sign next to your environment:

Add a new environment with the following settings:

Clone from: development

Environment type: staging

Environment name: staging

The Environment is always initialized to the current or past state of Deployment in another Environment, which is why we are cloning from Development.

Step 9: Override value for new environment

Click on the three dots ... and then Environment settings:

Edit the shared values and secrets overrides:

Change the value to whatever you want and click on Save

Environment overrides allow you to override global shared values in case you need the same value to be different in each environment.

Step 10: Deploy Workload to the new environment

score-humanitec delta --org $HUMANITEC_ORG  --app $HUMANITEC_APP --env staging --token $HUMANITEC_TOKEN --file humanitec.score.yaml --deploy
Copy

Notice we changed the flag  --env to staging.

Step 11: Verify your deployment to the development environment

Go into your new environment and click on your Workload and see the logs:

As you can see here, we only need to do an environment override for the greeting_name variable because we want/need it to be different for the staging environment. The username and password for the DB have changed automatically because Humanitec is configured to create a new DB for this workload on the first deployment.

Using Placeholders means we don’t need to modify out workload specs at all, it also means that if we needed to change something in the Workload Specification, it would ever only need to be done once and it can easily be applied to all environments.

Step 12: Clean up

If you want to start the tutorial over you can delete the App you just created with the following command. You can then start again from Step 3.

curl -sw '%{http_code}' \
--request DELETE "https://api.humanitec.io/orgs/${HUMANITEC_ORG}/apps/$HUMANITEC_APP" \
--header "Authorization: Bearer ${HUMANITEC_TOKEN}" \
--header "Content-Type: application/json" \
| jq
Copy

Summary

In this tutorial, we demonstrated how to use Score and Humanitec to deploy a workload with a single Workload Specification file. This approach is particularly useful for ensuring that the workload is consistently deployed across different environments, with no deviation in configuration. By defining the workload using Score, you can easily deploy it to multiple environments and have the necessary resources and values automatically resolved based on the specific environment.

To get started with Score and Humanitec, register for a free trial and install score-humanitec.