From db4d19771c386c8824c85a3a227c40b051ae8998 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Sun, 4 Jun 2023 07:29:39 +0530 Subject: [PATCH] feat: O11y cluster - Packer, Terraform and Ansible --- ...1y.yml => terraform--ops-cluster-o11y.yml} | 6 +- .gitignore | 54 +--- ansible/Makefile | 22 ++ ansible/ansible.cfg | 12 + ansible/inventory/o11y/linode.yml | 12 + ansible/play-o11y--0-initialize.yml | 8 + ansible/play-o11y--9-uptime.yml | 11 + ansible/play-test--0-docker-swarm.yml | 51 ++++ ansible/requirements.txt | 4 + ansible/requirements.yml | 4 + ansible/roles/dns/tasks/main.yml | 18 ++ ansible/roles/docker/defaults/main.yml | 15 ++ ansible/roles/docker/tasks/install-docker.yml | 29 +++ ansible/roles/docker/tasks/install-swarm.yml | 55 ++++ ansible/roles/docker/tasks/main.yml | 16 ++ ansible/roles/ubuntu/tasks/main.yml | 6 + ansible/roles/ubuntu/tasks/reboot.yml | 8 + ansible/roles/ubuntu/tasks/update.yml | 22 ++ .../linode/scripts/ansible/install-common.yml | 11 +- .../linode/scripts/ansible/install-docker.yml | 10 +- terraform/cluster-o11y/.terraform.lock.hcl | 23 -- terraform/cluster-o11y/main.tf | 101 ------- .../cluster-o11y/terraform.tfvars.sample | 6 - .../ops-cluster-o11y/.terraform.lock.hcl | 37 +++ .../backend.tf | 0 .../ops-cluster-o11y/cloud-init--userdata.yml | 12 + terraform/ops-cluster-o11y/main.tf | 246 ++++++++++++++++++ .../outputs.tf | 0 .../providers.tf | 0 .../ops-cluster-o11y/terraform.tfvars.sample | 4 + .../variables.tf | 23 +- .../versions.tf | 0 terraform/ops-dns/.terraform.lock.hcl | 14 + terraform/ops-dns/main.tf | 25 +- 34 files changed, 668 insertions(+), 197 deletions(-) rename .github/workflows/{terraform--cluster-o11y.yml => terraform--ops-cluster-o11y.yml} (96%) create mode 100644 ansible/Makefile create mode 100644 ansible/ansible.cfg create mode 100644 ansible/inventory/o11y/linode.yml create mode 100644 ansible/play-o11y--0-initialize.yml create mode 100644 ansible/play-o11y--9-uptime.yml create mode 100644 ansible/play-test--0-docker-swarm.yml create mode 100644 ansible/requirements.txt create mode 100644 ansible/requirements.yml create mode 100644 ansible/roles/dns/tasks/main.yml create mode 100644 ansible/roles/docker/defaults/main.yml create mode 100644 ansible/roles/docker/tasks/install-docker.yml create mode 100644 ansible/roles/docker/tasks/install-swarm.yml create mode 100644 ansible/roles/docker/tasks/main.yml create mode 100644 ansible/roles/ubuntu/tasks/main.yml create mode 100644 ansible/roles/ubuntu/tasks/reboot.yml create mode 100644 ansible/roles/ubuntu/tasks/update.yml delete mode 100644 terraform/cluster-o11y/.terraform.lock.hcl delete mode 100644 terraform/cluster-o11y/main.tf delete mode 100644 terraform/cluster-o11y/terraform.tfvars.sample create mode 100644 terraform/ops-cluster-o11y/.terraform.lock.hcl rename terraform/{cluster-o11y => ops-cluster-o11y}/backend.tf (100%) create mode 100644 terraform/ops-cluster-o11y/cloud-init--userdata.yml create mode 100644 terraform/ops-cluster-o11y/main.tf rename terraform/{cluster-o11y => ops-cluster-o11y}/outputs.tf (100%) rename terraform/{cluster-o11y => ops-cluster-o11y}/providers.tf (100%) create mode 100644 terraform/ops-cluster-o11y/terraform.tfvars.sample rename terraform/{cluster-o11y => ops-cluster-o11y}/variables.tf (55%) rename terraform/{cluster-o11y => ops-cluster-o11y}/versions.tf (100%) diff --git a/.github/workflows/terraform--cluster-o11y.yml b/.github/workflows/terraform--ops-cluster-o11y.yml similarity index 96% rename from .github/workflows/terraform--cluster-o11y.yml rename to .github/workflows/terraform--ops-cluster-o11y.yml index f80fee50..9a3334d1 100644 --- a/.github/workflows/terraform--cluster-o11y.yml +++ b/.github/workflows/terraform--ops-cluster-o11y.yml @@ -1,17 +1,17 @@ -name: TF -- Linode - Cluster o11y +name: TF -- Linode - Ops Cluster o11y on: pull_request: branches: - main paths: - - 'terraform/cluster-o11y/**' + - 'terraform/ops-cluster-o11y/**' env: TF_CLOUD_ORGANIZATION: freecodecamp TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }} TF_WORKSPACE: tfws-ops-o11y - CONFIG_DIRECTORY: terraform/cluster-o11y + CONFIG_DIRECTORY: terraform/ops-cluster-o11y GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: diff --git a/.gitignore b/.gitignore index 98804113..01663795 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,3 @@ -**/*.txt -*.pem -manifest.json - -*.env -scratchpad/ -*.act.secrets - -# keys -*id_rsa* - -# other -.idea -.DS_Store -*.out -secrets.auto.tfvars -.envrc -*.zip -*.gz -*.tgz -*.env* -.vscode - -**/coverage/* - -### Terraform ### # Local .terraform directories **/.terraform/* @@ -33,13 +7,14 @@ secrets.auto.tfvars # Crash log files crash.log +crash.*.log -# Exclude all .tfvars files, which are likely to contain sentitive data, such as +# Exclude all .tfvars files, which are likely to contain sensitive data, such as # password, private keys, and other secrets. These should not be part of version # control as they are data points which are potentially sensitive and subject # to change depending on the environment. -# *.tfvars +*.tfvars.json # Ignore override files as they are usually used to override resources locally and so # are not checked in @@ -58,22 +33,11 @@ override.tf.json .terraformrc terraform.rc -tsconfig.tsbuildinfo -*.d.ts -*.js - -node_modules -cdktf.out -cdktf.log - -*terraform.*.tfstate* -.gen -.terraform +# Ignore Visual Studio Code files +.vscode/ -tf-cdk-ts/scripts/data/ +# Ignore User-specific temporary files +__scratchpad__/ -!jest.config.js -!setup.js -!.eslintrc.js - -dist +# Ignore generated files +manifest.json diff --git a/ansible/Makefile b/ansible/Makefile new file mode 100644 index 00000000..5b9a31a4 --- /dev/null +++ b/ansible/Makefile @@ -0,0 +1,22 @@ +SHELL := /bin/bash + +.DEFAULT_GOAL := help + +.PHONY: help +help: + @echo "Usage: make [Target] [Environment Variables]" + @echo "" + @echo "Targets:" + @echo " help Show this help message" + @echo " install Install ansible and ansible-lint" + + +.PHONY: install +install: + pip install ansible ansible-lint + pip install -r requirements.txt + ansible-galaxy install -r requirements.yml + +.PHONY: check-inventory +check-inventory: + ansible-inventory -i inventory --graph diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 00000000..ff29997d --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,12 @@ +[defaults] +python_interpreter=/usr/bin/python3 +host_key_checking=false +stdout_callback=yaml +ansible_user=freecodecamp +remote_user=freecodecamp + +[inventory] +enable_plugins = community.general.linode +cache = true +cache_connection = ~/.ansible/.cache +cache_timeout = 60 diff --git a/ansible/inventory/o11y/linode.yml b/ansible/inventory/o11y/linode.yml new file mode 100644 index 00000000..62db3a4b --- /dev/null +++ b/ansible/inventory/o11y/linode.yml @@ -0,0 +1,12 @@ +# IMPORTANT: This file has to be be named linode.yml or linode.yaml +# See: https://github.com/ansible-collections/community.general/blob/011b2f8bdc2a042f0eb44739ff51ce425f391afa/plugins/inventory/linode.py#L274 + +plugin: community.general.linode # can be replaced with linode.cloud.instance, when they support Jinja2 template strings +access_token: "{{ lookup('env', 'LINODE_API_TOKEN') }}" + +groups: + managers: "'o11y_leader' in (tags|list)" + workers: "'o11y_worker' in (tags|list)" + +compose: + ansible_ssh_host: ipv4[0] diff --git a/ansible/play-o11y--0-initialize.yml b/ansible/play-o11y--0-initialize.yml new file mode 100644 index 00000000..80bb46de --- /dev/null +++ b/ansible/play-o11y--0-initialize.yml @@ -0,0 +1,8 @@ +--- +- name: Initialize a O11y Cluster + hosts: all + become: true + roles: + - ubuntu # Update the OS and reboot the server + - dns # Configure ansible facts for networking info lookup + - docker # Intialize docker and docker swarm cluster diff --git a/ansible/play-o11y--9-uptime.yml b/ansible/play-o11y--9-uptime.yml new file mode 100644 index 00000000..4680a0bf --- /dev/null +++ b/ansible/play-o11y--9-uptime.yml @@ -0,0 +1,11 @@ +--- +- name: Check uptime on virtual machines + hosts: '{{ variable_host | default("all") }}' + gather_facts: true + become_user: root + tasks: + - name: Print uptime + debug: + msg: + 'Host machine {{ inventory_hostname }} has been up for {{ + ansible_facts.uptime_seconds/86400 }} days' diff --git a/ansible/play-test--0-docker-swarm.yml b/ansible/play-test--0-docker-swarm.yml new file mode 100644 index 00000000..c3e1716f --- /dev/null +++ b/ansible/play-test--0-docker-swarm.yml @@ -0,0 +1,51 @@ +--- +- name: Start a docker swarm cluster and test it + hosts: all + become: true + vars: + # Use `--extra-vars '{ "variable_purge_test" : false }'` to skip + # deleteing the resources. This is helpful if you want to keep + # debugging manually after the playbook run is finished. + purge_test: '{{ variable_purge_test | default(true) }}' + + roles: + - ubuntu # Update the OS and reboot the server + - dns # Configure ansible facts for networking info lookup + - docker # Intialize docker and docker swarm cluster + + tasks: + - name: Run a docker container + docker_container: + name: echo + image: hashicorp/http-echo + state: started + restart_policy: always + command: ['-text', 'hello world from {{ ansible_hostname }}'] + ports: + - '5080:5678' + + - name: Test the docker container + uri: + url: http://localhost:5080 + return_content: yes + register: result + + - name: Print the result + debug: + msg: '{{ result.content }}' + + - name: Stop the docker container + docker_container: + name: echo + image: hashicorp/http-echo + state: absent + when: purge_test + + - name: Prune the docker system + docker_prune: + images: yes + containers: yes + volumes: yes + networks: yes + builder_cache: yes + when: purge_test diff --git a/ansible/requirements.txt b/ansible/requirements.txt new file mode 100644 index 00000000..d9206e90 --- /dev/null +++ b/ansible/requirements.txt @@ -0,0 +1,4 @@ +linode-api4>=5.5.1 +polling>=0.3.2 +types-requests==2.31.0.1 +ansible-specdoc>=0.0.13 diff --git a/ansible/requirements.yml b/ansible/requirements.yml new file mode 100644 index 00000000..f8066553 --- /dev/null +++ b/ansible/requirements.yml @@ -0,0 +1,4 @@ +--- +collections: + - community.general + - community.docker diff --git a/ansible/roles/dns/tasks/main.yml b/ansible/roles/dns/tasks/main.yml new file mode 100644 index 00000000..b854a656 --- /dev/null +++ b/ansible/roles/dns/tasks/main.yml @@ -0,0 +1,18 @@ +--- +# We expect the /etc/hostname file to be populated with a FQDN +# when the host is provisioned, using terraform or cloud-init. +# +# The FQDN should point to a Public IP address that is resolvable +# +- name: Get Hostname from /etc/hostname + slurp: + src: /etc/hostname + register: hostname + +- name: Set the anisble_fqdn + set_fact: + ansible_fqdn: "{{ hostname['content'] | b64decode }}" + +- name: Print the ansible_fqdn + debug: + var: ansible_fqdn diff --git a/ansible/roles/docker/defaults/main.yml b/ansible/roles/docker/defaults/main.yml new file mode 100644 index 00000000..adca4972 --- /dev/null +++ b/ansible/roles/docker/defaults/main.yml @@ -0,0 +1,15 @@ +--- +# defaults file for roles/docker + +docker_packages: + - apt-transport-https + - ca-certificates + - curl + - software-properties-common + - docker-ce + - docker-ce-cli + - containerd.io + - python3-docker +docker_repo: deb [arch=amd64] https://download.docker.com/linux/{{ ansible_distribution|lower }} {{ ansible_distribution_release }} stable +docker_repo_key: https://download.docker.com/linux/ubuntu/gpg +docker_repo_key_id: 0EBFCD88 diff --git a/ansible/roles/docker/tasks/install-docker.yml b/ansible/roles/docker/tasks/install-docker.yml new file mode 100644 index 00000000..2cc71bb7 --- /dev/null +++ b/ansible/roles/docker/tasks/install-docker.yml @@ -0,0 +1,29 @@ +# tasks file for roles/docker +--- +- name: add gpg key + apt_key: + url: "{{ docker_repo_key }} " + state: present + +- name: Add repository + apt_repository: + repo: "{{ docker_repo }}" + +- name: install docker and dependencies + apt: + name: "{{ docker_packages }}" + state: latest + update_cache: yes + cache_valid_time: 3600 + with_items: "{{ docker_packages}}" + +- name: Add user to docker group + user: + name: "{{ ansible_user }}" + group: docker + +- name: start docker + service: + name: docker + state: started + enabled: yes diff --git a/ansible/roles/docker/tasks/install-swarm.yml b/ansible/roles/docker/tasks/install-swarm.yml new file mode 100644 index 00000000..7dac591d --- /dev/null +++ b/ansible/roles/docker/tasks/install-swarm.yml @@ -0,0 +1,55 @@ +# tasks file for roles/swarm +--- +- name: Retrieve the initial Swarm Info + community.docker.docker_swarm_info: + register: swarm_info_initial + when: inventory_hostname == groups['managers'][0] + ignore_errors: true + no_log: "{{ variable_no_log | default (true) }}" + changed_when: swarm_info_initial.docker_swarm_active == false + +- name: Initialize Swarm + community.docker.docker_swarm: + state: present + advertise_addr: "{{ ansible_default_ipv4.address }}" + listen_addr: "{{ ansible_default_ipv4.address }}:2377" + when: inventory_hostname == groups['managers'][0] and swarm_info_initial.docker_swarm_active == false + no_log: "{{ variable_no_log | default (true) }}" + +- name: Refresh the Swarm Info + community.docker.docker_swarm_info: + nodes: true + register: swarm_info + when: inventory_hostname == groups['managers'][0] + no_log: "{{ variable_no_log | default (true) }}" + +# TODO: Add checks and add more managers if needed + +- name: Set useful information on Workers as Facts + set_fact: + swarm_join_token_worker: "{{ hostvars[groups['managers'][0]]['swarm_info']['swarm_facts']['JoinTokens']['Worker'] }}" + swarm_manager_addr: "{{ hostvars[groups['managers'][0]]['ansible_default_ipv4']['address'] }}" + when: inventory_hostname in groups['workers'] + no_log: "{{ variable_no_log | default (true) }}" + +- name: Join Swarm as worker using token + community.docker.docker_swarm: + state: join + advertise_addr: "{{ ansible_default_ipv4.address }}" + join_token: "{{ swarm_join_token_worker }}" + remote_addrs: [ + "{{ swarm_manager_addr }}" + ] + when: inventory_hostname in groups['workers'] + +- name: List Swarm Nodes + community.docker.docker_swarm_info: + nodes: true + when: inventory_hostname == groups['managers'][0] + register: result + no_log: "{{ variable_no_log | default (true) }}" + +- name: Print Swarm Nodes + debug: + msg: "{{ result.nodes }}" + when: inventory_hostname == groups['managers'][0] diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml new file mode 100644 index 00000000..e0b79699 --- /dev/null +++ b/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Check if Docker is installed + stat: + path: /usr/bin/docker + register: docker_installed + +- name: Install Docker if not installed using the role + include_role: + name: docker + tasks_from: install-docker.yml + when: docker_installed.stat.exists == false + +- name: Initialize Swarm using the role + include_role: + name: docker + tasks_from: install-swarm.yml diff --git a/ansible/roles/ubuntu/tasks/main.yml b/ansible/roles/ubuntu/tasks/main.yml new file mode 100644 index 00000000..1422f86c --- /dev/null +++ b/ansible/roles/ubuntu/tasks/main.yml @@ -0,0 +1,6 @@ +--- +- name: Update + import_tasks: update.yml + +- name: Reboot + import_tasks: reboot.yml diff --git a/ansible/roles/ubuntu/tasks/reboot.yml b/ansible/roles/ubuntu/tasks/reboot.yml new file mode 100644 index 00000000..d0a18e4b --- /dev/null +++ b/ansible/roles/ubuntu/tasks/reboot.yml @@ -0,0 +1,8 @@ +--- +- name: Reboot + ansible.builtin.reboot: + connect_timeout: 5 + reboot_timeout: 300 + pre_reboot_delay: 120 + post_reboot_delay: 120 + test_command: uptime diff --git a/ansible/roles/ubuntu/tasks/update.yml b/ansible/roles/ubuntu/tasks/update.yml new file mode 100644 index 00000000..b467cac7 --- /dev/null +++ b/ansible/roles/ubuntu/tasks/update.yml @@ -0,0 +1,22 @@ +# tasks file for roles/ubuntu +--- +- name: Update apt repo and cache + apt: + update_cache: true + force_apt_get: true + cache_valid_time: 3600 + +- name: Upgrade all packages + apt: + upgrade: dist + force_apt_get: true + +- name: Autoclean old packages + apt: + autoclean: true + force_apt_get: true + +- name: Autoremove unused packages + apt: + autoremove: true + force_apt_get: true diff --git a/packer/linode/scripts/ansible/install-common.yml b/packer/linode/scripts/ansible/install-common.yml index d1d1a5ce..6a4dcd41 100644 --- a/packer/linode/scripts/ansible/install-common.yml +++ b/packer/linode/scripts/ansible/install-common.yml @@ -4,10 +4,9 @@ become: true vars: ansible_python_interpreter: /usr/bin/python3 - stdout_callback: "yaml" + stdout_callback: 'yaml' tasks: - - name: Update apt package index ansible.builtin.apt: update_cache: true @@ -21,22 +20,22 @@ - name: Install common packages ansible.builtin.apt: - name: "{{ packages }}" + name: '{{ packages }}' state: present autoclean: true autoremove: true purge: true vars: packages: + - build-essential - curl - git - htop - - vim - software-properties-common - - build-essential + - tar - unzip + - vim - zip - - tar - name: Disable automatic and security updates ansible.builtin.apt: diff --git a/packer/linode/scripts/ansible/install-docker.yml b/packer/linode/scripts/ansible/install-docker.yml index 748e3ab5..33107bda 100644 --- a/packer/linode/scripts/ansible/install-docker.yml +++ b/packer/linode/scripts/ansible/install-docker.yml @@ -4,7 +4,7 @@ become: true vars: ansible_python_interpreter: /usr/bin/python3 - stdout_callback: "yaml" + stdout_callback: 'yaml' tasks: - name: Update apt package index @@ -20,6 +20,10 @@ - curl - gnupg - lsb-release + - python3-pip + - python3-setuptools + - software-properties-common + - virtualenv state: present autoclean: true autoremove: true @@ -41,3 +45,7 @@ become: true register: docker_install changed_when: docker_install.stdout.find('Setting up docker-ce') != -1 + + - name: Install Docker Module for Python + pip: + name: docker diff --git a/terraform/cluster-o11y/.terraform.lock.hcl b/terraform/cluster-o11y/.terraform.lock.hcl deleted file mode 100644 index 56e1a65b..00000000 --- a/terraform/cluster-o11y/.terraform.lock.hcl +++ /dev/null @@ -1,23 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/linode/linode" { - version = "2.4.0" - constraints = "2.4.0" - hashes = [ - "h1:0vQLEOllh4hwouO3k2ZLM2iFWbh2gK3eNFNWx/N9g0Q=", - "h1:4yNboZ1GUnPdvWOynTZLw6OVqTyqKwnUITysLr/PYx4=", - "h1:6EY9AF05EH/8+AW/zmnyLs4b5UYCKfMFKbq/1BKTIIE=", - "h1:6x6RpPE5DztuyFMffsxn8cG1E4qlQlcMaAMXFwz/tz4=", - "h1:FteeLVH9elsP90bxkqzDlpkeeXGeE5770hEhazjpLBc=", - "h1:UswMlusRYzdJC3x+EMYyMbX57EOxjj6OfANREVICbYk=", - "h1:W2vUu8eaODe2YdvZ135msn57GLjvAujb7XduG+TuGgg=", - "h1:aVDU+7BI/qfxp8xkS97b+Ohsklwj9dSODzGduDvjj7A=", - "h1:bGx8Zjsfme9JN3wQDB7CBCJc9xT7dBmtEeC+cZShevA=", - "h1:e8NYDeVUGwsJgPy6sjzt+svqac7SGfbSrfRjlXKu6cs=", - "h1:sxRr4uzPtZQhCmWveKXK+8XPP1TLW8hErv3yx/qn2n0=", - "h1:tVohNV/2tNCr8WCHcTF6JFM0dwdp0QGr9C9fEcfAV5c=", - "h1:uXc/TizlgpMBa8so/KVCcQAt+ZP4EL5XaNe+gOMtFFk=", - "h1:xh2wy1GU4pVkQPXtQTlZ9sZBFhuwjftzfrt8d4Ek6ZA=", - ] -} diff --git a/terraform/cluster-o11y/main.tf b/terraform/cluster-o11y/main.tf deleted file mode 100644 index 1484ce24..00000000 --- a/terraform/cluster-o11y/main.tf +++ /dev/null @@ -1,101 +0,0 @@ -# This data source depends on the stackscript resource -# which is created in terraform/ops-stackscripts/main.tf -data "linode_stackscripts" "cloudinit_scripts" { - filter { - name = "label" - values = ["CloudInit"] - } -} - -# This data source depends on the domain resource -# which is created in terraform/ops-dns/main.tf -data "linode_domain" "ops_dns_domain" { - domain = "freecodecamp.net" -} - -resource "linode_instance" "ops_o11y_leaders" { - count = var.leader_node_count - image = var.image_id - label = "ops-vm-o11y-ldr-${count.index + 1}" - group = "ops-o11y" - region = var.region - type = "g6-standard-2" - root_pass = var.password - - stackscript_id = data.linode_stackscripts.cloudinit_scripts.stackscripts.0.id - stackscript_data = { - userdata = "${var.userdata}" - } - - connection { - type = "ssh" - user = "root" - password = var.password - host = self.ip_address - } - - provisioner "remote-exec" { - inline = [ - # Update the system. - "apt-get update -qq", - # Disable password authentication; users can only connect with an SSH key. - "sed -i '/PasswordAuthentication/d' /etc/ssh/sshd_config", - "echo \"PasswordAuthentication no\" >> /etc/ssh/sshd_config", - # Set the hostname. - "hostnamectl set-hostname ${self.label}" - ] - } -} - -resource "linode_domain_record" "ops_o11y_leaders_records" { - count = var.leader_node_count - - domain_id = data.linode_domain.ops_dns_domain.id - name = "ldr-${count.index + 1}.o11y.${data.linode_domain.ops_dns_domain.domain}" - record_type = "A" - target = linode_instance.ops_o11y_leaders[count.index].ip_address - ttl_sec = 60 -} - -resource "linode_instance" "ops_o11y_workers" { - count = var.worker_node_count - image = var.image_id - label = "ops-vm-o11y-wkr-${count.index + 1}" - group = "ops-o11y" - region = var.region - type = "g6-standard-2" - root_pass = var.password - - stackscript_id = data.linode_stackscripts.cloudinit_scripts.stackscripts.0.id - stackscript_data = { - userdata = "${var.userdata}" - } - - connection { - type = "ssh" - user = "root" - password = var.password - host = self.ip_address - } - - provisioner "remote-exec" { - inline = [ - # Update the system. - "apt-get update -qq", - # Disable password authentication; users can only connect with an SSH key. - "sed -i '/PasswordAuthentication/d' /etc/ssh/sshd_config", - "echo \"PasswordAuthentication no\" >> /etc/ssh/sshd_config", - "hostnamectl set-hostname ${self.label}" - ] - } -} - -resource "linode_domain_record" "ops_o11y_workers_records" { - count = var.worker_node_count - - domain_id = data.linode_domain.ops_dns_domain.id - name = "wkr-${count.index + 1}.o11y.${data.linode_domain.ops_dns_domain.domain}" - record_type = "A" - target = linode_instance.ops_o11y_workers[count.index].ip_address - ttl_sec = 60 -} diff --git a/terraform/cluster-o11y/terraform.tfvars.sample b/terraform/cluster-o11y/terraform.tfvars.sample deleted file mode 100644 index e1e92ccd..00000000 --- a/terraform/cluster-o11y/terraform.tfvars.sample +++ /dev/null @@ -1,6 +0,0 @@ -linode_token = "" -password = "" -worker_node_count = 3 -leader_node_count = 1 -image_id = "private/ami-ubuntu-20.04-golden" -base64_userdata = "I2Nsb3VkLWNvbmZpZwp1c2VyczoKICAtIG5hbWU6IGZyZWVjb2RlY2FtcAogICAgZ3JvdXBzOiBzdWRvCiAgICBzaGVsbDogIC9iaW4vYmFzaAogICAgc3VkbzogWydBTEw9KEFMTCkgTk9QQVNTV0Q6QUxMJ10KICAgIHNzaF9pbXBvcnRfaWQ6CiAgICAgIC0gZ2g6Y2FtcGVyYm90CiAgICAgIC0gcmFpc2VkYWRlYWQKcnVuY21kOgogIC0gdXNlcm1vZCAtYUcgZG9ja2VyIGZyZWVjb2RlY2FtcApmaW5hbF9tZXNzYWdlOiAnU2V0dXAgY29tcGxldGUnCg==" diff --git a/terraform/ops-cluster-o11y/.terraform.lock.hcl b/terraform/ops-cluster-o11y/.terraform.lock.hcl new file mode 100644 index 00000000..9d9873aa --- /dev/null +++ b/terraform/ops-cluster-o11y/.terraform.lock.hcl @@ -0,0 +1,37 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/linode/linode" { + version = "2.4.0" + constraints = "2.4.0" + hashes = [ + "h1:0vQLEOllh4hwouO3k2ZLM2iFWbh2gK3eNFNWx/N9g0Q=", + "h1:4yNboZ1GUnPdvWOynTZLw6OVqTyqKwnUITysLr/PYx4=", + "h1:6EY9AF05EH/8+AW/zmnyLs4b5UYCKfMFKbq/1BKTIIE=", + "h1:6x6RpPE5DztuyFMffsxn8cG1E4qlQlcMaAMXFwz/tz4=", + "h1:FteeLVH9elsP90bxkqzDlpkeeXGeE5770hEhazjpLBc=", + "h1:UswMlusRYzdJC3x+EMYyMbX57EOxjj6OfANREVICbYk=", + "h1:W2vUu8eaODe2YdvZ135msn57GLjvAujb7XduG+TuGgg=", + "h1:aVDU+7BI/qfxp8xkS97b+Ohsklwj9dSODzGduDvjj7A=", + "h1:bGx8Zjsfme9JN3wQDB7CBCJc9xT7dBmtEeC+cZShevA=", + "h1:e8NYDeVUGwsJgPy6sjzt+svqac7SGfbSrfRjlXKu6cs=", + "h1:sxRr4uzPtZQhCmWveKXK+8XPP1TLW8hErv3yx/qn2n0=", + "h1:tVohNV/2tNCr8WCHcTF6JFM0dwdp0QGr9C9fEcfAV5c=", + "h1:uXc/TizlgpMBa8so/KVCcQAt+ZP4EL5XaNe+gOMtFFk=", + "h1:xh2wy1GU4pVkQPXtQTlZ9sZBFhuwjftzfrt8d4Ek6ZA=", + "zh:04a5e3edb9130fec836c9b2f3b7074316816b24303a3c292e12be4d488c13963", + "zh:197c3b94ca13b039d96a196d69568af04625c03adf26537a676c8b836b0cf7f4", + "zh:465c29bed6da32c556b67483ee6d407b84ac4d4ed7ddd6b544b34004e6468058", + "zh:5adbdf874c27e63e878e613a5b4ec41269bf46755535ac7fa00bcd015b995a64", + "zh:8622e9239ceb44a4d879db255f2a8c1c9919b5abfa71d8723ffddbead88bd74c", + "zh:90c1afa50645abd63b6abcdc04b48b3a5267c59c0534a3c4cc2b5a8e7e47469b", + "zh:985d415ff7221dc0e2443474a6e121a42c7a70b85a69e5baeabdc9e209e68185", + "zh:a836f794e5f20fecb3897a5db3944e80c8c3b7eb9df71d2011dd20e3d6876389", + "zh:a86193fc1e5b249d0356777991973f669cee809223e7373530162cd88974a227", + "zh:ac34155f864ec8b745f5551aabdaf8b7e5bec4ec7567b072b90878f7f5911ba6", + "zh:cc4267d20613bad6421303e712aa7342df207d7e15db894ca1db82c77e66d527", + "zh:d900a4a00e8987129136917d3fed12ea43b32305797ce800c9b40aa74eb03296", + "zh:e2722f95129378a4c5088cc7de8f4df06ba3b6d62e4271e75f50377b7769b069", + "zh:f4e4508f4ae103266d1fb9ec6326aaba29af11d6ad49492493e768d3f93b4c11", + ] +} diff --git a/terraform/cluster-o11y/backend.tf b/terraform/ops-cluster-o11y/backend.tf similarity index 100% rename from terraform/cluster-o11y/backend.tf rename to terraform/ops-cluster-o11y/backend.tf diff --git a/terraform/ops-cluster-o11y/cloud-init--userdata.yml b/terraform/ops-cluster-o11y/cloud-init--userdata.yml new file mode 100644 index 00000000..4dda6331 --- /dev/null +++ b/terraform/ops-cluster-o11y/cloud-init--userdata.yml @@ -0,0 +1,12 @@ +#cloud-config +users: + - name: freecodecamp + groups: sudo + shell: /bin/bash + sudo: ['ALL=(ALL) NOPASSWD:ALL'] + ssh_import_id: + - gh:camperbot + - raisedadead +runcmd: + - sudo usermod -aG docker freecodecamp +final_message: 'Setup complete' diff --git a/terraform/ops-cluster-o11y/main.tf b/terraform/ops-cluster-o11y/main.tf new file mode 100644 index 00000000..442f9c39 --- /dev/null +++ b/terraform/ops-cluster-o11y/main.tf @@ -0,0 +1,246 @@ +# This data source depends on the stackscript resource +# which is created in terraform/ops-stackscripts/main.tf +data "linode_stackscripts" "cloudinit_scripts" { + filter { + name = "label" + values = ["CloudInit"] + } +} + +# This data source depends on the domain resource +# which is created in terraform/ops-dns/main.tf +data "linode_domain" "ops_dns_domain" { + domain = "freecodecamp.net" +} + +resource "linode_instance" "ops_o11y_leaders" { + count = var.leader_node_count + label = "ops-vm-o11y-ldr-${count.index + 1}" + group = "o11y-ldr" + region = var.region + type = "g6-standard-2" + + tags = ["ops", "o11y", "o11y_leader"] # tags should use underscores for Ansible compatibility +} + +resource "linode_instance_disk" "ops_o11y_leaders_disk__boot" { + count = var.leader_node_count + label = "ops-vm-o11y-ldr-${count.index + 1}-boot" + linode_id = linode_instance.ops_o11y_leaders[count.index].id + size = linode_instance.ops_o11y_leaders[count.index].specs.0.disk + + image = var.image_id + root_pass = var.password + + stackscript_id = data.linode_stackscripts.cloudinit_scripts.stackscripts.0.id + stackscript_data = { + userdata = filebase64("${path.root}/cloud-init--userdata.yml") + } +} + +resource "linode_instance_config" "ops_o11y_leaders_config" { + count = var.leader_node_count + label = "ops-vm-o11y-ldr-config" + linode_id = linode_instance.ops_o11y_leaders[count.index].id + + devices { + sda { + disk_id = linode_instance_disk.ops_o11y_leaders_disk__boot[count.index].id + } + } + + # eth0 is the public interface. + interface { + purpose = "public" + } + + # eth1 is the private interface. + interface { + purpose = "vlan" + label = "o11y-vlan" + # This results in IPAM address like 10.0.0.11/24, 10.0.0.12/24, etc. + ipam_address = "${cidrhost("10.0.0.0/8", 10 + count.index + 1)}/24" + } + + connection { + type = "ssh" + user = "root" + password = var.password + host = linode_instance.ops_o11y_leaders[count.index].ip_address + } + + provisioner "remote-exec" { + inline = [ + # Disable password authentication; users can only connect with an SSH key. + "sed -i '/PasswordAuthentication/d' /etc/ssh/sshd_config", + "echo \"PasswordAuthentication no\" >> /etc/ssh/sshd_config", + # Set the hostname. + "hostnamectl set-hostname ldr-${count.index + 1}.o11y.${data.linode_domain.ops_dns_domain.domain}" + ] + } + + helpers { + updatedb_disabled = true + } + + booted = true +} + +resource "linode_domain_record" "ops_o11y_leaders_records" { + count = var.leader_node_count + + domain_id = data.linode_domain.ops_dns_domain.id + name = "ldr-${count.index + 1}.o11y" + record_type = "A" + target = linode_instance.ops_o11y_leaders[count.index].ip_address + ttl_sec = 120 +} + +resource "linode_domain_record" "ops_o11y_leaders_records__public" { + count = var.leader_node_count + + domain_id = data.linode_domain.ops_dns_domain.id + name = "pub.ldr-${count.index + 1}.o11y" + record_type = "A" + target = linode_instance.ops_o11y_leaders[count.index].ip_address + ttl_sec = 120 +} + +resource "linode_domain_record" "ops_o11y_leaders_records__private" { + count = var.leader_node_count + + domain_id = data.linode_domain.ops_dns_domain.id + name = "prv.ldr-${count.index + 1}.o11y" + record_type = "A" + target = trimsuffix(linode_instance_config.ops_o11y_leaders_config[count.index].interface[1].ipam_address, "/24") + ttl_sec = 120 +} + +resource "linode_instance" "ops_o11y_workers" { + count = var.worker_node_count + label = "ops-vm-o11y-wkr-${count.index + 1}" + group = "o11y-wkr" + region = var.region + type = "g6-standard-2" + + tags = ["ops", "o11y", "o11y_worker"] +} + +resource "linode_instance_disk" "ops_o11y_workers_disk__boot" { + count = var.worker_node_count + label = "ops-vm-o11y-wkr-${count.index + 1}-boot" + linode_id = linode_instance.ops_o11y_workers[count.index].id + size = linode_instance.ops_o11y_workers[count.index].specs.0.disk + + image = var.image_id + root_pass = var.password + + stackscript_id = data.linode_stackscripts.cloudinit_scripts.stackscripts.0.id + stackscript_data = { + userdata = filebase64("${path.root}/cloud-init--userdata.yml") + } +} + +resource "linode_instance_config" "ops_o11y_workers_config" { + count = var.worker_node_count + label = "ops-vm-o11y-wkr-config" + linode_id = linode_instance.ops_o11y_workers[count.index].id + + devices { + sda { + disk_id = linode_instance_disk.ops_o11y_workers_disk__boot[count.index].id + } + } + + # eth0 is the public interface. + interface { + purpose = "public" + } + + # eth1 is the private interface. + interface { + purpose = "vlan" + label = "o11y-vlan" + # This results in IPAM address like 10.0.0.21/24, 10.0.0.22/24, etc. + ipam_address = "${cidrhost("10.0.0.0/8", 20 + count.index + 1)}/24" + } + + connection { + type = "ssh" + user = "root" + password = var.password + host = linode_instance.ops_o11y_workers[count.index].ip_address + } + + provisioner "remote-exec" { + inline = [ + # Update the system. + "apt-get update -qq", + # Disable password authentication; users can only connect with an SSH key. + "sed -i '/PasswordAuthentication/d' /etc/ssh/sshd_config", + "echo \"PasswordAuthentication no\" >> /etc/ssh/sshd_config", + # Set the hostname. + "hostnamectl set-hostname wkr-${count.index + 1}.o11y.${data.linode_domain.ops_dns_domain.domain}" + ] + } + + helpers { + updatedb_disabled = true + } + + booted = true +} + +resource "linode_domain_record" "ops_o11y_workers_records" { + count = var.worker_node_count + + domain_id = data.linode_domain.ops_dns_domain.id + name = "wkr-${count.index + 1}.o11y" + record_type = "A" + target = linode_instance.ops_o11y_workers[count.index].ip_address + ttl_sec = 120 +} + +resource "linode_domain_record" "ops_o11y_workers_records__public" { + count = var.worker_node_count + + domain_id = data.linode_domain.ops_dns_domain.id + name = "pub.wkr-${count.index + 1}.o11y" + record_type = "A" + target = linode_instance.ops_o11y_workers[count.index].ip_address + ttl_sec = 120 +} + +resource "linode_domain_record" "ops_o11y_workers_records__private" { + count = var.worker_node_count + + domain_id = data.linode_domain.ops_dns_domain.id + name = "prv.wkr-${count.index + 1}.o11y" + record_type = "A" + target = trimsuffix(linode_instance_config.ops_o11y_workers_config[count.index].interface[1].ipam_address, "/24") + ttl_sec = 120 +} + +resource "linode_firewall" "ops_o11y_firewall" { + label = "ops-fw-o11y" + + inbound { + label = "allow-ssh" + ports = "22" + protocol = "TCP" + action = "ACCEPT" + ipv4 = ["0.0.0.0/0"] + ipv6 = ["::/0"] + } + + inbound_policy = "DROP" + + # outbound { } + + outbound_policy = "ACCEPT" + + linodes = flatten([ + [for i in linode_instance.ops_o11y_leaders : i.id], + [for i in linode_instance.ops_o11y_workers : i.id], + ]) +} diff --git a/terraform/cluster-o11y/outputs.tf b/terraform/ops-cluster-o11y/outputs.tf similarity index 100% rename from terraform/cluster-o11y/outputs.tf rename to terraform/ops-cluster-o11y/outputs.tf diff --git a/terraform/cluster-o11y/providers.tf b/terraform/ops-cluster-o11y/providers.tf similarity index 100% rename from terraform/cluster-o11y/providers.tf rename to terraform/ops-cluster-o11y/providers.tf diff --git a/terraform/ops-cluster-o11y/terraform.tfvars.sample b/terraform/ops-cluster-o11y/terraform.tfvars.sample new file mode 100644 index 00000000..9bfa48cb --- /dev/null +++ b/terraform/ops-cluster-o11y/terraform.tfvars.sample @@ -0,0 +1,4 @@ +linode_token = "" +password = "" + +# Override any more variables from the variables.tf file here diff --git a/terraform/cluster-o11y/variables.tf b/terraform/ops-cluster-o11y/variables.tf similarity index 55% rename from terraform/cluster-o11y/variables.tf rename to terraform/ops-cluster-o11y/variables.tf index de5bb286..8ed01663 100644 --- a/terraform/cluster-o11y/variables.tf +++ b/terraform/ops-cluster-o11y/variables.tf @@ -1,32 +1,43 @@ variable "linode_token" { description = "The Linode API Personal Access Token." + type = string } variable "password" { description = "The root password for the Linode instances." + type = string } variable "worker_node_count" { description = "The number of worker instances to create." default = 3 + type = number + + validation { + condition = var.worker_node_count > 0 + error_message = "The number of worker instances must atleast 1." + } } variable "leader_node_count" { description = "The number of leader instances to create." default = 1 + type = number + + validation { + condition = var.leader_node_count > 0 && var.leader_node_count <= 3 + error_message = "The number of leader instances must be between 1-3." + } } variable "region" { description = "The name of the region in which to deploy instances." default = "us-east" + type = string } variable "image_id" { description = "The ID for the Linode image to be used in provisioning the instances" - default = "private/20418248" -} - -variable "userdata" { - description = "The userdata to be used in provisioning the instances" - default = "I2Nsb3VkLWNvbmZpZwp1c2VyczoKICAtIG5hbWU6IGZyZWVjb2RlY2FtcAogICAgZ3JvdXBzOiBzdWRvCiAgICBzaGVsbDogIC9iaW4vYmFzaAogICAgc3VkbzogWydBTEw9KEFMTCkgTk9QQVNTV0Q6QUxMJ10KICAgIHNzaF9pbXBvcnRfaWQ6CiAgICAgIC0gZ2g6Y2FtcGVyYm90CiAgICAgIC0gcmFpc2VkYWRlYWQKcnVuY21kOgogIC0gdXNlcm1vZCAtYUcgZG9ja2VyIGZyZWVjb2RlY2FtcApmaW5hbF9tZXNzYWdlOiAnU2V0dXAgY29tcGxldGUnCg==" + default = "private/20521335" + type = string } diff --git a/terraform/cluster-o11y/versions.tf b/terraform/ops-cluster-o11y/versions.tf similarity index 100% rename from terraform/cluster-o11y/versions.tf rename to terraform/ops-cluster-o11y/versions.tf diff --git a/terraform/ops-dns/.terraform.lock.hcl b/terraform/ops-dns/.terraform.lock.hcl index 56e1a65b..9d9873aa 100644 --- a/terraform/ops-dns/.terraform.lock.hcl +++ b/terraform/ops-dns/.terraform.lock.hcl @@ -19,5 +19,19 @@ provider "registry.terraform.io/linode/linode" { "h1:tVohNV/2tNCr8WCHcTF6JFM0dwdp0QGr9C9fEcfAV5c=", "h1:uXc/TizlgpMBa8so/KVCcQAt+ZP4EL5XaNe+gOMtFFk=", "h1:xh2wy1GU4pVkQPXtQTlZ9sZBFhuwjftzfrt8d4Ek6ZA=", + "zh:04a5e3edb9130fec836c9b2f3b7074316816b24303a3c292e12be4d488c13963", + "zh:197c3b94ca13b039d96a196d69568af04625c03adf26537a676c8b836b0cf7f4", + "zh:465c29bed6da32c556b67483ee6d407b84ac4d4ed7ddd6b544b34004e6468058", + "zh:5adbdf874c27e63e878e613a5b4ec41269bf46755535ac7fa00bcd015b995a64", + "zh:8622e9239ceb44a4d879db255f2a8c1c9919b5abfa71d8723ffddbead88bd74c", + "zh:90c1afa50645abd63b6abcdc04b48b3a5267c59c0534a3c4cc2b5a8e7e47469b", + "zh:985d415ff7221dc0e2443474a6e121a42c7a70b85a69e5baeabdc9e209e68185", + "zh:a836f794e5f20fecb3897a5db3944e80c8c3b7eb9df71d2011dd20e3d6876389", + "zh:a86193fc1e5b249d0356777991973f669cee809223e7373530162cd88974a227", + "zh:ac34155f864ec8b745f5551aabdaf8b7e5bec4ec7567b072b90878f7f5911ba6", + "zh:cc4267d20613bad6421303e712aa7342df207d7e15db894ca1db82c77e66d527", + "zh:d900a4a00e8987129136917d3fed12ea43b32305797ce800c9b40aa74eb03296", + "zh:e2722f95129378a4c5088cc7de8f4df06ba3b6d62e4271e75f50377b7769b069", + "zh:f4e4508f4ae103266d1fb9ec6326aaba29af11d6ad49492493e768d3f93b4c11", ] } diff --git a/terraform/ops-dns/main.tf b/terraform/ops-dns/main.tf index 80ff20f8..0bbe2cb6 100644 --- a/terraform/ops-dns/main.tf +++ b/terraform/ops-dns/main.tf @@ -17,18 +17,31 @@ provider "linode" { # # This resource is a Domain resource that may have records # outside of this module or even outside of Terraform. +# resource "linode_domain" "freecodecamp_net" { domain = "freecodecamp.net" type = "master" soa_email = "dev@freecodecamp.org" } -# DO NOT DELETE OR MODIFY THIS RESOURCE -------- -resource "linode_domain_record" "local" { +resource "linode_domain_record" "dmarc" { + domain_id = linode_domain.freecodecamp_net.id + name = "_dmarc" + record_type = "TXT" + target = "v=DMARC1;p=reject;sp=reject;adkim=s;aspf=s" +} + +resource "linode_domain_record" "spf" { domain_id = linode_domain.freecodecamp_net.id - name = "local" - record_type = "A" - target = "127.0.0.1" + name = linode_domain.freecodecamp_net.domain + record_type = "TXT" + target = "v=spf1 -all" +} - ttl_sec = 3600 +resource "linode_domain_record" "dkim" { + domain_id = linode_domain.freecodecamp_net.id + name = "*._domainkey" + record_type = "TXT" + target = "v=DKIM1; p=" } +# DO NOT DELETE OR MODIFY THIS RESOURCE --------