Kubernetes прекратил поддержку Docker и отказался от dockershim — прокладки между kubelet и Docker, которая позволяет последнему работать с CRI. В итоге разработчики столкнулись с необходимостью использования новых, совместимых с CRI, движков для запуска контейнеров. Из числа общеизвестных таких два — containerd и CRI-O.
Меня зовут Александр Чадин, я руководитель команды разработки в VK Cloud. Расскажу, как мы искали замену Docker для сервиса Cloud Containers, на что ориентировались при выборе нового движка, как внедряли новое решение и с какими подводными камнями при этом столкнулись.
Подробнее о предпосылках
Docker — один из удобных и популярных инструментов для работы с контейнерами, который можно использовать как на сервере, так и на любом ПК. Он содержит набор инструментов, благодаря которым может выполнять несколько задач:
- создавать образы контейнеров;
- работать с репозиториями;
- создавать, запускать и управлять контейнерами.
Фактически Docker — мультитул для работы с контейнерами.
Но это все-таки вендорское решение. У него открыт канал обратной связи, но, учитывая последние новости, путь этого решения сложно предсказать. Устранение уязвимостей, добавление новых функций, расширение совместимостей и других обновлений — все зависит от вендора.
Для регулирования экосистемы контейнерных технологий были разработаны два основных стандарта. Они помогают добиться совместимости между различными инструментами и избежать зависимости от одной компании или проекта.
-
Container Runtime Interface (CRI). CRI — API, который Kubernetes использует для управления различными Container Runtime, создающими и управляющими контейнерами. CRI упрощает работу с Container Runtime. Вместо того, чтобы включать в Kubernetes поддержку каждой из них, используется стандарт CRI. При этом задача управления контейнерами полностью ложится на Container Runtime.
-
Open Container Initiative (OCI). Определяет стандарт образов и контейнеров.
С появлением и внедрением стандартов Kubernetes стал поддерживать только те Container Runtime, которые работают с Container Runtime Interface (CRI). При этом Docker не поддерживает этот стандарт напрямую и использует для работы с Kubernetes прослойку — компонент dockershim.
Кроме того, у Docker при обновлении иногда изменяется API. Поэтому приходится привязываться к конкретным версиям Docker, чтобы быть уверенным, что все работает как надо.
В результате от монолитного Docker, выполняющего сразу много задач, отказались — Kubernetes полностью прекратил его поддержку. Разработчики, в том числе и наша команда, столкнулись с необходимостью переезда на совместимые с CRI инструменты. Среди альтернатив — движки containerd и CRI-O, отвечающие за запуск контейнеров.
-
сontainerd — движок Container Runtime, который выделен из проекта Docker. Реализует спецификацию CRI. Умеет скачивать образы из репозитория, управлять ими, передавать их Container Runtime нижнего уровня. containerd использует собственный плагин для поддержки CRI в Kubernetes.
-
CRI-O — движок Container Runtime, реализующий Container Runtime Interface (CRI). Реализация создана с нуля при поддержке Red Hat, IBM, Intel и SUSE как Container Runtime для Kubernetes. Это альтернатива containerd, которая также позволяет загружать образы контейнеров из репозиториев, управлять ими и запускать Container Runtime нижнего уровня для запуска процессов контейнера.
CRI-O или сontainerd: что и как мы выбрали для себя
CRI-O или сontainerd — разные реализации CRI. Ввиду стандартизации компонента они практически идентичны, в том числе и по совместимости с другими узлами Kubernetes. Фактически движки отличаются только на уровне технических параметров — именно они и являются определяющими при выборе.
При поиске движка для нашего облачного Kubernetes Cloud Containers мы учитывали:
- скорость загрузки и остановки контейнеров;
- безопасность;
- структурированность компонентов;
- поддержку со стороны сообщества.
В результате изучения уже проведенных исследований и собственных тестов мы обнаружили, что:
Containerd в связке с runc работает быстрее CRI-O. Но отличие незначительно и глобально не влияет на производительность всей системы. На графике — сравнение скорости обработки запросов (в секундах) при одинаковых параметрах системы.
CRI-O безопаснее containerd. CRI-O, в отличие от containerd, имеет ограниченный набор функций и внутренних компонентов. Это уменьшает потенциальную поверхность атаки и снижает уязвимость компонента.
У CRI-O понятная структура компонентов. CRI-O состоит из нескольких компонентов, отвечающих за хранилище, образы, сетевой интерфейс, мониторинг и безопасность. Все, что надо для работы с CRI-O, есть в открытом доступе, в том числе сценарии использования, инструкции по внедрению и настройке.
У CRI-O шире поддержка со стороны коммьюнити. У CRI-O много пользователей и активное сообщество. Получить помощь в решении возникающих проблем легче, чем с containerd.
Взвесив плюсы и минусы, мы выбрали для себя CRI-O.
Внедрение CRI-O
Внедрять CRI-O мы начали с версии Kubernetes 1.21. Выполняли его силами двух разработчиков. Алгоритм стандартный: перед настройкой отключаем swap, все операции на хосте выполняем от имени пользователя root.
Настройка операционной системы, установка предварительных зависимостей для CRI-O
1. Обновляем операционную систему:
dnf -y update
2. Настраиваем firewall и SELinux. В зависимости от окружения, можно настраивать по документации или принять сторонний firewall, изменив зону:
firewall-cmd --set-default-zone trusted
firewall-cmd --reload
Также можно выключить firewall:
systemctl disable --now firewalld
SELinux выключаем или переводим в режим permissive:
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
3. Загружаем модули ядра и пакеты. Настраиваем автоматическую загрузку модуля br_netfilter при запуске системы:
modprobe overlay
modprobe br_netfilter
echo "br_netfilter" >> /etc/modules-load.d/br_netfilter.conf
dnf -y install iproute-tc
4. Активируем форвардинг пакетов и настраиваем корректную обработку трафика:
cat > /etc/sysctl.d/99-kubernetes-cri.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
Применяем внесенные настройки:
sysctl --system
5. Задаем нужную версию CRI-O. Она совпадает с версией Kubernetes:
export REQUIRED_VERSION=1.21
6. Добавляем нужные репозитории:
dnf -y install 'dnf-command(copr)'
dnf -y copr enable rhcontainerbot/container-selinux
curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/CentOS_8/devel:kubic:libcontainers:stable.repo
curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable:cri-o:$REQUIRED_VERSION.repo https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$REQUIRED_VERSION/CentOS_8/devel:kubic:libcontainers:stable:cri-o:$REQUIRED_VERSION.repo
7.Устанавливаем CRI-O:
dnf -y install cri-o
8. Активируем и запускаем образ CRI-O:
systemctl enable --now crio
9. Проверяем статус CRI-O:
systemctl status crio
Установка и активация Kubernetes
1. Добавляем репозиторий:
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF
2. Устанавливаем Kubernetes версии 1.21:
dnf install -y kubelet-1.21* kubeadm-1.21* kubectl-1.21* --disableexcludes=kubernetes
3. Для использования демона CRI-O, до запуска и инициализации Kubernetes настраиваем конфигурационный файл /var/lib/kubelet/config.yaml и предварительно создаем нужный каталог:
mkdir /var/lib/kubelet
cat <<EOF > /var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF
4. Добавляем в файл аргументы драйвера cgroup:
cat /dev/null > /etc/sysconfig/kubelet
cat <<EOF > /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS=--container-runtime=remote --cgroup-driver=systemd
--container-runtime-endpoint='unix:///var/run/crio/crio.sock'
EOF
5. Активируем kubelet:
sudo systemctl enable --now kubelet
Дополнительно мы указали insecure_registries, в который внесли перечень приватных клиентских реестров, для которых пропускается TLS-проверка сертификатов.
Для наших клиентов переход на CRI-O получился прозрачным и бесшовным — он выполняется при апгрейде кластера: если у клиента был кластер на 1.20 с Docker, то при апгрейде на 1.21 он получает CRI-O.
Подводные камни, или Что следует учесть при переходе на CRI-O
CRI-O — стандартизованный компонент с исходной совместимостью с Kubernetes. Благодаря этому его можно внедрить на замену Docker без особых проблем. Но на практике мы убедились, что подводные камни, которые нужно учитывать, все же есть.
Важно проверить, как работают контейнеры, которые ранее взаимодействовали с Docker. Для этого мы проводили CNCF-тесты: создавали кластер с тремя мастер-нодами и тремя воркерами и запускали тесты командой
./sonobuoy run --mode certified-conformance --plugin-env=e2e.E2E_EXTRA_ARGS="--allowed-not-ready-nodes=3"
Следует проверять наличие метрик из kubelet и их лейблов. Для этого можно с любой ноды выполнить запрос:
curl --cacert <path-to-certificate-authority.crt> \
--cert <path-to-kubelet.crt>\
--key <path-to-kubelet.key>\
https://<node_ip>:10250/metrics/cadvisor
В полученном запросе у контейнерных метрик должны быть заполнены лейблы. Например:
container_cpu_cfs_throttled_periods_total{container="metrics-server",id="/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8af71958_7609_48db_afe4_dc772d845654.slice/crio-f56572cefcf697840ad7a11cdaf0a6b4f6b074216a863447dc61d0287b1cd3c7.scope",name="k8s_metrics-server_metrics-server-6ffbbb5859-hch2c_kube-system_8af71958-7609-48db-afe4-dc772d845654_0",namespace="kube-system",pod="metrics-server-6ffbbb5859-hch2c"} 1788 1660899417380
Нужно учитывать, что cadvisor — утилита для сбора метрик с контейнеров и нод — имеет захардкоженный сокет crio. Из-за этого смена дефолтного сокета ведет к пустым лейблам в метриках.
Новая архитектура решения: что получили мы и конечные пользователи
После внедрения CRI-O мы получили следующую схему связей Kubernetes c runc и низкоуровневыми библиотеками.
Схема запуска контейнера в среде с CRI-O
Мы как провайдер Kubernetes as a Service получили более легковесное и управляемое решение (по сравнению с Docker). Также с внедрением CRI-O повысилась отказоустойчивость нашего Kubernetes — при необходимости компонент можно легко заменить аналогичным или совместимым с CRI, не допуская остановки всей системы.
Для пользователей Cloud Containers переход на CRI-O прошел практически незаметно — изменился только формат сбора логов с контейнеров: Docker позволял собирать логи в json формате, а CRI-O собирает только plain-логи. При этом всегда можно настроить fluentd или fluentbit для преобразования логов в json-формат.
Вы прямо сейчас можете попробовать Kubernetes от VK Cloud. Для тестирования мы начисляем новым пользователям 3 000 бонусных рублей и будем рады вашей обратной связи.
Комментарии (6)
jaroslau
26.01.2023 18:53+1А как вы измерили ширину поддержки комьюнити?
Если верить GitHub, у containerd 2.7к форков и 13к звезд против меньше тысячи форков и 4.4к звезд у CRI-O.shurup
27.01.2023 04:07+4Пришел в комментарии с тем же вопросом. Кажется, что преимущество в сообществе всё-таки у containerd, а это зачастую означает, что остальные плюсы тоже скорее у него (больше юзеров - больше кейсов и исправлений, реализации запросов юзеров и т.п.). Если это не какое-то узкое/нишевое решение, конечно, но в данном случае речи об этом нет.
CRI-O - это видение Red Hat, которое пытаются сделать массовым. А containerd - реальный (действующий) выбор сообщества и индустрии. Ведущие мировые облачные провайдеры используют именно containerd. В этой таблице от Oracle можно наглядно увидеть. А здесь обсуждается вопрос потенциальной поддержки CRI-O в EKS.
Наконец, containerd - это уже давно (с 2019 года) graduated project в CNCF (https://www.cncf.io/announcements/2019/02/28/cncf-announces-containerd-graduation/), а CRI-O - до сих пор incubating (https://www.cncf.io/projects/cri-o/), что подчеркивает разницу в их зрелости/принятии индустрией.
На фоне этого обоснование вашего выбора выглядит иначе. Каковы были действительные причины? :-)
shurup
27.01.2023 14:07UPD. По беглому анализу свежих данных последнего опроса от CNCF (https://github.com/cncf/surveys/tree/main/cloudnative) у меня получилось, что containerd используют в production 42% опрошенных (+25,5% пробуют), а CRI-O — менее 15% (+17,5% пробуют).
axxxolotl
27.01.2023 12:22+1Простите за ламерский вопрос, но теперь что, придётся всё приложения которые у меня в докер-имеждах упакованы были нужно в крио-имеджи перепаковывать? Ну и да хрен с ними с моими приложениями. А вот крио-имеджи того же кролика, нжинкса, постгреса, и прочих тысяч продуктов где брать?
Fitrager
Но ведь последний Kubernetes 1.26. 1.21 уже End of Life.
athlant64
Самое главное перенести из docker, а дальше обновить дело техники.