The Problem

At the day job, one of my team’s current projects is a bespoke “serverless” script execution service for internal use, not unlike AWS Lambda or similar offerings. I’m not the main guy on this, but I’ve been involved in some interesting discussions about how we should control the execution environments. Ideally, they would be sandboxed and completely disposable, possibly only alive for the lifetime of the script they are executing. The obvious solution to this is to use containers.

The dominant scripting language amongst our user base is PowerShell, so we need to try and find the smallest possible container that can execute PowerShell. Size is important because we want to maximize worker density on our container hosts.

Nano Server?

When you put the words “PowerShell” and “small” together, the first thing that comes to mind is Windows Nano Server. Nano Server is a headless, stripped-back version of Windows Server that runs .NET Core and can only be administered via PowerShell. This seems ideal as it’s far smaller than Windows Server Core, the minimum memory requirements are 512MB per server rather than 2GB.

That’s still quite chunky for a short-lived script/function processor though, you could only have twice as many workers as you had gigabytes of memory available. Thankfully PowerShell (or the Core variant at least) is no longer restricted to Windows operating systems, we can now install it on servers with a much smaller footprint than even Nano Server.

The tiniest PowerShell runner

Here is the Dockerfile I have put together for a Debian-based PowerShell runner with the smallest footprint I could get. It’s running PowerShell 6.0.0-beta.

FROM debian:jessie-slim

# Disables a lot of apt-get output that we don't want during an unattended install.
ENV DEBIAN_FRONTEND noninteractive

# Update package list and install dependencies.
RUN apt-get update && apt-get install -y \
        apt-transport-https \
        apt-utils \
        curl

# Import the public repository GPG keys for Microsoft
RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -

# Register the Microsoft .deb repository.
RUN curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list | tee /etc/apt/sources.list.d/microsoft.list

# Update package list again and install PowerShell.
RUN apt-get update && apt-get install -y powershell

# Change the default shell to PowerShell.
SHELL ["powershell", "-Command"]

# Runs on start. The loop keeps the container alive until it is terminated.
CMD while($true){Start-Sleep -Seconds 60}

Note: The above is based on installation instructions from the PowerShell GitHub repo. You can find them here.

  • The Debian container images are recommended by Docker themselves as a well-supported base image. debian:jessie-slim is nice and lean, weighing in at 79.2 MB in size. For comparison, the current microsoft/nanoserver image is 1.04 GB in size.
  • Then we install some requirements that will enable us to install PowerShell. We use curl to import and register Microsoft’s package repository and apt-transport-https to enable apt-get to use HTTPS transport.
  • After registering the Microsoft repository we refresh the package cache and install PowerShell from that repository.
  • Finally, we change the shell used by Docker commands to be PowerShell.
  • We set the runtime command (CMD) to just start a loop to keep the container running until interrupted. The container lives for as long as that command is doing something.

Once that Dockerfile is in our current working directory we can build the image with:

# Any name will do, we've used "tinypowershell".
docker build --tag tinypowershell .

# When built the image details can be viewed with:
docker image ls

The total size of the built image is 285 MB. Still around a quarter of the size of the official Nano Server image.

Running the image

The minimum requirement for RAM on Debian is 128 MB, much less than Nano Server’s 512 MB. To restrict the amount of memory a Docker container can consume, we run it like so:

# The --detach parameter causes the container to be run in the background
# rather than take over the current shell.
docker run --detach --memory=128m tinypowershell

Let’s check that we can run PowerShell. The below will start an interactive PowerShell session on the container:

# Get the container id, e.g. 7d54026cbec2.
docker ps -q

docker exec --interactive <ContainerId> powershell

You can exit the container’s PowerShell session with: exit.

To check how the container is performing:

# Get the container id, e.g. 7d54026cbec2.
docker ps -q

# The --no-stream parameter returns a shapshot of the current container stats
# rather than take over the shell with a live stream.
docker stats --no-stream <ContainerId>

The above gives you output similar to below:

CONTAINER           CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O
7d54026cbec2        0.00%               39.73 MiB / 128 MiB   31.04%              22 kB / 6.87 kB     0 B / 4.1 kB

About the Author Kirk MacPhee

An experienced software developer and technical lead, specializing in automation technologies and their application.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s