Как развернуть приложение в kubernetes
В статье мы расскажем, что такое Kubernetes, покажем, как установить Kubernetes с нуля, и опишем начало работы с Kubernetes.
Введение в Kubernetes: что это такое и как устроено
Kubernetes (Кубернетис) — это платформа для автоматизации управления контейнерами приложений с помощью кластеров на Ubuntu, CentOS и других ОС семейства Linux. Кластеры подключены к высокоуровневому API . Благодаря этому можно системно группировать контейнеры, балансировать нагрузку и активировать задачи одновременно из сотен приложений.
Настройка Kubernetes-кластера будет интересна крупным компаниям с разработками формата Big Data или команде программистов, которым нужна крупная производственная среда. Для таких масштабных проектов хорошо подойдёт инфраструктурная платформа на основе Kubernetes. Kubernetes требователен к приложениям, с которыми работает — они должны функционировать строго по принципу микросервисов. Также кластер Кубернетес расходует много ресурсов, поэтому запускать его в небольших проектах невыгодно.
Облачные серверы нового поколения
Виртуализация KVM, почасовая оплата, резервные копии, готовые шаблоны, 10 доступных ОС на выбор!
Основные компоненты архитектуры Kubernetes
Архитектура Kubernetes состоит из следующих компонентов:
Nodes (нода или узел) — виртуальная или физическая машина, на которой работают контейнеры приложений. Каждая нода содержит компоненты, которые позволяют запускать сервисы контейнеризации (например, Docker).
Pods или поды — базовые модули для управления приложениями. Могут состоять из одного или нескольких контейнеров.
Volume или том — ресурс для единовременного использования нескольких контейнеров.
Kube-proxy — совокупность балансировщика нагрузки и прокси-сервера, которые позволяют маршрутизировать входящий трафик для конкретных контейнеров пода.
Kubelet — транслирует статус подов на узле и следит за корректностью работы конкретного контейнера, образа и других компонентов.
Kubernetes можно установить с помощью готового инструмента или самостоятельно, добавляя каждый компонент архитектуры вручную. Мы расскажем, как установить полный функционал Kubernetes на Ubuntu вторым способом.
Сначала необходимо установить Docker и Kubernetes для каждого узла (нода) сервера, а затем развернуть Kubernetes. Для этого выполните этапы инструкции ниже.
Поскольку Python – единственный язык программирования, на котором я что-то умею писать, особого выбора у меня нет. Приложения будет запускаться как веб-сервер, слушать указанный порт и при обращении выдавать приветствие "Hello from Python". Кроме Python потребуется фреймворк Flask.
Код приложения:
В первых двух строчках мы подключаем Flask, а далее создаем обработку корневого запроса. Приложение неидеально, но как минимальный вариант для развертывания в кластере k8s оно сгодится. Так как k8s – это среда запуска контейнеров, для переноса приложение необходимо упаковать, например, в Docker.
Упаковываем приложение в контейнер Docker
Чтобы собрать контейнер, нужно поставить Docker на локальную машину (да, да – Капитан очевидность). Инструкция по инсталляции есть на официальном сайте – процесс довольно несложен. Далее собирать образ можно средствами Docker, я использую для этих целей утилиту buildah. Она ни разу меня не подводила, рекомендую.
Для сборки контейнера на хосте должны быть установлены Docker и buildah. Все действия я провожу в CentOS, в других дистрибутивах Linux они могут немного отличаться.Для начала создаем директорию, из которой будем собирать контейнер. Первый файл в директории назовем, например, app.py (его код приведен выше). Для установки зависимостей нам потребуется файл requirements.txt . Поскольку приложение простенькое, достаточно добавить только модуль Flask:
Третий файл называется Dockerfile – без расширения. Его содержимое:
Разберем его построчно:
- 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 .
Находясь в директории с файлами, выполняем команду для сборки контейнера на основе Dockerfile с помощью утилиты buildah:
Запустится сборка, прогрузятся все зависимости из requirements.txt , и в итоге сгенерируется хеш – строка Storing signatures , которая нам в дальнейшем понадобится (копируем ее в буфер обмена)
Дальше необходимо запушить сборку в локальный репозиторий Docker. Выполняем следующую команду, подставляя в параметры свой хеш:
- :app – это имя нашего контейнера (можно указать свой).
- :v0 – это tag. Обычно через него выставляется версионность для удобной навигации в репозитории.
Контейнер уже можно увидеть в списке локального репозитория Docker, выполнив следующую команду:
Приложение откликается, значит контейнер рабочий. Последний штрих – запушить контейнер из локального репозитория в удаленный, например, в Docker Hub. Это нужно, чтобы мы могли скачать контейнер в кластер k8s и запуститься его уже там.
Чтобы это сделать, потребуется авторизоваться на Docker Hub и создать пустой репозиторий. Детально описывать эту процедуру не буду: процесс похож на создание Git-репозитория.
Теперь можно на любом хосте с Docker запустить контейнер и в качестве расположения указать уже не локальный репозиторий, а Docker Hub.
Дальнейшие действия проводятся в кластере k8s. Как его развернуть, мы уже писали.Цель – развернуть приложение Python из контейнера, но уже в кластере k8s в нескольких репликах.
Для этого необходимо создать два объекта (подробнее мы рассматривали их в предыдущей статье):
- Deployment – развернет приложение и будет поддерживать необходимое количество реплик.
- Service – обеспечит сетевое взаимодействие внутри кластера.
Создаем Deployment для приложения в кластере k8s
Заходим по SSH на мастер-узел (ноду), проверяем работоспособность kubectl . Далее необходимо создать deployment.yaml .
Разбираем подробнее:
- 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 и выполняем команду для развертывания приложения:
Если ошибок в файле не было, успешный исход – активное приложение в 2 репликах. Посмотреть состояние можно следующей командой:
Приложение развернуто в двух репликах – с помощью curl можно обратиться к каждой из них по IP пода, но это плохой вариант (поды рано или поздно переедут на другие узлы и IP поменяются). Давайте напишем еще один объект кластера, который закроет эту проблему – Service .
Создаем Service приложения в кластере k8s
Из статьи по базовым объектам кластера k8s мы уже знаем, что объекты Service нужны для мапинга всех реплик приложения на один IP ( endpoint ).Создаем еще один файл service.yaml .
Его главные отличительные особенности:
- ports: port – порт, по которому Service будет принимать трафик.
- targetPort – порт для связи с приложением.
- selector: – мапим наш Deployment и все реплики по этой метке.
- type: clusterIP – тип объекта Service . В нашем случае для сетевого взаимодействия внутри кластера k8s.
Запускаем процедуру создания следующей командой:
Далее смотрим детали Service :
Супер! У нас есть IP, по которому можно обращаться к сервису в кластере с любого пода.
Подведем итог статьи. Мы добились следующих результатов:
- Написали приложение на Python + Flask.
- Упаковали приложение в контейнер Docker.
- Разместили контейнер в репозитории Docker hub.
- Сделали Deployment и Service в кластере k8s.
Поздравляю, вы развернули первое приложение в кластере k8s! Возможно оно простовато, но в следующих статьях мы будем доводить его до ума.
После запуска кластера Kubernetes на нем могут быть развернуты контейнерные приложения. Для этого вам необходимо создать конфигурацию развертывания Kubernetes. Развертывание направляет Kubernetes, как создавать и обновлять экземпляры приложения. После создания развертывания мастер Kubernetes планирует экземпляры приложений для каждого узла в кластере.
После создания экземпляров приложения контроллер развертывания Kubernetes непрерывно отслеживает эти экземпляры. Если узел, на котором размещен экземпляр, выключен или удален, контроллер развертывания заменяет экземпляр экземпляром на другом узле в кластере.Это обеспечивает механизм самовосстановления для решения проблем технического обслуживания после отказа машины.
Вы можете использовать интерфейс командной строки Kubernetes для создания и управления развертываниями, а Kubectl. Kubectl использует API Kubernetes для взаимодействия с кластером.
- Проверьте, настроен ли kubectl для связи с кластером, выполнив команду kubectl version:
2. Чтобы просмотреть узлы в кластере, выполните команду kubectl get node:
Здесь мы видим доступные узлы. Kubernetes выберет место для развертывания приложения на основе доступных ресурсов узла.
- Чтобы развернуть наше приложение, давайте развернем первое приложение в Kubernetes с помощью команды kubectl create deploy. Нам нужно указать имя развертывания и расположение образа приложения (включая URL-адрес образа концентратора Docker).
- Просто развернуло первое приложение, создав развертывание. Поиск подходящего узла, который может запустить экземпляр приложения (у нас есть только 1 доступный узел)
Запланируйте запуск приложения на этом узле. Настройте кластер так, чтобы при необходимости перепланировать экземпляры на новом узле.
Чтобы вывести список развертываний, используйте команду get deployments:
Мы видим, что есть развертывание, которое запускает один экземпляр вашего приложения. Экземпляр работает в контейнере Docker на узле.
- Посмотреть приложение
Модуль, работающий в Kubernetes, работает в частной изолированной сети. По умолчанию они видны в других модулях и службах в том же кластере kubernetes, но они не видны за пределами сети. Когда мы используем kubectl, мы взаимодействуем с нашим приложением через конечную точку API.
Мы откроем второе окно терминала для запуска агента.
Теперь мы установили соединение между хостом (онлайн-терминалом) и кластером Kubernetes. Прокси-сервер обеспечивает прямой доступ к API с этих терминалов.
Вы можете видеть все эти API, передаваемые конечными точками прокси. Например, мы можем использовать команду curl для запроса версии напрямую через API:
Если вы не можете получить доступ к порту 8001, убедитесь, что запущенный выше агент kubectl работает.
Сервер API автоматически создает конечную точку для каждого модуля на основе имени модуля, к которому также можно получить доступ через прокси-сервер.
Сначала нам нужно получить имя Pod, а затем сохранить его в переменной окружения Pod_name:
Примечание. Проверьте верхнюю часть терминала. Агент работает в терминале 2, а самая последняя команда выполняется в терминале 1. Прокси все еще работает в Терминале 2, что позволяет команде curl работать с использованием localhost: 8001.
Контейнеризация приложений — один из главных трендов современных IT-разработок. Однако, у контейнеров есть один существенный недостаток для массового потребителя — сложная настройка масштабирования.
Решением стали автоматические системы управления контейнеризацией, наиболее популярной из которых является Kubernetes. Это программное обеспечение с открытым исходным кодом от компании Google завоевало признание благодаря сочетанию гибкости, безопасности и мощности.
Cтатья «Kubernetes для чайников» поможет разобраться как устроена платформа управления контейнеризацией, как установить ПО и для чего его можно использовать в дальнейшем. Она будет полезна как для начинающих пользователей Kubernetes, так и для профильных IT-специалистов.
История создания
Проект Kubernetes (сокращенно K8s) вырос из системы управления кластерами Borg. Внутренний продукт поискового гиганта Google получил название в честь кибер-рассы боргов из легендарного сериала «Звездный путь».
Команде разработчиков Google Borg была поставлена масштабная задача — создать открытое программное обеспечение для оркестрирования* контейнеров, которое станет вкладом Google в развитие мировых IT-технологий. Приложение было написано на основе языка Go.
* Под «оркестрированием» подразумевается автоматизированное управление связанными сущностями — контейнерами или виртуальными машинами.
На этапе разработки K8s назвался Project Seven («Проект «Седьмая»). Это было прямой отсылкой к персонажу «Звездного пути» Seven of Nine («Седьмая-из-девяти») — андроиду-боргу, сумевшему вернуть себе человечность. Позже проект получил имя «Кубернетес», от греческого слова κυβερνήτης, которое означает «управляющий», «рулевой» или «кормчий».
В 2014 году общественности представили исходные коды, а годом позже появилась первая версия программы Kubernetes 1.0. В дальнейшем все права на продукт были переданы некоммерческому фонду Cloud Native Computing Foundation (CNCF), куда входят Google, The Linux Foundation и ряд крупнейших технологических корпораций.
Как работает технология
Принципы устройства
Основы работы K8s – применение декларативного подхода. От разработчика требуется указать, чего необходимо достичь, а не способы достижения.
Помимо этого, в Kubernetes могут быть задействованы императивные команды (create, edit, delete), которые позволяют непосредственно создавать, модифицировать и удалять ресурсы. Однако, их не рекомендуется использовать для критически важных задач.
Для развертывания программного обеспечения в Kubernetes применяется база Linux-контейнеров (например, Containerd или CRI-O) и описание — сколько потребуется контейнеров и в каком количестве им потребуются ресурсы. Само развертывание контейнеров происходит на основе рабочих нод — виртуальных или физических машин.
Основные задачи Kubernetes
- Развертывание контейнеров и все операции для запуска необходимой конфигурации. В их число входят перезапуск остановившихся контейнеров, их перемещение для выделения ресурсов на новые контейнеры и другие операции.
- Масштабирование и запуск нескольких контейнеров одновременно на большом количестве хостов.
- Балансировка множества контейнеров в процессе запуска. Для этого Kubernetes использует API, задача которого заключается в логическом группировании контейнеров. Это дает возможность определить их пулы, задать им размещение, а также равномерно распределить нагрузку.
Преимущества K8s
- Обнаружение сервисов ибалансировка нагрузок. Контейнеры могут работать через собственные IP-адреса или использовать общее имя DNS для целой группы. K8s может регулировать нагрузку сетевого трафика и распределять его, чтобы поддержать стабильность развертывания.
- Автоматическое управление хранилищами. Пользователь может задавать, какое хранилище использовать для развертывания по умолчанию — внутреннее, внешнего облачного провайдера (GKE, Amazon EKS, AKS), другие варианты.
- Автоматическое внедрение и откат изменений. Пользователь может на лету делать любые дополнения к текущей конфигурации контейнеров. Если это нарушит стабильность развертывания, K8s самостоятельно откатит изменения до стабильно работающей версии.
- Автоматическое распределение ресурсов. Kubernetes сам распределяет пространство и оперативную память из выделенного кластера нод, чтобы обеспечить каждый контейнер всем необходимым для работы.
- Управление паролями и настройками. K8s может служить приложением для безопасной обработки конфиденциальной информации, связанной с работой приложений — паролей, OAuth-токенов, SSH-ключей. В зависимости от способа применения, данные и настройки можно обновлять без создания контейнера заново.
- Самовосстановление при возникновении сбоя. С помощью особых метрик и тестов система может быстро опознать поврежденные или переставшие отвечать на запросы контейнеры. Вышедшие из строя контейнеры создаются заново и перезапускаются на том же поде.
Kubernetes – удобный инструмент оркестрации контейнеров. Однако, это решение, не работает само по себе, без подготовки и дополнительных настроек. Например, пользователям придется решать вопросы по миграции схем баз данных или разбираться с обратной совместимостью API.
Основные компоненты
Схема взаимодействия основных компонентов K8s
Node (Нода)
Ноды или узлы — виртуальные или физические машины, на которых разворачивают и запускают контейнеры. Совокупность нод образует кластер Kubernetes.
Первая запущенная нода или мастер-нода непосредственно управляет кластером, используя для этого менеджер контроллеров (controller manager) и планировщик (scheduler). Она ответственна за интерфейс взаимодействия с пользователями через сервер API и содержит в себе хранилище «etcd» с конфигурацией кластера, метаданными и статусами объектов.
Namespace (Пространство имен)
Объект, предназначенный для разграничения ресурсов кластера между командами и проектами. Пространства имен — несколько виртуальных кластеров, запущенные на одном физическом.
Pod (Под)
Первичный объект развертывания и основной логический юнит в K8s. Поды — набор из одного и более контейнеров для совместного развертывания на ноде.
Группировка контейнеров разных типов требуется в том случае, когда они взаимозависимы и должны запускаться в одной ноде. Это позволяет увеличить скорость отклика во время взаимодействия. Например, это могут быть контейнеры, хранящие веб-приложение и сервис для его кэширования.
ReplicaSet (Набор реплик)
Объект, отвечающий за описание и контроль за несколькими экземплярами (репликами) подов, созданных на кластере. Наличие более одной реплики позволяет повысить устойчивость от отказов и масштабирование приложение. На практике ReplicaSet создается с использованием Deployment.
ReplicaSet является более продвинутой версией предыдущего способа организации создания реплик (репликации) в K8s – Replication Controller.
Deployment (Развертывание)
Объект, в котором хранится описание подов, количество реплик и алгоритм их замены в случае изменения параметров. Контроллер развертывания позволяет выполнять декларативные обновления (с помощью описания нужного состояния) на таких объектах, как ноды и наборы реплик.
StatefulSet (Набор состояния)
Как и другие объекты, например — ReplicaSet или Deployment, Statefulset позволяет развертывать и управлять одним или несколькими подами. Но в отличие от них, идентификаторы подов имеют предсказуемые и сохраняемые при перезапуске значения.
DaemonSet (Набор даемона)
Объект, который отвечает за то, чтобы на каждой отдельной ноде (или ряде выбранных) запускался один экземпляр выбранного пода.
Job/CronJob (Задания/Задания по расписанию)
Объекты для регулировки однократного или регулярного запуска выбранных подов и контроля завершения их работы. Контроллер Job отвечает за однократный запуск, CronJob — за запуск нескольких заданий по расписанию.
Label/Selector (Метки/Селекторы)
Метки предназначены для маркировки ресурсов. Позволяют упростить групповые манипуляции с ними. Селекторы позволяют выбирать/фильтровать объекты на основе значения меток.
По факту, метки и селекторы не являются самостоятельными объектами Kubernetes, но без них система не сможет полноценно функционировать.
Service (Сервис)
Средство для публикации приложения как сетевого сервиса. Используется, в том числе, для балансировки трафика/нагрузки между подами.
Процесс установки
Установка Kubernetes, рассмотренная ниже, предполагает наличие одного (или более) серверов с операционной системой Centos 7 или Ubuntu 16.04.
Проект Kubernetes действует на основе контейнеров Docker, существенно расширяя их функциональность. Логично, что начинать работу Kubernetes следует именно с установки Docker.
Проще всего остановить выбор на версии, добавленной на текущий момент в репозитории. Ее протестировали разработчики Kubernetes и она работает наиболее стабильно.
Установка контейнеров на Ubuntu 16.04
Чтобы установить Docker на Ubuntu 16.04, необходимо выполнить следующие команды с правами суперпользователя:
Если требуется работать с более новыми версиями контейнеров, запустите команды:
Установка контейнеров в CentOS 7
Для установки Docker на Centos, в консоли нужно выполнить команды:
Установка kubeadm, kubelet и kubectl в Ubuntu
Для работы с Kubernetes понадобится установить компоненты kubeadm, kubelet и kubectl. Эти утилиты понадобятся для создания управления кластером Kubernetes.
- Kubectl — позволяет создавать и настраивать объекты в кластере.
- Kubelet — занимается запуском контейнеров на хостах.
- Kubeadm — позволит настраивать компоненты, составляющие кластер.
В Ubuntu эти компоненты можно установить следующим способом:
Установка kubeadm, kubelet и kubectl в CentOS
В CentOS 7 компоненты устанавливаются следующим образом:
Обращаем внимание! Команда setenforce 0 позволит получить корректный доступ контейнеров к файловой системе хоста. Последняя необходима для функционирования сети у подов.
Нужно убедиться, что «kubelet» и «docker» пользуются одним и тем же драйвером «cgroup». В этом может помочь команда:
Настройка Kubernetes
Инициализация кластера
Нужно указать сервер, на котором установлен K8s (он будет первичным — там будут запускаться остальные операции) и выполнить инициализацию кластера:
В данном примере будем использован наиболее распространенный сетевой плагин — Flannel. По умолчанию он использует сеть «10.244.0.0/16», которая была указана в параметре, приведенном выше.
При выполнении команды в консоли, есть вероятность появления ошибок или предупреждений. Ошибки нужно исправлять в обязательном порядке, а на предупреждения можно не обращать внимание, если это не окружение «production».
Если все сделано правильно, на экране отобразится команда, позволяющая присоединить остальные ноды кластера к первичному хосту. Команда может отличаться, в зависимости от структуры кластера. Ее нужно сохранить на будущее.
После выполнения этой команды система выведет примерный результат:
Остается выполнить следующие команды от имени пользователя, который будет управлять кластером:
Настройка CNI
Перед тем, как начать запускать в кластере приложения, нужно выполнить настройку Container Network Interface («сетевой интерфейс контейнера» или CNI). CNI нужен для настройки взаимодействия и управления контейнерами внутри кластера.
Существует много плагинов для создания CNI. В данном примере применяется Flannel, так как это наиболее простое и проверенное решение. Однако, не меньшей популярностью пользуются плагины Weave Net от компании Weaveworks и Calico (Project Calico), обладающие более широким функционалом и возможностями сетевых настроек.
Чтобы установить Flannel, выполните в терминале команду:
В выводе будут отображены имена всех созданных ресурсов.
Добавление узлов (нод) в кластер
Чтобы добавить новые ноды в существующий кластер, требуется выполнить следующий алгоритм:
- Подключиться к серверу через SSH.
- Установить на сервер Docker, Kubelet, Kubeadm (как показано выше).
- Выполнить команду:
Данная команда была выведена при выполнении команды «kubeadm init» на мастер-ноде.
Если команда не была сохранена, то можно ее составить повторно.
Получение токена авторизации кластера (<token>)
- Подключиться к серверу через SSH.
- Запустить команду, которая присутствовала на выводе команды «kubeadm init». Например:
Если токена нет, его можно получить, выполнив следующую команду на мастер-ноде:
Вывод должен быть примерно таков:
По умолчанию, срок действия токена — 24 часа. Если требуется добавить новый узел в кластер по окончанию этого периода, можно создать новый токен следующей командой:
Вывод будет примерно таков:
Будет получен примерно такой вывод:
Для ввода IPv6-адреса в параметр «<control-plane-host>:<control-plane-port>», адрес должен быть заключен в квадратные скобки. Например:
Дополнительные настройки
В дефолтной конфигурации мастер-нода не запускает контейнеры, так как занимается отслеживанием состояния кластера и перераспределением ресурсов. Ввод данной команды даст возможность запускать контейнеры на мастере, собенно, если кластер содержит лишь одну ноду:
Проверка работоспособности кластера
Проверить, что кластер запустился и правильно работает, можно так:
Вывод будет аналогичен. В нем будут отображены системные POD’ы k8s.
Теперь установку можно считать завершенной. Далее можно продолжить настройку K8s для работы с веб-приложениями. Например, подключить диспетчер пакетов «helm» для автоматического развертывания приложений или контроллер «nginx ingress», отвечающий за маршрутизацию внешнего трафика.
Заключение
Несмотря на кажущуюся сложность настройки, K8s стоит времени, потраченного на его изучение. Kubernetes — наиболее совершенный на сегодня инструмент оркестрирования контейнеров. Он позволяет не только автоматизировать процесс развертывания, но и максимально упрощает дальнейший процесс работы с массивами контейнеров.
С помощью этого краткого руководства начать работу с K8s сможет даже начинающий пользователь. В дальнейшей работе с платформой поможет подробная официальная документация, доступная, в том числе, на русском языке.
Читайте также: