Skip to main content

Ansible Playground: Simple Playbook & Role Structure, Collection of Common Used Ansible Tasks

1474 words·
Ansible Debian
Table of Contents

Ansible Installation (Debian)
#

Official Documentation: https://docs.ansible.com/ansible/latest/installation_guide/installation_distros.html#installing-ansible-on-debian

# Install requirements
sudo apt install gnupg -y
# Ansible installation script for Debian 12

UBUNTU_CODENAME=jammy
wget -O- "https://keyserver.ubuntu.com/pks/lookup?fingerprint=on&op=get&search=0x6125E2A8C77F2818FB7BD15B93C4A3FD7BB9C367" | sudo gpg --dearmour -o /usr/share/keyrings/ansible-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/ansible-archive-keyring.gpg] http://ppa.launchpad.net/ansible/ansible/ubuntu $UBUNTU_CODENAME main" | sudo tee /etc/apt/sources.list.d/ansible.list
sudo apt update && sudo apt install ansible -y
# Verify installation / list version
ansible --version



Ansible Playground
#

File & Folder Structure
#

# The file and folder structure looks like this:
ansible-playground
├── ansible.cfg
├── inventory
├── playbooks
│   └── example_playbook.yml
└── roles
    └── example_role
        └── tasks
            └── main.yml
# Create the folder structure
mkdir -p \
  ansible-playground/playbooks \
  ansible-playground/roles/example_role/tasks \
  && cd ansible-playground

Ansible Configuration
#

  • ansible.cfg
[defaults]
roles_path = ./roles

Inventory File
#

  • inventory
[example_hosts]
192.168.30.161 ansible_user=debian

Example Playbook
#

  • playbooks/example_playbook.yml
---
- name: Example Playbook
  hosts: example_hosts
  become: true
  gather_facts: true

  roles:
    - example_role

Example Role
#

  • roles/example_role/tasks/main.yml

  • Add the tasks to the main.yml file.

  • All tasks were tested on Debian 12 server.


Task: Create User
#

  • This task creates a new user with home directory, adds the user to the sudo and docker group, allowes the user to uses sudo without password and adds a public SSH key to the “~/.ssh/authorized_keys” file of the user.
---
- name: Ensure user exists
  ansible.builtin.user:
    name: example-user
    shell: /bin/bash
    home: /home/example-user
    groups: sudo, docker
    append: yes  # Add user to groups, keep user in any other groups the user might already be part of
    expires: -1  # No expiration
    state: present
    create_home: yes

- name: Add public SSH key to authorized_keys
  ansible.posix.authorized_key:
    user: example-user
    state: present
    key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC56iSOcPyN6CGW4RAzcv7w1YxO3wJMY9mC1lEAT5W6SgnjiUjQFstQyNCJJcrPNHYF1sjMfbN8lThY/b007fwVh9XQX7tnVZ6rQ5CU1VrmgMzyMPkcLPoIaVTWSggDRu/PUtrBtcURje+HZQno9oRYyA768l6acG0PSHwftRyyKcJ4TE/4Iv6tmQPXi5HKt4aIRy1MYqtMR3FcKd+enCx0I9TM1+eSL/vxfYS+e2MZJwnz/Zl8HJxBg/XRfti7FIydy2CvPszQkIvFrMpP8BQhvNHtIWDzooabLnxuAv7//xlWwwC6GXBeQ+UxWmn6VUexpN6Hr0WUqI7ErmODhPLqzXtOPHhfq8MC2SAtZxx9+b+yjpjT0rlDnm1Fv9MWi0kEiYiYnJxsJZZiEKAxwUSb0pz/uneU1Cj66A6xoFNCwG3H6bW7gySGtKWtjrjM2EmwzRAvA7h4xZufLns0tPx4MhR9AaounWpdj1vaJhJiHAACG2scmJMjUYhMTuY9lIUUW9viak/z8VlBsKCgve+oSRZrXg84kleHR8eREeAtDwQdPEtxXgNID2B9v2XwDYVo3Lgl41tSiloA7TU/49GG6SvCsyVfW1r2AdlI/QBoncoP3OxixxfDNauVg7omJ0W8Fh1MSb7h+G5Hq8mV2l8L1s75yPTVnmOrMt3jfIjlEQ== debian@debansible01"

- name: Allow user to use sudo without a password
  ansible.builtin.copy:
    dest: /etc/sudoers.d/example-user
    content: "example-user ALL=(ALL) NOPASSWD:ALL\n"
    mode: '0440'
    owner: root
    group: root

Task: Delete User
#

Delete the user but keep the users home directory:

---
- name: Remove user account and home directory
  ansible.builtin.user:
    name: example-user
    state: absent

Delete the user and the users home directory:

