Skip to content

Ansible

Setup

  • git clone diveintoansible github repo (make sure .env is there)
  • Now, docker-compose up
  • Move to localhost:1000
  • Connect to main ansible container
  • SSH password to all the containers is: password

How to verify whose remote system fingerprint is in ~/.ssh?

  • Go to ~/.ssh
  • cat known_hosts -> list of all the fingerprints ever accepted.
  • ssh-keygen -H -F ubuntu1 -> shows the fingerprint for the specified system.

Setup key pairs for authentication

  • It's repetitive to enter password everytime we ssh into the remote server. The better way is to generate key pairs and exchange public key with the remote server.

1) Generate key pair:

ssh-keygen
inside the admin server. 2) Copy it to child servers:
ssh-copy-id ansible@ubuntu1
3) Now, try ssh'ing into the remote servers, you'll login without password.

Automate!

for user in ansible root
    do 
        for os in ubuntu centos
            do 
                for instance in 1 2 3
                    do
                        ssh-copy-id -o StrictHostKeyChecking=no ${user}@${os}${instance}
                    done
            done
    done

4) Ping the child server with:

ansible -i,ubuntu1,ubuntu2,ubuntu3,centos1,centos2,centos3 all -m ping

Inside Ansible

Repo Setup

git clone https://github.com/spurin/diveintoansible

Check info about ansible and other configurations

ansible --version

Configuration Files

  • Define the Ansible behavior.
  • The priority of using configuration files by ansible is in the following order:
    • 1) ANSIBLE_CONFIG (env variable, with a filename target)
      • i.e. export ANSIBLE_CONFIG=/home/directory/example_ansible.cfg
      • Can remove the env variable using:
        • unset ANSIBLE_CONFIG
    • 2)./ansible.cfg (hidden file; in the current directory)
    • 3)~/.ansible.cfg (hidden file; in the users home directory)
    • 4)/etc/ansible/ansible.cfg

Inventory Files

They are plain text files that serve as a central source of information about the managed nodes (hosts). - Hosts - Groups

I) Example

Dir structure: - ansible.cfg - known_hosts

ansible.cfg

[defaults]
inventory=hosts

hosts

[all]
centos1

Try to do: ansible all -m ping

[!NOTE] To skip fingerprint verification, add following to ansible.cfg: host_key_checking=False

II) Groups

  • hosts
    [centos]
    centos1
    centos2
    
    [ubuntu]
    ubuntu1
    ubuntu2
    

Ping with ansible all -m ping or specific one with ansible centos/centos1 -m ping.

[!TIP] Can also use regex for ping i.e.

[centos]
centos[1:3]

[ubuntu]
ubuntu[1:3]

CONDENSED OUTPUT ansible all -m ping -o

III) Listing hosts - ansible centos --list-hosts - ansible all --list-hosts - ansible *1 --list-hosts (regex)

IV) Different User & Port We can include a different user as well as the ansible connection port in our hosts file.

[centos]
centos1 ansible_user=root ansible_port=22
centos2 ansible_user=root

[ubuntu]
ubuntu1 ansible_become=true ansible_become_pass=password

  • The port can also be written as:
    [centos]
    centos1:22
    

COMMAND To check about the user in the child servers, do: ansible all -a 'id' -o

V) Local connection For the host server, we can also mention it in the hosts.

[control]
ubuntu-c ansible_connection=local

VI) :vars (removing redundant args) We can remove repetitive arguments using :vars.

[centos]
centos[1:3]

[centos:vars]
ansible_user=root

[ubuntu]
ubuntu[1:3]

[ubuntu:vars]
ansible_become=true 
ansible_become_pass=password

If we want to make a var global but with less precedence than in-line defined var, do:

[all:vars]
ansible_port=2212

VII) :children (grouping) We can also group groups and ping with the group name.

[linux:children]
centos
ubuntu

VIII) Corresponding yaml file

---
control:
  hosts:
    ubuntu-c:
      ansible_connection: local
centos:
  hosts:
    centos1:
      ansible_port: 2222
    centos2:
    centos3:
  vars:
    ansible_user: root
ubuntu:
  hosts:
    ubuntu1:
    ubuntu2:
    ubuntu3:
  vars:
    ansible_become: true
    ansible_become_pass: password
