Edit Page

Lab 9: Virtualization

Install, configure, and manage a custom virtual machine image.

Virtualization Cloud-Computing

Due on Monday 07/11/2022 at 11:59PM

You will install and configure a custom virtual machine image locally. You will use a tool called Packer, which allows you to create custom virtual machine images from one json file for multiple providers (e.g., Azure, AWS, Digital Ocean), hypervisors (e.g., Virtual Box), or tools designed for managing virtual machine environments (e.g., Vagrant). You will also manage your virtual machine environment using Vagrant.

In this lab, you will learn how to create a custom VM image (CentOS) for VirtualBox that can be used with Vagrant. The VM image must have the following installed:

  • Apache httpd web server version 2.x
  • PHP version 7.2
  • nano text eitor
  • MariaDB version 10.x
  • firewalld

Setup/Prerequisites

Step One - Generate SSH keys for SSH key-based authentication

  • We need to authenticate with the custom image using key-based SSH authentication. Create an ssh key-pair using ssh-keygen on your local machine:

       $ ssh-keygen -t rsa -b 4096 -C "vagrant-key"
    
       Generating public/private rsa key pair.
       Enter file in which to save the key (/Users/khalid/.ssh/id_rsa): /Users/khalid/.ssh/vagrant_key
       Enter passphrase (empty for no passphrase): 
       Enter same passphrase again: 
       Your identification has been saved in /Users/khalid/.ssh/vagrant_key.
       Your public key has been saved in /Users/khalid/.ssh/vagrant_key.pub.
       The key fingerprint is:
       be:c7:d1:bc:bd:75:7c:df:d7:a3:95:80:f5:66:13:33 vagrant-key
       The key's randomart image is:
       +--[ RSA 4096]----+
       |                 |
       |                 |
       |             . E |
       |            o . +|
       |        S  + . = |
       |       .  . o +.o|
       |        .. . o o*|
       |         .o . ooB|
       |        ..   .o.=|
       +-----------------+
    

Step Two - Creating a Packer template and provisioning scripts

Packer uses a template file in JSON to create a virtual machine. The template file contains a set of properties and values. The main properties are: builders, provisioners, and post-processors.

  • builders are tasks that produce an image for a single platform. It can be for example virtualBox, AWS, or Azure.
  • provisioners are sections in Packer for running multiple scripts before launching the VM image (e.g., custom bash scripts to install or configure software and tools).
  • post-processors are sections in Packer for running multiple scripts after the machine image has been created (e.g., converting a VirtualBox image into a suitable image or box for Vagrant).

Packer supports two builders for building an image for VirtualBox:

  • virtualbox-iso - This builder is useful when you want to start from an existing ISO file. It creates a new VM image from the ISO file for VirtualBox, provisions the VM with your software and tools, and exports the VM to an image.
  • virtualbox-ovf - Takes an input file in the Open Virtualization Format (ovf or ova) and runs provisioners on top of that VM, and exports that machine to create an image. In order to use this builder, you need to export the existing VM in your hypervisor into an open virtualization format (.ovf) archive file. To export a VM in VirtualBox, go to the File menu in VirtualBox, select Export Appliance, select the VM to export (e.g., CentOS), and export the VM file as .ova.

In this lab, will use the former builder, virtualbox-iso.

Building an image from an ISO using the virtualBox builder (virtualbox-iso)

Step 2.1 Adding Builders:
  • Create a file named centos-7-virtualbox.json in your local machine. This JSON will be considered the template for Packer:
    • Create a directory and a JSON file on your local machine:

         $ mkdir ~/packer-template
         $ cd ~/packer-template
         $ nano centos-7-virtualbox.json
      
    • Add the following content:

      {
          "builders": [
              {
                  "type": "virtualbox-iso",
                  "guest_os_type": "RedHat_64",
                  "iso_url": "/path/to/CentOS-7-x86_64-Minimal-xxxx/CentOS-7-x86_64-Minimal-xxxxx.iso",
                  "iso_checksum": "fabdc67ff3a1674a489953effa285dfd",
                  "iso_checksum_type": "md5",
                  "ssh_username": "{{user `user`}}",
                  "ssh_password": "{{user `password`}}",
                  "ssh_timeout": "20m",
                  "disk_size": "8192",
                  "vm_name": "packer-centOS-7",
                  "http_directory": "http",
                  "boot_wait": "5s",
                  "boot_command": [
                      "<tab> text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos-7-kickstart.cfg<enter><wait>"
                  ],
                  "shutdown_command": "echo '{{user `password`}}' | sudo -S shutdown -P now",
                  "vboxmanage": [
                     [
                      "modifyvm",
                      "{{.Name}}",
                      "--memory",
                      "512"
                      ],
                      [
                      "modifyvm",
                      "{{.Name}}",
                      "--cpus",
                      "1"
                      ]
                  ]
              }
          ]
      }
      
    • You need to change the iso_url to the URL or the path to the .iso file you downloaded, and the iso_checksum contains the md5 checksum of the downloaded .iso file. See below for more details.

      • The value of the iso_checksum property is the the md5 checksum of the file. You can also run the command line tool md5sum or md5 against the .iso file to get checksum value.
      • We specified the disk size to create for the virtual machine in the disk_size key with a value of 8192 in megabytes (8GB).
      • We select the amount of CPU and memory for this virtual machine as an array of commands to the VirtualBox’s vboxmanage command
    • This template has a provisioner of type shell that executes a file named startup.sh. This file will be executed by the value of the execute_command property.

      • The execute_command property has the following value: echo 'root-password-here' | sudo -S sh '{{ .Path }}'. This pipes the root password into the stdin of sudo and execute the shell script as a root user.
