[Перевод] Развертывание распределенного приложения Kubernetes и экземпляр приложения для распознавания лиц

задняя часть база данных сервер API Программа перевода самородков Kubernetes NSQ

Ладно, чувак, давай просто успокоимся. Далее следует долгое, но полное надежд и интересное путешествие.

я используюKubernetesРазвертывание распределенных приложений. Я пытаюсь создать приложение, похожее на реальное приложение. Очевидно, из-за ограниченности времени и сил мне пришлось игнорировать некоторые детали.

Я сосредоточусь на Kubernetes и развертывании приложений.

Готовы перейти к делу?

О приложении

Резюме

kube overview

Приложение состоит из шести частей. Репозиторий кода можно найти здесь:Kube Cluster Sample.

Это сервис распознавания лиц, который распознает изображения людей и сравнивает их с известными людьми. Результаты распознавания будут отображаться в виде таблицы в простом интерфейсе, и вы сможете увидеть, какие символы на распознаваемых изображениях. Процесс работы приложения выглядит следующим образом:получательОтправьте запрос, путь к изображению должен быть указан в запросе. Эти изображения могут храниться в чем-то вроде NFS, а получатель будет хранить путь к изображению в БД (MySQL). Наконец, отправьте запрос на обработку в очередь с идентификатором сохраненного изображения. использовать здесьNSQкак очередь(Примечание переводчика: NSQ — это распределенная платформа для обмена сообщениями в реальном времени, основанная на языке Go).

период,Обработка изображенияСлужба постоянно отслеживает очередь выполнения заданий. Поток обработки состоит из следующих шагов: получение идентификатора, загрузка изображения, наконец, передачаgRPCОтправить изображение в Pythonраспознавание лицапрограмма для бэкенда. Если распознавание прошло успешно, серверная часть вернет имя, соответствующее человеку на этом изображении. Затем процессор изображений обновляет поле идентификатора человека в записи изображения и помечает изображение как «успешно обработанное». Если распознавание не удалось, изображение останется «ожидающим». Если в процессе распознавания произойдет сбой, изображение будет помечено как «неудачное».

Обработку неудачных изображений можно повторить с помощью задания cron, например:

Так как это работает? Посмотрим .

получатель

Служба получателя является отправной точкой всего процесса. Этот API принимает запросы в следующем формате:

curl -d '{"path":"/unknown_images/unknown0001.jpg"}' http://127.0.0.1:8000/image/post

В этом примере приемник хранит пути изображения путем совместного использования кластеров базы данных. Когда база данных сохраненного пути изображения успешно, экземпляр приемника может принимать идентификатор изображения из службы базы данных. Это приложение основано на модели, которая обеспечивает объекты объекта на слое постоянства. Как только идентификатор генерируется, приемник отправляет сообщение NSQ. Здесь работа приемника завершена.

процессор изображений

Вот захватывающее начало. Когда процессор изображений запускается в первый раз, он создает две подпрограммы Go. они есть:

Consume

Это потребитель NSQ. У него есть три необходимых рабочих места. Во-первых, он может прослушивать сообщения в очереди. Во-вторых, когда он получает сообщение, он добавляет полученный идентификатор к потокобезопасному фрагменту идентификатора, обрабатываемому второй процедурой. Наконец, проходитsync.ConditionСообщите второй сопрограмме, что есть работа.

ProcessImages

Эта подпрограмма обрабатывает срезы идентификаторов до тех пор, пока они не будут полностью исчерпаны. Как только слайс будет использован, подпрограмма приостановится вместо ожидания канала. Вот шаги для обработки одного идентификатора:

  • Установите gRPC-соединение со службой распознавания лиц (объяснение приведено в разделе распознавания лиц ниже).
  • Получить записи изображений из базы данных
  • настраиватьвыключательдве функции
    • Функция 1: основная функция, которая запускает вызов метода RPC.
    • Функция 2: проверка работоспособности эхо-сигнала автоматического выключателя.
  • Вызовите функцию 1, чтобы отправить путь к изображению в службу распознавания лиц. Служба должна иметь доступ к этому пути. Предпочтительно файлообменник типа NFS
  • Если вызов не удался, обновите поле состояния записи изображения на «FAILED PROCESSING».
  • В случае успеха будет возвращено имя человека, связанного с изображением в базе данных. Он выполнит запрос на соединение SQL, чтобы получить соответствующий идентификатор человека.
  • Обновите поле состояния записи изображения в базе данных, чтобы оно было «ОБРАБОТАНО», а поле человека — идентификатором идентифицированного человека.

Эта служба может быть реплицирована, другими словами, несколько служб могут работать одновременно.

выключатель

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

Вот как это работает:

kube circuit

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

внешний интерфейс

Это просто табличное представление, которое использует собственный HTML-шаблон Go для отображения списка изображений.

распознавание лица

Вот где происходит магия идентификации. В погоне за гибкостью я решил инкапсулировать функцию распознавания лиц в сервис на основе gRPC. Сначала я собирался написать это на Go, но понял, что проще сделать это на Python. На самом деле, помимо кода gPRC, часть распознавания лиц требует около 7 строк кода Python. Я использую отличную библиотеку, которая содержит все вызовы OpenCV, реализованные на C.распознавание лица. Подписание соглашения об использовании API здесь означает, что я могу изменить реализацию кода распознавания лиц в любое время в соответствии с лицензией соглашения.