linux:
  children:
    centos:
    ubuntu:
...

[!NOTE] Ansible also supports json. The specific inventory file can be specified by: ansible all -i hosts.json -o -m ping.

IX) Passing env through cmd

ansible linux -m ping -e 'ansible_port=22' -o

Modules

Used for gathering facts when executing playbooks.

setup module

  • for getting info about the remote server ansible centos1 -m setup or ansible centos1 -m setup | more

file module

  • sets attributes of files, dirs or symlinks and their targets.

ansible all -m file -a 'path=/placeholder.txt state=touch'

copy module

ansible all -m copy -a 'src=/home/X.txt dest=/home/X.txt'

command module

  • takes command name followed by a list of space-delimited arguments.
  • the command is not processed through shell, so variables like $HOME will not work.

ansible all -a 'whoami' -o

YAML

---

ex1: hi
ex2: yo
ex3: >
    this is a string
    that goes over
    multiple lines using >
...

Ansible Playbooks

  • Contains a list of plays.
---

- # minus indicates a YAML list item. Each list represents a play.
    Hosts:
        - where our play will run and the options it will run with

    Vars:
        - variables that will apply to the hosts

    Tasks:
        - list of tasks to be executed

    Handlers:
        - handlers to be notified

    Roles:
        - list of roles to be imported into the play
...

Example 1 (vars)

---
-
  hosts: centos

  vars:
    message: "Oh my my by One Republic."

  tasks:
    - name: Message
      copy:
        content: "{{ message }}"
        dest: /home/message

...
  • Can also pass env var using terminal like:
    • ansible-playbook example.yaml -e 'message="hello world"'

Example 2 (handlers)

  • handlers are the tasks that run only when notified.
---
-
  hosts: centos

  vars:
    message: "Run by One Republic."

  tasks:
    - name: Message
      copy:
        content: "{{ message }}"
        dest: /home/message
      notify: Message is changed # same name in handlers' name

  handlers:
    - name: Message is changed
      debug:
        msg: The message has been changed now, it is now "{{ message }}"
...

[!INFO] For extra information about a remote server, do: ansible all -i ubuntu1, -m setup | more

Example 3 (conditions)

---
-

  hosts: linux

  vars:
    motd_centos: "Welcome to CentOS Linux - Ansible Rocks\n"
    motd_ubuntu: "Welcome to Ubuntu Linux - Ansible Rocks\n"

  tasks:
    - name: Configure a MOTD (message of the day)
      copy:
        content: "{{ motd_centos }}"
        dest: /etc/motd
      notify: MOTD changed
      when: ansible_distribution == "CentOS" # the variable is in setup module

    - name: Configure a MOTD (message of the day)
      copy:
        content: "{{ motd_ubuntu }}"
        dest: /etc/motd
      notify: MOTD changed
      when: ansible_distribution == "Ubuntu"

  handlers:
    - name: MOTD changed
      debug:
        msg: The MOTD was changed
...

[!NOTE] Variables need not be under quotes but under only specific syntax-error prone scenarios.

Example Exercise

  • We have two os: CentOS and Ubuntu.
  • We create two files: TEXT and message.
  • Then, we delete those two.

create.yaml

---
-
  hosts: linux

  vars:
    file1: "TEXT"
    file2: "message"
    centos: "CentOS"
    ubuntu: "Ubuntu"

  tasks:
    - name: Create file on "{{ centos }}"
      file:
        path: /home/{{ item }}
        state: touch
        mode: u=rw,g=r,o=r
      when: ansible_distribution == centos
      with_items:
        - "{{ file1 }}"
        - "{{ file2 }}"
      notify:
        - Files created in "{{ centos }}"


    - name: Create file in "{{ ubuntu }}"
      file:
        path: "/home/{{ file1 }}"
        state: touch
        mode: u=rw,g=r,o=r
      when: ansible_distribution == ubuntu
      notify:
        - File created in "{{ ubuntu }}"

  handlers:
    - name: Files created in "{{ centos }}"
      debug:
        msg: "{{ file1 }} and {{ file2 }} created"


    - name: File created in "{{ ubuntu }}"
      debug:
        msg: "{{ file1 }} created"
...

delete.yaml