---
- name: Remove user account and home directory
  ansible.builtin.user:
    name: example-user
    state: absent
    remove: yes # Delete home directory

Task: Create Group
#

---
- name: Ensure group exists
  ansible.builtin.group:
    name: example-group
    gid: 1010
    state: present

Task: Create Directory
#

---
- name: Ensure directory exists
  ansible.builtin.file:
    path: /opt/example-directory
    state: directory
    owner: debian
    group: debian
    recurse: yes
  become: true

Task: Create File with Content
#

---
- name: Ensure file exists
  ansible.builtin.copy:
    dest: /tmp/example-file
    content: |
      some text
      more text      
    owner: debian
    group: debian

Task: Create Symlink #

- name: Create symlink
  ansible.builtin.file:
    src: /tmp/some-script
    dest: /usr/local/bin/some-script
    state: link
  become: true

Task: Copy Static File
#

# The file and folder structure looks like this:
ansible-playground
├── ansible.cfg
├── inventory
├── playbooks
│   └── example_playbook.yml
└── roles
    └── example_role
        ├── files
        │   └── example-file.txt
        └── tasks
            └── main.yml
# Create the folder structure
mkdir -p roles/example_role/files
# Create static file that will be copied
vi roles/example_role/files/example-file.txt
  • roles/example_role/tasks/main.yml
- name: configure gitlab docker dnd runner
  copy:
    src: example-file.txt
    dest: /tmp/destination-file.txt
    owner: debian
    group: debian
    mode: "600"



Task: Copy File from Template
#

# The file and folder structure looks like this:
ansible-playground
├── ansible.cfg
├── inventory
├── playbooks
│   └── example_playbook.yml
└── roles
    └── example_role
        ├── tasks
        │   └── main.yml
        └── templates
            └── config.toml.j2 # template file
# Create the folder structure
mkdir -p roles/example_role/templates

Create the template file:

  • roles/example_role/templates/config.toml.j2
concurrent = 5
check_interval = 0
shutdown_timeout = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "example-runner"
  url = {{ gitlab_runner_url }}
  id = 1
  token = {{ gitlab_runner_token }}
  executor = "docker"

Define the varibales in the playbook:

  • playbooks/example_playbook.yml
---
- name: Example Playbook
  hosts: example_hosts
  become: true
  gather_facts: true
  # Define the variables that are used by the template
  vars:
    gitlab_runner_url: "https://gitlab.example.com/"
    gitlab_runner_token: "SOME-TOKEN"

  roles:
    - example_role

Create the task thak copies the file from the template:

  • roles/example_role/tasks/main.yml
---
- name: Ensure GitLab Runner config directory exists
  file:
    path: /etc/gitlab-runner
    state: directory
    owner: root
    group: root
    mode: '0755'

- name: Deploy GitLab Runner config from template
  template:
    src: config.toml.j2
    dest: /etc/gitlab-runner/config.toml
    owner: root
    group: root
    mode: '0644'



Task: Clone Git Repository
#

The following task clones a git repository from GitHub and pulls the latest version, every time the playbook triggers the task:

---
- name: Ensure git repository exists
  ansible.builtin.git:
    repo: https://github.com/octocat/Hello-World.git
    dest: /tmp/github-repository # The content of the git repo gets cloned into this directory
    version: master
    update: yes
    clone: yes
    force: yes
  become_user: debian

Task: Install Packages
#

---
- name: Ensure packages are installed
  ansible.builtin.apt:
    name: "{{ item }}"
    state: latest
    update_cache: true
    cache_valid_time: 0
  loop:
    - sudo
    - git
    - curl
    - neovim

Task: Uninstall Package
#

  • The task is idempotent, if the package is already uninstalled, nothing happens.

Uninstall a package:

---
- name: Uninstall package
  ansible.builtin.apt:
    name: ufw
    state: absent
    purge: yes # Remove the package and its configuration files

Uninstall several packages:

- name: Uninstall packages
  ansible.builtin.apt:
    name: "{{ item }}"
    state: absent
    purge: yes # Remove the package and its configuration files
  loop:
    - curl
    - ufw
    - git

Task: Install & Configure UFW Firewall
#

  • Using state: reset clears all existing firewall rules before applying new ones. This ensures that the firewall configuration matches exactly what is defined in the playbook.

  • to remove a port, simply delete it from the list and rerun the playbook.

- name: Ensure UFW firewall is installed
  ansible.builtin.apt:
    name: ufw
    state: latest  # If already installed, update to the latest version
    update_cache: yes  # Update the APT cache

- name: Reset UFW firewall rules
  community.general.ufw:
    state: reset

- name: Add UFW firewall rules
  community.general.ufw:
    rule: allow
    port: "{{ item }}"
    proto: tcp
  loop:
    - 22
    - 80
    - 443