Step 2.2 Adding a kickstart installation file

When installing an OS, you will be presented with various options in the user interface. You will have to follow the on-screen installer steps and answer all the questions until the installation is complete. However, we would prefer to use an automated/unattended installation method to speed up and automate the process. Thus, we will create a Kickstart file containing the answers to all the questions that would normally be asked during a typical installation. CentOS’ Kickstart provides a way for users to automate a CentOS Linux installation with. That’s it, a kickstart file will let us install CentOS automatically and answer the installer’s questions without any user interaction.

  • The template file has a key named http_directory, which contains the path to a directory that will be served using an HTTP server. The files in this directory will be available over HTTP and can be requested from the virtual machine. We need to create this directory and create our kickstart file inside this directory.

  • Create a directory named http in the same directory that your Packer template file is in, and create a kickstart file named centos-7-kickstart.cfg inside it:

    $ mkdir http
    $ nano http/centos-7-kickstart.cfg
    
  • Add the following content to the file

       # Install OS instead of upgrade
       install
       cdrom
       # System authorization information
       auth --enableshadow --enablemd5
       # Use text mode install
       text
       # Firewall configuration
       firewall --disabled
       firstboot --disable
       # Keyboard layouts
       keyboard us
       # System language
       lang en_US.UTF-8
    
       # Network information
       network  --bootproto=dhcp --device=eth0 --activate
       network  --hostname=localhost.localdomain
       # Reboot after installation
       reboot
       # Root password
       rootpw vagrant
       # SELinux configuration
       selinux --enforcing
       # System services
       services --enabled="chronyd,NetworkManager,sshd"
       # Do not configure the X Window System
       skipx
       # System timezone
       timezone UTC --isUtc
       user --name=vagrant --password=vagrant --groups=vagrant,wheel
       # System bootloader configuration
       bootloader --location=mbr
       # Clear the Master Boot Record
       zerombr
       # Partition clearing information
       clearpart --all --initlabel
       autopart
    
       %packages --instLangs=en --ignoremissing --
    
       @Base
       @Core
       @Development Tools
       openssh-clients
       sudo
       openssl-devel
       yum-utils
       readline-devel
       zlib-devel
       kernel-headers
       kernel-devel
       net-tools
       bash-completion
       vim
       curl
       rsync
    
       %end
    
       %post
    
       ## Install sudo and configure it to allow passwordless sudo for the "vagrant" user
       # This is important to allow vagrant to install and configure network and other tools
       yum install -y sudo
       echo "vagrant        ALL=(ALL)       NOPASSWD: ALL" >> /etc/sudoers.d/vagrant
       sed -i "s/^.*requiretty/#Defaults requiretty/" /etc/sudoers
    
       yum clean all
    
       %end
    
Step 2.3 Adding Provisioners

