Tuesday, October 22, 2024

Grub repair with LVM+Multipath

I recently encountered an issue where I ran a grub update in an off-prem cloudy environment (Power Virtual Server) where I couldn't use a USB key for grub rescue. 

I *really* wanted that VM back, so I lived through the pain of a browser console repair operation. (Tip: you can select and middle-mouse-click to paste from within the console, even if you can't copy-paste into the console.)

I used https://www.linuxfoundation.org/blog/blog/classic-sysadmin-how-to-rescue-a-non-booting-grub-2-on-linux, which is accurate and very helpful, except that it doesn't mention LVM/multipath disks. If you're seeing disk labels like (ieee1275//vdevice/vfc-client@30000007/disk@5005076810149062,msdos2) back when you execute the "ls -l" command, here's what I modified in the instructions to get my VM booted with a basic grub config.

1. My set root looked like this:

set root=(ieee1275//vdevice/vfc-client@30000007/disk@5005076810149062,msdos2) 

2. My linux line looked like this:

linux /boot/vmlinuz<tab-complete> root=UUID=85399057-074b-4c82-85fb-1f82770b2646

3. My initrd was an initramfs file, so my initrd line looked like this:

initrd /boot/initramfs<tab-complete>.img


The UUID for the disk I needed was included in the disk info that ls -l provided. You can also find it (after you find the root partition you need), under /boot/grub/grub.cfg.

Wednesday, March 25, 2020

Some arch-querying examples for building

Most likely when building, be it container images or standalone binaries, you don't want a separate set of scripts or Makefiles or Dockerfiles, or whatever you're using, for each architecture you want to target (e.g. amd64 or ppc64le). I was asked by some new team-members to provide examples of ways to get the architecture of the system on your scripts are running. It was a short little list of things I've used and seen over the years, so I thought it might be nice to share publicly.

-------------------------

The first example is using shell and uname in a Makefile. Under the lint target, you can see that linting is only done for amd64.

.PNONY: lint
lint:
ifeq ($(ARCH), amd64)
@git diff-tree --check $(shell git hash-object -t tree /dev/null) HEAD $(shell ls -d * | grep -v cfc-files)
@yamllint .
@docker run --rm -v $(shell pwd):/data -w /data $(ANSIBLE_IMAGE) ansible-playbook -e cluster/config.yaml playbook/install.yaml --syntax-check
endif

But where did `ARCH` come from? In this use-case, which I've lifted from an internal project, there's an included file called Configfile that gets the arch using `uname`. You could just as easily put that into the top of your Makefile.
ARCH ?= $(shell uname -m | sed 's/x86_64/amd64/g')
ifeq ($(ARCH), amd64)
DOCKER_FLAG ?= Dockerfile
else
DOCKER_FLAG ?= Dockerfile.$(ARCH)
You can see in this example that this project *does* have a separate Dockerfile for each target arch, which is fine. (However, you can just maintain one if you use multi-arch images, and/or build-args).

You can also see that there's a substitution done for x86_64. Most of the time using those interchangably is fine. There are most likely similar cases for ARM variants, so this is a good thing to keep in mind and keep your case statements cleaner later on.

---------------------------

My  next example is a trick I stole from the nvidia-docker maintainers that lets you get an if-statement into a Dockerfile. Docker intentionally excludes conditional logic in Dockerfiles so that your images are the same from build to build. So use this example with great caution, and keep your images consistent.


RUN set -eux; \
     \
     arch="$(uname -m)"; \
          case "${arch##*-}" in \
          x86_64 | amd64) ARCH='amd64' ;; \
          ppc64el | ppc64le) ARCH='ppc64le' ;; \
         *) echo "unsupported architecture"; exit 1 ;; \
     esac; \
wget -nv -O - https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${ARCH}.tar.gz \
| tar -C /usr/local -xz

I have this in a Dockerfile of my own that I use it to set up a build environment. As you can see, it's a way to use a URL that has a hard-coded architecure.


-----------------------------

So that's it! If you have any of your own fun tricks for your projects, please share them!

Monday, August 28, 2017

Create a private docker registry with a self-signed cert

Disclaimer: Don't do this in production. :D




I have found myself needing to have my own registry so that I could see its internal debug log. It's easy enough to spin up a registry locally and use it with loopback and the --insecure-registry flag -- but I want to use TLS. And I want to use a self-signed cert. And I want to use an IP address instead of a hostname. And I want a pony (j/k, a kitten. j/k, 3 kittens).

The doc that I found didn't tell me about the /etc/docker/certs.d/<host>:<port>/ part of all this. And it was a pain for me to get the IP SAN working (not going to explain the embarrassing mistake I made there).

So here is my dirty dirty cheat sheet for next time:

create a cert with an IP SAN (Subject Alternative Name, not Storage Area Network):

$ cp /etc/pki/tls/openssl.cnf .   # location may vary
$ vi openssl.cnf

# uncomment
req_extensions = v3_req

# Modify the v3_req section as follows:
[ v3_req ]
subjectAltName = @alt_names
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[alt_names]
IP.1 = 192.168.1.2
IP.2 = 10.53.10.1

--

For just one IP, you can simply use
subjectAltName =IP: 10.53.10.1
(removing the alt_names section)

--

# then run:
$ openssl req -x509 -nodes -days 730 -newkey rsa:4096  -keyout certs-dir/domain.key -out certs-dir/domain.crt -config openssl.cnf  -sha256

(If that doesn't work, add `-extensions v3_req`)

--

# [optional sanity-check] confirm IPs in cert:
openssl x509 -text -in certs-dir/domain.crt -noout | grep "IP Address"

# run docker and bind-mount in the cert + key:

docker run -dit -p 5000:5000 --name registry -v `pwd`/certs-dir/:/certs -e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt" -e "REGISTRY_HTTP_TLS_KEY=/certs/domain.key" registry:2

export DOMAIN_NAME=192.168.1.2

# load the cert system-wide:
openssl s_client -connect $DOMAIN_NAME:5000 -showcerts </dev/null 2>/dev/null | openssl x509 -outform PEM | sudo tee /etc/pki/ca-trust/source/anchors/$DOMAIN_NAME.crt

$ sudo update-ca-trust

# load the cert into docker's config:
$ mkdir -p /etc/docker/certs.d/$DOMAIN_NAME:5000
$ openssl s_client -connect $DOMAIN_NAME:5000 -showcerts </dev/null 2>/dev/null | openssl x509 -outform PEM | sudo tee /etc/docker/certs.d/$DOMAIN_NAME:5000/ca.crt

$ sudo /bin/systemctl restart docker.service

# verify again
openssl x509 -text -in /etc/pki/ca-trust/source/anchors/$DOMAIN_NAME.crt -noout | grep "IP Addr"

$ docker start registry

$ docker push $DOMAIN_NAME:5000/hello-world

--

Saturday, April 15, 2017

DockerCon 2017: Multi-Arch Resources

A huge shout-out to everyone who came to our DockerCon talk! Here is a short list of resources if you'd like to get started on a multi-arch journey.
Thanks,

- Christy & Chris

Wednesday, August 31, 2016

Using the host timezone in a docker container

I was recently asked if there was a docker option available to set the timezone in a container. There isn't one, and I started looking into whether it would be a good feature-add. I found several github issues discussing how to best set it. There were recommendations of bind-mounting /etc/localtime, or setting an environment variable (though no one went into much detail on that one). I did a little googling this morning, and came across a new environment variable: TZ.

TZ is available on all POSIX-compliant systems. You can pass in a timezone using Olson Format, e.g. America/Chicago. Read more about TZ here: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html.

To find the timezone, Ubuntu, and RHEL-based systems all seem to sym-link /etc/localtime to a timezone file. If someone has just overwritten it on your system, you'll have to modify accordingly.

On my laptop:
> readlink /etc/localtime
../usr/share/zoneinfo/America/Chicago

Docker has a flag, -e, to pass in environment variables via the CLI.

-----------------

Using the two together, all you really need is:

`docker run -it --rm -e "TZ=$(readlink /etc/localtime | cut -d '/' -f5,6 )" centos:7`

You can run it once without specifying the timezone:
> docker run -it --rm  centos:7 date +%Z
UTC

And again with it to check:
> docker run -it --rm -e "TZ=$(readlink /etc/localtime | cut -d '/' -f5,6)" centos:7 date +%Z
CDT

You can also choose a specific timezone:
> docker run -it --rm -e "TZ=Asia/Tehran" centos:7 date +%Z
IRDT

Wednesday, June 15, 2016

running x86 containers on your ppc64le system

My last post was about running other architectures' containers on your laptop. This one's about running x86_64/amd64 containers on your ppc64le system!

If you didn't read the previous post, and want to know how this works, here is is: http://christycodes.blogspot.com/2016/06/running-cross-arch-container-images-on.html

Here's how you can do this (informational items in gray):
~> uname -m
ppc64le


1. Get the qemu emulator binaries:
~> apt-get download qemu-user-static
~> ls qemu-user-static_1%3a2.5+dfsg-5ubuntu10.1_ppc64el.deb
qemu-user-static_1%3a2.5+dfsg-5ubuntu10.1_ppc64el.deb
~> sudo dpkg --force-all -i qemu-user-static_1%3a2.5+dfsg-5ubuntu10.1_ppc64el.deb
Note: I intentionally didn't use apt-get install for qemu-user-static because I didn't want the binfmt-utils package.

2. Get/run the container that registers the binfmt hooks:
  ~> mkdir multiarch && cd multiarch && git clone https://github.com/clnperez/qemu-user-static.git && cd qemu-user-static/register
~> docker build -t multiarch/qemu-user-static:register .
~> ls /proc/sys/fs/binfmt_misc/
register  status

~> docker run --rm --privileged multiarch/qemu-user-static:register
~> ls /proc/sys/fs/binfmt_misc/
aarch64  alpha  arm  armeb  i386  i486  m68k  mips  mips64  mips64el  mipsel  mipsn32  mipsn32el  register  s390x  sh4  sh4eb  sparc  status


3. Run your x86 image:
> docker run --rm -v /usr/bin/qemu-x86_64-static:/usr/bin/qemu-x86_64-static busybox uname -a
Linux 77ce603ac0f1 4.4.0-22-generic #40-Ubuntu SMP Thu May 12 22:03:35 UTC 2016 x86_64 GNU/Linux
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]


Note: That TCG error means that there's a missing CPU feature, but I'm not entirely sure that TCG doesn't support vmx, so I'm going to ask around.



Running cross-arch container images on your linux laptop

With the introduction of Docker for Mac, I ran across an exciting blog post: http://blog.hypriot.com/post/first-touch-down-with-docker-for-mac. I don't use a Mac for development, but what made that blog post interesting to me was the "Easter Egg" bit. It was titled, "There is another big ARM surprise," which is pretty sweet (so hopefully you've read that by now).

But what about other architectures? And what about not just doing this in a Mac? Well, get your ribboned baskets ready, because that Easter Egg has led me to a giant Easter Egg minefield of awesome. There are some folks over at Scaleway working on a multiarch project, and they've put together two key things:

  1. All the Easter Eggs: [scroll to Downloads] https://github.com/multiarch/qemu-user-static/releases
  2. The prep your Easter basket needs to use them: https://hub.docker.com/r/multiarch/qemu-user-static
So let's back up a little and talk about what is going on here. Some of this was mentioned in the blog post I referenced at the beginning of this post, but I think a bit more exploration is fun. (If you don't, skip down to the teal deer).

In Linux, there's a binary that allows you to run ELF binaries that weren't compiled for the architecture you are running on. It's called binfmt_misc, and you can read more about it here: https://www.kernel.org/doc/Documentation/binfmt_misc.txt.That binary doesn't actually run the program. It just provides the mechanism to make sure the right interpreter does, based on some bits embedded in the program itself.  The binfmt_misc binary checks the magic bits, then cross-references with what it finds in /proc/sys/fs/binfmt_misc/ for what to do.

That's where #1 comes in. binfmt_misc comes shipped with most Linux distros, but it can't do the job alone. It needs an interpreter. And what better interepreter than qemu? The multiarch project includes over a dozen compiled static qemu binaries! There's more than just the ARM qemu binary in there, so whatever architecture you want to run, I bet it's in their list.

But you can't just plop the emulator into your system and start running ARM or POWER containers. You've got to let binfmt_misc know what binaries should do what. You've got to set up those magic numbers, and also have them point to the right place. That's where #2 is fantastic. Not only are all the strings that binfmt_misc needs already assembled, the Scaleway folks created a docker container that will add them all to your host! If they hadn't you'd have to put together strings like  

:ppc64le:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00:'${QEMU_BIN_DIR}'/qemu-ppc64le-static:

and then get them in the right place in your fs. Instead, you just run:
$ docker run --rm --privileged multiarch/qemu-user-static:register

