{{tag>projects cloud club computing virtualization machines VMs AWS Azure GCP}} [[cloud_club|About the Club]] ==== Cloud Club Topics - Infrastructure as Code ( IaC ) ==== ==== Questions ==== - What is Infrastructure as Code ( IaC )? - Why do we need it? We can do it manually. ==== Forms of IaC ==== - Configuration files designed for specific virtual platform - Examples - AWS CloudFormation - Custom scripts - Code tools designed for multiple platforms - Examples - Pulumi - Chef - Puppet - Ansible - Terraform ==== Plan your installation ==== - See "Plan your Installation" checklist from [[cloud_club_server_install|server installation]]a - Select your IaC tool - Use Git for virsion control!!! ==== Build the VM ==== - Create the base code needed to build the network, security, and VM ==== Install software and configure ==== - If capable, add code incrementally to configure the VM as required - If capable, add code incremetnally to install and configure software as required ==== Hands-on ==== - Build Ansible Server - Create Linux VM ( Manually ) - Install Ansible sudo dnf install ansible-core -y - 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 ==== - ansible.cfg file(s) - Generate your own ansible.cfg in your home directory ansible-config init --disabled -t all > ~/ansible.cfg - Review the ansible.cfg file and make changes as desired - setup Ansible static inventory - 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 - Test inventory with command ansible-inventory --graph @all: |--@ungrouped: |--@repo_servers: | |--repo.cloudclub.edu |--@web_servers: | |--repo.cloudclub.edu |--@dns_servers: | |--dns.cloudclub.edu - Setup Ansible dynamic inventorya - Setup ssh keys - Generate ssh key pair - **Note**: Keep pressing enter ( 3 times ) for the default values until you get your command prompt back ssh-keygen - 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. - 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 - Test the key by using ssh with the key ssh 192.168.1.11 - 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 ==== - Accessing and readding the Ansible documentation - There are 2 ways to access the documentation - Online - You can use your browser to navigate through [[https://docs.ansible.com/]] - **Easiest**: Google search for what you're tying to do - Example search: "ansible add user" and select the top result ( reference: [[https://www.google.com/search?q=ansible+add+user]] ) - ansible-doc command - To see a list of builtin modules ansible-doc -l ansible.builtin - To see the documentation on a particular module ( same one as above ) ansible-doc ansible.builtin.user - Understand the components - Modules - Playbooks - Tasks - Roles - collections - List module collections with command ansible-galaxy collection list - Collection installation ansible-galaxy collection install - Offline collection installation - 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. - 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. - 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. - 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 ==== - Prepare a location for your ansible codea - Create a "code" directory mkdir ~/code cd ~/code - As a best practice, create a local git repo git init -b main ansible-basic cd ansible-basic - Create a playbook with tasks - 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 - 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 - Use git command to add your file git add first-playbook.yml - Use git to create a "commit" object git commit -m 'Initial version' 1 file changed, 9 insertions(+) create mode 100644 first-playbook.yml - 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 }} - 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(-) - ( Optional ) If you want to see the changes made to your files, use the git log command git log - 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 - Createing roles - Create ( initialize ) a new git repository git init -b main ansible-roles cd ansible-roles mkdir roles cd roles - use the ansible-galaxy command to create the directory structure of a role ansible-galaxy init docs - Role docs was created successfully - Install the **tree** command to help visualize the directory structure sudo dnf install -y tree - 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 - Add **"hidden"** files to instruct git to keep these empty directories touch docs/files/.gitkeep touch docs/templates/.gitkeep - 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 - Change to the tasks directory cd docs/tasks - 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 - 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 - Change to the "root" of the repo cd ../.. - Create a playbook with the new role nano doc-files.yml - name: My first playbook hosts: repo_servers roles: - name: docs - Add and commit the new playbook - 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 - **Oops!** It looks like we found an error. Let's fix it. - "msg": "Unsupported parameters for (ansible.builtin.file) module: user. - 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 - It looks like we fixed it. - Add and commit the changes - 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 - **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 - 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 - Run the playbook again - 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. - 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 - We fixed the error for creating the directory, but we're failing to create the empty file. Why? - Whom are we running the playbook as? YOU! -- More specifically, your user account that you loged into the ansible server with. - What is the ownership and permissions of the directory? -- Owner: nobody -- Group: nobody -- Permissions: read-only for the owner and nothing for everyone elsae. - Update the main.yml file to fix these things - 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 - 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. - Create a collection with roles ?? ==== Build a web server ==== - Create a role ( prefix with your initials ) to install and start nginx web server - Update the role to configure and restart the webserver - Create web content ( About me content ) as index.html - Example playbook - name: Setup web server hosts: web_servers roles: - name: miked_web - 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 - Example template:

Welcome, {{ ansible_user_gecos }}, from {{ ansible_host }}!!

Let's have some more fun. ;)

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