From 35644b9a77e30e1b9c107d36d640e4e03daebe10 Mon Sep 17 00:00:00 2001 From: Vladimir Protsenko Date: Wed, 2 Nov 2022 00:54:23 +0400 Subject: [PATCH] Fixed docker task. --- .../foodtrucks-app/build_and_run.sh | 6 +- .../foodtrucks-app/flask-app/app.py | 7 +- module2/07_containerization_docker/tasks.md | 170 +++++++++++------- 3 files changed, 114 insertions(+), 69 deletions(-) diff --git a/module2/07_containerization_docker/foodtrucks-app/build_and_run.sh b/module2/07_containerization_docker/foodtrucks-app/build_and_run.sh index 748c37f..9cd41fd 100644 --- a/module2/07_containerization_docker/foodtrucks-app/build_and_run.sh +++ b/module2/07_containerization_docker/foodtrucks-app/build_and_run.sh @@ -6,9 +6,9 @@ docker build -t studX/foodtrucks-web ./ # create the network docker network create foodtrucks-network -# start the ES container -docker run -d --net foodtrucks-network --name elastic elasticsearch:8.4.3 +# start the ES container, specify password beforehand +docker run -d --net foodtrucks-network --name elastic -e ELASTIC_PASSWORD=v7SLsbtXticPLADei5vS elasticsearch:8.4.3 # start the flask app container # point to elastic host: https://elastic:9200 user: elastic password: v7SLsbtXticPLADei5vS -docker run -d -p 80:5000 --net foodtrucks-network --name foodtrucks-web studX/foodtrucks-web https elastic 9200 elastic v7SLsbtXticPLADei5vS +docker run -d -p 80:5000 --net foodtrucks-network studX/foodtrucks-web https elastic 9200 elastic v7SLsbtXticPLADei5vS diff --git a/module2/07_containerization_docker/foodtrucks-app/flask-app/app.py b/module2/07_containerization_docker/foodtrucks-app/flask-app/app.py index f0b9e67..27e1c64 100644 --- a/module2/07_containerization_docker/foodtrucks-app/flask-app/app.py +++ b/module2/07_containerization_docker/foodtrucks-app/flask-app/app.py @@ -7,14 +7,15 @@ import requests es_scheme = sys.argv[1] es_host = sys.argv[2] -es_port =int(sys.argv[3]) +es_port = int(sys.argv[3]) es_user = sys.argv[4] es_password = sys.argv[5] +connection_string = f"{es_scheme}://{es_host}:{es_port}" print(f"Elastic server: {es_scheme}://{es_host}:{es_port}, auth: {es_user}:{es_password}") es = Elasticsearch( - f"{es_scheme}://{es_host}:{es_port}", + connection_string, basic_auth=(es_user, es_password), verify_certs=False ) @@ -40,7 +41,7 @@ def safe_check_index(index, retry=60): status = es.indices.exists(index=index) return status except exceptions.ConnectionError as e: - print("Unable to connect to ES. Retrying in 5 secs...") + print(f"Unable to connect to {connection_string}. Retrying in 5 secs...", flush=True) time.sleep(5) safe_check_index(index, retry-1) diff --git a/module2/07_containerization_docker/tasks.md b/module2/07_containerization_docker/tasks.md index f25ee77..5fa64c3 100644 --- a/module2/07_containerization_docker/tasks.md +++ b/module2/07_containerization_docker/tasks.md @@ -26,7 +26,7 @@ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin Проверте, всё ли установлено корректно: ``` -$ docker run hello-world +$ sudo docker run hello-world Hello from Docker. This message shows that your installation appears to be working correctly. @@ -38,14 +38,14 @@ This message shows that your installation appears to be working correctly. Для начала, запустите следующую команду: ``` -$ docker pull busybox +$ sudo docker pull busybox ``` **Внимание.** В зависимости от того, как вы установили Docker, вы можете увидеть сообщение permission denied (доступ запрещён) в ответ на вызов выше приведённой команды. Если вы на Mac, убедитесь, что Docker движок запущен. Если на Линукс, вам может потребоваться повысить права доступа с помощью команды `sudo`. В качестве альтернативного варианта вы можете добавить пользователя в Docker группу для решения этой проблемы. Команда `pull` скачивает образ `busybox` из Docker реестра и сохраняет его в систему. Вы можете использовать команду `docker images` для вывода в консоль списка образов находящихся в вашей системе. ``` -# docker images +$ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE busybox latest 00f017a8c2a6 2 weeks ago 1.11 MB hello-world latest 48b5124b2768 2 months ago 1.84 kB @@ -56,12 +56,12 @@ hello-world latest 48b5124b2768 2 months ago Великолепно! Теперь перейдём к запуску контейнера на основе этого образа. Для этого мы воспользуемся всемогущей командой `docker run`. ``` -$ docker run busybox +$ sudo docker run busybox ``` Постойте, но ничего не произошло! Это баг? Ну, нет. Под капотом произошло много всего. Когда вы запустили команду `run`, клиент Docker нашёл образ (в нашем случае, `busybox`), загрузил контейнер и запустил команду внутри этого контейнера. Мы не указали никаких аргументов, так что контейнер загрузился, выполнил команду `sh` и процесс контейнера завершился. Ну, да, как-то обидно. Попробуем сделать что-нибудь поинтереснее. ``` -$ docker run busybox echo "hello from busybox" +$ sudo docker run busybox echo "hello from busybox" hello from busybox ``` @@ -69,13 +69,13 @@ hello from busybox Давайте взглянем на команду `docker ps`. Она выводит на экран список всех запущенных контейнеров. ``` -$ docker ps +$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ``` В силу того, что ни один контейнер не запущен, выводится пустая строка. Попробуем более информативный вариант `docker ps -a`: ``` -$ docker ps -a +$ sudo docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 56fdebcf3df0 busybox "echo hi" About a minute ago Exited (0) About a minute ago jovial_wozniak 5f585bdd9545 busybox "echo hi" About a minute ago Exited (0) About a minute ago focused_golick @@ -87,7 +87,7 @@ c73ceb428f23 hello-world "/hello" 25 minutes ago Наверное вы думаете, существует ли способ запустить более одной команды в контейнере. Давайте попробуем: ``` -$ docker run -it busybox sh +$ sudo docker run -it busybox sh / # ls bin dev etc home proc root sys tmp usr var / # uptime @@ -104,14 +104,14 @@ bin dev etc home proc root sys tmp usr var Давайте вкратце рассмотрим удаление контейнеров. Мы видели выше, что с помощью команды `docker ps -a` всё ещё можно увидеть остатки завершённых контейнеров. На протяжении этого занятия, вы будете запускать `docker run` несколько раз, и оставшиеся, покинутые контейнеры будут съедать дисковое пространство. Так что если они больше вам не понадобятся, вы можете взять за правило удалять контейнеры после завершения работы с ними. Для этого используется команда `docker rm`. Просто скопируйте ID (можно несколько) из вывода выше и передайте параметрами в команду. ``` -$ docker rm 305297d7a235 ff0a5c3750b9 +$ sudo docker rm 305297d7a235 ff0a5c3750b9 305297d7a235 ff0a5c3750b9 ``` При удалении идентификаторы будут снова выведены на экран. Если нужно удалить много контейнеров, то вместо ручного копирования и вставки можно сделать так: ``` -$ docker rm $(docker ps -a -q -f status=exited) +$ sudo docker rm $(docker ps -a -q -f status=exited) ``` Эта команда удаляет все контейнеры, у которых статус `exited`. Флаг `-q` возвращает только численные ID, а флаг `-f` фильтрует вывод на основе предоставленных условий. Последняя полезная деталь — команде docker run можно передать флаг `--rm`, тогда контейнер будет автоматически удаляться при завершении. Это очень удобно для разовых запусков и экспериментов с Docker. @@ -144,20 +144,20 @@ $ docker rm $(docker ps -a -q -f status=exited) Поехали. Для одностраничного сайта нам понадобится заранее созданный образ и размещённый в реестре - `nginx:latest`. Можно запустить образ напрямую командой `docker run`. ``` -$ docker run nginx:latest +$ sudo docker run nginx:latest ``` Так как образа не существует локально, клиент сначала скачает образ из реестра, а потом запустит его. Если всё пройдёт без проблем, то вы увидите в терминале сообщения о запуске nginx. Теперь сервер запущен. Как увидеть сайт в действии? На каком порту работает сервер? И, что самое важное, как напрямую достучаться до контейнера из хост системы? В нашем случае клиент не открывает никакие порты, так что нужно будет перезапустить команду `docker run` чтобы сделать порты публичными. Заодно давайте сделаем так, чтобы терминал не был прикреплен к запущенному контейнеру. В таком случае можно будет спокойно закрыть терминал, а контейнер продолжит работу. Этот режим называется `detached`. ``` -$ docker run -d -P --name static-site nginx:latest +$ sudo docker run -d -P --name static-site nginx:latest e61d12292d69556eabe2a44c16cbd54486b2527e2ce4f95438e504afb7b02810 ``` Флаг `-d` открепит (`--detach`) терминал, флаг `-P` сделает все открытые порты публичными и случайными, и, наконец, флаг `--name` это имя, которое мы хотим дать контейнеру. Теперь можно увидеть порты с помощью команды `docker port [CONTAINER]`. ``` -$ docker port static-site +$ sudo docker port static-site 80/tcp -> 0.0.0.0:49153 80/tcp -> :::49153 ``` @@ -166,7 +166,7 @@ $ docker port static-site Вы также можете назначить свой порт, на который Docker клиент будет перенаправлять запросы на соединение к контейнеру. ``` -$ docker run -p 8888:80 --name static-site nginx:latest +$ sudo docker run -p 8888:80 --name static-site nginx:latest ``` Ключ `-p` устанавливает соответствие между портом хост операционной системы (8888) с портом контейнера (80). @@ -215,17 +215,17 @@ $ docker pull ubuntu:12.04 ### 2.3 Наш первый образ -Теперь, когда мы лучше понимаем, что такое образы и какие они бывают, самое время создать собственный образ. Цель этого раздела — создать образ с простым приложением на Flask. Для этого задания подготовлено маленькое приложение `flask-app`, которое выводит случайную картинку с кошкой. Склонируйте этот репозиторий к себе на локальную машину `git clone <адрес-репозитория>` и перейдите в папку с приложением. +Теперь, когда мы лучше понимаем, что такое образы и какие они бывают, самое время создать собственный образ. Цель этого раздела — создать образ с простым приложением на Flask. Для этого задания подготовлено маленькое приложение `cats-app`, которое выводит случайную картинку с кошкой. Склонируйте этот репозиторий к себе на локальную машину `git clone <адрес-репозитория>` и перейдите в папку с приложением. Следующим шагом является создание образа с данным веб-приложением. Как говорилось выше, все пользовательские образы базируются на базовых образах. Так как приложение написано на Python, базовый образ следует выбрать с предустановленным Python 3. Точнее мы собираемся использовать `python:3.8` версию python образа. -Теперь у нас есть все ингредиенты для создания собственных образов - работающее веб-приложение и базовый образ. Как мы будем подходить к этой задаче? Ответ - `Dockerfile`. +Теперь у нас есть все ингредиенты для создания собственных образов - работающее веб-приложение и базовый образ. Как мы будем подходить к этой задаче? Ответ — `Dockerfile`. ### 2.4 Dockerfile **Dockerfile** — это простой текстовый файл, в котором содержится список команд Docker клиента. Это простой способ автоматизировать процесс создания образа. Самое классное, что команды в Dockerfile почти идентичны своим аналогам в Linux. Это значит, что в принципе не нужно изучать новый синтаксис, чтобы начать работать с докер-файлами. -В директории с приложением есть Dockerfile, но так как мы делаем все впервые, нам нужно создать его с нуля. Создайте новый пустой файл в любимом текстовом редакторе, и сохраните его в той же директории, где находится flask-приложение. Назовите файл Dockerfile. +В директории с приложением, нам нужно его создать. Создайте пустой файл в любимом текстовом редакторе, и сохраните его в той же директории `cats-app`. Назовите файл Dockerfile. Для начала укажем базовый образ. Для этого нужно использовать ключевое слово `FROM`. ```Dockerfile @@ -243,7 +243,7 @@ RUN pip install --no-cache-dir -r requirements.txt EXPOSE 5000 ``` -Последний шаг — указать команду для запуска приложения. Это просто `python ./app.py`. Для этого используем команду `CMD`: +Последний шаг — указать команду по-умолчанию для запуска приложения. Это просто `python ./app.py`. Для этого используем команду `CMD`: ```Dockerfile CMD ["python", "./app.py"] ``` @@ -270,11 +270,15 @@ CMD ["python", "./app.py"] Теперь можно создать образ. Команда `docker build` занимается сложной задачей создания образа на основе `Dockerfile`. -Листинг ниже демонстрирует процесс. Перед тем, как запустите команду сами (не забудьте точку в конце), проверьте, чтобы там был ваш username. Username должен соответствовать тому, что использовался при регистрации на Docker hub. Если вы еще не регистрировались, то сделайте это до выполнения команды. +Листинг ниже демонстрирует процесс. Перед тем, как запустите команду сами (не забудьте путь к `cats-app` в конце), проверьте, чтобы там был ваш `имя_пользователя`. Имя пользователя должено соответствовать тому, что использовалось при регистрации на Docker Hub. Если вы используете частный реестр, то к тэгу добавляется в начало доменное имя хоста и опциональный порт, например +```Dockerfile +# -t доменное_имя_реестра:порт/имя_пользователя/имя_образа:тэг_образа +-t myregistryhost:5000/fedora/httpd:version1.0 +``` Команда `docker build` довольно проста: она принимает опциональный тег с флагом `-t имя_пользователя/имя_образа` и путь до директории, в которой лежит `Dockerfile`. ``` -$ docker build -t имя_пользователя/имя_образа ./ +$ docker build -t studX/catsapp ./ Sending build context to Docker daemon 8.704 kB Step 1 : FROM python:3-onbuild # Executing 3 build triggers... @@ -294,13 +298,14 @@ Step 3 : CMD python ./app.py ---> 13e87ed1fbc2 Removing intermediate container f01401a5ace9 Successfully built 13e87ed1fbc2 +Successfully tagged studX/catsapp:latest ``` -Если у вас нет образа `python:3.8`, то клиент сначала скачает его, а потом возьмётся за создание вашего образа. Так что, вывод на экран может отличаться от приведённого выше. Если все прошло хорошо, то образ готов! Запустите `docker images` и увидите свой образ в списке. +Если у вас нет образа `python:3.8`, то клиент сначала скачает его, а потом возьмётся за создание вашего образа. Так что, вывод на экран может отличаться от приведённого выше. Если всё прошло хорошо, то образ готов! Запустите `docker images` и увидите свой образ в списке. Последний шаг — запустить образ и проверить его работоспособность: ``` -$ docker run -p 80:5000 --name catsapp имя_пользователя/имя_образа +$ sudo docker run -p 80:5000 --name catsapp studX/catsapp * Serving Flask app 'app' (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. @@ -329,7 +334,7 @@ Press CTRL+C to quit ### 3.1 Приложение для поиска фургонов c едой в Сан-Франциско -Приложение, которое мы переведём в Docker, называется SF Food Trucks. Приложение создавалось с целью сделать что-то похожее на реально эксплуатируемое приложение и приносещее пользу, но не слишком сложное. +Приложение, которое мы переведём в Docker, называется `foodtrucks-app`. Приложение создавалось с целью сделать что-то похожее на реально эксплуатируемое приложение и приносещее пользу, но не слишком сложное. Серверная часть написана на Python (Flask framework), а для поиска используется Elasticsearch. Как и всё остальное в этом обучении, код проекта находится на Github. Мы используем это приложение, чтобы научиться запускать и разворачивать многоконтейнерное окружение. @@ -341,15 +346,22 @@ Press CTRL+C to quit Не удивительно, что существует официальный образ для Elasticsearch. Чтобы запустит ES, нужно всего лишь выполнить `docker run`, и вскоре у нас будет локальный, работающий контейнер с одним узлом ES. Некоторые компании считают плохой практикой выкладывать образы с тегом `latest`, в том числе Elastic, поэтому выберем и укажеи тег явно. -**Замечание.** Если контейнер не запускается, попробуйте подключиться в интерактивном режиме (с ключом `-it`) и выяснить причину ошибки. Одной из причин может быть нехватка памяти для Elasticsearch. Для версии старше 5 требуется минимум 2 Гб. Проблема также может быть в хост машине. Увеличьте количество доступных кусочков памяти процессу выполнением команды `sysctl -w vm.max_map_count=262144`. Обратите внимание, что это модифицирует свойство системы, в которой может выполняться множество других контейнеров. +**Замечание.** Если контейнер не запускается, попробуйте подключиться в интерактивном режиме (с ключом `-it`) и выяснить причину ошибки. Одной из причин может быть нехватка памяти для Elasticsearch. Для версии старше 5 требуется минимум 2 Гб. Если вы увеличили оббъём памяти в виртуальной машине, не забудьте остановить и запустить виртуальную машину, чтобы изменения применились. + +Проблема также может быть в максимальное количество кусочков памяти `vm.max_map_count`, которые может иметь процесс . Увеличьте количество доступных кусочков памяти процессу выполнением команды `sysctl -w vm.max_map_count=262144`. Обратите внимание, что это модифицирует свойство системы, в которой может выполняться множество других контейнеров. ``` -$ docker run -d -p 9200:9200 --name elastic elasticsearch:8.4.3 +$ sudo docker run -d -p 9200:9200 --name elastic elasticsearch:8.4.3 d582e031a005f41eea704cdc6b21e62e7a8a42021297ce7ce123b945ae3d3763 ``` +Проверьте, что в логах нет ошибок. +``` +$ sudo docker logs elastic +``` + При запуске в режиме демона сервис не генерирует автоматически пароль для пользователя `elastic`. Мы можем сделать это сами после запуска, чтобы протестировать сервис. ``` -docker exec -ti elastic /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic +$ sudo docker exec -ti elastic /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic WARNING: Owner of file [/usr/share/elasticsearch/config/users] used to be [root], but now is [elasticsearch] WARNING: Owner of file [/usr/share/elasticsearch/config/users_roles] used to be [root], but now is [elasticsearch] This tool will reset the password of the [elastic] user to an autogenerated value. @@ -382,11 +394,11 @@ $ curl https://localhost:9200 --insecure --user elastic:36EBVQtPjbiFPMh9Bk7X } ``` -Заодно давайте запустим контейнер с Flask. Но вначале нужен Dockerfile. В прошлой секции мы использовали образ `python:3.8` в качестве базового. Однако, в этом раз, кроме установки зависимостей через `pip`, нам нужно, чтобы приложение генерировало Javascript файл. Для этого потребуется Nodejs. В связи с появлением дополнительных файлов для работы контейнера нам нужно построить новый образ. Начнем с базового образа debian:buster. +Заодно давайте запустим контейнер с Flask. Но вначале нужен Dockerfile. В прошлой раз мы использовали образ `python:3.8` в качестве базового. Однако, в этом раз, кроме установки зависимостей через `pip`, нам нужно, чтобы приложение генерировало Javascript файл. Для этого потребуется Nodejs. В связи с появлением дополнительных файлов для работы контейнера нам нужно построить новый образ. Начнем с базового образа `ubuntu:focal`. **Замечание.** Если оказывается, что существующий образ не подходит для вашей задачи, то спокойно создавайте свой образ на основе другого базового образа. В большинстве случаев, для образов на Docker Hub можно найти соответствующий Dockerfile на Github. Почитайте существующие докерфайлы — это один из лучших способов научиться делать свои образы. -Наш Dockerfile для Flask-приложения выглядит следующим образом: +Наш Dockerfile для приложения `foodtrucks-app` выглядит следующим образом: ``` # start from base FROM ubuntu:focal @@ -415,16 +427,16 @@ ENTRYPOINT [ "python3", "./app.py" ] Тут много всего нового. Вначале указан `focal` образ Ubuntu, потом используется пакетный менеджер `apt` для установки зависимостей, в частности: Python и Node. Флаг `y` нужен автоматического выбора "Yes" во всех диалогах. Также создается символическая ссылка для бинарного файла node. Это нужно для решения проблем обратной совместимости. -Потом мы используем команду ADD для копирования приложения в нужную директорию в контейнере — `/opt/flask-app`. Здесь будет находиться весь наш код. Мы также устанавливаем эту директорию в качестве рабочей, так что следующие команды будут выполняться в контексте этой локации. Теперь, когда наши системные зависимости установлены, пора установить зависимости уровня приложения. Начнем с Node, установки пакетов из npm и запуска команды сборки, как указано в нашем `package.json` файле. В конце устанавливаем пакеты Python, открываем порт и определяем запуск приложения с помощь ENTRYPOINT. ENTRYPOINT отличается от CMD тем, что при запуске можно передвать в контейнер параметры, которые добавятся к ENTRYPOINT. +Потом мы используем команду ADD для копирования приложения в нужную директорию в контейнере — `/opt/flask-app`. Здесь будет находиться весь наш код. Мы также устанавливаем эту директорию в качестве рабочей, так что следующие команды будут выполняться в контексте этой локации. Теперь, когда наши системные зависимости установлены, пора установить зависимости уровня приложения. Начнем с Node, установки пакетов из npm и запуска команды сборки, как указано в нашем `package.json` файле. В конце устанавливаем пакеты Python, открываем порт и определяем запуск приложения с помощь ENTRYPOINT. ENTRYPOINT отличается от CMD тем, что при запуске можно передвать в контейнер параметры, которые добавятся к ENTRYPOINT. В нашем случае это будет параметры elasticsearch сервера: протокол, доменное имя, порт, имя пользователя, пароль. Наконец, можно собрать образ и запустить контейнер. ``` -$ docker build -t studX/<имя образа>. +$ sudo docker build -t studX/foodtrucks-web. ``` При первом запуске нужно будет больше времени, так как клиент Докера будет скачивать образ ubuntu, запускать все команды и готовить образ. Повторный запуск `docker build` после последующих изменений будет практически моментальным. Давайте попробуем запустить приложение. ``` -$ docker run -P studX/<имя образа> https elastic 9200 elastic 36EBVQtPjbiFPMh9Bk7X +$ docker run -P studX/foodtrucks-web https localhost 9200 elastic 36EBVQtPjbiFPMh9Bk7X Unable to connect to ES. Retying in 5 secs... Unable to connect to ES. Retying in 5 secs... Unable to connect to ES. Retying in 5 secs... @@ -439,34 +451,44 @@ Out of retries. Bailing out... Ладно, давайте запустим docker ps. Что тут у нас: ``` -$ docker ps +$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c31bf3beb299 elasticsearch "/docker-entrypoin..." 2 hours ago Up 2 hours 0.0.0.0:9200->9200/tcp, 9300/tcp tender_wilson ``` -Итак, у нас есть контейнер ES по любому локальному адресу (0.0.0.0) и порту 9200, и мы можем напрямую обращаться к нему. Если можно было бы сообщить нашему приложению подключаться к этому адресу, то оно сможет общаться с ES, верно? Давайте взглянем на код на Питоне, туда, где описано подключение. -``` +Итак, у нас есть контейнер `elastic`, который слушает по любому локальному адресу (0.0.0.0) и порту 9200, и мы можем напрямую обращаться к нему с хоста. Если можно было бы сообщить нашему приложению как подключиться к этому адресу, то оно сможет общаться с `elastic`, верно? Давайте взглянем на код на Python, туда, где описано подключение и вспомним, переданные в контейнер аргументы. +```python +es_scheme = sys.argv[1] +es_host = sys.argv[2] +es_port = int(sys.argv[3]) +es_user = sys.argv[4] +es_password = sys.argv[5] +connection_string = f"{es_scheme}://{es_host}:{es_port}" + es = Elasticsearch( - f"{es_scheme}://{es_host}:{es_port}", + connection_string, basic_auth=(es_user, es_password), verify_certs=False ) ``` +``` +https localhost 9200 elastic 36EBVQtPjbiFPMh9Bk7X +``` -Для того, чтобы это заработало, нужно запустить Flask контейнер на том же хосте, что и контейнер ES, и всё заработает, да? К сожалению, нет, потому что контейнер ES доступен по адресу хост-машины только с хост-машины. Другой контейнер не сможет обратиться по этому адресу. Ладно, если не этот адрес, то какой тогда адрес нужно использовать для работы с контейнером ES? Рады, что вы спросили. +Для того, чтобы это заработало, нужно запустить `foodttrucks-web` контейнер на том же хосте, что и контейнер `elastic`, и всё заработает, да? К сожалению, нет, потому что контейнер `elastic` доступен по адресу хост-машины только с хост-машины. Другой контейнер не сможет обратиться по этому адресу. Ладно, если не этот адрес, то какой тогда адрес нужно использовать для работы с контейнером `elastic`? Хорошо, что вы спросили. Подошло время, чтобы изучить работу сети в Docker. После установки, Docker автоматически создает три сети: ``` -$ docker network ls +$ sudo docker network ls NETWORK ID NAME DRIVER 075b9f628ccc none null be0f7178486c host host 8022115322ec bridge bridge ``` -Сеть bridge — это сеть, в которой контейнеры запущены по умолчанию (программный роутер). Это значит, что когда вы запускаете контейнер ES, он работает в bridge сети. Чтобы удостовериться, давайте проверим: +Сеть bridge — это сеть, в которой контейнеры запущены по умолчанию (программный роутер). Это значит, что когда вы запускаете контейнер `elastic`, он работает в bridge сети. Чтобы удостовериться, давайте проверим: ``` -$ docker network inspect bridge +$ sudo docker network inspect bridge [ { "Name": "bridge", @@ -521,9 +543,9 @@ $ docker network inspect bridge ] ``` -Видно, что контейнер `e931ab24dedc` находится в секции Containers. Также виден IP-адрес, выданный этому контейнеру —` 172.17.0.2`. Именно этот адрес мы и искали? Давайте проверим: запустим Flask-приложение и попробуем обратиться к нему по IP: +Видно, что контейнер `e931ab24dedc` находится в секции Containers. Также виден IP-адрес, выданный этому контейнеру — `172.17.0.2`. Именно этот адрес мы и искали? Давайте проверим: запустим `foodtrucks-web` приложение и попробуем обратиться к нему по IP: ``` -$ docker run -it --rm studX/<имя образа> --name flaskapp bash +$ sudo docker run -it --rm studX/foodtrucks-web --name flaskapp bash root@35180ccc206a:/opt/flask-app# curl https://172.17.0.2:9200 --insecure --user elastic:36EBVQtPjbiFPMh9Bk7X { "name" : "a739a33489d9", @@ -545,11 +567,11 @@ root@35180ccc206a:/opt/flask-app# curl https://172.17.0.2:9200 --insecure --user root@35180ccc206a:/opt/flask-app# exit ``` -Cейчас всё должно стать понятно. Мы запустили контейнер в интерактивном режиме с процессом bash. Флаг `--rm` нужен для удобства, благодаря нему контейнер автоматически удаляется после выхода. Мы попробуем curl, но нужно сначала установить его. После этого можно удостовериться, что по адресу 172.17.0.2:9200 на самом деле можно обращаться к ES! Супер! +Cейчас всё должно стать понятно. Мы запустили контейнер в интерактивном режиме с процессом `bash`. Флаг `--rm` нужен для удобства. Благодаря нему контейнер автоматически удаляется после выхода. Мы попробуем `curl`, но нужно сначала установить его. После этого можно удостовериться, что по адресу `172.17.0.2:9200` на самом деле можно обращаться к `elastic`! Супер! -Не смотря на то, что мы нашли способ наладить связь между контейнерами, существует несколько проблем с этим подходом: +Не смотря на то, что мы нашли способ наладить связь между контейнерами, существует несколько проблем с этим подходом. -Придется добавлять записи в файл `/etc/hosts` (локальный DNS) внутри Flask-контейнера, чтобы приложение понимало, что имя хоста es означает 172.17.0.2. Если IP-адрес меняется, то придется вручную менять запись. +Придется добавлять записи в файл `/etc/hosts` (локальный DNS) внутри `foodtrucks-web` контейнера, чтобы приложение понимало, что имя хоста `elastic` означает `172.17.0.2`. Если IP-адрес меняется, то придется вручную менять запись. Так как сеть bridge используется всеми контейнерами по умолчанию, этот метод не безопасен. @@ -557,11 +579,11 @@ Cейчас всё должно стать понятно. Мы запустил Во-первых, давайте создадим свою сеть. ``` -$ docker network create foodtrucks-network +$ sudo docker network create foodtrucks-network 1a3386375797001999732cb4c4e97b88172d983b08cd0addfcb161eed0c18d89 ``` ``` -$ docker network ls +$ sudo docker network ls NETWORK ID NAME DRIVER 1a3386375797 foodtrucks-network bridge 8022115322ec bridge bridge @@ -573,20 +595,31 @@ be0f7178486c host host Теперь у нас есть сеть. Можно запустить наши контейнеры внутри сети с помощью флага `--net`. Давайте так и сделаем, но сначала остановим контейнер с ElasticSearch, который был запущен в сети `bridge` по умолчанию. ``` -$ docker ps +$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e931ab24dedc elasticsearch "/docker-entrypoint.s" 4 hours ago Up 4 hours 0.0.0.0:9200->9200/tcp, 9300/tcp cocky_spence ``` ``` -$ docker stop e931ab24dedc +$ sudo docker stop e931ab24dedc +e931ab24dedc +$ sudo docker rm e931ab24dedc e931ab24dedc ``` ``` -$ docker run -d -p 9200:9200 --net foodtrucks-network --name elastic elasticsearch:8.4.3 +$ sudo docker run -d -p 9200:9200 --net foodtrucks-network --name elastic elasticsearch:8.4.3 2c0b96f9b8030f038e40abea44c2d17b0a8edda1354a08166c33e6d351d0c651 ``` +Сгенерируем пароль для Elasticsearch, как делали ранее: ``` -$ docker network inspect foodtrucks-network +$ sudo docker exec -ti elastic /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic +... +Password for the [elastic] user successfully reset. +New value: 9gllhwpGufZq2GfkfZ9c +``` + +Мы сделали то же, что и раньше, но на этот раз присоединили контейнер к сети `foodtrucks-network`. +``` +$ sudo docker network inspect foodtrucks-network [ { "Name": "foodtrucks-network", @@ -627,10 +660,10 @@ $ docker network inspect foodtrucks-network ] ``` -Мы сделали то же, что и раньше, но на этот раз присоединили контейнер к сети foodtrucks. Перед тем, как запускать контейнер с приложением, давайте проверим что происходит, когда запуск происходит в сети. +Перед тем, как запускать контейнер с приложением, давайте проверим что происходит, когда запуск происходит в сети. ``` -$ docker run -it --rm --net foodtrucks-network studX/<имя образа> bash -root@53af252b771a:/opt/flask-app# curl https://elastic:9200 --insecure --user elastic:v7SLsbtXticPLADei5vS +$ sudo docker run -it --rm --net foodtrucks-network --entrypoint /bin/bash studX/foodtrucks-web +root@53af252b771a:/opt/flask-app# curl https://elastic:9200 --insecure --user elastic:9gllhwpGufZq2GfkfZ9c { "name" : "fe09955428cc", "cluster_name" : "docker-cluster", @@ -649,44 +682,55 @@ root@53af252b771a:/opt/flask-app# curl https://elastic:9200 --insecure --user el "tagline" : "You Know, for Search" } ``` +Файлы приложения скопировались внутрь. ``` root@53af252b771a:/opt/flask-app# ls app.py node_modules package.json requirements.txt static templates webpack.config.js ``` +Попробуем запустить. ``` root@53af252b771a:/opt/flask-app# python3 app.py https elastic 9200 elastic v7SLsbtXticPLADei5vS -Index not found... -Loading data in elasticsearch ... -Total trucks loaded: 733 - * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) +... +Total trucks loaded: 478 + * Serving Flask app 'app' (lazy loading) + * Environment: production + WARNING: This is a development server. Do not use it in a production deployment. + Use a production WSGI server instead. + * Debug mode: off +WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. + * Running on all addresses (0.0.0.0) + * Running on http://127.0.0.1:5000 + * Running on http://172.21.0.3:5000 +Press CTRL+C to quit ``` ``` root@53af252b771a:/opt/flask-app# exit ``` -Ура! Работает! Магическим образом Docker теперь разрешает имя es в нужный ip, и поэтому es:9200 можно использовать в приложении — этот адрес корректно направляет запросы в контейнер ES. Отлично! Давайте теперь запустим Flask-контейнер по-настоящему: +Ура! Работает! Магическим образом Docker теперь разрешает имя `elastic` в нужный ip, и поэтому `https://elastic:9200` можно использовать в приложении — этот адрес корректно направляет запросы в контейнер `elastic`. Отлично! Давайте теперь запустим `foodtrucks-web` контейнер по-настоящему: ``` -$ docker run -d --net foodtrucks -p 80:5000 --name foodtrucks-web <имя пользователя Docker>/<имя образа> +$ sudo docker run -d --net foodtrucks-network -p 80:5000 --name foodtrucks-web studX/foodtrucks-web 2a1b77e066e646686f669bab4759ec1611db359362a031667cacbe45c3ddb413 ``` ``` -$ docker ps +$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2a1b77e066e6 prakhar1989/foodtrucks-web "python ./app.py" 2 seconds ago Up 1 seconds 0.0.0.0:5000->5000/tcp foodtrucks-web -2c0b96f9b803 elasticsearch "/docker-entrypoint.s" 21 minutes ago Up 21 minutes 0.0.0.0:9200->9200/tcp, 9300/tcp es +2c0b96f9b803 elasticsearch "/docker-entrypoint.s" 21 minutes ago Up 21 minutes 0.0.0.0:9200->9200/tcp, 9300/tcp elastic ``` Зайдите на http://studX.myoffice.ru, и увидите приложение в работе. Опять же, может показаться, что было много работы, но на самом деле мы ввели всего 4 команды чтобы с нуля дойти до работающего приложения. Все команды, которые мы проделали собраны в bash скрипте. ``` #!/bin/bash + # build the flask container docker build -t studX/foodtrucks-web ./ # create the network docker network create foodtrucks-network -# start the ES container -docker run -d --net foodtrucks-network --name elastic elasticsearch:8.4.3 +# start the ES container, specify password beforehand +docker run -d --net foodtrucks-network --name elastic -e ELASTIC_PASSWORD=v7SLsbtXticPLADei5vS elasticsearch:8.4.3 # start the flask app container # point to elastic host: https://elastic:9200 user: elastic password: v7SLsbtXticPLADei5vS