One of the sillier things I've done as an AWS/linux admin is provision an EBS disk as swap to an EC2 instance. I kept getting max allocate errors for a script I needed to run to execute a series of database queries. Reprovisioning to a new EC2 instance class with more RAM wasn't feasible at the time for some long-forgotten reason.
I would never do this if I owned the disks - provisioning swap to SSD will greatly reduce the lifetime of the disk, among many reasons why this is less than ideal. But Amazon has plenty of money. I figured I could cheaply provision an EBS volume & buy myself enough swap to complete the query. Then, in some point in the future, I could create a more beautimous solution.
Well, if you're a sysadmin you know how this story ends. I moved onto other fires/projects, quickly forgot about the swap situation, and here I am years later, deprovisioning the server, in all its swappy glory.
This wouldn't warrant a blog post, except for the fact that I received an error when trying to disable swap using "swapoff -a":
swapoff failed: Cannot allocate memory
In this case, the swap had about 750MB of swap in use, and this tiny little EC2 Nano instance only had about 5MB of free RAM. In order for me to detach the EBS swap device, I needed a temporary place to store the swap that is currently in use assuming that the server must stay online. Another option would have been to have edited my /etc/fstab file to comment out the line binding the EBS UUID to /dev/swap and rebooting.
Another method of resolving the issue is to shift the used swap space to a temporary swap file. This does not require a reboot and allows me to reclaim the EBS device immediately. That's what I opted to do, and here is how I did that:
First, you need to find the path to the swap device or file. Because I am a dummy and used an EBS device for this purpose, I could easily find that path using the blkid command (note I am censoring command output, so your output will look different than mine):
$ sudo blkid
/dev/nvme1n1: UUID="****" TYPE="swap"
[...]
$ free -h total used free shared buff/cache availableMem: 464M 202M 5.5M 15M 260M 233MSwap: 8.0G 816M 7.2G
$ sudo dd if=/dev/zero of=/home/swap bs=1024 count=10240001024000+0 records in1024000+0 records out1048576000 bytes (1.0 GB) copied, 6.8052 s, 154 MB/s
$ sudo fdisk -l /dev/nvme1n1Disk /dev/nvme1n1: 8589 MB, 8589934592 bytes, 16777216 sectorsUnits = sectors of 1 * 512 = 512 bytesSector size (logical/physical): 512 bytes / 512 bytesI/O size (minimum/optimal): 512 bytes / 512 bytes
$ sudo chmod 0600 /home/swap
Use 'mkswap' to properly configure the file for swapping:
$ sudo mkswap /home/swap
Setting up swapspace version 1, size = 1023996 KiB
no label, UUID=1f3f78ed-8e5d-4672-bbd1-59e6f29c8b08
And tell the host to use the new file with 'swapon':
$ sudo swapon /home/swap
Unless you use the -v flag, a successful swapon will not return output to the command line. Its still a good idea to check the "free" command to make sure that the new swap has been applied:
$ free -h
total used free shared buff/cache available
Mem: 464M 198M 5.5M 15M 260M 237M
Swap: 9.0G 734M 8.3G
Notice the total swap amount: 9.0G. This number includes the 1GB file we just created as well as the 8GB swap device that already existed. Linux does not show two separate swap devices in this context, the same way it would not show two different sticks of RAM in this context.
Finally, we are ready to disable the old swap device. Here I am using the -v flag to look out for additional information in the event of an error. But the command was successful, so swapoff simply prints back my command when it completes:
$ sudo swapoff -v /dev/nvme1n1
swapoff /dev/nvme1n1
Running the free command again, we can see that the total Swap value has been reduced by about 8GB
$ free -h
total used free shared buff/cache available
Mem: 464M 390M 5.9M 27M 67M 34M
Swap: 999M 524M 475M
Looks good. I'm not quite done yet, though. Even though I don't plan to reboot immediately, I still need to remove the /etc/fstab entry for the old swap device. I used a text editor to add a hash symbol (#) to the line relevant to the swap device:
#/dev/nvme1n1 none swap sw 0 0
$ free -h total used free shared buff/cache availableMem: 464M 238M 77M 14M 149M 199MSwap: 999M 84M 915M
$ sysctl vm.swappinessvm.swappiness = 10
At the default value of vfs_cache_pressure=100 the kernel will attempt to
reclaim dentries and inodes at a "fair" rate with respect to pagecache and
swapcache reclaim. Decreasing vfs_cache_pressure causes the kernel to prefer
to retain dentry and inode caches. When vfs_cache_pressure=0, the kernel will
never reclaim dentries and inodes due to memory pressure and this can easily
lead to out-of-memory conditions. Increasing vfs_cache_pressure beyond 100
causes the kernel to prefer to reclaim dentries and inodes.
Increasing vfs_cache_pressure significantly beyond 100 may have negative
performance impact. Reclaim code needs to take various locks to find freeable
directory and inode objects. With vfs_cache_pressure=1000, it will look for
ten times more freeable objects than there are.
# swapoff /home/swapswapoff: /home/swap: swapoff failed: Cannot allocate memory
vm.swappiness=1 ### changed from a value of 10vm.vfs_cache_pressure=100 ### changed from a value of 200
# free -h total used free shared buff/cache availableMem: 464M 282M 66M 46M 116M 123MSwap: 0B 0B 0B
$ sudo umount -d /dev/sdfumount: /dev/sdf: mountpoint not found
# umount -d /dev/nvme1n1umount: /dev/nvme1n1: not mounted
The point is, we already took care of unmounting the device when we eliminated the swap partition. So we can skip directly to detaching the volume using the AWS console. Be careful to select the correct Volume ID (its a good idea to use a Name tag to avoid mistakes).
And that's it. What could be easier? :/