first commit

This commit is contained in:
Paul Ernst 2024-01-20 22:28:11 +00:00
commit 98cb157593
21 changed files with 513 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.terraform*
terraform.tfstate*

23
README.md Normal file
View File

@ -0,0 +1,23 @@
### Architecture Ref. Card 03
Für das Projekt kann ein eigenes Schlüsselpaar verwendet werden. Der Name des öffentlichen Schlüssels ist per default auf "ansible.pub" gesetzt - kann aber in der variables.tf angepasst werden. Der private Schlüssel könnte auch irgendwo anders liegen - in der variables.tf anpassen!
Terraform installiert eine RDS-Instanz (mariadb) und eine EC2-Instanz. Auf der EC2-Instanz wird zusätzlich ein mariadb-Server via Ansible installiert, da die Applikation die Datenbank-URL hart kodiert hat...
Ansible wird direkt durch Terraform gestartet. Man könnte das auch separat machen und die Ausgabe von "tofu output" oder "terraform output" direkt weiter verwursten.
1. Erzeugen eines Schlüsselpaares
```
ssh-keygen -f keys/ansible -P ''
```
2. tofu oder terraform init
3. tofu oder terraform plan -out="name des jobs z.B. jokesdb"
4. tofu oder terraform apply "name des jobs z.B. jokesdb"
5. have fun
Was man noch so alles machen könnte:
1. Portumleitung ingress 80 -> localhost 8080 oder besser
2. Reverse-Proxy (z.B. nginx) und stattdessen ingress 80 -> ingress 443 und ingress 443 nach localhost 8080.
3. Zertifikat mit LetsEncrypt drauf packen
4. Applikation direkt auf dem Server kompilieren
5. Applikation patchen, dass sie auch DDB_URI unterstützt...

8
ansible/all.yml Normal file
View File

@ -0,0 +1,8 @@
---
- hosts:
- all
roles:
- db
- web
become: true
gather_facts: false

9
ansible/database.yml Normal file
View File

@ -0,0 +1,9 @@
---
- name: install and configure database
hosts:
- all
roles:
- db
become: true
gather_facts: false

View File

@ -0,0 +1,12 @@
INSERT INTO `section` (`id`, `name`) VALUES
(1, 'Flachwitze'),
(2, 'Schwarzer Humor');
INSERT INTO `joke` (`id`, `section_idfs`, `text`, `rating`, `creation_date`) VALUES
(1, 1, 'Kunde: \"Ich möchte Ihren Chef sprechen!\"\r\nSekretärin: \"Geht leider nicht, er ist nicht da!\"\r\nKunde: \"Ich hab ihn doch durchs Fenster gesehen!\"\r\nSekretärin: \"Er Sie auch!\"', 5, '2014-01-08 21:39:40'),
(2, 1, 'Der Verwaltungsrat zum CEO:\r\n\"Na, wie macht sich denn der neue Buchhalter?\"\r\nCEO: \"Toll, dieser Mann!\"\r\nVerwaltungsrat: \"Was kann er denn so besonderes?\"\r\nCEO: \"Er ist gelernter Friseur, er kann frisieren!\"', 3, '2014-01-08 21:42:41'),
(3, 1, 'Chef: \"Müller, Sie sind das beste Pferd in meinem Stall!\"\r\nMüller: \"Wirklich, Chef?\"\r\nChef: \"Ja, Sie machen den meisten Mist!\"', 5, '2014-01-08 21:43:20'),
(6, 2, 'Was steht auf dem Grabstein eines Mathematikers?\r\n\"Damit hat er nicht gerechnet.\"', 3, '2021-04-06 12:47:17');

View File

