Немного хардкора: как поднять Kubernetes на двух старых ноутбуках с Gentoo

Хочу рассказать об интересном эксперименте, суть которого заключалась в развертывании и настройке Kubernetes на двух старых ноутбуках — один из них, кроме того, был с процессором на архитектуре i386. В качестве теоретической основы использовалось руководство Kubernetes The Hard Way, которое по ходу дела пришлось немного доработать, а в качестве системы на хостах — Gentoo (да, вам не показалось). Давайте погрузимся в этот увлекательный хардкор!

1cf9fee26671ba48f34b79b86084b6db.png

Наверное, многие слышали про полезный и по-своему легендарный репозиторий Келси Хайтауэра — Kubernetes The Hard Way, инструкцию по развертыванию кластера K8s без готовых скриптов и утилит. В ней объясняется, как вручную запустить Kubernetes на Google Cloud Platform (GCP). Но даже сам автор утверждает, что стоимость такого кластера будет больше, чем 300 USD, которые даются в подарок за регистрацию на сервисе.

А что, если у вас есть один или несколько старых ноутбуков, которые пылятся на полке, и вы хотите поднять-такиKubernetes своими руками и бесплатно?

Попробуем!

Важно: эту статью стоит воспринимать лишь как дополнение к оригинальному руководству Келси Хайтауэра, так как практически всё, что им описывается, применимо не только к GCP, но и к любому другому железу или виртуальным машинам, хотя и с некоторыми оговорками.

Исходные данные

Для экспериментов у меня было два ноутбука на базе Gentoo Linux:

  • Dell G3 3590 на базе Intel Core i5–9300H (архитектура AMD64) (далее — просто dell);

  • HP Compaq 6720s на базе Intel Core2 Duo (архитектура i686) (далее — hpcom).

Они стали узлами кластера. Операционная система ставилась по официальной документации для amd64 и x32. Но ядро собиралось с несколькими дополнительными настройками для IPSet и Docker. Для kube-proxy были включены параметры ядра NETFILTER_XT_MATCH_COMMENT и NETFILTER_XT_MATCH_STATISTIC. Также в процессе установки я столкнулся с парой трудностей, решить которые помог форум сообщества Gentoo. На всякий случай оставлю ссылки: черный экран сразу после GRUB и ошибка с ​​non-PAE kernel.

Что еще:

  • самый обычный роутер от провайдера;

  • основной ноутбук, с которого удаленно проводились все работы;

  • сильное желание повозиться с Kubernetes.

Как правильно пользоваться руководством

Выше я отметил, что внес лишь небольшие правки в оригинальное руководство по Kubernetes. Поэтому еще раз подчеркну, что в статье описываю только эти правки, указывая места, где нужно отступить от исходных инструкций. В остальных случаях даю ссылки на оригинальные разделы руководства.

Установка Kubernetes

Prerequisites

В этом разделе ничего не делаем, поэтому его можно со спокойной душой пропустить.

Installing the Client Tools

Здесь никаких изменений: делаем так, как написано.

Provisioning Compute Resources

Эту страницу официального руководства можно пропустить, а вместо нее для упрощения дальнейшей работы записать локальные IP-адреса будущих узлов кластера (у меня получилось так: hpcom — 192.168.1.71, dell — 192.168.1.253).

Для удобства запишем эти адреса в файлик node_list.txt:

cat <

Для имитации Load Balancer«а на рабочей машине можно установить и развернуть NGINX. Затем добавить в конце конфигурационного файла nginx.conf строку include passthrough.conf (на Linux стандартная директория для конфигов NGINX — /etc/nginx/, а на macOS при установке через brew — /opt/homebrew/etc/).

Далее рядом создаем файл passthrough.conf. Сделать это можно такой командой (не забудьте поменять путь до NGINX-директории и путь до файла с хостами на ваши):

cat <

Это поможет нам создать простейшую имитацию High-Availability-кластера (HA). Для обучения большего и не нужно.

Provisioning a CA and Generating TLS Certificates

В этом разделе выполняем все в точности до пункта The Kubelet Client Certificates. Теперь нам пригодятся записанные ранее IP-адреса узлов будущего кластера:

for instance in $(cat node_list.txt | cut -d" " -f1); do
cat > ${instance}-csr.json <

Далее идем по исходной инструкции вплоть до пункта The Kubernetes API Server Certificate. Здесь меняем вводимые строки на такие:

