~/blog/using-multipass-with-minikube
Published on

Using multipass with minikube

1578 words8 min read
Authors
  • avatar

Introduction

Having recently upgraded from a Macbook Pro with an intel chip, to a M1 Macbook, one of the biggest challenges was getting minikube to run without jumping through a load of hurdles or having to port-forward just to get something similar to ingress working.

After trying multiple "solutions" (podman, docker etc) this post will describe how to use multipass to run minikube and then configuring your local machine to use it.

Updates and Edits

2022-08-02

After getting everything set-up as per the original article, I kept encountering weird edge cases where some amd64 images just wouldn't start; there were usually no errors, but the containers wouldn't output any logs when I knew there should have been some.

I tried a lot of different things, but what eventually solved my issue was to NOT install most of the packages needed to get this working (e.g. qemu-user-static, gcc-10-base) and instead use the tonistiigi/binfmt docker image.

My cloud-init.yaml file became the following:

---
package_update: true

packages:
  - curl
  - conntrack

## ...

# runcmd runs after the start-up process has completed
runcmd:
  # Install minikube (note: because I'm using a M1, I use the ARM64 architecture)
  - curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm64
  - sudo install minikube-linux-arm64 /usr/local/bin/minikube
  # Install Docker
  - sudo mkdir -p /etc/apt/keyrings
  - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
  - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  - sudo apt-get update
  - sudo apt-get -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin
  # Add ubuntu user to the docker group (so you don't need to use sudo with docker commands)
  - sudo usermod -aG docker ubuntu
  # Ensure ubuntu owns the home directory
  - chown -R ubuntu:ubuntu /home/ubuntu
  # Install binfmt abstraction layer
  - docker run --privileged --rm tonistiigi/binfmt --install all

Multipass VM Setup

The rest of this tutorial assumes that you have installed multipass.

Firstly, we need to spin up a multipass VM. We'll create it with six CPUs, 10GB memory and 100GB disk. Feel free to adjust these as your use case requires.

multipass launch --name remote-minikube --cpus=6 --mem=10G --disk=100G Launched: remote-minikube

Next, we'll exec into the VM to install/configure what's needed

multipass exec remote-minikube -- /bin/bash</code>

The following commands are run while on the VM:

# Install minikube (note: because I'm using a M1, I use the ARM64 architecture)
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm64
sudo install minikube-linux-arm64 /usr/local/bin/minikube

# Install Docker
sudo apt-get update

sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

sudo mkdir -p /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

# Add your user to the docker group (so you don't need to use sudo with docker commands)
# Note: You'll need to log out/in for the changes to take effect.
sudo usermod -aG docker ${USER}

# Install conntrack
sudo apt-get install conntrack

# Install qemu-user-static to allow amd64 images to be used
sudo apt-get install qemu-user-static

# (Optional) install additional packages that may be needed for running amd64 images that use the C runtime
sudo apt-get install gcc-10-base libc6 libcrypt1 libgcc-s1 libidn2-0 libunistring2 zlib1g

# Run minikube with the bare-metal drive
minikube start  --extra-config=apiserver.service-node-port-range=30000-39999 --kubernetes-version 1.18.20 --driver=none

Confirm everything is working by running minikube status

minikube status minikube type: Control Plane host: Running kubelet: Running apiserver: Running
kubeconfig: Configured

Set-up local environment to use the VM minikube instance

Now our VM is running minikube, we need to configure our local environment to be able to talk to it.

While on the VM, grab the contents of ~/.kube/config and add it to your local ~/.kube/config. Note: If you're adding to an already existing config file, ensure you copy over the cluster, context and user. Ensure you change the folder path (/home/ubuntu) if appropriate.

Then, grab the contents of /home/ubuntu/.minikube/ca.crt, /home/ubuntu/.minikube/profiles/minikube/client.crt and /home/ubuntu/.minikube/profiles/minikube/client.key and add them to your local machine at the path that you've defined in ~/.kube/config.

Now, set your context to minikube using kubectx minikube.

You should now be able to query the pods, use kubectl apply etc.

kubectl get pods No resources found in default namespace

