Pull to refresh

Основы Kubernetes

Reading time13 min
Views891K
В этой публикации я хотел рассказать об интересной, но незаслуженно мало описанной на Хабре, системе управления контейнерами Kubernetes.

image

Что такое Kubernetes?


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

Компания Google пользуется контейнерной технологией уже более десяти лет. Она начинала с запуска более 2 млрд контейнеров в течение одной недели. С помощью проекта Kubernetes компания делится своим опытом создания открытой платформы, предназначенной для масштабируемого запуска контейнеров.

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

Концепции Kubernetes


Nodes (node.md): Нода это машина в кластере Kubernetes.
Pods (pods.md): Pod это группа контейнеров с общими разделами, запускаемых как единое целое.
Replication Controllers (replication-controller.md): replication controller гарантирует, что определенное количество «реплик» pod'ы будут запущены в любой момент времени.
Services (services.md): Сервис в Kubernetes это абстракция которая определяет логический объединённый набор pod и политику доступа к ним.
Volumes (volumes.md): Volume(раздел) это директория, возможно, с данными в ней, которая доступна в контейнере.
Labels (labels.md): Label'ы это пары ключ/значение которые прикрепляются к объектам, например pod'ам. Label'ы могут быть использованы для создания и выбора наборов объектов.
Kubectl Command Line Interface (kubectl.md): kubectl интерфейс командной строки для управления Kubernetes.

Архитектура Kubernetes


Работающий кластер Kubernetes включает в себя агента, запущенного на нодах (kubelet) и компоненты мастера (APIs, scheduler, etc), поверх решения с распределённым хранилищем. Приведённая схема показывает желаемое, в конечном итоге, состояние, хотя все ещё ведётся работа над некоторыми вещами, например: как сделать так, чтобы kubelet (все компоненты, на самом деле) самостоятельно запускался в контейнере, что сделает планировщик на 100% подключаемым.
image

Нода Kubernetes

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

Kubelet

Kubelet управляет pod'ами их контейнерами, образами, разделами, etc.

Kube-Proxy

Также на каждой ноде запускается простой proxy-балансировщик. Этот сервис запускается на каждой ноде и настраивается в Kubernetes API. Kube-Proxy может выполнять простейшее перенаправление потоков TCP и UDP (round robin) между набором бэкендов.

Компоненты управления Kubernetes

Система управления Kubernetes разделена на несколько компонентов. В данный момент все они запускаются на мастер-ноде, но в скором времени это будет изменено для возможности создания отказоустойчивого кластера. Эти компоненты работают вместе, чтобы обеспечить единое представление кластера.

etcd

Состояние мастера хранится в экземпляре etcd. Это обеспечивает надёжное хранение конфигурационных данных и своевременное оповещение прочих компонентов об изменении состояния.

Kubernetes API Server

Kubernetes API обеспечивает работу api-сервера. Он предназначен для того, чтобы быть CRUD сервером со встроенной бизнес-логикой, реализованной в отдельных компонентах или в плагинах. Он, в основном, обрабатывает REST операции, проверяя их и обновляя соответствующие объекты в etcd (и событийно в других хранилищах).

Scheduler

Scheduler привязывает незапущенные pod'ы к нодам через вызов /binding API. Scheduler подключаем; планируется поддержка множественных scheduler'ов и пользовательских scheduler'ов.

Kubernetes Controller Manager Server

Все остальные функции уровня кластера представлены в Controller Manager. Например, ноды обнаруживаются, управляются и контролируются средствами node controller. Эта сущность в итоге может быть разделена на отдельные компоненты, чтобы сделать их независимо подключаемыми.

ReplicationController — это механизм, основывающийся на pod API. В конечном счете планируется перевести её на общий механизм plug-in, когда он будет реализован.

Пример настройки кластера


В качестве платформы для примера настройки была выбрана Ubuntu-server 14.10 как наиболее простая для примера и, в то же время, позволяющая продемонстрировать основные параметры настройки кластера.

