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

Наверное, многие слышали про полезный и по-своему легендарный репозиторий Келси Хайтауэра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 (архитектура i386) (далее — 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 <<EOF | tee node_list.txt
dell 192.168.1.253 # Поменять на свои адреса и имена
hpcom 192.168.1.71
EOF

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

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

cat <<EOF | tee /opt/homebrew/etc/nginx/passthrough.conf
stream {
    upstream kube {
$(cat node_list.txt | cut -d" " -f2 | xargs -I{} echo "$(printf '%8s')server {}:6443;")
    }

    server {
        listen       443;
        proxy_pass kube;
        proxy_next_upstream on;
    }
}

EOF

Это поможет нам создать простейшую имитацию 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 <<EOF
{
  "CN": "system:node:${instance}",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:nodes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

INTERNAL_IP=$(cat node_list.txt | grep $instance | cut -d" " -f2)

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=${instance},${INTERNAL_IP},127.0.0.1 \
  -profile=kubernetes \
  ${instance}-csr.json | cfssljson -bare ${instance}
done

Далее идем по исходной инструкции вплоть до пункта 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 <<EOF
{
  "CN": "kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=$(cut -d" " -f2 node_list.txt| tr '\n' ',')127.0.0.1,${KUBERNETES_HOSTNAMES} \
  -profile=kubernetes \
  kubernetes-csr.json | cfssljson -bare kubernetes

}

В следующем разделе, 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 <<EOF | sudo tee /etc/systemd/system/etcd.service
[Unit]
Description=etcd
Documentation=https://github.com/etcd-io/etcd

[Service]
Type=notify
Environment="ETCD_UNSUPPORTED_ARCH=386" # Только для архитектуры i386, для amd64 не нужно указывать.
ExecStart=/usr/local/bin/etcd \\
  --name ${ETCD_NAME} \\
  --cert-file=/etc/etcd/kubernetes.pem \\
  --key-file=/etc/etcd/kubernetes-key.pem \\
  --peer-cert-file=/etc/etcd/kubernetes.pem \\
  --peer-key-file=/etc/etcd/kubernetes-key.pem \\
  --trusted-ca-file=/etc/etcd/ca.pem \\
  --peer-trusted-ca-file=/etc/etcd/ca.pem \\
  --peer-client-cert-auth \\
  --client-cert-auth \\
  --initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\
  --listen-peer-urls https://${INTERNAL_IP}:2380 \\
  --listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \\
  --advertise-client-urls https://${INTERNAL_IP}:2379 \\
  --initial-cluster-token etcd-cluster-0 \\
  --initial-cluster $(cat node_list.txt | sed 's# #=https://#g' | tr '\n' ',' | sed 's#,#:2380,#g' | sed 's#,$##g') \\
  --initial-cluster-state new \\
  --data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

Теперь возвращаемся к исходной инструкции и выполняем все, что идет после пункта 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 <<EOF | sudo tee /etc/portage/package.use/nginx
www-servers/nginx NGINX_MODULES_HTTP: access gzip gzip_static gunzip proxy push_stream stub_status upstream_check upstream_hash upstream_ip_hash upstream_keepalive upstream_least_conn upstream_zone
www-servers/nginx NGINX_MODULES_STREAM: access geo limit_conn map return split_clients upstream_hash upstream_least_conn upstream_zone geoip realip ssl_preread geoip2 javascript
EOF

sudo emerge --ask www-servers/nginx

В 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 <<EOF | sudo tee /etc/nginx/passthrough.conf
stream {
    upstream kube {
$(cat node_list.txt | cut -d" " -f2 | xargs -I{} echo "$(printf '%8s')server {}:6443;")
    }

    server {
        listen       443;
        proxy_pass kube;
        proxy_next_upstream on;
    }
}
EOF

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

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

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

cat <<EOF | sudo tee /etc/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-apiserver \\
  --advertise-address=${INTERNAL_IP} \\
  --allow-privileged=true \\
  --apiserver-count=$(cat node_list.txt | wc -l) \\
  --audit-log-maxage=30 \\
  --audit-log-maxbackup=3 \\
  --audit-log-maxsize=100 \\
  --audit-log-path=/var/log/audit.log \\
  --authorization-mode=Node,RBAC \\
  --bind-address=0.0.0.0 \\
  --client-ca-file=/var/lib/kubernetes/ca.pem \\
  --enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
  --etcd-cafile=/var/lib/kubernetes/ca.pem \\
  --etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
  --etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
  --etcd-servers=$(cut -d' ' -f2 node_list.txt | sed 's#^#https://#g' | sed 's#$#:2379#g' | xargs | tr ' ' ',') \\
  --event-ttl=1h \\
  --encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\
  --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
  --kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
  --kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
  --runtime-config='api/all=true' \\
  --service-account-key-file=/var/lib/kubernetes/service-account.pem \\
  --service-account-signing-key-file=/var/lib/kubernetes/service-account-key.pem \\
  --service-account-issuer=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \\
  --service-cluster-ip-range=10.32.0.0/24 \\
  --service-node-port-range=30000-32767 \\
  --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
  --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

Далее выполняем всё из оригинальной инструкции до пункта 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 <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf
{
    "cniVersion": "0.4.0",
    "name": "bridge",
    "type": "bridge",
    "bridge": "cnio0",
    "isGateway": true,
    "ipMasq": true,
    "ipam": {
        "type": "host-local",
        "ranges": [
          [{"subnet": "${POD_CIDR}"}]
        ],
        "routes": [{"dst": "0.0.0.0/0"}]
    }
}
EOF

cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf
{
    "cniVersion": "0.4.0",
    "name": "lo",
    "type": "loopback"
}
EOF

Конфигурируем 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/<DockerHub username>/pause:i386 --build-arg BASE=scratch --build-arg ARCH=i386 .
docker push docker.io/<DockerHub username>/pause:i386

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

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

cat <<EOF | sudo tee /etc/systemd/system/containerd.service
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target

[Service]
ExecStartPre=/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd
Restart=always
RestartSec=5
Delegate=yes
KillMode=process
OOMScoreAdjust=-999
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity

[Install]
WantedBy=multi-user.target
EOF

Сконфигурировать 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/<DockerHub username>/coredns:i386 .
docker push docker.io/<DockerHub username>/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/<DockerHub username>/coredns:i386#g' | kubectl apply -f -

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

cat <<EOF | yq -ojson  | tee patch.json
spec:
  template:
    spec:
      nodeSelector:
        kubernetes.io/arch: "386"
    
EOF

kubectl patch -n kube-system deployments.apps coredns --patch-file patch.json

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

На этом этапе может возникнуть ошибка: 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.

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

Комментарии (7)


  1. Inskin
    23.09.2022 10:36
    +3

    А почему x32 для HP? Там же даже младший pentium dual-core T2370 поддерживает x64. Чисто для опыта?


    1. AlexMakh Автор
      23.09.2022 11:29

      Доброго дня!
      Не глубоко разбирался с этим вопросом но, вроде как, модель проца в этом ноутбуке не поддерживает x64. Могу ошибаться, так как даже не пробовал накатывать x64.


  1. iliketech
    23.09.2022 12:29
    +1

    Здорово! Интересно насколько оно производительно будет работать с разными планировщиками ввода/вывода.
    https://habr.com/ru/company/selectel/blog/346844/
    https://xakep.ru/2014/05/11/input-out-linux-planning/#toc07.


    1. AlexMakh Автор
      23.09.2022 16:22
      +2

      Добрый день!
      Не совсем понял вопроса, на чем именно тестировать планировщики?:)

      Если вы про использование HostPath внутри куба - то этот кластер не для этого:) Это, конечно, ванильный куб, но лучше использовать для сетапа kubeadm или deckhouse. Вообще в принципе данная статья - это скорее мануал по внутренностям k8s, а не про сетап боевого прод кластера:)


      1. iliketech
        23.09.2022 19:26
        +1

        Меня преследует спортивный интерес адской оптимизации - выжать из генты и железа максимум по производительности, а далее самого кластера посредством адского тюнинга настроек. Но этак к слову, мой небольшой фетиш и заодно диагноз)) Попробую повторить Ваш опыт, повертеть настройки - получить удовольствие :)


  1. SexTools
    24.09.2022 15:21

    Но ведь Intel Core2 Duo 64-разрядный процессор. Я вот реально подумал, что у вас ноут на 80386 и вы там установили k8s, а у вас тут целый зоопарк из названий одной и той же архитектуры - i386, i686, x86_32. (


    1. AlexMakh Автор
      25.09.2022 02:00

      Добрый день!
      По поводу названия проца - в начале сей наркомании статьи есть указание на модели ноутов и их процов:)

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

      По поводу архитектуры - спасибо большое за информацию, буду иметь ввиду.
      Но, с другой стороны, кто мешает накатывать на 64битный проц 32х битную систему?:)

      И по поводу названий - поправил в самом начале (как раз, где описания ноутов), чтобы поменьше путаницы было, но ссылки на скачивания с x86_32 к сожалению поправить не смогу - github не даст :D