@ -0,0 +1,26 @@
DROP TABLE IF EXISTS section;
CREATE TABLE `section` (
`id` int(11) NOT NULL,
`name` varchar(255) COLLATE utf8_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
DROP TABLE IF EXISTS joke;
CREATE TABLE `joke` (
`id` int(11) NOT NULL,
`section_idfs` int(11) NOT NULL,
`text` text COLLATE utf8_bin NOT NULL,
`rating` int(11) NOT NULL,
`creation_date` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
--
-- Indizes für die Tabelle `joke`
--
ALTER TABLE `joke` ADD PRIMARY KEY (`id`);
ALTER TABLE `section` ADD PRIMARY KEY (`id`);
ALTER TABLE `joke` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;
ALTER TABLE `section` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

View File

@ -0,0 +1,3 @@
---
- name: restart mariadb
service: name=mysql state=restarted

View File

@ -0,0 +1,52 @@
---
- name: update
apt: update_cache=yes
ignore_errors: yes
- name: install mariadb server because jokesdb has localhost hard coded
apt: name=mariadb-server state=latest
notify: restart mariadb
- name: install mariadb client
apt: name=mariadb-client state=latest
- name: install python3-pymysql
apt: name=python3-pymysql state=latest
- name: create a new database with name "{{ db_name }}"
community.mysql.mysql_db:
name: "{{ db_name }}"
state: present
login_unix_socket: /run/mysqld/mysqld.sock
- name: create database user with all database privileges
community.mysql.mysql_user:
name: "{{ db_username }}"
password: "{{ db_password }}"
priv: "{{ db_name }}.*:ALL"
state: present
login_unix_socket: /run/mysqld/mysqld.sock
- name: copy database schema file
copy:
src: files/schema.sql
dest: /tmp
- name: copy database data file
copy:
src: files/data.sql
dest: /tmp
- name: import db schema
community.mysql.mysql_db:
state: import
name: "{{ db_name }}"
target: /tmp/schema.sql
login_unix_socket: /run/mysqld/mysqld.sock
- name: import db data
community.mysql.mysql_db:
state: import
name: "{{ db_name }}"
target: /tmp/data.sql
login_unix_socket: /run/mysqld/mysqld.sock

View File

@ -0,0 +1,12 @@
INSERT INTO `section` (`id`, `name`) VALUES
(1, 'Flachwitze'),
(2, 'Schwarzer Humor');
INSERT INTO `joke` (`id`, `section_idfs`, `text`, `rating`, `creation_date`) VALUES
(1, 1, 'Kunde: \"Ich möchte Ihren Chef sprechen!\"\r\nSekretärin: \"Geht leider nicht, er ist nicht da!\"\r\nKunde: \"Ich hab ihn doch durchs Fenster gesehen!\"\r\nSekretärin: \"Er Sie auch!\"', 5, '2014-01-08 21:39:40'),
(2, 1, 'Der Verwaltungsrat zum CEO:\r\n\"Na, wie macht sich denn der neue Buchhalter?\"\r\nCEO: \"Toll, dieser Mann!\"\r\nVerwaltungsrat: \"Was kann er denn so besonderes?\"\r\nCEO: \"Er ist gelernter Friseur, er kann frisieren!\"', 3, '2014-01-08 21:42:41'),
(3, 1, 'Chef: \"Müller, Sie sind das beste Pferd in meinem Stall!\"\r\nMüller: \"Wirklich, Chef?\"\r\nChef: \"Ja, Sie machen den meisten Mist!\"', 5, '2014-01-08 21:43:20'),
(6, 2, 'Was steht auf dem Grabstein eines Mathematikers?\r\n\"Damit hat er nicht gerechnet.\"', 3, '2021-04-06 12:47:17');

View File

@ -0,0 +1,26 @@
DROP TABLE IF EXISTS section;
CREATE TABLE `section` (
`id` int(11) NOT NULL,
`name` varchar(255) COLLATE utf8_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
DROP TABLE IF EXISTS joke;
CREATE TABLE `joke` (
`id` int(11) NOT NULL,
`section_idfs` int(11) NOT NULL,
`text` text COLLATE utf8_bin NOT NULL,
`rating` int(11) NOT NULL,
`creation_date` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
--
-- Indizes für die Tabelle `joke`
--
ALTER TABLE `joke` ADD PRIMARY KEY (`id`);
ALTER TABLE `section` ADD PRIMARY KEY (`id`);
ALTER TABLE `joke` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;
ALTER TABLE `section` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

View File

@ -0,0 +1,3 @@
---
- name: restart mariadb
service: name=mysql state=restarted

View File

@ -0,0 +1,72 @@
---
- name: install jre
apt: name=openjdk-21-jre-headless state=latest
# the next steps we can skip here because we run the db-role first
# thanks to the java app wich has the db uri hard coded
#
#- name: install mariadb-client
# apt: name=mariadb-client state=latest
#
#- name: install PyMySQL
# apt: name=python3-pymysql state=latest
#
#- name: copy database schema file
# copy:
# src: files/schema.sql
# dest: /tmp
#
#- name: copy database data file
# copy:
# src: files/data.sql
# dest: /tmp
- name: add user for jokesdb
ansible.builtin.user:
name: jokesdb
comment: user to run application jokesdb
create_home: True
home: /var/jokesdb
- name: that destination path exists
file:
path: /var/jokesdb/bin
state: directory
- name: copy architecture-refcard-03-0.0.1-SNAPSHOT.jar
copy:
src: files/architecture-refcard-03-0.0.1-SNAPSHOT.jar
dest: /var/jokesdb/bin
# we run this only because we want to show that it works
- name: import db schema
community.mysql.mysql_db:
state: import
name: jokedb
login_host: "{{ db_address }}"
login_user: "{{ db_username }}"
login_password: "{{ db_password }}"
target: /tmp/schema.sql
- name: import db data
community.mysql.mysql_db:
state: import
name: "{{ db_name }}"
login_host: "{{ db_address }}"
login_user: "{{ db_username }}"
login_password: "{{ db_password }}"
target: /tmp/data.sql
- name: create default file for application
ansible.builtin.template:
src: templates/jokesdb.j2
dest: /etc/default/jokesdb
- name: create service file to start the application
ansible.builtin.template:
src: templates/jokesdb_service.j2
dest: /etc/systemd/system/jokesdb.service
- name: start jokesdb
systemd: state=started name=jokesdb daemon_reload=yes

View File

@ -0,0 +1,5 @@
DDB_USERNAME="{{ db_username }}"
DDB_PASSWORD="{{ db_password }}"
DDB_URI="mysql://{{ db_address }}:{{ db_port }}/{{ db_name }}"
JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64"
JAR_FILE="/var/jokesdb/bin/architecture-refcard-03-0.0.1-SNAPSHOT.jar"

View File

@ -0,0 +1,19 @@
[Unit]
Description=JokesDB
After=syslog.target network.target
[Service]
EnvironmentFile=-/etc/default/jokesdb
SuccessExitStatus=143
User=jokesdb
Group=jokesdb
Type=simple
WorkingDirectory=/var/jokesdb
ExecStart=/usr/bin/env ${JAVA_HOME}/bin/java -DDB_USERNAME=${DDB_USERNAME} -DDB_PASSWORD=${DDB_PASSWORD} -jar ${JAR_FILE}
ExecStop=/bin/kill -15 $MAINPID
[Install]
WantedBy=multi-user.target

8
ansible/web.yml Normal file
View File

@ -0,0 +1,8 @@
---
- name: install and configure database server and start application
hosts:
- all
roles:
- web
become: true
gather_facts: false

45
instances.tf Normal file
View File

@ -0,0 +1,45 @@
resource "aws_db_instance" "mariadb" {
allocated_storage = 20
storage_type = "gp2"
engine = "mariadb"
engine_version = "10.6.14"
instance_class = "db.t3.micro"
db_name = var.db_name
username = var.db_username
password = var.db_password
port = var.db_port
skip_final_snapshot = true
vpc_security_group_ids = [aws_security_group.rds.id]
db_subnet_group_name = aws_db_subnet_group.private.name
}
resource "aws_instance" "ec2_instance" {
instance_type = "t3.micro"
ami = data.aws_ami.ubuntu_22_04.id
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.ec2.id]
key_name = aws_key_pair.ansible.key_name
lifecycle {
ignore_changes = [ami]
}
provisioner "local-exec" {
command = "ansible-playbook -u ubuntu --private-key='${var.private_key}' -i '${aws_instance.ec2_instance.public_ip},' --extra-vars 'web_public_ip=${aws_instance.ec2_instance.public_ip} db_address=${aws_db_instance.mariadb.address} db_name=${var.db_name} db_username=${var.db_username} db_password=${var.db_password} db_port=${var.db_port}' ansible/all.yml"
interpreter = ["/bin/bash", "-c"]
environment = {
ANSIBLE_HOST_KEY_CHECKING = "False"
}
}
}
data "aws_ami" "ubuntu_22_04" {
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"]
}

26
main.tf Normal file
View File

@ -0,0 +1,26 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
data "aws_availability_zones" "available" {
state = "available"
}
provider "aws" {
region = var.region
}
resource "aws_vpc" "main" {
cidr_block = var.main_cidr
enable_dns_hostnames = true
}
resource "aws_key_pair" "ansible" {
key_name = "ansible"
public_key = file(var.public_key)
}

95
networking.tf Normal file
View File

@ -0,0 +1,95 @@
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = var.public_cidr
map_public_ip_on_launch = true
}
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, 10 + count.index)
availability_zone = element(data.aws_availability_zones.available.names, count.index)
tags = {
Name = "private_${data.aws_availability_zones.available.names[count.index]}"
}
}
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" {
route_table_id = aws_route_table.public.id
subnet_id = aws_subnet.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table_association" "private" {
count = length(aws_subnet.private)
route_table_id = aws_route_table.private.id
subnet_id = aws_subnet.private[count.index].id
}
resource "aws_security_group" "ec2" {
name = "ec2-allow-ssh-http"
description = "Security group for the EC2 instance"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 8080
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"]
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "rds" {
name = "rds-allow-sql"
description = "Security group for the RDS instance"
vpc_id = aws_vpc.main.id
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.ec2.id]
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_db_subnet_group" "private" {
name = "db_subnet_group"
subnet_ids = [for subnet in aws_subnet.private : subnet.id]
}

