├── .gitignore ├── .gitlab-ci.yml ├── LICENSE ├── README.md ├── img ├── ami-deregister.png ├── infra.png ├── s3-state.png ├── snapshot-delete.png └── website.png ├── packer ├── ansible │ ├── ansible.cfg │ ├── group_vars │ │ └── all.yml │ ├── provision_host.yml │ └── roles │ │ ├── common │ │ └── tasks │ │ │ └── main.yml │ │ └── web │ │ ├── handlers │ │ └── main.yml │ │ ├── tasks │ │ └── main.yml │ │ └── templates │ │ ├── etc.nginx.conf.j2 │ │ └── index.html.j2 ├── scripts │ ├── cleanup.sh │ ├── prepare.sh │ └── requirements.txt └── template_ami.json └── terraform ├── aws-demo.tf ├── aws-demo.tfvars ├── aws_fake_key ├── modules ├── alb │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── bastion │ ├── data.tf │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── ec2 │ ├── data.tf │ ├── main.tf │ └── variables.tf ├── nat_gateway │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── private_subnet │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── public_subnet │ ├── main.tf │ ├── outputs.tf │ └── variables.tf └── vpc │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── outputs.tf └── state.tf /.gitignore: -------------------------------------------------------------------------------- 1 | [terraform] 2 | 3 | # Local .terraform directories 4 | **/.terraform/* 5 | 6 | # .tfstate files 7 | *.tfstate 8 | *.tfstate.* 9 | 10 | # .tfvars files 11 | # *.tfvars 12 | 13 | [macos] 14 | 15 | # General 16 | .DS_Store 17 | .AppleDouble 18 | .LSOverride 19 | 20 | # Icon must end with two \r 21 | Icon 22 | 23 | 24 | # Thumbnails 25 | ._* 26 | 27 | # Files that might appear in the root of a volume 28 | .DocumentRevisions-V100 29 | .fseventsd 30 | .Spotlight-V100 31 | .TemporaryItems 32 | .Trashes 33 | .VolumeIcon.icns 34 | .com.apple.timemachine.donotpresent 35 | 36 | # Directories potentially created on remote AFP share 37 | .AppleDB 38 | .AppleDesktop 39 | Network Trash Folder 40 | Temporary Items 41 | .apdisk 42 | 43 | [sublime] 44 | 45 | # Cache files for Sublime Text 46 | *.tmlanguage.cache 47 | *.tmPreferences.cache 48 | *.stTheme.cache 49 | 50 | # Workspace files are user-specific 51 | *.sublime-workspace 52 | 53 | # Project files should be checked into the repository, unless a significant 54 | # proportion of contributors will probably not be using Sublime Text 55 | # *.sublime-project 56 | 57 | # SFTP configuration file 58 | sftp-config.json 59 | 60 | # Package control specific files 61 | Package Control.last-run 62 | Package Control.ca-list 63 | Package Control.ca-bundle 64 | Package Control.system-ca-bundle 65 | Package Control.cache/ 66 | Package Control.ca-certs/ 67 | Package Control.merged-ca-bundle 68 | Package Control.user-ca-bundle 69 | oscrypto-ca-bundle.crt 70 | bh_unicode_properties.cache 71 | 72 | # Sublime-github package stores a github token in this file 73 | # https://packagecontrol.io/packages/sublime-github 74 | GitHub.sublime-settings 75 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: 2 | name: dmitrijsf/ci-docker:latest 3 | entrypoint: 4 | - '/usr/bin/env' 5 | - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' 6 | 7 | variables: 8 | PACKER_DIR: packer 9 | TF_DIR: terraform 10 | PLAN: plan.tfplan 11 | S3_BUCKET: terraform-remote-state-defo 12 | 13 | cache: 14 | paths: 15 | - .terraform 16 | 17 | before_script: 18 | - packer --version 19 | - terraform --version 20 | - cd $TF_DIR && terraform init -input=false 21 | 22 | stages: 23 | - validate 24 | - build 25 | - plan 26 | - deploy 27 | 28 | packer validate: 29 | stage: validate 30 | script: 31 | - cd ../$PACKER_DIR && find . -maxdepth 1 -name '*.json' -print0 | xargs -t0n1 packer validate 32 | 33 | terraform validate: 34 | stage: validate 35 | script: 36 | - cd ../$TF_DIR && terraform validate -var-file=aws-demo.tfvars 37 | 38 | build-ami: 39 | stage: build 40 | environment: 41 | name: production 42 | script: 43 | - cd ../$PACKER_DIR && find . -maxdepth 1 -name '*.json' -print0 | xargs -t0n1 packer build 44 | when: manual 45 | only: 46 | - master 47 | 48 | plan: 49 | stage: plan 50 | script: 51 | - cd ../$TF_DIR && terraform plan -var-file=aws-demo.tfvars -out=$PLAN 52 | - cd ../$TF_DIR && aws s3 cp $PLAN s3://$S3_BUCKET/$PLAN 53 | artifacts: 54 | name: plan 55 | paths: 56 | - $PLAN 57 | 58 | deploy: 59 | stage: deploy 60 | environment: 61 | name: production 62 | script: 63 | - cd ../$TF_DIR && aws s3 cp s3://$S3_BUCKET/$PLAN $PLAN 64 | - cd ../$TF_DIR && terraform apply -input=false $PLAN 65 | dependencies: 66 | - plan 67 | - build-ami 68 | when: manual 69 | only: 70 | - master -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dmitri Fedotov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Gitlab CI demo with Terraform and Packer 2 | 3 | This project provisions an AWS Application Load Balancer including the required infrastructure. 4 | It was created mainly for learning purposes and consists of the following building blocks: 5 | - EC2 instance AMI is pre-baked with [Packer](https://www.packer.io) using [Ansible](https://ansible.com) as a provisioner. 6 | - Infrastructure is provisioned using [Terraform](https://www.terraform.io) module. 7 | - Gitlab CI orchestrates actions with Packer and Terraform. 8 | 9 | **Note** 10 | - Public and private keys provided are just for demo purposes 11 | - You are encouraged to create your own ones and edit the *resource "aws_key_pair"* entry in the *aws-demo.tf* file 12 | 13 | ![AWS infra](img/infra.png) 14 | 15 | ## Terraform state 16 | Terraform state and plan files are stored on S3 bucket, so it needs to be created beforehand. 17 | No public access is required. 18 | 19 | ![S3 state](img/s3-state.png) 20 | 21 | ``` 22 | # state.tf 23 | terraform { 24 | backend "s3" { 25 | bucket = "terraform-remote-state-defo" 26 | key = "terraform-state-packer-aws-with-gitlab.tfstate" 27 | region = "eu-west-1" 28 | } 29 | } 30 | ``` 31 | 32 | # Manual way 33 | 34 | **Set desired AWS credentials** 35 | 36 | In this example I am using [**aws-vault**](https://github.com/99designs/aws-vault) to work with desired profile. 37 | 38 | ``` 39 | ❯ aws-vault add home 40 | Enter Access Key ID: your-aws-access-key-id 41 | Enter Secret Access Key: your-aws-access-key 42 | Added credentials to profile "home" in vault 43 | 44 | # launches subshell with desired AWS environment variables 45 | ❯ aws-vault exec -- home 46 | ``` 47 | 48 | **Requirements** 49 | - [Packer](https://www.packer.io) 50 | - [Terraform](https://www.terraform.io) 51 | - [aws-vault](https://github.com/99designs/aws-vault) 52 | 53 | 54 | ``` 55 | ❯ git clone git@github.com:dmitrijsf/hello-world-gitlab-ci.git 56 | ❯ cd hello-world-gitlab-ci 57 | ``` 58 | 59 | **Build AWS AMI using Packer** 60 | 61 | Ansible will apply 2 roles: 62 | - common: install common software and perform upgrade 63 | - web: install nginx and copy custom index.html 64 | 65 | ``` 66 | ❯ cd packer && packer validate template_ami.json 67 | Template validated successfully. 68 | 69 | ❯ packer build template_ami.json 70 | amazon-ebs output will be in this color. 71 | 72 | ==> amazon-ebs: Prevalidating AMI Name: packer-ami 1529266559 73 | amazon-ebs: Found Image ID: ami-ca0135b3 74 | ==> amazon-ebs: Creating temporary keypair: packer_5b26c17f-0903-cb8b-b765-0648cca27038 75 | ==> amazon-ebs: Creating temporary security group for this instance: packer_5b26c180-2856-7266-5c9f-2f9747183e4b 76 | ==> amazon-ebs: Authorizing access to port 22 from 0.0.0.0/0 in the temporary security group... 77 | ==> amazon-ebs: Launching a source AWS instance... 78 | ==> amazon-ebs: Adding tags to source instance 79 | amazon-ebs: Adding tag: "Name": "Packer Builder" 80 | amazon-ebs: Instance ID: i-0cd68683514e8c5d9 81 | ==> amazon-ebs: Waiting for instance (i-0cd68683514e8c5d9) to become ready... 82 | ==> amazon-ebs: Waiting for SSH to become available... 83 | ==> amazon-ebs: Connected to SSH! 84 | ==> amazon-ebs: Uploading scripts => /home/ec2-user/ 85 | ==> amazon-ebs: Provisioning with shell script: scripts/prepare.sh 86 | -- snip -- 87 | amazon-ebs: Uploading Playbook directory to Ansible staging directory... 88 | amazon-ebs: Creating directory: /tmp/packer-provisioner-ansible-local/5b26c17f-7a7a-ab3f-f096-0e3a47c4fe90 89 | amazon-ebs: Uploading main Playbook file... 90 | amazon-ebs: Uploading inventory file... 91 | amazon-ebs: Executing Ansible: cd /tmp/packer-provisioner-ansible-local/5b26c17f-7a7a-ab3f-f096-0e3a47c4fe90 && ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 ansible-playbook /tmp/packer-provisioner-ansible-local/5b26c17f-7a7a-ab3f-f096-0e3a47c4fe90/provision_host.yml --extra-vars "packer_build_name=amazon-ebs packer_builder_type=amazon-ebs packer_http_addr=" -c local -i /tmp/packer-provisioner-ansible-local/5b26c17f-7a7a-ab3f-f096-0e3a47c4fe90/packer-provisioner-ansible-local700540066 92 | amazon-ebs: 93 | amazon-ebs: PLAY [prepare host] ************************************************************ 94 | amazon-ebs: 95 | amazon-ebs: TASK [common : install basic packages] ***************************************** 96 | amazon-ebs: changed: [127.0.0.1] => (item=[u'git', u'tree', u'vim', u'htop', u'mlocate']) 97 | amazon-ebs: 98 | amazon-ebs: TASK [common : upgrade all packages, including kernel] ************************* 99 | amazon-ebs: changed: [127.0.0.1] 100 | amazon-ebs: 101 | amazon-ebs: TASK [web : install nginx] ***************************************************** 102 | amazon-ebs: changed: [127.0.0.1] 103 | amazon-ebs: 104 | amazon-ebs: TASK [web : remove web root directory if exists] ******************************* 105 | amazon-ebs: ok: [127.0.0.1] 106 | amazon-ebs: 107 | amazon-ebs: TASK [web : create web root directory] ***************************************** 108 | amazon-ebs: changed: [127.0.0.1] 109 | amazon-ebs: 110 | amazon-ebs: TASK [web : generate and copy nginx configuration file to server] ************** 111 | amazon-ebs: changed: [127.0.0.1] 112 | amazon-ebs: 113 | amazon-ebs: TASK [web : generate and copy index.html file to server] *********************** 114 | amazon-ebs: changed: [127.0.0.1] 115 | amazon-ebs: 116 | amazon-ebs: TASK [web : enable and start http] ********************************************* 117 | amazon-ebs: changed: [127.0.0.1] 118 | amazon-ebs: 119 | amazon-ebs: RUNNING HANDLER [web : restart nginx] ****************************************** 120 | amazon-ebs: changed: [127.0.0.1] 121 | amazon-ebs: 122 | amazon-ebs: PLAY RECAP ********************************************************************* 123 | amazon-ebs: 127.0.0.1 : ok=9 changed=8 unreachable=0 failed=0 124 | amazon-ebs: 125 | ==> amazon-ebs: Provisioning with shell script: scripts/cleanup.sh 126 | amazon-ebs: Uninstalling ansible-2.5.5: 127 | amazon-ebs: You are using pip version 9.0.3, however version 10.0.1 is available. 128 | amazon-ebs: You should consider upgrading via the 'pip install --upgrade pip' command. 129 | amazon-ebs: Successfully uninstalled ansible-2.5.5 130 | ==> amazon-ebs: Stopping the source instance... 131 | amazon-ebs: Stopping instance, attempt 1 132 | ==> amazon-ebs: Waiting for the instance to stop... 133 | ==> amazon-ebs: Creating the AMI: packer-ami 1529266559 134 | amazon-ebs: AMI: ami-47f0f3ad 135 | ==> amazon-ebs: Waiting for AMI to become ready... 136 | ==> amazon-ebs: Adding tags to AMI (ami-47f0f3ad)... 137 | ==> amazon-ebs: Tagging snapshot: snap-04af53cb030792e30 138 | ==> amazon-ebs: Creating AMI tags 139 | amazon-ebs: Adding tag: "Name": "webserver" 140 | amazon-ebs: Adding tag: "Project": "testing" 141 | ==> amazon-ebs: Creating snapshot tags 142 | ==> amazon-ebs: Terminating the source AWS instance... 143 | ==> amazon-ebs: Cleaning up any extra volumes... 144 | ==> amazon-ebs: No volumes to clean up, skipping 145 | ==> amazon-ebs: Deleting temporary security group... 146 | ==> amazon-ebs: Deleting temporary keypair... 147 | Build 'amazon-ebs' finished. 148 | 149 | ==> Builds finished. The artifacts of successful builds are: 150 | --> amazon-ebs: AMIs were created: 151 | eu-west-1: ami-47f0f3ad 152 | ``` 153 | 154 | **Build AWS infrastructure using Terraform** 155 | 156 | Terraform will build the AWS infrastructure. 157 | Previosly pre-baked AMI will be used in the Auto-Scaling Group. 158 | 159 | ``` 160 | # aws-demo.tfvars 161 | environment = "dev" 162 | vpc_cidr = "10.172.0.0/16" 163 | public_subnet_cidrs = ["10.172.1.0/24", "10.172.2.0/24"] 164 | private_subnet_cidrs = ["10.172.81.0/24", "10.172.82.0/24"] 165 | availability_zones = ["eu-west-1a", "eu-west-1b"] 166 | max_size = 4 167 | min_size = 2 168 | instance_type = "t2.micro" 169 | ``` 170 | 171 | ``` 172 | ❯ cd ../terraform 173 | ❯ terraform validate -var-file=aws-demo.tfvars 174 | ❯ terraform apply -var-file=aws-demo.tfvars 175 | -- snip -- 176 | Apply complete! Resources: 32 added, 0 changed, 0 destroyed. 177 | 178 | Outputs: 179 | 180 | ALB DNS Name = default-397873677.eu-west-1.elb.amazonaws.com 181 | Bastion DNS Name = ec2-34-247-38-196.eu-west-1.compute.amazonaws.com 182 | ``` 183 | 184 | **Test web access** 185 | 186 | ![Website](img/website.png) 187 | 188 | **Access the instances** 189 | 190 | ``` 191 | # ssh to bastion host 192 | ❯ ssh -i aws_fake_key ubuntu@ec2-34-247-38-196.eu-west-1.compute.amazonaws.com 193 | Are you sure you want to continue connecting (yes/no)? yes 194 | ubuntu@ip-10-172-1-73:~$ 195 | 196 | # ssh to the instance through the bastion host 197 | ❯ ssh-add -K aws_fake_key 198 | ❯ ssh -A ubuntu@ec2-34-247-38-196.eu-west-1.compute.amazonaws.com 199 | ubuntu@ip-10-172-1-73:~$ ssh ec2-user@10.172.82.192 200 | Warning: Permanently added '10.172.82.192' (ECDSA) to the list of known hosts. 201 | 202 | __| __|_ ) 203 | _| ( / Amazon Linux AMI 204 | ___|\___|___| 205 | 206 | https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/ 207 | [ec2-user@ip-10-172-82-192 ~]$ service nginx status 208 | nginx (pid 2625 2622) is running... 209 | [ec2-user@ip-10-172-82-192 ~]$ 210 | ``` 211 | 212 | **Destroy the environment** 213 | 214 | ``` 215 | ❯ terraform destroy -var-file=aws-demo.tfvars 216 | -- snip -- 217 | Destroy complete! Resources: 32 destroyed. 218 | ``` 219 | 220 | Deregister the AMI created by Packer. 221 | 222 | ![AMI deregister](img/ami-deregister.png) 223 | 224 | Delete the snaphot to avoid being charged for storing it. 225 | 226 | ![Snapshot delete](img/snapshot-delete.png) 227 | 228 | 229 | # Using Gitlab CI 230 | 231 | ## Configuration 232 | 233 | [Gitlab CI](https://about.gitlab.com/features/gitlab-ci-cd/) is used together with [GitLab Runner run in a container](https://docs.gitlab.com/runner/install/docker.html). 234 | GitLab Runner should already be [registered](https://docs.gitlab.com/runner/register/index.html#docker). 235 | 236 | You need to pass your [AWS credentials](https://docs.gitlab.com/ee/ci/variables/#secret-variables) to the runner: 237 | - AWS_ACCESS_KEY_ID 238 | - AWS_SECRET_ACCESS_KEY 239 | 240 | All CI actions are defined in the [.gitlab-ci.yml](.gitlab-ci.yml) file. 241 | 242 | **Docker container for CI** 243 | 244 | Each job will trigger the Gitlab runner to launch a [Docker container](https://hub.docker.com/r/dmitrijsf/ci-docker/) to execute commands as instructed in the **gitlab-ci.yml** file. 245 | 246 | ``` 247 | image: 248 | name: dmitrijsf/ci-docker:latest 249 | entrypoint: 250 | - '/usr/bin/env' 251 | - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' 252 | ``` 253 | 254 | **Operations workflow** 255 | 256 | Gitlab CI Pipeline is triggered by committing to the repository. 257 | If merged or commited to master, **terraform apply** and **packer build** options are available to run manually. 258 | 259 | Edit the variable for S3 bucket (S3_BUCKET) accordingly. 260 | 261 | ``` 262 | variables: 263 | PACKER_DIR: packer 264 | TF_DIR: terraform 265 | PLAN: plan.tfplan 266 | S3_BUCKET: terraform-remote-state-defo 267 | ``` 268 | 269 | - before_script: verify Packer and Terraform versions and run **terraform init** 270 | 271 | - validate stage: run validation of Packer and Terraform configuration 272 | 273 | - build-ami stage: pre-bake AWS AMI with desired configuration using Ansible as provisioner 274 | 275 | - plan stage: run **terraform plan** and copy the plan file to an S3 bucket 276 | 277 | - deploy stage: copy the plan file back from an S3 bucket locally and run **terraform apply** -------------------------------------------------------------------------------- /img/ami-deregister.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defo89/hello-world-gitlab-ci/ca24c3dc50a74bb66f6bdfc0078dcf1ef0bba08d/img/ami-deregister.png -------------------------------------------------------------------------------- /img/infra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defo89/hello-world-gitlab-ci/ca24c3dc50a74bb66f6bdfc0078dcf1ef0bba08d/img/infra.png -------------------------------------------------------------------------------- /img/s3-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defo89/hello-world-gitlab-ci/ca24c3dc50a74bb66f6bdfc0078dcf1ef0bba08d/img/s3-state.png -------------------------------------------------------------------------------- /img/snapshot-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defo89/hello-world-gitlab-ci/ca24c3dc50a74bb66f6bdfc0078dcf1ef0bba08d/img/snapshot-delete.png -------------------------------------------------------------------------------- /img/website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defo89/hello-world-gitlab-ci/ca24c3dc50a74bb66f6bdfc0078dcf1ef0bba08d/img/website.png -------------------------------------------------------------------------------- /packer/ansible/ansible.cfg: -------------------------------------------------------------------------------- 1 | # File: ansible.cfg 2 | # Overrides default configuration for this project 3 | 4 | [defaults] 5 | host_key_checking = False 6 | retry_files_enabled = False -------------------------------------------------------------------------------- /packer/ansible/group_vars/all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ansuser_name: manager 3 | ansuser_comment: "Management User" 4 | ansuser_group: wheel 5 | jumpbox_sshkey: files/id_rsa.pub 6 | 7 | port_web: 80 8 | nginx_web_root: /var/www 9 | nginx_config_file: /etc/nginx/nginx.conf 10 | nginx_user: nginx 11 | nginx_group: nginx -------------------------------------------------------------------------------- /packer/ansible/provision_host.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Prepare host 3 | 4 | - name: prepare host 5 | hosts: all 6 | become: true 7 | gather_facts: false 8 | roles: 9 | - common 10 | - web 11 | -------------------------------------------------------------------------------- /packer/ansible/roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install basic packages 3 | yum: 4 | update_cache: yes 5 | name: "{{ item }}" 6 | state: present 7 | with_items: 8 | - git 9 | - tree 10 | - vim 11 | - htop 12 | - mlocate 13 | tags: [ 'packages' ] 14 | 15 | - name: upgrade all packages, including kernel 16 | yum: 17 | name: '*' 18 | state: latest 19 | -------------------------------------------------------------------------------- /packer/ansible/roles/web/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart nginx 3 | service: 4 | name: nginx 5 | state: restarted -------------------------------------------------------------------------------- /packer/ansible/roles/web/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install nginx 3 | yum: 4 | update_cache: yes 5 | name: nginx 6 | state: present 7 | tags: [ 'packages' ] 8 | 9 | - name: remove web root directory if exists 10 | file: 11 | path: "{{ nginx_web_root }}" 12 | state: absent 13 | 14 | - name: create web root directory 15 | file: 16 | path: "{{ nginx_web_root }}" 17 | owner: "{{ nginx_user }}" 18 | group: "{{ nginx_group }}" 19 | mode: 0775 20 | state: directory 21 | 22 | - name: generate and copy nginx configuration file to server 23 | template: 24 | src: etc.nginx.conf.j2 25 | dest: "{{ nginx_config_file }}" 26 | owner: root 27 | group: root 28 | mode: 0644 29 | notify: restart nginx 30 | 31 | - name: generate and copy index.html file to server 32 | template: 33 | src: index.html.j2 34 | owner: "{{ nginx_user }}" 35 | group: "{{ nginx_group }}" 36 | dest: "{{ nginx_web_root }}/index.html" 37 | 38 | - name: enable and start http 39 | service: 40 | name: nginx 41 | enabled: yes 42 | -------------------------------------------------------------------------------- /packer/ansible/roles/web/templates/etc.nginx.conf.j2: -------------------------------------------------------------------------------- 1 | # For more information on configuration, see: 2 | # * Official English Documentation: http://nginx.org/en/docs/ 3 | # * Official Russian Documentation: http://nginx.org/ru/docs/ 4 | 5 | user nginx; 6 | worker_processes auto; 7 | error_log /var/log/nginx/error.log; 8 | pid /run/nginx.pid; 9 | 10 | # Load dynamic modules. See /usr/share/nginx/README.dynamic. 11 | include /usr/share/nginx/modules/*.conf; 12 | 13 | events { 14 | worker_connections 1024; 15 | } 16 | 17 | http { 18 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 19 | '$status $body_bytes_sent "$http_referer" ' 20 | '"$http_user_agent" "$http_x_forwarded_for"'; 21 | 22 | access_log /var/log/nginx/access.log main; 23 | 24 | sendfile on; 25 | tcp_nopush on; 26 | tcp_nodelay on; 27 | keepalive_timeout 65; 28 | types_hash_max_size 2048; 29 | 30 | include /etc/nginx/mime.types; 31 | default_type application/octet-stream; 32 | 33 | # Load modular configuration files from the /etc/nginx/conf.d directory. 34 | # See http://nginx.org/en/docs/ngx_core_module.html#include 35 | # for more information. 36 | include /etc/nginx/conf.d/*.conf; 37 | 38 | server { 39 | listen 80 default_server; 40 | listen [::]:80 default_server; 41 | server_name _; 42 | root {{ nginx_web_root }}; 43 | 44 | # Load configuration files for the default server block. 45 | include /etc/nginx/default.d/*.conf; 46 | 47 | location / { 48 | } 49 | 50 | error_page 404 /404.html; 51 | location = /40x.html { 52 | } 53 | 54 | error_page 500 502 503 504 /50x.html; 55 | location = /50x.html { 56 | } 57 | } 58 | 59 | # Settings for a TLS enabled server. 60 | # 61 | # server { 62 | # listen 443 ssl http2 default_server; 63 | # listen [::]:443 ssl http2 default_server; 64 | # server_name _; 65 | # root /usr/share/nginx/html; 66 | # 67 | # ssl_certificate "/etc/pki/nginx/server.crt"; 68 | # ssl_certificate_key "/etc/pki/nginx/private/server.key"; 69 | # ssl_session_cache shared:SSL:1m; 70 | # ssl_session_timeout 10m; 71 | # ssl_ciphers HIGH:!aNULL:!MD5; 72 | # ssl_prefer_server_ciphers on; 73 | # 74 | # # Load configuration files for the default server block. 75 | # include /etc/nginx/default.d/*.conf; 76 | # 77 | # location / { 78 | # } 79 | # 80 | # error_page 404 /404.html; 81 | # location = /40x.html { 82 | # } 83 | # 84 | # error_page 500 502 503 504 /50x.html; 85 | # location = /50x.html { 86 | # } 87 | # } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /packer/ansible/roles/web/templates/index.html.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World 4 | 5 | 6 |