For extra convenience you can get the IP of the VM (by running ip a on the VM, or getting it from multipass list) and adding it to /etc/hosts under a name of your choice.

[vm ip]   minikube

Automating all the steps

Luckily for us, multipass supports cloud-init out of the box, so we can automate a lot of the initial set-up with a configuration file.

First, create a cloud-init.yaml file:

---
package_update: true

packages:
  - ca-certificates
  - curl
  - conntrack
  - gnupg
  - lsb-release
  - qemu-user-static
  - gcc-10-base
  - libc6
  - libcrypt1
  - libgcc-s1
  - libidn2-0
  - libunistring2
  - zlib1g

## Note: this isn't required but is provided as a useful reference
## for what cloud-init can do
write_files:
  - content: |-
      my dummy content

    owner: ubuntu:ubuntu
    path: /home/ubuntu/file.txt
    permissions: '0644'

## bootcmd happens at the beginning of the start-up process
## Note: this isn't required but is provided as a useful reference
## for what cloud-init can do
bootcmd:
  - echo $(whoami) > /root/boot.txt

# runcmd runs after the start-up process has completed
runcmd:
  # Install minikube (note: because I'm using a M1, I use the ARM64 architecture)
  - curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm64
  - sudo install minikube-linux-arm64 /usr/local/bin/minikube
  # Install Docker
  - sudo mkdir -p /etc/apt/keyrings
  - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
  - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  - sudo apt-get update
  - sudo apt-get -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin
  # Add ubuntu user to the docker group (so you don't need to use sudo with docker commands)
  - sudo usermod -aG docker ubuntu
  # Ensure ubuntu owns the home directory
  - chown -R ubuntu:ubuntu /home/ubuntu

Now, coupling this configuration file with a couple make recipes we can automate almost everything:

## Makefile
SHELL:=/bin/bash -o pipefail

VM_EXISTS:=$(shell multipass list | grep '^minikube\s' | awk '{print $$1}')
.PHONY: multipass-minikube-start
multipass-minikube-start:
ifeq ("$(VM_EXISTS)", "")
	multipass launch --name minikube --cpus=6 --mem=10G --disk=100G --cloud-init ./cloud-init.yaml
	multipass exec minikube -- minikube start --extra-config=apiserver.service-node-port-range=30000-39999 --kubernetes-version 1.18.20 --driver=none
else
	@echo "minikube vm is already running"
endif

.PHONY: multipass-configure
multipass-configure: multipass-minikube-start
	# Create the minikube directory if it doesn't exist
	mkdir -p ~/.minikube/profiles/minikube
	# Ensure the correct files are copied over from the VM
	multipass transfer minikube:/home/ubuntu/.minikube/ca.crt /Users/${USER}/.minikube/ca.crt
	multipass transfer minikube:/home/ubuntu/.minikube/profiles/minikube/client.crt /Users/${USER}/.minikube/profiles/minikube/client.crt
	multipass transfer minikube:/home/ubuntu/.minikube/profiles/minikube/client.key /Users/${USER}/.minikube/profiles/minikube/client.key
	# Copy over the VM kube config and place it in a new file
	multipass transfer minikube:/home/ubuntu/.kube/config ~/.kube/remote-config
	# Update the contents for the current user
	sudo sed -i "" 's/home\/ubuntu/Users\/${USER}/g' ~/.kube/remote-config
	# Change the name to remote-minikube to avoid conflicts
	sudo sed -i "" "s/name: minikube/name: remote-minikube/g" ~/.kube/remote-config
	sudo sed -i "" "s/cluster: minikube/cluster: remote-minikube/g" ~/.kube/remote-config
	sudo sed -i "" "s/user: minikube/user: remote-minikube/g" ~/.kube/remote-config
	# Remove current-context to avoid conflicts
	sudo sed -i "" '/^current-context/d' ~/.kube/remote-config
	# Use the new file
	export KUBECONFIG="${HOME}/.kube/config:${HOME}/.kube/remote-config"

Then, we can use kubectl --context=remote-minikube to interact with the remote cluster or we can change our context permanently with kubectx remote-minikube.

Tip: if needed, the default location for cloud-init logs will be /var/log/cloud-init-output.log, on the VM.