Есть множество правильных способов развернуть Kubernetes поверх существующей инфраструктуры. Разворачивая поверх OpenStack и vSphere я столкнулся с множеством багов, и в первую очередь, с подсистемой хранения. В результате родилась мысль, что должен быть унифицированный слой инфраструктуры, который бы не зависел от того, на какой именно платформе производится запуск.
Ниже я опишу как производится установка в полу-ручном режиме с ссылками на документацию, чтобы не дублировать текст. По мотивам можно написать Ansible скрипты и использовать их, например, с Terraform, как это делается в Kubespray. С той лишь разницей, что в Kubespray очень уж все усложнили по сравнению с kubeadm.
В связке Kubernetes-GlusterFS есть только одна большая проблема — нужно вручную создавать разделы. Но для решения этой проблемы существует фреймворк Heketi.
Исходные дынные
- 2 узла хранилища у каждого два диска/раздела — системный и данных
2 ядра/2Гб/16Гб+1Тб - 1 мастер Kubernetes
2 ядра/4Гб/128Гб+256Тб - 2 ноды Kubernetes
16 ядер/64Гб/128Гб+1Тб
Для простоты у всех машин по 1 сетевому интерфейсу и все объединены в плоскую сеть.
GlusterFS
Разработчики ориентируются на Red Hat семейство. Так что я ставил на CentOS 7. Ставится все очень просто по мануалу. Но, так как управлять разделами у нас будет Heketi — нам не нужно их форматировать.
Нам достаточно:
- убедиться, что на серверах статический IP
- стоит ssh-сервер на 22 порту
- сервера видят друг-друга по hostname (можно прописать в /etc/hosts)
- подключить репозиторий
Gluster Ubuntu PPAs
CentOS Wiki - установить серверные пакеты на все узлы хранилища
glusterfs gluster-cli glusterfs-libs glusterfs-server
- установить клиентские пакеты на ноды Kubernetes (они будут монтировать)
glusterfs-client
- форматировать диски в xfs и что-либо монтировать не нужно! (это будет делать Heketi)
- но нужно объединить серверы в пул
на первомgluster peer probe gluster2.example.com
и на второмgluster peer probe gluster1.example.com
- если вы планируете отдать целиком диск под хранилище, лучше на весь диск сделать primary раздел fdisk’ом — это может предотвратить некоторые проблемы в будущем
- на всякий случай нужно убрать с диска следы прошлых файловых систем
sudo wipefs --all --force /dev/sdb1
Пример установки на CentOS 7 (от root):
# yum install centos-release-gluster
# yum --enablerepo=centos-gluster*-test install glusterfs-server
# systemctl enable glusterd
# systemctl start glusterd
# firewall-cmd --zone=public --add-port=24007-24008/tcp --permanent
# firewall-cmd --zone=public --add-port=24009/tcp --permanent
# firewall-cmd --zone=public --add-service=nfs --add-service=samba --add-service=samba-client --permanent
# firewall-cmd --zone=public --add-port=111/tcp --add-port=139/tcp --add-port=445/tcp --add-port=965/tcp --add-port=2049/tcp \
> --add-port=38465-38469/tcp --add-port=631/tcp --add-port=111/udp --add-port=963/udp --add-port=49152-49251/tcp --permanent
# firewall-cmd --reload
# gluster peer probe <NODE-2-NAME>
# fdisk /dev/vdb
В fdisk: n(ew) -> Enter -> Enter ->Enter
Одни и те-же действия выполняются на всех узлах, все ноды в кластере равнозначны, но не нужно делать probe с той машины, которая уже добавлена.
Docker
Для работы Kubernetes важно. чтобы на всех узлах был установлен docker. Причем не любой, для Kubernetes 1.6 — это а 1.12. И именно эта версия есть в официальных репозиториях основных Linux дистрибутивов. В Ubuntu 16.04 это docker.io. Kubernetes 1.8 уже протестирован с 1.13 и 17.03.2, но есть баг с настройкой iptables. Так же не обнаружено проблем запуска на Docker 17.09.0.
Но есть одна особенность, нужно внимательно подходить к настройке драйвера хранилища. По умолчанию docker использует overlayfs/overlayfs2 — более-менее универсальное решение. В современных debian-based дистрибутивах появился драйвер aufs — но он не лучше overlayfs. В продакшене рекомендуется использовать отдельный диск и devicemapper для хранения образов, контейнеров и volume’ов. Это наиболее эффективный и стабильный вариант. Именно по-этому у меня на нодах Kubernetes дополнительный диск. Настраивается довольно быстро по инструкции. Но если у вас нет отделного диска, то не стоит использовать devicemapper-loop — он работает хуже, чем overlay. Есть еще zfs и btrf — эти решения не для каждого, применять их можно только в том случае, если вы сами пришли к этой необходимости.
Пример установки на Debian 9 (от root):
# apt-get install -y --no-install-recommends \
# apt-transport-https \
ca-certificates \
curl \
software-properties-common
# curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
# add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
$(lsb_release -cs) \
stable"
# apt-get update && apt-get install -y docker-ce=$(apt-cache madison docker-ce | grep 17.09 | head -1 | awk '{print $3}')
# systemctl enable docker
# systemctl start docker
# docker info
По умолчанию будет использовться драйвер overlayfs2.
И glusterfs:
# apt-get -y install glusterfs-client
Kubernetes
Я пробовал несколько различных способов поставить k8s: magnum, kube-up, kubespray и другие. В итоге пришел к выводу, что удобнее и надежнее всего kubeadm.
Поставить так-же просто, как нарисовать сову:
- Ставим kubectl на свое рабочее место (хотя можно и пакет из репозитория как в п.2)
# curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
# chmod +x ./kubectl
# mv ./kubectl /usr/local/bin/kubectl - Ставим kubeadm на все ноды
# curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
# cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
# apt-get update
# apt-get install -y kubelet kubeadm kubectl - Разворачиваем кластер
- На мастере
# kubeadm init --pod-network-cidr=10.244.0.0/16
сохраняем себе весь вывод, он пригодится в будущем
- Особенно настройки для kubectl
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config - И команду, которую нужно выполнить на нодах для включения в класетр (выполнять после установки сетевога плагина)
# kubeadm join --token <TOKEN> <MASTER_IP>:6443 --discovery-token-ca-cert-hash <TOKEN_HASH>
- Устанавливаем сетевой плагин
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.9.1/Documentation/kube-flannel.yml
- На момент написания, был баг Dokcer 17.03 с Kubernetes 1.8.4. На всех узлах нужно включить маршрутизацию вручную
# iptables -P FORWARD ACCEPT
# sysctl -w net.ipv4.ip_forward=1
# echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
- На мастере
Инструкция по ссылке проста и понятна. Нужно только не забыть, что на всех нодах должен быть установлен клиент glusterfs.
Начиная с версии Kubernetes 1.8, токен, по умолчанию, живет 24 часа. После этого, для подключения новых нод, нужно выпустить новый
# kubeadm token create
Нужно выбирать сетевой плагин. Сейчас наиболее стабильным и быстрым является flannel с vxlan бэк-эндом. С одной лишь оговоркой — только поверх физической сети. Vxlan поверх Vxlan (flannel в openstack) у меня выдавал 3Мбит/с по чети 1Гбит/сек.
Соответственно при инициализации мастера нужно не забыть указать соответствующий ключ
# kubeadm init --pod-network-cidr=10.244.0.0/16
Инструкция по установке сетевого плагина подразумевает, что файл деплоя будет браться с raw.githubusercontent.com. У меня были проблемы с доступом, так что я просто скачал kube-flannel.yml и kube-flannel-rbac.yml с репозитория на github. Кстати это так-же может быть полезно, если нужно подправить какие-либо параметры, например тот-же pod-network-cidr.
После инициализации мастера, создастся файл /etc/kubernetes/admin.conf. Его можно скопировать себе в $HOME/.kube/config чтобы работал kubectl.
Heketi
Это самая темная часть системы. Суть проста — слушает запросы по RESTful интерфейсу и если просят создать volume — подключается к нодам харнилища и создает нужные разделы. А после отвечает на REST-запрос путем к разделу в нотации GlusterFS.
Проблема только в том, что приложение, по-видимому, писали для какого-то конкретного случая и не особо потрудились документировать.
Подготовка узлов хранилища
Heketi должен иметь возможность создавать разделы на узлах хранения. Делать это он может двумя способами — используя Kubernetes exec — если узлы GlusterFS запущены в Kubernetes, и по ssh. В моем случае, когда у меня узлы GlusterFS — это отдельные машины, остается только вариант ssh.
А так как нужно работать с дисками, то нужны root права. Конечно можно было дать пользователя с sudo, но конкретно этот параметр разработчики не посчитали нужным вынести в переменные окружения. И авторизовываться придется по ключу, так как других вариантов нам тоже не предлагают.
- Заходим на один из узлов root’ом и генерируем ssh-ключ
# ssh-keygen
- Разрешаем подключаться с этим ключом к этой-же машине
# ssh-copy-id localhost
- Проделываем то-же самое с другими узлами
# ssh-copy-id gluster2.example.com
- Сохраняем себе приватный ключ, чтобы отдать его Heketi
# scp ~/.ssh/id_rsa master.example.com:
Помимо прочего, на узлах должен быть lvm и xfs.
Секрет
Теперь нужно положить id_rsa в секрет Kubernetes, чтобы позже его использовать в контейнере Heketi
$ kubectl create secret generic heketi-id-rsa --from-file=id_rsa
Deployment
Официальная инструкция, помимо описания установки gluster-daemonset, который нам не нужен, описывает деплой самого Heketi с использованием deploy-heketi-deployment.json в Kubernetes. Нам он не совсем подходит, так что я его немного переписал: изменил переменные окружения, настроил права сервис-акканута и привел в формат yaml.
apiVersion: v1 kind: ServiceAccount metadata: name: heketi-service-account --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-manager rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: heketi-can-change-secrets subjects: - kind: ServiceAccount name: heketi-service-account roleRef: kind: Role name: secret-manager apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: Secret metadata: labels: heketi: db glusterfs: heketi-service name: heketi-db-backup data: heketi.db: "" type: Opaque --- apiVersion: extensions/v1beta1 kind: Deployment metadata: annotations: description: Defines how to deploy Heketi labels: glusterfs: heketi-deployment name: heketi spec: replicas: 1 template: metadata: labels: glusterfs: heketi-pod name: heketi name: heketi spec: containers: - env: - name: HEKETI_EXECUTOR value: ssh - name: HEKETI_SSH_KEYFILE value: /key/id_rsa - name: HEKETI_SSH_USER value: root - name: HEKETI_BACKUP_DB_TO_KUBE_SECRET value: "true" image: heketi/heketi:latest imagePullPolicy: Always livenessProbe: httpGet: path: /hello port: 8080 scheme: HTTP initialDelaySeconds: 30 timeoutSeconds: 3 name: heketi ports: - containerPort: 8080 protocol: TCP readinessProbe: httpGet: path: /hello port: 8080 scheme: HTTP initialDelaySeconds: 3 timeoutSeconds: 3 volumeMounts: - mountPath: /backupdb name: heketi-db-secret - mountPath: /key name: id-rsa - mountPath: /var/lib/heketi name: db serviceAccount: heketi-service-account volumes: - name: db - name: heketi-db-secret secret: secretName: heketi-db-backup - name: id-rsa secret: secretName: heketi-id-rsa --- apiVersion: v1 kind: Service metadata: annotations: description: Exposes Heketi Service labels: deploy-heketi: support glusterfs: heketi-service name: heketi spec: ports: - name: heketi port: 8080 protocol: TCP targetPort: 8080 selector: name: heketi
Этот файл деплоится как и все остальное через
$ kubectl apply -f heketi.yaml
CLI
Теперь нужно передать параметры GlusetrFS кластера. Это делается с помощью утилиты командной строки. Нужно просто скачать бинарник со страницы релизов — для меня это linux.arm64, распаковать архив, найти в нем исполняемый файл и положить его в /usr/local/bin/.
- Установить heketi-cli
$ wget -O - https://github.com/heketi/heketi/releases/download/v5.0.0/heketi-client-v5.0.0.linux.amd64.tar.gz | tar -zxv
$ sudo mv heketi-client/bin/heketi-cli /usr/local/bin/ - Узнать имя пода (не обязательно)
kubectl get pod -lname=heketi -o name
- Прокинуть временно порт до него
kubectl port-forward $(kubectl get pod -lname=heketi -o name | cut -d"/" -f2) 8182:8080 &
- Сконфигурировать адрес сервера
export HEKETI_CLI_SERVER=http://localhost:8182
Теперь можно пользоваться утилитой командной строки. А можно добавить две строки в .bashrc
Есть даже простой автокомплит:
$ wget https://raw.githubusercontent.com/heketi/heketi/release/5/client/cli/go/heketi-cli.sh
$ source heketi-cli.sh
Топология
Теперь нужно объяснить Heketi какие у нас есть сервера и диски на них. Есть возможность на каждый сервис указывать два разных адреса: один для ssh, второй для GlusterFS.
{ "clusters": [ { "nodes": [ { "node": { "hostnames": { "manage": [ "<NODE 1 IP>" ], "storage": [ "<NODE 1 IP>" ] }, "zone": 1 }, "devices": [ "/dev/vdb1" ] }, { "node": { "hostnames": { "manage": [ "<NODE 2 IP>" ], "storage": [ "<NODE 2 IP>" ] }, "zone": 2 }, "devices": [ "/dev/vdb1" ] } ] } ] }
Есть объяснение того, как правильно использовать зоны: разные зоны нужно использовать, когда есть разные источники питания, что-то еще. Но так как у меня всего 2 сервера, я просто сделал их в разных зонах.
Дальше файл с зоной просто загружается в Heketi
$ heketi-cli topology load --json=topology.json
И если все в порядке, Heketi зайдет на сервера, настроит lvm на разделах и напишет clusterid. Он пригодится дальше.
Проверяем
$ heketi-cli volume create --size=1 --replica 2
Если все хорошо, тестовый образ можно удалить. Если случилась ошибка, чаше всего это «Out of space», нужно смотреть логи pod’а:
$ kubectl logs $(kubectl get pod -lname=heketi -o name | cut -d"/" -f2)
Storage Class
Теперь осталось объяснить Kubernetes как использовать наше хранилище.
Нужно узнать IP сервиса Heketi
$ kubectl get svc heketi
Колонка CLUSTER-IP. Этот IP нужно подставить в файл storageclass.yaml, как и clusterid
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: gfs-mirror provisioner: kubernetes.io/glusterfs parameters: resturl: "http://10.96.97.101:8080" clusterid: "1530dd1c0d44182ab07390890b1950a0" volumetype: "replicate:2"
$ kubectl apply -f storageclass-mirror.yaml
В дополнение создадим еще одно хранилище, но без отказоустойчивости, зададим его для использования по умолчанию:
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: standard annotations: storageclass.kubernetes.io/is-default-class: true provisioner: kubernetes.io/glusterfs parameters: resturl: "http://10.96.97.101:8080" clusterid: "1530dd1c0d44182ab07390890b1950a0" volumetype: "none"
$ kubectl apply -f storageclass-distributed.yaml
Подробнее про настройку класса можно почитать на kubernetes.io.
Все, теперь можно использовать наше хранилище
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: testvolumeclaim spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: gfs-mirror --- kind: Pod apiVersion: v1 metadata: name: testpod labels: name: testpod spec: containers: - name: test image: busybox command: ["sleep"] args: ["31536000"] volumeMounts: - mountPath: "/volume" name: vol volumes: - name: vol persistentVolumeClaim: claimName: testvolumeclaim
$ kubectl apply -f test-pv.yaml
Можно зайти внутрь
$ kubectl exec -ti testpod sh
И посмотреть скорость, например:
$ dd if=/dev/urandom of=/test1 bs=1M count=100
$ dd if=/test1 of=/dev/null bs=1M count=100
$ dd if=/dev/urandom of=/volume/test2 bs=1M count=100
$ dd if=/volume/test2 of=/dev/null bs=1M count=100
Уроборос
А как же быть с хранением самой базы Heketi? Если ее потерять, то придется заново форматировать диски. Данные, конечно, вытащить будет не сложно, настраивать все нужно заново.
Бэкап базы (топология, кластер, ноды, диски) периодически сохраняется в kubernetes secret heketi-db-backup. А при запуске пода из секрета загружается. Еще можно, на всякий случай, сохранить себе копию базы:
$ kubectl cp $(kubectl get pod -lname=heketi -o name | cut -d"/" -f2):/var/lib/heketi/heketi.db ./heketi.db
Ну и самое лучшее место для хранения базы — GlusterFS.
Можно было бы использовать
heketi-cli setup-openshift-heketi-storage
но для этого требуется не менее 3 нод. Так что придется в полу-ручном режиме.
Создадим контейнер чтобы использовать его диск
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: heketivolumeclaim spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: gfs-mirror --- kind: Pod apiVersion: v1 metadata: name: fakepod labels: name: fakepod spec: containers: - name: bb image: busybox volumeMounts: - mountPath: "/volume" name: vol volumes: - name: vol persistentVolumeClaim: claimName: heketivolumeclaim
$ kubectl apply -f create-pvc-for-heketi.yaml
Помимо claim здесь еще присутствует pod, он нужен, чтобы k8s и heketi отработали связывание pvc-pv-heketi пока heketi еще работает. При обновлении деплоя это может не получится.
Удаляем временный pod
$ kubectl delete pod fakepod
На всякий случай сохраняем БД
$ kubectl cp $(kubectl get pod -lname=heketi -o name | cut -d"/" -f2):/var/lib/heketi/heketi.db ./heketi.db
Обновляем deploy heketi
Разница только в добавлении двух строк описания volume db, теперь ссылаемся на созданный ранее pvc
apiVersion: extensions/v1beta1 kind: Deployment metadata: annotations: description: Defines how to deploy Heketi labels: glusterfs: heketi-deployment name: heketi spec: replicas: 1 template: metadata: labels: glusterfs: heketi-pod name: heketi name: heketi spec: containers: - env: - name: HEKETI_EXECUTOR value: ssh - name: HEKETI_SSH_KEYFILE value: /key/id_rsa - name: HEKETI_SSH_USER value: root - name: HEKETI_BACKUP_DB_TO_KUBE_SECRET value: "true" image: heketi/heketi:latest imagePullPolicy: Always livenessProbe: httpGet: path: /hello port: 8080 scheme: HTTP initialDelaySeconds: 30 timeoutSeconds: 3 name: heketi ports: - containerPort: 8080 protocol: TCP readinessProbe: httpGet: path: /hello port: 8080 scheme: HTTP initialDelaySeconds: 3 timeoutSeconds: 3 volumeMounts: - mountPath: /backupdb name: heketi-db-secret - mountPath: /key name: id-rsa - mountPath: /var/lib/heketi name: db serviceAccount: heketi-service-account volumes: - name: db persistentVolumeClaim: claimName: heketivolumeclaim - name: heketi-db-secret secret: secretName: heketi-db-backup - name: id-rsa secret: secretName: heketi-id-rsa
$ kubectl delete deployment heketi
$ kubectl apply -f heketi-deployment-wih-pvs.yaml
Теперь у нас есть полноценное сетевой хранилище.
P.S. Написано по мотивам Heketi v5.0.0-5-gb005e0f-release-5. В 4 версии еще нельзя было через переменную окружения включить бэкап базы в secret. А в 5-й версии, при использовании pvc и storageclass, размер volume на 1Гб больше, чем запрашивали.
0 Комментарии。