- name: Ensure UFW is enabled
  community.general.ufw:
    state: enabled
    policy: deny  # Everything is blocked by default

Task: Install Docker & Docker Compose
#

---
# Install required dependencies for Docker
- name: Install dependencies
  ansible.builtin.apt:
    name: "{{ item }}"
    state: latest
    update_cache: true
    cache_valid_time: 0
  loop:
    - ca-certificates
    - curl
    - git
    - gnupg
    - lsb-release


# Install Docker & Docker Compose
- name: Ensure /etc/apt/keyrings directory exists
  ansible.builtin.file:
    path: /etc/apt/keyrings
    state: directory
    mode: '0755'

- name: Add Docker GPG key
  ansible.builtin.get_url:
    url: https://download.docker.com/linux/debian/gpg
    dest: /etc/apt/keyrings/docker.asc
    mode: '0644'

- name: Add Docker APT repository
  ansible.builtin.apt_repository:
    repo: "deb [arch={{ (ansible_architecture == 'aarch64') | ternary('arm64', (ansible_architecture == 'x86_64') | ternary('amd64', ansible_architecture)) }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable"
    state: present
    filename: docker

- name: Install docker
  ansible.builtin.apt:
    name: "{{ item }}"
    state: present
    update_cache: true
    cache_valid_time: 0
  loop:
    - docker-ce
    - docker-ce-cli
    - containerd.io
    - docker-buildx-plugin
    - docker-compose-plugin


# Add user to Docker group
- name: Ensure user is in the Docker group
  ansible.builtin.user:
    name: debian
    groups: docker
    append: yes

# Reboot
- name: Reboot if needed (optional)
  ansible.builtin.reboot:
    msg: "Rebooting to apply docker group membership"
    reboot_timeout: 120
  when: reboot_required | default(true)

Task: Start Docker Container
#

---
- name: Start Docker container
  docker_container:
    name: nginx
    image: "nginx:latest"
    restart_policy: unless-stopped
    ports:
      - "8080:80"

Task: Start Docker Container with Volume Mapping
#

---
- name: Ensure directory exists
  ansible.builtin.file:
    path: /opt/nginx/data
    state: directory
    mode: '0755'
    owner: debian
    group: debian
    recurse: yes
  become: true

- name: Ensure file exists
  ansible.builtin.copy:
    dest: /opt/nginx/data/index.html
    content: |
      hi there
      some text      
    owner: debian
    group: debian

- name: Start a Docker container with volume mapping
  docker_container:
    name: nginx
    image: "nginx:latest"
    restart_policy: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - "/opt/nginx/data:/usr/share/nginx/html"

Task: Stop Docker Container
#

---
- name: Stop Docker container without removing it
  docker_container:
    name: nginx
    state: stopped

Task: Stop & Remove Docker Container
#

---
- name: Stop and remove Docker container
  docker_container:
    name: nginx
    state: absent

Task: Start Docker Compose Stack
#

  • This task creates a “docker-compose.yml” file with the specified content. As an alternative, the file could be copied from a static file or template.

  • The stack is started using a command, which is not idempotent. The task runs even if the stack is already up, and Ansible will always report a “changed” status.

---
- name: Ensure directory exists
  ansible.builtin.file:
    path: /opt/nginx-stack
    state: directory
    owner: debian
    group: debian
    recurse: yes
  become: true

- name: Ensure file exists
  ansible.builtin.copy:
    dest: /opt/nginx-stack/docker-compose.yml
    content: |
      services:
        nginx:
          image: nginx:latest
          restart: unless-stopped
          ports:
            - "8080:80"      
    owner: debian
    group: debian
  become: true

- name: Start Docker Compose stack
  ansible.builtin.command: docker compose up -d
  args:
    chdir: /opt/nginx-stack
  become: true
  become_user: debian

Task: Stop Docker Compose Stack
#

---
- name: Stop Docker Compose stack
  ansible.builtin.command: docker compose down
  args:
    chdir: /opt/nginx-stack
  become: true

Task: Check Compose Status & Stop Stack
#

- name: Check if Docker Compose steck is up
  ansible.builtin.command: docker compose ps
  register: compose_status
  failed_when: false
  changed_when: false
  args:
    chdir: /opt/nginx-stack
  become: yes

- name: Stop Docker Compose stack
  ansible.builtin.command: docker compose down --remove-orphans
  args:
    chdir: /opt/nginx-stack
  when: "'Up' in compose_status.stdout"
  become: yes



Run the Playbook
#

# CD into Ansible project
cd ~/ansible-playground

# Run the playbook
ansible-playbook playbooks/example_playbook.yml -i inventory