Антон Рябов bio photo

Антон Рябов

Не люблю бриться и у меня умный взгляд.

Email Twitter Telegram Github RSS

Внутренности Kubernetes

Обзор

Недавно я провел два воркшопа по внутреннему устройству Kubernetes (k8s), решил сделать текстовую заметку по простой части.

Kubernetes это 5 бинарей

Такую фразу можно часто услышать в DevOps комьюнити, её сарказм в том, что k8s выглядит простым, но на деле является очень сложным продуктом, с большим количеством вариантов запуска и конфигурирования конкретного кластера.

Работу с k8s можно разделить на две части: запуск и поддержка самого кластера/кластеров и работа с кластером в качестве клиента. Мы сейчас больше говорим про первую часть. Потому что прежде чем начать работать с кластером, нужно его запустить и настроить.

Обобщенная схема k8s выглядит примерно так.

Компоненты делятся на компоненты мастер нод и компоненты воркер нод. К компонентам мастера относятся:

  • etcd - key/value база данных, single source of truth
  • kube-apiserver - центральный “роутер”, единственный компонент который может писать и читать etcd
  • kube-controller-manager - набор лупов, которые периодически просыпаются и обрабатывают конкретные сущности
  • kube-scheduler - гранд-мастер тетриса, решает на какой ноде какой под запустить

К компонентам воркер нод, относятся:

  • kubelet - связующее звено между apiserver и непосредственно container runtime
  • kube-proxy - сетевая прокси, необходимая для работы service

Установка зависимостей

Для начала нам нужно два сервера с linux, я использовал Ubuntu 18.04. Хотя в самом минимальном случае хватит и одного сервера. Перед началом нужно установить все зависимости.

Docker

установим необходимые пакеты

apt update && apt install --no-install-recommends -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common screen jq

теперь добавим gpg ключ APT репозитория docker в нашу систему

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -

ну и добавим сам репозиторий:

add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

теперь можем установить собственно докер

apt update && apt install docker-ce docker-ce-cli containerd.io

docker является container runtime и должен быть установлен на всех серверах, которые мы хотим использовать в качестве worker node

Etcd

Установка Etcd одна из самых простых, из компонентов, необходимых нам. Cкачиваем последний доступный релиз с github

curl -L https://github.com/etcd-io/etcd/releases/download/v3.3.18/etcd-v3.3.18-linux-amd64.tar.gz -o etcd-v3.3.18-linux-amd64.tar.gz

создаем временную папку

mkdir /tmp/etcd-download

разархивируем релиз во временную папку

tar -xvzf etcd-v3.3.18-linux-amd64.tar.gz -C /tmp/etcd-download --strip-components=1

переносим исполняемый файл etcd в каталог исполняемых файлов

mv /tmp/etcd-download/etcd /usr/local/bin/

удаляем временную папку

rm -r /tmp/etcd-download

Kubernetes

Теперь нам осталось только скачать исполняемые файлы Kubernetes, cкачиваем архив с релизом

curl -LO 'https://dl.k8s.io/v1.16.0/kubernetes.tar.gz'

разархивируем его

tar -xvzf kubernetes.tar.gz

переходим в папку с файлами и выполняем скрипт, который достанет нам клиентский исполняемый файл - kubectl

cd kubernetes
echo "y" | cluster/get-kube-binaries.sh

теперь разархивируем серверные компоненты

cd ..
tar -xvzf kubernetes/server/kubernetes-server-linux-amd64.tar.gz

так же как и с Etcd, перенесем все наши исполняемый файлы в общесистемный каталог, чтобы они заработали

mv kubernetes/client/bin/kubectl /usr/local/bin/
for bin in kube-apiserver kube-controller-manager kube-scheduler kubelet kube-proxy; do mv kubernetes/server/bin/$bin /usr/local/bin/; done

kubelet и kube-proxy также нужно скопировать на все серверы, которые мы хотим использовать в качестве worker node

теперь у нас есть все необходимые и мы можем приступить.

Собираем кластер

Будем запускать все компоненты в foreground, поэтому я использую разные вкладки в терминале, но с таким же успехом можно использовать screen.

Docker

для начала убедимся, что docker запущен

systemctl status docker

если статус не running, то запустим его

systemctl start docker

Etcd

теперь, вспоминаем схему компонентов k8s, единственным источником правды и хранилищем является Etcd, без него ничего работать не будет, запустим его

etcd

Apiserver

Ну а единственным, кто может общаться с Etcd является apiserver, запустим и его

kube-apiserver

упс, ошибочка, apiserver должен знать где Etcd, поэтому используем флаг –etcd-servers

kube-apiserver --etcd-servers=http://127.0.0.1:2379

теперь, попробуем выполнить команду

kubectl create deployment web --image=nginx

если сделали все правильно, мы должны увидеть что-то вроде

deployment created

Controller-manager

