feat(infra): distribute terraform modules under infra/terraform
POC: lift the homelab-grown nextcloud-mcp-server and nextcloud-mcp-deployer-role Terraform modules into this repo so external operators can consume them via a `git::` source. Includes a top-level README documenting the two-phase deploy flow (bootstrap deployer role with a copy-pasteable IAM policy, then assume the role to deploy the MCP server) and supports both in-VPC Qdrant and external/managed Qdrant modes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
0c2d3e1086
commit
ccf4b91bf9
@@ -0,0 +1,151 @@
|
||||
locals {
|
||||
qdrant_name = "${var.name}-qdrant"
|
||||
qdrant_port = 6333
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_group" "qdrant" {
|
||||
count = var.use_external_qdrant ? 0 : 1
|
||||
name = "/ecs/${local.qdrant_name}"
|
||||
retention_in_days = var.log_retention_days
|
||||
}
|
||||
|
||||
resource "aws_security_group" "qdrant" {
|
||||
count = var.use_external_qdrant ? 0 : 1
|
||||
name = local.qdrant_name
|
||||
description = "Qdrant ECS task ENI for ${var.name}"
|
||||
vpc_id = var.vpc_id
|
||||
}
|
||||
|
||||
resource "aws_vpc_security_group_ingress_rule" "qdrant_from_task" {
|
||||
count = var.use_external_qdrant ? 0 : 1
|
||||
security_group_id = aws_security_group.qdrant[0].id
|
||||
referenced_security_group_id = aws_security_group.task.id
|
||||
from_port = local.qdrant_port
|
||||
to_port = local.qdrant_port
|
||||
ip_protocol = "tcp"
|
||||
description = "Qdrant REST from mcp-server"
|
||||
}
|
||||
|
||||
resource "aws_vpc_security_group_egress_rule" "qdrant_all_v4" {
|
||||
count = var.use_external_qdrant ? 0 : 1
|
||||
security_group_id = aws_security_group.qdrant[0].id
|
||||
cidr_ipv4 = "0.0.0.0/0"
|
||||
ip_protocol = "-1"
|
||||
}
|
||||
|
||||
resource "aws_vpc_security_group_egress_rule" "qdrant_all_v6" {
|
||||
count = var.use_external_qdrant ? 0 : 1
|
||||
security_group_id = aws_security_group.qdrant[0].id
|
||||
cidr_ipv6 = "::/0"
|
||||
ip_protocol = "-1"
|
||||
}
|
||||
|
||||
resource "aws_service_discovery_service" "qdrant" {
|
||||
count = var.use_external_qdrant ? 0 : 1
|
||||
name = "qdrant"
|
||||
|
||||
dns_config {
|
||||
namespace_id = aws_service_discovery_private_dns_namespace.this.id
|
||||
routing_policy = "MULTIVALUE"
|
||||
|
||||
dns_records {
|
||||
type = "A"
|
||||
ttl = 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ecs_task_definition" "qdrant" {
|
||||
count = var.use_external_qdrant ? 0 : 1
|
||||
family = local.qdrant_name
|
||||
requires_compatibilities = ["FARGATE"]
|
||||
network_mode = "awsvpc"
|
||||
cpu = tostring(var.qdrant_cpu)
|
||||
memory = tostring(var.qdrant_memory)
|
||||
execution_role_arn = aws_iam_role.execution.arn
|
||||
task_role_arn = aws_iam_role.qdrant_task[0].arn
|
||||
|
||||
volume {
|
||||
name = "storage"
|
||||
efs_volume_configuration {
|
||||
file_system_id = aws_efs_file_system.this.id
|
||||
transit_encryption = "ENABLED"
|
||||
authorization_config {
|
||||
access_point_id = aws_efs_access_point.qdrant[0].id
|
||||
iam = "ENABLED"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container_definitions = jsonencode([
|
||||
{
|
||||
name = "qdrant"
|
||||
image = "${var.qdrant_image}:${var.qdrant_image_tag}"
|
||||
essential = true
|
||||
|
||||
portMappings = [
|
||||
{
|
||||
containerPort = local.qdrant_port
|
||||
protocol = "tcp"
|
||||
},
|
||||
]
|
||||
|
||||
mountPoints = [
|
||||
{ sourceVolume = "storage", containerPath = "/qdrant/storage", readOnly = false },
|
||||
]
|
||||
|
||||
# No container-level healthCheck: qdrant's image (debian:13-slim) ships
|
||||
# only ca-certificates/tzdata/libunwind8 — no bash, no curl/wget — and
|
||||
# the qdrant binary has no health subcommand. ECS still detects process
|
||||
# exit; Cloud Map only routes to RUNNING tasks.
|
||||
|
||||
logConfiguration = {
|
||||
logDriver = "awslogs"
|
||||
options = {
|
||||
awslogs-group = aws_cloudwatch_log_group.qdrant[0].name
|
||||
awslogs-region = data.aws_region.current.region
|
||||
awslogs-stream-prefix = "ecs"
|
||||
}
|
||||
}
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
# Qdrant uses local file storage with a portalocker file lock; concurrent
|
||||
# instances can't share the EFS path. Stop the old task before starting the
|
||||
# new one (brief downtime per qdrant deploy — rare). AZ rebalancing requires
|
||||
# maxPercent>100, which would re-introduce the overlap, so it's disabled.
|
||||
# The mcp-server service no longer pays this cost.
|
||||
resource "aws_ecs_service" "qdrant" {
|
||||
count = var.use_external_qdrant ? 0 : 1
|
||||
name = local.qdrant_name
|
||||
cluster = aws_ecs_cluster.this.id
|
||||
task_definition = aws_ecs_task_definition.qdrant[0].arn
|
||||
desired_count = 1
|
||||
launch_type = "FARGATE"
|
||||
|
||||
deployment_minimum_healthy_percent = 0
|
||||
deployment_maximum_percent = 100
|
||||
availability_zone_rebalancing = "DISABLED"
|
||||
|
||||
enable_execute_command = true
|
||||
|
||||
network_configuration {
|
||||
subnets = var.public_subnet_ids
|
||||
security_groups = [aws_security_group.qdrant[0].id]
|
||||
assign_public_ip = true
|
||||
}
|
||||
|
||||
service_registries {
|
||||
registry_arn = aws_service_discovery_service.qdrant[0].arn
|
||||
}
|
||||
|
||||
deployment_circuit_breaker {
|
||||
enable = true
|
||||
rollback = true
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_efs_mount_target.this,
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user