Add swap memory with ansible
Asked Answered
B

8

42

I'm working on a project where having swap memory on my servers is a needed to avoid some python long running processes to go out of memory and realized for the first time that my ubuntu vagrant boxes and AWS ubuntu instances didn't already have one set up.

In https://github.com/ansible/ansible/issues/5241 a possible built in solution was discussed but never implemented, so I'm guessing this should be a pretty common task to automatize.

How would you set up a file based swap memory with ansible in an idempotent way? What modules or variables does ansible provide help with this setup (like ansible_swaptotal_mb variable) ?

Blocked answered 15/7, 2014 at 18:50 Comment(1)
I'm unable to reply to Greg Dubicki answer but I think that on "Remove swap entry from fstab" task, state should be set to "absent" instead of "present", otherwise the task won't do what it announces.Incline
B
52

This is my current solution:

- name: Create swap file
  command: dd if=/dev/zero of={{ swap_file_path }} bs=1024 count={{ swap_file_size_mb }}k
           creates="{{ swap_file_path }}"
  tags:
    - swap.file.create


- name: Change swap file permissions
  file: path="{{ swap_file_path }}"
        owner=root
        group=root
        mode=0600
  tags:
    - swap.file.permissions


- name: "Check swap file type"
  command: file {{ swap_file_path }}
  register: swapfile
  tags:
    - swap.file.mkswap


- name: Make swap file
  command: "sudo mkswap {{ swap_file_path }}"
  when: swapfile.stdout.find('swap file') == -1
  tags:
    - swap.file.mkswap


- name: Write swap entry in fstab
  mount: name=none
         src={{ swap_file_path }}
         fstype=swap
         opts=sw
         passno=0
         dump=0
         state=present
  tags:
    - swap.fstab


- name: Mount swap
  command: "swapon {{ swap_file_path }}"
  when: ansible_swaptotal_mb < 1
  tags:
    - swap.file.swapon
Blocked answered 15/7, 2014 at 18:51 Comment(8)
I'd worry about idempotence -- in particular, if you run a deployment when there's already a swap file, this might zero it, which, I imagine, would crash every process that has any pages swapped.Killjoy
The creates="{{ swap_file_path }}" prop in the first task should take care of this, if the file exist dd won't run. I'm still using this in production and current task conditionals (when/creates) seem to be doing their job, which is to make this idempotent... Could you elaborate on the scenario where this might zero the file?Blocked
gonz: you're right; the creates should take care of it. I didn't notice that originally.Killjoy
swapon fails if already been run: stderr: swapon: /swapfile: swapon failed: Device or resource busyYarn
Never had that problem on my instances. Could you provide more info? What's the output for swapon -s? That last task shouldn't be run if you already have swap available, which happens after the first time it ran successfullyBlocked
I used the above and noticed that the swapfile didn't stop at the file size I specified, it kept going and filled the entire drive (20GB in my case). It turned out to be the k next to the count. Took that out and it worked like a charm, using Centos 7.5Mardellmarden
Like Richard Bale was saying, because of the block size bs and the k at the end, swap_file_size_kb is actually megabytes, not kilobytes.Premolar
Richard, James: Good catch! Changed the variable name to swap_file_size_mb (cc @JamesD)Blocked
J
38

I tried the answer above but "Check swap file type" always came back as changed and therefore isn't idempotent which is encouraged as a best practice when writing Ansible tasks.

The role below has been tested on Ubuntu 14.04 Trusty and doesn't require gather_facts to be enabled.

- name: Set swap_file variable
  set_fact:
    swap_file: "{{swap_file_path}}"
  tags:
    - swap.set.file.path

- name: Check if swap file exists
  stat:
    path: "{{swap_file}}"
  register: swap_file_check
  tags:
    - swap.file.check

- name: Create swap file
  command: fallocate -l {{swap_file_size}} {{swap_file}}
  when: not swap_file_check.stat.exists
  tags:
    - swap.file.create

- name: Change swap file permissions
  file: path="{{swap_file}}"
        owner=root
        group=root
        mode=0600
  tags:
    - swap.file.permissions

- name: Format swap file
  sudo: yes
  command: "mkswap {{swap_file}}"
  when: not swap_file_check.stat.exists
  tags:
    - swap.file.mkswap

- name: Write swap entry in fstab
  mount: name=none
         src={{swap_file}}
         fstype=swap
         opts=sw
         passno=0
         dump=0
         state=present
  tags:
    - swap.fstab

- name: Turn on swap
  sudo: yes
  command: swapon -a
  when: not swap_file_check.stat.exists
  tags:
    - swap.turn.on

- name: Set swappiness
  sudo: yes
  sysctl:
    name: vm.swappiness
    value: "{{swappiness}}"
  tags:
    - swap.set.swappiness

As of Ansible 2.9, sudo declarations should be become:

- name: Set swap_file variable
  set_fact:
    swap_file: "{{swap_file_path}}"
  tags:
    - swap.set.file.path

- name: Check if swap file exists
  stat:
    path: "{{swap_file}}"
  register: swap_file_check
  tags:
    - swap.file.check

