9

I have read quite a few posts on this, but I am still unsure about the correct approach, assuming:

  1. I have a default Ubuntu 14.04 LTS VM created by and running on Azure, which doesn't come with a swap

  2. I would like to create a swap using existing VM storage, instead of create a new disk using additional storage

Posts I have read:

Many solutions were discussed but I can't seem to find one that will persists across server reboots (probably due to cloud-init has it's own idea about image partitioning), can someone advise me on the best practice?

8

Assuming that you have Linux Agent installed. All you have to do is to enable swap under /etc/waagent.conf. These are the relevant lines:

ResourceDisk.Format=y                   # Format if unformatted. If 'n', resour$
ResourceDisk.Filesystem=ext4            # Typically ext3 or ext4. FreeBSD image$
ResourceDisk.MountPoint=/mnt/resource   #
ResourceDisk.EnableSwap=y               # Create and use swapfile on resource d$
ResourceDisk.SwapSizeMB=2048            # Size of the swapfile.

It will automatically use the resource disk (which comes with every VM) to create the swap. There's no need to create a disk for it.

Update: You also need to execute the steps below in order to create the swapfile:

umount /mnt
service walinuxagent restart
  • Disk provision is controlled by Cloud Init on Ubuntu, unlike other distributions. So no, this shouldn't work, and both doc and my test confirm it. – bitinn May 31 '15 at 18:54
  • 2
    I contacted MS support and found the solution is to set ResourceDisk.Format, ResourceDisk.EnableSwap and ResourceDisk.SwapSizeMB. BUT the important step is to do a manual sudo service walinuxagent restart to create the swap file, as just rebooting server doesn't work for me. – bitinn Jun 1 '15 at 4:22
  • I am still asking about how Cloud Init figure into all this, because their doc and waagent.conf comments are misleading. – bitinn Jun 1 '15 at 4:23
  • Yup. i'm sorry. forgot to include the agent restart. I tested on my ubuntu vm and worked without problems. I've updated the answer. Regarding cloud-init, i don't think it has anything to do with the creation of the swapfile at all since the agent creates the file inside an ext4 partition (/mnt). It does not create a swap partition. – Bruno Faria Jun 1 '15 at 12:05
  • 2
    Did not work on Ubuntu 14.04 LTS VM, created from Azure Gallery image. After performing all the steps, swapon -s still shows empty list of swap files. – JustAMartin May 26 '16 at 15:11
2

Bruno's answer is a great starting point, but it only worked after I rebooted and gave it another minute after booting.

a. Enable swap in /etc/waagent.conf, relevant lines:

ResourceDisk.Format=y                   # Format if unformatted. If 'n', resour$
ResourceDisk.Filesystem=ext4            # Typically ext3 or ext4. FreeBSD image$
ResourceDisk.MountPoint=/mnt/resource   #
ResourceDisk.EnableSwap=y               # Create and use swapfile on resource d$
ResourceDisk.SwapSizeMB=2048            # Size of the swapfile.

b. Do the following as root, which includes rebooting your machine:

umount /mnt
service walinuxagent restart
reboot

c. After booting it will still take some time before the swap is actually enabled. You can check it with swapon -s.

1

I believe the right way to do this so that both cloud-init and the waagent play 'nice' together (from Cloud-Init Azure docs) is to keep these values set to this

# disabling provisioning turns off all 'Provisioning.*' function
Provisioning.Enabled=n
# this is currently not handled by cloud-init, so let walinuxagent do it.
ResourceDisk.Format=y
ResourceDisk.MountPoint=/mnt

I tried changing the mountpoint but it didn't seem to work properly so the docs are probably accurate about the values

And then you can customize the swap options as you want

# Create and use swapfile on resource disk.
ResourceDisk.EnableSwap=y

# Size of the swapfile.
ResourceDisk.SwapSizeMB=8192

A basic restart picks up the new swap fine

sudo service walinuxagent restart

free -m
             total       used       free     shared    buffers     cached
