Обзор
Недавно я провел два воркшопа по внутреннему устройству 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 изолированы друг от друга, но это уже тема следующего воркшопа.
#DevOps #Kubernetes #TechAndDevЕсли у вас есть вопросы, предложения или вы нашли ошибку, можете смело писать мне в Twitter или на почту (ссылки слева под аватаркой).