Андрей Трошин 04 августа 2021

☸️ Первое знакомство с Kubernetes: создаем приложение для развертывания в кластере

Изучив основы и подготовив тестовый полигон, мы переходим к практической части. Сегодня разберем, как создать первое приложение на Python + Flask и развернуть его в кластере k8s.
Мы изучили, что находится под капотом у оркестратора Kubernetes, разобрали базовые объекты и научились разворачивать кластер k8s для экспериментов. Стоит перейти к практике и написать простенькое веб-приложение на стеке Python и Flask, а затем задеплоить его в кластер.

Пишем приложение (Python + Flask)

Поскольку Python – единственный язык программирования, на котором я что-то умею писать, особого выбора у меня нет. Приложения будет запускаться как веб-сервер, слушать указанный порт и при обращении выдавать приветствие "Hello from Python". Кроме Python потребуется фреймворк Flask.

Код приложения:

app.py
        from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello from Python"
if __name__ == "__main__":
    app.run(host='0.0.0.0')

    

В первых двух строчках мы подключаем Flask, а далее создаем обработку корневого запроса. Приложение неидеально, но как минимальный вариант для развертывания в кластере k8s оно сгодится. Так как k8s – это среда запуска контейнеров, для переноса приложение необходимо упаковать, например, в Docker.

Упаковываем приложение в контейнер Docker

Чтобы собрать контейнер, нужно поставить Docker на локальную машину (да, да – Капитан очевидность). Инструкция по инсталляции есть на официальном сайте – процесс довольно несложен. Далее собирать образ можно средствами Docker, я использую для этих целей утилиту buildah. Она ни разу меня не подводила, рекомендую.

Для сборки контейнера на хосте должны быть установлены Docker и buildah. Все действия я провожу в CentOS, в других дистрибутивах Linux они могут немного отличаться.

Для начала создаем директорию, из которой будем собирать контейнер. Первый файл в директории назовем, например, app.py (его код приведен выше). Для установки зависимостей нам потребуется файл requirements.txt. Поскольку приложение простенькое, достаточно добавить только модуль Flask:

requirements.txt
        Flask==1.0.2
    

Третий файл называется Dockerfile – без расширения. Его содержимое:

Dockerfile
        FROM python:3.9-alpine
RUN mkdir /app
WORKDIR /app
ADD . /app/
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "/app/app.py"]

    

Разберем его построчно:

  • FROM python:3.9-alpine – добавляем в контейнер интерпретатор Python 3.9, сборка alpine.
  • RUN mkdir /app – создаем директорию /app внутри контейнера.
  • WORKDIR /app – позволяет один раз указать конкретный путь (каталог на диске), после чего большинство инструкций (например, RUN или COPY) будут выполняться в контексте этого каталога.
  • ADD . /app/ – очень важная команда, если мы делаем контейнер для k8s. Дело в том что когда отрабатывает запуск контейнера, k8s думает, что нам необходима внешняя директория /app. Ее в кластере нет, и контейнер падает с ошибкой (не найден файл /app/app.py).
  • RUN pip install -r requirements.txt – запускаем pip и скармливаем ему файл зависимостей. Flask скачивается и устанавливается в контейнер.
  • EXPOSE 5000 – пробрасываем порт 5000 для общения с нашим приложением.
  • CMD ["python", "/app/app.py"] – запускаем интерпретатор Python и наше приложение app.py.
Готово, директория в итоге должна содержать следующие файлы: app.py, requirements.txt, Dockerfile.

Находясь в директории с файлами, выполняем команду для сборки контейнера на основе Dockerfile с помощью утилиты buildah:

        buildah bud -f ./Dockerfile .
    

Запустится сборка, прогрузятся все зависимости из requirements.txt, и в итоге сгенерируется хеш – строка Storing signatures, которая нам в дальнейшем понадобится (копируем ее в буфер обмена)

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

        buildah push dedd9a5526d3c97231e9a0b73ca1e4a91ece0d70a7f7bff254f61f7d28d8e9fb docker-daemon:app:v0
    
  • :app – это имя нашего контейнера (можно указать свой).
  • :v0 – это tag. Обычно через него выставляется версионность для удобной навигации в репозитории.

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

        docker image ls
    
Итак, у нас на локальном хосте есть собранный контейнер который мы можем запустить для проверки работоспособности с помощью curl.
        docker run --rm -d -v `pwd`:/app -p 5000:5000 app:v0
    

Приложение откликается, значит контейнер рабочий. Последний штрих – запушить контейнер из локального репозитория в удаленный, например, в Docker Hub. Это нужно, чтобы мы могли скачать контейнер в кластер k8s и запуститься его уже там.

Чтобы это сделать, потребуется авторизоваться на Docker Hub и создать пустой репозиторий. Детально описывать эту процедуру не буду: процесс похож на создание Git-репозитория.

        docker login
