Archive for the ‘Docker’ Category

Docker for Windows (beta) and msysgit

Friday, April 15th, 2016

I’ve recently joined the beta program for Docker on Windows (now based on Hyper-V).

I wanted to keep my current config using msysGit but got weird errors when executing Docker commands from msysGit: https://forums.docker.com/t/weird-error-under-git-bash-msys-solved/9210

I could fix the issue by installing a newer version of msysGit with support for the MSYS_NO_PATHCONV environment variable. With that installed, I then changed my docker alias to a better approach:

docker()
{
    export MSYS_NO_PATHCONV=1
    ("$DOCKER_HOME/docker.exe" "$@")
    export MSYS_NO_PATHCONV=0
}

Hope this helps!


A bit more Windows Docker bash-fu

Wednesday, April 22nd, 2015

Feeling bashy enough yet? :)

In my last post, I’ve given you a few useful functions for making your life with Docker easier on Windows. In this post, I’ll give you some more, but before that let’s look a bit a what docker-machine does for us.

When you invoke docker-machine to provision a Docker engine using Virtualbox, it “simply” creates a new VM… Okay though pretty basic, this explanation is valid ^^.

What? Not enough for you? Okay okay, let’s dive a bit deeper =)

Besides the VM, behind the scenes, docker-machine generates multiple things for us:

  • a set of self-signed certificates: used to create a server certificate for the Docker engine in the VM and a client certificate for the Docker client (also used by docker-machine to interact with the engine in the VM)
  • an SSH key-pair (based on RSA): authorized by the SSH daemon and used to authenticate against the VM

Docker-machine uses those to configure the SSH daemon as well as the Docker engine in the VM and stores these locally on your computer. If you run the following command (where docker-local is the name of the VM you’ve created), you’ll see where those files are stored:

command: eval "$(docker-machine env docker-local)"

export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH="C:\\Users\username\.docker\\machine\\machines\\docker-local"
export DOCKER_HOST=tcp://192.168.99.108:2376

As you can see above, the files related to my “docker-local” are all placed under c:\Users\username\.docker\machine\machines\docker-local. Note that DOCKER_TLS_VERIFY is enabled (which is nice). Also note that the DOCKER_HOST (i.e., engine) IP is the one of the VM (we’ll come back to this later on). Finally, the DOCKER_HOST port is 2376, which is Docker’s default.

Using docker-machine you can actually override just about any setting (including the location where the files are stored).