- name: Create swap file
  command: fallocate -l {{swap_file_size}} {{swap_file}}
  when: not swap_file_check.stat.exists
  tags:
    - swap.file.create

- name: Change swap file permissions
  file: path="{{swap_file}}"
        owner=root
        group=root
        mode=0600
  tags:
    - swap.file.permissions

- name: Format swap file
  become: yes
  command: "mkswap {{swap_file}}"
  when: not swap_file_check.stat.exists
  tags:
    - swap.file.mkswap

- name: Write swap entry in fstab
  mount: name=none
         src={{swap_file}}
         fstype=swap
         opts=sw
         passno=0
         dump=0
         state=present
  tags:
    - swap.fstab

- name: Turn on swap
  become: yes
  command: swapon -a
  when: not swap_file_check.stat.exists
  tags:
    - swap.turn.on

- name: Set swappiness
  become: yes
  sysctl:
    name: vm.swappiness
    value: "{{swappiness}}"
  tags:
    - swap.set.swappiness

Vars required:

swap_file_path: /swapfile
# Use any of the following suffixes
# c=1
# w=2
# b=512
# kB=1000
# K=1024
# MB=1000*1000
# M=1024*1024
# xM=M
# GB=1000*1000*1000
# G=1024*1024*1024
swap_file_size: 4G
swappiness: 1
Judges answered 11/10, 2015 at 23:35 Comment(6)
That works on a 'ubuntu/trusty' vagrant box. Thanks!!Gilgilba
Works on CentOS 7.3 tooTwinge
Works on Ubuntu 16.04 LTS (Xenial Xerus) too. Thank you.Peddada
Works on Ubuntu 18.04 LTS too. Thank you.Brewery
As of Ansible 2.9, sudo is no longer a valid keyword type. So, sudo: yes should be changed to become: yes.Mandler
I would have become: true on the play part instead of the tasks, but yea depends what you are doing.Jacket
B
19

I based my take on the great Ashley's answer (please upvote it!) and added the following extra features:

  • global flag to manage the swap or not,
  • allow changing the swap size,
  • allow disabling swap,

...plus these technical improvements:

  • full idempotency (only ok & skipping when nothing is changed, otherwise some changed),
  • use dd instead of fallocate for compatibility with XFS fs (see this answer for more info),

Limitations:

  • changes the existing swapfile works correctly only if you provide its path as swap_file_path.

Tested on Centos 7.7 with Ansible 2.9 and Rocky 8 with Ansible 4.8.0.

Requires using privilege escalation as many of the commands need to run as root. (The easiest way to do it is to add --become to ansible-playbook arguments or to set the appropriate value in your ansible.cfg).

Parameters:

swap_configure: true # or false
swap_enable: true # or false
swap_file_path: /swapfile
swap_file_size_mb: 4096
swappiness: 1

Code:


- name: Configure swap
  when: swap_configure | bool
  block:

    - name: Check if swap file exists
      stat:
        path: "{{swap_file_path}}"
        get_checksum: false
        get_md5: false
      register: swap_file_check
      changed_when: false

    - name: Set variable for existing swap file size
      set_fact:
        swap_file_existing_size_mb: "{{ (swap_file_check.stat.size / 1024 / 1024) | int }}"
      when: swap_file_check.stat.exists

    - name: Check if swap is on
      shell: swapon --show | grep {{swap_file_path}}
      register: swap_is_enabled
      changed_when: false
      failed_when: false

    - name: Disable swap
      command: swapoff {{swap_file_path}}
      register: swap_disabled
      when: >
        swap_file_check.stat.exists
        and 'rc' in swap_is_enabled and swap_is_enabled.rc == 0
        and (not swap_enable or (swap_enable and swap_file_existing_size_mb != swap_file_size_mb))

    - name: Delete the swap file
      file:
        path: "{{swap_file_path}}"
        state: absent
      when: not swap_enable

    - name: Remove swap entry from fstab
      mount:
        name: none
        src: "{{swap_file_path}}"
        fstype: swap
        opts: sw
        passno: '0'
        dump: '0'
        state: present
      when: not swap_enable

    - name: Configure swap
      when: swap_enable | bool
      block:

        - name: Create or change the size of swap file
          command: dd if=/dev/zero of={{swap_file_path}} count={{swap_file_size_mb}} bs=1MiB
          register: swap_file_created
          when: >
            not swap_file_check.stat.exists
            or swap_file_existing_size_mb != swap_file_size_mb

        - name: Change swap file permissions
          file:
            path: "{{swap_file_path}}"
            mode: 0600

        - name: Check if swap is formatted
          shell: file {{swap_file_path}} | grep 'swap file'
          register: swap_file_is_formatted
          changed_when: false
          failed_when: false

        - name: Format swap file if it's not formatted
          command: mkswap {{swap_file_path}}
          when: >
            ('rc' in swap_file_is_formatted and swap_file_is_formatted.rc > 0)
            or swap_file_created.changed

        - name: Add swap entry to fstab
          mount:
            name: none
            src: "{{swap_file_path}}"
            fstype: swap
            opts: sw
            passno: '0'
            dump: '0'
            state: present

        - name: Turn on swap
          shell: swapon -a
          # if swap was disabled from the start
          # or has been disabled to change its params
          when: >
            ('rc' in swap_is_enabled and swap_is_enabled.rc != 0)
            or swap_disabled.changed

        - name: Configure swappiness
          sysctl:
            name: vm.swappiness
            value: "{{ swappiness|string }}"
            state: present

