Changing Container Runtime from Docker to containerd
The dockershim
has been announced as deprecated since Kubernetes v1.20. And
Kubernetes v1.23 is about to step into its EOL after the end of this month. So
it’s time to move forward! Let’s upgrade the cluster to v1.24. This article is
solely for the personal record about changing container runtimes to upgrade
Kubernetes from v1.23 to v1.24.
Prerequisites
Firstly, make sure what container runtime you are using:
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-master.internal.zespre.com Ready control-plane,master 2y97d v1.23.15 192.168.88.111 <none> Ubuntu 18.04.5 LTS 4.15.0-194-generic docker://20.10.7
k8s-worker-1.internal.zespre.com Ready <none> 2y97d v1.23.15 192.168.88.112 <none> Ubuntu 18.04.5 LTS 4.15.0-194-generic docker://20.10.7
k8s-worker-2.internal.zespre.com Ready <none> 2y97d v1.23.15 192.168.88.113 <none> Ubuntu 18.04.5 LTS 4.15.0-194-generic docker://20.10.7
k8s-worker-3.internal.zespre.com Ready <none> 2y50d v1.23.15 192.168.88.114 <none> Ubuntu 18.04.5 LTS 4.15.0-194-generic docker://20.10.7
For my environment, it is docker://20.10.7
. So it’s a must to change to other
CRI-compliant container runtimes.
Drain the node that you want to work on:
kubelet drain <node> --ignore-daemonsets --delete-emptydir-data
Stop the kubelet
and docker
on the node:
sudo systemctl stop kubelet.service
sudo systemctl disable docker.service --now
Installation
containerd
Install containerd
binaries and their corresponding service file:
$ wget https://github.com/containerd/containerd/releases/download/v1.6.18/containerd-1.6.18-linux-amd64.tar.gz
$ sudo tar -zxvf containerd-1.6.18-linux-amd64.tar.gz -C /usr/local
$ sudo mkdir -p /usr/local/lib/systemd/system/
$ sudo curl -sfL https://raw.githubusercontent.com/containerd/containerd/main/containerd.service \
-o /usr/local/lib/systemd/system/containerd.service
sudo systemctl daemon-reload
sudo systemctl enable containerd.service --now
runc
wget https://github.com/opencontainers/runc/releases/download/v1.1.4/runc.amd64
sudo install -m 755 runc.amd64 /usr/local/sbin/runc
CNI
wget https://github.com/containernetworking/plugins/releases/download/v1.2.0/cni-plugins-linux-amd64-v1.2.0.tgz
sudo mv /opt/cni/bin /opt/cni/bin.bak
sudo mkdir -p /opt/cni/bin
sudo tar -zxvf cni-plugins-linux-amd64-v1.2.0.tgz -C /opt/cni/bin
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo systemctl restart containerd.service
Configure kubelet to Use containerd
Basically, there are two places to modify if you provision the node with
kubeadm
at the beginning:
- The
/var/lib/kubelet/kubeadm-flags.env
file - The
kubeadm.alpha.kubernetes.io/cri-socket
annotation of the node
In /var/lib/kubelet/kubeadm-flags.env
, append two flags to make kubelet
use
containerd
as the new container runtime:
--container-runtime=remote
--container-runtime-endpoint=unix:///run/containerd/containerd.sock
KUBELET_KUBEADM_ARGS="--network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.2 --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
For the annotation in the node’s resource, a similar change can be done via
kubectl annotate node <node> kubeadm.alpha.kubernetes.io/cri-socket=unix:///run/containerd/containerd.sock --overwrite=true
Then we’re ready to start the kubelet
again with the new container runtime!
sudo systemctl start kubelet.service
After a while, you should see the container runtime of the target node becomes
something like containerd-1.6.18
:
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-master.internal.zespre.com Ready control-plane,master 2y98d v1.23.15 192.168.88.111 <none> Ubuntu 18.04.5 LTS 4.15.0-194-generic docker://20.10.7
k8s-worker-1.internal.zespre.com Ready,SchedulingDisabled <none> 2y98d v1.23.15 192.168.88.112 <none> Ubuntu 18.04.5 LTS 4.15.0-194-generic containerd://1.6.18
k8s-worker-2.internal.zespre.com NotReady <none> 2y98d v1.23.15 192.168.88.113 <none> Ubuntu 18.04.5 LTS 4.15.0-194-generic docker://20.10.7
k8s-worker-3.internal.zespre.com Ready <none> 2y51d v1.23.15 192.168.88.114 <none> Ubuntu 18.04.5 LTS 4.15.0-194-generic docker://20.10.7
And you can uncordon the node:
kubectl uncordon <node>
Iterate these steps through all the nodes in the cluster.
- Drain the node
- Install containerd, runc, CNI binaries on the node
- Configure kubelet with the new container runtime
- Uncordon the node
Cleanup
If things are going well, you can remove the old container runtime since it’s no
longer needed. In my case, it is the [docker.io](http://docker.io)
package to
remove:
$ sudo apt purge docker.io
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
bridge-utils cgroupfs-mount containerd pigz runc ubuntu-fan
Use 'sudo apt autoremove' to remove them.
The following packages will be REMOVED:
docker.io*
0 upgraded, 0 newly installed, 1 to remove and 70 not upgraded.
After this operation, 193 MB disk space will be freed.
Do you want to continue? [Y/n]
(Reading database ... 141670 files and directories currently installed.)
Removing docker.io (20.10.7-0ubuntu5~18.04.3) ...
'/usr/share/docker.io/contrib/nuke-graph-directory.sh' -> '/var/lib/docker/nuke-graph-directory.sh'
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
(Reading database ... 141464 files and directories currently installed.)
Purging configuration files for docker.io (20.10.7-0ubuntu5~18.04.3) ...
debconf: unable to initialize frontend: Dialog
debconf: (Dialog frontend requires a screen at least 13 lines tall and 31 columns wide.)
debconf: falling back to frontend: Readline
Nuking /var/lib/docker ...
(if this is wrong, press Ctrl+C NOW!)
+ sleep 10
+ rm -rf /var/lib/docker/builder /var/lib/docker/buildkit /var/lib/docker/containers /var/lib/docker/image /var/lib/docker/network /var/lib/docker/nuke-graph-directory.sh /var/lib/docker/overlay2 /var/lib/docker/plugins /var/lib/docker/runtimes /var/lib/docker/swarm /var/lib/docker/tmp /var/lib/docker/trust /var/lib/docker/volumes
dpkg: warning: while removing docker.io, directory '/etc/docker' not empty so not removed
Post-configs
To make sure things won’t break after node reboots, we need to persist some configurations currently effective on the system. Please do the following steps on every node if there’s no similar setup existed.
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
Troubleshooting
PLEG is not healthy
When doing the work on the first node, I noticed that the second worker node became unready even though I didn’t drain the node. And the reason for the unready state is shown below:
Ready Unknown Mon, 27 Feb 2023 13:55:37 +0800 Mon, 27 Feb 2023 13:54:29 +0800 NodeStatusUnknown [container runtime is down, PLEG is not healthy: pleg was
ast seen active 7m27.961229215s ago; threshold is 3m0s[]
The load is extremely high on the worker node:
$ uptime
14:56:34 up 135 days, 2:36, 1 user, load average: 190.44, 174.46, 169.43