If you take a look at that location, you’ll see that docker-machine actually stores many interesting things in there:

  • a docker-local folder containing the VM metadata and log files
  • boot2docker.iso: the ISO used as basis for the VM (which you can update easily using docker-machine)
  • the CA, server and client certificates (ca.pem, cert.pem, server.pem, …)
  • config.json: more about this below
  • disk.vmdk: the VM’s disk (useful to take in backup if you care (you shouldn’t :p)
  • the SSH key-pair that you can use to authenticate against the VM (id_rsa, id_rsa.pub)

As noted above, there’s also a ‘config.json’ file, which contains everything docker-machine needs to know about that Docker engine:

{
	"DriverName" : "virtualbox",
	"Driver" : {
		"CPU" : -1,
		"MachineName" : "docker-local",
		"SSHUser" : "docker",
		"SSHPort" : 51648,
		"Memory" : 1024,
		"DiskSize" : 20000,
		"Boot2DockerURL" : "",
		"CaCertPath" : "C:\\Users\\...\\.docker\\machine\\certs\\ca.pem",
		"PrivateKeyPath" : "C:\\Users\\...\\.docker\\machine\\certs\\ca-key.pem",
		"SwarmMaster" : false,
		"SwarmHost" : "tcp://0.0.0.0:3376",
		"SwarmDiscovery" : ""
	},
	"StorePath" : "C:\\Users\\...\\.docker\\machine\\machines\\docker-local",
	"HostOptions" : {
		"Driver" : "",
		"Memory" : 0,
		"Disk" : 0,
		"EngineOptions" : {
			"Dns" : null,
			"GraphDir" : "",
			"Ipv6" : false,
			"Labels" : null,
			"LogLevel" : "",
			"StorageDriver" : "",
			"SelinuxEnabled" : false,
			"TlsCaCert" : "",
			"TlsCert" : "",
			"TlsKey" : "",
			"TlsVerify" : false,
			"RegistryMirror" : null
		},
		"SwarmOptions" : {
			"IsSwarm" : false,
			"Address" : "",
			"Discovery" : "",
			"Master" : false,
			"Host" : "tcp://0.0.0.0:3376",
			"Strategy" : "",
			"Heartbeat" : 0,
			"Overcommit" : 0,
			"TlsCaCert" : "",
			"TlsCert" : "",
			"TlsKey" : "",
			"TlsVerify" : false
		},
		"AuthOptions" : {
			"StorePath" : "C:\\Users\\...\\.docker\\machine\\machines\\docker-local",
			"CaCertPath" : "C:\\Users\\...\\.docker\\machine\\certs\\ca.pem",
			"CaCertRemotePath" : "",
			"ServerCertPath" : "C:\\Users\\...\\.docker\\machine\\certs\\server.pem",
			"ServerKeyPath" : "C:\\Users\\...\\.docker\\machine\\certs\\server-key.pem",
			"ClientKeyPath" : "C:\\Users\\...\\.docker\\machine\\certs\\key.pem",
			"ServerCertRemotePath" : "",
			"ServerKeyRemotePath" : "",
			"PrivateKeyPath" : "C:\\Users\\...\\.docker\\machine\\certs\\ca-key.pem",
			"ClientCertPath" : "C:\\Users\\...\\.docker\\machine\\certs\\cert.pem"
		}
	},
	"SwarmHost" : "",
	"SwarmMaster" : false,
	"SwarmDiscovery" : "",
	"CaCertPath" : "",
	"PrivateKeyPath" : "",
	"ServerCertPath" : "",
	"ServerKeyPath" : "",
	"ClientCertPath" : "",
	"ClientKeyPath" : ""
}

One thing that I want to mention about that file, since I’m only drawing the picture of the current Windows integration of Docker, is the SSHPort. You can see that it’s ‘51648’. That port is the HOST port (i.e., the port I can use from Windows to connect to the SSH server of the Docker VM).

How does this work? Well unfortunately there’s no voodoo magic at work here.

The thing with Docker on Windows is that the Docker engine runs in a VM, which makes things a bit more complicated since the onion has one more layer: Windows > VM > Docker Engine > Containers. Accessing ports exposed to the outside world when running a container will not be as straightforward as it would be when running Docker natively on a Linux box.

When docker-machine provisions the VM, it creates two network interfaces on it; a first one in NAT mode to communicate with the outside world (i.e., that’s the one we’re interested in) and a second one in VPN mode (which we won’t really care about here).

On the first interface, which I’ll further refer to as the “public” interface, docker-machine configures a single port redirection for SSH (port 51648 on the host towards port 22 on the guest). This port forwarding rule is what allows docker-machine and later the Docker client to interact with the Docker engine in the VM (I assume that the port is fixed though it might be selected randomly at creation time, I didn’t check this).

So all is nice and dandy, docker-machine provisions and configures many things for you and now that Microsoft has landed a Docker CLI for Windows, we can get up and running very quickly, interacting with the Docker engine in the VM through the Docker API, via SSH and using certificates for authentication. That’s a mouthful and it’s really NICE.. but.

Yeah indeed there’s always a but :(

Let’s say that you want to start a container hosting a simple Web server serving your pimped AngularJS+Polymer+CSS3+HTML5+whatever-cool-and-trendy-today application. Once started, you probably want to be able to access it in some way (let’s say using your browser or curl if you’re too cool).

Given our example, we can safely assume that the container will EXPOSE port 80 or the like to other containers (e.g., set in the Dockerfile). When you start that container, you’ll want to map that container port to a host port, let’s say.. 8080.

Okay curl http://localhost:8080 … 1..2..3, errr nothing :(

As you might have guessed by now, the annoying thing is that when you start a container in your Docker VM, the host that you’re mapping container ports to… is your VM.

I know it took a while for me to get there but hey, it might not be THAT obvious to everyone right? :)

I’ve mentioned earlier that docker-machine configures a port forwarding rule on the VM after creating it (for SSH, remember?). Can’t we do the same for other ports? Well the thing is that you totally can using VirtualBox’s CLI but it’ll make you understand that the current Windows integration of Docker is “nice” but clearly not all that great.

As stated, we’re going the BASH way. You can indeed achieve the same using your preferred language, whether it is PERL, Python, PowerShell or whatever.

So the first thing we’ll need to do is to make the VirtualBox CLI easily available in our little Bash world:

append_to_path /c/Program\ Files/Oracle/VirtualBox
alias virtualbox='VirtualBox.exe &'
alias vbox='virtualbox'
alias vboxmanage='VBoxManage.exe'
alias vboxmng='vboxmanage'

You’ll find the description of the append_to_path function in the previous post.

Next, we’ll add three interesting functions based on VirtualBox’s CLI; one to check whether the Docker VM is running or not and two other ones to easily add/remove a port redirection to our Docker VM:

is-docker-vm-running()
{
	echo "Checking if the local Docker VM ($DOCKER_LOCAL_VM_NAME) is running"
	vmStatusCheckResult=$(vboxmanage list runningvms)
	#echo $vmStatusCheckResult
	if [[ $vmStatusCheckResult == *"$DOCKER_LOCAL_VM_NAME"* ]]
	then
		echo "The local Docker VM is running!"
		return 0
	else
		echo "The local Docker VM is not running (or does not exist or runs using another account)"
		return 1
	fi
}


# redirect a port from the host to the local Docker VM
# call: docker-add-port-redirection rule_name host_port guest_port
docker-add-port-redirection()
{
	echo "Preparing to add a port redirection to the Docker VM"
	is-docker-vm-running
	if [ $? -eq 0 ]; then
		# vm is running
		vboxmanage modifyvm $DOCKER_LOCAL_VM_NAME --natpf1 "$1,tcp,127.0.0.1,$2, ,$3"
	else
		# vm is not running
		vboxmanage controlvm $DOCKER_LOCAL_VM_NAME natpf1 "$1,tcp,127.0.0.1,$2, ,$3"
	fi
	echo "Port redirection added to the Docker VM"
}
alias dapr='docker-add-port-redirection'


# remove a port redirection by name
# call: docker-remove-port-redirection rule_name
docker-remove-port-redirection()
{
	echo "Preparing to remove a port redirection to the Docker VM"
	is-docker-vm-running
	if [ $? -eq 0 ]; then
		# vm is running
		vboxmanage modifyvm $DOCKER_LOCAL_VM_NAME --natpf1 delete "$1"
	else
		# vm is not running
		vboxmanage controlvm $DOCKER_LOCAL_VM_NAME natpf1 delete "$1"
	fi
	echo "Port redirection removed from the Docker VM"
}
alias drpr='docker-remove-port-redirection'


docker-list-port-redirections()
{
    portRedirections=$(vboxmanage showvminfo $DOCKER_LOCAL_VM_NAME | grep -E 'NIC 1 Rule')
	for i in "${portRedirections[@]}"
	do
		printf "$i\n"
	done
}
alias dlrr='docker-list-port-redirections'
alias dlpr='docker-list-port-redirections'

Note that these functions will work whether the Docker VM is running or not. Since I’m an optimist, I don’t check whether the VM actually exists or not beforehand or if the commands did succeed (i.e., use at your own risk). One caveat is that these functions will not work if you started the Docker VM manually through Virtualbox’s GUI (because it keeps a lock on the configuration). These functions handle tcp port redirections, but adapting the code for udp is a no brainer.

The last function (docker-list-port-redirections) will allow you to quickly list the port redirections that you’ve already configured. You can do the same through Virtalbox’s UI but that’s only interesting if you like moving the mouse around and clicking on buttons, real ITers don’t do that no more (or do they? :p).

With these functions you can also easily create port redirections for port ranges using a simple loop:

for i in { 49152..65534 }; do
    dapr "rule$i" $i $i

Though I would recommend against that. You should rather add a few useful port redirections such as for port 8080, 80 and the like. These can only ‘bother’ while the Docker VM is running and if you’re trying to use redirected ports.

Another option would be to switch the “public” interface from NAT mode to bridge mode, though I’m not too fond of making my local Docker VM a ‘first’ class citizen of my LAN.

Okay, two more functions and I’m done for today :)

