Skip to content

Commit 98a5160

Browse files
authored
Add a Terraform configuration to deploy lnt.llvm.org (#128)
This patch adds a Terraform configuration file that should allow deploying to an EC2 instance. It requires a few secrets to be made available to Github Actions.
1 parent 4d177ab commit 98a5160

File tree

7 files changed

+306
-0
lines changed

7 files changed

+306
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Deploy lnt.llvm.org
2+
3+
on:
4+
workflow_dispatch:
5+
# Eventually:
6+
# pull_request_target:
7+
# paths:
8+
# - '.github/workflows/deploy-lnt.llvm.org.yaml'
9+
# - 'deployment/*'
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
deploy:
16+
runs-on: ubuntu-24.04
17+
18+
steps:
19+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
20+
21+
- name: Setup Terraform
22+
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
23+
24+
- name: Configure AWS Credentials
25+
uses: aws-actions/configure-aws-credentials@00943011d9042930efac3dcd3a170e4273319bc8 # v5.1.0
26+
with:
27+
aws-region: us-west-2
28+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
29+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
30+
31+
- name: Initialize Terraform
32+
run: terraform -chdir=deployment init
33+
34+
- name: Plan Terraform changes
35+
run: terraform -chdir=deployment plan
36+
37+
- name: Apply Terraform changes
38+
if: ${{ github.ref == 'refs/heads/main' }} # only apply changes on pushes to main
39+
run: terraform -chdir=deployment apply -auto-approve

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
.tox/
44
/llvm_lnt.egg-info
55
build
6+
deployment/.terraform
67
dist
78
docs/_build
89
lnt/server/ui/static/docs

deployment/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
This directory contains configuration files to deploy lnt.llvm.org.
2+
3+
The https://lnt.llvm.org instance gets re-deployed automatically whenever changes
4+
are made to the configuration files under `deployment/` on the `main` branch via
5+
a Github Action. Manually deploying the instance is also possible by directly using
6+
Terraform:
7+
8+
```bash
9+
aws configure # provide appropriate access keys
10+
terraform -chdir=deployment init
11+
terraform -chdir=deployment plan # to see what will be done
12+
terraform -chdir=deployment apply
13+
```
14+
15+
At a high level, lnt.llvm.org is running in a Docker container on an EC2 instance.
16+
The database is stored in an independent EBS storage that gets attached and detached
17+
to/from the EC2 instance when it is created/destroyed, but the EBS storage has its own
18+
independent life cycle (because we want the data to outlive any specific EC2 instance).
19+
20+
The state used by Terraform to track the current status of the instance, EBS storage, etc
21+
is located in a S3 bucket defined in the Terraform file. It is updated automatically when
22+
changes are performed via the `terraform` command-line. Terraform is able to access that
23+
data via the AWS credentials that are set up by `aws configure`.

deployment/compose.env.tpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
LNT_DB_PASSWORD=${__db_password__}
2+
LNT_AUTH_TOKEN=${__auth_token__}
3+
LNT_IMAGE=${__lnt_image__}
4+
LNT_HOST_PORT=${__lnt_host_port__}

deployment/ec2-startup.sh

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/bin/bash
2+
3+
#
4+
# This is the startup script that gets executed when the EC2 instance running lnt.llvm.org
5+
# is brought up. This script references some files under /etc/lnt that are put into place
6+
# by cloud-init, which is specified in the Terraform configuration file.
7+
#
8+
9+
set -e
10+
11+
echo "Installing docker"
12+
sudo yum update -y
13+
sudo yum install -y docker
14+
docker --version
15+
16+
echo "Installing docker compose"
17+
sudo mkdir -p /usr/local/lib/docker/cli-plugins
18+
sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m) \
19+
-o /usr/local/lib/docker/cli-plugins/docker-compose
20+
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
21+
docker compose version
22+
23+
echo "Starting the Docker service"
24+
sudo service docker start
25+
sudo systemctl enable docker # also ensure the Docker service starts on reboot
26+
27+
if ! lsblk --output FSTYPE -f /dev/sdh | grep --quiet ext4; then
28+
echo "Formatting /dev/sdh -- this is a new EBS volume"
29+
sudo mkfs -t ext4 /dev/sdh
30+
else
31+
echo "/dev/sdh already contains a filesystem -- reusing previous EBS volume"
32+
fi
33+
34+
echo "Mounting EBS volume with persistent information at /persistent-state"
35+
sudo mkdir /persistent-state
36+
sudo mount /dev/sdh /persistent-state
37+
38+
echo "Creating folders to map volumes in the Docker container to locations on the EC2 instance"
39+
sudo mkdir -p /persistent-state/var/lib/lnt
40+
(cd /var/lib && ln -s /persistent-state/var/lib/lnt lnt)
41+
sudo mkdir -p /persistent-state/var/lib/postgresql
42+
(cd /var/lib && ln -s /persistent-state/var/lib/postgresql postgresql)
43+
sudo mkdir -p /var/log/lnt # logs are not persisted
44+
45+
echo "Starting LNT service with Docker compose"
46+
sudo docker compose --file /etc/lnt/compose.yaml \
47+
--file /etc/lnt/ec2-volume-mapping.yaml \
48+
--env-file /etc/lnt/compose.env \
49+
up --detach

