:::success # LS lab 2 - Infrastructure as Code (IaC) **Name: Ahmad Mohamad Quasem Almoumani** ::: <!-- This is a comment which gets skiped from rendering --> --- # Task 1 - IaC Theory :::info **Briefly answer for the following questions what is and for what:** ::: * **git repository:** **/.git** : The ".git" directory is the repository of a Git version control system. It contains all the metadata and objects required to maintain the history and state of a Git repository. **/.github** : It is a directory that is used to store configuration files and actions for a specific repository or organization. **.gitignore** : is a file used in a Git repository to specify files or directories that should not be tracked by Git. The contents of the .gitignore file can be specific file patterns, file names or directory names that should not be tracked or committed to the repository. **.gitmodules** : is a file used in a Git repository to specify submodules that are included in the repository. * **ansible directory:** **ansible.cfg** : is a configuration file for Ansible, an open-source IT automation tool. The ansible.cfg file is used to specify the settings for Ansible, such as the location of the library files, the default behavior for module execution, and the default SSH connection settings. **inventory folder** use to define the target systems that Ansible should manage. An inventory file is a list of hosts, or target systems, and the associated information such as their IP addresses, SSH credentials, and the groups they belong to. **roles folder** : a directory that contains roles, which are a way to organize and structure Ansible playbooks. **tasks** : individual actions or steps that are executed on the target systems in an Ansible playbook. Tasks are defined in YAML format and specify the name of the action, the module that should be used to perform the action, and any associated arguments or parameters. **defaults/group_vars** : a subdirectory within the Ansible environment that contains default variables for specific groups of target systems. Group variables allow administrators to specify default values for specific groups of hosts defined in the inventory. **handlers** : a type of task that are triggered by a change in state on the target systems. Handlers are used to perform specific actions in response to a change in state, such as restarting a service, or notifying a monitoring system of a change. **templates** : a type of file that is used to generate configuration files on the target systems. Templates are written in a templating language, such as Jinja2, and allow administrators to specify a template with placeholders for dynamic values. **playbooks folder** : a directory that contains playbooks, which are the scripts that define the actions that should be executed on the target systems. A playbook is a collection of tasks, roles, and other Ansible components that define the actions that should be taken on the target systems. * **terraform folder:** **main. tf** : configuration file that contains the infrastructure code that defines the resources that should be created on the target environment. The main.tf file is the primary configuration file for Terraform and typically contains the majority of the code for a given environment. **variables. tf** : configuration file that contains variables that are used within the Terraform code. Variables are used to provide flexibility and dynamic management of the infrastructure, allowing administrators to make changes to the infrastructure in a centralized and flexible manner. **outputs. tf** : configuration file that contains definitions of output variables. Output variables in Terraform are used to output the values of certain resources or variables after Terraform has run. Outputs can be used to provide information about the state of the infrastructure, such as the IP addresses of virtual machines or the URLs of APIs. --- # Task 2 - Prepare your application --- :::info **Find and choose (it is much better to develop by yourself) a simple application. For example, it could be a web server with the static HTML page, time zones server or a currency calculator. Use whatever programming language that you want (python, golang, C#, java...). Include the link to VCS where your application is stored.** ::: --- * I chose an calculator application using Python language. <center> ![](https://i.imgur.com/KUrlTWn.png) </center> **repo:** ``` https://github.com/weez98/L2/blob/main/calculator.py ``` --- # Task 3 - Dockerize your application --- :::info **1. Build Docker image for your application (make Dockerfile). Look for the best Dockerfile practices and try to follow them.** ::: --- * This is my Dockerfile for the application . <center> ![](https://i.imgur.com/jY59ein.png) </center> **repo:** ``` https://github.com/weez98/L2/blob/main/Dockerfile ``` --- # Task 4 - Deliver your application using Software Configuration Management --- :::info **1. Get your personal cloud account. Free tiers for a AWS and GCP users has been blocked in Russia. If you already have accounts, it should work and be enough for this lab. If not, try other cloud providers with a free subscription: Yandex.Cloud, IBM, Oracle, Alibaba Cloud... If you will not be able to work with cloud, you have to proceed within the local deployment for the whole Task 4.For example, prepare a local virtual machine for the further tasks. Include the explanation into the report why you were forced to work locally.** ::: I used my AWS account for this lab. And yo initialise it in my virtual machine, I installed the AWS CLI by running the command: ``` sudo apt install awscli -y ``` Then I configured the cli to connect with my AWS session with the command `aws configure` as shown below: <center> ![](https://i.imgur.com/Rqlj47b.png) </center> This task recquires one to have the session Access key ID and Secret access key which could be gotten by connecting to AWS and navigating to `IAM > Secret Credentials > Create access keys`: <center> ![](https://i.imgur.com/4BW0CnV.png) </center> --- :::info **2. Use Terraform to deploy your required cloud instance. Please notice that to run terraform init command you have to use VPN. Look for the best Terraform practices and try to follow them. If for a some reason you will not able to use VPN, prepare a local VM using Vargant tool. Include the explanation into the report about the inability to work with VPN.** ::: I will be using terraform to deploy an EC2 instance. My terraform script usually the **main.tf** file will be designed to: - Load the provider in this case AWS - Create an **AWS VPC(Virtual private Cloud)** using the **aws_vpc** resource who’s role is to set an isolated environment for our application - Create an **AWS Subnet** inside the previously created vpc - Create an **Internet gateway** and link it to the vpc. This will help make my application availaible over the internet - Create a **routing table** at subnet level and configure it to route trafic to the Internet gateway - Define a **security group** whose role will be to filter traffic from and to the application subnet - Finally create and **AWS EC2 instance** on which my application will be running. Below is a view of my script which is also in the repo provided above: ```yaml provider "aws" { region = "eu-central-1" } variable vpc_cidr_bloc {} variable subnet_cidr_block {} variable availaibility_zone {} variable env_prefix {} variable public_key_path {} variable myip_address {} resource "aws_vpc" "app-vpc" { cidr_block = var.vpc_cidr_bloc tags = { Name = "${var.env_prefix}-vpc" } } resource "aws_subnet" "app-subnet" { vpc_id = aws_vpc.app-vpc.id cidr_block = var.subnet_cidr_block availability_zone = var.availaibility_zone tags = { Name = "${var.env_prefix}-subnet" } } resource "aws_default_route_table" "main-route-table" { default_route_table_id = aws_vpc.app-vpc.default_route_table_id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.app-igw.id } tags = { Name = "${var.env_prefix}-main-rtb" } } resource "aws_internet_gateway" "app-igw" { vpc_id = aws_vpc.app-vpc.id tags = { Name = "${var.env_prefix}-igw" } } resource "aws_default_security_group" "default-sg" { vpc_id = aws_vpc.app-vpc.id ingress { protocol = "tcp" from_port = 8080 to_port = 8080 cidr_blocks = [ "0.0.0.0/0" ] } ingress { protocol = "tcp" from_port = 3000 to_port = 3000 cidr_blocks = [ "0.0.0.0/0" ] } ingress { protocol = "tcp" from_port = 22 to_port = 22 cidr_blocks = [ "${var.myip_address}" ] } egress { protocol = "-1" from_port = 0 to_port = 0 cidr_blocks = [ "0.0.0.0/0" ] prefix_list_ids = [] } tags = { Name = "${var.env_prefix}-default-sg" } } output "ec2_public_ip" { value = aws_instance.app-server.public_ip } resource "aws_key_pair" "ssh-key" { key_name = "ssh-key" public_key = file(var.public_key_path) } resource "aws_instance" "app-server" { ami = "ami-09cd747c78a9add63" instance_type = "t2.micro" vpc_security_group_ids = [ aws_default_security_group.default-sg.id ] subnet_id = aws_subnet.app-subnet.id availability_zone = var.availaibility_zone associate_public_ip_address = true key_name = aws_key_pair.ssh-key.key_name tags = { Name = "${var.env_prefix}-server" } } ``` And the variables used in my script are defined in a `terraform.tfvars` file as follows: ```yaml vpc_cidr_bloc = "10.10.0.0/16" subnet_cidr_block = "10.10.1.0/24" availaibility_zone = "eu-central-1a" env_prefix = "ls-lab2" public_key_path = "/home/st4/.ssh/id_rsa.pub" myip_address = "0.0.0.0/0" ``` Then apply the configuration using `terraform init` and `terraform apply --auto-approve` <center> ![](https://i.imgur.com/42FBrh6.png) ![](https://i.imgur.com/k38QDTR.png) </center> --- :::info **3. Choose Software Configuration Management (SCM) tool. Ansible is the industry standard. Of course, we have other SCM solutions such as SaltStack, Puppet, Chef. You can try them but remember that it is probably more difficult to work with these tools and you are responsible for your choice.** ::: Again I used the industry standard **Ansible**. Installed it on my local running ubuntu machine as follows: ```sh= curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python3 get-pip.py --user python3 -m pip install --user ansible ``` --- :::info **4. Using SCM tool, write a playbook tasks to deliver and run your application to cloud instance/to local VM. Try to separate your configuration files into inventory/roles/playbooks files. In real practice, we almost newer use poor playbooks where everything all in one. Also try to use the best practices for you SCM tool, e.g. Ansible.** ::: I created the playbook below for deploying my application. ```yaml= --- - name: Deploy Git, Docker, and Docker Compose hosts: 100.25.132.202 become: true tasks: - name: Update apt repository and cache apt: update_cache=yes force_apt_get=yes - name: Install Git and Docker apt: pkg: - git - docker - name: Clone Git repository hosts: 100.25.132.202 become: true tasks: - name: Remove previous repository ansible.builtin.command: "true" args: removes: /home/ubuntu/L2 - name: Clone Git Repository git: repo=https://github.com/weez98/L2.git dest=/home/ubuntu/L2 - name: Running the different containers hosts: 100.25.132.202 become: true tasks: - name: Stop previous container exixtence shell: docker rm -f calc - name: Build the application image shell: docker build -t calc /home/ubuntu/L2 - name: Run app in a container docker_container: name: calculator image: calc state: started ports: - "5000:5000" ``` Finally ran the command `ansible-playbook -i hosts config.yaml` to apply the configuration and deploy my application to the cloud EC2 instance: <center> ![](https://i.imgur.com/SpuRxiG.png) ![](https://i.imgur.com/C88pRpM.png) </center> --- # Task 5 - Teamwork with the version control system :::info **1. a) Create and log in to your personal version control system account on the git engine:** ::: I created my personal version control system account on github <center> ![](https://i.imgur.com/xZcAApw.png) </center> I then created a **develop** branch and protected the main branch from direct pushes. <center> ![](https://i.imgur.com/R0Ekz8b.png) </center> --- :::info **b) Create a repository with your application/microservices and all required code/configs.** ::: I created a repository and I uploaded my application <center> ![](https://i.imgur.com/ie4wha1.png) </center> **repo:** ``` https://github.com/weez98/L2 ``` --- :::info **c) Synchronize your local and remote repository.** ::: I used these commands for Synchronize ![](https://i.imgur.com/x64t6oE.png) --- :::info **2. Learn some advanced git features. Answer for the following questions and show a PoC wherever it is possible for you:** ::: **a) What's the difference between git pull and git fetch ?** git fetch updates your local repository's copy of a remote repository, but does not merge the changes into your local branch. git pull updates the local branch with changes from the remote repository and automatically merges the changes. --- **b) What's the difference between git rebase and git merge ?** both used to integrate changes from one branch into another branch. **git merge** creates a new merge commit in the target branch that brings together the changes from the source branch. --- **git rebase**, on the other hand, reapplies the changes from the source branch on top of the target branch. --- **c) How we can Rebase one branch with commits from other head branch? And to replace one branch by another?** To rebase a branch with changes from another branch, i can use the git rebase command. Here's the basic syntax for rebasing a branch called branchA with changes from another branch called branchB: **$ git checkout branchA $ git rebase branchB** This will reapply the changes from branchB on top of branchA. If there are conflicts during the rebase, i'll need to resolve them before continuing the rebase. --- **d) How we can replace (overwrite) one branch with other remote upstream branch entirely** To replace one local branch with a remote upstream branch entirely, i can use the git fetch and git reset commands. Here's the basic syntax: **$ git fetch origin <upstream-branch> $ git checkout <local-branch> $ git reset --hard origin/<upstream-branch>** This will update my local copy of the remote upstream-branch, then switch to the local local-branch and reset it to the remote upstream-branch. The --hard option discards any local changes in the local-branch and replaces them with the changes from the remote upstream-branch. --- **e) How we can add a remote-tracking repository? When it's suitable?** A remote-tracking repository is a local representation of a remote repository that i have cloned from. You can add a remote-tracking repository to my local repository using the git remote command. Here's the basic syntax: **$ git remote add <remote-name> <remote-url>** Where remote-name is a shorthand name for the remote repository (e.g. origin), and remote-url is the URL of the remote repository (e.g. https://github.com/user/repo.git). --- **f) How we can merge fork branch with origin/upstream branch?** To merge changes from a fork of a remote repository into the upstream repository first need to add the upstream repository as a remote-tracking repository in my local fork. and can do this using the git remote command. Here's the basic syntax: **$ git remote add upstream <upstream-repository-url>** --- **g) How we can push the new branch to origin when this branch is set up for tracking to the specified remote-tracking repository?** To push a new local branch to a remote repository use this command **$ git push -u origin [branch_name]** --- **h) How we can join several commits into one?** To join several commits into a single commit, i use the git rebase command with the --interactive or -i option. This will open an editor where i specify which commits you want to squash (combine) into a single commit. **$ git rebase -i HEAD~2** --- **i) How we can change commit message?** To change the message of a recent commit, i use the git rebase command with the --interactive or -i option. This will open an editor where i specify the new message for the commit. Here's an example of how to change the message of the most recent commit: **$ git rebase -i HEAD~1** --- **j) How we can cancel(undo) the last pushed commit to the target remote branch?** To undo the last pushed commit to a remote branch, i use the git revert command or the git reset command, depending on my use case. If you just want to undo the changes introduced by the last commit, without deleting it, i use the git revert command: **$ git revert HEAD**