DEV Community

Yuri Zinchenko
Yuri Zinchenko

Posted on • Updated on

Rails + VSCode + Docker. Разработка с devcontainer.

Содержание

Введение

Docker помогает разработчикам и девопсам, упрощая и автоматизируя шаги, необходимые для деплоя. Так же он часто используется разработчиками в повседневной работе, предоставляя изолированную среду, не зависимую от текущей ОС и окружения. Но переход на докер помимо упрощения добавляет и свои сложности. Например, при запуске приложения в докере использование линтера для подсветки синтаксиса становится недоступным, так как все зависимости и библиотеки приложения находятся в изолированном контейнере. И каким образом можно отлаживать приложение?

Ответы на эти вопросы я постарался собрать в этой статье. Эти советы будут полезны и для работы с кодом на удалённой машине через ssh, так как методы взаимодействия будут схожи.

Основными инструментами для разработки в докере у нас будут редактор VSCode и расширение Remote Containers. Это расширение позволит нам запускать редактор в таком же контейнере, что и приложение.

Докер будет использоваться в качестве сервера с приложением, а так же как контейнер для разработки (devcontainer), в котором содержатся необходимые плагины VSCode, настройки редактора и зависимости проекта. Общая схема выглядит так:

image

Создание приложения в 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
Enter fullscreen mode Exit fullscreen mode

Так как нам нужно создать новое приложение с помощью команды rails new, создадим контейнер с гемом rails.

Сначала сделаем том докера, чтобы сохранить в нём установленные гемы. Это не обязательный шаг, но сохранит время в дальнейшем. При изменении Dockerfile или Gemfile гемы не будут удалятся вместе с контейнером, а останутся в отдельном томе(volume).

docker volume create ruby-2.7.2-gems
Enter fullscreen mode Exit fullscreen mode

Теперь создадим образ из файла Dockerfile.

docker build -t rails-docker-debug .
Enter fullscreen mode Exit fullscreen mode

Для создания приложения создадим одноразовый контейнер и запустим в нём терминал

docker run -it --rm -v ruby-2.7.2-gems:/usr/local/bundle -v $(pwd):/app rails-docker-debug bash
Enter fullscreen mode Exit fullscreen mode

--rm удалит контейнер после его остановки
-it позволит запустить команду в контейнере, в нашем случае bash
-v смонтируем текущую директорию в WORKDIR контейнерa, чтобы файлы при создании попали в неё. Если у вас Windows, замените $(pwd) на полный путь до директории проекта
-v смонтируем том, где будут храниться гемы
В Windows вместо $(pwd) нужно будет указать полный путь до папки проекта.

Внутри контейнера установим rails и создадим новое приложение:

gem install rails
Enter fullscreen mode Exit fullscreen mode
rails new . -d postgresql --api
Enter fullscreen mode Exit fullscreen mode

-d postgresql будем использовать PostgresQL в качестве базы данных
--api пропускаем установку вебпака, так как он требует node.js. При желании его установку можно добавить в Dockerfile

Теперь можно закрыть контейнер

exit
Enter fullscreen mode Exit fullscreen mode

Дополнение:

В unix системах (Mac и Linux) файлы созданные в контейнере будут созданы от имени рут пользователя и у нас не будет прав на изменение этих файлов вне докера. Выполним chown команду, чтобы иметь возможность редактировать эти файлы.

sudo chown <username>:<group> -R .
Enter fullscreen mode Exit fullscreen mode

Название группы можно увидеть, выполнив 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:
Enter fullscreen mode Exit fullscreen mode

Параметром 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
Enter fullscreen mode Exit fullscreen mode

здесь db это название сервиса из docker-compose.yml. Оно по сути выступает адресом в сети, наподобие домена нулевого уровня или localhost. Эту сеть создаёт docker-compose для общения между контейнерами.

Осталось только создать базу и можно работать с приложением

docker-compose build
docker-compose run --rm web rails db:create
docker-compose up
Enter fullscreen mode Exit fullscreen mode

Дополнение:

Если возникли ошибки в процессе создания базы данных, рекомендую прежде всего удалить volume:

docker-compose down --volumes
docker volume rm -f rails-docker-debug_db-data
Enter fullscreen mode Exit fullscreen mode