27
output.tf Normal file
View File

@ -0,0 +1,27 @@
# Output data for input in ansible if ansible will be started manually like with:
# "--extra-vars=$(terraform output --json | jq 'with_entries(.value |= .value)')"
output "web_public_ip" {
description = "The public IP address of the web server"
value = aws_instance.ec2_instance.public_ip
}
output "db_address" {
description = "The endpoint of the database"
value = aws_db_instance.mariadb.address
}
output "db_name" {
description = "The name of the database"
value = var.db_name
}
output "db_username" {
description = "The user for the database"
value = var.db_username
}
output "db_password" {
description = "The password for the database"
value = var.db_password
}
output "db_port" {
description = "The port of the database"
value = var.db_port
}

40
variables.tf Normal file
View File

@ -0,0 +1,40 @@
variable "public_cidr" {
description = "Cidr block main"
default = "10.0.0.0/24"
}
variable "main_cidr" {
description = "Cidr block main"
default = "10.0.0.0/16"
}
variable "region" {
description = "Region for deployment"
default = "us-west-2"
}
variable "db_name" {
description = "The name of the database"
default = "jokedb"
}
variable "db_username" {
description = "The user for the database"
default = "jokedbuser"
}
variable "db_password" {
description = "The password for the database"
default = "a4d2410bcc42ebc32af364e15b4bcd5e"
}
variable "db_port" {
description = "The port of the database"
default = "3306"
}
variable "ssh_key" {
description = "Public private key to access the ec2 instance"
default = "./keys/ansible"
}
variable "private_key" {
description = "Private SSH key to access the ec2 instance"
default = "./keys/ansible"
}
variable "public_key" {
description = "Public SSH key to access the ec2 instance"
default = "./keys/ansible.pub"
}