diff --git a/README.md b/README.md index 9909ecf..f2612e9 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,15 @@ This repo is using [Opentofu](https://opentofu.org/) and other tools to create c ## Platform Support Status -| | VM | Docker | K8s | -| --------------------- | ------ | ------ | ---- | -| Microsoft Azure | VM ✅ | ACI 🔲 | AKS 🔲 | -| Amazon Web Services | EC2 🚧 | ECS 🔲 | EKS 🔲 | -| Google Cloud Platform | GCE 🔲 | GAE 🔲 | GKE 🔲 | -| Render | ❌ | DOCKER ✅(See 1) | ❌ | -| Heroku | ❌ | HCR 🔲 | ❌ | -| DigitalOcean | Droplets 🔲 | CR 🔲 | DOKS 🔲 | -| Native K8s | ❌ | ❌ | K8S ✅(See 2) | +| | VM | Docker | K8s | +|-----------------------|-------------|-----------------|--------------| +| Microsoft Azure | VM ✅ | ACI 🔲 | AKS 🔲 | +| Amazon Web Services | EC2 ✅ | ECS 🔲 | EKS 🔲 | +| Google Cloud Platform | GCE 🚧 | GAE 🔲 | GKE 🔲 | +| Render | ❌ | DOCKER ✅(See 1) | ❌ | +| Heroku | ❌ | HCR 🔲 | ❌ | +| DigitalOcean | Droplets 🔲 | CR 🔲 | DOKS 🔲 | +| Native K8s | ❌ | ❌ | K8S ✅(See 2) | The following icons are used to represent the status of support for each platform: - ✅: completed diff --git a/modules/aws-ec2/.env.example b/modules/aws-ec2/.env.example new file mode 100644 index 0000000..2a9316e --- /dev/null +++ b/modules/aws-ec2/.env.example @@ -0,0 +1,16 @@ +GTFS_URL=https://api.cityofkingston.ca/gtfs/gtfs.zip +TEST_API_KEY=test +VEHICLE_POSITIONS_URL=https://api.cityofkingston.ca/gtfs-realtime/vehicleupdates.pb +TRIP_UPDATES_URL=https://api.cityofkingston.ca/gtfs-realtime/tripupdates.pb +ALERTS_URL=https://api.cityofkingston.ca/gtfs-realtime/alerts.pb +REFRESH_INTERVAL=30 +AGENCY_ID=Kingston Transit +TZ=America/Toronto +GOOGLE_MAPS_API_KEY= +GOOGLE_MAPS_CHANNEL_ID= +GOOGLE_MAPS_CLIENT_ID= +# Your Domain Name, leave blank if you don't have one +DOMAIN=oba.example.com +# OBA image version. You can find the available versions at: +# https://hub.docker.com/r/opentransitsoftwarefoundation/onebusaway-api-webapp/tags +OBA_VERSION=latest diff --git a/modules/aws-ec2/.terraform.lock.hcl b/modules/aws-ec2/.terraform.lock.hcl new file mode 100644 index 0000000..44c49e1 --- /dev/null +++ b/modules/aws-ec2/.terraform.lock.hcl @@ -0,0 +1,82 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/hashicorp/aws" { + version = "5.55.0" + hashes = [ + "h1:PySaEq9/Y1CILERJXiJXzZONvyzck2oiapSxvJVpgmc=", + "zh:5f3b59c7c66ee0cd2de72e3cece150bc6c15d4b5cef7f477b5edc8e2cf4a1a4d", + "zh:83c4869bd79df190290462531912c71ac44378bce006c0e0642b7bf7a0515d25", + "zh:87bcf0a21810867c0d30e526b9f9e78bc4ac503f3eb699de1d31c3d59d01006e", + "zh:c6a941a9c3fd3a91e2cb6a0987838659ed002cf0a0bbcb4b9471415cdf5c3540", + "zh:c6abf77975feb99a2fc5ff86477d7d173a10d07daa4cebbcb46944aa76ce2754", + "zh:c7d215071cd6afcb6d11befd95214cda9013402ed739707fcdbc55928b0a1d5d", + "zh:d21cf10a39552e6e2df4f55c6d7426befdc8a94d1d471b9eccf567ccfbd42497", + "zh:decd64542e1183fc32227dd1faaca79926035ca8d332b085d9ca8874432ad4f3", + "zh:e5677331ebeee0ae8cbca33323c6aff401875f41e4ce1d7a2afefae5752d27d0", + "zh:fb932872eab2e6d96332ef306c6562ca721ca78164fad109945de48f50465d25", + ] +} + +provider "registry.opentofu.org/hashicorp/local" { + version = "2.5.1" + hashes = [ + "h1:rAU/hplI5EeV/gyAGzDNX/JxYkNTE0xzxt2ChaHCjfY=", + "zh:031c2c2070672b7e78e0aa15560839278dc57fe7cf1e58a617ac13c67b31d5fb", + "zh:1ef64ea4f8382cd538a76f3d319f405d18130dc3280f1c16d6aaa52a188ecaa4", + "zh:422ce45691b2f384dbd4596fdc8209d95cb43d85a82aaa0173089d38976d6e96", + "zh:7415fbd8da72d9363ba55dd8115837714f9534f5a9a518ec42268c2da1b9ed2f", + "zh:92aa22d071339c8ef595f18a9f9245c287266c80689f5746b26e10eaed04d542", + "zh:9cd0d99f5d3be835d6336c19c4057af6274e193e677ecf6370e5b0de12b4aafe", + "zh:a8c1525b389be5809a97f02aa7126e491ba518f97f57ed3095a3992f2134bb8f", + "zh:b336fa75f72643154b07c09b3968e417a41293358a54fe03efc0db715c5451e6", + "zh:c66529133599a419123ad2e42874afbd9aba82bd1de2b15cc68d2a1e665d4c8e", + "zh:c7568f75ba6cb7c3660b69eaab8b0e4278533bd9a7a4c33ee6590cc7e69743ea", + ] +} + +provider "registry.opentofu.org/hashicorp/null" { + version = "3.2.2" + hashes = [ + "h1:MxFOpWumJFKtyg4CufbHwFGQ+A896aW7SvNTP3nzI3E=", + "zh:00e5877d19fb1c1d8c4b3536334a46a5c86f57146fd115c7b7b4b5d2bf2de86d", + "zh:1755c2999e73e4d73f9de670c145c9a0dc5a373802799dff06a0e9c161354163", + "zh:2b29d706353bc9c4edda6a2946af3322abe94372ffb421d81fa176f1e57e33be", + "zh:34f65259c6d2bd51582b6da536e782b181b23725782b181193b965f519fbbacd", + "zh:370f6eb744475926a1fa7464d82d46ad83c2e1148b4b21681b4cec4d75b97969", + "zh:5950bdb23b4fcc6431562d7eba3dea37844aa4220c4da2eb898ae3e4d1b64ec4", + "zh:8f3d5c8d4b9d497fec36953a227f80c76d37fc8431b683a23fb1c42b9cccbf8a", + "zh:8f6eb5e65c047bf490ad3891efecefc488503b65898d4ee106f474697ba257d7", + "zh:a7040eed688316fe00379574c72bb8c47dbe2638b038bb705647cbf224de8f72", + "zh:e561f28df04d9e51b75f33004b7767a53c45ad96e3375d86181ba1363bffbc77", + ] +} + +provider "registry.opentofu.org/hashicorp/template" { + version = "2.2.0" + hashes = [ + "h1:cFVlpc+2y6My/2Nm0KTcAqMjk/p8JTAMMGvFaAEbkDk=", + "zh:374c28bafc43cd65e578cb209efc9eee4c1cec7618f451528e928db98059e8c8", + "zh:6a2982e70fbc2ab2668d624c648ef2eb32243c1a1185246b03991a7a21326db9", + "zh:af83169c21bb13f141510a349e1f70cf7d893247a269bd71cad74dd22f1df0f5", + "zh:b81a5bedc91a1a81b938c393247248d6c3d1bd8ea685541f9c858908c0afb6b3", + "zh:de15486244af2d29d44d510d647cd6e0b1408e89952261013c572b7c9bfd744b", + ] +} + +provider "registry.opentofu.org/hashicorp/tls" { + version = "4.0.5" + hashes = [ + "h1:cTXfOXdSicFFq+zPIGNiaEDw1NwUJzVtDIC5b/9caeg=", + "zh:05a7dc3ac92005485714f87541ad6d0d478988b478c5774227a7d39b01660050", + "zh:547e0def44080456169bf77c21037aa6dc9e7f3e644a8f6a2c5fc3e6c15cf560", + "zh:6842b03d050ae1a4f1aaed2a2b1ca707eae84ae45ae492e4bb57c3d48c26e1f1", + "zh:6ced0a9eaaba12377f3a9b08df2fd9b83ae3cb357f859eb6aecf24852f718d9a", + "zh:766bcdf71a7501da73d4805d05764dcb7c848619fa7c04b3b9bd514e5ce9e4aa", + "zh:84cc8617ce0b9a3071472863f43152812e5e8544802653f636c866ef96f1ed34", + "zh:b1939e0d44c89315173b78228c1cf8660a6924604e75ced7b89e45196ce4f45e", + "zh:ced317916e13326766427790b1d8946c4151c4f3b0efd8f720a3bc24abe065fa", + "zh:ec9ff3412cf84ba81ca88328b62c17842b803ef406ae19152c13860b356b259c", + "zh:ff064f0071e98702e542e1ce00c0465b7cd186782fe9ccab8b8830cac0f10dd4", + ] +} diff --git a/modules/aws-ec2/docker-compose.caddy.yml b/modules/aws-ec2/docker-compose.caddy.yml new file mode 100644 index 0000000..2db3e6d --- /dev/null +++ b/modules/aws-ec2/docker-compose.caddy.yml @@ -0,0 +1,15 @@ +version: '3' + +services: + caddy: + image: lucaslorentz/caddy-docker-proxy:ci-alpine + ports: + - 80:80 + - 443:443 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - caddy_data:/data + restart: unless-stopped + +volumes: + caddy_data: {} diff --git a/modules/aws-ec2/init.tpl b/modules/aws-ec2/init.tpl new file mode 100644 index 0000000..bdea445 --- /dev/null +++ b/modules/aws-ec2/init.tpl @@ -0,0 +1,37 @@ +#!/bin/bash +# install Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sh get-docker.sh + +# install Docker Compose +curl -L "https://github.com/docker/compose/releases/download/v2.27.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +chmod +x /usr/local/bin/docker-compose + +cd /home/${user} + +# clone repo +git clone https://github.com/onebusaway/onebusaway-docker.git + +cd /home/${user}/onebusaway-docker + +# create .env file +echo "${docker_env}" > .env + + +# onebusaway-api-webapp depends on mysql, normally this will handle by docker-compose +# but in Azure, the mysql container will not be ready when onebusaway-api-webapp starts +# which leads to the error `Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set` +# so we need to start mysql container first +docker-compose -f docker-compose.prod.yml up -d oba_database + +sleep 5s + +# start Docker Compose +docker-compose -f docker-compose.prod.yml up -d + +if [ -n "${caddy}" ]; then + # create docker-compose.caddy.yml + echo "${docker_compose}" > docker-compose.caddy.yml + # start Caddy + docker-compose -f docker-compose.caddy.yml up -d +fi diff --git a/modules/aws-ec2/main.tf b/modules/aws-ec2/main.tf new file mode 100644 index 0000000..be730aa --- /dev/null +++ b/modules/aws-ec2/main.tf @@ -0,0 +1,153 @@ +provider "aws" { + region = var.region +} + +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" +} + + +resource "aws_security_group" "main" { + vpc_id = aws_vpc.main.id + name = var.security_group_name + + ingress { + description = "HTTP" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "HTTPS" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "HTTP on 8080" + from_port = 8080 + to_port = 8080 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "SSH" + 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_internet_gateway" "main" { + vpc_id = aws_vpc.main.id +} + +resource "aws_route_table" "public" { + vpc_id = aws_vpc.main.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.main.id + } +} + +resource "aws_route_table_association" "public" { + subnet_id = aws_subnet.main.id + route_table_id = aws_route_table.public.id +} + +resource "aws_subnet" "main" { + vpc_id = aws_vpc.main.id + cidr_block = "10.0.1.0/24" + availability_zone = var.availability_zone + map_public_ip_on_launch = true +} + +resource "aws_key_pair" "ssh_key" { + key_name = "ec2-key-pair" + public_key = tls_private_key.ssh_key.public_key_openssh +} + +resource "aws_instance" "main" { + ami = data.aws_ami.ubuntu.id + instance_type = var.instance_type + key_name = aws_key_pair.ssh_key.key_name + vpc_security_group_ids = [aws_security_group.main.id] + subnet_id = aws_subnet.main.id + associate_public_ip_address = true + + user_data = base64encode(templatefile("${path.module}/init.tpl", { + user = var.username, + caddy = var.caddy, + docker_compose = file("${path.module}/docker-compose.caddy.yml"), + docker_env = file("${path.module}/.env") + })) +} + +resource "tls_private_key" "ssh_key" { + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "local_sensitive_file" "private_key" { + content = tls_private_key.ssh_key.private_key_pem + filename = "${path.module}/ssh/id_rsa" +} + +# Only use this resource if you are using Linux or MacOS +resource "null_resource" "set_permission" { + depends_on = [local_sensitive_file.private_key] + + provisioner "local-exec" { + command = "chmod 0600 ${local_sensitive_file.private_key.filename}" + } +} + +resource "local_file" "public_key" { + content = tls_private_key.ssh_key.public_key_openssh + filename = "${path.module}/ssh/id_rsa.pub" +} + +# Fetch the latest Ubuntu Server AMI for the provided region and instance type +data "aws_ami" "ubuntu" { + most_recent = true + owners = ["099720109477"] # Canonical + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +# Remove ssh keys after destroy +resource "null_resource" "remove_ssh_keys" { + triggers = { + always_run = timestamp() + } + + # replace `rm` with `del` if you are using Windows + provisioner "local-exec" { + command = "rm ${path.module}\\ssh\\id_rsa ${path.module}\\ssh\\id_rsa.pub" + when = destroy + } +} + + diff --git a/modules/aws-ec2/output.tf b/modules/aws-ec2/output.tf new file mode 100644 index 0000000..580955e --- /dev/null +++ b/modules/aws-ec2/output.tf @@ -0,0 +1,9 @@ +output "private_ip" { + value = aws_instance.main.private_ip + description = "The private IP address of the EC2 instance." +} + +output "public_ip" { + value = aws_instance.main.public_ip + description = "The public IP address of the EC2 instance." +} diff --git a/modules/aws-ec2/readme.md b/modules/aws-ec2/readme.md new file mode 100644 index 0000000..6c52968 --- /dev/null +++ b/modules/aws-ec2/readme.md @@ -0,0 +1,59 @@ +# Deployment Guide for AWS EC2 + +AWS EC2 is a good choice for deploying Onebusaway server, it provides a wide range of instance types and sizes, and you can choose the best one for your use case. This guide will show you how to deploy Onebusaway server on AWS EC2. + +## Prerequisites + +1. An AWS account, if you don't have one, you can create a free account [here](https://aws.amazon.com/free/). + +2. AWS CLI, you can install it by following the instructions [here](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). + +3. Opentofu, an open-source Terraform alternative, you can install it by following the instructions [here](https://opentofu.org/docs/intro/install/). + +4. Make sure you install *ALL* the prerequisites before you start the deployment. +## Steps + +1. Clone this repository to your local machine, you can run +```bash +git clone https://github.com/OneBusAway/onebusaway-deployment.git +``` + +2. Change the directory to `modules/aws-ec2`, you can use command +```bash +cd onebusaway-deployment/modules/aws-ec2 +``` +3. Configure your AWS credentials using AWS CLI, you can find guide [here](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html). + +4. Initialize the project, this will download the necessary plugins and providers for the project +```bash +tofu init +``` + +5. Create your custom `.env` file by copying the `.env.example` file, you can find parameters meaning in [onebusaway-docker](https://github.com/OneBusAway/onebusaway-docker/) +```bash +cp .env.example .env +``` + +6. Modify the `.env` and `variables.tf` file according to your needs. You can modify `DOMAIN` to your own domain, this will be used to generate SSL certificate by [Caddy](https://caddyserver.com/). If you want to configure your own certs instead of automatic HTTPS, you can leave `caddy` blank, you should also check `main.tf` file to make sure it works with your OS. + +7. Deploy the project +```bash +tofu apply +``` + +8. Tofu will automatically generate ssh key pairs in the `ssh` folder, you can connect to the server using `ssh -i ./ssh/id_rsa @`. + +9. After the deployment is finished, you need to wait a few minutes to let the server start. You can access the Onebusaway server by visiting `http://:8080`. You can find the instance IP in the AWS Management Console. + +10. (optional) If you configured your own domain, you should go to your domain provider and add an A record pointing to the instance IP. For example, if you use Cloudflare, you can follow the instructions [here](https://support.cloudflare.com/hc/en-us/articles/360019093151-Managing-DNS-records-in-Cloudflare). Caddy will configure certs based on the record. You should be able to visit your server by visiting `https://` after a few minutes. + +## Clean up +If you want to shut down the server and clean up the resources, you can run the following command +```bash +tofu destroy +``` + +Opentofu will destroy all resources created by this project, including the EC2 instance, network, ssh key pairs, and other resources. + +## Conclusion +This guide shows you how to deploy Onebusaway server on AWS EC2 using Opentofu. If you have any questions or suggestions, feel free to open an issue in this repository. \ No newline at end of file diff --git a/modules/aws-ec2/ssh/.gitkeep b/modules/aws-ec2/ssh/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/modules/aws-ec2/variables.tf b/modules/aws-ec2/variables.tf new file mode 100644 index 0000000..1e02b66 --- /dev/null +++ b/modules/aws-ec2/variables.tf @@ -0,0 +1,35 @@ +variable "region" { + type = string + description = "AWS region to deploy resources." + default = "us-east-1" +} + +variable "availability_zone" { + type = string + description = "AWS availability zone in the region to deploy resources." + default = "us-east-1a" +} + +variable "username" { + type = string + description = "username for the VM." + default = "ubuntu" +} + +variable "caddy" { + description = "Wheather to use Caddy or not, leave empty to disable" + type = string + default = "1" +} + +variable "instance_type" { + type = string + default = "t3.medium" + description = "EC2 instance type, you can find details here: https://cloudprice.net/aws/ec2/instances/t3.medium." +} + +variable "security_group_name" { + type = string + description = "Name of the security group." + default = "oba-security-group" +}