и проверить, совпадают ли пароли для базы в database.yml и docker-compose.yml.

Так же можно посмотреть логи контейнеров

docker-compose logs web
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Создадим в директории нашего проекта папку .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"
}

Enter fullscreen mode Exit fullscreen mode

В этом файле помимо настроек редактора и списка плагинов есть строка подключения к контейнеру тома с гемами, который мы создали ранее.

После сохранения этого файла можно запустить команду, чтобы перейти в разработку внутри дев контейнера. Для этого в палитре команд (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
Enter fullscreen mode Exit fullscreen mode

.rubocop.yml

require:
  - rubocop-rails
  - rubocop-rspec
  - rubocop-performance
Enter fullscreen mode Exit fullscreen mode

Следуя совету из гайдов solargraph запустим команду для генерации кэша с документацией. Запускать нужно внутри контейнера, например в терминале vscode.

solargraph bundle
Enter fullscreen mode Exit fullscreen mode

и создадим файл, который поможет 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>]
Enter fullscreen mode Exit fullscreen mode

Добавляем немного кода

Создадим модель и контроллер с книгами и сгенерируем несколько записей:

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)
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

Отладка с Byebug

Этот гем позволяет запустить простой отладчик в терминале, достаточно написать слово byebug в коде и это станет брейкпоинтом. В консоли будет возможность посмотреть содержимое переменных или пошагово выполнить код. Он входит в набор гемов, которые устанавливаются при создании rails приложения, а значит мы уже можем им воспользоваться.
Для запуска Byebug в докере необходимо включить интерактивный режим консоли. Для этого в сервис web в docker-compose.yml добавим параметры.

  stdin_open: true
  tty: true
Enter fullscreen mode Exit fullscreen mode

В итоге получится

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
...
Enter fullscreen mode Exit fullscreen mode

Попробуем Byebug в деле, добавим строчку с byebug в контроллер книг

app/controllers/books_controller.rb

  # GET /books
  def index
    @books = Book.all

    byebug

    render json: @books
  end
Enter fullscreen mode Exit fullscreen mode

Теперь запустим приложение

docker-compose up
Enter fullscreen mode Exit fullscreen mode

И в другом терминале подключимся к консоли сервиса web, в ней мы сможем управлять отладкой.
Найдём имя контейнера сервиса web.

docker ps
Enter fullscreen mode Exit fullscreen mode

Обычно это имя папки плюс название сервиса и цифра: rails-docker-debug_web_1. Подключимся к нему:

docker attach rails-docker-debug_web_1
Enter fullscreen mode Exit fullscreen mode

Теперь при открытии страницы http://localhost:3000/books произойдёт вызов byebug и в окне терминала появится интерактивный режим, как если бы мы открыли консоль пользователя. Здесь мы можем вывести любую переменную, которая была объявлена в коде, выполнить следующую строку (step) или продолжить выполнение кода (continue).

image

Чтобы отключить терминал от докера нужно выполнить комбинацию клавиш Ctrl+P и Ctrl+Q.

Отладка с Pry

Pry - аналог irb, интерактивная среда выполнения со множеством дополнительных функций. Основные особенности pry:

  • отображение с цветной подсветкой синтаксиса и форматированием, это первое, что сразу добавляет удобства
  • возможность поставить брейкпоинт, аналогично гему byebug
  • удобная навигация по коду, быстрые переходы по состояниям (cd, ls)
  • отображение документации из кода и множество других команд и возможностей https://github.com/pry/pry

Установим pry

docker-compose run web bundle add pry
Enter fullscreen mode Exit fullscreen mode

Теперь при вызове команды rails c откроется интерактивная среда pry вместо irb. Если у вас в консоли написано irb то можно переключиться на pry командой pry.

image

docker-compose run web rails c
Enter fullscreen mode Exit fullscreen mode

Так же pry можно использовать во время отладки. Сделаем аналогично примеру с byebug, для этого в начало файла app/controllers/books_controller.rb добавим

require 'pry'
Enter fullscreen mode Exit fullscreen mode

и добавим binding.pry в метод index

  # GET /books
  def index
    @books = Book.all

    binding.pry

    render json: @books
  end
Enter fullscreen mode Exit fullscreen mode

После этого можно так же запустить сервер и подключиться к докеру