and you're set up! 

You can check that these were added by:
$ ls /proc/sys/fs/binfmt_misc/
aarch64  arm    kshcomp  mips    mips64el  mipsn32    ppc    ppc64le   s390x  sh4eb  status alpha    armeb  m68k     mips64  mipsel    mipsn32el  ppc64  register  sh4    sparc

 

(Note: You do also have to have binfmt_misc mounted on your system, but I'm leaving that step out because on my F23 workstation it was mounted by default.)

All that's left is running the container. But the container needs access to the emulator, so you can just bind-mount it at runtime (e.g. with docker's -v).

So now, for those of you who stayed with me, thanks. For everyone else, it's deer time.

Oh hai! Let's go:
Tada!

My example was with ppc64le, but you can download one of the other qemu binaries in the first step, depending on intended arch of the container you want run.

Monday, April 4, 2016

git log: Print more info, including merged patches


Put this in your .bashrc

git(){
    if [[ $@ == "log" ]]; then
        command git log --pretty=fuller --simplify-merges --full-history
    else
        command git "$@"
    fi
}

Thanks for the tip, http://superuser.com/a/105389

Thursday, March 3, 2016

Copying with tmux and vim on Fedora 23

I use tmux and vim on Fedora 23. When I have vertically split windows, selecting with my mouse cursor selects across multiple open files. Today I finally got unlazy and googled around enough to figure out how to get text into my system's clipboard (since vim uses it's own paste buffer) from vim.

I used
Conner's answer here: http://stackoverflow.com/questions/11489428/how-to-make-vim-paste-from-and-copy-to-systems-clipboard and Ignacio Vazquez-Abrams's answer here:
http://superuser.com/questions/194747/how-to-install-vim-with-clipboard-support-on-fedora
to get copy/paste working for me.
  1. install gvim
  2. create an alias in my .bashrc file so I don't have to type 'gvim -v' instead of 'vi'
  3. To select:
    1. position your cursor at the beginning of the text block you want to copy
    2. shift+v ('V')
    3. arrow down
    4. "yank" the text using "+y (keydown shift ' and +, then keyup shift, then y)
    5. Paste into a different program (like gedit, which I use as a scratch-pad) using your mouse wheel or ctrl+v
Pasting is easy.

If you're new to vim, use :paste before pasting in code-blocks to preserve formatting.


Thursday, January 21, 2016

My Vagrant How-To (with libvirt)

I am super late the the Vagrant party. "All you have to do is vagrant up!" is all I hear from developers everywhere. But when I tried it, that was not my experience.

A lot of my life over the past several years has revolved around Linux and libvirt. I even did some backend work for a virtualization management platform called Kimchi. But since I was coming from the land of, "Here's your image, and here's your VM," I over-complicated it. "Where is the image I just downloaded?" "Doesn't my Vagrantfile need to be in the same place as that image?" The answers: "Don't worry about it," and "No."

So here's what I think is a pretty good explanation for people who are used to something like virt-manager but want to use Vagrant.

Intro: Projects, Vagrantfiles, & Boxes


Set up a directory to house your projects. You could source-control it, too. Then make subdirectories for each project:

$ mkdir vagrantfiles && cd vagrantfiles
$ mkdir someproject && cd someproject

This is before you've added any boxes. The directories will hold your `Vagrantfile`s – not your boxes. The base boxes (or .img files) live in ~/.vagrant.d/boxes, and are base images. When you set up a project, you'll be building on top of that base image. This allows sharing of one base image across multiple projects. So, pick a base you'd like to use a lot, and you'll save space on your hardrive.

Decide which box you want. You can look at available boxes from hashicorp here: https://atlas.hashicorp.com/boxes/search. Clicking on a box will show you more useful info, like the init command to use to get that box.

I want to use Fedora and libvirt (see Vagrant with Libvirt (on Fedora)), so I'm looking at https://atlas.hashicorp.com/fedora/boxes/23-cloud-base.  Let's init our project:

$ vagrant init fedora/23-cloud-base

If this is the first time you've used this box, it will download the base image. When it completes, you'll see a Vagrantfile in your directory.  If you want to do additional config, change your Vagrantfile first. The default one will get you a working VM with host networking, so that's good enough for me.

