Aws (Cloud)
AWS - Amazon Web Services
Finally, there are really low cost AWS services for developers who want to explore the AWS ecosystem, like AWS Free Tier services and AWS Lightsail ($3.5 -> $5 USD/Month services).
Configure an AWS account
- Create a Lightsail account.
- Create an admin-user (best practices) using web interface
from https://docs.aws.amazon.com/lambda/latest/dg/setup.html
and https://docs.aws.amazon.com/IAM/latest/UserGuide/getting-started_create-admin-group.html
go to https://console.aws.amazon.com/iam/ click in Groups -> Create New Group use "admin-group" name attach "AdministratorAccess" policy confirm "Create Group"
go back to https://console.aws.amazon.com/iam/ click in Users -> Add user use "admin-user" name select "Programmatic access" select "admin-group" confirm "Create user"
NOTE: keep the "Access key ID" and the "Secret access key"
Install and configure AWS client
Install and configure aws cli for first use
Configure aws cli to use admin-user profile
NOTE: for this example we will use "eu-west-1" region
aws --profile admin-user configure # put the "Access key ID" # put the "Secret access key" # use "eu-west-1" # use "json" aws --profile admin-user iam list-users
Create a custom ssh keypair
ssh-keygen -q -t rsa -b 2048 -N '' -f ~/.ssh/aws-keypair chmod 400 ~/.ssh/aws-keypair
Install Terraform
Terraform allows to manage Infrastructure as Code, so that you can programmatically define (and versioning) your infrastructure using code and setup virtual servers and other resources in the Cloud in few minutes.
To install, follow Terraform_(Application) instructions.
Setup a new EC2 machine
Use AWS lightsail simplified service
Note: the source code is available at GitLab - terraform_aws_lightsail_module.
What do you need:
- an email and a phone number for identity verification
For the Nano virtual machine (1CPU, 512Mb, 20GB) the first month is free.
Find a bundle virtual server
aws --profile admin-user lightsail get-bundles --no-include-inactive --output table --query 'bundles[*].{Type:instanceType,Cpu:cpuCount,Disk:diskSizeInGb,Memory:ramSizeInGb,Transfer:transferPerMonthInGb,Price:price,Id:bundleId} | sort_by([],&Memory) | sort_by([],&Cpu) | sort_by([],&Price)' ------------------------------------------------------------------------------ | GetBundles | +-----+-------+-------------------+---------+--------+-----------+-----------+ | Cpu | Disk | Id | Memory | Price | Transfer | Type | +-----+-------+-------------------+---------+--------+-----------+-----------+ | 1 | 20 | nano_2_0 | 0.5 | 3.5 | 1024 | nano | | 1 | 40 | micro_2_0 | 1.0 | 5.0 | 2048 | micro | | 1 | 30 | nano_win_2_0 | 0.5 | 8.0 | 1024 | nano | | 1 | 60 | small_2_0 | 2.0 | 10.0 | 3072 | small | | 1 | 40 | micro_win_2_0 | 1.0 | 12.0 | 2048 | micro | | 1 | 60 | small_win_2_0 | 2.0 | 20.0 | 3072 | small | | 2 | 80 | medium_2_0 | 4.0 | 20.0 | 4096 | medium | | 2 | 80 | medium_win_2_0 | 4.0 | 40.0 | 4096 | medium | | 2 | 160 | large_2_0 | 8.0 | 40.0 | 5120 | large | | 2 | 160 | large_win_2_0 | 8.0 | 70.0 | 5120 | large | | 4 | 320 | xlarge_2_0 | 16.0 | 80.0 | 6144 | xlarge | | 4 | 320 | xlarge_win_2_0 | 16.0 | 120.0 | 6144 | xlarge | | 8 | 640 | 2xlarge_2_0 | 32.0 | 160.0 | 7168 | 2xlarge | | 8 | 640 | 2xlarge_win_2_0 | 32.0 | 240.0 | 7168 | 2xlarge | +-----+-------+-------------------+---------+--------+-----------+-----------+
Find a blueprint image
aws --profile admin-user lightsail get-blueprints --no-include-inactive --output table --query 'blueprints[?(type==`os`)&&(platform==`LINUX_UNIX`)].{Name:name,Group:group,Version:version,Id:blueprintId} | sort_by([],&Version) | sort_by([],&Id)' ---------------------------------------------------------------------------------- | GetBlueprints | +----------------+-------------------+-----------------+-------------------------+ | Group | Id | Name | Version | +----------------+-------------------+-----------------+-------------------------+ | amazon-linux | amazon_linux | Amazon Linux | 2018.03.0.20210126.1 | | amazon_linux_2| amazon_linux_2 | Amazon Linux 2 | 2.0.20210126.0 | | centos | centos_7_1901_01 | CentOS | 7 1901-01 | | debian_10 | debian_10 | Debian | 10.5 | | debian | debian_8_7 | Debian | 8.7 | | debian_9 | debian_9_5 | Debian | 9.5 | | freebsd | freebsd_12 | FreeBSD | 12.1 | | opensuse | opensuse_15_1 | openSUSE | 15.1 | | ubuntu | ubuntu_16_04_2 | Ubuntu | 16.04 LTS | | ubuntu_18 | ubuntu_18_04 | Ubuntu | 18.04 LTS | | ubuntu_20 | ubuntu_20_04 | Ubuntu | 20.04 LTS | +----------------+-------------------+-----------------+-------------------------+
Import the custom ssh keypair
aws --profile admin-user lightsail import-key-pair --key-pair-name aws-keypair --public-key-base64 file://~/.ssh/aws-keypair.pub
Create a virtual machine using Terraform
- configure Terraform
cat > versions.tf << 'EOF' terraform { required_version = ">= 0.13" required_providers { aws = ">= 3.0" } } EOF
- define an aws provider
cat > provider.tf << 'EOF' provider "aws" { region = var.aws_provider.region shared_credentials_file = var.aws_provider.credentials_file profile = var.aws_provider.profile } EOF
- create a module to manage multiple machines
mkdir -p modules/lightsail cat > modules/lightsail/input.tf << 'EOF' variable "aws_profile" { type = string } variable "name" { type = string } variable "zone" { type = string } variable "keypair_name" { type = string } variable "blueprint_id" { type = string } variable "bundle_id" { type = string } variable "static_ip" { type = bool default = false } variable "init_script_path" { type = string default = null } variable "public_ports_rules" { type = string default = null } EOF cat > modules/lightsail/main.tf << 'EOF' data "template_file" "init_script" { count = var.init_script_path != null ? 1 : 0 template = file(var.init_script_path) } resource "aws_lightsail_instance" "instance" { name = var.name availability_zone = var.zone key_pair_name = var.keypair_name blueprint_id = var.blueprint_id bundle_id = var.bundle_id user_data = var.init_script_path != null ? data.template_file.init_script[0].rendered : null } resource "aws_lightsail_static_ip" "static_ip" { count = var.static_ip ? 1 : 0 name = "${var.name}_static_ip" } resource "aws_lightsail_static_ip_attachment" "static_ip_att" { count = var.static_ip ? 1 : 0 static_ip_name = aws_lightsail_static_ip.static_ip[0].name instance_name = aws_lightsail_instance.instance.name } resource "null_resource" "firewall" { count = var.public_ports_rules != null ? 1 : 0 provisioner "local-exec" { command = "aws --profile ${var.aws_profile} lightsail put-instance-public-ports --instance-name=${aws_lightsail_instance.instance.name} --port-infos ${var.public_ports_rules}" } } EOF cat > modules/lightsail/output.tf << 'EOF' output "static_ip" { value = var.static_ip ? aws_lightsail_static_ip.static_ip[0].ip_address : "null" } EOF
- configure the module using external variables
cat > input.tf << 'EOF' variable "aws_provider" { type = map(string) } variable "lightsail_module" { type = map(any) } EOF cat > main.tf << 'EOF' module "lightsail" { source = "./modules/lightsail" for_each = var.lightsail_module aws_profile = var.aws_provider.profile name = each.key zone = each.value.zone keypair_name = each.value.keypair_name blueprint_id = each.value.blueprint_id bundle_id = each.value.bundle_id static_ip = lookup(each.value, "static_ip", false) init_script_path = lookup(each.value, "init_script_path", null) public_ports_rules = lookup(each.value, "public_ports_rules", null) } EOF cat > output.tf << 'EOF' output "lightsail_instances" { value = {for key, val in module.lightsail : key => val.static_ip} } EOF
- configure external variables
cat > vars.json << 'EOF' { "aws_provider": { "region": "eu-west-1", "profile": "admin-user", "credentials_file": "~/.aws/credentials" }, "lightsail_module": { "my_instance_name_1": { "zone": "eu-west-1a", "keypair_name": "aws-keypair", "blueprint_id": "debian_10", "bundle_id": "nano_2_0", "static_ip": true, "init_script_path": "init_script.sh" } } } EOF
- (optionally) configure init_script.sh
The script can be used to customize the O.S. image. In this example, I'm adapting the image to my objective: use this O.S. image for a successive (off-topic) use as host machine for multiple small containers. You may use it as example or skip it.
cat > init_script.sh << 'EOF' # START CUSTOM SECTION init_script.sh # # this script assume to be run in a Debian 10 AWS image (that will copy-paste this script in a bigger one) # and keep just a minimal number of packages and its dependencies export DEBIAN_FRONTEND=noninteractive apt-get -y update # mark all packages as "not requested by the user" apt-mark auto `apt-mark showmanual` # install or upgrade or at least mark the follow packages as "requested by the user" apt-get -y -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef install \ apt apt-utils bash binutils bsdutils bzip2 coreutils cron debconf debianutils dialog dpkg findutils grep grub-pc gzip ifupdown init iptables isc-dhcp-client kmod less libc-bin locales login lsb-release lsof mount nano openssh-server passwd procps psmisc readline-common rsyslog sed sudo systemd sysvinit-utils tar util-linux # remove all "not requested" packages apt-get -y autoremove --purge apt-get -y dist-upgrade apt-get -y clean sync echo "custom init script completed, see /var/log/cloud-init-output.log for details" > /var/log/init_script.log # restart in a minute, just giving the time to complete other stuffs for the first boot shutdown -r +1 # END CUSTOM SECTION init_script.sh EOF
- execute creation
terraform init terraform apply -input=false -var-file=vars.json
- destroy all resources
terraform destroy -input=false -var-file=vars.json
- Optionally, use a simple Makefile to setup the previous custom commands:
cat > Makefile << 'EOF' export TF_PLUGIN_CACHE_DIR := ${HOME}/.terraform.d/plugin-cache .PHONY: clean clean: @mkdir -p ${TF_PLUGIN_CACHE_DIR} || true @rm -rf .terraform .PHONY: init init: clean terraform init .PHONY: apply apply: terraform apply -input=false -var-file=vars.json .PHONY: destroy destroy: terraform destroy -input=false -var-file=vars.json EOF
Use AWS EC2 custom instance
Find a good minimal AMI
aws --profile admin-user ec2 describe-images --owners amazon --filters 'Name=block-device-mapping.volume-size,Values=2' 'Name=description,Values=*Minimal*' --output table --query 'Images[?(Public)&&(State==`available`)&&(Architecture==`x86_64`)&&(VirtualizationType==`hvm`)&&(RootDeviceType==`ebs`)&&(CreationDate>=`2020-06-01`)].{id:ImageId,name:Name,desc:Description,date:CreationDate,owner:ImageOwnerAlias,arch:Architecture,virt:VirtualizationType,type:RootDeviceType,size:to_string(BlockDeviceMappings[*].Ebs.VolumeSize)} | reverse(sort_by([],&date)) | reverse(sort_by([],&name))' --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | DescribeImages | +--------+---------------------------+----------------------------------------------------------------+------------------------+--------------------------------------------------------+---------+-------+-------+-------+ | arch | date | desc | id | name | owner | size | type | virt | +--------+---------------------------+----------------------------------------------------------------+------------------------+--------------------------------------------------------+---------+-------+-------+-------+ | x86_64| 2020-09-22T02:18:38.000Z | Amazon Linux 2 AMI 2.0.20200917.0 x86_64 Minimal HVM ebs | ami-06e7a85d57337c9fa | amzn2-ami-minimal-hvm-2.0.20200917.0-x86_64-ebs | amazon | [2] | ebs | hvm | | x86_64| 2020-09-04T02:37:57.000Z | Amazon Linux 2 AMI 2.0.20200904.0 x86_64 Minimal HVM ebs | ami-09b8e6a77f149e6ec | amzn2-ami-minimal-hvm-2.0.20200904.0-x86_64-ebs | amazon | [2] | ebs | hvm | | x86_64| 2020-09-01T18:21:15.000Z | Amazon Linux 2 AMI 2.0.20200824.0 x86_64 Minimal HVM ebs | ami-01726776b01c7d9c2 | amzn2-ami-minimal-hvm-2.0.20200824.0-x86_64-ebs | amazon | [2] | ebs | hvm | | x86_64| 2020-07-24T20:49:31.000Z | Amazon Linux 2 AMI 2.0.20200722.0 x86_64 Minimal HVM ebs | ami-09eb2d6d439ae885e | amzn2-ami-minimal-hvm-2.0.20200722.0-x86_64-ebs | amazon | [2] | ebs | hvm | | x86_64| 2020-06-23T06:18:00.000Z | Amazon Linux 2 AMI 2.0.20200617.0 x86_64 Minimal HVM ebs | ami-0249782201f0ee367 | amzn2-ami-minimal-hvm-2.0.20200617.0-x86_64-ebs | amazon | [2] | ebs | hvm | | x86_64| 2020-09-23T17:45:32.000Z | Amazon Linux AMI 2018.03.0.20200918.0 x86_64 Minimal HVM ebs | ami-09c00af71f9f4a141 | amzn-ami-minimal-hvm-2018.03.0.20200918.0-x86_64-ebs | amazon | [2] | ebs | hvm | | x86_64| 2020-09-04T02:17:52.000Z | Amazon Linux AMI 2018.03.0.20200904.0 x86_64 Minimal HVM ebs | ami-0780ed21a81408761 | amzn-ami-minimal-hvm-2018.03.0.20200904.0-x86_64-ebs | amazon | [2] | ebs | hvm | | x86_64| 2020-08-11T20:17:17.000Z | Amazon Linux AMI 2018.03.0.20200729.0 x86_64 Minimal HVM ebs | ami-02428347d30e4e928 | amzn-ami-minimal-hvm-2018.03.0.20200729.0-x86_64-ebs | amazon | [2] | ebs | hvm | | x86_64| 2020-07-21T18:39:43.000Z | Amazon Linux AMI 2018.03.0.20200716.0 x86_64 Minimal HVM ebs | ami-0be996716886f5772 | amzn-ami-minimal-hvm-2018.03.0.20200716.0-x86_64-ebs | amazon | [2] | ebs | hvm | | x86_64| 2020-06-16T06:36:13.000Z | Amazon Linux AMI 2018.03.0.20200602.1 x86_64 Minimal HVM ebs | ami-09e12379867321151 | amzn-ami-minimal-hvm-2018.03.0.20200602.1-x86_64-ebs | amazon | [2] | ebs | hvm | +--------+---------------------------+----------------------------------------------------------------+------------------------+--------------------------------------------------------+---------+-------+-------+-------+
Create a simple EC2
- It will use an elastic ip to see how resources are reused
Create
- example using a basic Ubuntu 16.04 LTS AMI
mkdir example_org cd example_org cat > example_org.tf << 'EOF' provider "aws" { profile = "admin-user" region = "eu-west-1" } resource "aws_instance" "example_org" { # Ubuntu 16.04 LTS ami = "ami-03746875d916becc0" instance_type = "t3.nano" } EOF terraform init # init terraform for the new project requirements terraform plan # see changes to be applyed terraform apply # apply changes
Edit
- example changing to Ubuntu 18.04 LST AMI
cat > example_org.tf << 'EOF' provider "aws" { profile = "admin-user" region = "eu-west-1" } resource "aws_instance" "example_org" { # # Ubuntu 16.04 LTS # ami = "ami-03746875d916becc0" # Ubuntu 18.04 AMI ami = "ami-01e6a0b85de033c99" instance_type = "t3.nano" } EOF terraform apply
- example: adding an elastic-ip
cat >> example_org.tf << 'EOF' resource "aws_eip" "ip" { instance = "${aws_instance.example_org.id}" } EOF terraform apply
- example: changing AMI again to minimal, keeping the Elastic IP
cat > example_org.tf << 'EOF' provider "aws" { profile = "admin-user" region = "eu-west-1" } resource "aws_instance" "example_org" { # # Ubuntu 16.04 LTS # ami = "ami-03746875d916becc0" # # Ubuntu 18.04 AMI # ami = "ami-01e6a0b85de033c99" # minimal hvm ebs x86_64 AMI 20190618 ami = "ami-0a3b59edf43c875be" instance_type = "t3.nano" } resource "aws_eip" "ip" { instance = "${aws_instance.example_org.id}" } EOF terraform apply
Delete
- cleanup
terraform destroy
List existing instances
aws --profile admin-user ec2 describe-instances --output table --query 'Reservations[*].Instances[*].{Name:[Tags[?Key==`Name`].Value][0][0],Ip:PrivateIpAddress,Type:InstanceType,Zone:Placement.AvailabilityZone,State:State.Name,Launched:LaunchTime,VPC:VpcId,SN:SubnetId,SG:join(`,`, SecurityGroups[*].GroupId)}' ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | DescribeInstances | +---------------+----------------------------+--------------------+------------------------------------------------------------------------------+------------------+----------+------------+---------------+--------------+ | Ip | Launched | Name | SG | SN | State | Type | VPC | Zone | +---------------+----------------------------+--------------------+------------------------------------------------------------------------------+------------------+----------+------------+---------------+--------------+ | 172.31.20.253| 2020-05-04T02:14:52+00:00 | ec2_campisano.org | sg-0265edc543d39e5ce,sg-5c21bb23,sg-07f4afef9a2105c23,sg-09c096105eacbf55d | subnet-76c1a23e | running | t3a.micro | vpc-0bc5e36d | eu-west-1a | +---------------+----------------------------+--------------------+------------------------------------------------------------------------------+------------------+----------+------------+---------------+--------------+
References
- https://aws.amazon.com/ec2/pricing/
- https://aws.amazon.com/ec2/pricing/on-demand/
- https://aws.amazon.com/ec2/pricing/reserved-instances/pricing/
- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/apply_ri.html
- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ri-market-concepts-buying.html#reserved-instances-process
- https://aws.amazon.com/efs/pricing/
- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html
- https://aws.amazon.com/ec2/pricing/on-demand/#Elastic_IP_Addresses
- https://calculator.s3.amazonaws.com/index.html?nc2=h_ql_pr
- https://console.aws.amazon.com/billing/home?region=eu-west-1#/
- https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/
- https://ping.varunagw.com/aws
- https://stackoverflow.com/a/40932906 (comparison)
- https://stackoverflow.com/a/46214617 (comparison)