---
-
  hosts: linux

  vars:
    file1: "TEXT"
    file2: "message"
    centos: "CentOS"
    ubuntu: "Ubuntu"

  tasks:
    - name: Delete "{{ file1 }}" and "{{ file2 }}" on "{{ centos }}"
      file:
        path: "/home/{{ item }}"
        state: absent
      with_items:
        - "{{ file1 }}"
        - "{{ file2 }}"
      when: ansible_distribution == centos
      notify: centos file delete


    - name: Delete "{{ file1 }}" on "{{ ubuntu }}"
      file:
        path: "/home/{{ file1 }}"
        state: absent
      when: ansible_distribution == ubuntu
      notify: ubuntu file delete


  handlers:
    - name: centos file delete
      debug:
        msg:
          - "{{ file1 }} deleted in centos"
          - "{{ file2 }} deleted in centos"


    - name: ubuntu file delete
      debug:
        msg: "{{ file1 }} deleted in ubuntu"

...

ansible.cfg

[defaults]
inventory = hosts
host_key_checking = False

hosts

[control]
ubuntu-c ansible_connection=local

[centos]
centos[1:3]

[centos:vars]
ansible_user=root

[ubuntu]
ubuntu[1:3]

[ubuntu:vars]
ansible_become=true
ansible_become_pass=password

[linux:children]
centos
ubuntu

[!NOTE] We can also specify an external yaml file in the vars section: vars: external_vars.yaml. Then we can reference those vars just by their name inside external_vars.yaml.

Example 4 (user input)

---
  hosts: centos1
  gather_facts: False

  vars_prompt:
    - name: yourname
      private: False # setting to True doesn't show the user input

  tasks:
    - name: Test vars_prompt
      debug:
        msg: "{{ yourname }}"

...

Hostvars & Groupvars

Hostvars host_vars/hostname (i.e. host_vars/ubuntu-c) - This stores vars for the hosts: - centos1 - ubuntu1 - ubuntu-c

  • This dir has a vars file named corresponding to the host.

centos1

---
ansible_port: 2222
...

Groupvars group_vars/group (i.e. group_vars/ubuntu) - Similarly, these for the groups. In our case: - centos - ubuntu

Playbook Facts

  • To get a fact using regex: ansible centos1 -m setup -a 'filter=ansible_mem*'

  • To get a variable from ansible_facts, no need to write ansible_facts.var_name as ansible_facts is available to all during runtime.

  • We can create custom facts in any language, the only thing is that it should return either a json or ini structure. Accessible under ansible_local i.e. ansible centos1 -m setup -a 'filter=ansible_local'.

  • By default, expects to use /etc/ansible/facts.d.

[!INFO] To refresh the facts do:

- name: Refresh Facts
 setup: 
Now, the default facts.d directory requires root access. To specify or refresh using custom facts directory, do:
- name: Refresh Facts
  setup:
     fact_path: /home/ansible/facts.d

[!TIP] We can also use jinja2 templating inside msg under debug

Syntax Check - We can verify the syntax of playbook using --syntax-check directive. ansible-playbook playbook.yaml --syntax-check

Step Run - We can make the playbook runs user dependent by --step directive. Asks for each step to proceed or not.

High verbosity - We can increase the verbose level using -vvvv arg. ansible-playbook -vvvv playbook.yaml.

Pausing - We can pause the playbook for specified seconds:

tasks:
    - name: Pause the playbook
      pause:
         seconds: 10

  • We can also have a prompt:
    tasks:
        - name: Pausing
          pause:
              prompt: Check for the webserver
    

Waiting

  • We can use wait_for module for waiting on some condition:
    tasks:
        - name: Wait for the webserver to be running on port 80
          wait_for:
              port: 80
    

Playbook Register

  • captures the task output.
---
-
  hosts: linux

  tasks:
    - name: Exploring register
      command: ls /home/
      register: list_home_dir
      ignore_errors: true # ignores this step if error occurs here

    - name: List home dir
      debug:
        var: list_home_dir
...

[!INFO] Can also do list_home_dir.stdout in var for just output.

Playbook Loops

  • There are many ways to implement loops.

item loop - item keyword is used for the value of each loop iteration.

    copy:
        content: OS is {{ item }}
    with_items: ["centos", "ubuntu"]
  • Can also pass contents as list:
    copy:
        content: OS is {{ item }}
    with_items:
        - CentOS
        - Ubuntu
  • There is also with_file.