Mem:          3944        882       3061         44         29        163
-/+ buffers/cache:        689       3255
Swap:         8192          0       8192
0

I have read quite a few posts on this, but I am still unsure about the correct approach, assuming: 1. I have a default Ubuntu 14.04 LTS VM created by and running on Azure, which doesn't come with a swap 2. I would like to create a swap using existing VM storage, instead of create a new disk using additional storage

I was also in need for this (actually 16.04 instead of 14.04, but my answer will apply to both I think).

Posts I have read:

But when I saw I had to read so long essays that you point out, I was going to give up... But suddenly I remembered a very straightforward article in DigitalOcean's blog:

How To Add Swap On Ubuntu 14.04

It's so simple that I've even written a script for it (at least for the best part, not yet the swappiness settings and other advanced stuff):

#!/usr/bin/env fsharpi

open System
open System.IO
open System.Net
open System.Diagnostics

#load "InfraTools.fs"
open Gatecoin.Infrastructure

// automation of https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04

let NUMBER_OF_GB_FOR_SWAP = 1

let isThereSwapMemoryInTheSystem (): bool =
    let _,output,_ = Tools.SafeHiddenExec("swapon", "-s")
    (output.Trim().Length > 0)

if (isThereSwapMemoryInTheSystem()) then
    Console.WriteLine("Swap already setup")
    Environment.Exit(0)

let swapFile = new FileInfo(Path.Combine("/", "swapfile"))
if not (swapFile.Exists) then
    Tools.BailIfNotSudoer("Need to use 'fallocate' to create swap file")
    Console.WriteLine("Creating swap file...")
    Tools.SafeExec("fallocate", String.Format("-l {0}G {1}", NUMBER_OF_GB_FOR_SWAP, swapFile.FullName), true)

let permissionsForSwapFile = 600
if not (Tools.OctalPermissions(swapFile) = permissionsForSwapFile) then
    Tools.BailIfNotSudoer("Need to adjust permissions of the swap file")
    Tools.SafeExec("chmod", String.Format("{0} {1}", permissionsForSwapFile, swapFile.FullName), true)

Tools.BailIfNotSudoer("Enable swap memory")
Tools.SafeExec("mkswap", swapFile.FullName, true)
Tools.SafeExec("swapon", swapFile.FullName, true)
if not (isThereSwapMemoryInTheSystem()) then
    Console.WriteLine("Something went wrong while enabling the swap file")
    Environment.Exit(1)

Tools.BailIfNotSudoer("Writing into /etc/fstab")
Tools.SafeHiddenExecBashCommand(String.Format("echo \"{0}   none    swap    sw    0   0\" >> /etc/fstab", swapFile.FullName))

For the above to work, you need to sudo apt install fsharp first (at least Ubuntu 16.04 has fsharp in the repositories, not sure about 14.04).

Also you need this InfraTools.fs file:

open System
open System.IO
open System.Net

namespace Gatecoin.Infrastructure

