Rails + VSCode + Docker. Разработка с devcontainer.
Tags: rails, docker, vscode, devops
Published: 2021-03-13 17:16:26

## Содержание

- [Введение](#Введение)
- [Создание приложения в Docker контейнере](#СозданиеприложениявDockerконтейнере)
- [Запуск Rails приложения в docker-compose](#ЗапускRailsприложениявdocker-compose)
- [Настройка контейнера для разработки](#Настройкаконтейнерадляразработки)
- [Настройка Rubocop и Solargraph](#НастройкаRubocopиSolargraph)
- [Добавляем немного кода](#Добавляемнемногокода)
- [Отладка с Byebug](#ОтладкасByebug)
- [Отладка с Pry](#Отладкасpry)
- [Подключение отладчика в VSCode](#ПодключениеотладчикавVSCode)
- [Заключение](#Заключение)
- [Ссылки](#Ссылки)

<a name="Введение"></a>

# Введение

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

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

Основными инструментами для разработки в докере у нас будут редактор VSCode и расширение [Remote Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). Это расширение позволит нам запускать редактор в таком же контейнере, что и приложение.

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/op1zwiynjgh6pb5eha9a.png)

<a name="СозданиеприложениявDockerконтейнере"></a>

# Создание приложения в Docker контейнере

В вашей системе должен быть установлен [Docker](https://docs.docker.com/get-docker/) и [Docker-compose](https://docs.docker.com/compose/install/).

Создайте файл `Dockerfile`:

```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).

```sh
docker volume create ruby-2.7.2-gems
```

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

```sh
docker build -t rails-docker-debug .
```

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

```sh
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 и создадим новое приложение:

```sh
gem install rails
```

```sh
rails new . -d postgresql --api
```

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

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

```sh
exit
```

Дополнение:

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

```sh
sudo chown <username>:<group> -R .
```

Название группы можно увидеть, выполнив `ls -al`.

<a name="ЗапускRailsприложениявdocker-compose"></a>

# Запуск Rails приложения в docker-compose

Создадим файл `docker-compose.yml`. Docker-compose делает работу с контейнерами заметно удобнее, чем просто Docker.

```yml
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`:

```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 для общения между контейнерами.

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

```sh
docker-compose build
docker-compose run --rm web rails db:create
docker-compose up
```

Дополнение:

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

```sh
docker-compose down --volumes
docker volume rm -f rails-docker-debug_db-data
```

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

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

```sh
docker-compose logs web
```
`web` - имя сервисa в docker-compose.yml

<a name="Настройкаконтейнерадляразработки"></a>

# Настройка контейнера для разработки

Для использования контейнера для разработки установим плагин [Remote Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)

В качестве линтера для руби будем использовать **rubocop** и **solargraph**. Для создания девконтейнера мы возьмём тот же самый Dockerfile, который сделали раньше. Так как гемы у нас лежат в отдельном томе, то устанавливать rubocop и solargraph надо так же в этот том. Для этого запустим docker-compose с командой установки.

```sh
docker-compose run web bundle add solargraph rubocop rubocop-rails rubocop-rspec rubocop-performance
```

Создадим в директории нашего проекта папку `.devcontainer`, в ней создадим файл конфигурации контейнера для vscode. Этот файл содержит информацию о контейнере для разработки, наборе плагинов и настройках, которые будет использовать vscode внутри этого контейнера.

`.devcontainer/devcontainer.json`

```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`.

<a name="НастройкаRubocopиSolargraph"></a>

# Настройка Rubocop и Solargraph

Чтобы настроить линтеры для работы с rails, добавим им конфиг файлы

`.solargraph.yml`

```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`

```yml
require:
  - rubocop-rails
  - rubocop-rspec
  - rubocop-performance
```

Следуя совету из [гайдов solargraph](https://solargraph.org/guides/rails) запустим команду для генерации кэша с документацией. Запускать нужно внутри контейнера, например в терминале vscode.

```sh
solargraph bundle
```

и создадим файл, который поможет solargraph анализоровать rails приложение

`definitions.rb`

```ruby
# 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>]
```

<a name="Добавляемнемногокода"></a>

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

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

`db/seeds.rb`

```ruby
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)
```

```bash
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
```

<a name="ОтладкасByebug"></a>

# Отладка с Byebug

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

```yml
  stdin_open: true
  tty: true
```

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

```yml
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`

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

    byebug

    render json: @books
  end
```

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

```sh
docker-compose up
```

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

```sh
docker ps
```

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

```sh
docker attach rails-docker-debug_web_1
```

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9leea12nsab9ksiadnge.png)

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

<a name="Отладкасpry"></a>

# Отладка с Pry

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

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

Установим pry

```sh
docker-compose run web bundle add pry
```

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vxm0keexqwcegxibrnzj.png)

```sh
docker-compose run web rails c
```

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

```rb
require 'pry'
```

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

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

    binding.pry

    render json: @books
  end
```

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

```sh
docker-compose up
docker attach rails-docker-debug_web_1
```

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y8hfqo5ez598st60tdz5.png)

Теперь в консоли можно посмотреть доступные методы и переменные: `ls`, исходный код метода или класса: `show-source render` покажет код метода `render` и многое другое.
Для выхода введите `exit`.
Подробнее про методы pry можно почитать в репозитории <https://github.com/pry/pry>

<a name="ПодключениеотладчикавVSCode"></a>

# Подключение отладчика в VSCode

Так же имеется возможность отладки приложения напрямую из VSCode. Для этого нужно сделать несколько шагов

- Добавим гемы `ruby-debug-ide` `debase`, чтобы к нашему приложению можно было подключиться для отладки

```sh
 docker-compose run web bundle add ruby-debug-ide debase
 ```

- Создадим папку и в ней файл `.vscode/launch.json`. Добавим туда конфиг для подключения отладчика VSCode к приложению.

```json
{
  "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`

```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` строчку с параметром запуска.

```json
"runArgs": [ "--network=host" ]
```

После изменений нужно сделать ребилд контейнера: ```Command Palette -> Rebuild Container```

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

```sh
docker-compose -f docker-compose.yml -f docker-compose.debug.yml up
```

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

Для этого зайдем в окно отладки. И начнём отладку.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/djgwb17eoymwcjs02o09.png)

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hnle71g3e3zgbuzw912u.png)

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e08jx7h19vzvz4v4snqu.png)

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/doulu3p3tu04ngzwj2gb.png)

Дополнение

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

```sh
 docker-compose run --rm web rm -f tmp/pids/server.pid
```

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

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

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

<a name="Заключение"></a>

# Заключение

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

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

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

Недостатки:

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

<a name="Ссылки"></a>

# Ссылки

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

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

- [Create a development container](https://code.visualstudio.com/docs/remote/create-dev-container)
- [Starting a Rails app using vscode containers](https://dev.to/imiked/starting-a-rails-app-using-vscode-containers-1gj9)
- [Development Acceleration Through VS Code Remote Containers](https://stelligent.com/2020/04/10/development-acceleration-through-vs-code-remote-containers-setting-up-a-foundational-configuratio/)
- [Debugging Rails App With Docker Compose](https://medium.com/gogox-technology/debugging-rails-app-with-docker-compose-39a3767962f4)
- [Debugging Rails on Docker with VSCode](https://medium.com/@GrantMercer/debugging-rails-on-docker-with-vscode-6f9d920413fd)

****