We need to perform actions before launching the image. We want to upload our public key that we created in step 1 into the virtual machine image. We also want to execute a shell script to install tools on our vm image.

  • Open the Packer template file and add the following provisioners after the end of the builders array:

       "provisioners": [
           {
               "type": "file",
               "source": "{{user `public_key`}}",
               "destination": "{{user `key_destination`}}"
           },
           {
               "type": "shell",
               "script": "./installer.sh",
               "execute_command": "chmod +x '{{ .Path }}'; echo '{{user `password`}}' | sudo -S sh '{{ .Path }}'"
           },
           {
               "type": "shell",
               "script": "./set_ssh.sh",
               "execute_command": "chmod +x '{{ .Path }}'; echo '{{user `password`}}' | sudo -S sh '{{ .Path }}' {{user `user`}} {{user `key_destination`}}"
           }
       ]
    
  • Create a shell script file that contains the commands to install and configure software in the VM image:

    • Create a file named installer.sh in the same directory that your Packer template file is in. Edit it in your text editor and add the following content:
        #!/bin/bash
        # You must execute this shell script as a root user
        # Exit immediately if any command exits with a non-zero exit status.
        set -e
        echo "Installing software"
        yum update -y
        # Install nano text editor
        yum -y install nano
        # Install Apache httpd
        yum -y install httpd
        # Change Apache root directory ownership
        chown apache:apache /var/www/html
        # SELinux security context
        chcon -Rt httpd_sys_content_t /var/www/html
        setsebool -P httpd_can_network_connect on
        # Install PHP 7.2
        yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
        yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm
        yum -y install yum-utils
        yum-config-manager --enable remi-php72
        yum -y install php
        # Install MariaDB 10.1
        sudo printf "[mariadb]\nname = MariaDB\nbaseurl = http://yum.mariadb.org/10.1/centos7-amd64\ngpgkey = https://yum.mariadb.org/RPM-GPG-KEY-MariaDB\ngpgcheck = 1\n" | sudo tee /etc/yum.repos.d/MariaDB.repo
        yum -y install MariaDB-server MariaDB-client
        # Clean the cache
        yum clean all
    
    • Create another file named set_ssh.sh in the same directory that your Packer template file is in. Edit it in your text editor and add the following content:
        #!/bin/bash
        # Exit immediately if any command exits with a non-zero exit status.
        set -e
        # usage
        if [[ $# -ne 2 ]]
        then
            echo "Error: Usage $0 user_name public_key"
            exit 1
        fi
        # Disable SSH password-based authentication
        sed -n 'H;${x;s/\#PasswordAuthentication yes/PasswordAuthentication no/;p;}' /etc/ssh/sshd_config > new_sshd_config
        cat new_sshd_config > /etc/ssh/sshd_config
        rm new_sshd_config
        # move the public key
        mkdir -p /home/$1/.ssh
        mv $2 /home/$1/.ssh/authorized_keys
        chmod 700 /home/$1/.ssh
        chown -R $1:$1 /home/$1/.ssh
        chmod 644 /home/$1/.ssh/authorized_keys
    
Step 2.4 Adding post-processors

We need to perform actions once the image has been created. We want to convert our virtual machine image into a Vagrant box.

  • Open the Packer template file and add the following post-processors after the end of the provisioners array:

       "post-processors": [
           {
               "type": "vagrant",
               "compression_level": "7",
               "output": "builds/{{.Provider}}-centos7.box"
           }
       ]
    
Step 2.5 Adding sensitive user variables

We want our template to use some common variables and receive sensitive data such as keys, passwords, and other user data dynamically from the command line.

  • Finally, add the following user variables at the beginning of the template file:

       "variables": {
           "key_destination": "/tmp/vagrant_key.pub"
       },
       "sensitive-variables": [
           "user",
           "password",
           "public_key",
           "private_key"
       ]
    

Step Three - Building the Virtual Machine with Packer

If you have followed the previous steps, you should end up with a packer template file that looks like the one below:

{

  "variables": {
    "key_destination": "/tmp/vagrant_key.pub"
  },
  "sensitive-variables": [
    "user",
    "password",
    "public_key",
    "private_key"
  ],
  "builders": [
    {
      "type": "virtualbox-iso",
      "guest_os_type": "RedHat_64",
      "iso_url": "/path/to/CentOS-7-x86_64-Minimal-xxxx/CentOS-7-x86_64-Minimal-xxxx.iso",
      "iso_checksum": "fabdc67ff3a1674a489953effa285dfd",
      "iso_checksum_type": "md5",
      "ssh_username": "{{user `user`}}",
      "ssh_password": "{{user `password`}}",
      "ssh_timeout": "20m",
      "disk_size": "8192",
      "vm_name": "packer-centOS-7",
      "http_directory": "http",
      "boot_wait": "5s",
      "boot_command": [
        "<tab> text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos-7-kickstart.cfg<enter><wait>"
      ],
      "shutdown_command": "echo '{{user `password`}}' | sudo -S shutdown -P now",
      "vboxmanage": [
        [
          "modifyvm",
          "{{.Name}}",
          "--memory",
          "512"
        ],
        [
          "modifyvm",
          "{{.Name}}",
          "--cpus",
          "1"
        ]
      ]
    }
  ],
  "provisioners": [
    {
      "type": "file",
      "source": "{{user `public_key`}}",
      "destination": "{{user `key_destination`}}"
    },
    {
      "type": "shell",
      "script": "./installer.sh",
      "execute_command": "chmod +x '{{ .Path }}'; echo '{{user `password`}}' | sudo -S sh '{{ .Path }}'"
    },
    {
      "type": "shell",
      "script": "./set_ssh.sh",
      "execute_command": "chmod +x '{{ .Path }}'; echo '{{user `password`}}' | sudo -S sh '{{ .Path }}' {{user `user`}} {{user `key_destination`}}"
    }
  ],
  "post-processors": [
    {
      "type": "vagrant",
      "compression_level": "7",
      "output": "builds/{{.Provider}}-centos7.box"
    }
  ]
}
Step 3.1 Validate the template
  • Before building the image, we want to check that our template is valid. We need to pass the user name, password, and public key into packer from the command line as user values.
   $ packer validate -var "user=vagrant" -var "password=vagrant" -var "public_key=/path/to/public/key" -var "private_key=/path/to/private/key" ./centos-7-virtualbox.json 
Step 3.2 Building the image from the template
   $ packer build -var "user=vagrant" -var "password=vagrant" -var "public_key=/path/to/public/key" -var "private_key=/path/to/private/key" ./centos-7-virtualbox.json

This may take a few minutes or more to build the image, start the kickstart script, and install the provisioning scripts. The action defined in the post-processor section of the template will take the build and convert it into a Vagrant box stored at builds/virtualbox-centos7.box.

Step Four — Start and provision the Vagrant box

You should have a Vagrant Box built by Packer. Vagrant Box is the package format for Vagrant environments that include the base image and the additional tools installed on top of the image. This box can be used by anyone to create an identical virtual environment or shared on the public Vagrant box repository.

Step 4.1 - Create a Vagrantfile
  • You need to create a file named Vagrantfile at the same working directory that you created the Packer template at.

    $ cd ~/packer-template
    $ nano Vagrantfile
    
  • Add the following content to the Vagrantfile

       # -*- mode: ruby -*-
       # vi: set ft=ruby :
       user_name = ENV['vagrant_user'] or 'vagrant'
       private_key_path = ENV['vagrant_private_key']
       public_key_path = ENV['vagrant_public_key']
    
       if private_key_path == ''
       puts("Missing private key")
       exit(1)
       elif public_key_path == ''
       puts("Missing public key")
       exit(1)
       end
    
       Vagrant.configure("2") do |config|
       config.vm.box_check_update = true
       config.vm.box = "centos-7-vagrant"
       config.vm.box_url = "file://./builds/virtualbox-centos7.box"
       config.vm.hostname = "virtualbox-centos7"
       config.ssh.host = "127.0.0.1"
       config.ssh.port = 2222
       config.ssh.private_key_path = [private_key_path]
       config.ssh.insert_key = false
       config.vm.network "forwarded_port", guest: 22, host: 2222, host_ip: "127.0.0.1", id: 'ssh'
       config.vm.provision "shell", path: "./set_ssh.sh", args: "#{user_name} #{public_key_path}"
    
       config.vm.provider :virtualbox do |vbox|
           vbox.gui = false
           vbox.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
           vbox.customize ["modifyvm", :id, "--ioapic", "on"]
           vbox.name = "centos-7-vagrant"
       end
    
       end
    
Step 4.2 - Create, start, and access the vagrant box
  • Store the path to the public and private keys in two environment variables vagrant_public_key and vagrant_private_key that are defined in the Vagrantfile.

  • From the command line, export the following environment variables:

    • On a Unix-like system such as macos or Linux, run:

      export vagrant_private_key=/path/to/private/key
      export vagrant_public_key=/path/to/public/key
      
    • On Windows, export may not work, so run:

      set vagrant_private_key=/path/to/private/key
      set vagrant_public_key=/path/to/public/key
      
  • Validate the Vagrantfile

    vagrant validate
    
  • Create your Vagrant Box and provision the vagrant environment in VirtualBox.

    vagrant up
    
  • You will be prompted to enter the passphrase you chose in the first step. Vagrant needs this to provision the created box. If you have encountered an error, refer to the notes section below1.

Step 4.3 - Connect to the vagrant box via SSH and manage it using vagrant
  • Log in to your VM instances using SSH

     vagrant ssh
    
  • You will be prompted to enter the passphrase you chose in step 1.

  • Check the installed software on your vagrant box

    php -v
    mysql -v
    
  • Stop the vagrant machine. You need to exit from the connected VM and then execute vagrant halt.

    exit
    vagrant halt
    

Notes

  1. On Windows, if you get an error message that says “unknown encoding name”, try to change the system locale on your system:
  • Control Panel » Region » Administrative tab » change system locale to English (United States).
  1. On CentOS, there’s a different tool called packer included in the PATH, so when installing packer, you need to resolve the name conflict. Run which -a packer to see if you get an error which means you have a name conflict. Then, rename the installed packer tool into a different name such as packer.io, call it using the absolute path name, or create a symbolic link with a different name (e.g., ln -s /usr/local/packer /usr/local/bin/packer.io). Please refer to this link for more information.

Submission

Submit your answers with screenshots showing the commands you executed as a PDF file by the due date.