Bose answered 10/10, 2020 at 14:33 Comment(7)
Thanks! FYI the "Turn on swap" was skipped when I had no swap (since the swap_disabled.changed => "Disable swap" was skipped as well)Massorete
Sorry, I didn't get it @vehovmar... ^_^' So do I have a bug in my code? If so then please feel free to update it as you think it should look like. :)Bose
Yes there is a bug, the "Turn on swap" needs to check if "swap_enabled.rc != 0" as it will be 0 only if the swap was already on and in that case no need to enable it again.Massorete
tested & validated on ubuntu 18.04 (with the suggested fix from vehovmar)Pence
Thanks! I think on the task Turn on swap needs to be swap_emabled.rc != 0 or swap_disabled.changedInitiatory
Thanks @vehovmar, I applied your fix!Bose
To "Remove swap entry from fstab", the state: absent neededTelethon
H
1

I'm not able to reply to Greg Dubicki answer so I add my 2 cents here:

I think adding casting to int when doing calculation on swap_file_size_mb * 1024 * 1024 to transform it into swap_file_size_mb | int * 1024 * 1024 will make the code a bit smarter in case you want to use a fact to extract the final swap size based on the amount of RAM installed like:

swap_file_size_mb: "{{ ansible_memory_mb['real']['total'] * 2 }}"

else it will always resize the swap file even if the size it's the same.

Herder answered 23/6, 2021 at 9:31 Comment(1)
Thanks for the suggestion how to make the swap size dynamic according to the server RAM! I have used your suggestion about casting to int to ensure that my code works only when needed in its most recent update. :)Bose
L
0

Here is the ansible-swap playbook that I use to install 4GB (or whatever I configure group_vars for dd_bs_size_mb * swap_count) of swap space on new servers:

https://github.com/tribou/ansible-swap

I also added a function in my ~/.bash_profile to help with the task:

# Path to where you clone the repo
ANSIBLE_SWAP_PLAYBOOK=$HOME/dev/ansible-swap

install-swap ()
{
    usage='Usage: install-swap HOST';
    if [ $# -ne 1 ]; then
        echo "$usage";
        return 1;
    fi;
    ansible-playbook $ANSIBLE_SWAP_PLAYBOOK/ansible-swap/site.yml --extra-vars "target=$1"
}

Just make sure to add your HOST to your ansible inventory first.

Then it's just install-swap HOST.

Ludovika answered 21/5, 2016 at 17:32 Comment(0)
V
0

Here is a preexisting role on Galaxy which achieves the same thing: https://galaxy.ansible.com/geerlingguy/swap

Vimineous answered 20/2, 2023 at 12:49 Comment(0)
V
0

I've created the following task, though i "optimized" small things for us.

---
- name: add-swap-file | Check whether SWAP file exist
  ansible.builtin.command:
    cmd: swapon -s
  register: swap_exist
  no_log: true
  changed_when: false
  check_mode: false

- name: add-swap-file | Debug show server differences
  ansible.builtin.debug:
    msg: "Current swap file:\n{{ swap_exist.stdout }}\nRecommended swap file size: {{ swap_file_size_gb }}G"
    verbosity: 1

- name: add-swap-file | Create and configure swap file
  when: swap_exist.stdout | length == 0
  block:
    - name: add-swap-file | Create swap file
      community.general.filesize:
        path: "{{ swap_file_path }}"
        size: "{{ swap_file_size_gb }}G"
        blocksize: 1024
        mode: "600"
        owner: root
        group: root

    - name: add-swap-file | Check swap file type
      ansible.builtin.command:
        cmd: "file {{ swap_file_path }}"
      register: swapfile
      check_mode: false
      changed_when: false

    - name: add-swap-file | Make swap file
      ansible.builtin.command:
        cmd: "mkswap {{ swap_file_path }}"
      when: swapfile.stdout.find('swap file') == -1
      changed_when: true

    - name: add-swap-file | Write swap entry in fstab
      ansible.posix.mount:
        name: none
        src: "{{ swap_file_path }}"
        fstype: swap
        opts: sw
        passno: 0
        dump: 0
        state: present

    - name: add-swap-file | Mount swap
      ansible.builtin.command:
        cmd: "swapon {{ swap_file_path }}"
      when: ansible_swaptotal_mb < 1
      changed_when: true
Vaunt answered 1/9, 2023 at 12:38 Comment(0)
S
-4

What works for me is to add 4Gib swap through the playbook.

Check this:

this.

Shirline answered 29/5, 2020 at 7:58 Comment(1)
Please replace the screenshot with text, @krishna-upadhyay. See meta.#304312 for rationale.Bose

© 2022 - 2024 — McMap. All rights reserved.