Для создания тестового кластера будут использованы три машины для создания нод и отдельная машина для проведения удалённой установки. Можно не выделять отдельную машину и производить установку с одной из нод.

Список используемых машин:
  • Conf
  • Node1: 192.168.0.10 — master, minion
  • Node2: 192.168.0.11 — minion
  • Node3: 192.168.0.12 — minion

Подготовка нод

Требования для запуска:

  1. На всех нодах установлен docker версии 1.2+ и bridge-utils
  2. Все машины связаны друг с другом, необходимости в доступе к интернету нет (в этом случае необходимо использовать локальный docker registry)
  3. На все ноды можно войти без ввода логина/пароля, с использованием ssh-ключей

Установка ПО на ноды

Установку Docker можно произвести по статье в официальных источниках:

node% sudo apt-get update $ sudo apt-get install wget
node% wget -qO- https://get.docker.com/ | sh

Дополнительная настройка Docker после установки не нужна, т.к. будет произведена скриптом установки Kubernetes.
Установка bridge-utils:

node% sudo apt-get install bridge-utils

Добавление ssh-ключей

Выполняем на машине, с которой будет запущен скрипт установки.
Если ключи ещё не созданы, создаём их:

conf% ssh-keygen 

Копируем ключи на удалённые машины, предварительно убедившись в наличии на них необходимого пользователя, в нашем случае core.

conf% ssh-copy-id core@192.168.0.10
conf% ssh-copy-id core@192.168.0.11
conf% ssh-copy-id core@192.168.0.12

Установка Kubernetes

Далее мы займёмся установкой непосредственно Kubernetes. Для этого в первую очередь скачаем и распакуем последний доступный релиз с GitHub:

conf% wget https://github.com/GoogleCloudPlatform/kubernetes/releases/download/v0.17.0/kubernetes.tar.gz
conf% tar xzf ./kubernetes.tar.gz
conf% cd ./kubernetes

Настройка

Настройка Kubernetes через стандартные скрипты примеров полностью производится перед установкой производится через конфигурационные файлы. При установке мы будем использовать скрипты папке ./cluster/ubuntu/.

В первую очередь изменим скрипт ./cluster/ubuntu/build.sh который скачивает и подготавливает необходимые для установки бинарники Kubernetes, etcd и flannel:

conf% vim ./cluster/ubuntu/build.sh

Для того, чтобы использовать последний, на момент написания статьи, релиз 0.17.0 необходимо заменить:

# k8s
echo "Download kubernetes release ..."
K8S_VERSION="v0.15.0"

На:

# k8s
echo "Download kubernetes release ..."
K8S_VERSION="v0.17.0"

И запустим:

conf% cd ./cluster/ubuntu/ 
conf% ./build.sh      #Данный скрипт важно запускать именно из той папки, где он лежит.

Далее указываем параметры будущего кластера, для чего редактируем файл ./config-default.sh:

## Contains configuration values for the Ubuntu cluster
# В данном пункте необходимо указать все ноды будущего кластера, MASTER-нода указывается первой
# Ноды указываются в формате <user_1@ip_1> <user_2@ip_2> <user_3@ip_3> разделитель - пробел
# В качестве пользователя указывается тот пользователь для которого по нодам разложены ssh-ключи
export nodes="core@192.168.0.10 core@192.168.0.10 core@192.168.0.10"
# Определяем роли нод : a(master) или i(minion) или ai(master и minion), указывается в том же порядке, что и ноды в списке выше.
export roles=("ai" "i" "i")
# Определяем количество миньонов
export NUM_MINIONS=${NUM_MINIONS:-3}
# Определяем IP-подсеть из которой, в последствии будут выделяться адреса для сервисов.
# Выделять необходимо серую подсеть, которая не будет пересекаться с имеющимися, т.к. эти адреса будут существовать только в пределах каждой ноды.
#Перенаправление на IP-адреса сервисов производится локальным iptables каждой ноды.
export PORTAL_NET=192.168.3.0/24
#Определяем подсеть из которой будут выделяться подсети для создания внутренней сети flannel.
#flannel по умолчанию выделяет подсеть с маской 24 на каждую ноду, из этих подсетей будут выделяться адреса для Docker-контейнеров.
#Подсеть не должна пересекаться с PORTAL_NET
export FLANNEL_NET=172.16.0.0/16
# Admission Controllers определяет политику доступа к объектам кластера.
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,ResourceQuota
# Дополнительные параметры запуска Docker.  Могут быть полезны для дополнительных настроек
# например установка --insecure-registry для локальных репозиториев.
DOCKER_OPTS=""