Example 5 (running a script)

---
-
  hosts: linux

  tasks:
    - name: Run a script until we hit 10
      script: random.sh
      register: result
      retries: 100
      until: result.stdout.find("10") != -1
      delay: 1

...

Blocks

  • Various one's in single block.
---
-
  hosts: linux

  tasks:
    - name: A block of modules being executed
      block:
        - name: Example 1 CentOS only
          debug:
            msg: Example 1 CentOS only
          when: ansible_distribution == 'CentOS'

        - name: Example 2 Ubuntu only
          debug:
            msg: Example 2 Ubuntu only
          when: ansible_distribution == 'Ubuntu'

        - name: Example 3 with items
          debug:
            msg: "Example 3 with items - {{ item }}"
          with_items: ['x', 'y', 'z'] 

...

Ansible Vault

  • For encrypting vars, files.

To encrypt

ansible-vault encrypt_string --ask-vault-pass --name 'ansible_become_pass' 'password'

To use

ansible --ask-vault-pass ubuntu -m ping -o

Encrypt vars.yaml

ansible-vault encrypt external_vars.yaml

Decrypt

ansible-vault decrypt external_vars.yaml

Ansible Playbook structure

include_tasks

---
-

  hosts: all

  tasks:

     - name: Play 1 - Task 1
       debug: 
         msg: Play 1 - Task 1

     - include_tasks: play1_task2.yaml # this file only contains name

...

play1_task2.yaml

---

- name: Play 1 - Task 2
  debug: 
    msg: Play 1 - Task 2

...

Playbook tags

  • Can be used to run a specific task by specifying tag name.
---
-
  hosts: linux

  vars_files:
    - vars/logos.yaml

  tasks:
    - name: Install EPEL
      yum:
        name: epel-release
        update_cache: yes
        state: latest
      when: ansible_distribution == 'CentOS'
      tags:
        - install-epel

    - name: Install Nginx
      package:
        name: nginx
        state: latest
      tags:
        - install-nginx

    - name: Restart nginx
      service:
        name: nginx
        state: restarted
      notify: Check HTTP Service
      tags:
        - restart-nginx

    - name: Template index.html-easter_egg.j2 to index.html on target
      template:
        src: index.html-easter_egg.j2
        dest: "{{ nginx_root_location }}/index.html"
        mode: 0644
      tags:
        - deploy-app

    - name: Install unzip
      package:
        name: unzip
        state: latest

    - name: Unarchive playbook stacker game
      unarchive:
        src: playbook_stacker.zip
        dest: "{{ nginx_root_location }}"
        mode: 0755
      tags:
        - deploy-app

  handlers:
    - name: Check HTTP Service
      uri:
        url: http://{{ ansible_default_ipv4.address }}
        status_code: 200 

...

Run by: ansible-playbook playbook.yaml --tags "install-nginx"

Ansible Roles

  • Better way of doing ansibling.
  • Initiate a role using: ansible-galaxy init <role-name>.
  • To use role inside a playbook, write the role name under Roles.
---
-

  hosts: all

  roles:
      - nginx # role

  tasks:

     - name: Play 1 - Task 1
       debug: 
         msg: Play 1 - Task 1

     - include_tasks: play1_task2.yaml # this file only contains name

...
  • meta/main.yml
    • This contains meta info.
    • We can also specify a dependency of the current role on another role by specifying the other role under dependencies key.

become & become_user

  • become is the sudo equivalent in ansible.
  • become_user is the user to become; not the one to login as.

  • To enter the sudo password interactively, use -K arg:

ansible-playbook x.yaml -K

[!IMPORTANT] For use with docker, see Ansible with Docker.

Example 5 (Roles + Docker)

1) Create a role using ansible-galaxy init docker. 2) Under tasks, create three files: (main.yml will be the default one) - dockerImageDelete.yml - dockerContainer.yml - dockerPull.yml

dockerImageDelete.yml

---
- name: Docker Image Delete
  docker_image:
    name: centos
    state: absent
  register: image_delete

- name: Debug message
  debug:
    var: image_delete["actions"]
...

dockerContainer.yml