Обратите внимание, что в Go есть библиотека для разработки OpenCV. Я почти использовал его, но он не включал вызов C-реализации OpenCV. Эта библиотека называетсяGoCV, вы можете узнать. В них есть несколько действительно замечательных вещей, таких как обработка обратной связи камеры в реальном времени, которую можно сделать всего несколькими строками кода.

Библиотека Python по своей сути проста. Теперь у нас есть набор известных изображений людей, и мы называем ихhannibal_1.jpg, hannibal_2.jpg, gergely_1.jpg, john_doe.jpgположить в папку. В базе есть две таблицы, а именноpersonиperson_images. Они выглядят так:

+----+----------+
| id | name     |
+----+----------+
|  1 | Gergely  |
|  2 | John Doe |
|  3 | Hannibal |
+----+----------+
+----+----------------+-----------+
| id | image_name     | person_id |
+----+----------------+-----------+
|  1 | hannibal_1.jpg |         3 |
|  2 | hannibal_2.jpg |         3 |
+----+----------------+-----------+

Библиотека распознавания лиц возвращает имена из изображений известных людей, которые соответствуют людям на неизвестных изображениях. После этого простой запрос на соединение, подобный этому, вернет информацию об идентифицированном человеке.

select person.name, person.id from person inner join person_images as pi on person.id = pi.person_id where image_name = 'hannibal_2.jpg';

Вызов gRPC возвращает идентификатор человека и используется для изменения записи изображения, которое необходимо распознать.personзначение этого столбца.

NSQ

NSQ — отличная очередь на основе Go. Он масштабируется и занимает минимум места в системе. У него также есть служба поиска, которую потребители используют для получения сообщений, и демон, который отправители используют при отправке сообщений.

Философия NSQ заключается в том, что демон должен работать вместе с приложением-отправителем. Таким образом, отправитель будет отправлять только на локальный хост. Но демоны подключены к службе поиска, и именно так они реализуют глобальную очередь.

Это означает, сколько существует отправителей и сколько демонов NSQ необходимо развернуть. Поскольку требования к ресурсам демона невелики, это не влияет на потребности основного приложения.

настроить

Чтобы быть максимально гибким и использовать ConfigSets Kubernetes, я использую файлы .env в разработке для хранения конфигурации, такой как расположение служб базы данных или где искать NSQ. В производственной среде это означает, что в среде Kubernetes я буду использовать переменные среды.

Обзор приложений для распознавания лиц

Это архитектура приложения, которое мы собираемся развернуть. Все его компоненты изменяемы и могут быть связаны только через базы данных, очереди и gRPC. Это важно при развертывании распределенных приложений из-за того, как работает механизм обновления. Я расскажу об этом в разделе «Развертывание».

Развертывание приложений в Kubernetes

База

КакиедаКубернетес?

Здесь я расскажу о некоторых основах, но не буду вдаваться в подробности. Если вы хотите узнать больше, прочитайте всю книгу:Kubernetes Up And Running. Кроме того, если вы достаточно смелы, вы можете взглянуть на эту документацию:Kubernetes Documentation.

Kubernetes — это контейнерная платформа для управления сервисами и приложениями. Его легко расширять, он управляет целой кучей контейнеров и, что наиболее важно, легко настраивается с помощью файлов шаблонов на основе yaml. Люди часто сравнивают Kubernetes с кластером Docker, но Kubernetes — это гораздо больше! Например: он может управлять разными контейнерами. Вы можете использовать Kubernetes для управления и организации LXC, и таким же образом вы можете управлять Docker. Он обеспечивает уровень выше управления кластерами развернутых служб и приложений. Как насчет этого? Давайте кратко рассмотрим строительные блоки Kubernetes.

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

Одной из основ Kubernetes является использование меток и аннотаций для всех компонентов. Сервисы, развертывания, наборы реплик, наборы демонов, все можно пометить. Рассмотрим следующую ситуацию. Чтобы определить, какой модуль принадлежит какому приложению, мы будем использоватьapp:myappТег. Предположим, вы развернули два контейнера этого приложения, если вы удалите метку с одного из контейнеровapp, Kubernetes обнаружит только одну метку, поэтому запустит новую.myappпример.

Kubernetes Cluster

Для работы Kubernetes требуется кластер Kubernetes. Настройка кластера может быть очень болезненной, но, к счастью, помощь всегда под рукой. Minikube настраивает нам кластер с одним узлом локально. У AWS есть тестовая служба, работающая как кластер Kubernetes, где единственное, что вам нужно сделать, — это запросить узлы и определить свое развертывание. Документация по компонентам кластера Kubernetes находится здесь:Kubernetes Cluster Components.

Nodes

Узел — это рабочий хост. Это может быть что угодно, например физические машины, виртуальные машины и виртуальные ресурсы, предоставляемые различными облачными сервисами.

Pods

Поды — это логически сгруппированные контейнеры, что означает, что под может содержать несколько контейнеров. При создании Pod получает собственный DNS и виртуальный IP-адрес, поэтому Kubernetes может сбалансировать для него трафик. Вам редко нужно иметь дело с контейнером напрямую, даже при отладке (например, при просмотре журналов) он часто вызываетсяkubectl logs deployment / your-app -fвместо того, чтобы смотреть на конкретный контейнер. Хотя можно позвонить-c container_name.-fПараметр постоянно отображает конец файла журнала.

Deployments

Когда в Kubernetes создается ресурс любого типа, он будет использовать развертывание за кулисами. Объект Deployment описывает желаемое состояние текущего приложения. Эту штуку можно использовать для изменения состояния пода или сервиса, обновления или развертывания новой версии приложения. Вы не управляете ReplicaSet напрямую (как описано ниже), но можете управлять объектом Deployment для создания ReplicaSet и управления им.

