Table of Contents

, , , , , , , , ,

About the Club

Cloud Club Topics - Infrastructure as Code ( IaC )

Questions

  1. What is Infrastructure as Code ( IaC )?
  2. Why do we need it? We can do it manually.

Forms of IaC

  1. Configuration files designed for specific virtual platform
    1. Examples
      1. AWS CloudFormation
      2. Custom scripts
  2. Code tools designed for multiple platforms
    1. Examples
      1. Pulumi
      2. Chef
      3. Puppet
      4. Ansible
      5. Terraform

Plan your installation

  1. See “Plan your Installation” checklist from server installationa
  2. Select your IaC tool
  3. Use Git for virsion control!!!

Build the VM

  1. Create the base code needed to build the network, security, and VM

Install software and configure

  1. If capable, add code incrementally to configure the VM as required
  2. If capable, add code incremetnally to install and configure software as required

Hands-on

  1. Build Ansible Server
    1. Create Linux VM ( Manually )
    2. Install Ansible
      sudo dnf install ansible-core -y
    3. Verify ansible command works to display the version
      ansible --version
      
      ansible [core 2.14.17]
        config file = /etc/ansible/ansible.cfg
        configured module search path = ['/home/garfield/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
        ansible python module location = /usr/lib/python3.9/site-packages/ansible
        ansible collection location = /home/garfield/.ansible/collections:/usr/share/ansible/collections
        executable location = /usr/bin/ansible
        python version = 3.9.19 (main, Sep 11 2024, 00:00:00) [GCC 11.5.0 20240719 (Red Hat 11.5.0-2)] (/usr/bin/python3)
        jinja version = 3.1.2

Configure Ansible

  1. ansible.cfg file(s)
    1. Generate your own ansible.cfg in your home directory
      ansible-config init --disabled -t all > ~/ansible.cfg
    2. Review the ansible.cfg file and make changes as desired
  2. setup Ansible static inventory
    1. Append the following to the bottom of the inventory file ( /etc/ansible/hosts )
      [repo-servers]
      repo.cloudclub.edu
      
      [web-servers]
      repo.cloudclub.edu
      
      [dns-servers]
      dns.cloudclub.edu
  3. Test inventory with command
    ansible-inventory --graph
    
    @all:
      |--@ungrouped:
      |--@repo_servers:
      |  |--repo.cloudclub.edu
      |--@web_servers:
      |  |--repo.cloudclub.edu
      |--@dns_servers:
      |  |--dns.cloudclub.edu
    
  4. Setup Ansible dynamic inventorya
  5. Setup ssh keys
    1. Generate ssh key pair - Note: Keep pressing enter ( 3 times ) for the default values until you get your command prompt back
      ssh-keygen
    2. Copy the public key to the target devices - Note: type “yes” if prompted and enter the password when prompted
      ssh-copy-id 192.168.1.11
      
      /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/garfield/.ssh/id_rsa.pub"
      The authenticity of host '192.168.1.11 (192.168.1.11)' can't be established.
      ED25519 key fingerprint is SHA256:CFrma1AskxuKYOHZm1mB0B4y1v1WPPhD2zD+TKcZHSY.
      This host key is known by the following other names/addresses:
          ~/.ssh/known_hosts:1: repo.cloudclub.edu
      Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
      /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
      /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
      garfield@192.168.1.11's password: 
      
      Number of key(s) added: 1
      
      Now try logging into the machine, with:   "ssh '192.168.1.11'"
      and check to make sure that only the key(s) you wanted were added.
    3. Login to the target device and configure ssh with the following content to use the key by default
      command
      =============
      sudo nano ~/.ssh/config
      
      content
      =============
      Host *.cloudclub.edu
              IdentityFile ~/.ssh/id_rsa
    4. Test the key by using ssh with the key
      ssh 192.168.1.11
  6. Test inventory with command
    ansible -m ping dns_servers
    
    dns.cloudclub.edu | SUCCESS => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python3"
        },
        "changed": false,
        "ping": "pong"
    }
    
    -----------------------------------------------------------------
    
    ansible -m ping all
    
    repo.cloudclub.edu | SUCCESS => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python3"
        },
        "changed": false,
        "ping": "pong"
    }
    dns.cloudclub.edu | SUCCESS => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python3"
        },
        "changed": false,
        "ping": "pong"
    }
    
    

