Чтобы лучше понять, как нынешнее ИТ-сообщество пришло к контейнерам, и что это вообще такое, необходимо немного окунуться в историю, а именно в 1979 год!
В 1979 году Unix V7 (не путать с Linux) представила системный вызов chroot, который стал началом того, что мы сегодня понимаем как “виртуализация процессов”. Сhroot позволял ядру изменить видимый корневой каталог процесса и его “детей”. Т.е. процесс думает, что он работает на машине отдельно, потому что его файловая система отделена от других процессов. Позже это появилось в BSD, где оно приобрело концепт тюрьмы (jail), что стало толчком к разработке одноименной утилиты в FreeBSD. Она ограничивала доступ не только к файловой системе, но также к сети, общей памяти и видимым переменным ядра. Таким образом, ПО в “тюрьме” никак не могло навредить основной системе или другим программам в аналогичных клетках. Позднее, концепт “тюрьмы” перекочевал в Linux, и в последствии появилась реализация Linux Namespaces, а уже потом появились привычные сегодня контейнеры. Проще говоря, какие-то «патлатые гики в свитерах» для своего удобства изобрели контейнеризацию для упрощения тестирования нового ПО, ОС и так далее.
Как же контейнеры помогают в обкатке ПО? Тут все просто! Давайте подумаем, как мы можем быстро запустить несколько копий одного приложения на одном сервере? Первое что приходит в голову - создать виртуальную машину (ВМ), подготовить всю среду для запуска приложения, запустить там один экземпляр, и повторить действия для необходимого количества приложений... Скажем честно, это не просто сложно, это крайне трудоемко... Да, соглашусь, можно это немного оптимизировать, используя Ansible, клонирование ВМ и так далее, но все-же, не кажется ли это вам громоздким и неприятным для исполнения?
Вот как раз ответ на вышестоящий вопрос - контейнер! Время на билд контейнера и его запуск минимально, а в совокупности с простотой создания и управления контейнеризация не оставляет никаких шансов привычным ранее виртуальным машинам! И это мы еще не берем в расчет затраченные впустую серверные мощности, ведь на виртуализацию тратится во много раз больше ресурсов. Для примера: минимальная Ubuntu 20.04 в простое потребляет ~300 мегабайт оперативной памяти. В случае виртуализации windows, то там пропорционально от выданных мощностей резервируется оперативная память (чем больше дадите ВМ оперативной памяти, тем больше в процессе работы она будет РЕЗЕРВИРОВАТЬ под только свои нужды).
Что мы видим в сухом остатке? Контейнер - некая сущность, которая должна все делать хорошо, а плохо не делать. Но у не знакомых с контейнеризацией пользователей, возникает много вопросов, и самый частый из них: «А как мне сделать свой контейнер, что мне для этого нужно?». Для ответа на этот вопрос я решил подготовить краткий ликбез с примером создания вашего собственного первого контейнера. В данном примере мы будем создавать контейнер с Elasticsearch 7.4.2 со всеми необходимыми плагинами для работы полнотекстового поиска системы DirectumRX.
Для начала работы нам необходим образ контейнера, на основании которого мы будем создавать свой контейнер с приложением. В нашем случае будет использоваться платформа контейнеризации docker. Для начала мы немного окунемся в матчасть, а именно узнаем немного про Dockerfile и что он умеет. Dockerfile – это сценарий, который состоит из последовательности команд и аргументов, необходимых для создания образа. Такие сценарии упрощают развёртывание и процесс подготовки приложения к запуску. Сначала Dockerfile определяет образ, на основе которого будет происходить сборка. Затем идёт ряд методов, команд и аргументов, которые создадут новый образ. Содержимое Dockerfile передаётся демону Docker для сборки образа. Из крайне немногочисленных инструкций который могут содержаться в Dockerfile нам необходимы:
Только эти инструкции необходимы конкретно в нашем случае, более подробно по инструкциям можно почитать в документации: https://docs.docker.com/engine/reference/builder/.
На этом матчасть заканчивается, приступаем к написанию Dockerfile. Для удобства создайте пустую папку и поместите туда все необходимые плагины из комплекта поставки, для полнотекстового поиска:
Переходим в директорию с плагинами и создаем там Dockerfile:
>vi Dockerfile
Выбираем на основании чего мы будем собирать наш образ, в нашем случае советую ubuntu:20.04, все ниже написанное будет подходить только под нее!
FROM ubuntu:20.04
Создадим группу и пользователя, чтоб потом в случае чего нам не доставило проблем:
RUN groupadd -g 1000 elasticsearch && useradd elasticsearch -u 1000 -g 1000
Теперь нам необходимо, добавить в контейнер все необходимое для скачивания и работы elasticsearch:
RUN apt-get update && \
apt-get install -y --no-install-recommends \
apt-transport-https \ wget -y
Сохраняем файл. НЕ ТОРОПИТЕСЬ ЕГО БИЛДИТЬ! Мы только в начале пути!
Теперь нам нужен самый минимальный конфиг для elasticsearch, чтоб он удовлетворял нашим требованиям (одна нода, а не кластер). Создаем файл:
>vi elasticsearch.yml
И записываем туда:
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
cluster.name: "rx-elk-docker"
network.host: 0.0.0.0
http.port: 9200
discovery.type: single-node
Сохраняем файл и выходим. Также создадим минимальный файлик с настройками jvm:
>vi jvm.options
Запишем туда:
## JVM configuration
# То, сколько необходимы выдать джава машине оперативной памяти
# В нашем случае 4 гига оперативы хватит с головой
-Xms4g
-Xmx4g
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-Des.networkaddress.cache.ttl=60
-Des.networkaddress.cache.negative.ttl=10
-XX:+AlwaysPreTouch
-Xss1m
Сохраняемся и выходим. Возвращаемся в Dockerfile.
>vi Dockerfile
Добавляем копирование созданного ранее файла:
COPY elasticsearch.yml /etc/elasticsearch/elasticsearch.yml
Также добавляем jvm.options:
COPY jvm.options etc/elasticsearch/jvm.options
Далее необходимо скачать и установить необходимую нам версию ELK:
RUN wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.4.2-amd64.deb --no-check-certificate
RUN dpkg -i --force-confdef ./elasticsearch-7.4.2-amd64.deb
Создаем директорию куда скинем плагины для elasticsearch:
RUN mkdir /usr/plugins/
И копируем туда:
COPY analysis-morphology-7.4.2.zip /usr/plugins/
Устанавливаем плагин ingest-attachment:
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch ingest-attachment
Копируем tika-core-1.22.jar в папку с ранее установленным плагином:
COPY tika-core-1.22.jar usr/share/elasticsearch/plugins/ingest-attachment
Устанавливаем analysts-morphology-7.4.2:
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch file:/usr/plugins/analysis-morphology-7.4.2.zip
Копируем синонимы:
COPY synonyms.txt /etc/elasticsearch
Удаляем установочный файл:
RUN rm elasticsearch-7.4.2-amd64.deb
Казалось бы все сделано, и если мы соберем контейнер и запустим, то все будет работать, но это не так. По умолчанию Elasticsearch отключен как служба, и его необходимо включить навсегда, но systemctl в docker-е нет, приходится искать обходной путь, в нашем случае он будет крайне простой.
Создаем скрипт для запуска elasticsearch, который в последствии у нас будет выполняться каждый раз при старте контейнера.
RUN echo "service elasticsearch start" > start.sh
Делаем его исполняемым:
RUN chmod +x start.sh
Теперь осталось лишь добавить команду, которая будет выполняться при запуске контейнера:
CMD bash start.sh ; /bin/sh
И напоследок объявляем порты, которые будут открыты для контейнера:
EXPOSE 9200
Хочу внести немного ясности. Инструкцией выше, мы не объявляем то, что при билде и запуске контейнера по умолчанию будет прокидываться порт напрямую в контейнер через хост систему. Это лишь объявление того, что сам контейнер для себя откроет порт 9200. Теперь посмотрим что у нас в итоге получилось в Dockerfile:
FROM ubuntu:20.04
RUN groupadd -g 1000 elasticsearch && useradd elasticsearch -u 1000 -g 1000
RUN apt-get update && \
apt-get install -y --no-install-recommends \
apt-transport-https \
wget -y
COPY elasticsearch.yml /etc/elasticsearch/elasticsearch.yml
COPY jvm.options etc/elasticsearch/jvm.options
RUN wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.4.2-amd64.deb --no-check-certificate
RUN dpkg -i --force-confdef ./elasticsearch-7.4.2-amd64.deb
RUN mkdir /usr/plugins/
COPY analysis-morphology-7.4.2.zip /usr/plugins/
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch ingest-attachment
COPY tika-core-1.22.jar usr/share/elasticsearch/plugins/ingest-attachment
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch file:/usr/plugins/analysis-morphology-7.4.2.zip
COPY synonyms.txt /etc/elasticsearch
RUN rm elasticsearch-7.4.2-amd64.deb
RUN echo "service elasticsearch start" > start.sh
RUN chmod +x start.sh
CMD bash start.sh ; /bin/sh
EXPOSE 9200
Итого, 19 слоев образа... Много, конечно, но это же первый контейнер, знать все тонкости в начале пути невозможно. Теперь мы можем начать собирать контейнер.
Но подождите, а что такое СЛОЙ? Давайте вновь окунемся немного в матчасть: ”Каждый Docker-образ состоит из слоёв, каждый из которых описывает какую-то инструкцию. Далее – Docker объединяет информацию из каждого слоя, и создает шаблон-образ, из которого запускается контейнер, в котором выполняются инструкции из каждого слоя, который был включен в данный образ. Каждый слой описывает какое-то изменение, которое должно быть выполнено с данными на запущенном контейнере.” Вроде бы все понятно, но если у вас еще возникнут вопросы, постараюсь своими словами объяснить ситуацию, примерно так-же как объясняют почти на любой конференции: «Чем меньше слоев, тем лучше, потому-что при внесении изменений в нижние слои не будет происходить пересборка вышестоящих слоев». Т.е если мы добавим после EXPOSE 9200 любой слой, например:
RUN mkdir /root/test
то все верхние слои у нас будут в кэше, и новый слой просто создастся и закэшируется. Но если мы добавим это же в начало, перед строчкой
RUN groupadd -g 1000 elasticsearch && useradd elasticsearch -u 1000 -g 1000
то все слои ниже будут пересобриться и кэшироваться вновь, а это не круто, поэтому давайте приведем наш Dockerfile к более-менее приемлемому виду. Вновь открываем наш Dockerfile:
>vi Dockerfile
FROM ubuntu:20.04
RUN groupadd -g 1000 elasticsearch && useradd elasticsearch -u 1000 -g 1000 && apt-get update && \
apt-get install -y --no-install-recommends \
apt-transport-https \
wget -y
COPY elasticsearch.yml /etc/elasticsearch/elasticsearch.yml
COPY jvm.options etc/elasticsearch/jvm.options
RUN wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.4.2-amd64.deb --no-check-certificate && dpkg -i --force-confdef ./elasticsearch-7.4.2-amd64.deb && rm elasticsearch-7.4.2-amd64.deb
RUN mkdir /usr/plugins/
COPY analysis-morphology-7.4.2.zip /usr/plugins/
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch ingest-attachment
COPY tika-core-1.22.jar usr/share/elasticsearch/plugins/ingest-attachment
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch file:/usr/plugins/analysis-morphology-7.4.2.zip
COPY synonyms.txt /etc/elasticsearch
RUN echo "service elasticsearch start" > start.sh && chmod +x start.sh
CMD bash start.sh ; /bin/sh
EXPOSE 9200
Так сказать немного причесали файлик, но это далеко не предел совершенства, в самом лучшем случае все COPY из файла необходимо убрать, оставить только один, а всю установку плагинов сделать в 1 слой, это уже если вам необходимо сделайте сами, ученье - свет, а не ученье - невозможность выйти из vim. Теперь когда мы привели в порядок наш файлик, настал наконец момент билда!
>docker build -t %nickname%/%reponame%:%tag% .
Под переменными выше можете подставить любые значения, а лучше зарегистрироваться на hub.docker.com и создать свой репозиторий.
После сборки образа, хочется уже запустить наше приложение. Но подождите, обратимся к документации elasticsearch для решения одной проблемы, которая не даст нам полноценно запустить контейнер. Идем в поисковик и гуглим "docker elasticsearch".
Переходим по первой ссылке до раздела
Using the Docker images in production
Там первый подраздел:
>set vm.max_map_count to at least 262144
На ХОСТОВОЙ МАШИНЕ открываем файлик /etc/sysctl.conf и пишем в самый низ новую строчку:
vm.max_map_count=262144
Далее, чтобы изменения применились без перезапуска или перечитки конфига, используем команду
>sudo sysctl -w vm.max_map_count=262144
Также создадим отдельную docker-сеть для elasticsearch:
>sudo docker network create -d bridge elastic
>docker run -d --name elasticsearch -v elasticsearch:/var/lib/elasticsearch --net elastic -p 9200:9200 -it %nickname%/%reponame%:%tag%
Поясню, что делает каждый флаг:
Для понимания: в нашем случае используется том на хостой машине elasticsearch который является прокинутым по пути /var/lib/elasticsearch. Т.е. ВСЕ, что создаст контейнер по пути /var/lib/elasticsearch будет доступно на хостовой машине. И также если вы создадите что-то в этом томе, будет доступно в контейнере. У любого админа сразу возникает вопрос, а как мне на хостовой машине посмотреть эти файлики? Как потрогать? Тут все просто:
Смотрим, какие тома у нас есть:
>sudo docker volume ls
<
DRIVER VOLUME NAME
local elasticsearch
Видим, что у нас появился наш том. Смотрим на него физически:
>sudo ls /var/lib/docker/volumes/
<
elasticsearch metadata.db
Все, что внутри этой папки, это прямой путь в контейнере. В нашем случае это elasticsearch. Если вы «старовер» и не доверяете абстрактным томам docker-а, то пожалуйста, можете создать на хостовой системе путь /var/lib/elasticsearch и прокидывать его напрямую в контейнер, тогда у вас строка запуска будет иметь вид:
>docker run -d --name elasticsearch -v /var/lib/elasticsearch:/var/lib/elasticsearch --net elastic -p 9200:9200 -it %nickname%/%reponame%:%tag%
Ну и все, теперь смотрим какие контейнеры у нас запущены
>sudo docker ps
<
9582606c8fa3 paperdad/elasticsearch-rx:full "/bin/sh -c 'bash st…" 4 seconds ago Up 1 second 0.0.0.0:9200->9200/tcp, :::9200->9200/tcp elasticsearch
И все. Мы создали с нуля свой первый образ, а из него запустили контейнер, который можно использовать в продуктивной среде без опаски. Но чтоб скачать еще где-то контейнер, его необходимо добавить в репозиторий, а для этого либо создаем локальный и выкидываем на внешку, либо добро пожаловать на hub.docker.com.
Данный материал рассчитан, в первую очередь, на новичков. Тертый админ сможет упрекнуть статью в том, что все описанное выше делится на 0 просто потому, что есть заранее готовый контейнер с эластиком, который понимает входящие переменные. Соглашусь что это так, и за основу можно было брать его. Но, по моему мнению, лучше научить человека основам, и принципам, а потом он уже сам доработает все под себя.
Спасибо за уделенное время на прочтение статейки! Комментарии и пожелания приветствуются.
Авторизуйтесь, чтобы написать комментарий