Overview
What are Resources?
In the context of the Platform Orchestrator, “Resources” refers to all elements which need to be provisioned for the complete Deployment of one or several workloads. These elements include the workloads themselves as well as all their direct and indirect dependencies.
Resources can be either infrastructure-based, like an Amazon S3 bucket, or logical, such as an AWS IAM Policy. Each Resource is characterized by a type that signifies the technology it represents, irrespective of its implementation or provisioning method.
Resources are a common ground for both core personas:
- Developers specify dependencies on types of Resources that their workloads require
- Platform engineers provide definitions for provisioning types of Resources in various contexts
The Platform Orchestrator brings both worlds together with each Deployment it executes.
The following guide walks you through the core concepts around Resources.
Resources 101
Developers request Resources via Score
Developers request any Resources their workload requires through Score . They deploy the Score file to the Platform Orchestrator, usually via a CI/CD pipeline.
# Score file for a workload requesting a postgres resource
apiVersion: score.dev/v1b1
metadata:
name: my-workload
...
resources:
my-db:
type: postgres
flowchart LR
score[Score file] -->|deploy| platform_orchestrator{Platform<br/>Orchestrator}
The Resource Graph
On deployment, the Platform Orchestrator generates a dependency Graph of all the requested Resources, the Resource Graph . You can view and navigate the Graph in the Humanitec Portal after the deployment finished.
As Resources can depend on each other, they need to be provisioned in the correct sequence. The Graph is created to determine that sequence. It therefore must be free of cycles.
flowchart LR
workload(Workload<br/>"my-workload") --> postgres(Postgres<br/>"my-db")
Once the Graph structure is generated, the Platform Orchestrator provisions every Resource in the Graph. This usually means provisioning a real-world resource like a PostgreSQL database which is managed through the Orchestrator going foward, but it can also mean just representing a real-world resource in the Graph without managing it - we provide more details further down .
Resource Graph per Environment
There is one Resource Graph per Application Environment . All workloads deployed into that Environment are added to the Graph as root nodes, along with any resource dependencies defined via Score. Each node represents an Active Resource created during deployment.
flowchart LR
workload(Workload<br/>"my-workload") --> postgres(Postgres<br/>"my-db")
workload_other(Workload<br/>"my-other-workload") --> storage(S3 bucket<br/>"my-bucket")
Resource Definitions
The Platform Orchestrator finds the instructions on how to provision each Resource in a Resource Definition of the respective Resource Type . The chosen Driver (more details next ) determines how a Definition is executed, e.g. by running IaC code or using templates to create Kubernetes manifests on the target cluster.
Platform engineers maintain the Resource Definitions.
There are a number of Default Resource Definitions in the Orchestrator, including one for the “workload”.
Platform Engineers also control which Resource Definition to use in which context through matching criteria . The exact Definition to use in a deployment may therefore differ by Application or Environment, or it can be the same everywhere.
flowchart TB
subgraph resDefs[Resource Definitions]
direction LR
resDefWorkload[Resource Definition<br/>"Workload"] ~~~ resDefPostgres[Resource Definition<br/>"Postgres"]
end
subgraph resources[Resource Graph]
direction LR
workload(Workload<br/>"my-workload") --> postgres(Postgres<br/>"my-db")
end
resDefs -.-> resources
Drivers
As part of each Resource Definition, the specified Driver determines the technical means for provisioning a Resource. Drivers may execute an IaC tool to provision a real-world resource, produce Kubernetes manifests on the target cluster, or just provide output values.
flowchart TB
postgres(Postgres<br/>"my-db") -->|Terraform Driver| database@{ shape: cyl, label: "my-db" }
Resource references add Resources to the Graph
A Resource may again depend on other Resources. For example, the PostgreSQL database may require a PostgreSQL instance in which the database is hosted.
Platform engineers can define this dependency by placing a Resource reference to a PostgreSQL instance into the Definition for the “postgres” database Resource. Now whenever a “postgres” is requested, a “postgres instance” will automatically be added to the Graph as a dependent Resource.
flowchart LR
workload(Workload<br/>"my-workload") --> postgres(Postgres<br/>"my-db")
postgres --> postgres_instance(Postgres instance<br/>for "my-db")
Infrastructure abstraction
Developers do not have to be concerned with underlying infrastructure details. They just request the “postgres” database via Score. The infrastructure behind it is abstracted away from them. The Postgres instance does not appear in Score.
flowchart LR
workload(Workload<br/>"my-workload") --> postgres(Postgres<br/>"my-db")
postgres -.-> postgres_instance(Postgres instance<br/>for "my-db"):::dashed
classDef dashed stroke-dasharray:5,5
Resource Types
Resources are strongly typed. Each Resource in the Graph has exactly one
Resource Type
, e.g. workload
, postgres
, or postgres-instance
.
Each Resource Definition covers exactly one Resource Type. There may be more than one Definition for a Resource Type. The Definition’s matching criteria determine which one of those is picked in a specific deployment context , e.g. for a specific Application or Environment.
flowchart TB
subgraph resDefs[Resource Definitions]
direction LR
resDefWorkload@{ shape: processes, label: "Resource Definition<br/>type: <span style="font-family:Courier">workload</span>" } ~~~ resDefPostgres@{ shape: processes, label: "Resource Definition<br/>type: <span style="font-family:Courier">postgres</span>"}
resDefPostgres ~~~ resDefPostgresInstance@{ shape: processes, label: "Resource Definition<br/>type: <span style="font-family:Courier">postgres-instance</span>"}
end
subgraph resources[Resource Graph]
direction LR
workload(Workload<br/>"my-workload"<br/>type: <span style="font-family:Courier">workload</span>) --> postgres(Postgres<br/>"my-db"<br/>type: <span style="font-family:Courier">postgres</span><br/>(direct))
postgres --> postgres_instance(Postgres instance<br/>for "my-db"<br/>type: <span style="font-family:Courier">postgres-instance</span><br/>(indirect))
end
resDefs -.-> resources
Direct and indirect Resource Types
Types of Resources that developers may request via Score such as postgres
are called
direct Resource Types
. Other types used further downstream such as the postgres-instance
are called
indirect Resource Types
.
Implicit Resource Types
The workload
itself is a so-called
implicit Resource Type
. One Resource of each implicit Type is automatically added to each deployment by the Platform Orchestrator. Other implicit types include the k8s-cluster
and k8s-namespace
, representing the target Kubernetes cluster and namespace for the workload deployment.
flowchart LR
workload(Workload<br/>"my-workload"<br/>type: <span style="font-family:Courier">workload</span><br/>(implicit)) --> postgres(Postgres<br/>"my-db"<br/>type: <span style="font-family:Courier">postgres</span><br/>(direct))
postgres --> postgres_instance(Postgres instance<br/>for "my-db"<br/>type: <span style="font-family:Courier">postgres-instance</span><br/>(indirect))
k8sCluster(Kubernetes cluster<br/>type: <span style="font-family:Courier">k8s-cluster</span><br/>(implicit))
k8sNamespace(Kubernetes namespace<br/>type: <span style="font-family:Courier">k8s-namespace</span><br/>(implicit))
Using Resource outputs in the Graph
Resources can
produce outputs
and provide them to upstream Resources. E.g. the postgres-instance
can provide its host
value as an output to the postgres
Resource so that the Platform Orchestrator may use that data to provision the database in the proper instance.
In the Resource Graph, i.e. between Resources, a Resource Definition has access to any output another referenced Resource may produce.
flowchart LR
workload(Workload<br/>"my-workload") --> postgres(Postgres<br/>"my-db")
postgres --> postgres_instance(Postgres instance<br/>for "my-db")
postgres_instance -.->|host| postgres
Using Resource outputs in Score
Direct Resources can pass the outputs defined in their
Resource Type
schema - and only those - to the workload in Score. E.g. the postgres
can pass on its host
value as an output to the workload to help it create a database connection.
apiVersion: score.dev/v1b1
...
container:
my-container:
variables:
# Reference the postgres resource to read the "host" output
DB_HOST: ${resources.my-db.host}
resources:
my-db:
type: postgres
flowchart LR
workload(Workload<br/>"my-workload") --> postgres(Postgres<br/>"my-db")
postgres --> postgres_instance(Postgres instance<br/>for "my-db")
postgres_instance -.->|host| postgres
postgres -.->|host| workload
Graph resources and real-world resources
A real-world Resource represented in the Graph may or may not be managed by the Platform Orchestrator. E.g. the Orchestrator may provision the “my-db” database in a PostgreSQL instance, while the instance itself is maintained externally.
You will still want to reflect the PostgreSQL instance in the Resource Graph to pass required outputs such as the host
to the Resources of postgres
databases located in that PostgreSQL instance. You can use the
Echo Driver
in the instance Resource Definition for that purpose.
flowchart TB
subgraph resDefs[Resource Graph]
direction LR
postgres(Postgres<br/>"my-db") --> postgresInstance(Postgres instance<br/>for "my-db")
postgresInstance -.->|host| postgres
end
subgraph realWorld[Real-world resources]
direction LR
database@{ shape: cyl, label: ""my-db" database<br/>managed by<br>Platform<br>Orchestrator" } ~~~ postgresInstanceReal(PostgreSQL instance<br/>managed<br/>externally)
end
resDefs -->|Terraform Driver for PostgreSQL database| realWorld
resDefs -->|Echo Driver for PostgreSQL instance| realWorld
Workload Resources
The workloads themselves are deployed to a Kubernetes cluster using the container image defined via Score. The Platform Orchestrator creates a Kubernetes Deployment by default, but you can customize this behavior using a different Workload Profile .
The cluster Resource
The target cluster is expressed by the Resource of the Type k8s-cluster
. That is an
implicit Type
, so one such Resource is always added. Like for all other Resource Definitions, the
matching criteria
control which specific Definition, and therefore which cluster, is used in a deployment context.
The cluster Resource Definition provides the data required to access the cluster .
The real-world Kubernetes cluster is always managed externally, not by the Platform Orchestrator.
flowchart LR
workload(Workload<br/>"my-workload"<br/>type: <span style="font-family:Courier">workload</span><br/>(implicit))
k8sCluster(Kubernetes cluster<br/>type: <span style="font-family:Courier">k8s-cluster</span><br/>(implicit))
subgraph k8sClusterRealWorld[Kubernetes cluster]
workloadOnK8s(my-workload)
end
k8sCluster -->|defines| k8sClusterRealWorld
workload -->|deployed to| workloadOnK8s
Shared Resources
When several Resources need to access the same Resource, you can make it a Shared Resource .
E.g. given that the PostgreSQL databases requested by two workloads reside in the same PostgreSQL instance, then both postgres
Resources in the Graph can reference the same postgres-instance
Resource.
flowchart LR
workload(Workload<br/>"my-workload") --> postgres(Postgres<br/>"my-db")
workload2(Workload<br/>"my-other-workload") --> postgres2(Postgres<br/>"my-other-db")
postgresInstance(Postgres instance<br/>"Shared instance")
postgres --> postgresInstance
postgres2 --> postgresInstance
Advanced Resource topics
This section covers topics around Resources you will not need to know initially, but will be helpful in more advanced scenarios.
Co-provision other Resources
Sometimes a Resource needs another Resource to be created along with it, but with the dependency reversed. E.g. an AWS IAM policy may need to be created for each postgres
database Resource. The policy needs the name of the database and so it must depend on the postgres
.
flowchart LR
subgraph justForLayout[ ]
workload(Workload<br/>"my-workload") --> iamPolicy(IAM policy<br/>for "my-db")
end
iamPolicy --> postgres
workload --> postgres(Postgres<br/>"my-db")
postgres -.->|name| iamPolicy
classDef invisible fill:#00000000,stroke:#00000000
class justForLayout invisible
A
Resource Reference
from the postgres
to an IAM Policy would create a new IAM policy Resource, but the dependency would be directed the wrong way.
Instead, the postgres
Resource can
co-provision
the IAM policy and have it depend on itself, i.e. on the postgres
. The IAM policy may thus read the name
output of the postgres
Resource.
The co-provisioning can also have the workload
depend on the IAM policy to facilitate advanced Graph lookups like the one shown next.
Perform lookups with Resource Selectors
You can perform lookups into the Graph from a Resource to locate other Resources somewhere else and read their outputs. You can even go round corners.
Consider this Graph: on top of the postgres
and the IAM policy, there is now a Kubernetes service account and an IAM role. Both were created using
Resource references
: the workload referenced and thus created the service account, and the service account referenced and thus created the IAM role.
flowchart LR
iamPolicy --> postgres
workload --> postgres(Postgres<br/>"my-db")
workload(Workload<br/>"my-workload") ----> iamPolicy(IAM policy<br/>for "my-db")
workload --> k8sServiceAccount(K8s service account<br/>for "my-workload") --> iamRole(IAM Role<br/>for "my-workload")
The IAM role now needs the names of all IAM policies for the postgres
Resources of its upstream workload.
It can perform a lookup using a Resource Selector in the IAM Resource Definition stating “Please give me the names of all IAM policies that my upstream workload depends on”. This selector finds the IAM policy for “my-db” by traversing the dependencies of the Graph.
The Resource selector will add a new dependency from the IAM role to the IAM policy so that the IAM role can read the name
output.
flowchart LR
subgraph justForLayout[ ]
direction LR
workload(Workload<br/>"my-workload") --> k8sServiceAccount(K8s service account<br/>for "my-workload") --> iamRole(IAM Role<br/>for "my-workload")
end
iamPolicy --> postgres
iamRole --> iamPolicy
workload ----> iamPolicy(IAM policy<br/>for "my-db")
workload --> postgres(Postgres<br/>"my-db")
iamPolicy -.->|name| iamRole
classDef invisible fill:#00000000,stroke:#00000000
class justForLayout invisible
Specialize Resources with Resource Classes
Resource Classes provide a way of offering different flavors or specializations of a Resource Type. Platform engineers define available classes and provide either separate Resource Definitions per class or differentiate between classes in the IaC code within one Definition.
Developers can then request Resources of a particular class via Score:
# Score file for a workload requesting resources with classes
apiVersion: score.dev/v1b1
metadata:
name: my-workload
...
resources:
external-bucket:
type: s3
# Class for a bucket open to the public internet
class: external
sensitive-bucket:
type: s3
# Class for a protected, encrypted bucket
class: sensitive
flowchart LR
workload(Workload<br/>"my-workload") --> s3BucketExternal(S3 bucket<br/>"external-bucket"<br/>class: external)
workload --> s3BucketSensitive(S3 bucket<br/>"sensitive-bucket"<br/>class: sensitive)
Propagate Resource Classes to associated Resources
A Resource can propagate its class to other associated Resources it creates so that this new Resource can be provisioned with a matching configuration. E.g. each S3 bucket of a certain classs can co-provision an IAM policy of the same class. The Resource Definition code for the IAM policy can then query the class of the current Resource and provision the proper policy implementation.
flowchart LR
iamPolicyExternal(IAM policy<br/>for "external-bucket"<br>class: external) --> s3BucketExternal
workload(Workload<br/>"my-workload") ---> s3BucketExternal(S3 bucket<br/>"external-bucket"<br/>class: external)
workload ---> s3BucketSensitive(S3 bucket<br/>"sensitive-bucket"<br/>class: sensitive)
iamPolicySensitive(IAM policy<br/>for "sensitive-bucket"<br>class: sensitive) --> s3BucketSensitive
The same mechanism works for Resources being created via Resource References .
Use Resource IDs to structure the Graph
Each Resource in the Graph has a permanent, non-semantic Globally Unique Resource ID ( GUResID ) to identify the Resource throughout its lifecycle.
Each Resource also has a semantic
Resource ID
. Its value depends on how the Resource was created. By default, it is derived from the name of the upstream workload. Shared Resources have a shared.*
ID.
flowchart LR
workload(Workload<br/>"my-workload"<br/>ID: modules.my-workload) --> postgres(Postgres<br/>"my-db"<br/>ID: modules.my-workload.externals.my-db)
workload2(Workload<br/>"my-other-workload"<br/>ID: modules.my-other-workload) --> postgres2(Postgres<br/>"my-other-db"<br/>ID: modules.my-other-workload.externals.my-other-db)
postgresInstance(Postgres instance<br/>"Shared instance"<br/>ID: shared.my-db-instance)
postgres --> postgresInstance
postgres2 --> postgresInstance
The combination of Resource Type, Resource Class, and Resource ID uniquely identifies a Resource in the Graph.
Just like with Resource classes, a Resource may propagate its ID to Resources it creates via Resource References or co-provisioning so that related sets of Resources share an ID. This allows a Resource to locate associated Resources in the Graph using Resource Selectors .
Context
Each Resource is provisioned within a specified context, comprising these elements:
- The target Application for the deployment
- The target Environment for the deployment
- The Resource Type , Resource Class , and Resource ID of the Resource to be provisioned
The context uniquely identifies a provisioned Resource. This combination allows differentiating between active instances of the same Resource across various Applications and Environments.
You can query each element of the context in a Resource Definition using Placeholders .
Example
A developer has added a shared
Resource Dependency
on a Resource of type dns
and given it the ID api-dns
. This makes its Resource ID shared.api-dns
.
If there are three environments development
, staging
, and production
, then there are three instances of this Resource - one in each environment. Each Active Resource has the same Application ID and Resource ID, but they contain different Environment IDs. Therefore, they each have a unique context:
Application ID | Environment ID | Resource ID |
---|---|---|
awesome-app |
development |
shared.api-dns |
awesome-app |
staging |
shared.api-dns |
awesome-app |
production |
shared.api-dns |
Resource provisioning
During deployment, the Platform Orchestrator needs to provision every Resource in the Resource Graph using the Driver named in the associated Resource Definition .
The term provision is used broadly to denote making Resources available. Often, this involves creating a new real-world entity - a new DNS name, a new database in an existing instance, etc. However, there are situations where you might want to use an existing entity without the Orchestrator creating a new one.
For instance, in a Production environment, you might want your Application to be accessible via a fixed DNS name that you manage outside of the Platform Orchestrator. Instead of having the Orchestrator automatically create a new subdomain, the production environment should be accessible via a pre-defined DNS name.
You still need to have a dns
Resource in the Resource Graph in Production. However, the provisioning of this Resource can just mean returning a pre-configured DNS name, and do nothing more. The
Echo Driver
is useful for this purpose.
In practice, this means providing two Resource Definitions for the type dns
:
- One Definition matching the non-production Environments and actually creating a DNS entry, e.g. using the Wildcard DNS Driver
- One Definition matching the Production Environment and just returning the configured DNS name using the Echo Driver