Глава 5. За рамками прикладного приложения: добавляем Redis
Содержание
Хорошо, признайтесь. Вы посмотрели на заголовок этой главы и подумали: "Redis ?! А как насчёт настройки базы данных?!" Раз это так, я обещаю, что я не сошёл с ума: есть очень веская причина для того чтобы сначала заняться Redis, как вы вскоре узнаете.
Прежде всего, давайте рассмотрим чего мы добились. Мы уже изучили как:
-
Применять Docker для генерации свежего роекта Rails не имея установленного Rails
-
Запускать сервер Ruby для исполнения нашего приложения
-
Обеспечивать факт установки наших gems и современности
-
Создавать лично наш образ Docker подогнанный под исполнение нашего прикладного приложения
-
Применять Docker Compose для управления всем нашим процессом
Всё это неплохо для начала, но в данный момент недостаточно для построения чего- то отличающегося от наиболее базовых вебсайтов или приложений. Мы упустили некий ключевой момент в нашей мозаике: как подключать наше приложение Rails к внешним службам таким как ... некая база данных. В этой главе вы не изучите в точности как это делать, ачав с Redis (если вам не не известен Redis, это некое хранимое в оперативной памяти хранилище ключ- значение, применяемое при обмене сообщениями pub/sub, в очередях, при кэшировании и так далее - все крутые парни применяют его, а после этой главы и вы тоже).
Почему перед базами данных Redis? Потому как процесс добавления служб в наше прикладное приложение аналогичен, оказывается, что Redis проще для интеграции в ваше приложение чем некая база данных. Доверьтесь мне на слово: если вы последуете моему совету, вам будет проще.
На самом деле в этой главе мы изучаем основные навыки добавления любой службы в ваше прикладное приложение, будь то база данных (что мы сделаем в идущей вслед за этой главе), фоновых исполнителях, Elasticsearch или даже обособленном интерфейсе JavaScript. Вскоре наши запитанные Docker прикладные приложения станут настолько же мощными что мы применяем, а затем заменят их.
Реквизит для Аанда | |
---|---|
Данное применяемое в этой главе демонстрационное приложение было вдохновлено демонстрацией Аанда Прасада, которая показала как подключать базовое приложение Python Flask к Redis с помощью Compose. Аанд является создателем Fig - предшественника Docker Compose - и в недавнем прошлом сотрудника Docker. |
Итак, мы желаем чтобы наше приложение Rails общалось с Redis, так? Ну, во- первых, нам понадобится некий сервер Redis, с которым сможет общаться наше приложение. Как и следовало ожидать, теперь мы не намерены устанавливать и запускать Redis в своей локальной машине. Вместо этого давайте воспользуемся возможностями Docker и запустим некий сервер Redis внутри контейнера.
В конечном счёте мы желаем добавить Redis как некую новую службу с помощью Compose. Однако так как это самое первое
что нам приходится добавлять, мы собираемся выполнить первые детские шаги. Мы начнём с рассмотрения того как запускать
Redis в неком контейнере при помощи docker run
прежде чем мы вернём цикл обратно
с тем чтобы Compose делал это автоматически. По мере того как вы приобретёте дополнительный опыт и уверенность вы
окажетесь в состоянии пропускать этот первый шаг и перепрыгивать сразу к настройке службы в Compose.
Для запуска сервера Redis при помощи docker run
мы вызываем такую команду:
$ docker run --name redis-container redis
Эта команда в целом нам знакома: она просит Docker запустить некий контейнер на основе
официального образа Docker
redis
. Тем не менее, здесь имеется пара параметров, которые нам ранее не встречались.
Для идентификации всякого нового контейнера Docker предоставляет ему некий уникальный
идентификатор контейнера. В точности как в разделе
Именование и сопровождение версий для нашего образа, параметр
--name
просит Docker присвоить название нашему новому контейнеру в виде удобного,
читаемого человеком названия.
Теперь остановим свой сервер Redis при помощи Ctrl - C
.
Окончательной нашей целью является файл docker-compose.yml
для полного описания
нашего приложения, включая все его зависимости. Посмотрев на запуск некого сервера Redis при помощи
docker run
мы готовы настроить Compose на управлением Redis под нас.
Давайте просмотрим свой файл docker-compose.yml
:
version: '3'
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- .:/usr/src/app
Давайте изменим его чтобы он содержал новую службу, которая вызывает redis
:
version: '3'
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- .:/usr/src/app
» redis:
» image: redis
Данное определение для нашей новой службы redis
совершенно отлично от нашей
службы web
. Для начала, оно намного проще; оно имеет всего лишь единственное
свойство с названием image
.
при определении некой службы у нас имеется два способа определения образа, который будет применён для создания
контейнеров. Наша служба web
применяет свойство
build
для указания Compose на необходимость сборки нашего персонального образа
из некого Dockerfile
. Однако для применения вместо этого предварительно имеющегося
образа мы можем определить соответствующее название образа свойством image
.
Здесь мы определяем свой образ redis
в точности как мы это делали в своей команде
docker run
.
Помимо этого основное отличие заключается в том, что мы не делаем определения.
Мы не публикуем никакие порты. Нашей службе web
требуется публикация порта
с тем чтобы те запросы, которые делаются в нашей локальной машине достигали имеющийся сервер Rails, запущенный
внутри контейнера. Redis, однако, не нуждается в доступе извне; на самом деле, с целью безопасности, мы предпочитаем чтобы
его не было вовсе. Не выставляя некий порт он скрыт и не имеет доступа из внешнего мира.
Мы также не определяем никаких томов подлежащих монтированию. Наша служба web
применяет тома для монтирования нашего локального каталога, содержащего код нашего проекта Rails внутри своего
контейнера. Мы делаем это для того, чтобы мы были способны изменять эти файлы локально, причём все изменения автоматически
также автоматически подхватываются внутри нашего контейнера. Для Redis нам не требуется такое поведение - мы не изменяем
никакие файлы.
Теперь давайте запустим свой сервер Redis:
$ docker-compose up -d redis
Мы можем видеть запуск Redis просмотрев регистрационные записи:
$ docker-compose logs redis
Attaching to myapp_redis_1
redis_1 | 1:C 15 Jan 2019 10:03:52.794 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis_1 | 1:C 15 Jan 2019 10:03:52.794 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=1, just started
...
redis_1 | 1:M 15 Jan 2019 10:03:52.796 * Running mode=standalone, port=6379
...
redis_1 | 1:M 15 Jan 2019 10:03:52.796 # Server initialized
...
redis_1 | 1:M 15 Jan 2019 10:03:52.796 * Ready to accept connections
Отлично! Мы успешно настроили Redis в качестве некой новой службы для своего приложения.
Мы только что запустили Redis при помощи Compose и увидели из его вывода что он исполняется. Однако поскольку мы всё ещё знакомимся с Docker, давайте вручную подключимся к нашему серверу Redis и вступим с ним во взаимодействие чтобы проверить для себя что он на самом деле работает.
Некий быстрый способ для этого состоит в применении интерфейса командной строки Redis
(redis-cli
). Мы можем воспользоваться тем же самым образом
redis
который уже обладает установленным
redis-cli
. Что удобно.
Вместо того чтобы настраивать некую новую обособленную службу в Compose мы можем подхватить прицепом свою уже
имеющуюся службу redis
, так как она использует нужный нам образ
redis
. Применив тот приём, который мы изучили в
Исполнение одноразовых команд, мы можем исполнить
redis-cli
и подключиться к своему серверу Redis при помощи такой команды:
$ docker-compose run --rm redis redis-cli -h redis
Эта команда говорит: "В неком одноразовом контейнере (--rm
) для нашей
службы redis
выполнить команду
redis-cli -h redis
". Выполнив её вы должны обнаружить стандартное приглашение
Redis отображающее название самого хоста и порт исполнения:
redis:6379>
Не стесняйтесь потренироваться здесь. Например, попробуйте запустить команду ping
,
которая должна выдать вам соответствующий отклик "PONG"
. Когда вы
закончите, выполните выход при помощи команды quit
- это завершит ваш клиент
Redis и, как результат и сам контейнер.{Прим. пер.: подробнее с работой redis-cli
можете ознакомиться в нашем переводе Книги рецептов Redis 4.x Пеньчень Хуань, Зуофей Вань}
Итак, вы его получили. Наш сервер Redis поднят и исполняется и мы можем подключаться к нему из некого обособленного контейнера. Обратите внимание,
что мы применяли docker-compose run
вместо exec
- специально для того
чтобы наш redis-cli
исполнялся в неком новом, обособленном контейнере, хотя и на основе того же самого
образа redis
. Это показывает что мы имеем возможность выполнять доступ к своему серверу Redis из некого
другого контейнера.
Но позвольте, секундочку! Разве контейнеры не предполагают изолированности? Как мы стали способны подключаться из контейнера исполняющего
redis-cli
к имеющемуся контейнеру с запущенным сервером redis
?
Хороший вопрос. Давайте изучим это в своём следующем разделе.
Если два контейнера изолированы, независимыми процессами, почему, как мы только что наблюдали, они имеют возможность общаться между собой? Несмотря на то, что код и процессы на самом деле являются песочницами, это вовсе не означает что сам контейнер не способен взаимодействовать с своим внешним миром. Если бы контейнеры не могли обмениваться информацией, мы бы не могли соединять их воедино для создания некой мощной, связанной системы служб которые совместно составляют наше приложение.
Если вы вспомните наш раздел Запуск нашего прикладного приложения, мы говорили, что
docker-compose up
создаёт некую новую сетевую среду для имеющегося прикладного приложения. По умолчанию все
контейнеры для нашего приложения подключены к данной сетевой среде приложения и имеют возможность взаимодействовать друг с другом. Это означает что
наши контейнеры, в точности как и некий физический или виртуальный сервер, способны взаимодействовать вне своих пределов при помощи сетевой среды
TCP/IP {Прим. пер.: а также, как это показано в Дополнении D
поверх сетевой среды RDMA.}
Давайте перечислим наши определённые в данный момент сетевые среды при помощи следующей команды:
$ docker network ls
Вы должны получить некий вывод, похожий на такой:
NETWORK ID NAME DRIVER SCOPE
128925dfad81 bridge bridge local
5bd7167263e8 host host local
e2af02026928 myapp_default bridge local
d1145155d62a none null local
Самая первая сетевая среда с названием bridge
является наследуемой сетевой средой для предоставления
обратной совместимости с некоторыми более ранними свойствами Docker - мы не будем ею пользоваться сейчас, когда мы переключились на Compose.
Аналогично, сетевые среды host
и none
являются особыми сетями, которые
Docker устанавливает и нам не требуется о них заботиться.
Та сетевая среда о которой нам следует заботиться, именуется как myapp_default
- именно она является
выделенной сетью нашего приложения, которую Compose создал для нас (Compose применяет соглашение об именах
<appname>_default
). Та причина, по которой Compose создал эту сетевую среду для нас проста: он знает что
все те службы, что мы определяем связаны с одним и тем же приложением, поэтому неминуемо им потребуется общаться друг с другом.
Но как контейнеры в этой сетевой среде обнаруживают друг друга?
Все сетевые среды Docker(за исключением наследуемой сети bridge
) имеют встроенное разрешение имён DNS
(Domain Name System). Это означает, что мы можем взаимодействовать с прочими контейнерами, запущенными в той же самой сетевой среде по имени.
Compose применяет соответствующее название службы (как оно определено в docker-compose.yml
) как некую запись DNS.
Поэтому если мы желаем достичь своей службы web
, она становится доступной через её название хоста
web
. Это предоставляется некой базовой формой службы обнаружения
(service discovery) - согласованного способа поиска служб на основе контейнеров, даже по мере перезапусков контейнеров.
Это поясняет как мы способны подключаться из узкоспециализированного контейнера исполняющего конкретный redis-cli
к нашему серверу Redes в качестве службы redis
. Вот та команда которую мы применяли:
$ docker-compose run --rm redis redis-cli -h redis
Наш параметр -h redis
сообщает, "Подключиться к хосту с названием redis
".
Это работает исключительно потому что Compose уже создал нашу сетевую среду приложения и установил записи DNS для каждой службы. В частности, на нашу службу
redis
можно ссылаться по имени её хоста redis
.
Хотя это и прекрасно, что мы запустили некий сервер Redis при помощи Compose, это само по себе не слишком полезно. Весь смысл работы сервера Redis состоит в том, чтобы наше приложение Rails могло общаться с ним и применять его в качестве хранилища ключ- значение. Итак, давайте подключим своё приложение Rails к Redis и реально чем= нибудь воспользуемся. Звучит весело?
Теперь, существует миллион способов каким наше приложение может пожелать применять Redis. Для наших целей, тем не менее, нам в реальности не важно для чего мы применяем Redis; нас больше заботит как мы его применяем. Мы намерены воспользоваться нарочито базовым примером: наше приложение Rails будет сохранять и выбирать некое значение. Тем не менее, имейте в виду - раз вы знаете как настраивать ваше приложение Rails на общение с имеющимся сервером в неком контейнере, вы можете применять его как заблагорассудится.
Готовы? давайте приступим.
Самый первый момент, которого нам стоит добиться чтобы заставить наше приложение Rails общаться с Redis, так это установить необходимый gem
redis
. Вы можете помнить, что для обновления своих gem нам требуется
Повторное построение наших образов, которое мы уже рассмотрели.
Поэтому в своём Gemfile
удалите комментарий с gem Redis в самом
Gemfile
следующим образом:
gem 'redis', '~> 4.0'
Затем остановите наш сервер Rails:
$ docker-compose stop web
и выполните повторную сборку нашего персонального образа Rails:
$ docker-compose build web
Помимо прочих моментов, это запускает bundle instal
, который устанавливает необходимые gem Redis:
Building web
Step 1/8 : FROM ruby:2.6
...
Step 6/8 : RUN bundle install
...
Installing redis 4.1.0
...
Bundle complete! 16 Gemfile dependencies, 69 gems now installed.
Bundled gems are installed into `/usr/local/bundle`
...
Removing intermediate container 3831c10d2cb5
---> 1ca01125bd35
Step 7/8 : COPY . /usr/src/app/
---> 852dc1f2b419
Step 8/8 : CMD ["bin/rails", "s", "-b", "0.0.0.0"]
---> Running in 280c7e2eb556
Removing intermediate container 280c7e2eb556
---> d9b3e5325308
Successfully built d9b3e5325308
Successfully tagged myapp_web:latest
Полезно свыкнуться с применяемым образом повторной сборки нашего образа для bundle install
обновляя наш
Gemfile
. Тем не менее, мы узнаем о Главе 9, Расширенном управлении
Gem, который помимо того что намного быстрее, позволяет нам придерживаться нашего привычного рабочего процесса
bundle install
.
Давайте снова запустим свой вновь собранный сервер Rails:
$ docker-compose up -d web
Далее мы намерены реально применить Redis из нашего приложения Rails. Как мы уже говорили ранее, мы всего лишь желаем выполнить базовую
демонстрацию того как мы можем подключаться к серверу Redis и извлекать значения. Поэтому давайте начнём с выработки контроллера
welcome
в нашем приложении Rails с одним единственным действием index
:
Пользователи Linux: владение файлами | |
---|---|
Убедитесь что вы выполнили
За дополнительными подробностями отсылаем к Дополнению A. Владельцы файлов и полномочия. |
$ docker-compose exec web bin/rails g controller welcome index
create app/controllers/welcome_controller.rb
route get 'welcome/index'
invoke erb
create app/views/welcome
create app/views/welcome/index.html.erb
invoke helper
create app/helpers/welcome_helper.rb
invoke assets
invoke coffee
create app/assets/javascripts/welcome.coffee
invoke scss
create app/assets/stylesheets/welcome.scss
Давайте изменим своё действие welcome#index
(в app/controllers/welcome_controller.rb
) чтобы оно выглядело так:
1: class WelcomeController < ApplicationController
2: def index
3: redis = Redis.new(host: "redis", port: 6379)
4: redis.incr "page hits"
5:
6: @page_hits = redis.get "page hits"
7: end
8: end
В нашем действии index
в строке 3 мы применяем gem клиента Redis для подключения к серверу Redis по имени
и номеру порта, с которыми, как мы ожидаем, он должен быть запущен. Затем в строке 4 мы увеличиваем значение пары ключ- значение Redis с названием
"page hits". Если вам интересно что происходит при самом первом исполнении данного кода, не волнуйтесь: Redis инициализирует его нулём если
этот ключ не найден, поэтому наш код будет работать как мы и ожидаем от него. Наконец, в строке 6 мы опрашиваем число попаданий на страницу из Redis,
сохраняем его в неком экземпляре переменной, готовой к отображению в нашем просмотре.
Теперь изменим свой файл просмотра (app/views/welcome/index.html.erb
) для отображения значения числа
заходов на страницу:
<h1>This page has been viewed <%= pluralize(@page_hits, 'time') %>!</h1>
Наконец, в config/routes.rb
, давайте изменим свой автоматически вырабатываемый маршрут с тем чтобы мы могли
получить доступ к новому действию index
в WelcomeController
из
/welcome
(вместо /welcome/index
):
Rails.application.routes.draw do
get 'welcome', to: 'welcome#index'
end
Теперь давайте посетим своё приложение Rails в нашем браузере по http://localhost:3000/welcome
.
Вы должны обнаружить некую страницу с нашим файлом отрисовки приветствия index.html.erb
, как это показано на
следующем рисунке:
Эта страница загружена без ошибок - хороший знак. Теперь давайте перезагрузим эту страницу. Всякий раз когда вы это делаете вы должны видеть рост числа посещений страницы.
Что это означает? Это означает что наше приложение Rails подключилось к имеющемуся серверу Redis, увеличило значение заходов на страницу с 0 до 1 и, наконец, отобразило наше сообщение приветствия с числом заходов на эту страницу. Говоря в общем, мы успешно получили два общающихся между собой контейнера. Это возможно благодаря созданной Compose сетевой среде для нашего приложения и автоматическому подключению к ней контейнеров.
Мы только что добавили Redis в качестве некой новой службы в свой файл Compose и настроили своё приложение Rails для общения с ним. Пока мы это делали,
наш сервер Rails уже исполнялся, поэтому мы запустили сам сервер Redis при помощи docker-compose run redis
.
Тем не менее, одно из преимуществ Compose состоит в том, что вне зависимости от того насколько много служб мы добавляем в своё приложение, мы можем
управлять им целиком при помощи одной единственной команды, заменяя потребность в gem словно
foreman.
Мы можем остановить и сервер Rails, и сервер Redis за один шаг с помощью:
$ docker-compose stop
Вы можете убедиться что оба процесса остановлены запуском:
$ docker-compose ps
Вы должны обнаружить нечто подобное:
Name Command State Ports
---------------------------------------------------------------
myapp_redis_1 docker-entrypoint.sh redis ... Exit 0
myapp_web_1 bin/rails s -b 0.0.0.0 Exit 1
Это показывает что и Redis и наша служба web
были остановлены; значение столбца
State
сообщает Exit
наряду с кодом останова ту
команду, которой он был осуществлён (ваше сотсояние выхода может отличаться). Если по какой- то причине хотя бы один продолжает
исполнение, остановите его при помощи команды docker-compose stop
(или kill
).
Теперь давайте запустим все приложение целиком снова - оба сервера, Rails и Redis:
$ docker-compose up -d
Теперь если мы исполним:
$ docker-compose ps
мы можем обнаружить запущенными обе службы:
Name Command State Ports
----------------------------------------------------------------------------
myapp_redis_1 docker-entrypoint.sh redis… Up 6379/tcp
myapp_web_1 bin/rails s -b 0.0.0.0 Up 0.0.0.0:3000->3000/tcp
Теперь наступает момент истины. Соединяется ли наше действие welcome#index
всё ещё с имеющимся сервером
Redis? Просмотрите вновь http://localhost:3000/welcome
(или перезапустите эту страницу, если она всё ещё
открыта) и вы должны увидеть следующий знакомый нам экран (но с увеличившимся числом посещений):
Истинная мощь использования контейнеров для нашего приложения не в исполнении некого процесса в каком- то персональном контейнере (хотя и это полезно), а больше в том, что мы имеем возможность связывать контейнеры воедино с тем чтобы они были способны общаться друг с другом.
В этой главе мы увидели как мы можем добавлять службы в своё приложение, запуская их в отдельных контейнерах. Что ещё более важно, мы увидели как применяется встроенная сетевая среда Docker чтобы позволять этим службам общаться друг с другом.
Давайте повторим основные моменты:
-
Мы запустили некий сервер Redis в контейнере при помощи
docker run
. Мы рассмотрели два новых параметра:--name
для придания конетйнеру какого- то дружественного нам названия и-d
для запуска контейнера в отключённом (detached) режиме. -
Мы добавили отдельную службу в Compose для запуска такого сервера Redis.
-
Мы убедились что этот сервер Redis исполняется (и что мы имеем возможность подключаться к нему из обособленного контейнера) запустив некий новый контейнер для выполнения
redis-cli
. -
Мы обсудили функциональность сетевой среды, предоставляемой Docker, и как Compose управляет общением друг с другом контейнеров.
-
Мы подключили свое приложение Rails к имеющемуся серверу Redis, заставив его сохранять и увеличивать некое значение, которое затем мы извлекли и отобразили.
-
Наконец, мы увидели что наш верный
docker-compose up
просто тработает и запустит сразу Rails и Redis за один проход.
Далее мы возьмём то что узнали о Compose и применим это для добавления базы данных Postgres. Мы продвинемся на шаг далее и посмотрим как обеспечить сохранность своих данных даже если исполняемый контейнер с нашей базой данных был удалён.