deployment/ec2-volume-mapping.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# This file maps volumes in the Docker container to actual locations on the EC2 instance.
3+
# We basically bind the volumes inside the Docker image to the same filesystem location
4+
# on the EC2 instance (e.g. /var/lib/lnt -> /var/lib/lnt) for ease of access.
5+
#
6+
7+
volumes:
8+
instance:
9+
driver: local
10+
driver_opts:
11+
o: bind
12+
type: none
13+
device: /var/lib/lnt
14+
15+
logs:
16+
driver: local
17+
driver_opts:
18+
o: bind
19+
type: none
20+
device: /var/log/lnt
21+
22+
database:
23+
driver: local
24+
driver_opts:
25+
o: bind
26+
type: none
27+
device: /var/lib/postgresql

deployment/main.tf

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#
2+
# Terraform file for deploying lnt.llvm.org.
3+
#
4+
5+
terraform {
6+
backend "s3" {
7+
bucket = "lnt.llvm.org-test-bucket" # TODO: Adjust this for the real LLVM Foundation account
8+
key = "terraform.tfstate"
9+
region = "us-west-2"
10+
encrypt = true
11+
}
12+
}
13+
14+
locals {
15+
availability_zone = "us-west-2a"
16+
}
17+
18+
provider "aws" {
19+
region = "us-west-2"
20+
}
21+
22+
#
23+
# Setup secrets and other variables
24+
#
25+
# Note that the LNT database password and the LNT authentication token for destructive actions
26+
# must be stored in the AWS Secrets Manager under a secrets named `lnt.llvm.org-secrets`, and
27+
# with the `lnt-db-password` and `lnt-auth-token` keys respectively. This secrets must exist
28+
# in whatever AWS account is currently authenticated when running Terraform.
29+
#
30+
data "aws_secretsmanager_secret" "lnt_secrets" {
31+
name = "lnt.llvm.org-secrets"
32+
}
33+
34+
data "aws_secretsmanager_secret_version" "lnt_secrets_latest" {
35+
secret_id = data.aws_secretsmanager_secret.lnt_secrets.id
36+
}
37+
38+
locals {
39+
# The Docker image to use for the webserver part of the LNT service
40+
lnt_image = "d9ffa5317a9a42a1d2fa337cba97ec51d931f391"
41+
42+
# The port on the EC2 instance used by the Docker webserver for communication
43+
lnt_host_port = "80"
44+
45+
# The database password for the lnt.llvm.org database.
46+
lnt_db_password = jsondecode(data.aws_secretsmanager_secret_version.lnt_secrets_latest.secret_string)["lnt-db-password"]
47+
48+
# The authentication token to perform destructive operations on lnt.llvm.org.
49+
lnt_auth_token = jsondecode(data.aws_secretsmanager_secret_version.lnt_secrets_latest.secret_string)["lnt-auth-token"]
50+
}
51+
52+
#
53+
# Setup the EC2 instance
54+
#
55+
data "aws_ami" "amazon_linux_2023" {
56+
most_recent = true
57+
owners = ["amazon"]
58+
59+
filter {
60+
name = "name"
61+
values = ["al2023-ami-ecs-hvm-*-kernel-*-x86_64"]
62+
}
63+
}
64+
65+
data "cloudinit_config" "startup_scripts" {
66+
base64_encode = true
67+
68+
part {
69+
filename = "ec2-startup.sh"
70+
content_type = "text/x-shellscript"
71+
content = file("${path.module}/ec2-startup.sh")
72+
}
73+
74+
part {
75+
content_type = "text/cloud-config"
76+
content = yamlencode({
77+
write_files = [
78+
{
79+
path = "/etc/lnt/compose.yaml"
80+
permissions = "0400" # read-only for owner
81+
content = file("${path.module}/../docker/compose.yaml")
82+
},
83+
{
84+
path = "/etc/lnt/ec2-volume-mapping.yaml"
85+
permissions = "0400" # read-only for owner
86+
content = file("${path.module}/ec2-volume-mapping.yaml")
87+
},
88+
{
89+
path = "/etc/lnt/compose.env"
90+
permissions = "0400" # read-only for owner
91+
content = templatefile("${path.module}/compose.env.tpl", {
92+
__db_password__ = local.lnt_db_password,
93+
__auth_token__ = local.lnt_auth_token,
94+
__lnt_image__ = local.lnt_image,
95+
__lnt_host_port__ = local.lnt_host_port,
96+
})
97+
}
98+
]
99+
})
100+
}
101+
}
102+
103+
resource "aws_security_group" "server" {
104+
name = "lnt.llvm.org/server-security-group"
105+
description = "Allow SSH and HTTP traffic"
106+
107+
ingress {
108+
description = "Allow incoming SSH traffic from anywhere"
109+
from_port = 22
110+
to_port = 22
111+
protocol = "tcp"
112+
cidr_blocks = ["0.0.0.0/0"]
113+
}
114+
115+
ingress {
116+
description = "Allow incoming HTTP traffic from anywhere"
117+
from_port = 80
118+
to_port = 80
119+
protocol = "tcp"
120+
cidr_blocks = ["0.0.0.0/0"]
121+
}
122+
123+
egress {
124+
description = "Allow outgoing traffic to anywhere"
125+
from_port = 0
126+
to_port = 0
127+
protocol = "-1"
128+
cidr_blocks = ["0.0.0.0/0"]
129+
}
130+
}
131+
132+
resource "aws_instance" "server" {
133+
ami = data.aws_ami.amazon_linux_2023.id
134+
availability_zone = local.availability_zone
135+
instance_type = "t2.micro" # TODO: Adjust the size of the real instance
136+
associate_public_ip_address = true
137+
security_groups = [aws_security_group.server.name]
138+
tags = {
139+
Name = "lnt.llvm.org/server"
140+
}
141+
142+
user_data_base64 = data.cloudinit_config.startup_scripts.rendered
143+
}
144+
145+
#
146+
# Setup the EBS volume attached to the instance that stores the DB
147+
# and other instance-related configuration (e.g. the schema files,
148+
# profiles and anything else that should persist).
149+
#
150+
resource "aws_ebs_volume" "persistent_state" {
151+
availability_zone = local.availability_zone
152+
size = 128 # GiB
153+
type = "gp2"
154+
tags = {
155+
Name = "lnt.llvm.org/persistent-state"
156+
}
157+
}
158+
159+
resource "aws_volume_attachment" "persistent_state_attachment" {
160+
instance_id = aws_instance.server.id
161+
volume_id = aws_ebs_volume.persistent_state.id
162+
device_name = "/dev/sdh"
163+
}

0 commit comments

Comments
 (0)