Содержание
- Введение
- Создание приложения в Docker контейнере
- Запуск Rails приложения в docker-compose
- Настройка контейнера для разработки
- Настройка Rubocop и Solargraph
- Добавляем немного кода
- Отладка с Byebug
- Отладка с Pry
- Подключение отладчика в VSCode
- Заключение
- Ссылки
Введение
Docker помогает разработчикам и девопсам, упрощая и автоматизируя шаги, необходимые для деплоя. Так же он часто используется разработчиками в повседневной работе, предоставляя изолированную среду, не зависимую от текущей ОС и окружения. Но переход на докер помимо упрощения добавляет и свои сложности. Например, при запуске приложения в докере использование линтера для подсветки синтаксиса становится недоступным, так как все зависимости и библиотеки приложения находятся в изолированном контейнере. И каким образом можно отлаживать приложение?
Ответы на эти вопросы я постарался собрать в этой статье. Эти советы будут полезны и для работы с кодом на удалённой машине через ssh, так как методы взаимодействия будут схожи.
Основными инструментами для разработки в докере у нас будут редактор VSCode и расширение Remote Containers. Это расширение позволит нам запускать редактор в таком же контейнере, что и приложение.
Докер будет использоваться в качестве сервера с приложением, а так же как контейнер для разработки (devcontainer), в котором содержатся необходимые плагины VSCode, настройки редактора и зависимости проекта. Общая схема выглядит так:
Создание приложения в Docker контейнере
В вашей системе должен быть установлен Docker и Docker-compose.
Создайте файл Dockerfile
:
FROM ruby:2.7.2
RUN curl https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update -qq && apt-get install -y postgresql-client
RUN gem install bundler
WORKDIR /app
Так как нам нужно создать новое приложение с помощью команды rails new
, создадим контейнер с гемом rails.
Сначала сделаем том докера, чтобы сохранить в нём установленные гемы. Это не обязательный шаг, но сохранит время в дальнейшем. При изменении Dockerfile или Gemfile гемы не будут удалятся вместе с контейнером, а останутся в отдельном томе(volume).
docker volume create ruby-2.7.2-gems
Теперь создадим образ из файла Dockerfile.
docker build -t rails-docker-debug .
Для создания приложения создадим одноразовый контейнер и запустим в нём терминал
docker run -it --rm -v ruby-2.7.2-gems:/usr/local/bundle -v $(pwd):/app rails-docker-debug bash
--rm
удалит контейнер после его остановки
-it
позволит запустить команду в контейнере, в нашем случае bash
-v
смонтируем текущую директорию в WORKDIR контейнерa, чтобы файлы при создании попали в неё. Если у вас Windows, замените $(pwd) на полный путь до директории проекта
-v
смонтируем том, где будут храниться гемы
В Windows вместо $(pwd)
нужно будет указать полный путь до папки проекта.
Внутри контейнера установим rails и создадим новое приложение:
gem install rails
rails new . -d postgresql --api
-d postgresql
будем использовать PostgresQL в качестве базы данных
--api
пропускаем установку вебпака, так как он требует node.js. При желании его установку можно добавить в Dockerfile
Теперь можно закрыть контейнер
exit
Дополнение:
В unix системах (Mac и Linux) файлы созданные в контейнере будут созданы от имени рут пользователя и у нас не будет прав на изменение этих файлов вне докера. Выполним chown
команду, чтобы иметь возможность редактировать эти файлы.
sudo chown <username>:<group> -R .
Название группы можно увидеть, выполнив ls -al
.
Запуск Rails приложения в docker-compose
Создадим файл docker-compose.yml
. Docker-compose делает работу с контейнерами заметно удобнее, чем просто Docker.
version: '3'
services:
web:
build: .
command: rails s -b 0.0.0.0
volumes:
- ruby-2.7.2-gems:/usr/local/bundle
- .:/app
ports:
- 3000:3000
depends_on:
- db
db:
image: postgres:10
volumes:
- db-data:/var/lib/postgresql
environment:
POSTGRES_PASSWORD: yourpassword
volumes:
ruby-2.7.2-gems:
external: true
db-data:
Параметром POSTGRES_PASSWORD
мы задаём пароль для БД, это можно сделать только в момент создания базы, чтобы это повторить, нужно удалить volume с БД.
В раздел volumes
мы подключаем внешний том с гемами.
Изменим конфиг подключения к базе config/database.yml
:
default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
host: db
username: postgres
password: yourpassword
здесь db это название сервиса из docker-compose.yml. Оно по сути выступает адресом в сети, наподобие домена нулевого уровня или localhost. Эту сеть создаёт docker-compose для общения между контейнерами.
Осталось только создать базу и можно работать с приложением
docker-compose build
docker-compose run --rm web rails db:create
docker-compose up
Дополнение:
Если возникли ошибки в процессе создания базы данных, рекомендую прежде всего удалить volume:
docker-compose down --volumes
docker volume rm -f rails-docker-debug_db-data
и проверить, совпадают ли пароли для базы в database.yml
и docker-compose.yml
.
Так же можно посмотреть логи контейнеров
docker-compose logs web
web
- имя сервисa в docker-compose.yml
Настройка контейнера для разработки
Для использования контейнера для разработки установим плагин Remote Containers
В качестве линтера для руби будем использовать rubocop и solargraph. Для создания девконтейнера мы возьмём тот же самый Dockerfile, который сделали раньше. Так как гемы у нас лежат в отдельном томе, то устанавливать rubocop и solargraph надо так же в этот том. Для этого запустим docker-compose с командой установки.
docker-compose run web bundle add solargraph rubocop rubocop-rails rubocop-rspec rubocop-performance
Создадим в директории нашего проекта папку .devcontainer
, в ней создадим файл конфигурации контейнера для vscode. Этот файл содержит информацию о контейнере для разработки, наборе плагинов и настройках, которые будет использовать vscode внутри этого контейнера.
.devcontainer/devcontainer.json
{
"name": "Rails Api",
"dockerFile": "../Dockerfile",
"mounts": [
"source=ruby-2.7.2-gems,target=/usr/local/bundle,type=volume"
],
"settings": {
"[ruby]": {
"editor.insertSpaces": true,
"editor.tabSize": 2
},
"solargraph.commandPath": "/usr/local/bundle/bin/solargraph",
"solargraph.bundlerPath": "/usr/local/bin/bundle",
"ruby.rubocop.executePath": "/usr/local/bundle/bin/",
"ruby.linter.executablePath": "/usr/local/bundle/bin/",
},
"extensions": [
"rebornix.Ruby",
"castwide.solargraph",
"misogi.ruby-rubocop",
"hoovercj.ruby-linter",
"miguel-savignano.ruby-symbols",
"wingrunr21.vscode-ruby",
"kaiwood.endwise",
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Uncomment the next line to run commands after the container is created - for example installing curl.
// "postCreateCommand": "apt-get update && apt-get install -y curl",
// Uncomment when using a ptrace-based debugger like C++, Go, and Rust
// "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
// Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
// "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
// "remoteUser": "vscode"
}
В этом файле помимо настроек редактора и списка плагинов есть строка подключения к контейнеру тома с гемами, который мы создали ранее.
После сохранения этого файла можно запустить команду, чтобы перейти в разработку внутри дев контейнера. Для этого в палитре команд (ctrl+shift+p) выбираем Rebuild and Reopen in Container
.
После запуска команды редактор перезапустится и построит дев контейнер. После успешного завершения откроется обычное окно vscode, но уже с плагинами и настройками, указанными в devcontainer.json
. Так же в этом окне не будет доступна файловая система хоста. При необходимости можно вернуться в редактор без запуска дев контейнера: ctrl+shift+p
-> Reopen Locally
.
Настройка Rubocop и Solargraph
Чтобы настроить линтеры для работы с rails, добавим им конфиг файлы
.solargraph.yml
---
include:
- "**/*.rb"
exclude:
- spec/**/*
- test/**/*
- vendor/**/*
- ".bundle/**/*"
require:
- actioncable
- actionmailer
- actionpack
- actionview
- activejob
- activemodel
- activerecord
- activestorage
- activesupport
domains: []
reporters:
- rubocop
- require_not_found
require_paths: []
max_files: 5000
.rubocop.yml
require:
- rubocop-rails
- rubocop-rspec
- rubocop-performance
Следуя совету из гайдов solargraph запустим команду для генерации кэша с документацией. Запускать нужно внутри контейнера, например в терминале vscode.
solargraph bundle
и создадим файл, который поможет solargraph анализоровать rails приложение
definitions.rb
# The following comments fill some of the gaps in Solargraph's understanding of
# Rails apps. Since they're all in YARD, they get mapped in Solargraph but
# ignored at runtime.
#
# You can put this file anywhere in the project, as long as it gets included in
# the workspace maps. It's recommended that you keep it in a standalone file
# instead of pasting it into an existing one.
#
# @!parse
# class ActionController::Base
# include ActionController::MimeResponds
# extend ActiveSupport::Callbacks::ClassMethods
# extend AbstractController::Callbacks::ClassMethods
# end
# class ActiveRecord::Base
# extend ActiveRecord::QueryMethods
# extend ActiveRecord::FinderMethods
# extend ActiveRecord::Associations::ClassMethods
# extend ActiveRecord::Inheritance::ClassMethods
# include ActiveRecord::Persistence
# end
# @!override ActiveRecord::FinderMethods#find
# @overload find(id)
# @param id [Integer]
# @return [self]
# @overload find(list)
# @param list [Array]
# @return [Array<self>]
# @overload find(*args)
# @return [Array<self>]
# @return [self, Array<self>]
Добавляем немного кода
Создадим модель и контроллер с книгами и сгенерируем несколько записей:
db/seeds.rb
Book.create(title: "War and Peace", author: "Leo Tolstoy", publication_year: 1869)
Book.create(title: "Hamlet", author: "William Shakespeare", publication_year: 1870)
Book.create(title: "Crime and Punishment", author: "Fyodor Dostoevsky", publication_year: 1866)
docker-compose run web rails g scaffold books title:string author:string publication_year:integer
docker-compose run web rails db:migrate
docker-compose run web rails db:seed
Отладка с Byebug
Этот гем позволяет запустить простой отладчик в терминале, достаточно написать слово byebug
в коде и это станет брейкпоинтом. В консоли будет возможность посмотреть содержимое переменных или пошагово выполнить код. Он входит в набор гемов, которые устанавливаются при создании rails приложения, а значит мы уже можем им воспользоваться.
Для запуска Byebug в докере необходимо включить интерактивный режим консоли. Для этого в сервис web
в docker-compose.yml
добавим параметры.
stdin_open: true
tty: true
В итоге получится
services:
web:
build: .
command: rails s -b 0.0.0.0
volumes:
- ruby-2.7.2-gems:/usr/local/bundle
- .:/app
ports:
- 3000:3000
depends_on:
- db
stdin_open: true
tty: true
...
Попробуем Byebug в деле, добавим строчку с byebug
в контроллер книг
app/controllers/books_controller.rb
# GET /books
def index
@books = Book.all
byebug
render json: @books
end
Теперь запустим приложение
docker-compose up
И в другом терминале подключимся к консоли сервиса web, в ней мы сможем управлять отладкой.
Найдём имя контейнера сервиса web.
docker ps
Обычно это имя папки плюс название сервиса и цифра: rails-docker-debug_web_1
. Подключимся к нему:
docker attach rails-docker-debug_web_1
Теперь при открытии страницы http://localhost:3000/books произойдёт вызов byebug и в окне терминала появится интерактивный режим, как если бы мы открыли консоль пользователя. Здесь мы можем вывести любую переменную, которая была объявлена в коде, выполнить следующую строку (step
) или продолжить выполнение кода (continue
).
Чтобы отключить терминал от докера нужно выполнить комбинацию клавиш Ctrl+P
и Ctrl+Q
.
Отладка с Pry
Pry - аналог irb, интерактивная среда выполнения со множеством дополнительных функций. Основные особенности pry:
- отображение с цветной подсветкой синтаксиса и форматированием, это первое, что сразу добавляет удобства
- возможность поставить брейкпоинт, аналогично гему byebug
- удобная навигация по коду, быстрые переходы по состояниям (cd, ls)
- отображение документации из кода и множество других команд и возможностей https://github.com/pry/pry
Установим pry
docker-compose run web bundle add pry
Теперь при вызове команды rails c
откроется интерактивная среда pry вместо irb. Если у вас в консоли написано irb
то можно переключиться на pry командой pry
.
docker-compose run web rails c
Так же pry можно использовать во время отладки. Сделаем аналогично примеру с byebug
, для этого в начало файла app/controllers/books_controller.rb
добавим
require 'pry'
и добавим binding.pry
в метод index
# GET /books
def index
@books = Book.all
binding.pry
render json: @books
end
После этого можно так же запустить сервер и подключиться к докеру
docker-compose up
docker attach rails-docker-debug_web_1
и открыть http://localhost:3000/books
Теперь в консоли можно посмотреть доступные методы и переменные: ls
, исходный код метода или класса: show-source render
покажет код метода render
и многое другое.
Для выхода введите exit
.
Подробнее про методы pry можно почитать в репозитории https://github.com/pry/pry
Подключение отладчика в VSCode
Так же имеется возможность отладки приложения напрямую из VSCode. Для этого нужно сделать несколько шагов
- Добавим гемы
ruby-debug-ide
debase
, чтобы к нашему приложению можно было подключиться для отладки
docker-compose run web bundle add ruby-debug-ide debase
- Создадим папку и в ней файл
.vscode/launch.json
. Добавим туда конфиг для подключения отладчика VSCode к приложению.
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for rdebug-ide",
"type": "Ruby",
"request": "attach",
"remoteHost": "127.0.0.1",
"remotePort": "9000",
"remoteWorkspaceRoot": "/app"
}
]
}
Создадим новый docker-compose файл для дебага docker-compose.debug.yml
version: '3'
services:
web:
command: rdebug-ide --host 0.0.0.0 --port 9000 -- bin/rails s -p 3000 -b 0.0.0.0
ports:
- 9000:9000
Чтобы наш контейнер для разработки имел доступ к порту отладки, сделаем внутри контейнера ту же самую сеть, что и на хосте. Для этого добавим в файл devcontainer.json
строчку с параметром запуска.
"runArgs": [ "--network=host" ]
После изменений нужно сделать ребилд контейнера:
Command Palette -> Rebuild Container
Выполним команду запуска проекта в режиме дебага.
docker-compose -f docker-compose.yml -f docker-compose.debug.yml up
На этом этапе адрес сайта http://localhost:3000/
будет не доступен до тех пор, пока мы не включим режим отладки в редакторе VSCode.
Для этого зайдем в окно отладки. И начнём отладку.
После запуска отладки наш проект заработает как обычно.
А в редакторе мы сможем поставить точку остановки.
При открытии страницы http://localhost:3000/books в браузере редактор отобразит текущее состояние приложения в данной точке.
Дополнение
В случае ошибки A server is already running. Check /app/tmp/pids/server.pid.
удалите файл server.pid
командой
docker-compose run --rm web rm -f tmp/pids/server.pid
или в файле docker-compose
значение свойства command
оберните следующим кодом
bash -c "rm -f tmp/pids/server.pid && <здесь_исходная_команда>"
В этом случае удаление файла будет происходить автоматически при запуске.
Заключение
Мы рассмотрели основные способы создания и отладки приложения и адаптировали их использование для Docker окружения. Использование Docker в разработке вносит удобства запуска, но так же и повышает сложность настройки и взаимодействия с приложением в контейнере. Как по мне, использование Docker позволяет лучше понять принципы работы разных компонентов приложения. Сделаем выводы, какие достоинства и недостатки есть у использования Docker в разработке.
Достоинства:
- Docker-compose изолирует приложение и позволяет запускать его независимо от вашей ОС или от того, какие версии Ruby или Postgres установлены на вашей хост машине.
- Devcontainer изолирует плагины VSCode и необходимое для них окружение, это удобно если вы работаете в VSCode с разными языками или фреймворками.
- В случае необходимости запуска проекта на новой машине это пройдёт быстрее, благодаря автоматизации деплоя.
- Все шаги деплоя задокументированы в файлах докера.
Недостатки:
- Контейнеризация усложняет процесс взаимодействия компонентов приложения.
- Необходимо иметь представление об устройстве докера, чтобы правильно его использовать.
- Докер приложение будет использовать больше системных ресурсов, чем запущенное локально.
Ссылки
Код из этой статьи https://github.com/ZinChen/rails-docker-debug
Статьи, которые помогли разобраться в данной теме и написать эту статью. Большое спасибо их авторам! :)
- Create a development container
- Starting a Rails app using vscode containers
- Development Acceleration Through VS Code Remote Containers
- Debugging Rails App With Docker Compose
- Debugging Rails on Docker with VSCode
Top comments (4)
Спасибо за подробную статью.
Попробовал сделать так как описано в своем проекте. Не смог подключить отладчик vscode.
Ошибка воь эта: Debugger error: Client: Error: connect ECONNREFUSED 127.0.0.1:9000.
Буду благодарен за подсказку в какуюсторону копать.
Ошибка при запуске дебаггера в VSCode?
Почему то недоступен порт, скорее всего нужно сделать ребилд девконтейнера:
ctrl+shift+p -> Rebuild container
, чтобы применить изменение"runArgs": [ "--network=host" ]
.Спасибо огромное. Всё настроил! Работает отлично!
Потрясный гайд, не припоминаю, чтобы на глаза попадалась статья с таким подробным объяснением, интересно было почитать про отладку через VSCode. Отличная работа!