Skip to content

Commit

Permalink
v23.9.0 (#24)
Browse files Browse the repository at this point in the history
* Update README.md

* feat(do): validate construct/deconstruct flows

* feat(do): turn on tenant, bump version, pg backend

* Update README.md

* feat: update bridge/connect commands

* feat(aws): turn on tenant, bump versions

* fix: remove tenant_id from tags

* docs: update ii diagram
  • Loading branch information
VladyslavKurmaz authored Sep 24, 2023
1 parent ae7e570 commit af6280b
Show file tree
Hide file tree
Showing 48 changed files with 451 additions and 302 deletions.
2 changes: 1 addition & 1 deletion .env.template
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
TF_VAR_org_id=
TF_VAR_project_id=
TF_VAR_env_id=
#TF_VAR_tenant_id=
TF_VAR_tenant_id=

TF_TOKEN_app_terraform_io=

Expand Down
41 changes: 24 additions & 17 deletions .tln.conf
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,19 @@ const buildScript = (layers, reverse, fn) => {
const getBackendConfig = (backend, layer, env) => {
const cmds = [];
let params = '';
const name = [env.TF_VAR_project_id].concat(env.TLN_CLOUDS_STATE.split(',').map(v => {
const name = env.TLN_CLOUDS_STATE.split(',').map(v => {
if (v === 'project' )
return env.TF_VAR_project_id;
if (v === 'provider' )
return env.TLN_COMPONENT_ID;
else if (v === 'env' )
return env.TF_VAR_env_id;
else if (v === 'layer' )
return layer;
return null;
}));
else if (v === 'tenant' )
return env.TF_VAR_tenant_id;
return v;
});
const wName = name.join('-');
const pName = name.join('/');
if (backend) {
Expand All @@ -55,8 +59,9 @@ const getBackendConfig = (backend, layer, env) => {
cmds.push(`echo ' }' >> backend.tf`);
break;
case 'pg':
cmds.push(`echo ' backend "remote" {' >> backend.tf`);
cmds.push(`echo ' backend "pg" {' >> backend.tf`);
cmds.push(`echo ' conn_str = "${env.TF_VAR_backend_pg_conn_str}"' >> backend.tf`);
cmds.push(`echo ' schema_name = "${wName}"' >> backend.tf`);
cmds.push(`echo ' }' >> backend.tf`);
break;
case 's3':
Expand All @@ -65,7 +70,7 @@ const getBackendConfig = (backend, layer, env) => {
cmds.push(`echo ' bucket = "${env.TF_VAR_backend_s3_bucket}"' >> backend.tf`);
cmds.push(`echo ' key = "tfenvs/${pName}/terraform.tfstate"' >> backend.tf`);
cmds.push(`echo ' dynamodb_table = "${env.TF_VAR_backend_s3_dynamodb_table}"' >> backend.tf`);
cmds.push(`echo ' region = \\"${env.TF_VAR_backend_s3_region}"' >> backend.tf`);
cmds.push(`echo ' region = "${env.TF_VAR_backend_s3_region}"' >> backend.tf`);
cmds.push(`echo ' }' >> backend.tf`);
break;
}
Expand Down Expand Up @@ -103,16 +108,16 @@ module.exports = {
options: async (tln, args) => {
args
.prefix('TLN_CLOUDS')
.option('backend', { describe: 'Use cloud based terraform backend', default: null, type: 'string' })
.option('state', { describe: 'Define which level store the state: provider,env,layer', default: 'provider,env,layer', type: 'string' })
.option('init', { describe: 'Terraform init', default: false, type: 'boolean' })
.option('upgrade', { describe: 'Terraform upgrade mode for init', default: false, type: 'boolean' })
.option('plan', { describe: 'Terraform plan execution', default: false, type: 'boolean' })
.option('apply', { describe: 'Terraform apply execution', default: false, type: 'boolean' })
.option('auto-approve', { describe: 'Auto apprive apply & destroy', default: false, type: 'boolean' })
.option('layers', { describe: 'List of comma-separated layers application has', default: "network,managed", type: 'string' })
.option('bastion', { describe: 'Bastion in form user@ip', default: null, type: 'string' })
.option('bridge-port', { describe: 'Local port for bridge to bastion user@ip', default: '8888', type: 'string' })
.option('backend', { describe: 'Defines which backend provider should bu used (cloud, pg)', default: null, type: 'string' })
.option('state', { describe: 'Defines how store name will be built: project,provider,env,layer,tenant,<custom_string>', default: 'project,provider,env,layer', type: 'string' })
.option('init', { describe: 'Run Terraform init', default: false, type: 'boolean' })
.option('upgrade', { describe: 'Run Terraform upgrade mode for init', default: false, type: 'boolean' })
.option('plan', { describe: 'Run Terraform plan', default: false, type: 'boolean' })
.option('apply', { describe: 'Run Terraform apply', default: false, type: 'boolean' })
.option('auto-approve', { describe: 'Tun on auto approve for apply & destroy', default: false, type: 'boolean' })
.option('layers', { describe: 'Select which layers will be included', default: "network,managed", type: 'string' })
.option('bastion', { describe: 'Bastion address in form user@ip', default: null, type: 'string' })
.option('bridge-port', { describe: 'Local port for bridge to bastion ', default: '8888', type: 'string' })
;
},
env: async (tln, env) => {
Expand All @@ -135,10 +140,11 @@ module.exports = {
}
},
{ id: 'bridge', builder: async (tln, script) => {
const port = script.env.TLN_CLOUDS_BRIDGE_PORT;
switch (script.env.TLN_COMPONENT_ID) {
case 'aws':
script.set([`
ssh -i ./managed/${script.env.TF_VAR_env_id}-bastion-ssh-key.pem -L8888:127.0.0.1:${script.env.TLN_CLOUDS_BRIDGE_PORT} ${script.env.TLN_CLOUDS_BASTION}
ssh -i ./managed/${script.env.TF_VAR_env_id}-bastion-ssh-key.pem -L${port}:127.0.0.1:${port} ${script.env.TLN_CLOUDS_BASTION}
`]);
break;
case 'azure':
Expand All @@ -149,10 +155,11 @@ ssh -i ./managed/${script.env.TF_VAR_env_id}-bastion-ssh-key.pem -L8888:127.0.0.
}
},
{ id: 'connect', builder: async (tln, script) => {
const port = script.env.TLN_CLOUDS_BRIDGE_PORT;
switch (script.env.TLN_COMPONENT_ID) {
case 'aws':
script.set([`
export HTTPS_PROXY=127.0.0.1:8888
export HTTPS_PROXY=127.0.0.1:${port}
tln shell
`]);
break;
Expand Down
155 changes: 113 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,93 @@
# Description
Cloud agnostic IaC based SaaS skeleton.
## Cloud agnostic IaC based SaaS skeleton.
![Infrastructure Instance](ii.png)

## Features
Framework
* supports AWS, DO (Azure, GCP - in progress)
* provides Multi-tenancy feature via layers architecture (provider, network, managed, appl, tenant)
* implements easy-to-construct multiple environment approach (single env var)
* is based on IaC (Terraform)
* supports of multiple backend providers - Local, Cloud (PG, S3 - in progress)
* provides Multi-tenancy feature via layers architecture (Provider, Network, Managed, Appl, Tenant)
* implements easy-to-construct multiple environment approach, controls by one environment variable - **TF_VAR_env_id**
* IaC - Terraform, Helm
* supports of multiple backend providers - Local, Cloud, PG (S3 - in progress)

## Infrastructure Instance layers
| Layer | Description |
| ------------- | ------------- |
| Tenant | Is constructed during new tenant (customer) onboarding: DNS record creation (customerN.project.io), tenant DB creation etc. |
| Appl | Holds Application resources: DB server, Message broker cluster, DNS records (api.project.io) etc. |
| Managed | Constructs K8s cluster, runs security checks etc. |
| Network | VPC, pruvate & public networks, Bastion |
| Provider | Provider level configrations: SSL certificates, Docker registry etc. |

## Quick start
* Install [tln](https://www.npmjs.com/package/tln-cli)
* Goto **projects** folder from tln-cli installation above and clone repository
```
git clone --depth 1 --branch v23.7.0 [email protected]:project-talan/tln-clouds.git
git clone --depth 1 --branch v23.9.0 [email protected]:project-talan/tln-clouds.git && cd tln-clouds
```
> Important<br>
> Commands below assume that Terraform Cloud is used as a storage for states<br/>
> By skipping **--backend cloud** local backend will be used
* Use **.env.template** file as an examples and fill it with actual values
* root .env
```
TF_VAR_org_id=project-talan
TF_VAR_org_id=<your_terraform_cloud_org>
TF_VAR_project_id=tln-clouds
TF_VAR_env_id=dev
#TF_VAR_tenant_id=
TF_VAR_tenant_id=
TF_TOKEN_app_terraform_io=<your_token>
TF_TOKEN_app_terraform_io=<your_terraform_cloud_token>
```
* do/.env
### Digital Ocean
* Create **do/.env** file using **do/.env.template** as an example
```
DIGITALOCEAN_TOKEN=<your_token>
DIGITALOCEAN_TOKEN=<your_do_token>
TF_VAR_do_region=nyc3
TF_VAR_do_k8s_version=1.27.4-do.0
TF_VAR_do_k8s_nodes_min=1
TF_VAR_do_k8s_nodes_max=2
TF_VAR_do_k8s_nodes_size=s-2vcpu-2gb
```
* aws/.env
* Install dependencies
```
tln install do --depends
```
* Construct DO Dev infrastructure instance
```
tln construct do -- --backend cloud --init --plan --apply
```
* Verify access to the k8s cluster and install/uninstall ingress
* Create ssh session
```
tln shell do
```
```
tln nginx-ingress-install@k8s -- --ver 4.7.2
```
```
kubectl get pods --all-namespaces
```
AWS_ACCESS_KEY_ID=<your_token>
AWS_SECRET_ACCESS_KEY=<your_token>
```
tln nginx-ingress-status@k8s
```
* Use IP address from command outpu below to check access to the cluster using browser/curl
* Uninstall Ingress
```
tln nginx-ingress-uninstall@k8s
```
* Close ssh session
```
^d
```
* Deconstruct DO Dev infrastructure instance
```
tln deconstruct do -- --backend cloud --plan --apply
```
### AWS
* Create **aws/.env** file using **aws/.env.template** as an example
```
AWS_ACCESS_KEY_ID=<your_aws_id>
AWS_SECRET_ACCESS_KEY=<your_aws_key>
AWS_SESSION_TOKEN=
AWS_DEFAULT_REGION=eu-central-1
Expand All @@ -51,36 +99,59 @@ Framework
TF_VAR_aws_k8s_nodes_size=t3a.medium
TF_VAR_aws_k8s_nodes_disk=50
```
* NOTE. Commands below assume that Terraform Cloud is used as a storage for states
* Next commands will guide you to configure k8s infrastructure usign DO. By replacing **do** with **aws** you can have AWS based infrastructure
* Install dependencies
```
tln install do --depends
tln install aws --depends
```
* Construct DO Dev infrastructure instance
* Construct AWS Dev infrastructure instance
```
tln construct do -- --backend cloud --init --plan --apply
tln construct aws -- --backend cloud --init --plan --apply
```
* Verify access to the k8s cluster and install/uninstall ingress
```
tln shell do
```
```
tln nginx-ingress-install@k8s
```
```
kubectl get pods --all-namespaces
```
```
tln nginx-ingress-status@k8s
```
```
tln nginx-ingress-uninstall@k8s
```
```
^d
```
* Deconstruct DO Dev infrastructure instance
```
tln deconstruct do -- --backend cloud --plan --apply
```
* Open separate terminal and establish connection with bastion, use user@ip from previous command output (bastion_remote_address)
```
tln bridge aws -- --bastion user@ip
```
* Switch back to the original terminal and initiate session for kubectl
```
tln connect aws
```
```
tln nginx-ingress-install@k8s -- --ver 4.7.2
```
```
kubectl get pods --all-namespaces
```
```
tln nginx-ingress-status@k8s
```
* Use DNS address name from command output below to check access to the cluster using browser/curl
* Uninstall Ingress
```
tln nginx-ingress-uninstall@k8s
```
* Close both terminals
```
^d
```
* Deconstruct AWS Dev infrastructure instance
```
tln deconstruct aws -- --backend cloud --plan --apply
```
## Command line options
General format
```
tln [construct | deconstruct] [do | aws] [-u] -- [option, [option], ...]
```
| Option | Description | Example |
| ------------- | ------------- | ------------- |
| backend | Defines which backend provider should bu used (cloud, pg) | $ tln construct do -- --backend cloud <br /> $ tln construct aws -- --backend pg |
| state | Defines how store name will be built: project, provider, env, layer, tenant, <custom_string> | $ tln construct do -- --backend cloud -- state project,provider,env,layer <br /> will use tln-clouds-do-dev-managed Terraform Cloud workspace |
| init | Run Terraform init | $ tln construct aws -- --backend cloud --init |
| upgrade | Run Terraform upgrade mode for init | $ tln construct aws -- --backend cloud --init --upgrade |
| plan | Run Terraform plan | $ tln construct aws -- --backend cloud --plan |
| apply | Run Terraform apply | $ tln construct aws -- --backend cloud --apply |
| auto-approve | Tun on auto approve for apply & destroy | $ tln construct aws -- --backend cloud --apply --auto-approve |
| layers | Select which layers will be included | $ tln construct aws -- --backend cloud --apply --layers tenant <br /> will construct infrastructure for tenant layer only |
| bastion | Bastion address in form user@ip | $ tln bridge aws -- --bastion [email protected] <br /> will establish ssh connection with local box and bastion |
| bridge-port | Local port for bridge to bastion | $ tln connect aws -- --bridge-port 8888 <br /> will run shell with ssh connection into k8s cluster |
2 changes: 1 addition & 1 deletion aws/.tln.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module.exports = {
},
dotenvs: async (tln) => ['.env'],
inherits: async (tln) => [],
depends: async (tln) => ['kubectl-1.27.2', 'helm-3.12.2', 'terraform-1.5.3', 'aws-cli-2.13.1'],
depends: async (tln) => ['kubectl-1.27.2', 'helm-3.12.3', 'terraform-1.5.7', 'aws-cli-2.13.21'],
steps: async (tln) => [
{ id: 'test', builder: async (tln, script) => {
script.set(['aws ec2 describe-instances --output yaml --query "Reservations[*].Instances[*].{Instance:InstanceId}"']);
Expand Down
2 changes: 0 additions & 2 deletions aws/app/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,5 @@ module "shared" {
org_id = var.org_id
project_id = var.project_id
env_id = var.env_id
/*
tenant_id = var.tenant_id
*/
}
2 changes: 0 additions & 2 deletions aws/app/variables.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
variable "org_id" { type = string }
variable "project_id" { type = string }
variable "env_id" { type = string }
/*
variable "tenant_id" { type = string }
*/
4 changes: 2 additions & 2 deletions aws/app/versions.tf
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
terraform {
required_version = "= 1.5.3"
required_version = "= 1.5.7"

required_providers {
aws = {
source = "hashicorp/aws"
version = "5.9.0"
version = "5.17.0"
}
}
}
Loading

0 comments on commit af6280b

Please sign in to comment.