На этом настройка заканчивается и можно переходить к установке.

Установка

Первым делом необходимо сообщить системе про наш ssh-agent и используемый ssh-ключ для этого выполняем:

eval `ssh-agent -s`
ssh-add /путь/до/ключа

Далее переходим непосредственно к установке. Для этого используется скрипт ./kubernetes/cluster/kube-up.sh которому необходимо указать, что мы используем ubuntu.

conf% cd ../
conf% KUBERNETES_PROVIDER=ubuntu ./kube-up.sh

В процессе установки скрипт потребует пароль sudo для каждой ноды. По окончанию установки проверит состояние кластера и выведет список нод и адреса Kubernetes api.

Пример вывода скрипта
Starting cluster using provider: ubuntu
... calling verify-prereqs
... calling kube-up
Deploying master and minion on machine 192.168.0.10
<Список копируемых файлов>
[sudo] password to copy files and start node: 
etcd start/running, process 16384
Connection to 192.168.0.10 closed.
Deploying minion on machine 192.168.0.11
<Список копируемых файлов>
[sudo] password to copy files and start minion: 
etcd start/running, process 12325
Connection to 192.168.0.11 closed.
Deploying minion on machine 192.168.0.12
<Список копируемых файлов>
[sudo] password to copy files and start minion: 
etcd start/running, process 10217
Connection to 192.168.0.12 closed.
Validating master
Validating core@192.168.0.10
Validating core@192.168.0.11
Validating core@192.168.0.12
Kubernetes cluster is running.  The master is running at:
  http://192.168.0.10
... calling validate-cluster
Found 3 nodes.
     1  NAME           LABELS    STATUS
     2  192.168.0.10   <none>    Ready
     3  192.168.0.11   <none>    Ready
     4  192.168.0.12   <none>    Ready
Validate output:
NAME                 STATUS    MESSAGE   ERROR
etcd-0               Healthy   {"action":"get","node":{"dir":true,"nodes":[{"key":"/coreos.com","dir":true,"modifiedIndex":11,"createdIndex":11},{"key":"/registry","dir":true,"modifiedIndex":5,"createdIndex":5}],"modifiedIndex":5,"createdIndex":5}}
                     nil
controller-manager   Healthy   ok        nil
scheduler            Healthy   ok        nil
Cluster validation succeeded
Done, listing cluster services:
Kubernetes master is running at http://192.168.0.10:8080


Посмотрим, какие ноды и сервисы присутствуют в новом кластере:
conf% cp ../kubernetes/platforms/linux/amd64/kubectl /opt/bin/
conf% /opt/bin/kubectl get services,minions -s "http://192.168.0.10:8080"
NAME            LABELS                                    SELECTOR     IP           PORT(S)
kubernetes      component=apiserver,provider=kubernetes   <none>       192.168.3.2     443/TCP
kubernetes-ro   component=apiserver,provider=kubernetes   <none>       192.168.3.1     80/TCP
NAME           LABELS    STATUS
192.168.0.10   <none>    Ready
192.168.0.11   <none>    Ready
192.168.0.12   <none>    Ready

Видим список из установленных нод в состоянии Ready и два предустановленных сервиса kubernetes и kubernetes-ro — это прокси для непосредственного доступа к Kubernetes API. Как и к любому сервису Kubernetes к kubernetes и kubernetes-ro можно обратиться непосредственно по IP адресу с любой из нод.