Но нас интересует не deployment, а запущенный контейнер, чтобы этого добиться, запустим controller-manager, который создаст replicaSet и pods для нас

kube-controller-manager

controller-manager тоже не желает запускаться просто так и ему нужно указать где apiserver

kube-controller-manager --master=http://127.0.0.1:8080

и вроде бы все неплохо, но если в читаться в логи controller-manager, мы увидим ошибку, говорящую что-то про service account и tokens, у нас нет времени с этим разбираться, поэтому просто выполним

kubectl edit sa default

и добавим в открывшийся файл в конец, с начала строки:

automountServiceAccountToken: false

ошибки в controller-manager должны уйти, но на всякий случай мы можем остановить его (ctrl+C) и запустить заново

мы можем посмотреть состояние в нашем кластере с помощью команды

kubectl get all

Scheduler

Cледующим компонентом мы запустим scheduler, ему также нужно знать где apiserver

kube-scheduler --master=http://127.0.0.1:8080

но подождите, scheduler может назначить pod на какой-нибудь сервер, только если он у нас есть, чтобы проверить это выполним

kubectl get nodes

Kubelet

У нас сейчас нет ни одной ноды, потому что мы не запустили kubelet, сделаем это командой

kubelet

и если вам покажется что все заработало, не обольщайтесь, если запустить kubelet без параметров, он запустится в standalone режиме, т.е. не будет подключен ни к какому кластеру а это не наш случай, остановим его и попробуем передать ему apiserver как с другими компонентами

kubelet --master=http://127.0.0.1:8080

упс, так не работает, kubelet воспринимает только конфиг в файле, чтобы сгенерить его используем команды

ls ~/.kube
kubectl config set-cluster local --server=http://127.0.0.1:8080
kubectl config set-context local --cluster=local
kubectl config use-context local
cat ~/.kube/config

таким образом мы получим конфиг по умолчанию, указывающий на то, что apiserver работает на 127.0.0.1 на порту 8080, чтобы использовать этот конфиг запустим kubelet c параметром –kubeconfig

kubelet --kubeconfig ~/.kube/config

теперь, команда kubectl get nodes, должна возвращать нашу ноду в статусе Ready, а спустя некоторое время, наш pod должен перейти в статус running

Kube-proxy

мы даже можем попробовать обратиться в наш pod, для этого создадим service

kubectl expose deployment web --port=80

теперь посмотрим какой ip адрес был выдан нашему service

kubectl get svc

допустим это адрес 10.0.0.241, попробуем выполнить curl на него

curl 10.0.0.241

этот запрос не выполнится, можете не ждать. Для того, чтобы service заработал, нужно запустить kube-proxy, в новой вкладке выполним

kube-proxy --master=http://127.0.0.1:8080

теперь повторим наш curl, он должен выполниться и мы получим html страницы по умолчанию веб-сервера nginx

curl 10.0.0.241

если вы дошли до этого шага, это уже можно считать успехом. Но если у вас есть второй сервер, можем попробовать добавить его как вторую worker node.

Добавление второй ноды

Для этого скопируем наш конфиг на второй сервер

cat ~/.kube/config
vim kubeconfig

и внесем в него исправление, вместо 127.0.0.1 нам нужно использовать IP адрес нашего первого сервера, его можно найти используя команду ifconfig или ip.

ip -c a

попробуем запустить наш kubelet на втором сервере командой

kubelet --kubeconfig kubeconfig

и тут мы столкнемся с ошибкой, потому что apiserver запущен на локальном интерфейсе 127.0.0.1. Давайте вернемся во вкладку на первом сервере в которой запущен apiserver, остановим его и запустим снова но уже командой

kube-apiserver --etcd-servers=http://127.0.0.1:2379 --address=0.0.0.0

при этом controller-manager и scheduler автоматически остановятся, запустим их заново

kube-controller-manager --master=http://127.0.0.1:8080
kube-scheduler --master=http://127.0.0.1:8080

снова попробуем запустить kubelet на втором сервере

kubelet --kubeconfig kubeconfig

теперь команда kubectl get nodes должна возвращать оба сервера в статусе Ready, ну и чтобы проверить что pods запускаются на обоих серверах, запустим простой сервис

kubectl create deploy httpenv --image=jpetazzo/httpenv
kubectl scale deploy httpenv --replicas=3 # количество реплик можно выбрать любое, главное больше 2ух
kubectl expose deploy httpenv --port=8888

посмотрим на каких нодах запустились pods командой

kubectl get pods -o wide

pods должны быть распределены по нодам примерно одинаково. Посмотрим какой IP адрес получил наш service

kubectl get svc

допустим это 10.0.0.74, попробуем обратиться на него через curl

while sleep 0.3; do curl -s 10.0.0.74:8888 | jq .HOSTNAME; done

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

Если у вас есть вопросы, предложения или вы нашли ошибку, можете смело писать мне в Twitter или на почту (ссылки слева под аватаркой).

#DevOps #Kubernetes