Skip to content

Commit

Permalink
Add terraform module for ECS (#4765)
Browse files Browse the repository at this point in the history
* Add terraform module for ECS

* Make module id customizable

* More example and docs

* Address review comments
  • Loading branch information
rdettai authored Apr 10, 2024
1 parent 9ce91c6 commit 0bb20f3
Show file tree
Hide file tree
Showing 22 changed files with 1,072 additions and 0 deletions.
4 changes: 4 additions & 0 deletions distribution/ecs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.terraform
terraform.tfstate*
.terraform.tfstate*
terraform.tfvars
113 changes: 113 additions & 0 deletions distribution/ecs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# ECS deployment for quickwit

## Run Quickwit in your infrastructure

Create a Quickwit module using:

```terraform
module "quickwit" {
source = "github.com/quickwit-oss/quickwit/distribution/ecs/quickwit"
vpc_id = # VPC in which all resources will be created
subnet_ids = [...] # At least 2 private subnets must be specified
quickwit_ingress_cidr_blocks = [...] # List of CIDR blocks allowed to access to the Quickwit API
}
```

The Quickwit cluster is running on a private subnet. For ECS to pull the image:
- if using the default Docker Hub image `quickwit/quickwit`, the subnets
specified must be configured with a NAT Gateway (no public IPs are attached to
the tasks)
- if using an image hosted on ECR, a VPC endpoint for ECR can be used instead of
a NAT Gateway


## Module configurations

To get the list of available configurations, check the `./quickwit/variables.tf`
file.

### Tips

Metastore database backups are disabled as restoring one would lead to
inconsistencies with the index store on S3. To ensure high availability, you
should enable `rds_config.multi_az` instead. The module currently doesn't allow
using an externally provided metastore.

Using NAT Gateways for the image registry is quite costly (~$0.05/hour/AZ). If
you are not already using NAT Gateways in the AZs where Quickwit will be
deployed, you should probably push the Quickwit image to ECR and use ECR
interface VPC endpoints instead (~$0.01/hour/AZ).

When using the default image, you will quickly run into the Docker Hub rate
limiting. We recommand pushing the Quickwit image to ECR and configure that as
`quickwit_image`. Note that the architecture of the image that you push to ECR
must match the `quickwit_cpu_architecture` variable (`ARM64` by default).

Sidecar container and custom logging configurations can be configured using the
variables `sidecar_container_definitions`, `sidecar_container_dependencies`,
`log_configuration`, `enable_cloudwatch_logging`. See [custom log
routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html).

You can use sidecars to inject additional secrets as files. This can be
useful for configuring sources such as Kafka. See `./example/kafka.tf` for an
example.

## Running the example stack

We provide an example of self contained deployment with an ad-hoc VPC.

> [!IMPORTANT] This stack costs ~$150/month to run (Fargate tasks, NAT Gateways
> and RDS)
To make it easy to access your the Quickwit cluster, this stack includes a
bastion instance. Access is secured using an SSH key pair that you need to
provide (e.g generated with `ssh-keygen -t ed25519`).

In the `./example` directory create a `terraform.tfvars` file with the public
key of your RSA key pair:

```terraform
bastion_public_key = "ssh-ed25519 ..."
```

> [!NOTE] You can skip the creation of the bastion by not specifying the
> `bastion_public_key` variable, but that would make it hard to access and
> experiment with the created Quickwit cluster.
In the same directory (`./example`) run:

```bash
terraform init
terraform apply
```

The successful `apply` command should output the IP of the bastion EC2 instance.
You can port forward Quickwit's search UI using:

```bash
ssh -N -L 7280:searcher.quickwit:7280 -i {your-private-key-file} ubuntu@{bastion_ip}
```

To ingest some example dataset, log into the bastion:

```bash
ssh -i {your-private-key-file} ubuntu@{bastion_ip}

# create the log index
wget https://raw.githubusercontent.com/quickwit-oss/quickwit/main/config/tutorials/hdfs-logs/index-config.yaml
curl -X POST \
-H "content-type: application/yaml" \
--data-binary @index-config.yaml \
http://indexer.quickwit:7280/api/v1/indexes

# import some data
wget https://quickwit-datasets-public.s3.amazonaws.com/hdfs-logs-multitenants-10000.json
curl -X POST \
-H "content-type: application/json" \
--data-binary @hdfs-logs-multitenants-10000.json \
http://indexer.quickwit:7280/api/v1/hdfs-logs/ingest?commit=force
```

If your SSH tunnel to the searcher is still running, you should be able to see
the ingested data in the UI.
45 changes: 45 additions & 0 deletions distribution/ecs/example/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions distribution/ecs/example/bastion.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
variable "bastion_public_key" {
description = "The public key used to connect to the bastion host. If empty, no bastion is created."
default = ""
}

output "bastion_ip" {
value = var.bastion_public_key != "" ? aws_instance.bastion[0].public_ip : null
}

data "aws_ami" "ubuntu" {
most_recent = true

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}

owners = ["099720109477"] # Canonical
}