module Tools =

    let HiddenExec (command: string, arguments: string) =
        let startInfo = new System.Diagnostics.ProcessStartInfo(command)
        startInfo.Arguments <- arguments
        startInfo.UseShellExecute <- false

        // equivalent to `>/dev/null 2>&1` in unix
        startInfo.RedirectStandardError <- true
        startInfo.RedirectStandardOutput <- true

        use proc = System.Diagnostics.Process.Start(startInfo)
        proc.WaitForExit()
        (proc.ExitCode,proc.StandardOutput.ReadToEnd(),proc.StandardError.ReadToEnd())

    let HiddenExecBashCommand (commandWithArguments: string) =
        let args = String.Format("-c \"{0}\"", commandWithArguments.Replace("\"", "\\\""))
        HiddenExec("bash", args)

    let SafeHiddenExecBashCommand (commandWithArguments: string) =
        let exitCode,stdOut,stdErr = HiddenExecBashCommand commandWithArguments
        if not (exitCode = 0) then
            Console.Error.WriteLine(stdErr)
            Console.Error.WriteLine()
            Console.Error.WriteLine("Bash command '{0}' failed with exit code {1}.", commandWithArguments, exitCode.ToString())
            Environment.Exit(1)
        exitCode,stdOut,stdErr

    let Exec (command: string, arguments: string, echo: bool) =
        let psi = new System.Diagnostics.ProcessStartInfo(command)
        psi.Arguments <- arguments
        psi.UseShellExecute <- false
        if (echo) then
            Console.WriteLine("{0} {1}", command, arguments)
        let p = System.Diagnostics.Process.Start(psi)
        p.WaitForExit()
        p.ExitCode

    let ExecBashCommand (commandWithArguments: string, echo: bool) =
        let args = String.Format("-c \"{0}\"", commandWithArguments.Replace("\"", "\\\""))
        if (echo) then
            Console.WriteLine(commandWithArguments)
        Exec("bash", args, false)

    let SafeHiddenExec (command: string, arguments: string) =
        let exitCode,stdOut,stdErr = HiddenExec(command, arguments)
        if not (exitCode = 0) then
            Console.Error.WriteLine(stdErr)
            Console.Error.WriteLine()
            Console.Error.WriteLine("Command '{0}' failed with exit code {1}. Arguments supplied: '{2}'", command, exitCode.ToString(), arguments)
            Environment.Exit(1)
        exitCode,stdOut,stdErr

    let SafeExec (command: string, arguments: string, echo: bool) =
        let exitCode = Exec(command, arguments, echo)
        if not (exitCode = 0) then
            Console.Error.WriteLine("Command '{0}' failed with exit code {1}. Arguments supplied: '{2}'", command, exitCode.ToString(), arguments)
            Environment.Exit(1)
            failwith "unreached"
        ()

    let SafeExecBashCommand (commandWithArguments: string, echo: bool) =
        let args = String.Format("-c \"{0}\"", commandWithArguments.Replace("\"", "\\\""))
        if (echo) then
            Console.WriteLine(commandWithArguments)
        SafeExec("bash", args, false)

    let FirstElementOf3Tuple (a, _, _) = a
    let SecondElementOf3Tuple (_, b, _) = b

    let SimpleStringSplit (str: string, separator: string): string list =
        List.ofSeq(str.Split([|separator|], StringSplitOptions.RemoveEmptyEntries))

    let SplitStringInLines (str: string): string list =
        SimpleStringSplit(str,Environment.NewLine)

    let CommandWorksInShell (command: string): bool =
        let exitCode =
            try
                Some(FirstElementOf3Tuple(HiddenExec(command,String.Empty))
            with
                | :? System.ComponentModel.Win32Exception -> (); None
        if exitCode.IsNone then
            false
        else
            true

    let BailIfNotSudoer(reason: string): unit =   
        if not (CommandWorksInShell "id") then
            Console.WriteLine ("'id' unix command is needed for this script to work")
            Environment.Exit(2)
            ()

        let _,idOutput,_ = HiddenExec("id","-u")
        if not (idOutput.Trim() = "0") then
            Console.Error.WriteLine ("Error: needs sudo privilege. Reason: {0}", reason)
            Environment.Exit(3)
            ()
        ()

    let OctalPermissions (file: FileInfo): int =
        let output = SecondElementOf3Tuple(SafeHiddenExec("stat", String.Format("-c \"%a\" {0}", file.FullName)))
        Int32.Parse(output.Trim())

Many solutions were discussed but I can't seem to find one that will persists across server reboots

The part that makes my answer work through server reboots is the writing to /etc/fstab file.

The good thing about this solution is that it should work in Azure, DigitalOcean, YouNameIt, ...

Enjoy!

  • 2
    "It's so simple" followed by 50 lines of code seems a bit of an oxymoron! – guaka Jul 22 '16 at 22:12
0

Now there are instructions at the official Azure documentation: https://support.microsoft.com/en-us/help/4010058/how-to-add-a-swap-file-in-linux-azure-virtual-machines

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.