docker-compose up
docker attach rails-docker-debug_web_1
Enter fullscreen mode Exit fullscreen mode

и открыть http://localhost:3000/books

image

Теперь в консоли можно посмотреть доступные методы и переменные: 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
Enter fullscreen mode Exit fullscreen mode
  • Создадим папку и в ней файл .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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Создадим новый 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
Enter fullscreen mode Exit fullscreen mode

Чтобы наш контейнер для разработки имел доступ к порту отладки, сделаем внутри контейнера ту же самую сеть, что и на хосте. Для этого добавим в файл devcontainer.json строчку с параметром запуска.

"runArgs": [ "--network=host" ]
Enter fullscreen mode Exit fullscreen mode

После изменений нужно сделать ребилд контейнера:

Command Palette -> Rebuild Container

Выполним команду запуска проекта в режиме дебага.

docker-compose -f docker-compose.yml -f docker-compose.debug.yml up
Enter fullscreen mode Exit fullscreen mode

На этом этапе адрес сайта http://localhost:3000/ будет не доступен до тех пор, пока мы не включим режим отладки в редакторе VSCode.

Для этого зайдем в окно отладки. И начнём отладку.
image

После запуска отладки наш проект заработает как обычно.

image

А в редакторе мы сможем поставить точку остановки.

image

При открытии страницы http://localhost:3000/books в браузере редактор отобразит текущее состояние приложения в данной точке.

image

Дополнение

В случае ошибки A server is already running. Check /app/tmp/pids/server.pid. удалите файл server.pid командой

 docker-compose run --rm web rm -f tmp/pids/server.pid
Enter fullscreen mode Exit fullscreen mode

или в файле docker-compose значение свойства command оберните следующим кодом

bash -c "rm -f tmp/pids/server.pid && <здесь_исходная_команда>"
Enter fullscreen mode Exit fullscreen mode

В этом случае удаление файла будет происходить автоматически при запуске.

Заключение

Мы рассмотрели основные способы создания и отладки приложения и адаптировали их использование для Docker окружения. Использование Docker в разработке вносит удобства запуска, но так же и повышает сложность настройки и взаимодействия с приложением в контейнере. Как по мне, использование Docker позволяет лучше понять принципы работы разных компонентов приложения. Сделаем выводы, какие достоинства и недостатки есть у использования Docker в разработке.

Достоинства:

  • Docker-compose изолирует приложение и позволяет запускать его независимо от вашей ОС или от того, какие версии Ruby или Postgres установлены на вашей хост машине.
  • Devcontainer изолирует плагины VSCode и необходимое для них окружение, это удобно если вы работаете в VSCode с разными языками или фреймворками.
  • В случае необходимости запуска проекта на новой машине это пройдёт быстрее, благодаря автоматизации деплоя.
  • Все шаги деплоя задокументированы в файлах докера.

Недостатки:

  • Контейнеризация усложняет процесс взаимодействия компонентов приложения.
  • Необходимо иметь представление об устройстве докера, чтобы правильно его использовать.
  • Докер приложение будет использовать больше системных ресурсов, чем запущенное локально.

Ссылки

Код из этой статьи https://github.com/ZinChen/rails-docker-debug

Статьи, которые помогли разобраться в данной теме и написать эту статью. Большое спасибо их авторам! :)


Top comments (4)

Collapse
 
makrom profile image
Roman

Спасибо за подробную статью.
Попробовал сделать так как описано в своем проекте. Не смог подключить отладчик vscode.
Ошибка воь эта: Debugger error: Client: Error: connect ECONNREFUSED 127.0.0.1:9000.
Буду благодарен за подсказку в какуюсторону копать.

Collapse
 
zinchen profile image
Yuri Zinchenko

Ошибка при запуске дебаггера в VSCode?
Почему то недоступен порт, скорее всего нужно сделать ребилд девконтейнера: ctrl+shift+p -> Rebuild container, чтобы применить изменение "runArgs": [ "--network=host" ].

Collapse
 
makrom profile image
Roman • Edited

Спасибо огромное. Всё настроил! Работает отлично!

Collapse
 
kuznetsovvl profile image
kuznetsovvl

Потрясный гайд, не припоминаю, чтобы на глаза попадалась статья с таким подробным объяснением, интересно было почитать про отладку через VSCode. Отличная работа!