---
- name: Create centos container
  docker_container:
    name: containercentos
    image: centos
    ports:
      - 4287:80
    state: started
    container_default_behavior: no_defaults
  register: container_start

- name: Debug message
  debug:
    var: container_start
...

dockerPull.yml

---
- name: Pulling the docker image
  docker_image:
    name: centos
    source: pull 
  register: image_pull

- name: Debug message
  debug:
    var: image_pull["actions"]
...

3) The above can be reference in the main.yml file as:

---
- name: Pull docker image on the remote servers
  import_tasks: dockerPull.yml
  when: docker_input == "1"

- name: Delete docker image on the remote servers
  import_tasks: dockerImageDelete.yml
  when: docker_input == "2"

- name: Start the docker container
  import_tasks: dockerContainer.yml
  when: docker_input == "3"

...

4) Alongside the role folder docker, create a playbook and include the following:

---
- 
  hosts: localhost
  connection: local 
  become: yes
  become_user: root

  vars_prompt:
    - name: docker_input
      prompt: "1) pull\n2) delete\n3) start"
      private: False

  roles:
    - docker
...

[!NOTE] We can also build using Dockerfile. The concept remains the same. Just keep in mind that the project directory location should be relative to the directory from where ansible-playbook is being run.

Example 6 (AWS EC2 + Ansible)

1) Make sure that the credentials are configured inside ~/.aws/credentials. 2) Better to use IAM Identity Centre now-a-days. Just create the user there, and configure in the terminal using aws configure sso. 3) Now, we can either have a static inventory or dyanmic inventory. For dynamic one, create a new inventory file: aws_ec2.yaml and add it to inventory directory. 4) Create hosts under inventory and add it as a default inventory to ansible.cfg.

hosts

[localhost_group]
localhost

ansible.cfg

[defaults]
inventory = inventory/hosts
host_key_checking = False
enable_plugins = aws_ec2

aws_ec2.yaml

---
plugin: amazon.aws.aws_ec2
aws_profile: test-ec2

hostnames:
  - ip-address

regions: 
  - us-east-1

groups:
  ansible: "'urlShortenerServer' in tags.Ansible"
...

5) Run with: ansible-inventory -i aws_ec2.yaml --list/--graph.

keyed_groups vs groups

  • groups is another way when we know info before hand. Suppose we know the tags of ec2 instances, then we can create our own group.
---
plugin: amazon.aws.aws_ec2
aws_profile: test-ec2

hostnames:
    - ip-address

regions:
    - us-east-1

groups:
    redhat: "'redhat' in tags.OS"
    ubuntu: "'ubuntu' in tags.OS"
...

6) After configuring sso using aws configure sso, for using the profile just put the configured name in the aws_profile section in ansible.

[!NOTE] It may happen that the credentials expire after the specified time. Just do aws configure sso-session and then just aws sso login --sso-session <session-name>.

[!IMPORTANT] It may also happen that after doing the above, the ansible is still saying credentials expired. It may be that the root user on the system may have the old credentials. Just run the configure commands in root.

7) Also create a key-pair in aws for connecting to the ec2 instance.

8) Do chmod 600 key.pem for securing it otherwise there'll be an error while ssh'in.

9) Now when we run any playbook using aws_ec2.yaml as inventory, an in-memory group will be formed for which we can beforehand specify some group vars under group_vars directory. The in-memory group will be ansible, so:

ansible

---
ansible_ssh_private_key_file: ansible.pem
ansible_user: ubuntu
ansible_become: true
#ansible_ssh_common_args: '-o StrictHostKeyChecking=no' # not in prod
# docker_github_token: !vault |
#           $ANSIBLE_VAULT;1.1;AES256
#           61333462616136663461316334366130633061393365383763633936616233646334343833666437
#           6465386231656632353038343631333464313533623133330a326230666663366236616337626238
#           63376531323461303935616133653936306630383463666265386236313739633165396634323731
#           3164643166613230630a363361363532323139353837353532663464386631383731353561316165
#           65303061363465653963303662393432633563363736333863626365396532333236336634313031
#           3365373731373437663830623434363332633532636662326231
...

[!NOTE] Make sure that the ansible.pem is in the same directory.

localhost_group.yaml