Запуск тестового сервиса


Для запуска сервиса необходимо подготовить docker контейнер, на основе которого будет создан сервис. Дабы не усложнять, в примере будет использован общедоступный контейнер nginx. Обязательными составляющими сервиса являются Replication Controller, обеспечивающий запущенность необходимого набора контейнеров (точнее pod) и service, который определяет, на каких IP адресе и портах будет слушать сервис и правила распределения запросов между pod'ами.

Любой сервис можно запустить 2-я способами: вручную и с помощью конфиг-файла. Рассмотрим оба.

Запуск сервиса вручную

Начнём с создания Replication Controller'а:

conf% /opt/bin/kubectl run-container nginx --port=80 --port=443 --image=nginx --replicas=6 -s "http://192.168.0.10:8080"

Где:
  • nginx — имя будущего rc
  • --port — порты на которых будут слушать контейнеры rc
  • --image — образ из которого будут запущены контейнеры
  • --replicas=6 — количество реплик

Посмотрим, что у нас получилось:

/opt/bin/kubectl get pods,rc -s "http://192.168.0.10:8080"

Вывод
POD           IP           CONTAINER(S)   IMAGE(S)   HOST                        LABELS                STATUS    CREATED     MESSAGE
nginx-3gii4   172.16.58.4                             192.168.0.11/192.168.0.11   run-container=nginx   Running   9 seconds   
                           nginx          nginx                                                        Running   9 seconds   
nginx-3xudc   172.16.62.6                             192.168.0.10/192.168.0.10   run-container=nginx   Running   9 seconds   
                           nginx          nginx                                                        Running   8 seconds   
nginx-igpon   172.16.58.6                             192.168.0.11/192.168.0.11   run-container=nginx   Running   9 seconds   
                           nginx          nginx                                                        Running   8 seconds   
nginx-km78j   172.16.58.5                             192.168.0.11/192.168.0.11   run-container=nginx   Running   9 seconds   
                           nginx          nginx                                                        Running   8 seconds   
nginx-sjb39   172.16.83.4                             192.168.0.12/192.168.0.12   run-container=nginx   Running   9 seconds   
                           nginx          nginx                                                        Running   8 seconds 
nginx-zk1wv   172.16.62.7                             192.168.0.10/192.168.0.10   run-container=nginx   Running   9 seconds   
                           nginx          nginx                                                        Running   8 seconds   
CONTROLLER   CONTAINER(S)   IMAGE(S)   SELECTOR              REPLICAS
nginx        nginx          nginx      run-container=nginx   6


Был создан Replication Controller с именем nginx и количеством реплик равным 6. Реплики в произвольном порядке запущены на нодах, местоположения каждой pod'ы указано в столбце HOST.
Вывод может отличаться от приведённого в некоторых случаях, например:
  • Часть pod находится в состоянии pending: это значит, что они ещё не запустились, необходимо немного подождать
  • У pod не определён HOST: это значит, что scheduler ещё не назначил ноду на которой будет запущен pod


Далее создаём service который будет использовать наш Replication Controller как бекенд.
Для http:

conf% /opt/bin/kubectl expose rc nginx --port=80 --target-port=80 --service-name=nginx-http -s "http://192.168.0.10:8080"

И для https:

conf% /opt/bin/kubectl expose rc nginx --port=443 --target-port=443 --service-name=nginx-https -s "http://192.168.0.10:8080"

Где:
  • rc nginx — тип и имя используемого ресурса (rc = Replication Controller)
  • --port — порт на котором будет «слушать» сервис
  • --target-port — порт контейнера на который будет производиться трансляция запросов
  • --service-name — будущее имя сервиса

Проверяем результат:

/opt/bin/kubectl get rc,services -s "http://192.168.0.10:8080"

