Install a K8s based Flask app using Mantis
This post explores how Mantis Flow enables developers to deploy a cloud-native Flask application integrated with AWS RDS and managed through Kubernetes. We'll walk through the structure of the example code, how the tasks are broken down, and how the CUE-based modules simplify reusable infrastructure components.
Let’s dive into the file structure and flow that powers this deployment.
Project File Structure Below is the structure of the example deployment:
tree -L 2
.
├── cue.mod
│ └── module.cue # Defines the module name and CUE version
├── defs
│ ├── deployment.cue # Deployment configurations for the Flask app
│ ├── rds.cue # RDS database configurations
│ ├── variables.cue # Variable definitions and inputs
│ └── providers.cue # Providers configuration
└── install_flask_app.tf.cue # Main Mantis flow for deploying the app
This file structure reflects how Mantis organizes infrastructure code using modular and reusable CUE configurations. Let's look at what each file and directory does.
1. Main Flow (install_flask_app.tf.cue)
Main Flow Code
package main
import (
"augur.ai/rds-flask-app/defs"
)
deploy_flask_rds: {
@flow(deploy_flask_rds)
setup_tf_providers: {
@task(mantis.core.TF)
config: defs.#providers
}
get_default_vpc: {
@task(mantis.core.TF)
dep: [setup_tf_providers]
config: data: aws_vpc: default: {
default: true
}
exports: [{
jqpath: ".data.aws_vpc.default.id"
var: "vpc_id"
}, {
jqpath: ".data.aws_vpc.default.cidr_block"
var: "vpc_cidr_block"
}, {
jqpath: ".data.aws_vpc.default.id"
var: "vpc_id_set"
as: [string]
},{
jqpath: ".data.aws_vpc.default.cidr_block"
var: "vpc_cidr_block_set"
as: [string]
}]
}
get_subnets: {
@task(mantis.core.TF)
dep: [get_default_vpc]
config: data: aws_subnets: default: {
filter: [{
name: "vpc-id"
values: [...string] | *null @var(vpc_id_set)
}]
}
exports: [{
jqpath: ".data.aws_subnets.default.ids"
var: "subnet_ids"
}]
}
select_subnets: {
@task(mantis.core.Eval)
dep: [get_subnets]
cueexpr: """
subnet_ids: @var(subnet_ids)
result: {
subnet_az1_id: subnet_ids[0]
subnet_az2_id: subnet_ids[1]
}
"""
exports: [{
var: "selected_subnets"
jqpath: "."
}]
}
create_subnets: {
@task(mantis.core.TF)
dep: [select_subnets]
config: data: aws_subnet: {
subnet_az1: {
id: string | *null @var(selected_subnets.subnet_az1_id)
}
subnet_az2: {
id: string | *null @var(selected_subnets.subnet_az2_id)
}
}
exports: [{
jqpath: ".data.aws_subnet.subnet_az1.id"
var: "subnet_az1_id"
}, {
jqpath: ".data.aws_subnet.subnet_az2.id"
var: "subnet_az2_id"
},{
jqpath: ".data.aws_subnet[].id",
var:"subnet_ids"
}]
}
setup_db_subnet_group: {
@task(mantis.core.TF)
dep: [create_subnets]
config: resource: aws_db_subnet_group: default: {
name: "flask-rds-subnet-group-2"
subnet_ids: [...string] | *null @var(subnet_ids)
tags: Name: "Flask RDS subnet group"
}
}
create_rds_security_group: {
@task(mantis.core.TF)
dep: [setup_db_subnet_group]
config: resource: aws_security_group: rds_sg: {
name: "rds-security-group-agr-1237"
description: "Security group for RDS instance"
vpc_id: string | *null @var(vpc_id)
ingress: [{
description: "MySQL access"
from_port: 3306
to_port: 3306
protocol: "tcp"
cidr_blocks: [string] | *null @var(vpc_cidr_block_set)
ipv6_cidr_blocks: []
prefix_list_ids: []
security_groups: []
self: false
}]
egress: [{
description: "Allow all outbound traffic"
from_port: 0
to_port: 0
protocol: "-1"
cidr_blocks: ["0.0.0.0/0"]
ipv6_cidr_blocks: ["::/0"]
prefix_list_ids: []
security_groups: []
self: false
}]
tags: Name: "RDS Security Group"
}
exports: [{
jqpath: ".aws_security_group.rds_sg.id"
var: "rds_sg_id"
as: [string]
}]
}
setup_rds: {
@task(mantis.core.TF)
dep: [setup_db_subnet_group, create_rds_security_group]
config: resource: aws_db_instance: this: {
identifier: "flask-rds-instance"
engine: "mysql"
engine_version: "5.7"
instance_class: "db.t3.micro"
allocated_storage: 20
db_name: "mydb"
username: "admin"
password: "change_this_password"
db_subnet_group_name: string | *null @var(aws_db_subnet_group.default.name)
multi_az: true
vpc_security_group_ids: [...string] | *null @var(rds_sg_id)
deletion_protection: false
skip_final_snapshot: true
tags: Name: "Flask RDS Instance"
}
exports: [{
jqpath: ".aws_db_instance.this.endpoint"
var: "rds_endpoint"
}, {
jqpath: ".aws_db_instance.this.port"
var: "rds_port"
}]
}
deploy_flask_app: {
@task(mantis.core.K8s)
dep: [setup_rds]
config: defs.flaskRdsDeployment
}
}
The main flow file orchestrates the entire deployment process by:
- Importing and using the definitions from defs/
- Defining task dependencies and execution order
- Managing state and variable passing between tasks
- Coordinating both AWS and Kubernetes resources
The core syntax of the main flow is:
deploy_flask_rds: {
@flow(deploy_flask_rds)
# Define the tasks that make up the flow
task_1: {
@task(mantis.core.TF) // Terraform task
...
}
task_2: {
@task(mantis.core.TF)
dep: [task_1] // Define task dependencies
...
}
task_3: {
@task(mantis.core.K8s) // Kubernetes task
dep: [task_1, task_2] // Define task dependencies
...
}
}
2. cue.mod/module.cue
This file defines the module name and CUE language version being used for the project. It also allows dependencies to be managed across the project.
module: "augur.ai/rds-flask-app"
language: {
version: "v0.10.0"
}
// Define the dependencies for the project
dependencies: [
"abc.xyz.com/module1",
"abc.xyz.com/module2",
]
Purpose: This ensures the project remains compatible across various CUE versions and clearly identifies the module for import across multiple flows.
3. defs Directory
- defs/deployment.cue
- defs/variables.cue
- defs/providers.cue
- defs/rds.cue
package defs
flaskRdsDeployment: {
apiVersion: "apps/v1"
kind: "Deployment"
metadata: {
name: "flask-rds-deployment"
labels: {
app: "flask-rds"
}
}
spec: {
replicas: 2
selector: {
matchLabels: {
app: "flask-rds"
}
}
template: {
metadata: {
labels: {
app: "flask-rds"
}
}
spec: {
containers: [{
name: "flask-rds"
image: "\(common.container_repo)"
ports: [{
containerPort: 80
}]
env: [
{
name: "DB_HOST"
value: "@var(rds_endpoint)"
},
{
name: "DB_NAME"
value: "\(common.db_name)"
},
{
name: "DB_USER"
value: "\(common.db_username)"
},
{
name: "DB_PASSWORD"
value: "\(common.db_password)"
}
]
resources: {
limits: {
memory: "256Mi"
cpu: "250m"
}
requests: {
memory: "128Mi"
cpu: "80m"
}
}
}]
}
}
}
package defs
common: {
// Common configurations for the RDS setup
project_name: "flask-rds-app"
// Database credentials (should be secured in practice)
db_username: "admin"
db_password: "supersecretpassword"
db_name: "rds_app"
container_repo: "registry.gitlab.com/flashresolve1/augur:v1"
}
package defs
#providers: {
provider: {
"aws": {}
}
terraform: {
required_providers: {
aws: {
source: "hashicorp/aws"
version: ">= 4.67.0"
}
}
}
}
package defs
rds: {
resource: {
aws_db_instance: {
"education_app": {
source: "terraform-aws-modules/rds/aws"
version: "~> 5.1"
identifier: "\(common.project_name)-db"
engine: "postgres"
engine_version: "14.1"
instance_class: "db.t3.micro"
allocated_storage: 5
username: "\(common.db_username)"
password: "\(common.db_password)"
publicly_accessible: true
skip_final_snapshot: true
db_subnet_group_name: [string] | *null @var(subnet_ids)
}
}
}
}
Understanding the Configuration Files
Let's understand the purpose and structure of each configuration file in our Flask-RDS application:
Module Definition (cue.mod/module.cue)
The module file identifies our project in the CUE ecosystem and ensures version compatibility. It's essential for package management and importing definitions across the project.
Definition Files (defs/)
The defs directory contains reusable CUE schemas that define our infrastructure components:
1. deployment.cue
Defines Kubernetes resource schemas for our Flask application:
- #FlaskDeployment: Template for Kubernetes Deployment resource
- #FlaskService: Template for Kubernetes Service resource
2. rds.cue
Contains AWS RDS-related schemas:
- #RDSInstance: Template for RDS database configuration
- #DBSubnetGroup: Template for RDS subnet group
Key Differences from Terraform
Task-Level State Management
Each task in Mantis maintains its own state file, unlike Terraform’s global state file. This makes it easier to:
- Debug individual tasks.
- Isolate failures without impacting the entire deployment.
Dynamic Configurations with CUE
Mantis uses CUE expressions to evaluate inputs dynamically at runtime. For example, subnet selection in the flow is performed dynamically based on retrieved data:
select_subnets: {
@task(mantis.core.Eval)
dep: [get_subnets]
cueexpr: """
subnet_ids: @var(subnet_ids)
result: {
subnet_az1_id: subnet_ids[0]
subnet_az2_id: subnet_ids[1]
}
"""
}
Conclusion
This example illustrates how Mantis Flow simplifies the deployment of a cloud-native Flask application using Kubernetes and AWS. With its modular design, task-level state management, and CUE-powered logic, Mantis provides a more flexible and debuggable alternative to Terraform.
Summary of Key Features:
- Modular Configurations: Reusable CUE modules in the defs directory.
- Task-Level State Management: Isolated state for better failure handling.
- Dynamic Evaluations with CUE: Advanced configuration logic within the flow.
- Kubernetes and AWS Integration: Seamless orchestration of cloud resources and application deployments.
By organizing infrastructure code into logical modules and flows, Mantis makes it easy to deploy applications reliably and consistently, paving the way for next-generation infrastructure automation.