{

KUBERNETES_HOSTNAMES=kubernetes,kubernetes.default,kubernetes.default.svc,kubernetes.default.svc.cluster,kubernetes.svc.cluster.local

cat > kubernetes-csr.json <

В следующем разделе, The Service Account Key Pair, выполняем все по инструкции, а в Distribute the Client and Server Certificates просто разносим все нужное по узлам с помощью scp. Чтобы это заработало, на рабочей машине создаем файл ~/.ssh/config с помощью команды:

cat node_list.txt | xargs -n2 bash -c 'echo -e "Host $0\n\tHostname $1\n\tUser <Ваш пользователь на узлах>"' | tee ~/.ssh/config

Копируем файлы на узлы:

{
for instance in $(cut -d" " -f1 node_list.txt); do
  scp node_list.txt ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem service-account-key.pem service-account.pem ca.pem ${instance}-key.pem ${instance}.pem node_list.txt \ # Также переносим файл с IP-адресами на все узлы для дальнейшей работы.
${instance}:~/
done
}

В моем случае оба ноутбука будут выступать в качестве master«а и рабочего узла одновременно, поэтому я скопировал на них не только сертификаты для control plane, но и для рабочих узлов.

Generating Kubernetes Configuration Files for Authentication

Здесь пропускаем пункт про Kubernetes Public IP Address, а в The kubelet Kubernetes Configuration File вместо исходной команды выполняем:

for instance in $(cat node_list.txt | cut -d" " -f1); do
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:443 \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-credentials system:node:${instance} \
    --client-certificate=${instance}.pem \
    --client-key=${instance}-key.pem \
    --embed-certs=true \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:node:${instance} \
    --kubeconfig=${instance}.kubeconfig

  kubectl config use-context default --kubeconfig=${instance}.kubeconfig
done

И аналогично для The kube-proxy Kubernetes Configuration File:

 kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://127.0.0.1:443 \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config set-credentials system:kube-proxy \
    --client-certificate=kube-proxy.pem \
    --client-key=kube-proxy-key.pem \
    --embed-certs=true \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:kube-proxy \
    --kubeconfig=kube-proxy.kubeconfig

  kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig

Далее следуем инструкции до момента копирования файлов на узлы. Вместо этого делаем так:

{
for instance in $(cat node_list.txt | cut -d" " -f1); do
  scp ${instance}.kubeconfig kube-proxy.kubeconfig admin.kubeconfig kube-controller-manager.kubeconfig kube-scheduler.kubeconfig ${instance}:~/
done
}

Generating the Data Encryption Config and Key

В этом разделе выполняем всё по инструкции, за исключением копирования файлов по узлам. Копируем так:

{
for instance in $(cat node_list.txt | cut -d" " -f1); do
  scp encryption-config.yaml ${instance}:~/
done
}

Bootstrapping the etcd Cluster

Вместо оригинальной инструкции вначале заходим на узел (выполнять для каждого узла):

ssh hpcom

Устанавливаем следующие необходимые утилиты:

sudo emerge --sync && emerge --ask dev-vcs/git sys-devel/make net-misc/wget net-misc/curl dev-lang/go app-shells/bash-completion

Скачиваем и собираем etcd:

git clone https://github.com/etcd-io/etcd.git
cd etcd
git checkout v3.4.15
go mod vendor
./build
sudo mv bin/etcd* /usr/local/bin/

Конфигурируем собранный etcd. Устанавливаем сертификаты:

{
  sudo mkdir -p /etc/etcd /var/lib/etcd
  sudo chmod 700 /var/lib/etcd
  sudo cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd/
}

Получаем IP-адрес и имя узла:

INTERNAL_IP=$(grep $(hostname -s) node_list.txt | cut -d' ' -f2)
ETCD_NAME=$(hostname -s)

И формируем Unit для systemd:

cat <

Теперь возвращаемся к исходной инструкции и выполняем все, что идет после пункта Start the etcd Server включительно.

Bootstrapping the Kubernetes Control Plane

Вначале заходим на узел (выполнять для каждого узла):

ssh hpcom

Создаем каталог для конфигурации:

sudo mkdir -p /etc/kubernetes/config

Скачиваем исходники Kubernetes и собираем control plane:

git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes && git checkout v1.21.0
make kube-scheduler kube-apiserver kube-controller-manager kubectl
cd _output/bin
chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl
sudo mv kube-apiserver kube-controller-manager kube-scheduler kubectl /usr/local/bin/

Переносим сертификаты:

{
sudo mkdir -p /var/lib/kubernetes/
cd && sudo mv ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
    service-account-key.pem service-account.pem \
    encryption-config.yaml /var/lib/kubernetes/
}

Для имитирования HA ставим NGINX (перед этим создав файл для USE-флагов дополнительных модулей Gentoo):

cat <

В nginx.conf в блок http добавляем проверку работы сервера:

server {
		listen 127.0.0.1:80;
		server_name localhost;

		access_log /var/log/nginx/localhost.access_log main;
		error_log /var/log/nginx/localhost.error_log info;

		location /nginx_status {
	        	stub_status on;

        		access_log off;
        		allow 127.0.0.1;
        		deny all;
	}

И также в конец файла дописываем строку с includeпо аналогии с Provisioning Compute Resources выше:

echo include passthrough.conf; | sudo tee -a /etc/nginx/nginx.conf

Затем, как на рабочей машине, создаем рядом файл passthrough.conf:

cat <

Конфигурируем API-сервер. Получаем нужные IP-адреса:

INTERNAL_IP=$(grep $(hostname -s) node_list.txt | cut -d' ' -f2)
KUBERNETES_PUBLIC_ADDRESS=127.0.0.1

Создаем Unit для systemd:

cat <

Далее выполняем всё из оригинальной инструкции до пункта The Kubernetes Frontend Load Balancer. Устанавливать и настраивать balancer не требуется, хотя при желании можно развернуть MetalLB после окончательной настройки кластера. Блок Verification также можно пропустить, так как мы уже развернули NGINX.

Проверить работоспособность API-сервера можно так:

curl --cacert /var/lib/kubernetes/ca.pem  https://127.0.0.1:443/healthz

Bootstrapping the Kubernetes Worker Nodes

Заходим на каждый узел и устанавливаем необходимые пакеты:

ssh hpcom
sudo emerge --ask net-misc/socat net-firewall/conntrack-tools net-firewall/ipset sys-fs/btrfs-progs

Создаем нужные каталоги, скачиваем и собираем нужные бинарники:

sudo mkdir -p \
  /etc/cni/net.d \
  /opt/cni/bin \
  /var/lib/kubelet \
  /var/lib/kube-proxy \
  /var/lib/kubernetes \
  /var/run/kubernetes

crictl для i386:

wget -q --show-progress --https-only --timestamping https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.21.0/crictl-v1.21.0-linux-386.tar.gz
tar -xvf crictl-v1.21.0-linux-386.tar.gz && sudo mv crictl /usr/local/bin/

И для amd64:

https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.21.0/crictl-v1.21.0-linux-amd64.tar.gz
tar -xvf crictl-v1.21.0-linux-amd64.tar.gz && sudo mv crictl /usr/local/bin/

runc:

cd && git clone git@github.com:opencontainers/runc.git && cd runc && git checkout v1.0.0-rc93
make
sudo make install

CNI plugins:

cd && git clone git@github.com:containernetworking/plugins.git && cd plugins && git checkout v0.9.1
./build_linux.sh
sudo mv bin/* /opt/cni/bin/

containerd requirements для i386:

cd && wget -c https://github.com/google/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_32.zip
sudo unzip protoc-3.11.4-linux-x86_32.zip -d /usr/local

И для amd64:

cd && wget -c 
https://github.com/google/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip
sudo unzip protoc-3.11.4-linux-x86_64.zip -d /usr/local

containerd:

cd && git clone git@github.com:containerd/containerd.git && cd containerd 
git clone git@github.com:containerd/containerd.git && cd containerd/
make 
sudo make install

K8s:

cd ~/kubernetes && git checkout v1.21.0
make kubelet kube-proxy
cd _output/bin
chmod +x kubelet kube-proxy
sudo mv kubelet kube-proxy /usr/local/bin/

Конфигурируем CNI-плагин:

POD_CIDR=10.200.$(grep -n $(hostname -s) node_list.txt | cut -d':' -f1).0/24
cat <

Конфигурируем containerd (используется другая версия конфига, в отличие от оригинального репозитория):

sudo mkdir -p /etc/containerd/

cat << EOF | sudo tee /etc/containerd/config.toml
version = 2
[plugins."io.containerd.grpc.v1.cri"]
  sandbox_image = "docker.io/alexeymakhonin/pause:i386" # k8s.gcr.io/pause:3.7 для amd64

  [plugins."io.containerd.grpc.v1.cri".containerd]
    snapshotter = "overlayfs"
    [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
      runtime_type = "io.containerd.runtime.v1.linux"
      runtime_engine = "/usr/local/sbin/runc"
      runtime_root = ""
EOF

Контейнер pause (он же sandbox_image в конфиге containerd) от K8s не собирается для i386-архитектуры. Поэтому его можно собрать самостоятельно или воспользоваться готовым образом из моего Docker Hub«а: docker.io/alexeymakhonin/pause: i386.

Если вы решили пойти первым путем, на узле с i386-архитектурой нужно собрать бинарник:

cd ~/kubernetes/build/pause && mkdir bin
gcc -Os -Wall -Werror -static -DVERSION=v3.6-bbc2dbb9801 -o bin/pause-linux-i386 linux/pause.c

Полученный бинарник и всю директорию следует скопировать на рабочий ноутбук с Docker Buildx, на котором будет собираться образ:

scp -r hpcom:~/kubernetes/build/pause ./ && cd pause
docker buildx build --pull --output=type=docker --platform linux/i386                 -t docker.io//pause:i386 --build-arg BASE=scratch --build-arg ARCH=i386 .
docker push docker.io//pause:i386

Затем в конфигурационном файле containerd нужно поменять sandbox_image на собранный образ.

Теперь создаем Unit containerd для systemd:

cat <

Сконфигурировать kubelet и kube-proxy можно по оригинальной инструкции. Однако стоит помнить, что ca.pem уже скопирован в /var/lib/kubernetes, поэтому команда mv упадет с ошибкой.

Проверить работоспособность узла можно так:

kubectl get node --kubeconfig ~/admin.kubeconfig

Configuring kubectl for Remote Access

Из этого раздела нас интересует только самое начало — пункт The Admin Kubernetes Configuration File. Выполняем его:

{
  KUBERNETES_PUBLIC_ADDRESS=127.0.0.1

  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:443

  kubectl config set-credentials admin \
    --client-certificate=admin.pem \
    --client-key=admin-key.pem

  kubectl config set-context kubernetes-the-hard-way \
    --cluster=kubernetes-the-hard-way \
    --user=admin

  kubectl config use-context kubernetes-the-hard-way
}

Provisioning Pod Network Routes

Эти инструкции можно пропустить.

Deploying the DNS Cluster Add-on

Здесь инструкции придется разделить на две части. Для машины с архитектурой amd64 выполняем всё, как в оригинале, а вот для i386 придется пойти другим путем. Обусловлено это тем, что официального образа coredns для i386 не существует, поэтому его придется собирать самим (или взять уже собранный мной). Чтобы собрать образ самостоятельно, на машине с Docker Buildx выполняем команды:

git clone git@github.com:coredns/coredns.git && cd coredns && git checkout v1.8.3
make CGO_ENABLED=0 GOOS=linux GOARCH=386
docker buildx build --pull --output=type=docker --platform linux/i386 -t docker.io//coredns:i386 .
docker push docker.io//coredns:i386

Затем берем конфиг с заменой образа на наш и деплоим его:

curl -L https://storage.googleapis.com/kubernetes-the-hard-way/coredns-1.8.yaml --silent -o - | sed 's#image: coredns/coredns:1.8.3#image: docker.io//coredns:i386#g' | kubectl apply -f -

Теперь остается только наложить патч на созданный ресурс, заменив значение аннотации на i386 или amd64 в зависимости от того, на какой узел вы хотите назначить coredns по архитектуре:

cat <

Теперь можно проверить, что все работает, выполнив команды из оригинальной инструкции. 

На этом этапе может возникнуть ошибка: Pod«ы будут зависать в состоянии ContainerCreating с ошибкой:

cgroups: cgroup mountpoint does not exist: unknown.

Если это произошло, починить можно так:

sudo mkdir /sys/fs/cgroup/systemd
sudo mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd

Smoke Test

Делаем всё по инструкции, за исключением двух пунктов.

Команда проверки шифрования:

ssh hpcom
sudo ETCDCTL_API=3 etcdctl get \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/etcd/ca.pem \
  --cert=/etc/etcd/kubernetes.pem \
  --key=/etc/etcd/kubernetes-key.pem\
  /registry/secrets/default/kubernetes-the-hard-way | hexdump -C

Для NODE_PORT-сервиса:

curl -I  http://$(grep hpcom node_list.txt | cut -d' ' -f2):${NODE_PORT}

Cleaning up

На этом этапе можно просто снести систему :)

Заключение

Мы рассмотрели, как можно с интересом провести время и не дать пропасть старому железу, которое, например, давно отложено в кладовку, а выбросить руки не поднимаются. Также мы научились разворачивать Kubernetes на физическом оборудовании, используя готовые инструкции из Kubernetes The Hard Way с небольшими доработками.

Особенно интересным и полезным может быть опыт самостоятельной сборки недостающих образов для архитектуры i386: хотя она уже давно выходит из обращения, иногда все-таки встречается.

Надеюсь, статья поможет тем, кто хочет поглубже влезть в Kubernetes и его настройку.

P.S.

Читайте также в нашем блоге:

© Habrahabr.ru