Вывод
CONTROLLER   CONTAINER(S)   IMAGE(S)   SELECTOR              REPLICAS
nginx        nginx          nginx      run-container=nginx   6
NAME            LABELS                                    SELECTOR              IP           PORT(S)
kubernetes      component=apiserver,provider=kubernetes   <none>                192.168.3.2     443/TCP
kubernetes-ro   component=apiserver,provider=kubernetes   <none>                192.168.3.1     80/TCP
nginx-http      <none>                                    run-container=nginx   192.168.3.66    80/TCP
nginx-https     <none>                                    run-container=nginx   192.168.3.172   443/TCP


Для проверки запущенности можно зайти на любую из нод и выполнить в консоли:

node% curl http://192.168.3.66

В выводе curl увидим стандартную приветственную страницу nginx. Готово, сервис запущен и доступен.

Запуск сервиса с помощью конфигов

Для этого способа запуска необходимо создать конфиги для Replication Controller'а и service'а. Kubernetes принимает конфиги в форматах yaml и json. Мне ближе yaml поэтому будем использовать его.

Предварительно очистим наш кластер от предыдущего эксперимента:

conf% /opt/bin/kubectl delete services nginx-http nginx-https -s "http://192.168.0.10:8080"
conf% /opt/bin/kubectl stop rc nginx -s "http://192.168.0.10:8080"
Теперь приступим к написанию конфигов.

nginx_rc.yaml
содержимое
apiVersion: v1beta3
kind: ReplicationController
# Указываем имя ReplicationController
metadata:
  name: nginx-controller
spec:
  # Устанавливаем количество реплик
  replicas: 6
  selector:
    name: nginx
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
        #Описываем контейнер
        - name: nginx
          image: nginx
          #Пробрасываем порты
          ports:
            - containerPort: 80
            - containerPort: 443
          livenessProbe:
            # включаем проверку работоспособности
            enabled: true
            type: http
            # Время ожидания после запуска pod'ы до момента начала проверок
            initialDelaySeconds: 30
            TimeoutSeconds: 5
            # http проверка
            httpGet:
              path: /
              port: 80
      portals:
        - destination: nginx


Применяем конфиг:

conf% /opt/bin/kubectl create -f ./nginx_rc.yaml -s "http://192.168.0.10:8080"

Проверяем результат:

conf% /opt/bin/kubectl get pods,rc -s "http://192.168.0.10:8080"

Вывод
POD                      IP           CONTAINER(S)   IMAGE(S)   HOST                        LABELS       STATUS    CREATED          MESSAGE
nginx-controller-0wklg   172.16.58.7                             192.168.0.11/192.168.0.11   name=nginx   Running   About a minute   
                                      nginx          nginx                                               Running   About a minute   
nginx-controller-2jynt   172.16.58.8                             192.168.0.11/192.168.0.11   name=nginx   Running   About a minute   
                                      nginx          nginx                                               Running   About a minute   
nginx-controller-8ra6j   172.16.62.8                             192.168.0.10/192.168.0.10  name=nginx   Running   About a minute   
                                      nginx          nginx                                               Running   About a minute   
nginx-controller-avmu8   172.16.58.9                             192.168.0.11/192.168.0.11   name=nginx   Running   About a minute   
                                      nginx          nginx                                               Running   About a minute   
nginx-controller-ddr4y   172.16.83.7                             192.168.0.12/192.168.0.12   name=nginx   Running   About a minute   
                                      nginx          nginx                                               Running   About a minute   
nginx-controller-qb2wb   172.16.83.5                             192.168.0.12/192.168.0.12  name=nginx   Running   About a minute   
                                      nginx          nginx                                               Running   About a minute   
CONTROLLER         CONTAINER(S)   IMAGE(S)   SELECTOR     REPLICAS
nginx-controller   nginx          nginx      name=nginx   6


Был создан Replication Controller с именем nginx и количеством реплик равным 6. Реплики в произвольном порядке запущены на нодах, местоположения каждой pod'ы указано в столбце HOST.

nginx_service.yaml
Содержимое
apiVersion: v1beta3
kind: Service
metadata:
  name: nginx