---
become: true
become_user: root
connection: local
#github_token: !vault |
#          $ANSIBLE_VAULT;1.1;AES256
#          33366266353635346265376534613339663633386131666337363739633831353962633639383431
#          6333316538313163393337663462613032363766666464660a303838613531326164316432626366
#          36326132633365623734303566643838616261303436663264303731633231363737323438336336
#          3230386638623763350a346664343861396533363836663135633735316330383466343365386564
#          38646235613533656131343862313763653339313730313737616530373937323566326334353932
#          3866663537373337393535313432303061326236616432653633
...

10) We can also have a localhost.yaml under host_vars:

localhost.yaml

---
ansible_become: true
ansible_connection: local
#github_token: !vault |
#         $ANSIBLE_VAULT;1.1;AES256
#          33366266353635346265376534613339663633386131666337363739633831353962633639383431
#          6333316538313163393337663462613032363766666464660a303838613531326164316432626366
#          36326132633365623734303566643838616261303436663264303731633231363737323438336336
#          3230386638623763350a346664343861396533363836663135633735316330383466343365386564
#          38646235613533656131343862313763653339313730313737616530373937323566326334353932
#          3866663537373337393535313432303061326236616432653633
...

11) Now, following is the playbook for creating ec2.

1_create_ec2.yaml

---
-
  name: Create EC2 instances
  hosts: localhost
  become: true
  connection: local
  gather_facts: false

  vars_prompt:
    - name: aws_profile
      prompt: "Enter aws profile: "
      private: false

    - name: region
      prompt: "Enter region: "
      private: false
      default: us-east-1

    - name: sec_group
      prompt: "Enter security group id: "
      private: false
      # default: "sg-012wxxxxx"

    - name: sg_desc
      prompt: "Desc about Security Group: "
      private: false
      default: Custom Security Group

    - name: image
      prompt: "Enter ami-id: "
      private: false
      default: ami-080e1f13689e07408 # ubuntu

    - name: instance_type
      prompt: "Enter instance type: "
      private: false
      default: t2.micro

    - name: key_name
      prompt: "Enter key name: "
      private: false
      default: ansible

    - name: count
      prompt: "Enter count of instances: "
      private: false
      default: 1


  tasks:
    - name: Create Security Group
      amazon.aws.ec2_security_group:
        name: "{{ sec_group }}"
        description: "{{ sg_desc }}"
        region: "{{ region }}"
        aws_profile: "{{ aws_profile }}"
        rules:
          - proto: tcp
            ports:
              - 22
            cidr_ip: 0.0.0.0/0
            rule_desc: allow_ssh

          - proto: tcp
            ports:
              - 3000
            cidr_ip: 0.0.0.0/0
            rule_desc: frontend-exposed

    - name: Create an instance
      amazon.aws.ec2_instance:
        key_name: "{{ key_name }}"
        aws_profile: "{{ aws_profile }}"
        instance_type: "{{ instance_type }}"
        region: "{{ region }}"
        image_id: "{{ image }}"
        security_group: "{{ sec_group }}"
        network:
          assign_public_ip: true
        wait: true
        exact_count: "{{ count }}"
        tags:
          Ansible: urlShortenerServer

...

12) Now, run the following using -i inventory/aws_ec2.yaml for configuring ec2 instances.

2_configure_ec2.yaml

---

- name: Configure EC2 instance
  hosts: ansible
  connection: ssh
  become: true

  tasks:
    - name: Update the machine
      command: sudo apt-get clean && update -y

    - name: Installing essentials
      command: apt-get install apt-transport-https ca-certificates curl software-properties-common -y

    - name: Installing Curl
      command: apt-get install curl -y

    - name: Pre-steps
      command: "{{ item }}"
      with_items:
        - curl -fsSL https://get.docker.com -o get-docker.sh
        - sudo sh get-docker.sh

    - name: Adding user to group
      command: sudo usermod -aG docker ${USER}

    - name: Restarting docker service
      command: sudo service docker restart 

    - name: Updating the machine 2
      command: sudo apt-get update

    - name: Installing Python Pip
      command: sudo apt-get install python-pip -y

    - name: Installing Docker compose
      command: sudo apt-get install docker-compose-plugin
...

13) Now a role called docker is created using ansible-galaxy init docker. Variables are defined under vars and are prefixed with the role name docker_:

