Update deployment with opentofu to Azure with changes of quizroom

This commit is contained in:
fserres 2024-12-09 11:45:35 -05:00
parent 2f1a96fdba
commit 4f36fcf6cd
12 changed files with 429 additions and 307 deletions

View file

@ -10,6 +10,15 @@ https://opentofu.org/docs/intro/install/
https://learn.microsoft.com/en-us/cli/azure/install-azure-cli#install
### Se connecter à Azure et récupérer l'id de l'abonnement Azure
Pour se connecter à Azure, faites la commande suivante
`az login`
Avec cette commande, vous allez sélectionner un abonnement Azure. Copiez l'id de l'abonnement, vous en aurez besoin
dans l'étape suivant.
### Modifier les configurations
Créer un fichier **terraform.tfvars** sur la base du fichier **terraform.tfvars.example** dans le répertoire **azure**.
@ -18,12 +27,8 @@ Toutes les variables, leur description et leur valeur par défaut sont disponibl
Créer un fichier **auth_config.json** sur la base du fichier **auth_config.json.example** dans le répertoire **opentofu**.
Modifier le fichier **default.conf** afin de pointer vers le bon url pour le backend et le frontend.
L'url du frontend est défini comme suit: http://\<container_group_app_dns>.\<location>.azurecontainer.io:\<frontend_port>".
L'url du backend est défini comme suit: http://\<container_group_app_dns>.\<location>.azurecontainer.io:\<backend_port>".
Location est sans espace et en minuscule.
Par défaut, l'url du frontend est http://evaluetonsavoir-app.canadacentral.azurecontainer.io:5173.
Par défaut, l'url du backend est http://evaluetonsavoir-app.canadacentral.azurecontainer.io:3000.
L'url est défini comme suit: http://<container_group_app_dns>.<location>.cloudapp.azure.com.
Par défaut, l'url est http://evaluetonsavoir.canadacentral.cloudapp.azure.com/
### Lancer le déploiement

View file