Services

По умолчанию поды получают IP-адрес. Однако, поскольку поды в Kubernetes — непостоянная вещь, вам нужно что-то более постоянное. Очереди, mysql, внутренние API, внешние интерфейсы, они должны работать долго и должны находиться за статическим, неизменяемым IP-адресом или, предпочтительно, записью DNS.

С этой целью Kubernetes предоставляет службы, которые определяют шаблоны доступности. Балансировка нагрузки, простой IP или внутренний DNS.

Как Kubernetes узнает, правильно ли работает служба? Вы можете настроить проверки работоспособности и проверки доступности. Проверка работоспособности проверит, работает ли контейнер, но это не означает, что ваша служба работает. Для этого вам необходимо выполнить проверку доступности доступных конечных точек в вашем приложении.

Так как Службы очень важны, я предлагаю вам прочитать их позже здесь:Services. Имейте в виду, что эта часть документации довольно объемная: 24 страницы формата A4, посвященные работе в сети, службам и обнаружению. Но очень важно решить, хотите ли вы использовать Kubernetes в производственной среде.

DNS / Service Discovery

Если вы создаете службу в кластере, служба получит записи DNS в Kubernetes, предоставленные специальными объектами Kubernetes Deployments, называемыми kube-proxy и kube-dns. Эти два объекта обеспечивают обнаружение служб в кластере. Если у вас запущен сервис mysql и установленclusterIP:none, тогда все в кластере смогут пинговатьmysql.default.svc.cluster.localдля доступа к сервису. в:

  • mysql- название услуги
  • default- имя пространства имен
  • svc- сам сервис
  • cluster.local– доменное имя локального кластера

Имя домена может быть изменено путем настройки. Для доступа к сервисам за пределами кластера у вас должен быть DNS-провайдер, а затем использовать Nginx (например) для привязки IP-адресов к записям. Общедоступный IP-адрес службы можно запросить с помощью следующей команды:

  • Порт узла –kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services mysql
  • Балансировщик нагрузки –kubectl get -o jsonpath="{.spec.ports[0].LoadBalancer}" services mysql

Template Files

Как и Docker Compose, TerraForm или другие инструменты управления службами, Kubernetes также предоставляет инфраструктуру для настройки шаблонов. Это означает, что вам редко нужно делать что-либо вручную.

Например, см. следующий шаблон, который использует файл yaml для настройки развертывания nginx:

apiVersion: apps/v1
kind: Deployment #(1)
metadata: #(2)
    name: nginx-deployment
    labels: #(3)
    app: nginx
spec: #(4)
    replicas: 3 #(5)
    selector:
    matchLabels:
        app: nginx
    template:
    metadata:
        labels:
        app: nginx
    spec:
        containers: #(6)
        - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

В этом простом развертывании мы сделали следующее:

  • (1) использоватьkindатрибут определяет тип шаблона
  • (2) Добавьте метаданные, идентифицирующие это развертывание, и создайте каждому ресурсу метку (3)
  • (4) Затем опишите требуемую спецификацию состояния.
  • (5) Для приложений nginx включены 3replicas
  • (6) Это определение шаблона для контейнера. Настроенный здесь модуль содержит контейнер с именем nginx. Среди них используется образ nginx версии 1.7.9 (в данном примере используется Docker), а выставленный номер порта: 80

ReplicaSet

ReplicaSet — это низкоуровневый менеджер репликации. Это гарантирует, что для приложения выполняется правильное количество репликаций. Однако при развертывании на более высоком уровне необходимо всегда управлять наборами реплик. Вам редко нужно использовать ReplicaSets напрямую, если только у вас нет особого случая, когда вам нужно контролировать детали репликации.

DaemonSet

Помните, я говорил о том, что Kubernetes продолжает использовать метки? DaemonSet — это контроллер, который гарантирует, что приложения-демоны всегда будут работать на узлах с определенными метками.

Например: Вы хотите, чтобы все были отмеченыloggerилиmission_criticalУзел запускает демон службы регистрации/аудита. Затем вы создаете DaemonSet и даете ему имяloggerилиmission_criticalселектор узла. Kubernetes будет искать узлы с этой меткой. Всегда следите за тем, чтобы на нем был запущен экземпляр демона. Поэтому каждый экземпляр, работающий на этом узле, может получить локальный доступ к демону.

В моем приложении демон NSQ может быть DaemonSet. Чтобы убедиться, что он работает на узле с компонентом-приемником, я используюreceiverотметить узел и использоватьreceiverСелектор приложений указывает DaemonSet.

DaemonSet обладает всеми преимуществами ReplicaSet. Он расширяемый и управляется Kubernetes. Это означает, что Kube обрабатывает все события жизненного цикла, гарантируя, что он никогда не умрет, а когда это произойдет, он будет немедленно заменен.

Scaling

Масштабирование в Kubernetes — это просто. Наборы реплик отвечают за управление количеством экземпляров пода, как видно из развертывания nginx, с настройкой «реплики: 3». Мы должны написать наше приложение таким образом, чтобы Kubernetes мог запускать несколько его копий.

