Привет и с возвращением! Это вторая часть статьи о настройке кластера Kubernetes на «голом железе». Ранее мы настраивали НА-кластер Kubernetes с помощью внешнего etcd, схемы «ведущий-ведущий» и балансировки нагрузки. Ну а теперь пришло время настроить дополнительную среду и утилиты, чтобы сделать кластер полезнее и максимально приближенным к рабочему состоянию.
В этой части статьи мы сосредоточимся на настройке внутреннего балансировщика нагрузки сервисов кластера — это будет MetalLB. Также мы установим и настроим распределенное хранилище файлов между нашими рабочими нодами. Будем использовать GlusterFS для постоянных томов, которые доступны в Kubernetes.
После выполнения всех действий схема нашего кластера будет выглядеть следующим образом:
1. Настройка MetalLB в качестве внутреннего балансировщика нагрузки.
Несколько слов о MetalLB, непосредственно со страницы документа:
MetalLB — это реализация балансировщика нагрузки для кластеров Kubernetes на «голом железе» со стандартными протоколами маршрутизации.
Kubernetes не предлагает реализацию сетевых балансировщиков нагрузки (сервис типа LoadBalancer) для «голого железа». Все варианты реализации Network LB, с которыми поставляется Kubernetes, — это связующий код, он обращается к различным платформам IaaS (GCP, AWS, Azure и др.). Если вы не работаете на платформе, поддерживаемой IaaS (GCP, AWS, Azure и др.), LoadBalancer при создании останется в состоянии «ожидания» на неопределенный срок.
Операторы BM-серверов располагают двумя менее эффективными инструментами для ввода трафика пользователя в свои кластеры, сервисы «NodePort» и «externalIPs». Оба этих варианта имеют существенные недостатки в продакшене, что превращает BM-кластеры в граждан второго сорта в экосистеме Kubernetes.
MetalLB стремится исправить этот дисбаланс, предлагая реализацию Network LB, которая интегрируется со стандартным сетевым оборудованием, так что внешние сервисы на BM-кластерах также «просто работают» на максималках.
Таким образом, с помощью этого инструмента мы запускаем сервисы в кластере Kubernetes, используя балансировщик нагрузки, — за что огромное спасибо команде MetalLB. Процесс настройки действительно прост и понятен.
Ранее в примере мы выбрали для нужд нашего кластера подсеть 192.168.0.0/24. Теперь возьмем некоторую часть этой подсети для будущего балансировщика нагрузки.
Входим в систему машины с настроенной утилитой kubectl и запускаем:
control# kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.7.3/manifests/metallb.yaml
Это развернет MetalLB в кластере, в неймспейсе metallb-system
. Убедитесь, что все компоненты MetalLB функционируют нормально:
control# kubectl get pod --namespace=metallb-system
NAME READY STATUS RESTARTS AGE
controller-7cc9c87cfb-ctg7p 1/1 Running 0 5d3h
speaker-82qb5 1/1 Running 0 5d3h
speaker-h5jw7 1/1 Running 0 5d3h
speaker-r2fcg 1/1 Running 0 5d3h
Теперь настроим MetalLB с помощью configmap. В этом примере мы используем настройку Layer 2. За информацией о других вариантах настройки обращайтесь к документации MetalLB.
Создайте файл metallb-config.yaml в любой директории внутри выбранного IP-диапазона подсети нашего кластера:
control# vi metallb-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.0.240-192.168.0.250
И примените эту настройку:
control# kubectl apply -f metallb-config.yaml
При необходимости проверьте и измените configmap позднее:
control# kubectl describe configmaps -n metallb-system
control# kubectl edit configmap config -n metallb-system
Теперь у нас есть собственный настроенный локальный балансировщик нагрузки. Проверим, как он работает, на примере сервиса Nginx.
control# vi nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
control# vi nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
name: http
Затем создайте тестовый деплой и сервис Nginx:
control# kubectl apply -f nginx-deployment.yaml
control# kubectl apply -f nginx-service.yaml
А теперь — проверим результат:
control# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-deployment-6574bd76c-fxgxr 1/1 Running 0 19s
nginx-deployment-6574bd76c-rp857 1/1 Running 0 19s
nginx-deployment-6574bd76c-wgt9n 1/1 Running 0 19s
control# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.100.226.110 192.168.0.240 80:31604/TCP 107s
Создано 3 пода Nginx, как мы и указали в деплое ранее. Сервис Nginx будет направлять трафик на все эти поды по схеме циклической балансировки. И вы также можете увидеть внешний IP, полученный от нашего балансировщика нагрузки MetalLB.
Теперь попробуйте свернуться на IP-адрес 192.168.0.240, и вы увидите страницу Nginx index.html. Не забудьте удалить тестовое развертывание и сервис Nginx.
control# kubectl delete svc nginx
service "nginx" deleted
control# kubectl delete deployment nginx-deployment
deployment.extensions "nginx-deployment" deleted
Хорошо, на этом с MetalLB всё, идем дальше — настроим GlusterFS для томов Kubernetes.
2. Настройка GlusterFS с Heketi на рабочих нодах.
По факту, кластер Kubernetes невозможно юзать без томов внутри него. Как вы знаете, поды — эфемерны, т.е. их можно создать и удалить в любой момент. Все данные внутри них будут потеряны. Таким образом, в реальном кластере потребуется распределенное хранилище, чтобы обеспечить обмен настройками и данными между нодами и приложениями внутри него.
В Kubernetes тома доступны в различных вариантах, выбирайте подходящие. В этом примере я продемонстрирую, как создается хранилище GlusterFS для любых внутренних приложений, оно — как постоянные тома. Ранее я использовал для этого «системную» установку GlusterFS на всех рабочих нодах Kubernetes, а затем просто создавал тома типа hostPath в каталогах GlusterFS.
Теперь у нас есть новый удобный инструмент Heketi.
Несколько слов из документации Heketi:
Инфраструктура управления томами на основе RESTful для GlusterFS.
Heketi предлагает интерфейс управления RESTful, который можно использовать для управления жизненным циклом томов GlusterFS. Благодаря Heketi облачные сервисы, например OpenStack Manila, Kubernetes и OpenShift, могут динамически предоставлять тома GlusterFS с любым поддерживаемым типом надежности. Heketi автоматически определяет местоположение блоков в кластере, обеспечивая расположение блоков и их реплик в разных областях отказов. Heketi также поддерживает любое количество кластеров GlusterFS, позволяя облачным сервисам предлагать сетевое хранилище файлов, не ограничиваясь единственным кластером GlusterFS.
Звучит неплохо, и, кроме того, этот инструмент приблизит наш ВМ-кластер к большим облачным кластерам Kubernetes. В конце концов вы сможете создавать PersistentVolumeClaims, которые будут формироваться автоматически, и многое другое.
Можно взять дополнительные системные жесткие диски для настройки GlusterFS или просто создать несколько фиктивных блочных устройств. В этом примере я воспользуюсь вторым методом.
Создайте фиктивные блочные устройства на всех трех рабочих нодах:
worker1-3# dd if=/dev/zero of=/home/gluster/image bs=1M count=10000
Вы получите файл размером около 10 ГБ. Затем используйте losetup — чтобы добавить его в эти ноды, в качестве петлевого устройства:
worker1-3# losetup /dev/loop0 /home/gluster/image
Обратите внимание: если у вас уже есть некое петлевое устройство 0, то вам потребуется выбрать любой другой номер.
Я не пожалел времени и выяснил, почему Heketi не хочет работать должным образом. Поэтому, чтобы предотвратить любые проблемы в будущих конфигурациях, сначала убедимся, что мы загрузили модуль ядра dm_thin_pool и установили пакет glusterfs-client на всех рабочих нодах.
worker1-3# modprobe dm_thin_pool
worker1-3# apt-get update && apt-get -y install glusterfs-client
Хорошо, теперь нужно, чтобы на всех рабочих нодах присутствовали файл /home/gluster/image и устройство /dev/loop0. Не забудьте создать сервис systemd, которая будет автоматически запускать losetup и modprobe при каждой загрузке этих серверов.
worker1-3# vi /etc/systemd/system/loop_gluster.service
[Unit]
Description=Create the loopback device for GlusterFS
DefaultDependencies=false
Before=local-fs.target
After=systemd-udev-settle.service
Requires=systemd-udev-settle.service
[Service]
Type=oneshot
ExecStart=/bin/bash -c "modprobe dm_thin_pool && [ -b /dev/loop0 ] || losetup /dev/loop0 /home/gluster/image"
[Install]
WantedBy=local-fs.target
И включите ее:
worker1-3# systemctl enable /etc/systemd/system/loop_gluster.service
Created symlink /etc/systemd/system/local-fs.target.wants/loop_gluster.service → /etc/systemd/system/loop_gluster.service.
Подготовительные работы закончены, и мы готовы деплоить GlusterFS и Heketi в наш кластер. Для этого я буду использовать вот это прикольное руководство. Большинство команд запускаем с внешнего управляющего компьютера, а очень маленькие команды запускаются с любой мастер-ноды внутри кластера.
Сначала скопируем репозиторий и создадим DaemonSet GlusterFS:
control# git clone https://github.com/heketi/heketi
control# cd heketi/extras/kubernetes
control# kubectl create -f glusterfs-daemonset.json
Теперь пометим наши три рабочие ноды для GlusterFS; после нанесения меток на них будут созданы поды GlusterFS:
control# kubectl label node worker1 storagenode=glusterfs
control# kubectl label node worker2 storagenode=glusterfs
control# kubectl label node worker3 storagenode=glusterfs
control# kubectl get pod
NAME READY STATUS RESTARTS AGE
glusterfs-5dtdj 1/1 Running 0 1m6s
glusterfs-hzdll 1/1 Running 0 1m9s
glusterfs-p8r59 1/1 Running 0 2m1s
Теперь создадим учетную запись сервиса Heketi:
control# kubectl create -f heketi-service-account.json
Обеспечим для этой учетной записи сервиса возможность управлять подами gluster. Для этого создадим кластерную функцию, обязательную для нашей только что созданной учетной записи сервиса:
control# kubectl create clusterrolebinding heketi-gluster-admin --clusterrole=edit --serviceaccount=default:heketi-service-account
Теперь давайте создадим секретный ключ Kubernetes, который блокирует конфигурацию нашего экземпляра Heketi:
control# kubectl create secret generic heketi-config-secret --from-file=./heketi.json
Создайте первый исходный под Heketi, который мы используем для первых операций по настройке и впоследствии удалим:
control# kubectl create -f heketi-bootstrap.json
service "deploy-heketi" created
deployment "deploy-heketi" created
control# kubectl get pod
NAME READY STATUS RESTARTS AGE
deploy-heketi-1211581626-2jotm 1/1 Running 0 2m
glusterfs-5dtdj 1/1 Running 0 6m6s
glusterfs-hzdll 1/1 Running 0 6m9s
glusterfs-p8r59 1/1 Running 0 7m1s
После создания и запуска сервиса Bootstrap Heketi нам потребуется перейти на один из наших мастер-нодов, там мы запустим несколько команд, поскольку наш внешний управляющий нод не находится внутри нашего кластера, поэтому мы не можем получить доступ к работающим подам и внутренней сети кластера.
Сначала давайте загрузим утилиту heketi-client и скопируем ее в системную папку bin:
master1# wget https://github.com/heketi/heketi/releases/download/v8.0.0/heketi-client-v8.0.0.linux.amd64.tar.gz
master1# tar -xzvf ./heketi-client-v8.0.0.linux.amd64.tar.gz
master1# cp ./heketi-client/bin/heketi-cli /usr/local/bin/
master1# heketi-cli
heketi-cli v8.0.0
Теперь найдем IP-адрес пода heketi и экспортируем его как системную переменную:
master1# kubectl --kubeconfig /etc/kubernetes/admin.conf describe pod deploy-heketi-1211581626-2jotm
For me this pod have a 10.42.0.1 ip
master1# curl http://10.42.0.1:57598/hello
Handling connection for 57598
Hello from Heketi
master1# export HEKETI_CLI_SERVER=http://10.42.0.1:57598
Теперь давайте предоставим Heketi информацию о кластере GlusterFS, которым он должен управлять. Мы предоставляем ее через файл топологии. Топология — это манифест JSON со списком всех нод, дисков и кластеров, используемых GlusterFS.
ПРИМЕЧАНИЕ. Убедитесь, чтоhostnames/manage
указывает на точное название, как в разделеkubectl get node
, и чтоhostnames/storage
— это IP-адрес нодов хранилища.
master1:~/heketi-client# vi topology.json
{
"clusters": [
{
"nodes": [
{
"node": {
"hostnames": {
"manage": [
"worker1"
],
"storage": [
"192.168.0.7"
]
},
"zone": 1
},
"devices": [
"/dev/loop0"
]
},
{
"node": {
"hostnames": {
"manage": [
"worker2"
],
"storage": [
"192.168.0.8"
]
},
"zone": 1
},
"devices": [
"/dev/loop0"
]
},
{
"node": {
"hostnames": {
"manage": [
"worker3"
],
"storage": [
"192.168.0.9"
]
},
"zone": 1
},
"devices": [
"/dev/loop0"
]
}
]
}
]
}
Затем загрузите этот файл:
master1:~/heketi-client# heketi-cli topology load --json=topology.json
Creating cluster ... ID: e83467d0074414e3f59d3350a93901ef
Allowing file volumes on cluster.
Allowing block volumes on cluster.
Creating node worker1 ... ID: eea131d392b579a688a1c7e5a85e139c
Adding device /dev/loop0 ... OK
Creating node worker2 ... ID: 300ad5ff2e9476c3ba4ff69260afb234
Adding device /dev/loop0 ... OK
Creating node worker3 ... ID: 94ca798385c1099c531c8ba3fcc9f061
Adding device /dev/loop0 ... OK
Далее мы используем Heketi, чтобы предоставить тома для хранения базы данных. Название команды немного странное, но все в порядке. Также создайте хранилище heketi:
master1:~/heketi-client# heketi-cli setup-openshift-heketi-storage
master1:~/heketi-client# kubectl --kubeconfig /etc/kubernetes/admin.conf create -f heketi-storage.json
secret/heketi-storage-secret created
endpoints/heketi-storage-endpoints created
service/heketi-storage-endpoints created
job.batch/heketi-storage-copy-job created
Это все команды, которые нужно запустить с мастер-ноды. Вернемся к управляющей ноде и продолжим оттуда; в первую очередь убедитесь, что последняя запущенная команда успешно выполнена:
control# kubectl get pod
NAME READY STATUS RESTARTS AGE
glusterfs-5dtdj 1/1 Running 0 39h
glusterfs-hzdll 1/1 Running 0 39h
glusterfs-p8r59 1/1 Running 0 39h
heketi-storage-copy-job-txkql 0/1 Completed 0 69s
И задание heketi-storage-copy-job выполнено.
Если на данный момент на ваших рабочих нодах отсутствует установленный пакет glusterfs-client, то имеет место ошибка.
Пришло время удалить установочный файл Bootstrap Heketi и произвести небольшую очистку:
control# kubectl delete all,service,jobs,deployment,secret --selector="deploy-heketi"
На последнем этапе нам нужно создать долгосрочный экземпляр Heketi:
control# cd ./heketi/extras/kubernetes
control:~/heketi/extras/kubernetes# kubectl create -f heketi-deployment.json
secret/heketi-db-backup created
service/heketi created
deployment.extensions/heketi created
control# kubectl get pod
NAME READY STATUS RESTARTS AGE
glusterfs-5dtdj 1/1 Running 0 39h
glusterfs-hzdll 1/1 Running 0 39h
glusterfs-p8r59 1/1 Running 0 39h
heketi-b8c5f6554-knp7t 1/1 Running 0 22m
Если на данный момент на ваших рабочих нодах отсутствует установленный пакет glusterfs-client, то имеет место ошибка. А мы почти закончили, теперь база данных Heketi сохраняется в томе GlusterFS и не сбрасывается при каждом перезапуске пода Heketi.
Чтобы начать использовать кластер GlusterFS с динамическим выделением ресурсов, нам нужно создать StorageClass.
Сначала давайте найдем конечную точку хранилища Gluster, которая будет передана в StorageClass в качестве параметра (heketi-storage-endpoints):
control# kubectl get endpoints
NAME ENDPOINTS AGE
heketi 10.42.0.2:8080 2d16h
....... ... ..
Теперь создайте несколько файлов:
control# vi storage-class.yml
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://10.42.0.2:8080"
control# vi test-pvc.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: gluster1
annotations:
volume.beta.kubernetes.io/storage-class: "slow"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Используйте эти файлы для создания class и pvc:
control# kubectl create -f storage-class.yaml
storageclass "slow" created
control# kubectl get storageclass
NAME PROVISIONER AGE
slow kubernetes.io/glusterfs 2d8h
control# kubectl create -f test-pvc.yaml
persistentvolumeclaim "gluster1" created
control# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE gluster1 Bound pvc-27f733cd-1c77-11e9-bb07-7efe6b0e6fa5 1Gi RWO slow 2d8h
Мы также можем просмотреть том PV:
control# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-27f733cd-1c77-11e9-bb07-7efe6b0e6fa5 1Gi RWO Delete Bound default/gluster1 slow 2d8h
Теперь у нас есть динамически созданный том GlusterFS, связанный с PersistentVolumeClaim, и мы можем использовать это утверждение в любом поде.
Создайте простой под Nginx и протестируйте его:
control# vi nginx-test.yml
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod1
labels:
name: nginx-pod1
spec:
containers:
- name: nginx-pod1
image: gcr.io/google_containers/nginx-slim:0.8
ports:
- name: web
containerPort: 80
volumeMounts:
- name: gluster-vol1
mountPath: /usr/share/nginx/html
volumes:
- name: gluster-vol1
persistentVolumeClaim:
claimName: gluster1
control# kubectl create -f nginx-test.yaml
pod "nginx-pod1" created
Просмотрите под (подождите несколько минут, возможно, потребуется загрузить образ, если он еще не существует):
control# kubectl get pods
NAME READY STATUS RESTARTS AGE
glusterfs-5dtdj 1/1 Running 0 4d10h
glusterfs-hzdll 1/1 Running 0 4d10h
glusterfs-p8r59 1/1 Running 0 4d10h
heketi-b8c5f6554-knp7t 1/1 Running 0 2d18h
nginx-pod1 1/1 Running 0 47h
Теперь войдите в контейнер и создайте файл index.html:
control# kubectl exec -ti nginx-pod1 /bin/sh
# cd /usr/share/nginx/html
# echo 'Hello there from GlusterFS pod !!!' > index.html
# ls
index.html
# exit
Потребуется найти внутренний IP-адрес пода и свернуться в него из любой мастер-ноды:
master1# curl 10.40.0.1
Hello there from GlusterFS pod !!!
При этом мы просто тестируем наш новый постоянный том.
Несколько полезных команд для проверки нового кластера GlusterFS:heketi-cli cluster list
иheketi-cli volume list
. Их можно запустить на своем компьютере при наличии установленного heketi-cli. В данном примере это нода master1.
master1# heketi-cli cluster list
Clusters:
Id:e83467d0074414e3f59d3350a93901ef [file][block]
master1# heketi-cli volume list
Id:6fdb7fef361c82154a94736c8f9aa53e Cluster:e83467d0074414e3f59d3350a93901ef Name:vol_6fdb7fef361c82154a94736c8f9aa53e
Id:c6b69bd991b960f314f679afa4ad9644 Cluster:e83467d0074414e3f59d3350a93901ef Name:heketidbstorage
На этом этапе мы успешно настроили внутренний балансировщик нагрузки с файловым хранилищем, и наш кластер теперь ближе к рабочему состоянию.
В следующей части статьи мы сосредоточимся на создании системы мониторинга кластера, а также запустим в нем тестовый проект для использования всех настроенных нами ресурсов.
Оставайтесь на связи, и всего хорошего!