@ -1,64 +1,67 @@
resource "azurerm_container_group" "app" {
name = var.container_group_app_name
location = azurerm_resource_group.resource_group.location
# Create Virtual Machine
resource "azurerm_linux_virtual_machine" "vm" {
name = var.vm_name
resource_group_name = azurerm_resource_group.resource_group.name
os_type = var.container_group_os
dns_name_label = var.container_group_app_dns
location = azurerm_resource_group.resource_group.location
size = var.vm_size
admin_username = var.vm_user
admin_password = var.vm_password
disable_password_authentication = false
image_registry_credential {
server = var.image_registry_server
username = var.image_registry_user
password = var.image_registry_password
network_interface_ids = [azurerm_network_interface.nic.id]
os_disk {
name = var.vm_os_disk_name
caching = "ReadWrite"
storage_account_type = var.vm_os_disk_type
}
container {
name = var.frontend_image_name
image = var.frontend_image
cpu = var.frontend_image_cpu
memory = var.frontend_image_memory
environment_variables = {
VITE_BACKEND_URL = "http://${var.container_group_router_dns}.${lower(replace(azurerm_resource_group.resource_group.location, " ", ""))}.azurecontainer.io"
source_image_reference {
publisher = var.vm_image_publisher
offer = var.vm_image_offer
sku = var.vm_image_plan
version = var.vm_image_version
}
ports {
port = var.frontend_port
}
}
custom_data = base64encode(<<-EOT
#!/bin/bash
sudo apt-get update -y
sudo apt-get install -y docker.io
sudo apt-get install -y docker-compose
sudo systemctl start docker
sudo systemctl enable docker
container {
name = var.backend_image_name
image = var.backend_image
cpu = var.backend_image_cpu
memory = var.backend_image_memory
sudo usermod -aG docker ${var.vm_user}
sudo newgrp docker
environment_variables = {
PORT = var.backend_port
MONGO_URI = azurerm_cosmosdb_account.cosmosdb_account.connection_strings[0]
MONGO_DATABASE = azurerm_cosmosdb_mongo_collection.cosmosdb_mongo_collection.database_name
EMAIL_SERVICE = var.backend_email_service
SENDER_EMAIL = var.backend_email_sender
EMAIL_PSW = var.backend_email_password
JWT_SECRET = var.backend_jwt_secret
SESSION_Secret = var.backend_session_secret
SITE_URL = "http://${var.container_group_router_dns}.${lower(replace(azurerm_resource_group.resource_group.location, " ", ""))}.azurecontainer.io"
FRONTEND_PORT = var.frontend_port
USE_PORTS = var.backend_use_port
AUTHENTICATED_ROOMS = var.backend_use_auth_student
}
su - ${var.vm_user} -c '
ports {
port = var.backend_port
}
curl -o auth_config.json \
"https://${azurerm_storage_account.storage_account.name}.file.core.windows.net/${azurerm_storage_share.backend_storage_share.name}/auth_config.json${data.azurerm_storage_account_sas.storage_access.sas}"
volume {
name = azurerm_storage_share.backend_storage_share.name
mount_path = var.backend_volume_mount_path
share_name = azurerm_storage_share.backend_storage_share.name
storage_account_name = azurerm_storage_account.storage_account.name
storage_account_key = azurerm_storage_account.storage_account.primary_access_key
}
}
curl -L -o docker-compose.yaml ${var.docker_compose_url}
depends_on = [azurerm_cosmosdb_mongo_collection.cosmosdb_mongo_collection]
export VITE_BACKEND_URL=http://${var.dns}.${lower(replace(azurerm_resource_group.resource_group.location, " ", ""))}.cloudapp.azure.com
export PORT=${var.backend_port}
export MONGO_URI="${azurerm_cosmosdb_account.cosmosdb_account.primary_mongodb_connection_string}"
export MONGO_DATABASE=${azurerm_cosmosdb_mongo_collection.cosmosdb_mongo_collection.database_name}
export EMAIL_SERVICE=${var.backend_email_service}
export SENDER_EMAIL=${var.backend_email_sender}
export EMAIL_PSW="${var.backend_email_password}"
export JWT_SECRET=${var.backend_jwt_secret}
export SESSION_Secret=${var.backend_session_secret}
export SITE_URL=http://${var.dns}.${lower(replace(azurerm_resource_group.resource_group.location, " ", ""))}.cloudapp.azure.com
export FRONTEND_PORT=${var.frontend_port}
export USE_PORTS=${var.backend_use_port}
export AUTHENTICATED_ROOMS=${var.backend_use_auth_student}
export QUIZROOM_IMAGE=${var.quizroom_image}
docker-compose up -d
'
EOT
)
depends_on = [
azurerm_cosmosdb_mongo_collection.cosmosdb_mongo_collection,
data.azurerm_storage_account_sas.storage_access]
}

View file

@ -4,6 +4,13 @@ resource "azurerm_cosmosdb_account" "cosmosdb_account" {
location = azurerm_resource_group.resource_group.location
offer_type = "Standard"
kind = "MongoDB"
mongo_server_version = "7.0"
is_virtual_network_filter_enabled = true
virtual_network_rule {
id = azurerm_subnet.subnet.id
}
capabilities {
name = "EnableMongo"

View file

@ -2,7 +2,7 @@ terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
version = "~> 4.0"
}
}
required_version = ">= 1.0"
@ -10,4 +10,5 @@ terraform {
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}

87
opentofu/azure/network.tf Normal file
View file

@ -0,0 +1,87 @@
# Create Virtual Network
resource "azurerm_virtual_network" "vnet" {
name = var.vnet_name
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
address_space = ["10.0.0.0/16"]
}
# Create Subnet
resource "azurerm_subnet" "subnet" {
name = var.subnet_name
resource_group_name = azurerm_resource_group.resource_group.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
service_endpoints = ["Microsoft.AzureCosmosDB"]
}
# Create Public IP Address
resource "azurerm_public_ip" "public_ip" {
name = var.public_ip_name
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
allocation_method = "Static"
domain_name_label = var.dns
}
resource "azurerm_network_security_group" "nsg" {
name = var.nsg_name
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
security_rule {
name = "SSH"
priority = 1000
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = var.nsg_ssh_ip_range
destination_address_prefix = "*"
}
security_rule {
name = "HTTP"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = var.nsg_http_ip_range
destination_address_prefix = "*"
}
security_rule {
name = "HTTPS"
priority = 1002
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = var.nsg_https_ip_range
destination_address_prefix = "*"
}
}
# Create Network Interface
resource "azurerm_network_interface" "nic" {
name = var.network_interface_name
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.public_ip.id
}
}
resource "azurerm_network_interface_security_group_association" "example" {
network_interface_id = azurerm_network_interface.nic.id
network_security_group_id = azurerm_network_security_group.nsg.id
}

View file

@ -1,3 +1,4 @@
# Create Resource Group
resource "azurerm_resource_group" "resource_group" {
name = var.resource_group_name
location = var.location

View file

@ -1,34 +0,0 @@
resource "azurerm_container_group" "router" {
name = var.container_group_router_name
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
os_type = var.container_group_os
dns_name_label = var.container_group_router_dns
image_registry_credential {
server = var.image_registry_server
username = var.image_registry_user
password = var.image_registry_password
}
container {
name = var.router_image_name
image = var.router_image
cpu = var.router_image_cpu
memory = var.router_image_memory
ports {
port = var.router_port
}
volume {
name = azurerm_storage_share.router_storage_share.name
mount_path = var.router_volume_mount_path
share_name = azurerm_storage_share.router_storage_share.name
storage_account_name = azurerm_storage_account.storage_account.name
storage_account_key = azurerm_storage_account.storage_account.primary_access_key
}
}
depends_on = [azurerm_container_group.app]
}

View file

@ -9,15 +9,7 @@ resource "azurerm_storage_account" "storage_account" {
}
resource "azurerm_storage_share" "backend_storage_share" {
name = var.backend_volume_share_name
storage_account_name = azurerm_storage_account.storage_account.name
quota = 1
depends_on = [azurerm_storage_account.storage_account]
}
resource "azurerm_storage_share" "router_storage_share" {
name = var.router_volume_share_name
name = var.backend_storage_share_name
storage_account_name = azurerm_storage_account.storage_account.name
quota = 1
@ -35,18 +27,48 @@ resource "null_resource" "upload_file" {
EOT
}
provisioner "local-exec" {
command = <<EOT
az storage file upload \
--account-name ${azurerm_storage_account.storage_account.name} \
--share-name ${azurerm_storage_share.router_storage_share.name} \
--source ../default.conf \
--path default.conf
EOT
depends_on = [azurerm_storage_share.backend_storage_share]
}
depends_on = [
azurerm_storage_share.backend_storage_share,
azurerm_storage_share.router_storage_share
]
locals {
# Get the current timestamp (UTC)
current_timestamp = timestamp()
start_time = local.current_timestamp
expiry_time = timeadd(local.current_timestamp, "1h")
}
data "azurerm_storage_account_sas" "storage_access" {
connection_string = azurerm_storage_account.storage_account.primary_connection_string
signed_version = "2022-11-02"
services {
file = true
blob = false
queue = false
table = false
}
resource_types {
object = true
container = false
service = false
}
permissions {
read = true
write = false
delete = false
list = true
add = false
create = false
update = false
process = false
tag = false
filter = false
}
start = local.start_time
expiry = local.expiry_time
depends_on = [null_resource.upload_file]
}

View file

@ -1,7 +1,7 @@
image_registry_user = "username"
image_registry_password = "password"
image_registry_server = "index.docker.io"
backend_session_secret = "session_secret"
subscription_id = "subscription_id"
backend_session_secret = "secret"
backend_email_sender = "mail@mail.com"
backend_email_password = "password"
backend_jwt_secret = "jwt_secret"
vm_user = "username"
vm_password = "password"

View file

@ -1,117 +1,26 @@
variable "subscription_id" {
description = "The azure subscription id"
type = string
}
variable "resource_group_name" {
description = "The name of the resource group"
type = string
default = "evaluetonsavoir"
}
variable "container_group_app_name" {
description = "The name of the app container group"
type = string
default = "evaluetonsavoir-app"
}
variable "container_group_app_dns" {
description = "The dns name of the app container group"
type = string
default = "evaluetonsavoir-app"
}
variable "container_group_router_name" {
description = "The name of the router container group"
type = string
default = "evaluetonsavoir"
}
variable "container_group_router_dns" {
description = "The dns name of the router container group"
type = string
default = "evaluetonsavoir"
}
variable "container_group_os" {
description = "The os type of the container group"
type = string
default = "Linux"
}
variable "image_registry_server" {
description = "The image registry server"
type = string
default = "index.docker.io"
}
variable "image_registry_user" {
description = "The image registry username"
type = string
default = "username"
}
variable "image_registry_password" {
description = "The image registry password"
type = string
default = "password"
}
variable "location" {
description = "The location for resources"
type = string
default = "Canada Central"
}
variable "frontend_image" {
description = "Docker image for the frontend"
type = string
default = "fuhrmanator/evaluetonsavoir-frontend:latest"
}
variable "frontend_image_name" {
description = "Docker image name for the frontend"
type = string
default = "frontend"
}
variable "frontend_image_cpu" {
description = "Docker image cpu for the frontend"
type = string
default = "1"
}
variable "frontend_image_memory" {
description = "Docker image memory for the frontend"
type = string
default = "2"
}
variable "frontend_port" {
description = "The frontend port"
type = number
default = 5173
}
variable "backend_image" {
description = "Docker image for the backend"
type = string
default = "fuhrmanator/evaluetonsavoir-backend:latest"
}
variable "backend_image_name" {
description = "Docker image name for the backend"
type = string
default = "backend"
}
variable "backend_image_cpu" {
description = "Docker image cpu for the backend"
type = string
default = "1"
}
variable "backend_image_memory" {
description = "Docker image memory for the backend"
type = string
default = "2"
}
variable "backend_port" {
description = "The backend port"
type = number
@ -133,7 +42,6 @@ variable "backend_use_auth_student" {
variable "backend_session_secret" {
description = "The backend session secret"
type = string
default = "secret"
}
variable "backend_email_service" {
@ -145,70 +53,19 @@ variable "backend_email_service" {
variable "backend_email_sender" {
description = "The email address used to send email"
type = string
default = "mail@mail.com"
}
variable "backend_email_password" {
description = "The email password"
type = string
default = "password"
}
variable "backend_jwt_secret" {
description = "The secret used to sign the jwt"
type = string
default = "secret"
}
variable "router_image" {
description = "Docker image for the router"
type = string
default = "nginx:alpine"
}
variable "router_image_name" {
description = "Docker image name for the router"
type = string
default = "nginx"
}
variable "router_image_cpu" {
description = "Docker image cpu for the router"
type = string
default = "1"
}
variable "router_image_memory" {
description = "Docker image memory for the router"
type = string
default = "2"
}
variable "router_port" {
description = "The router port"
type = number
default = 80
}
variable "router_volume_mount_path" {
description = "The router volume mount path"
type = string
default = "/etc/nginx/conf.d"
}
variable "router_volume_share_name" {
description = "The router volume share name"
type = string
default = "nginx-config-share"
}
variable "backend_volume_mount_path" {
description = "The backend volume mount path"
type = string
default = "/usr/src/app/serveur/config/auth"
}
variable "backend_volume_share_name" {
variable "backend_storage_share_name" {
description = "The backend volume share name"
type = string
default = "auth-config-share"
@ -231,3 +88,127 @@ variable "cosmosdb_account_name" {
type = string
default = "evaluetonsavoircosmosdb"
}
variable "vnet_name" {
description = "The name of the virtual network"
type = string
default = "evaluetonsavoirVnet"
}
variable "subnet_name" {
description = "The name of the subnet"
type = string
default = "evaluetonsavoirSubnet"
}
variable "public_ip_name" {
description = "The name of the public ip"
type = string
default = "evaluetonsavoirPublicIp"
}
variable "nsg_name" {
description = "The name of the network security group"
type = string
default = "evaluetonsavoirnsg"
}
variable "nsg_ssh_ip_range" {
description = "The ip range that can access to the port 22 using the network security group"
type = string
default = "0.0.0.0/0"
}
variable "nsg_http_ip_range" {
description = "The ip range that can access to the port 80 using the network security group"
type = string
default = "0.0.0.0/0"
}
variable "nsg_https_ip_range" {
description = "The ip range that can access to the port 443 using the network security group"
type = string
default = "0.0.0.0/0"
}
variable "network_interface_name" {
description = "The name of the network interface"
type = string
default = "evaluetonsavoirNetworkInterface"
}
variable "dns" {
description = "The dns of the public ip"
type = string
default = "evaluetonsavoir"
}
variable "vm_name" {
description = "The name of the virtual machine"
type = string
default = "evaluetonsavoir"
}
variable "vm_size" {
description = "The size of the virtual machine"
type = string
default = "Standard_B2s"
}
variable "vm_user" {
description = "The username of the virtual machine"
type = string
}
variable "vm_password" {
description = "The password of the virtual machine"
type = string
}
variable "vm_os_disk_name" {
description = "The name of the os disk of the virtual machine"
type = string
default = "evaluetonsavoirOsDisk"
}
variable "vm_os_disk_type" {
description = "The type of the os disk of the virtual machine"
type = string
default = "Standard_LRS"
}
variable "vm_image_publisher" {
description = "The publisher of the image of the virtual machine"
type = string
default = "Canonical"
}
variable "vm_image_offer" {
description = "The id of the image of the virtual machine"
type = string
default = "0001-com-ubuntu-server-jammy"
}
variable "vm_image_plan" {
description = "The plan of the image of the virtual machine"
type = string
default = "22_04-lts"
}
variable "vm_image_version" {
description = "The version of the image of the virtual machine"
type = string
default = "latest"
}
variable "docker_compose_url" {
description = "The url from where the docker compose file is downloaded"
type = string
default = "https://raw.githubusercontent.com/ets-cfuhrman-pfe/EvalueTonSavoir/refs/heads/main/opentofu/docker-compose.yaml"
}
variable "quizroom_image" {
description = "The image of the quiz room"
type = string
default = "ghrc.io/fuhrmanator/evaluetonsavoir-quizroom:latest"
}

View file

@ -1,31 +0,0 @@
upstream frontend {
server evaluetonsavoir-app.canadacentral.azurecontainer.io:5173;
}
upstream backend {
server evaluetonsavoir-app.canadacentral.azurecontainer.io:3000;
}
server {
listen 80;
location /api {
rewrite /backend/(.*) /$1 break;
proxy_pass http://backend;
}
location /socket.io {
rewrite /backend/(.*) /$1 break;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_hide_header 'Access-Control-Allow-Origin';
}
location / {
proxy_pass http://frontend;
}
}

View file

@ -0,0 +1,80 @@
services:
frontend:
image: ghcr.io/ets-cfuhrman-pfe/evaluetonsavoir-frontend:latest
container_name: frontend
ports:
- "5173:5173"
environment:
VITE_BACKEND_URL: ${VITE_BACKEND_URL:-http://localhost:3000}
networks:
- quiz_network
restart: always
backend:
image: ghcr.io/ets-cfuhrman-pfe/evaluetonsavoir-backend:latest
container_name: backend
ports:
- "3000:3000"
environment:
PORT: ${PORT:-3000}
MONGO_URI: ${MONGO_URI:-mongodb://mongo:27017/evaluetonsavoir}
MONGO_DATABASE: ${MONGO_DATABASE:-evaluetonsavoir}
EMAIL_SERVICE: ${EMAIL_SERVICE:-gmail}
SENDER_EMAIL: ${SENDER_EMAIL:-infoevaluetonsavoir@gmail.com}
EMAIL_PSW: ${EMAIL_PSW:-'vvml wmfr dkzb vjzb'}
JWT_SECRET: ${JWT_SECRET:-haQdgd2jp09qb897GeBZyJetC8ECSpbFJe}
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:5173}
SESSION_Secret: ${SESSION_Secret:-'lookMomImQuizzing'}
SITE_URL: ${SITE_URL:-http://localhost}
FRONTEND_PORT: ${FRONTEND_PORT:-5173}
USE_PORTS: ${USE_PORTS:-false}
AUTHENTICATED_ROOMS: ${AUTHENTICATED_ROOMS:-false}
QUIZROOM_IMAGE: ${QUIZROOM_IMAGE:-ghrc.io/fuhrmanator/evaluetonsavoir-quizroom:latest}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./auth_config.json:/usr/src/app/serveur/auth_config.json
networks:
- quiz_network
restart: always
quizroom:
image: ghcr.io/ets-cfuhrman-pfe/evaluetonsavoir-quizroom:latest
container_name: quizroom
ports:
- "4500:4500"
depends_on:
- backend
networks:
- quiz_network
restart: always
nginx:
image: ghcr.io/ets-cfuhrman-pfe/evaluetonsavoir-router:latest
container_name: nginx
ports:
- "80:80"
depends_on:
- backend
- frontend
networks:
- quiz_network
restart: always
watchtower:
image: containrrr/watchtower
container_name: watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- TZ=America/Montreal
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_DEBUG=true
- WATCHTOWER_INCLUDE_RESTARTING=true
- WATCHTOWER_SCHEDULE=0 0 5 * * * # At 5 am everyday
restart: always
networks:
quiz_network:
name: evaluetonsavoir_quiz_network
driver: bridge