hello world website 2.0

7 | 8 | -------------------------------------------------------------------------------- /packer/scripts/cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo pip uninstall -y -r scripts/requirements.txt 4 | sudo rm -rf ansible 5 | sudo rm -rf scripts -------------------------------------------------------------------------------- /packer/scripts/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo pip install -r scripts/requirements.txt 4 | -------------------------------------------------------------------------------- /packer/scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | ansible>=2.4.3.0 2 | -------------------------------------------------------------------------------- /packer/template_ami.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "aws_access_key": "", 4 | "aws_secret_key": "" 5 | }, 6 | "builders": [ 7 | { 8 | "type": "amazon-ebs", 9 | "access_key": "{{user `aws_access_key`}}", 10 | "secret_key": "{{user `aws_secret_key`}}", 11 | "region": "eu-west-1", 12 | "source_ami_filter": { 13 | "filters": { 14 | "virtualization-type": "hvm", 15 | "name": "amzn-ami-hvm-*", 16 | "root-device-type": "ebs" 17 | }, 18 | "owners": ["137112412989"], 19 | "most_recent": true 20 | }, 21 | "instance_type": "t2.micro", 22 | "ssh_username": "ec2-user", 23 | "ami_name": "packer-ami {{timestamp}}", 24 | "tags": { 25 | "Name": "webserver", 26 | "Project": "testing" 27 | } 28 | } 29 | ], 30 | "provisioners": [ 31 | { 32 | "type": "file", 33 | "source": "scripts", 34 | "destination": "/home/ec2-user/" 35 | }, 36 | { 37 | "type": "shell", 38 | "scripts": [ 39 | "scripts/prepare.sh" 40 | ] 41 | }, 42 | { 43 | "type": "file", 44 | "source": "ansible", 45 | "destination": "/home/ec2-user/" 46 | }, 47 | { 48 | "type": "ansible-local", 49 | "playbook_file": "ansible/provision_host.yml", 50 | "playbook_dir": "ansible" 51 | }, 52 | { 53 | "type": "shell", 54 | "scripts": [ 55 | "scripts/cleanup.sh" 56 | ] 57 | } 58 | ] 59 | } 60 | 61 | -------------------------------------------------------------------------------- /terraform/aws-demo.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | } 4 | 5 | module "alb" { 6 | source = "modules/alb" 7 | environment = "${var.environment}" 8 | vpc_id = "${module.vpc.id}" 9 | public_subnet_ids = "${module.public_subnet.ids}" 10 | } 11 | 12 | module "ec2" { 13 | source = "modules/ec2" 14 | environment = "${var.environment}" 15 | vpc_id = "${module.vpc.id}" 16 | private_subnet_ids = "${module.private_subnet.ids}" 17 | max_size = "${var.max_size}" 18 | min_size = "${var.min_size}" 19 | instance_type = "${var.instance_type}" 20 | target_group = "${module.alb.target_group}" 21 | key_name = "${aws_key_pair.key.key_name}" 22 | sg_bastion = "${module.bastion.sg_id}" 23 | } 24 | 25 | module "vpc" { 26 | source = "modules/vpc" 27 | cidr = "${var.vpc_cidr}" 28 | environment = "${var.environment}" 29 | } 30 | 31 | module "private_subnet" { 32 | source = "modules/private_subnet" 33 | name = "${var.environment}_private_subnet" 34 | environment = "${var.environment}" 35 | vpc_id = "${module.vpc.id}" 36 | nat_gateway_id = "${module.nat.ids}" 37 | cidrs = "${var.private_subnet_cidrs}" 38 | availability_zones = "${var.availability_zones}" 39 | } 40 | 41 | module "public_subnet" { 42 | source = "modules/public_subnet" 43 | name = "${var.environment}_public_subnet" 44 | environment = "${var.environment}" 45 | vpc_id = "${module.vpc.id}" 46 | igw_id = "${module.vpc.igw}" 47 | cidrs = "${var.public_subnet_cidrs}" 48 | availability_zones = "${var.availability_zones}" 49 | } 50 | 51 | module "nat" { 52 | source = "modules/nat_gateway" 53 | vpc_id = "${module.vpc.id}" 54 | subnet_ids = "${module.public_subnet.ids}" 55 | subnet_count = "${length(var.public_subnet_cidrs)}" 56 | } 57 | 58 | module "bastion" { 59 | source = "modules/bastion" 60 | environment = "${var.environment}" 61 | vpc_id = "${module.vpc.id}" 62 | public_subnet_ids = "${module.public_subnet.ids}" 63 | instance_type = "${var.instance_type}" 64 | key_name = "${aws_key_pair.key.key_name}" 65 | } 66 | 67 | resource "aws_key_pair" "key" { 68 | key_name = "key-${var.environment}" 69 | public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCjbmI/eB0DDI4w3vh43Ha4FXVQEHF0U1lAfCN/pow2NlCZoRuINJfoc48xgFNXBNDBZWuRXER3R+hV4H0GlsM3CEc8J0V7ggbCC87spXcdUcoNvvBfA4SXvLWz88tYf04vgtFl8Mwe0kF9wiWvs+gDefOAJ59iWK/0FQY5wD7hyZtksX33dfrcpjm62idC9QtWivbygin7FOBOBgLINOdW9OYDbVoTbplUtmAN+qiS2LoipuZ/jUBhyePU1+BawIuG7f57lI9x0Xl0oNBj+SQt/W0IVCrMy+2N3G1hVIQ8SFG/7FDPr+yy5c8rqahQw2eLrVFadunjxrY/5RSciZy/ notme@fake" 70 | } 71 | 72 | variable "vpc_cidr" {} 73 | variable "environment" {} 74 | variable "max_size" {} 75 | variable "min_size" {} 76 | variable "instance_type" {} 77 | 78 | variable "private_subnet_cidrs" { 79 | type = "list" 80 | } 81 | 82 | variable "public_subnet_cidrs" { 83 | type = "list" 84 | } 85 | 86 | variable "availability_zones" { 87 | type = "list" 88 | } 89 | -------------------------------------------------------------------------------- /terraform/aws-demo.tfvars: -------------------------------------------------------------------------------- 1 | vpc_cidr = "10.172.0.0/16" 2 | 3 | environment = "dev" 4 | 5 | public_subnet_cidrs = ["10.172.1.0/24", "10.172.2.0/24"] 6 | 7 | private_subnet_cidrs = ["10.172.81.0/24", "10.172.82.0/24"] 8 | 9 | availability_zones = ["eu-west-1a", "eu-west-1b"] 10 | 11 | max_size = 4 12 | 13 | min_size = 2 14 | 15 | instance_type = "t2.micro" 16 | -------------------------------------------------------------------------------- /terraform/aws_fake_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAo25iP3gdAwyOMN74eNx2uBV1UBBxdFNZQHwjf6aMNjZQmaEb 3 | iDSX6HOPMYBTVwTQwWVrkVxEd0foVeB9BpbDNwhHPCdFe4IGwgvO7KV3HVHKDb7w 4 | XwOEl7y1s/PLWH9OL4LRZfDMHtJBfcIlr7PoA3nzgCefYliv9BUGOcA+4cmbZLF9 5 | 93X63KY5utonQvULVor28oIp+xTgTgYCyDTnVvTmA21aE26ZVLZgDfqokti6Iqbm 6 | f41AYcnj1NfgWsCLhu3+e5SPcdF5dKDQY/kkLf1tCFQqzMvtjdxtYVSEPEhRv+xQ 7 | z6/ssuXPK6moUMNni61RWnbp48a2P+UUnImcvwIDAQABAoIBAHeE6mTwOodYT0R+ 8 | S8hXspq9VqXO4KnpqoP2RR+9dRzQa/FuWOwrSky89iOFbW1eICzPNvN/PrLc65Si 9 | L8z5xJoie7YKIk5IsRiH+jQmbg4LGnEjCjbyI620XE3WJQf7Ufp8+RZMG3sO2MZX 10 | jagJMcwZC3LVh0ItCcC+/2aMqXAFDSv+SJmUXKwQkcgFejH0U3HfN1TfhVxIHTZY 11 | NAVSDnYpFudRhgC0OviuwG8CmGFM9Mb+hRjJATG9FgOutSQ9drZbLZgTk+wZePCu 12 | gh6LWhzd6COnvsAvpgggCCKdhqqnSM8GSI3omxHD3LvhXe1OU7o/AlEofsrL3kdn 13 | UeImUIECgYEA1PjBFNa1ZRhBbWvODTtgZoyg2HAkIp9jL1GWJD7JLNUs9wgdyyOf 14 | N5wZOX/tw9oJDnMkp364DYWAERfRQGXE9zMGo+yFh1CjUL+3s3aLqYOWLwJPbB16 15 | Yp4jISov/bswilbE5MoXwAe6fgZ2sCesvN9qW0hjJnntBpF9o/JrB+cCgYEAxHNQ 16 | JVCggOmKoFXuTnaaYi5cPyI6rbBQg3xt7wtKNRmw9cYQ4vns45ivTiKxy3J9mFml 17 | IqI8rpgZaRqUvjHRQpsrJXhG5vjmLGmqJ9bsC07ft17bPY2G7Ec/K/t11c/FaxQf 18 | 51IImAnS1F9zhqHHvMmlTZUTH2UtsG7VYSU5yWkCgYEArCz7NBStC7EDXCn2qH3j 19 | XqS8eJ6YqCEBd8TWxDC6RXOGaaR3Kb5/vt6FChZhq5+/CJ6P+JWayNuwAKGnuw9L 20 | oKnw6V+TSCUqE2MbaYwiNwY5yYFtojNmuQmtzucja1cFVCH4BhtX/hiBbddd5uX8 21 | Yamhe3DXL8kMle3TjFK0susCgYBEZSVVKIII/8ZM1j4WwUI0xpFFRcSKLiWJ8atQ 22 | m2ylmiE7IE9Gxt7Lyp/WI0wlWeISRbuxmCxqWPgX2LJKmYuSlfVqoTqGNcflVJBy 23 | OGiL81T+QZ8xCfIclx2OUYkP/Ltt8V6+KZhmXp0xZV4SVIb22nmbzjgPKSnEEIJn 24 | xQ8eaQKBgQCqfJs+rg/m+2TEYJoVam3vnS7jikV+u/3csrPT/2pzdn7ho0COF32q 25 | 4os7H0C/3GFUB4oAgcEYSKVAi3pjSOvOP4R+Kx4gJjLXzg1FQCA6dEEZ/ppVlMc1 26 | FTfgX7ufPtJL/HeP7xJNwVIpPRemHRApzr46Zs1U3OPFqwezJLozUw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /terraform/modules/alb/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_alb" "default" { 2 | name = "${var.alb_name}" 3 | internal = false 4 | load_balancer_type = "application" 5 | security_groups = ["${aws_security_group.alb.id}"] 6 | subnets = ["${var.public_subnet_ids}"] 7 | 8 | tags { 9 | Environment = "${var.environment}" 10 | } 11 | } 12 | 13 | resource "aws_alb_target_group" "default" { 14 | name = "tf-lab-tg" 15 | port = "80" 16 | protocol = "HTTP" 17 | vpc_id = "${var.vpc_id}" 18 | 19 | health_check { 20 | healthy_threshold = 2 21 | interval = 15 22 | path = "/" 23 | timeout = 10 24 | unhealthy_threshold = 2 25 | } 26 | } 27 | 28 | resource "aws_alb_listener" "alb_listener" { 29 | load_balancer_arn = "${aws_alb.default.arn}" 30 | port = "80" 31 | protocol = "HTTP" 32 | 33 | default_action { 34 | target_group_arn = "${aws_alb_target_group.default.arn}" 35 | type = "forward" 36 | } 37 | } 38 | 39 | # elb security group to access the ELB over HTTP 40 | resource "aws_security_group" "alb" { 41 | name = "elb_sg" 42 | description = "managed by terraform - ALB SG" 43 | vpc_id = "${var.vpc_id}" 44 | 45 | # HTTP access from anywhere 46 | ingress { 47 | from_port = 80 48 | to_port = 80 49 | protocol = "tcp" 50 | cidr_blocks = ["${var.allow_cidr_block}"] 51 | } 52 | 53 | # outbound internet access 54 | egress { 55 | from_port = 0 56 | to_port = 0 57 | protocol = "-1" 58 | cidr_blocks = ["0.0.0.0/0"] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /terraform/modules/alb/outputs.tf: -------------------------------------------------------------------------------- 1 | output "dns" { 2 | value = "${aws_alb.default.dns_name}" 3 | } 4 | 5 | output "target_group" { 6 | value = "${aws_alb_target_group.default.arn}" 7 | } 8 | -------------------------------------------------------------------------------- /terraform/modules/alb/variables.tf: -------------------------------------------------------------------------------- 1 | variable "alb_name" { 2 | default = "default" 3 | description = "The name of the loadbalancer" 4 | } 5 | 6 | variable "environment" { 7 | description = "The name of the environment" 8 | } 9 | 10 | variable "vpc_id" { 11 | description = "The VPC id" 12 | } 13 | 14 | variable "allow_cidr_block" { 15 | default = "0.0.0.0/0" 16 | description = "Specify cird block that is allowd to acces the LoadBalancer" 17 | } 18 | 19 | variable "public_subnet_ids" { 20 | type = "list" 21 | description = "List of public subnet IDs" 22 | } 23 | -------------------------------------------------------------------------------- /terraform/modules/bastion/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_ami" "ubuntu" { 2 | most_recent = true 3 | 4 | filter { 5 | name = "name" 6 | values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*"] 7 | } 8 | 9 | filter { 10 | name = "virtualization-type" 11 | values = ["hvm"] 12 | } 13 | 14 | owners = ["099720109477"] # Canonical 15 | } 16 | -------------------------------------------------------------------------------- /terraform/modules/bastion/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "bastion" { 2 | ami = "${data.aws_ami.ubuntu.id}" 3 | instance_type = "${var.instance_type}" 4 | vpc_security_group_ids = ["${aws_security_group.bastion.id}"] 5 | key_name = "${var.key_name}" 6 | subnet_id = "${var.public_subnet_ids[0]}" 7 | associate_public_ip_address = true 8 | 9 | tags { 10 | Environment = "${var.environment}" 11 | } 12 | } 13 | 14 | # Create security group to access instances 15 | resource "aws_security_group" "bastion" { 16 | name = "sg_bastion" 17 | description = "security group for bastion host" 18 | vpc_id = "${var.vpc_id}" 19 | 20 | # SSH access 21 | ingress { 22 | from_port = 22 23 | to_port = 22 24 | protocol = "tcp" 25 | cidr_blocks = ["0.0.0.0/0"] 26 | } 27 | 28 | # Outbound internet access 29 | egress { 30 | from_port = 0 31 | to_port = 0 32 | protocol = "-1" # allow all protocols 33 | cidr_blocks = ["0.0.0.0/0"] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /terraform/modules/bastion/outputs.tf: -------------------------------------------------------------------------------- 1 | output "sg_id" { 2 | value = "${aws_security_group.bastion.id}" 3 | } 4 | 5 | output "dns" { 6 | value = "${aws_instance.bastion.public_dns}" 7 | } 8 | -------------------------------------------------------------------------------- /terraform/modules/bastion/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "The name of the environment" 3 | } 4 | 5 | variable "vpc_id" { 6 | description = "VPC id to place to subnet into" 7 | } 8 | 9 | variable "instance_type" { 10 | description = "EC2 instance type" 11 | } 12 | 13 | variable "key_name" { 14 | description = "SSH key name to be used" 15 | } 16 | 17 | variable "public_subnet_ids" { 18 | type = "list" 19 | description = "List of public subnet IDs" 20 | } 21 | -------------------------------------------------------------------------------- /terraform/modules/ec2/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_ami" "webserver" { 2 | filter { 3 | name = "state" 4 | values = ["available"] 5 | } 6 | 7 | filter { 8 | name = "tag:Name" 9 | values = ["webserver"] 10 | } 11 | 12 | most_recent = true 13 | } -------------------------------------------------------------------------------- /terraform/modules/ec2/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_launch_configuration" "launch_config" { 2 | image_id = "${data.aws_ami.webserver.id}" 3 | instance_type = "${var.instance_type}" 4 | security_groups = ["${aws_security_group.web.id}"] 5 | associate_public_ip_address = false 6 | key_name = "${var.key_name}" 7 | 8 | lifecycle { 9 | create_before_destroy = true 10 | } 11 | } 12 | 13 | resource "aws_autoscaling_group" "asg" { 14 | name = "asg-app - ${aws_launch_configuration.launch_config.name}" 15 | launch_configuration = "${aws_launch_configuration.launch_config.name}" 16 | min_size = "${var.min_size}" 17 | max_size = "${var.max_size}" 18 | target_group_arns = ["${var.target_group}"] 19 | vpc_zone_identifier = ["${var.private_subnet_ids}"] 20 | 21 | depends_on = ["aws_launch_configuration.launch_config"] 22 | 23 | lifecycle { 24 | create_before_destroy = true 25 | } 26 | } 27 | 28 | # Create security group to access instances 29 | resource "aws_security_group" "web" { 30 | name = "sg_webserver" 31 | description = "security group for web servers" 32 | vpc_id = "${var.vpc_id}" 33 | 34 | # HTTP access 35 | ingress { 36 | from_port = 80 37 | to_port = 80 38 | protocol = "tcp" 39 | cidr_blocks = ["0.0.0.0/0"] 40 | } 41 | 42 | ingress { 43 | from_port = 22 44 | to_port = 22 45 | protocol = "tcp" 46 | security_groups = ["${var.sg_bastion}"] 47 | } 48 | 49 | # Outbound internet access 50 | egress { 51 | from_port = 0 52 | to_port = 0 53 | protocol = "-1" # allow all protocols 54 | cidr_blocks = ["0.0.0.0/0"] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /terraform/modules/ec2/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "The name of the environment" 3 | } 4 | 5 | variable "vpc_id" { 6 | description = "VPC id to place to subnet into" 7 | } 8 | 9 | variable "instance_type" { 10 | description = "EC2 instance type" 11 | } 12 | 13 | variable "key_name" { 14 | description = "SSH key name to be used" 15 | } 16 | 17 | variable "sg_bastion" { 18 | description = "Bastion host Security Group ID" 19 | } 20 | 21 | variable "max_size" { 22 | description = "Max EC2 instance count in the auto-scaling group" 23 | } 24 | 25 | variable "min_size" { 26 | description = "Min EC2 instance count in the auto-scaling group" 27 | } 28 | 29 | variable "private_subnet_ids" { 30 | type = "list" 31 | description = "List of private subnet IDs" 32 | } 33 | 34 | variable "target_group" { 35 | description = "Target group ARN" 36 | } 37 | -------------------------------------------------------------------------------- /terraform/modules/nat_gateway/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_nat_gateway" "nat_gw" { 2 | allocation_id = "${element(aws_eip.nat.*.id, count.index)}" 3 | subnet_id = "${element(var.subnet_ids, count.index)}" 4 | count = "${var.subnet_count}" 5 | } 6 | 7 | resource "aws_eip" "nat" { 8 | vpc = true 9 | count = "${var.subnet_count}" 10 | } 11 | -------------------------------------------------------------------------------- /terraform/modules/nat_gateway/outputs.tf: -------------------------------------------------------------------------------- 1 | output "ids" { 2 | value = "${aws_nat_gateway.nat_gw.*.id}" 3 | } 4 | -------------------------------------------------------------------------------- /terraform/modules/nat_gateway/variables.tf: -------------------------------------------------------------------------------- 1 | variable "subnet_ids" { 2 | type = "list" 3 | description = "List of subnets in which to place the NAT Gateway" 4 | } 5 | 6 | variable "subnet_count" { 7 | description = "Size of the subnet_ids. This needs to be provided because: value of 'count' cannot be computed" 8 | } 9 | 10 | variable "vpc_id" { 11 | description = "VPC id to place to subnet into" 12 | } 13 | -------------------------------------------------------------------------------- /terraform/modules/private_subnet/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_subnet" "subnet" { 2 | vpc_id = "${var.vpc_id}" 3 | cidr_block = "${element(var.cidrs, count.index)}" 4 | availability_zone = "${element(var.availability_zones, count.index)}" 5 | count = "${length(var.cidrs)}" 6 | 7 | tags { 8 | Name = "${var.name}_${element(var.availability_zones, count.index)}" 9 | Environment = "${var.environment}" 10 | } 11 | } 12 | 13 | resource "aws_route_table" "table" { 14 | vpc_id = "${var.vpc_id}" 15 | count = "${length(var.cidrs)}" 16 | 17 | tags { 18 | Name = "${var.name}_${element(var.availability_zones, count.index)}" 19 | Environment = "${var.environment}" 20 | } 21 | } 22 | 23 | resource "aws_route" "private_nat_route" { 24 | count = "${length(var.cidrs)}" 25 | route_table_id = "${element(aws_route_table.table.*.id, count.index)}" 26 | nat_gateway_id = "${element(var.nat_gateway_id, count.index)}" 27 | destination_cidr_block = "${var.destination_cidr_block}" 28 | } 29 | 30 | resource "aws_route_table_association" "subnet" { 31 | subnet_id = "${element(aws_subnet.subnet.*.id, count.index)}" 32 | route_table_id = "${element(aws_route_table.table.*.id, count.index)}" 33 | count = "${length(var.cidrs)}" 34 | } 35 | -------------------------------------------------------------------------------- /terraform/modules/private_subnet/outputs.tf: -------------------------------------------------------------------------------- 1 | output "ids" { 2 | value = [ 3 | "${aws_subnet.subnet.*.id}", 4 | ] 5 | } 6 | 7 | output "route_table_ids" { 8 | value = [ 9 | "${aws_route_table.table.*.id}", 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /terraform/modules/private_subnet/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Name of the subnet, actual name will be, for example: name_eu-west-1a" 3 | } 4 | 5 | variable "environment" { 6 | description = "The name of the environment" 7 | } 8 | 9 | variable "cidrs" { 10 | type = "list" 11 | description = "List of cidrs, for every avalibility zone you want you need one. Example: 10.0.0.0/24 and 10.0.1.0/24" 12 | } 13 | 14 | variable "destination_cidr_block" { 15 | default = "0.0.0.0/0" 16 | description = "Specify all traffic to be routed either trough Internet Gateway or NAT to access the internet" 17 | } 18 | 19 | variable "availability_zones" { 20 | type = "list" 21 | description = "List of avalibility zones you want. Example: eu-west-1a and eu-west-1b" 22 | } 23 | 24 | variable "vpc_id" { 25 | description = "VPC id to place to subnet into" 26 | } 27 | 28 | variable "nat_gateway_id" { 29 | type = "list" 30 | description = "NAT Gateway id" 31 | } 32 | -------------------------------------------------------------------------------- /terraform/modules/public_subnet/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_subnet" "subnet" { 2 | vpc_id = "${var.vpc_id}" 3 | cidr_block = "${element(var.cidrs, count.index)}" 4 | availability_zone = "${element(var.availability_zones, count.index)}" 5 | count = "${length(var.cidrs)}" 6 | 7 | tags { 8 | Name = "${var.name}_${element(var.availability_zones, count.index)}" 9 | Environment = "${var.environment}" 10 | } 11 | } 12 | 13 | resource "aws_route_table" "table" { 14 | vpc_id = "${var.vpc_id}" 15 | count = "${length(var.cidrs)}" 16 | 17 | tags { 18 | Name = "${var.name}_${element(var.availability_zones, count.index)}" 19 | Environment = "${var.environment}" 20 | } 21 | } 22 | 23 | resource "aws_route" "public_igw_route" { 24 | count = "${length(var.cidrs)}" 25 | route_table_id = "${element(aws_route_table.table.*.id, count.index)}" 26 | gateway_id = "${var.igw_id}" 27 | destination_cidr_block = "${var.destination_cidr_block}" 28 | } 29 | 30 | resource "aws_route_table_association" "subnet" { 31 | subnet_id = "${element(aws_subnet.subnet.*.id, count.index)}" 32 | route_table_id = "${element(aws_route_table.table.*.id, count.index)}" 33 | count = "${length(var.cidrs)}" 34 | } 35 | -------------------------------------------------------------------------------- /terraform/modules/public_subnet/outputs.tf: -------------------------------------------------------------------------------- 1 | output "ids" { 2 | value = [ 3 | "${aws_subnet.subnet.*.id}", 4 | ] 5 | } 6 | 7 | output "route_table_ids" { 8 | value = [ 9 | "${aws_route_table.table.*.id}", 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /terraform/modules/public_subnet/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Name of the subnet, actual name will be, for example: name_eu-west-1a" 3 | } 4 | 5 | variable "environment" { 6 | description = "The name of the environment" 7 | } 8 | 9 | variable "cidrs" { 10 | type = "list" 11 | description = "List of cidrs, for every avalibility zone you want you need one. Example: 10.0.0.0/24 and 10.0.1.0/24" 12 | } 13 | 14 | variable "destination_cidr_block" { 15 | default = "0.0.0.0/0" 16 | description = "Specify all traffic to be routed either trough Internet Gateway or NAT to access the internet" 17 | } 18 | 19 | variable "availability_zones" { 20 | type = "list" 21 | description = "List of avalibility zones you want. Example: eu-west-1a and eu-west-1b" 22 | } 23 | 24 | variable "vpc_id" { 25 | description = "VPC id to place to subnet into" 26 | } 27 | 28 | variable "igw_id" { 29 | description = "IGW id" 30 | } 31 | -------------------------------------------------------------------------------- /terraform/modules/vpc/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_vpc" "vpc" { 2 | cidr_block = "${var.cidr}" 3 | enable_dns_hostnames = true 4 | 5 | tags { 6 | Name = "${var.environment}" 7 | Environment = "${var.environment}" 8 | } 9 | } 10 | 11 | # Create an internet gateway 12 | resource "aws_internet_gateway" "igw" { 13 | vpc_id = "${aws_vpc.vpc.id}" 14 | 15 | tags { 16 | Environment = "${var.environment}" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /terraform/modules/vpc/outputs.tf: -------------------------------------------------------------------------------- 1 | output "id" { 2 | value = "${aws_vpc.vpc.id}" 3 | } 4 | 5 | output "igw" { 6 | value = "${aws_internet_gateway.igw.id}" 7 | } 8 | -------------------------------------------------------------------------------- /terraform/modules/vpc/variables.tf: -------------------------------------------------------------------------------- 1 | variable "cidr" { 2 | description = "VPC cidr block. Example: 10.0.0.0/16" 3 | } 4 | 5 | variable "environment" { 6 | description = "The name of the environment" 7 | } 8 | -------------------------------------------------------------------------------- /terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | # Output ALB DNS Name 2 | output "ALB DNS Name" { 3 | value = "${module.alb.dns}" 4 | } 5 | 6 | output "Bastion DNS Name" { 7 | value = "${module.bastion.dns}" 8 | } 9 | -------------------------------------------------------------------------------- /terraform/state.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "s3" { 3 | bucket = "terraform-remote-state-defo" 4 | key = "terraform-state-packer-aws-with-gitlab.tfstate" 5 | region = "eu-west-1" 6 | } 7 | } 8 | --------------------------------------------------------------------------------