Now, up your box:
$ vagrant up --provider libvirt               # watch the config & start
$ sudo virsh list –all                               # see that your box is now running
$ vagrant ssh                                         # access your machine

For more, definitely read the Vagrant docs: https://www.vagrantup.com/docs/getting-started

Vagrant with Libvirt (on Fedora)

Installing the Plugin

There is a project to use libvirt instead of the default (Virtualbox) with Vagrant: https://github.com/pradels/vagrant-libvirt. The project instructions say to use the vagrant command to install the libvirt plugin. However, for Fedora at least, you should use dnf to install the plugin:

$ dnf install vagrant-libvirt

Box Configuration

As opposed to the Virtualbox provider, if you want to update some VM properties (e.g. VCPUs or memory), you'll need to use virsh (read: do it outside of Vagrant).

The default memory is 512M, which isn't going to cut it for my purposes. so I've gone back and updated my VM. In the future, I'll remember to add the following to my Vagrantfile *before* I 'vagrant up.'

config.vm.provider :libvirt do |libvirt|   
      libvirt.memory = 2048
end

Thursday, September 3, 2015

Docker SELinux Policy Revisited

This is a follow-up after my last post about pulling an selinux policy out of an rpm. It turns out, there's a policy in Docker's github at https://github.com/docker/docker/tree/master/contrib/docker-engine-selinux. Way! (Yes, that's a Wayne's World reference.)

So now I'm testing that out, to see if it gets me around the goroutine hangs (which then panic) I'm seeing if I have SELinux enabled/enforcing when I run the tests using gccgo (in my x86_64 Fedora 22 env).

To use:
# yum install selinux-policy-devel
# cd /path/to/local/docker/docker/tree/master/contrib/docker-engine-selinux
# make -f /path/to/local/docker/docker/tree/master/contrib/docker-engine-selinux/Makefile docker.pp
# sudo semodule -i docker.pp

But, I'm hitting https://bugzilla.redhat.com/show_bug.cgi?id=1177994, so that is a bummer. It took hours for the semodule command to finally fail, and my CPU was pegged at 99% until it finally return the error:

libsepol.check_assertion_helper: neverallow violated by allow restorecond_t semanage_store_t:file { relabelto };
libsemanage.semanage_expand_sandbox: Expand module failed
semodule:  Failed!

Update: I added the rawhide repo, updated libsepol & selinux-policy, and the semodule command completed.

Thursday, August 20, 2015

Getting an [un]official docker selinux policy for ppc64le

I've had all kinds of fun (yah, that's the word) with selinux and docker since I started working with docker back in March. My work laptop runs Fedora and I keep it at the latest supported release, so I've run into relabeling issues (https://github.com/docker/machine/issues/812) and others while building & running docker & docker-machine from upstream.

Recently I've been working on getting docker running on ppc64le. One of the things (because I'm a crazy stubborn person?) I refuse to do is disable selinux. Since RHEL doesn't ship docker for ppc64le, it doesn't ship an selinux policy for docker on ppc64le.

Option #1: Run audit2allow and create a policy.
Option #2: Use the docker policy that RedHat ships in an x86 rpm.

I am still learning how to safely generate my own selinux policies, so for now, I'm trying out #2.

Note that I'm using the newest policy available, which is for docker 1.7.1. Since I'm building from upstream, right now I'm running with docker 1.9 and hoping that docker doesn't want any new capabilities since this policy was written.

fc22-x86> rpm2cpio docker-selinux-1.7.1-108.el7.x86_64.rpm | cpio -id
fc22-x86> cd usr/share/selinux/packages/
fc22-x86> bunzip2 docker.pp.bz2
fc22-x86> scp docker.pp user@rhel-system:/home/user/location/.

rhel-ppc64le$ sudo semodule -i docker.pp



Now my docker tests are running. If they don't finish for selinux reasons, I'll post updates.

Update: When running with gccgo, I was getting goroutine hang/panics, and my tests weren't finishing. I happened upon a policy file in docker's github! See my newer blog post on this.


Tuesday, July 21, 2015

More VM Partition Fun

Docker images live in /var/lib/docker, so it's a good idea to set up a large /var if you're going to run docker. Otherwise, you get a large /home by default on some installs (like RHEL7).

A text-based install doesn't allow you to customize your partitions,, but a GUI install is a PITA to do on a VM in a lab on a slow private network that you're VPN'ing into, so just take the std LVM setup and then change it after installing:

