These are essentially my notes on setting up a single-node Kubernetes cluster at home. Every time I set up an instance I have to dig through lots of posts, articles and documentation, much of it contradictory or out-of-date. Hopefully this distilled and much-abridged version will be helpful to someone else.
This follows https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/ for the initial cluster setup. You should look there for more detail. I strongly recommend that you read about the various projects and verify that this information is still valid. Things change pretty quickly. I’ve deliberately not included a lot of detail in the post beyond what I did and the commands to use.
Requirements
You can work around these, but it’s recommended to have at least:
- 4GB RAM
- 2 cores
Decisions
Kubernetes is an open ended platform which lets you make a lot of decisions yourself. Unfortunately it also requires you to make them (see projects like k3s to avoid some of that). I decided to use:
- Debian stable (stretch), because why would you ever not use Debian?
- Docker as the container runtime. Next time I’ll try something else (CRI-O), but Docker is familiar so it’s easy to diagnose any issues.
- Flannel for networking. No particular reason; it has a nice name.
- No Helm. Helm is great, but it hides away details and I’m interested in details.
- nginx for ingress. There are a bunch of possibilities, but nginx is the common choice and integrates nicely with lots of other things.
- Let’s Encrypt certificates validated by Cloudflare DNS. I use Cloudflare already, so this was an easy choice. DNS validation allows for wildcards and for internal hosts.
- Single node. There are a lot of cool things about Kubernetes that you don’t get with a single node, but what I’m setting up here is for home. You can easily add more nodes by following the instructions
kubeadm
gives you when it runs.
Enable net.bridge.bridge-nf-call-iptables
This is required by Flannel and possibly other networking options. You can read more at https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#pod-network
This was actually already set on my machine, but it doesn’t hurt to be explicit.
cat > /etc/sysctl.d/20-bridge-nf.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system
Install Docker with recommended settings
mkdir /etc/docker
cat > /etc/docker/daemon.json <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
apt-get update
apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg2
curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
echo 'deb [arch=amd64] https://download.docker.com/linux/debian stretch stable' > /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y --no-install-recommends docker-ce
The --no-install-recommends
will avoid pulling in stuff you don’t need,
including the aufs DKMS package.
Install Kubernetes components
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl
The xenial
in the APT source is correct. That’s the repo they seem to
update, and these are Go binaries anyway so they’re self-contained.
Run kubeadm
to set up the cluster
The --pod-network-cidr
setting is required by Flannel, which I chose to use
for pod networking.
kubeadm init --pod-network-cidr=10.244.0.0/16
That’s it. Neat, huh?
There is still a bunch of work to do to make the cluster actually useful. You
can do most of the rest of this as a non-root user. Follow the instructions
kubeadm
gave you to copy the credential as your regular user.
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
And as a handy extra tip, you’ll want completion:
source <(kubectl completion bash)
Install Flannel for pod networking
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/a70459be0084506e4ec919aa1c114638878db11b/Documentation/kube-flannel.yml
kubectl get pods --all-namespaces
You should see coredns pods come to life if all is well.
Untaint the master so you can run pods
kubectl taint nodes --all node-role.kubernetes.io/master-
At this point you can run pods and expose them with services. If that’s all you need, you’re done! Next I set up an nginx-ingress and cert-manager to allow for hostname-based HTTPS ingress with Let’s Encrypt certificates.
Set up nginx-ingress
I set up the nginx-ingress with host networking so I could expose my cluster’s services via ports 80 and 443 on the host. You can also expose it via a NodePort or LoadBalancer, but this worked well for my simple setup.
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml
cat > nginx-host-networking.yaml <<EOF
spec:
template:
spec:
hostNetwork: true
EOF
kubectl -n ingress-nginx patch deployment nginx-ingress-controller --patch="$(<nginx-host-networking.yaml)"
You can now create Ingresses to make your services accessible externally. If you expose them via HTTP they should just work. HTTPS will work too, but with a self-signed certificate.
Set up cert-manager
This is a bit fiddly, which isn’t helped by the fact cert-manager’s documentation is a bit out of date. Luckily, I read the source so you don’t have to!
The cert-manager tool is pretty neat. It lets you provision Let’s Encrypt certificates either manually (useful for a fallback wildcard certificate) or automatically based on annotation on your Ingress.
I have overridden the nameservers used for verifying dns01 records because I use split-horizon DNS.
kubectl create ns cert-manager
kubectl config set-context --current --namespace=cert-manager
kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.7/deploy/manifests/cert-manager.yaml
cat > external-dns.yaml <<EOF
# Add dns01 recursive nameservers
spec:
template:
spec:
containers:
- name: cert-manager
args:
- --cluster-resource-namespace=\$(POD_NAMESPACE)
- --leader-election-namespace=\$(POD_NAMESPACE)
- --dns01-recursive-nameservers=1.1.1.1:53,8.8.8.8:53
EOF
kubectl -n cert-manager patch deployment cert-manager --patch="$(<external-dns.yaml)"
Set up a ClusterIssuer to issue certificates
You’ll need your Cloudflare API key to put into a Kubernetes secret.
kubectl -n cert-manager create secret generic cloudflare-api-key --from-literal=api-key=XXXXX
cat > clusterissuer-cf.yaml <<EOF
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt
dns01:
providers:
- name: cf-dns
cloudflare:
email: [email protected]
apiKeySecretRef:
name: cloudflare-api-key
key: api-key
EOF
kubectl apply -f clusterissuer-cf.yaml
Create a wildcard certificate
I created a wildcard certificate in the ingress-nginx namespace so I can use it as the default.
cat > wildcard-cert.yaml <<EOF
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: ingress-nginx
spec:
secretName: wildcard-example-com
issuerRef:
kind: ClusterIssuer
name: letsencrypt
commonName: '*.example.com'
dnsNames:
- example.com
acme:
config:
- dns01:
provider: cf-dns
domains:
- '*.example.com'
- 'example.com'
EOF
kubectl apply -f wildcard-cert.yaml
You can watch the progress of the certificate generation. Tab completion should find the names of your orders and challenges for you.
kubectl describe order ...
kubectl describe challenge ...
Set this as the default certificate for the ingress controller as follows:
kubectl -n ingress-nginx edit deployment nginx-ingress-controller
Add:
- --default-ssl-certificate=$(POD_NAMESPACE)/wildcard-example-com
to the list of args to the nginx-ingress-controller
container. You can do
this with a patch if you prefer.
Try it out!
You should have DNS set up for, in this example, *.example.com
pointing to you Kubernetes host.
Deploy a simple nginx service to test things out.
kubectl create ns testland
kubectl config set-context --current --namespace=testland
kubectl create deployment nginx --image nginx
kubectl expose deployment nginx --port 80
cat > nginx-ingress.yaml <<EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
spec:
rules:
- host: nginx.example.com
http:
paths:
- path: /
backend:
serviceName: nginx
servicePort: 80
tls:
- hosts:
- nginx.example.com
EOF
kubectl apply -f nginx-ingress.yaml
You should be able to visit http://nginx.example.com/ in your browser and get redirected to HTTPS with a real, live certificate.
I’ll run through the process again to check for any errors and update this post.