Конечно, эти настройки огромны. Вы можете указать, какие репликации должны выполняться на каких узлах или как долго ждать появления экземпляров в разное время ожидания. Подробнее об этой теме можно прочитать здесь:Horizontal Scalingи здесь: [Интерактивное масштабирование с Kubernetes](https://kubernetes.io/docs/tutorials/kubernetes-basics/scale-interactive/) и, конечно же,ReplicaSetПодробная информация об элементах управления В Kubernetes возможно любое масштабирование.

Резюме Kubernetes

Это удобный инструмент для работы с оркестровкой контейнеров. Его базовая единица — поды с многоуровневой архитектурой. На верхнем уровне находятся развертывания, через которые обрабатываются все остальные ресурсы. Он легко настраивается и предоставляет один API для всех вызовов, поэтому его проще запускать.kubectl, вы можете написать собственную логику для отправки информации в Kubernetes API.

Kubernetes теперь поддерживает всех основных облачных провайдеров, это полностью открытый исходный код, не стесняйтесь вносить свой вклад! Если вы хотите глубже понять, как это работает, ознакомьтесь с кодом:Kubernetes on Github.

Minikube

я используюMinikube. Minikube — это локальный эмулятор кластера Kubernetes. Хотя симуляция нескольких узлов не очень хороша, это здорово, если вы просто собираетесь учиться и метаться локально без каких-либо накладных расходов. Minikube основан на виртуальной машине и при необходимости может быть настроен с помощью VirtualBox и т. д.

Все файлы шаблонов kube, которые я буду использовать, можно найти здесь:Kube files.

**Примечание.** Если позже вы захотите использовать масштабирование, но заметите, что репликация всегда находится в состоянии «Ожидание», помните, что minikube использует только один узел. Это может не разрешить несколько реплик на одном узле или просто явно исчерпать ресурсы. Вы можете проверить доступные ресурсы с помощью:

kubectl get nodes -o yaml

Создать контейнер

Kubernetes поддерживает большинство контейнеров. Я собираюсь использовать Докер. Для всех сервисов, которые я создаю, Dockerfile включен в репозиторий. Я призываю вас изучить их. Большинство из них простые. Для службы Go я использую недавно представленную многоэтапную сборку. Сервис Go основан на Alpine Linux. Сервис распознавания лиц реализован на Python. NSQ и MySQL используют свои собственные контейнеры.

контекст

Kubernetes использует пространства имен. Если вы не укажете какое-либо пространство имен, оно будет использоватьdefaultПространства имен. Я постоянно устанавливаю контекст, чтобы не загрязнять пространство имен по умолчанию. Ты можешь это сделать:

❯ kubectl config set-context kube-face-cluster --namespace=face
Context "kube-face-cluster" created.

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

❯ kubectl config use-context kube-face-cluster
Switched to context "kube-face-cluster".

После этого всеkubectlКоманда будет использовать пространство именface.

Развернуть приложение

Обзор модулей и сервисов:

kube deployed

MySQL

Первая служба, которую я хочу развернуть, — это моя база данных.

Я использую пример Kubernetes, расположенный здесьKube MySQL, это соответствует моим потребностям. Обратите внимание, что в файле конфигурации используется открытый текстовый пароль. Я буду следовать инструкциям здесьKubernetes SecretsСоблюдайте некоторые меры безопасности.

Как описано в документации, я создал файл ключа локально, используя секретный файл yaml.

apiVersion: v1
kind: Secret
metadata:
    name: kube-face-secret
type: Opaque
data:
    mysql_password: base64codehere

Я создал код base64 с помощью следующей команды:

echo -n "ubersecurepassword" | base64

Вот что вы увидите в моем файле развертывания yaml:

...
- name: MYSQL_ROOT_PASSWORD
    valueFrom:
    secretKeyRef:
        name: kube-face-secret
        key: mysql_password
...

Также стоит упомянуть: он использует том для хранения базы данных. объем определяется следующим образом:

...
        volumeMounts:
        - name: mysql-persistent-storage
            mountPath: /var/lib/mysql
...
        volumes:
        - name: mysql-persistent-storage
        persistentVolumeClaim:
            claimName: mysql-pv-claim
...

presistentVolumeClainВот ключ. Это говорит Kubernetes, что этому ресурсу нужен постоянный том. Как обеспечить это абстрагировано от пользователя. Вы можете быть уверены, что Kubernetes предоставит объемы. Это похоже на стручки. Чтобы узнать подробности, ознакомьтесь с этим документом:Kubernetes Persistent Volumes.

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

kubectl apply -f mysql.yaml

applyвсе ещеcreate? короче,applyсчитаются декларативными командами настройки объекта, аcreateявляется обязательным. Это означает, что теперь «создать» обычно для одной из задач, например, запустить что-то или создать развертывание. Принимая во внимание, что при использовании приложения пользователь не определяет действие, которое необходимо предпринять. Это будет определено Kubernetes на основе текущего состояния кластера. Поэтому, когда нет имениmysqlслужба, когда я звонюapply -f mysql.yaml, он создаст службу. При повторном запуске Kubernetes ничего не сделает. Однако, если я снова побегуcreate, выдаст ошибку, что служба уже создана.

Для получения дополнительной информации ознакомьтесь со следующими документами:Kubernetes Object Management, [Императивная конфигурация](https://kubernetes.io/docs/concepts/overview/object-management-kubectl/imperative-config/),Declarative Configuration).

Чтобы увидеть информацию о прогрессе, запустите:

# 描述整个进程
kubectl describe deployment mysql
# 仅显示 pod
kubectl get pods -l app=mysql

Вывод должен быть похож на этот:

...
    Type           Status  Reason
    ----           ------  ------
    Available      True    MinimumReplicasAvailable
    Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   mysql-55cd6b9f47 (1/1 replicas created)
...

или вget podsв случае:

NAME                     READY     STATUS    RESTARTS   AGE
mysql-78dbbd9c49-k6sdv   1/1       Running   0          18s

Чтобы протестировать экземпляр, запустите следующий фрагмент кода:

kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -pyourpasswordhere

**Кое-что для понимания**: если вы сейчас измените свой пароль, повторного применения файла yaml для обновления контейнера будет недостаточно. Поскольку база данных сохраняется, пароль не изменится, вы должны использоватьkubectl delete -f mysql.yamlУдалите все развертывание.

бегатьshow databasesВы должны увидеть следующее.

If you don't see a command prompt, try pressing enter.
mysql>
mysql>
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| kube               |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

mysql> exit
Bye

Вы также заметите, что я установил здесь файл:Database Setup SQLв контейнер. Контейнер MySQL делает это автоматически. Этот файл будет инициализировать некоторые данные и схему, которую я собираюсь использовать.

объем определяется следующим образом:

    volumeMounts:
    - name: mysql-persistent-storage
    mountPath: /var/lib/mysql
    - name: bootstrap-script
    mountPath: /docker-entrypoint-initdb.d/database_setup.sql
volumes:
- name: mysql-persistent-storage
    persistentVolumeClaim:
    claimName: mysql-pv-claim
- name: bootstrap-script
    hostPath:
    path: /Users/hannibal/golang/src/github.com/Skarlso/kube-cluster-sample/database_setup.sql
    type: File

Чтобы проверить, успешно ли загрузочный скрипт, выполните следующую команду:

~/golang/src/github.com/Skarlso/kube-cluster-sample/kube_files master*
❯ kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -uroot -pyourpasswordhere kube
If you don't see a command prompt, try pressing enter.

mysql> show tables;
+----------------+
| Tables_in_kube |
+----------------+
| images         |
| person         |
| person_images  |
+----------------+
3 rows in set (0.00 sec)

mysql>

На этом настройка службы базы данных завершена. Журналы для этой службы можно просмотреть с помощью следующей команды:

kubectl logs deployment/mysql -f

Поиск NSQ

Поиск NSQ будет работать как внутренняя служба, он не должен быть доступен извне. Поэтому я установилclusterIP:None, который сообщает Kubernetes, что служба не имеет головы. Это означает, что он не будет сбалансирован по нагрузке и не будет отдельной IP-службой. DNS будет основываться на селекторах услуг.

Селектор поиска NSQ, который мы определяем, выглядит следующим образом:

selector:
matchLabels:
    app: nsqlookup

Таким образом, внутренний DNS будет выглядеть так:nsqlookup.default.svc.cluster.local.

Безголовый сервис подробно описан здесь:Headless Service.

В основном это то же самое, что и MySQL, только немного измененное. Как уже упоминалось, я использую собственный образ Docker от NSQ, который называетсяnsqio / nsq. Все команды nsq есть, так что nsqd тоже будет использовать это зеркало, только команды другие. Для nsqlookupd команда:

command: ["/nsqlookupd"]
args: ["--broadcast-address=nsqlookup.default.svc.cluster.local"]

вы можете спросить, что такое--broadcast-address? По умолчанию nsqlookup будет использоватьhostnameВ качестве широковещательного адреса, когда потребитель запускает обратный вызов, он пытается подключиться к чему-то вродеhttp://nsqlookup-234kf-asdf:4161/lookup?topics=imageURL. Пожалуйста, обрати вниманиеnsqlookup-234kf-asdfэто имя хоста контейнера. Установив широковещательный адрес для внутреннего DNS, обратный вызов будет:http://nsqlookup.default.svc.cluster.local:4161/lookup?topic=images. Это будет работать так, как ожидалось.

Для поиска NSQ также требуется переадресация двух портов: один для широковещательной рассылки и один для обратных вызовов nsqd. Они отображаются в Dockerfile, а затем используются в шаблонах Kubernetes. нравится:

В шаблоне контейнера:

ports:
- containerPort: 4160
    hostPort: 4160
- containerPort: 4161
    hostPort: 4161

В шаблоне службы:

spec:
    ports:
    - name: tcp
    protocol: TCP
    port: 4160
    targetPort: 4160
    - name: http
    protocol: TCP
    port: 4161
    targetPort: 4161

name требуется Kubernetes.

Чтобы создать этот сервис, я использую ту же команду, что и раньше:

kubectl apply -f nsqlookup.yaml

На этом работа nsqlookupd завершена.

получатель

Это более сложный вопрос. Получатель делает три вещи:

  • Создайте несколько развертываний
  • Создайте демон nsq
  • оказывать услуги населению

Deployments

Первый объект развертывания, который он создает, является его собственным. Контейнер получателяskarlso / kube-receiver-alpine.

Демон Nsq

Получатель запускает демон nq. Как уже упоминалось, получатель запускает nqd самостоятельно. Он делает это, общаясь локально, а не по сети. Если получатель сделает это, они окажутся на одном узле.

Демон NSQ также требует некоторой настройки и параметров.

ports:
- containerPort: 4150
    hostPort: 4150
- containerPort: 4151
    hostPort: 4151
env:
- name: NSQLOOKUP_ADDRESS
    value: nsqlookup.default.svc.cluster.local
- name: NSQ_BROADCAST_ADDRESS
    value: nsqd.default.svc.cluster.local
command: ["/nsqd"]
args: ["--lookupd-tcp-address=$(NSQLOOKUP_ADDRESS):4160", "--broadcast-address=$(NSQ_BROADCAST_ADDRESS)"]

Вы можете видеть, что параметры lookup-tcp-address и Broadcast-address заданы. TCP-адрес поиска — это DNS службы nsqlookupd. Широковещательный адрес необходим, как и nsqlookupd, поэтому обратный вызов работает нормально.

Государственная служба

Теперь я впервые развертываю общедоступный сервис. Здесь есть два варианта. Я могу использовать LoadBalancer, потому что этот API может обрабатывать большую нагрузку. Если это будет развернуто в производстве, то следует использовать это.

Я выполняю локальное развертывание с одним узлом, поэтому достаточно назвать его «NodePort». ОдинNodePortПредоставьте службу на каждом IP-адресе узла на статическом порту. Если не указано, будет назначен случайный порт на хосте между 30000-32767. Но его также можно настроить как определенный порт, используемый в файле шаблона.nodePort. Чтобы использовать эту услугу, пожалуйста, используйте<NodeIP>:<NodePort>.如果配置了多个节点,则 LoadBalancer 可以将它们复用到单个 IP。

Для получения дополнительной информации ознакомьтесь с этим документом:Publishing Service.

Собрав все вместе, мы получаем приемный сервис со следующим шаблоном:

apiVersion: v1
kind: Service
metadata:
    name: receiver-service
spec:
    ports:
    - protocol: TCP
    port: 8000
    targetPort: 8000
    selector:
    app: receiver
    type: NodePort

Требуется для фиксированных узловых портов на 8000nodePortОпределение:

apiVersion: v1
kind: Service
metadata:
    name: receiver-service
spec:
    ports:
    - protocol: TCP
    port: 8000
    targetPort: 8000
    selector:
    app: receiver
    type: NodePort
    nodePort: 8000

процессор изображений

Процессор изображений — это место, где я обрабатываю проходящие изображения для распознавания. Он должен иметь доступ к конечным точкам gRPC для nsqlookupd, mysql и службы распознавания лиц. На самом деле это довольно скучный сервис. На самом деле это даже не услуга. Он ничего не раскрывает, поэтому это первый развернутый компонент. Для краткости, вот весь шаблон:

---
apiVersion: apps/v1
kind: Deployment
metadata:
    name: image-processor-deployment
spec:
    selector:
    matchLabels:
        app: image-processor
    replicas: 1
    template:
    metadata:
        labels:
        app: image-processor
    spec:
        containers:
        - name: image-processor
        image: skarlso/kube-processor-alpine:latest
        env:
        - name: MYSQL_CONNECTION
            value: "mysql.default.svc.cluster.local"
        - name: MYSQL_USERPASSWORD
            valueFrom:
            secretKeyRef:
                name: kube-face-secret
                key: mysql_userpassword
        - name: MYSQL_PORT
            # TIL: 如果这里的 3306 没有引号,kubectl 会出现错误
            value: "3306"
        - name: MYSQL_DBNAME
            value: kube
        - name: NSQ_LOOKUP_ADDRESS
            value: "nsqlookup.default.svc.cluster.local:4161"
        - name: GRPC_ADDRESS
            value: "face-recog.default.svc.cluster.local:50051"

Единственные интересные вещи в этом файле — это большое количество свойств среды, используемых для настройки приложения. Обратите внимание на адрес nsqlookupd и адрес grpc.

Чтобы создать это развертывание, запустите:

kubectl apply -f image_processor.yaml

распознавание лица

Сервис распознавания лиц — это простой сервис, который нужен только процессорам изображений. Его шаблон выглядит следующим образом:

apiVersion: v1
kind: Service
metadata:
    name: face-recog
spec:
    ports:
    - protocol: TCP
    port: 50051
    targetPort: 50051
    selector:
    app: face-recog
    clusterIP: None

Самое интересное, что для этого требуется два тома. Эти два тома являютсяknown_peopleиunknown_people. Можете ли вы угадать, что они будут содержать? Да, изображения. Том «known_people» содержит все изображения, связанные с известными людьми в базе данных.unknown_peopleтом будет содержать все новые изображения. Это путь, который нам нужно использовать при отправке образа с получателя; на него указывает точка монтирования, в моем случае это было/ unknown_people. По сути, путь должен быть путем, доступным службе распознавания лиц.

Развертывание томов через Kubernetes и Docker теперь стало проще. Это может быть смонтированный S3 или какой-либо тип nfs, или локальное монтирование от хоста к гостю. Существуют и другие возможности. Для простоты я буду использовать локальную установку.

Монтирование тома выполняется в два этапа. Во-первых, в Dockerfile должен быть указан том:

VOLUME [ "/unknown_people", "/known_people" ]

Во-вторых, нужно добавить шаблон Kubernetes в сервис MySQL.volumeMounts, разницаhostPathНе заявленный объем:

volumeMounts:
- name: known-people-storage
    mountPath: /known_people
- name: unknown-people-storage
    mountPath: /unknown_people
volumes:
- name: known-people-storage
hostPath:
    path: /Users/hannibal/Temp/known_people
    type: Directory
- name: unknown-people-storage
hostPath:
    path: /Users/hannibal/Temp/
    type: Directory

Также нам необходимо установить службу распознавания лицknown_peopleКонфигурация папки. Это делается через переменные окружения:

env:
- name: KNOWN_PEOPLE
    value: "/known_people"

Затем код Python будет искать изображение следующим образом:

known_people = os.getenv('KNOWN_PEOPLE', 'known_people')
print("Known people images location is: %s" % known_people)
images = self.image_files_in_folder(known_people)

вimage_files_in_folderФункция выглядит следующим образом:

def image_files_in_folder(self, folder):
    return [os.path.join(folder, f) for f in os.listdir(folder) if re.match(r'.*\.(jpg|jpeg|png)', f, flags=re.I)]

Neat.

Теперь, если получатель получает запрос (и отправляет его на дальний провод), он аналогичен запросу ниже.

curl -d '{"path":"/unknown_people/unknown220.jpg"}' http://192.168.99.100:30251/image/post

это будет в/ unknown_peopleНиже ищет изображение с именем unknown220.jpg, находит изображение в unknown_folder, которое соответствует человеку на неизвестном изображении, и возвращает имя соответствующего изображения.

Глядя на логи, вы увидите что-то вроде этого:

# Receiver
❯ curl -d '{"path":"/unknown_people/unknown219.jpg"}' http://192.168.99.100:30251/image/post
got path: {Path:/unknown_people/unknown219.jpg}
image saved with id: 4
image sent to nsq

# Image Processor
2018/03/26 18:11:21 INF    1 [images/ch] querying nsqlookupd http://nsqlookup.default.svc.cluster.local:4161/lookup?topic=images
2018/03/26 18:11:59 Got a message: 4
2018/03/26 18:11:59 Processing image id:  4
2018/03/26 18:12:00 got person:  Hannibal
2018/03/26 18:12:00 updating record with person id
2018/03/26 18:12:00 done

Таким образом, все службы развернуты.

внешний интерфейс

Наконец, есть небольшое веб-приложение, которое удобно отображает информацию из базы данных. Это также общедоступная служба приема с теми же параметрами, что и у получателя.

Это выглядит так:

frontend

Суммировать

Сейчас мы находимся на этапе развертывания ряда сервисов. Подводя итог командам, которые я использовал до сих пор:

kubectl apply -f mysql.yaml
kubectl apply -f nsqlookup.yaml
kubectl apply -f receiver.yaml
kubectl apply -f image_processor.yaml
kubectl apply -f face_recognition.yaml
kubectl apply -f frontend.yaml

Так как приложение не выделяет соединения при запуске, они могут быть в любом порядке. (За исключением потребителей NSQ для image_processor.)

Если ошибок нет, используйтеkubectl get podsЗапрос куба, на котором запущен модуль, должен показать следующее:

❯ kubectl get pods
NAME                                          READY     STATUS    RESTARTS   AGE
face-recog-6bf449c6f-qg5tr                    1/1       Running   0          1m
image-processor-deployment-6467468c9d-cvx6m   1/1       Running   0          31s
mysql-7d667c75f4-bwghw                        1/1       Running   0          36s
nsqd-584954c44c-299dz                         1/1       Running   0          26s
nsqlookup-7f5bdfcb87-jkdl7                    1/1       Running   0          11s
receiver-deployment-5cb4797598-sf5ds          1/1       Running   0          26s

Бегminikube service list:

❯ minikube service list
|-------------|----------------------|-----------------------------|
|  NAMESPACE  |         NAME         |             URL             |
|-------------|----------------------|-----------------------------|
| default     | face-recog           | No node port                |
| default     | kubernetes           | No node port                |
| default     | mysql                | No node port                |
| default     | nsqd                 | No node port                |
| default     | nsqlookup            | No node port                |
| default     | receiver-service     | http://192.168.99.100:30251 |
| kube-system | kube-dns             | No node port                |
| kube-system | kubernetes-dashboard | http://192.168.99.100:30000 |
|-------------|----------------------|-----------------------------|

скользящее обновление

Что происходит во время непрерывного обновления?

kube rotate

Как это бывает при разработке программного обеспечения, некоторые части системы нужно/нужно изменить. Итак, что произойдет с нашим кластером, если я изменю один из компонентов, не затрагивая другие, при этом поддерживая обратную совместимость, не нарушая работу пользователя? К счастью, Kubernetes может помочь.

Моя критика заключается в том, что API может обрабатывать только одно изображение за раз. К сожалению, здесь нет возможности массовой загрузки.

код

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

// PostImage 处理图像的文章。 将其保存到数据库
// 并将其发送给 NSQ 以供进一步处理。
func PostImage(w http.ResponseWriter, r *http.Request) {
...
}

func main() {
    router := mux.NewRouter()
    router.HandleFunc("/image/post", PostImage).Methods("POST")
    log.Fatal(http.ListenAndServe(":8000", router))
}

У нас есть два варианта: использовать/ images / postДобавьте новую конечную точку и позвольте клиентам использовать ее или измените существующую конечную точку.

Преимущество нового клиентского кода в том, что если новая конечная точка недоступна, ее можно вернуть для отправки по-старому. Однако старый клиентский код не имеет этого преимущества, поэтому мы не можем изменить наш код, который теперь работает. Учтите это: у вас 90 серверов, и вы сделали медленное накатывающее обновление, один раз удалили только один сервер при одном обновлении. Если обновление длится около минуты, весь процесс занимает около полутора часов (не считая одновременных обновлений).

В течение этого времени на некоторых из ваших серверов будет работать новый код, а на некоторых — старый код. Вызовы сбалансированы по нагрузке, поэтому вы не можете контролировать, какие серверы будут поражены. Если клиент попытается выполнить вызов новым способом, но попадет на старый сервер, клиент не сможет выполнить вызов. Клиент может попытаться отступить, но, поскольку вы удалили старую версию, он не добьется успеха, если только по совпадению не попадет на сервер, выполняющий новый код, с новым кодом (при условии, что не установлены фиксированные сеансы).

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

Теперь вы можете возразить, что не хотите вечно хранить старую версию своего кода. Это правильно в каком-то смысле. Вот почему мы модифицируем старый код, чтобы вызвать новый код с небольшим дополнением. Таким образом, после переноса всех клиентов код можно просто удалить без каких-либо проблем.

новая конечная точка

Давайте добавим новый метод пути:

...
router.HandleFunc("/images/post", PostImages).Methods("POST")
...

Обновите старую версию, чтобы вызвать новую версию с измененной версией следующим образом:

// PostImage 处理图像的文章。 将其保存到数据库
// 并将其发送给 NSQ 以供进一步处理。
func PostImage(w http.ResponseWriter, r *http.Request) {
    var p Path
    err := json.NewDecoder(r.Body).Decode(&p)
    if err != nil {
        fmt.Fprintf(w, "got error while decoding body: %s", err)
        return
    }
    fmt.Fprintf(w, "got path: %+v\n", p)
    var ps Paths
    paths := make([]Path, 0)
    paths = append(paths, p)
    ps.Paths = paths
    var pathsJSON bytes.Buffer
    err = json.NewEncoder(&pathsJSON).Encode(ps)
    if err != nil {
        fmt.Fprintf(w, "failed to encode paths: %s", err)
        return
    }
    r.Body = ioutil.NopCloser(&pathsJSON)
    r.ContentLength = int64(pathsJSON.Len())
    PostImages(w, r)
}

Ну, имя может быть лучше, но вы должны уловить основную идею. Я изменяю переданный единственный путь, оборачиваю его в новый формат и отправляю новому обработчику конечной точки. Вот и все! Есть и некоторые модификации. Чтобы увидеть их, проверьте этот PR:Rolling Update Bulk Image Path PR.

Теперь приемник можно вызвать двумя способами:

# 单个路径:
curl -d '{"path":"unknown4456.jpg"}' http://127.0.0.1:8000/image/post

# 多个路径:
curl -d '{"paths":[{"path":"unknown4456.jpg"}]}' http://127.0.0.1:8000/images/post

Здесь клиент естьcurl. Обычно, если клиент является службой, я бы изменил его и снова попробовал старый путь, когда новый путь выдает ошибку 404.

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

новое зеркало

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

docker build -t skarlso/kube-receiver-alpine:v1.1 .

Как только это будет сделано, мы можем начать развертывание изменений.

скользящее обновление

В Kubernetes вы можете настроить последовательные обновления несколькими способами:

Ручное обновление

Если я использую имя в моем файле конфигурации, называемоеv1.0версию контейнера, затем обновление просто вызывает:

kubectl rolling-update receiver --image:skarlso/kube-receiver-alpine:v1.1

Если что-то пойдет не так во время развертывания, мы всегда можем откатиться.

kubectl rolling-update receiver --rollback

Это восстановит предыдущую версию. Без суеты, без хлопот.

Применить новый профиль

Проблема с ручными обновлениями заключается в том, что они не находятся в системе контроля версий.

Учтите следующее: некоторые серверы получили обновление из-за ручного «быстрого исправления», но никто этого не видел, и это не было задокументировано. Приходит другой человек, вносит изменения в шаблон и применяет шаблон к кластеру. Все сервера обновляются и тут вдруг происходит отключение сервиса.

Короче говоря, обновленный сервер был перезаписан, потому что шаблон не отражает работу, проделанную вручную.

Рекомендуемый способ — изменить шаблон, чтобы использовать новую версию, и использоватьapplyКоманда применяет шаблон.

Kubernetes рекомендует использовать ReplicaSets для развертываний, которые должны обрабатывать распространение, что означает, что скользящие обновления должны иметь как минимум две реплики. Обновление не будет работать, если существует менее двух реплик (если толькоmaxUnavailableустановить на 1). Я увеличил количество копий yaml. Я также установил новую версию образа для контейнера приемника.

    replicas: 2
...
    spec:
        containers:
        - name: receiver
        image: skarlso/kube-receiver-alpine:v1.1
...

Глядя на обработку, вы должны увидеть следующее:

❯ kubectl rollout status deployment/receiver-deployment
Waiting for rollout to finish: 1 out of 2 new replicas have been updated...

Вы можете указать шаблонstrategyраздел, чтобы добавить дополнительные параметры конфигурации развертывания следующим образом:

strategy:
type: RollingUpdate
rollingUpdate:
    maxSurge: 1
    maxUnavailable: 0

Дополнительные сведения о последовательном обновлении см. в следующих документах:Deployment Rolling Update, Updating a Deployment, Manage Deployments, Rolling Update using ReplicaController.

Вниманию пользователей MINIKUBE: Поскольку мы делаем это на локальной машине с одним узлом и одной копией приложения, мы должны поставитьmaxUnavailableУстановить как1; иначе Kubernetes не допустит обновления и останется новая версияPendingгосударство. Это связано с тем, что мы не разрешаем службы без запущенных контейнеров, что в основном означает перебои в работе службы.

Scaling

Масштабирование проще с Kubernetes. Поскольку он управляет всем кластером, он просто вставляет номер в шаблон нужной реплики, и все работает.

До сих пор это была отличная статья, но она занимает слишком много времени. Я планирую написать продолжение, в котором действительно расширю возможности AWS с несколькими узлами и репликами; плюсKopsРазверните кластер Kubernetes. Быть в курсе!

очистить

kubectl delete deployments --all
kubectl delete services -all

напиши в конце

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

Если у вас есть какие-либо вопросы, не стесняйтесь обсуждать их в комментариях ниже. Я очень рад ответить.

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

Спасибо за чтение.

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


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллекти другие поля, если вы хотите видеть больше качественных переводов, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.