---
# vars file for docker
docker_script_dest: ./
docker_github_token: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          61333462616136663461316334366130633061393365383763633936616233646334343833666437
          6465386231656632353038343631333464313533623133330a326230666663366236616337626238
          63376531323461303935616133653936306630383463666265386236313739633165396634323731
          3164643166613230630a363361363532323139353837353532663464386631383731353561316165
          65303061363465653963303662393432633563363736333863626365396532333236336634313031
          3365373731373437663830623434363332633532636662326231
...

[!NOTE] The docker_github_token is encrypted using ansible-vault encrypt_string <string>.

14) There are 3 tasks under tasks directory:

main.yaml

---
- name: Copying the required files
  ansible.builtin.import_tasks: setup_files.yaml

- name: Running Docker Compose
  ansible.builtin.import_tasks: docker_compose.yaml

- name: Copying env files
  ansible.builtin.import_tasks: copy_env.yaml
...

setup_files.yaml

---
- name: Getting the required files
  block:
    - name: Copying the script
      ansible.builtin.copy:
        src: get_files.sh # auto taken from files directory
        dest: "{{ docker_script_dest }}"
        mode: '0644'
      register: copy_output
      when: docker_input == "1"

    - name: Running the script
      ansible.builtin.script: "{{ docker_script_dest }}get_files.sh {{ docker_github_token }}"
      register: script_result
      when: docker_input == "1"

    - name: Install pip
      command: sudo apt-get install python3-pip -y
      when: docker_input == "1"

    - name: Install docker python
      command: pip install docker==6.1.3 # only this works
      when: docker_input == "1"

    - name: Install docker-compose python package
      ansible.builtin.pip:
        name: docker-compose
      when: docker_input == "1"

- name: Debug Output
  ansible.builtin.debug:
    var:
      - copy_output
      - script_result
...

docker_compose.yaml

---
- name: Docker Compose for Frontend + Backend
  community.docker.docker_compose:
    # project_name: frontend_+_backend
    project_src: url-shortener/url-shortener/frontend-servers/
  when: docker_input == "2"
  register: output

- name: Docker compose remove containers
  community.docker.docker_compose:
    project_src: url-shortener/url-shortener/frontend-servers/
    state: absent
  when: docker_input == "3"

- name: Docker Compose Status
  ansible.builtin.debug:
    var: output # its something else here
...

  • Have the respective folders under files for .env files as specified in the below code:

copy_env.yaml

---
- name: Copying .env's
  block:
    - name: Copying frontend .env
      ansible.builtin.copy:
        src: frontend/.env
        dest: url-shortener/url-shortener/frontend/
        mode: '0644'
      register: frontend_copy
      when: docker_input == "4"

    - name: Copying go server .env
      ansible.builtin.copy:
        src: go/.env
        dest: url-shortener/url-shortener/servers/go/
        mode: '0644'
      register: go_copy
      when: docker_input == "4"

    - name: Copying express js .env
      ansible.builtin.copy:
        src: express_js/.env
        dest: url-shortener/url-shortener/servers/express-js
        mode: '0644'
      register: express_copy
      when: docker_input == "4"

- name: Debug Output
  ansible.builtin.debug:
    var:
      - frontend_copy
      - go_copy
      - express_copy
...

15) Then we run the above using the following playbook:

3_docker_book.yaml

---
- 
  name: Setting up and running docker compose
  hosts: ansible
  #connection: local 
  become: yes
  #become_user: root

  vars_prompt:
    - name: docker_input
      prompt: "1) Setup Files\n2) Compose up\n3) Compose rm\n4) Copy .env files"
      private: False

  roles:
    - docker
...

16) > [!NOTE] Make sure that there are 3 dirs under files in docker named frontend, go and express_js containing .env files, if they need to be copied to the remote servers.

17) Now, to start everything up, run the following commands: - ansible-playbook 1_create_ec2.yaml -K - ansible-playbook -i inventory/aws_ec2.yaml 2_configure_ec2.yaml - ansible-playbook -i inventory/aws_ec2.yaml 3_docker_book.yaml

[!NOTE] Make sure that .env files are copied or set up there on the remote servers for my project url shortener otherwise docker-compose will not work.

[!IMPORTANT] In order to avoid ansible-lint check on a specific line, copy the error code like yaml[something-something] and put it in front of the line as: # noqa error-here.