---
title: Kubernetes the not so easy way
description: A few years ago, the simplest method to deploy and operate Kubernetes
  on Ubuntu was with conjure-up. Whether the substrate is a public cloud (AWS, Azure,
  GCP, etc) private virtualized environments (VMware) or bare metal, conjure-up will
  allow you to quickly install a fully functional, production-grade Kubernetes. But
  what if you wanted to […]
url: https://canonical.com/blog/kubernetes-the-not-so-easy-way?format=md
---

1. [Blog](https://canonical.com/blog)
2. Article

---

[Michael Iatrou](https://canonical.com/blog/author/michael-iatrou "More about Michael Iatrou")

12 October 2017

# Kubernetes the not so easy way

[cluster](https://canonical.com/blog/tag/cluster)
[conjure-up](https://canonical.com/blog/tag/conjure-up)
[Juju](https://canonical.com/blog/tag/juju)
[kubernetes](https://canonical.com/blog/tag/kubernetes)
[LXD](https://canonical.com/blog/tag/lxd)

---

Share the article

A few years ago, the simplest method to deploy and operate [Kubernetes](https://kubernetes.io/docs/getting-started-guides/ubuntu/) on Ubuntu was with [conjure-up](https://conjure-up.io/). Whether the substrate is a public cloud (AWS, Azure, GCP, etc) private virtualized environments (VMware) or bare metal, conjure-up will allow you to quickly install a fully functional, production-grade Kubernetes.

But what if you wanted to delve a bit more into the details of the process? What if you wanted to use directly the core tools of the conjure-up apparatus?

Here is the task at hand: deploy Kubernetes on a bare metal server. The control plane needs to be containerized and retain the same characteristics as a production environment (observability, scalability, upgradability, etc). The worker nodes real estate needs to be elastic, allowing to add/remove nodes on demand, without disruption of the existing services. Extra points for sane networking.

You will need a machine equipped with at least 4 CPU cores, 16GB RAM,100GB free disk space, preferably SSD and one NIC. As I am writing this, I am using [MAAS](https://maas.io/) to deploy Ubuntu 16.04.3 on such a machine. I have also configured a Linux bridge (br0) and have attached the NIC (eth0) to it, using MAAS’ [network configuration](https://docs.ubuntu.com/maas/2.2/en/nodes-commission#post-commission-configuration) capabilities. Moreover, MAAS will serve as DHCP server and DNS.

We will be using machine containers ([LXD](https://www.ubuntu.com/containers/lxd)) since they provide virtual machine operations semantics, and bare metal performance. We will also leverage [Juju](https://jujucharms.com/docs/stable/about-juju) and the [Charmed Kubernetes bundle](https://jujucharms.com/canonical-kubernetes/) — yes, the same bundle that we use for production deployments on public cloud and bare-metal.

Let’s SSH into our freshly deployed Xenial, using user ubuntu and update the critical components, LXD and Juju, to their latest stable versions:

```
$ sudo add-apt-repository ppa:juju/stable -y
$ sudo add-apt-repository ppa:ubuntu-lxc/lxd-stable -y
$ sudo apt update
$ sudo apt dist-upgrade -y
$ sudo apt install lxd juju-2.0 -y
```

Here are the versions of our tools:

We can now initialize LXD:

We have skipped the creation of a new network bridge, because we want our LXD machine containers to use the existing bridge (br0). We are modifying the default [LXD profile](http://lxd.readthedocs.io/en/latest/profiles/) accordingly:

```
$ lxc network attach-profile br0 default eth0
```

We are now ready to bootstrap our local Juju [controller](https://jujucharms.com/docs/2.2/controllers):

```
$ juju bootstrap lxd lxd-local
```

The juju controller is now instantiated! As part of the process, two new LXD profiles have been created:

```
$ lxc profile list
+-----------------+---------+
|      NAME       | USED BY |
+-----------------+---------+
| default         | 0       |
+-----------------+---------+
| juju-controller | 1       |
+-----------------+---------+
| juju-default    | 0       |
+-----------------+---------+
```

Let’s create a new [model](https://jujucharms.com/docs/2.2/models) for our k8s deployment:

```
$ juju add-model kubernetes
$ juju models
Controller: lxd-local

Model        Cloud/Region         Status     Machines  Cores  Access  Last connection
controller   localhost/localhost  available         1      -  admin   just now
default      localhost/localhost  available         0      -  admin   just now
kubernetes*  localhost/localhost  available         0      -  admin   never connected
```

Juju will automatically switch the active model to “kubernetes”. It will also create a new LXD profile, associated with this model:

```
$ lxc profile list
+-----------------+---------+
|      NAME       | USED BY |
+-----------------+---------+
| default         | 0       |
+-----------------+---------+
| juju-controller | 1       |
+-----------------+---------+
| juju-default    | 0       |
+-----------------+---------+
| juju-kubernetes | 0       |
+-----------------+---------+
```

So, Juju not only provides isolation through models, but ensures that if a model requires customized LXD containers, no other existing or future LXD profiles will be affected.

For Kubernetes, we will customize the juju-kubernetes profile to enable privileged machine containers and add an SSH key to it. Create a new YAML file `juju-lxd-profile.yaml` with the following configuration:

```
name: juju-kubernetes
config:
  user.user-data: |
    #cloud-config
    ssh_authorized_keys:
      - @@SSHPUB@@
  boot.autostart: "true"
  linux.kernel_modules: ip_tables,ip6_tables,netlink_diag,nf_nat,overlay
  raw.lxc: |
    lxc.aa_profile=unconfined
    lxc.mount.auto=proc:rw sys:rw
    lxc.cap.drop=
  security.nesting: "true"
  security.privileged: "true"
description: ""
devices:
  aadisable:
    path: /sys/module/nf_conntrack/parameters/hashsize
    source: /dev/null
    type: disk
  aadisable1:
    path: /sys/module/apparmor/parameters/enabled
    source: /dev/null
    type: disk
```

Make sure that you have generated an [SSH key pair](https://help.ubuntu.com/community/SSH/OpenSSH/Keys#Generating_RSA_Keys) for user “ubuntu”, before you execute the following one-liner:

```
$ sed -ri "s'@@SSHPUB@@'$(cat ~/.ssh/id_rsa.pub)'" juju-lxd-profile.yaml
```

Then update the juju-kubernetes LXD profile:

```
$ lxc profile edit "juju-kubernetes" < juju-lxd-profile.yaml
```

Final step, deploy Kubernetes already!

```
$ juju deploy canonical-kubernetes-101
```

You’ve noticed that I use version 101 of the canonical-kubernetes bundle. I could have as well omitted the version number and allow Juju to automatically get the latest available version. It’s going to take only a few minutes (or more, if you don’t have that SSD I mentioned earlier), before everything is successfully deployed:

All done, let’s start exploring! We need [kubectl](https://kubernetes.io/docs/user-guide/kubectl-overview/) and the “admin” credentials to interact with the cluster: Install the former as a [snap](https://snapcraft.io/) and copy the k8s config using juju:

```
$ sudo snap install kubectl --classic
kubectl 1.7.4 from 'canonical' installed
$ mkdir -p ~/.kube
$ juju scp kubernetes-master/0:config ~/.kube/config
```

For the k8s UI experience, get the URL and credentials using:

```
$ kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://172.27.29.19:443>
  name: juju-cluster
contexts:
- context:
    cluster: juju-cluster
    user: admin
  name: juju-context
current-context: juju-context
kind: Config
preferences: {}
users
- name:
  user:
    password: shannonWouldBeProud
    username: admin
```

We now have a fully operational kubernetes cluster, on bare-metal, with bridged networking, not very different from what conjure-up deploys. Most importantly, we got a glimpse of how Juju and LXD are used behind the scenes. Of course conjure-up offers much more functionality and evolves quickly: its upcoming release [adds support](http://blog.astokes.org/conjure-up-dev-summary-lxd-and-helm/) for Helm and Deis… The joy is in the journey, but keep moving fast.

Learn more about Charmed Kubernetes or [reach out to us](https://ubuntu.com/kubernetes/contact-us) about your Kubernetes challenges and use cases.

## Sign up for our newsletter

Get the latest Canonical news and updates in your inbox.

Work email:

\*I agree to receive information about Canonical's
products and services.

By submitting this form, I confirm that I have read and agree to [Canonical's Privacy Policy](https://canonical.com/legal/dataprivacy).

Sign up

## Share on

---

## Related posts

[### Deploy your Spring Boot application to production](https://canonical.com/blog/deploy-spring-application-to-production)

In this article we walk through the steps required to deploy a Spring Boot application to production using Juju and Kubernetes. The goal is to showcase the integration of the...

[Javier de la Puente](https://canonical.com/blog/author/javierdelapuente)

13 January 2026

[### Ubuntu Server: a platform made for enterprise scale](https://canonical.com/blog/ubuntu-server-a-platform-made-for-enterprise-scale)

A platform is an environment that allows software to run smoothly across the infrastructure, runtime, and application layers. The key word there is “smoothly”: a good platform...

[Rhys Knipe](https://canonical.com/blog/author/rhysknipe)

7 July 2026

[### Introducing MicroCloud Cluster Manager](https://canonical.com/blog/introducing-microcloud-cluster-manager)

Canonical introduces the beta release of MicroCloud Cluster Manager, a new way to discover, organize, and operate your MicroCloud environments from a single, unified interface.

[Miona Aleksic](https://canonical.com/blog/author/mionaalex)

20 March 2026

[### How to set up a micro lab: four principles for a reliable homelab](https://canonical.com/blog/how-to-set-up-a-reliable-homelab)

After over a decade of running a homelab, I have learned a few difficult lessons. Although it begins as a “lab,” you inevitably end up with something you want to keep. If a...

[Jake Nabasny](https://canonical.com/blog/author/slapcat)

17 March 2026