resource "aws_security_group" "allow_ssh" {
count = var.bastion_public_key != "" ? 1 : 0
name = "qw_ecs_bastion_allow_ssh"
description = "Allow SSH inbound traffic from everywhere"
vpc_id = module.vpc.vpc_id

ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

resource "aws_instance" "bastion" {
count = var.bastion_public_key != "" ? 1 : 0
ami = data.aws_ami.ubuntu.id
instance_type = "t3.nano"
key_name = aws_key_pair.bastion_key[0].key_name
subnet_id = module.vpc.public_subnets[0]
associate_public_ip_address = true
vpc_security_group_ids = [aws_security_group.allow_ssh[0].id]

tags = {
Name = "quickwit-ecs-bastion"
}
}

resource "aws_key_pair" "bastion_key" {
count = var.bastion_public_key != "" ? 1 : 0
key_name = "quickwit-ecs-bastion-key"
public_key = var.bastion_public_key
}
58 changes: 58 additions & 0 deletions distribution/ecs/example/kafka.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Example configuration for injecting SSL keys for securing a Kafka connection
# You can then create a secured Kafka source along these lines:
#
# version: 0.8
# source_id: kafka-source
# source_type: kafka
# num_pipelines: 2
# params:
# topic: your-topic
# client_params:
# bootstrap.servers: "your-kafka-broker.com"
# security.protocol: "SSL"
# ssl.ca.location: "/quickwit/keys/ca.pem"
# ssl.certificate.location: "/quickwit/keys/service.cert"
# ssl.key.location: "/quickwit/keys/service.key"


locals {
ca_pem = "echo \"$CA_PEM\" > /quickwit/cfg/ca.pem"
service_cert = "echo \"$SERVICE_CERT\" > /quickwit/cfg/service.cert"
service_key = "echo \"$SERVICE_KEY\" > /quickwit/cfg/service.key"
example_kafka_sidecar_container_definitions = {
kafka_key_init = {
name = "kafka_key_init"
essential = false
image = "busybox"
command = ["sh", "-c", "${local.ca_pem} && ${local.service_cert} && ${local.service_key}"]
enable_cloudwatch_logging = true
mount_points = [
{
sourceVolume = "quickwit-keys"
containerPath = "/quickwit/keys"
}
]
secrets = [
{
name = "CA_PEM"
valueFrom = "arn:aws:secretsmanager:eu-west-1:123456789:secret:your_kafka_ca_pem"
},
{
name = "SERVICE_CERT"
valueFrom = "arn:aws:secretsmanager:eu-west-1:123456789:secret:your_kafka_service_cert"
},
{
name = "SERVICE_KEY"
valueFrom = "arn:aws:secretsmanager:eu-west-1:123456789:secret:your_kafka_service_key"
}
]
}
}

example_kafka_sidecar_container_dependencies = [
{
condition = "SUCCESS"
containerName = "kafka_key_init"
}
]
}
92 changes: 92 additions & 0 deletions distribution/ecs/example/terraform.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
terraform {
backend "local" {}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.39.1"
}
}
}

provider "aws" {
region = "eu-west-1"
default_tags {
tags = {
provisioner = "terraform"
}
}
}

# resource "aws_ecr_repository" "quickwit" {
# name = "quickwit"
# force_delete = true
# image_tag_mutability = "MUTABLE"
# }

module "quickwit" {
source = "../quickwit"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
quickwit_ingress_cidr_blocks = [module.vpc.vpc_cidr_block]

## Optional configurations:

# quickwit_index_s3_prefix = "my-bucket/my-prefix"
# quickwit_domain = "quickwit"
# quickwit_image = aws_ecr_repository.quickwit.repository_url
# quickwit_cpu_architecture = "ARM64"

# quickwit_indexer = {
# desired_count = 1
# memory = 2048
# cpu = 1024
# }

# quickwit_metastore = {
# desired_count = 1
# memory = 512
# cpu = 256
# }

# quickwit_searcher = {
# desired_count = 1
# memory = 2048
# cpu = 1024
# }

# quickwit_control_plane = {
# memory = 512
# cpu = 256
# }

# quickwit_janitor = {
# memory = 512
# cpu = 256
# }

# rds_config = {
# instance_class = "db.t4g.micro"
# multi_az = false
# }

## Example logging configuration
# sidecar_container_definitions = {
# my_sidecar_container = see http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html
# }
# sidecar_container_dependencies = [{condition = "START", containerName = "my_sidecar_container"}]
# log_configuration = see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service#log_configuration
# enable_cloudwatch_logging = false

## Example Kafka key injection (see kafka.tf)
# sidecar_container_definitions = local.example_kafka_sidecar_container_definitions
# sidecar_container_dependencies = local.example_kafka_sidecar_container_dependencies
}


output "indexer_service_name" {
value = module.quickwit.indexer_service_name
}

output "searcher_service_name" {
value = module.quickwit.searcher_service_name
}
13 changes: 13 additions & 0 deletions distribution/ecs/example/vpc.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.3"

name = "quickwit-ecs"
cidr = "10.0.0.0/16"

azs = ["eu-west-1a", "eu-west-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]

enable_nat_gateway = true
}
Loading

0 comments on commit 0bb20f3

Please sign in to comment.