Replies: 1 comment 1 reply
-
After thinking about this feature quite a bit, one thing that didn't quite site right was the fact that the database is more synonymous with an "instance" than a platform – it would be a node in the dependency graph where the container platform is not. For that reason it felt more like we needed to find a way to solve this with our component spec than with our cloud platform. After much discussion, our current way of thinking involves finding a way to allow components to be "namespaced". Developers need a way of acquiring their own schema/namespace despite sharing a dependency on the same database type with other services in the stack. In order to solve this, we wanted to look closer at the point where upstream components interact with downstream dependencies – Instrumentation of smart interfacesInterfaces represent APIs that are exposed for upstreams to consume, and are a natural place for us to include namespacing logic. If we are able to encode interfaces with tasks and output variables to be executed and provided uniquely for each upstream, we can use that hook to generate schemas, roles, and more, and provide the corresponding values as dynamic outputs. Describing the interfaceBelow is an example of what it might look like to describe a "smart" component interface. In this example, we're defining a postgres component that can be cited as a dependency. Each upstream that cites it as a dependency would have access to their own schema/database on top of the generated service instance: name: postgres/postgres
parameters:
ROOT_USER: postgres
ROOT_PASS: somesecretpassword
services:
postgres:
image: postgres:13
interfaces:
main:
protocol: postgres
port: 5432
environment:
POSTGRES_USER: ${{ parameters.ROOT_USER }}
POSTGRES_PASSWORD: ${{ parameters.ROOT_PASS }}
interfaces:
database:
url: ${{ services.postgres.interfaces.main.url }}
output:
# Define additional properties using the `properties` syntax of json schema. This allows us to do validation
# and merge the values in with the other dynamic interface values like host, url, etc.
properties:
database:
type: string
description: Unique database name
username:
type: string
description: Username to access the unique database
password:
type: string
description: Password used to access the unique database
minLength: 32
maxLength: 32
# Same syntax as `tasks.<task-name>`. stdout must be a json string matching the schema.
task:
image: postgres:13
environment:
PGHOST: ${{ services.postgres.interfaces.main.host }}
PGPORT: ${{ services.postgres.interfaces.main.port }}
PGUSER: ${{ parameters.ROOT_USER }}
PGPASSWORD: ${{ parameters.ROOT_PASS }}
CONSUMER_NAME: ${{ interfaces.database.consumer.name }} # This is a new field available for the interfaces context
command:
- sh
- -c
- | # Creates the database, user, and assigns a unique password
NEW_PASS=$(openssl rand -base64 32)
postgres psql -v ON_ERROR_STOP=1 <<-EOSQL
SELECT 'CREATE DATABASE $CONSUMER_NAME' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '$CONSUMER_NAME')\gexec;
DO
$do$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '$CONSUMER_NAME') THEN
CREATE ROLE $CONSUMER_NAME;
GRANT ALL PRIVILEGES ON DATABASE $CONSUMER_NAME TO $CONSUMER_NAME;
END IF;
END
$do$;
ALTER ROLE $CONSUMER_NAME WITH PASSWORD '$NEW_PASS';
EOSQL
echo '{"database": "$CONSUMER_NAME", "username": "$CONSUMER_NAME", "password": "$NEW_PASS"}' Consuming the interfaceNote that despite the lengthy command used to generate the unique database and role inside the Of course we also want to see what it might look like to consume such a component and make use of this namespacing. Below is a simple API component that cites our prior component as a dependency: name: example/component
dependencies:
postgres/postgres: 13
services:
my-api:
build:
context: .
environment:
DB_ADDR: ${{ dependencies['postgres/postgres'].interfaces.database.url }}
DB_NAME: ${{ dependencies['postgres/postgres'].interfaces.database.outputs.database }}
DB_USER: ${{ dependencies['postgres/postgres'].interfaces.database.outputs.username }}
DB_PASS: ${{ dependencies['postgres/postgres'].interfaces.database.outputs.password }} As you can see, this upstream need not have its own parameters for username, password, or unique db name. Instead, those values will be issued uniquely to the component from the dependency. Now this API is even easier to deploy into any environment as it requires NO configuration! We've gotten a lot of great feedback from our design partners, but we're still looking for outside voices to share their thoughts and help inform a feature like this. Let us know what you think and if this kind of feature would benefit your team or application! |
Beta Was this translation helpful? Give feedback.
-
Overview
The literature for proper design of microservices has taught developers that each of their microservices should have its own, isolated database and state. This of course allows these services to be decoupled from one another ensuring that they each can grow and evolve autonomously.
While this architectural decision has a lot of benefits for developer and service autonomy, it also means that developers frequently need to provision or request new databases that fit their requirements. With so many databases being provisioned to power a growing application, teams very quickly have to figure out the answers to questions like:
These questions are important, but who are they important to? In most cases, all the the person who needs the database (the developer) cares about is, "I need a database of type
X
that supports features in versionY
". ReplaceX
with your favorite database type (e.g. PostgreSQL, MySQL, MongoDB, etc.) andY
with the version of that database type, and that should be enough for the initial request to be fulfilled.What Architect does today
Today, Architect treats databases just like any other service – developers can describe a service inside their components that is backed by a docker image for provisioning of dev and preview environments (e.g. postgres:12). Components can be parameterized to specify the credentials that should be used for the service (see our stateful-component example) and the database service host can be overridden in production to point to a pre-provisioned database (usually powered by RDS or some other DB service).
This flow, while enabling both automated, short-term environments and static, long-term environments, still requires that developers "request" a production-grade database from DevOps or SREs when deploying to a long-term environment. If they login to the cloud provider console and provision one themselves, they now have to take on the responsibility of long-term scaling, and network security. If they have an SRE that they request the database from, said SRE now needs to learn some intimate details about the application topology to secure network access and credentials for the consuming applications. In both cases, each party is taking on responsibilities that would best be handled by the other. Furthermore, this manual provisioning process creates a bottleneck when creating new stateful services.
Feature proposal
We'd like to make it as easy as possible for developers to request/create databases that suit their needs without taking on the burdens of scaling and security that they may not be prepared for. To accomplish this, let's look back at the statement we made above:
If we were to distill this statement down into a feature in our
architect.yml
files, it may look something like this:As you can see,
databases
become a first-class feature rather than a declared service. Making databases a first class feature in this way would have the following benefits:The "database" platform
In private developer environments, these database claims would be handled the same way they are in our current stateful example – Architect will maintain the logic needed to fulfill database claims with deployed, dockerized services into your environment. This flow isn't suitable for production, but it perfectly reasonable for small, short-lived environments.
For production use cases, the fulfillment of database claims would be through a new form of "platforms". Today, Architect only supports "container" platforms like Kubernetes and ECS for scheduling containerized services to be run. However, the relationship between these container platforms and the scheduled services is very similar to the relationship between a database instance and the databases that exist on top of it. Where Kubernetes separates the responsibilities for scaling the cluster from scaling the Deployments/Pods so that service creators don't need to worry about hardware, database instances provide a similar abstraction for the databases or schemas that are created on the instances.
To register a database instance as a platform, you'd simply create the instance on your favorite cloud provider (e.g. AWS RDS) and connect it to Architect as shown in the wireframes below. The first row shows the flow for registering you database as a platform, and the second row shows how you'd specify which platforms show act as the providers for an environment. Container platforms would fulfill
services
and database platforms would fulfilldatabases
:What's happening under the hood
This abstraction provides an excellent path forward for developers to deploy their code all the way to production without any continued work from SREs, DBAs, or other infra groups. As long as they connect and manage a database instance and register it as a platform, all database claims will be fulfilled by it automatically. Every time a component is deployed that requires a database, Architect will automatically instrument the following:
Not only will each claim get its own database/schema automatically to ensure they remain decoupled, but each claim will include scoped credentials that only allow access to their schema. This will ensure that components, despite sharing the instance, can't negatively impact one another. The best part is, this dynamic credential issuance happens automatically!
What we're looking for
We'd love to hear from developers and SREs that have lots of databases in their application topology to learn more about the pain points they're facing and whether or not this proposal provides value. A few questions we would want to know the answers to include:
Beta Was this translation helpful? Give feedback.
All reactions