<выполните авторизацию>. Делается один раз, пароль и логин запоминаются

    
        docker tag app:v0 <ваш логин docker hub>/<ваш репозиторий>/app:v0
buildah push <image ID> <ваш логин docker hub>/<ваш репозиторий>:app

    

Теперь можно на любом хосте с Docker запустить контейнер и в качестве расположения указать уже не локальный репозиторий, а Docker Hub.

        docker run --rm -d -v `pwd`:/app -p 5000:5000 test/learn_images:app:v0
    
Дальнейшие действия проводятся в кластере k8s. Как его развернуть, мы уже писали.

Цель – развернуть приложение Python из контейнера, но уже в кластере k8s в нескольких репликах.

Для этого необходимо создать два объекта (подробнее мы рассматривали их в предыдущей статье):

  • Deployment – развернет приложение и будет поддерживать необходимое количество реплик.
  • Service – обеспечит сетевое взаимодействие внутри кластера.

Создаем Deployment для приложения в кластере k8s

Дополнительные материалы
Чтобы не путаться в терминах, вспомним, что под капотом у системы оркестрации Kubernetes.

Заходим по SSH на мастер-узел (ноду), проверяем работоспособность kubectl. Далее необходимо создать deployment.yaml.

deployment.yaml
        apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  labels:
    app: app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      containers:
      - name: app
        image: <ваш логин docker hub>/<ваш репозиторий>:app
        ports:
        - containerPort: 5000
          protocol: TCP
          name: http
        resources:
          limits:
            cpu: 50m
            memory: 20Mi
          requests:
            cpu: 50m
            memory: 20Mi

    

Разбираем подробнее:

  • kind: Deployment – определяем, какой у нас будет объект. В данном случае Deployment, но может быть и Service или, скажем, Pod.
  • name: app – имя нашего Deployment (именно так он будет отображаться в кластере k8s).
  • matchLabels: и app: app – метка приложения, ее уникальный идентификатор для дальнейшего мапинга с сервисом и другими объектами k8s.
  • replicas: 2 – сколько экземпляров приложения необходимо создать
  • containers: – этой секции описывается, откуда нам спулить контейнер (ясно дело, с Docker Hub). Порт для сетевого взаимодействия.
  • resources: – интересная секция. Тут описаны минимально необходимые ресурсы для запуска (limits). Без этих ресурсов приложение не стартует и планировщик кластера будет искать подходящий узел, удовлетворяющую минимальным ресурсам. Запрашиваемые (requests) – это ресурсы, которые резервируются на ноде для вашего приложения. Сверх этих ограничений приложение не получит ресурсов CPU и RAM.

Сохраняем файл deployment.yaml и выполняем команду для развертывания приложения:

        kubectl create -f deployment.yaml
    

Если ошибок в файле не было, успешный исход – активное приложение в 2 репликах. Посмотреть состояние можно следующей командой:

        kubectl get pod -o wide
    

Приложение развернуто в двух репликах – с помощью curl можно обратиться к каждой из них по IP пода, но это плохой вариант (поды рано или поздно переедут на другие узлы и IP поменяются). Давайте напишем еще один объект кластера, который закроет эту проблему – Service.

Создаем Service приложения в кластере k8s

Из статьи по базовым объектам кластера k8s мы уже знаем, что объекты Service нужны для мапинга всех реплик приложения на один IP (endpoint).

Создаем еще один файл service.yaml.

service.yaml
        apiVersion: v1
kind: Service
metadata:
  labels:
    app: app
  name: app
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 5000
  selector:
    app: app
  type: ClusterIP

    

Его главные отличительные особенности:

  • ports: port – порт, по которому Service будет принимать трафик.
  • targetPort – порт для связи с приложением.
  • selector: – мапим наш Deployment и все реплики по этой метке.
  • type: clusterIP – тип объекта Service. В нашем случае для сетевого взаимодействия внутри кластера k8s.

Запускаем процедуру создания следующей командой:

        kubectl create -f service.yaml
    

Далее смотрим детали Service:

        kubectl get svc -o wide
    

Супер! У нас есть IP, по которому можно обращаться к сервису в кластере с любого пода.

Подведем итог статьи. Мы добились следующих результатов:

  • Написали приложение на Python + Flask.
  • Упаковали приложение в контейнер Docker.
  • Разместили контейнер в репозитории Docker hub.
  • Сделали Deployment и Service в кластере k8s.
Использованные в статье файлы YAML можно скачать здесь.

Поздравляю, вы развернули первое приложение в кластере k8s! Возможно оно простовато, но в следующих статьях мы будем доводить его до ума.

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Junior QA engineer
Самара, от 35000 RUB до 55000 RUB
Ведущий разработчик (Android)
Москва, по итогам собеседования
DevOps инженер
по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