Ansbile Components

  1. Accessing and readding the Ansible documentation
    1. There are 2 ways to access the documentation
      1. Online
        1. You can use your browser to navigate through https://docs.ansible.com/
        2. Easiest: Google search for what you're tying to do
          1. Example search: “ansible add user” and select the top result ( reference: https://www.google.com/search?q=ansible+add+user )
      2. ansible-doc command
        1. To see a list of builtin modules
          ansible-doc -l ansible.builtin
        2. To see the documentation on a particular module ( same one as above )
          ansible-doc ansible.builtin.user
  2. Understand the components
    1. Modules
    2. Playbooks
    3. Tasks
    4. Roles
    5. collections
      1. List module collections with command
        ansible-galaxy collection list
      2. Collection installation
        ansible-galaxy collection install <namespace.collection>
      3. Offline collection installation
        1. Use a computer connected to the internet to find the collection on https://galaxy.ansible.com/ui/collections/ and search for the collection you need. For example, you will find https://galaxy.ansible.com/ui/repo/published/ansible/windows/ for the ansible.windows collection.
        2. Compare the installed ansible version from the command listed above with the version listed on the website. Lower the version number on the website until you are ⇐ the installed version.
        3. Click the “Download tarball” link and save the tar.gz file(s) some place where you can transfer the file(s) to the ansible server.
        4. When you're in the directory containing the files, run the following installation command - NOTE: This command assumes you want to install the collection for the entire server instead of individual users.
          sudo ansible-galaxy collection install * -p /usr/share/ansible/collections/

Writing Ansible code

  1. Prepare a location for your ansible codea
    1. Create a “code” directory
      mkdir ~/code
      cd ~/code
    2. As a best practice, create a local git repo
      git init -b main ansible-basic
      cd ansible-basic
  2. Create a playbook with tasks
    1. Edit a new file fist-playbook.yml and add the following content
      - name: My first playbook
        hosts: localhost
          
        tasks:
          - name: My first task
            ansible.builtin.debug:
              msg: Hello, user
                
    2. Run your shiny new playbook
      ansible-playbook first-playbook.yml
      
      PLAY [My first playbook] *************************************************************************************************
      
      TASK [Gathering Facts] ***************************************************************************************************
      ok: [localhost]
      
      TASK [My first task] *****************************************************************************************************
      ok: [localhost] => {
          "msg": "Hello, user"
      }
      
      PLAY RECAP ***************************************************************************************************************
      localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
      
    3. Use git command to add your file
      git add first-playbook.yml
    4. Use git to create a “commit” object
      git commit -m 'Initial version'
      
       1 file changed, 9 insertions(+)
       create mode 100644 first-playbook.yml
    5. Change the string “user” to a veriable
      - name: My first playbook
        hosts: localhost
         
        vars:
          user: Garfield
          
        tasks:
          - name: My first task
            ansible.builtin.debug:
              msg: Hello, {{ user }}
                
    6. Use the above git commands to add the file, create a new commit object with a different message
      git add first-playbook.yml
      git commit -m 'Add user variable'
      
       1 file changed, 4 insertions(+), 1 deletion(-)
    7. ( Optional ) If you want to see the changes made to your files, use the git log command
      git log
    8. Run your updated playbook
      ansible-playbook first-playbook.yml
      
      PLAY [My first playbook] ****************************************************************************************************************************************************************************************
      
      TASK [Gathering Facts] ******************************************************************************************************************************************************************************************
      ok: [localhost]
      
      TASK [My first task] ********************************************************************************************************************************************************************************************
      ok: [localhost] => {
          "msg": "Hello, Garfield"
      }
      
      PLAY RECAP ******************************************************************************************************************************************************************************************************
      localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
      
  3. Createing roles
    1. Create ( initialize ) a new git repository
      git init -b main ansible-roles
      cd ansible-roles
      mkdir roles
      cd roles
    2. use the ansible-galaxy command to create the directory structure of a role
      ansible-galaxy init docs
      
      - Role docs was created successfully
    3. Install the tree command to help visualize the directory structure
      sudo dnf install -y tree
    4. View the directory strcuture
      tree
      
      .
      └── docs
          ├── defaults
          │   └── main.yml
          ├── files
          ├── handlers
          │   └── main.yml
          ├── meta
          │   └── main.yml
          ├── README.md
          ├── tasks
          │   └── main.yml
          ├── templates
          ├── tests
          │   ├── inventory
          │   └── test.yml
          └── vars
              └── main.yml
      
      9 directories, 8 files
      
    5. Add “hidden” files to instruct git to keep these empty directories
      touch docs/files/.gitkeep
      touch docs/templates/.gitkeep
    6. Run the tree command to show the differece
      tree -a
      
      .
      └── docs
          ├── defaults
          │   └── main.yml
          ├── files
          │   └── .gitkeep
          ├── handlers
          │   └── main.yml
          ├── meta
          │   └── main.yml
          ├── README.md
          ├── tasks
          │   └── main.yml
          ├── templates
          │   └── .gitkeep
          ├── tests
          │   ├── inventory
          │   └── test.yml
          └── vars
              └── main.yml
      
      9 directories, 10 files
      
    7. Change to the tasks directory
      cd docs/tasks
    8. Edit the main.yml file with the following content
      ---
      # tasks file for docs
      
      - name: Create a directory
        ansible.builtin.file:
          path: /opt/docs
          state: directory
          user: nobody
          group: nobody
          mode: '0400'
      
      - name: Create an empty file in the new directory
        ansible.builtin.file:
          path: /opt/docs/git-notes.txt
          state: touch
      
    9. Add the new files and create a commit
      git add -A
      git commit -m 'Initial version - Add role'
      
       10 files changed, 120 insertions(+)
       create mode 100644 roles/docs/README.md
       create mode 100644 roles/docs/defaults/main.yml
       create mode 100644 roles/docs/files/.gitkeep
       create mode 100644 roles/docs/handlers/main.yml
       create mode 100644 roles/docs/meta/main.yml
       create mode 100644 roles/docs/tasks/main.yml
       create mode 100644 roles/docs/templates/.gitkeep
       create mode 100644 roles/docs/tests/inventory
       create mode 100644 roles/docs/tests/test.yml
       create mode 100644 roles/docs/vars/main.yml
      
  4. Change to the “root” of the repo
    cd ../..
  5. Create a playbook with the new role
    nano doc-files.yml
    
    - name: My first playbook
      hosts: repo_servers
    
      roles:
        - name: docs
    
  6. Add and commit the new playbook
  7. Run the new playbook in “check” mode. This mode will run the code, but won't actually do the actions.
    ansible-playbook doc-files.yml --check
    
    PLAY [My first playbook] ****************************************************************************************************************************************************************************************
    
    TASK [Gathering Facts] ******************************************************************************************************************************************************************************************
    ok: [repo.cloudclub.edu]
    
    TASK [docs : Create a directory] ********************************************************************************************************************************************************************************
    fatal: [repo.cloudclub.edu]: FAILED! => {"changed": false, "msg": "Unsupported parameters for (ansible.builtin.file) module: user. Supported parameters include: _diff_peek, _original_basename, access_time, access_time_format, attributes, follow, force, group, mode, modification_time, modification_time_format, owner, path, recurse, selevel, serole, setype, seuser, src, state, unsafe_writes (attr, dest, name)."}
    
    PLAY RECAP ******************************************************************************************************************************************************************************************************
    repo.cloudclub.edu         : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
    
    1. Oops! It looks like we found an error. Let's fix it.
      1. “msg”: “Unsupported parameters for (ansible.builtin.file) module: user.
      2. Modify the docs/tasks/main.yml file to change 'user:' to 'owner:' and run the playbook again.
        ansible-playbook doc-files.yml --check
        
        PLAY [My first playbook] ****************************************************************************************************************************************************************************************
        
        TASK [Gathering Facts] ******************************************************************************************************************************************************************************************
        ok: [repo.cloudclub.edu]
        
        TASK [docs : Create a directory] ********************************************************************************************************************************************************************************
        changed: [repo.cloudclub.edu]
        
        TASK [docs : Create an empty file in the new directory] *********************************************************************************************************************************************************
        changed: [repo.cloudclub.edu]
        
        PLAY RECAP ******************************************************************************************************************************************************************************************************
        repo.cloudclub.edu         : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
        
        
    2. It looks like we fixed it.
    3. Add and commit the changes
    4. Run the playbook again without check mode
      ansible-playbook doc-files.yml
      
      PLAY [My first playbook] ****************************************************************************************************************************************************************************************
      
      TASK [Gathering Facts] ******************************************************************************************************************************************************************************************
      ok: [repo.cloudclub.edu]
      
      TASK [docs : Create a directory] ********************************************************************************************************************************************************************************
      fatal: [repo.cloudclub.edu]: FAILED! => {"changed": false, "msg": "There was an issue creating /opt/docs as requested: [Errno 13] Permission denied: b'/opt/docs'", "path": "/opt/docs"}
      
      PLAY RECAP ******************************************************************************************************************************************************************************************************
      repo.cloudclub.edu         : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
      
    5. Oops! Another error - See the task it failed on “TASK [docs | Create a directory ]” and the error itself -There was an issue creating /opt/docs as requested: [Errno 13] Permission denied: b'/opt/docs
    6. Update the main.yml file - add “become: true” to the bottom of the task like this. Be sure to “outdent” the line.
          group: nobody
          mode: '0400'
        become: true
      
    7. Run the playbook again
    8. Yet another error. This time it's asking about the sudo password. Why? Because we added the “become: true” line. We told it to use sudo, but didn't give it a password to use.
    9. Run the playbook again, but add these 2 options
      Options:
      ========
      -k = ask ssh password
      -K = ask sudo/become password
      
      New command:
      =============
      ansible-playbook doc-files.yml -kK
      
      ansible-playbook doc-files.yml -kK
      SSH password: 
      BECOME password[defaults to SSH password]: 
      
      PLAY [My first playbook] ****************************************************************************************************************************************************************************************
      
      TASK [Gathering Facts] ******************************************************************************************************************************************************************************************
      ok: [repo.cloudclub.edu]
      
      TASK [docs : Create a directory] ********************************************************************************************************************************************************************************
      changed: [repo.cloudclub.edu]
      
      TASK [docs : Create an empty file in the new directory] *********************************************************************************************************************************************************
      fatal: [repo.cloudclub.edu]: FAILED! => {"changed": false, "msg": "Error, could not touch target: [Errno 13] Permission denied: b'/opt/docs/git-notes.txt'", "path": "/opt/docs/git-notes.txt"}
      
      PLAY RECAP ******************************************************************************************************************************************************************************************************
      repo.cloudclub.edu         : ok=2    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
      
    10. We fixed the error for creating the directory, but we're failing to create the empty file. Why?
      1. Whom are we running the playbook as? YOU! – More specifically, your user account that you loged into the ansible server with.
      2. What is the ownership and permissions of the directory? – Owner: nobody – Group: nobody – Permissions: read-only for the owner and nothing for everyone elsae.
    11. Update the main.yml file to fix these things
    12. Run the playbook again - Success!!
      ansible-playbook doc-files.yml -kK
      SSH password: 
      BECOME password[defaults to SSH password]: 
      
      PLAY [My first playbook] ****************************************************************************************************************************************************************************************
      
      TASK [Gathering Facts] ******************************************************************************************************************************************************************************************
      ok: [repo.cloudclub.edu]
      
      TASK [docs : Create a directory] ********************************************************************************************************************************************************************************
      changed: [repo.cloudclub.edu]
      
      TASK [docs : Create an empty file in the new directory] *********************************************************************************************************************************************************
      changed: [repo.cloudclub.edu]
      
      PLAY RECAP ******************************************************************************************************************************************************************************************************
      repo.cloudclub.edu         : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
      
    13. What does the file ownership and permission look like now? – System default, since we didn't specify them in our task to create the file.
  8. Create a collection with roles ??

Build a web server

  1. Create a role ( prefix with your initials ) to install and start nginx web server
  2. Update the role to configure and restart the webserver
  3. Create web content ( About me content ) as index.html
    1. Example playbook
      - name: Setup web server
        hosts: web_servers
         
        roles:
          - name: miked_web
    2. Example main.yml from the role
      ---
      # tasks file for miked_web
      
      - name: Install xnginx web server
        ansible.builtin.dnf:
          name: nginx
        become: true
      
      - name: Configure nginx web server to start
        ansible.builtin.systemd_service:
          name: nginx
          enabled: true
          state: started
        become: true
      
      - name: Add new file for web content
        ansible.builtin.template:
          src: templates/pg2.html.j2
          dest: /usr/share/nginx/html/pg2.html
          owner: garfield
          group: garfield
          mode: 774
        become: true
    3. Example template:
      <html>
        <body bgcolor="lightblue">
          <h2>Welcome, {{ ansible_user_gecos }}, <font color="red"><b>from {{ ansible_host }}</b></font>!!</h2>
          <p>Let's have some more fun.   ;)</p>
        </body>
      </html>

Backup your web content and rebuild

  1. Backup your web content with the tar command
    1. Create a new role to backup your site content by creating a .tar.gz or .tgz file.
  2. Destroy your VM
  3. Build a new server
  4. Restore your content from backup with the tar command ( or untar if you prefer )