- resize (shrink):

   --- Logical volume ---
  LV Path                /dev/rhel-l/home
  LV Name                home
  VG Name                rhel-l
  LV UUID                tXagVh-QH6i-smrg-Vt7h-y0cX-IenE-oitcIx
  LV Write Access        read/write
  LV Creation host, time localhost.localdomain, 2015-07-20 15:13:46 -0500
  Current LE             39793
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     8192
  Block device           253:2

# lvresize /dev/rhel-l/home -L 55G

==
- make var:

lvcreate -a -n var -L 100G rhel-l

  --- Logical volume ---
  LV Path                /dev/rhel-l/var
  LV Name                var
  VG Name                rhel-l
  LV UUID                57vY5M-3Mq6-0z70-WlWI-wcjd-BJpU-UTeP8v
  LV Write Access        read/write
  LV Creation host, time rhel7-1-ci-ppc64, 2015-07-20 15:31:02 -0500
  LV Status              available
  # open                 0
  LV Size                100.00 GiB
  Current LE             25600
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     8192
  Block device           253:3

==

- mkfs -t xfs (since RHEL 7) on both /dev/mapper/ ... var and ... home
- update /etc/fstab (add line for /var)
- mount -a   

==

Thursday, July 9, 2015

My first tech blog post: Resizing a VM disk image [cheat sheet]

Much akin to my painful decision to buy a smartphone because I couldn't remember appointments I'd made, I'm deciding today to enter the tech blogsphere. I just spent way too long googling how to do something I once knew how to do without googling. So much struggle.

This first post is a super super sloppy how-to for making my dev VM's image bigger. The math/steps/somethings may be off because my laptop CRASHED in the middle of the partition resizing the first time (Thanks systemd. No, really. I have a coredump to prove it.), and I had to backtrack and fill in some blanks. Post-crash, I made sure to start a screen session on the server in the lab just in case.

===
Background: My swap partition was at the end of the disk, so I wanted to grow the middle partition (to keep my data in tact), and then re-add the swap back to the end.

1. qemu-img resize ubuntu-15.img +25G

3. use parted to resize partitions:

[root@localhost ~]# parted /var/lib/libvirt/images/ubuntu-15.img
GNU Parted 3.1
Using /var/lib/libvirt/images/ubuntu-15.img
Welcome to GNU Parted! Type 'help' to view a list of commands.

[If I had the original print output you could see the original three partitions, but this just shows two because I'd done a recover to get the 2nd one back after I deleted (which is when my laptop kicked it) it so I knew it's old start.]

Number Start End Size File system Name Flags
1 1049kB 8389kB 7340kB prep
2 8389kB 15.4GB 15.4GB ext4

[my math:]
disk is now 42.9 G, so 2G swap, ~38G disk
38G is 39845888kB
start = 8389kB
end = 39845888 + 8389 = 39854277kB

rm 2
mkpart
name []
type: ext4
start? 8389kB
end? 39854277kB

mkpart
name [] swap
type: linux-swap
start? 39.9GB
end? 42.9GB

(parted) print
Model: (file)
Disk /var/lib/libvirt/images/ubuntu-15.img: 42.9GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number Start End Size File system Name Flags
1 1049kB 8389kB 7340kB prep
2 8389kB 39.9GB 39.8GB ext4
3 39.9GB 42.9GB 3093MB linux-swap(v1) swap

quit

4. use resize2fs to fill in filesystem into newly expanded partition
- Boot into VM

~> df -h shows that /dev/sda2 is /

~> sudo resize2fs /dev/sda2
[sudo] password for christy:
resize2fs 1.42.12 (29-Aug-2014)
Filesystem at /dev/sda2 is mounted on /; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 3
The filesystem on /dev/sda2 is now 9728000 (4k) blocks long.

5. Re-create swap

~> sudo mkswap /dev/sda3
spits out something I didn't copy, but it includes your new UUID:
Grab that now.
UUID= 1cc8a190-ef01-4b07-b139-e1cef94b379c

~> sudo swapon -U UUID
 
6. Update fstab
~> sudo vi /etc/fstab
 change UUID from old one for swap to new one 
~> mount -a 
 
7. ~> sudo vi /etc/initramfs-tools/conf.d/resume # update with new swap UUID
8. ~> sudo update-initramfs -u 
 
Magical.