Port redirections are nice because they’ll allow you to expose your Docker containers to the outside world (i.e., not only your machine). Although there are situations where you might not want that. In that case, it’s useful to just connect directly to the local Docker VM.

docker-get-local-vm-ip(){
	export DOCKER_LOCAL_VM_IP=$(docker-machine ip $DOCKER_LOCAL_VM_NAME)
	echo "Docker local VM ($DOCKER_LOCAL_VM_NAME) IP: $DOCKER_LOCAL_VM_IP"
}
alias dockerip='docker-get-local-vm-ip'
alias dip='docker-get-local-vm-ip'

docker-open(){
	docker-get-local-vm-ip
	( explorer "http://$DOCKER_LOCAL_VM_IP:$*" )&	
}
alias dop='docker-open'

The ‘docker-get-local-vm-ip’ or ‘dip’ for close friends uses docker-machine to retrieve the IP it knows for the Docker VM. It’s best friend, ‘docker-open’ or ‘dop’ will simply open a browser window (you default one) towards that IP using the port specified in argument; for example ‘docker-open 8080’ will get you quickly towards your local Docker VM on port 8080.

With these functions, we can also improve the ‘docker-config-client’ function from my previous post to handle the case where the VM isn’t running:

docker-config-client()
{
	echo "Configuring the Docker client to point towards the local Docker VM ($DOCKER_LOCAL_VM_NAME)..."
	is_docker_vm_running
	if [ $? -eq 0 ]; then
		eval "$(docker-machine env $DOCKER_LOCAL_VM_NAME)"
		if [ $? -eq 0 ]; then
			docker-get-local-vm-ip
			echo "Docker client configured successfully! (IP: $DOCKER_LOCAL_VM_IP)"
		else
			echo "Failed to configure the Docker client!"
			return;
		fi
	else
		echo "The Docker client can't be configured because the local Docker VM isn't running. Please run 'docker-start' first."
	fi
}
alias dockerconfig='docker-config-client'
alias configdocker='docker-config-client'

Well that’s it for today. Hope this helps ;-)