spec:
  publicIPs: 
    - 12.0.0.5 # IP который будет присвоен сервису помимо автоматически назначенного.
  ports:
    - name: http
      port: 80 #порт на котором будет слушать сервис
      targetPort: 80 порт контейнера на который будет производиться трансляция запросов
      protocol: TCP
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
  selector:
    name: nginx # поле должно совпадать с аналогичным в конфиге ReplicationController


Можно заметить, что при использовании конфига за одним сервисом могут быть закреплены несколько портов.
Применяем конфиг:

conf% /opt/bin/kubectl create -f ./nginx_service.yaml -s "http://192.168.0.10:8080"

Проверяем результат:

/opt/bin/kubectl get rc,services -s "http://192.168.0.10:8080"

Вывод
CONTROLLER         CONTAINER(S)   IMAGE(S)   SELECTOR     REPLICAS
nginx-controller   nginx          nginx      name=nginx   6
NAME            LABELS                                    SELECTOR     IP           PORT(S)
kubernetes      component=apiserver,provider=kubernetes   <none>       192.168.3.2     443/TCP
kubernetes-ro   component=apiserver,provider=kubernetes   <none>       192.168.3.1     80/TCP
nginx           <none>                                    name=nginx   192.168.3.214   80/TCP
                                                                       12.0.0.5     443/TCP



Для проверки запущенности можно зайти на любую из нод и выполнить в консоли:

node% curl http://192.168.3.214
node% curl http://12.0.0.5

В выводе curl увидим стандартную приветственную страницу nginx.

Заметки на полях


В качестве заключения хочу описать пару важных моментов, о которые уже пришлось запнуться при проектировании системы. Связаны они были с работой kube-proxy, того самого модуля, который позволяет превратить разрозненный набор элементов в сервис.
PORTAL_NET. Сущность сама по себе интересная, предлагаю ознакомиться с тем, как же это реализовано.
Недолгие раскопки привели меня к осознанию простой, но эффективной модели, заглянем в вывод iptables-save:

-A PREROUTING -j KUBE-PORTALS-CONTAINER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT -j KUBE-PORTALS-HOST
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 10.0.42.0/24 ! -o docker0 -j MASQUERADE
-A KUBE-PORTALS-CONTAINER -d 10.0.0.2/32 -p tcp -m comment --comment "default/kubernetes:" -m tcp --dport 443 -j REDIRECT --to-ports 46041
-A KUBE-PORTALS-CONTAINER -d 10.0.0.1/32 -p tcp -m comment --comment "default/kubernetes-ro:" -m tcp --dport 80 -j REDIRECT --to-ports 58340
-A KUBE-PORTALS-HOST -d 10.0.0.2/32 -p tcp -m comment --comment "default/kubernetes:" -m tcp --dport 443 -j DNAT --to-destination 172.16.67.69:46041
-A KUBE-PORTALS-HOST -d 10.0.0.1/32 -p tcp -m comment --comment "default/kubernetes-ro:" -m tcp --dport 80 -j DNAT --to-destination 172.16.67.69:58340

Все запросы к IP-адресу сервиса попавшие в iptables заворачиваются на порт на котором слушает kube-proxy. В связи с этим возникает одна проблема: Kubernetes, сам по себе, не решает проблему связи с пользователем. Поэтому придётся решать этот вопрос внешними средствами, например:
  • gcloud — платная разработка от Google
  • bgp — с помощью анонсирования подсетей
  • IPVS
  • и прочие варианты, которых множество

SOURCE IP Так же. при настройке сервиса nginx мне пришлось столкнуться с интересной проблемой. Она выглядела как строчка в мануале: «Using the kube-proxy obscures the source-IP of a packet accessing a Service». Дословно — при использовании kube-proxy скрывает адрес источника пакета, а это значит, что всю обработку построенную на основе source-IP придётся проводить до использования kube-proxy.

На этом всё, спасибо за внимание
К сожалению, всю информацию, которую хочется передать, не получается уместить в одну статью.

Использование материалы:

Tags:
Hubs:
Total votes 25: ↑24 and ↓1+23
Comments86

Articles