Несоответствующий IOCTL |
Недавно я столкнулся с проблемой, с которой сталкивался в прошлом, поэтому решил сделать пост, чтобы напомнить (в основном себе), как я наконец решил ее. Это помогает мне избежать ситуации дня сурка, когда я постоянно совершаю одну и ту же ошибку и не помню, как я ее исправил, поскольку я не задокументировал ее должным образом.
Установка
У нас есть приложение Rails, которое развертывается с помощью Capistrano. Когда мы сливаемся с нашим develop
и main
ветки, у нас также есть средство запуска действий GitHub, которое будет выполнять развертывание (при условии, что сборка работает и тесты пройдены). Итак, наша установка выглядит примерно так:
---
title: Deployment (Staging)
---
flowchart LR
ldm[Local Development Machine]
ghr[Github Runner]
stage[stage.example.com]
ldm -->|push to develop|ghr
ghr -->|capistrano via ssh|stage
Капистрано проходит через ssh
, где он подключается к GitHub для загрузки репозитория. Поскольку это выполняется в автоматизированной среде, нам нужно использовать ключ SSH вместо пароля для аутентификации (через секунду мы увидим, почему).
Файл, управляющий работой развертывания, довольно прост. Он содержится в config/deploy/staging.rb
и выглядит так:
set :stage, :staging
set :bundle_without, 'test'
set :rails_env, fetch(:staging)
set :use_sudo, false
set :deploy_to, '/var/www/example.com/stage.api.example.com'
server "stage.api.example.com", user: "deployer-bot", roles: %w{web app db}
set :branch, ENV["REVISION"] || ENV["BRANCH_NAME"] || "develop"
set :ssh_options, {
keys: %w(~/.ssh/id_rsa),
forward_agent: true,
}
Теперь, когда я бегу bundle exec cap staging deploy
локально, он развертывается на сервере stage.api.example.com
и помещает ревизию в /var/www/example.com/stage.api.example.com
каталог.
Однако когда мы добавляем в смесь действия GitHub, все становится немного рискованно. Мой файл определения действий GitHub, расположенный в .github/workflows/staging.yml
выглядит так:
name: Build, Test, and Deploy to Staging
on:
push:
branches:
- develop
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build-and-test:
runs-on: ubuntu-latest
env:
DB_DATABASE: stage_db
DB_ROOT_USER: root
DB_ROOT_PASSWORD: root
DB_USER: eample_admin
DB_PASSWORD: ${{ secrets.MYSQL_USER_PASSWORD }}
steps:
- name: Set up MySQL
run: |
sudo systemctl start mysql.service
mysql -e 'CREATE DATABASE ${{ env.DB_DATABASE }};' -u${{ env.DB_ROOT_USER }} -p${{ env.DB_ROOT_PASSWORD }}
mysql -e "CREATE USER '${{ env.DB_USER }}'@'localhost' IDENTIFIED BY '${{ env.DB_PASSWORD }}';" -u${{env.DB_ROOT_USER}} -p${{ env.DB_ROOT_PASSWORD }}
mysql -e "CREATE DATABASE IF NOT EXISTS ${{ env.DB_DATABASE }};" -u${{env.DB_ROOT_USER}} -p${{ env.DB_ROOT_PASSWORD }}
mysql -e "GRANT ALL PRIVILEGES ON ${{ env.DB_DATABASE }}.* to '${{ env.DB_USER }}'@'localhost';" -u${{env.DB_ROOT_USER}} -p${{ env.DB_ROOT_PASSWORD }}
mysql -e "FLUSH PRIVILEGES;" -u${{env.DB_ROOT_USER}} -p${{ env.DB_ROOT_PASSWORD }}
- name: Install SSH key to Server
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.STAGE_API_DEPLOY_KEY }}
name: github-actions
known_hosts: ${{ secrets.STAGE_API_HOST_KEY }}
config: |
host stage.api.example.com
IdentityFile ~/.ssh/github-actions
IdentitiesOnly yes
ForwardAgent yes
- name: Verify SSH Key
run: cat ~/.ssh/github-actions
- uses: actions/checkout@v2
- name: Set up Ruby Environment
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.6.1
# runs 'bundle install' and caches installed gems automatically
bundler-cache: true
env:
RAILS_ENV: staging
- name: Setup Database
env:
RAILS_ENV: staging
run: bundle exec rake db:setup
- name: Perform Database Migrations
env:
RAILS_ENV: staging
run: bundle exec rake db:migrate
- name: Run specs
env:
RAILS_ENV: staging
run: bundle exec rails spec
deploy-staging:
needs: build-and-test
runs-on: ubuntu-latest
steps:
- name: Install SSH Host Key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.STAGE_API_DEPLOY_KEY }}
name: github-actions
known_hosts: ${{ secrets.STAGE_API_HOST_KEY }}
config: |
Host stage.api.example.com
IdentityFile ~/.ssh/github-actions
IdentitiesOnly yes
ForwardAgent yes
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
# # NOTE: This is not needed since we have a .ruby-version file.
# # ruby-version: 2.6.1
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Install SSH Key
run: |
eval "$(ssh-agent -s)"
ssh-add -D
ssh-add ~/.ssh/github-actions
- name: Check SSH Key Viability
run: |
echo "ls -al" | ssh deployer-bot@stage.api.example.com
- name: Deploy to staging
run: |
eval "$(ssh-agent -s)"
ssh-add -D
ssh-add ~/.ssh/github-actions
bundle exec cap staging deploy
Здесь много всего происходит, но, по сути, мы настраиваем SSH-ключ. на stage.api.example.com сервер (это становится важным через секунду), проверка работоспособности этого ключа SSH, настройка ruby и, наконец, развертывание с помощью Capistrano. Шаг «Проверить жизнеспособность ключа SSH» сейчас не нужен, но он был полезен при отладке основной проблемы (далее).
Проблема
Теперь, несмотря на то, что я смог развернуть на стадии локальнокогда он пытался выполнить развертывание на действиях Github, отображалась следующая ошибка во время Deploy to staging
фаза:
Run bundle exec cap staging deploy
#<Thread:0x0000560949aab9c0@/home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/runners/parallel.rb:10 run> terminated with exception (report_on_exception is true):
/home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/runners/parallel.rb:15:in `rescue in block (2 levels) in execute': Exception while executing as deployer-bot@stage.api.example.com: Inappropriate ioctl for device (SSHKit::Runner::ExecuteError)
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/runners/parallel.rb:11:in `block (2 levels) in execute'
/home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/net-ssh-7.0.1/lib/net/ssh/prompt.rb:44:in `noecho': Inappropriate ioctl for device (Errno::ENOTTY)
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/net-ssh-7.0.1/lib/net/ssh/prompt.rb:44:in `ask'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/net-ssh-7.0.1/lib/net/ssh/authentication/methods/password.rb:68:in `ask_password'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/net-ssh-7.0.1/lib/net/ssh/authentication/methods/password.rb:20:in `authenticate'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/net-ssh-7.0.1/lib/net/ssh/authentication/session.rb:87:in `block in authenticate'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/net-ssh-7.0.1/lib/net/ssh/authentication/session.rb:71:in `each'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/net-ssh-7.0.1/lib/net/ssh/authentication/session.rb:71:in `authenticate'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/net-ssh-7.0.1/lib/net/ssh.rb:254:in `start'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/backends/connection_pool.rb:63:in `call'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/backends/connection_pool.rb:63:in `with'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/backends/netssh.rb:177:in `with_ssh'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/backends/netssh.rb:130:in `execute_command'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/backends/abstract.rb:148:in `block in create_command_and_execute'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/backends/abstract.rb:148:in `tap'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/backends/abstract.rb:148:in `create_command_and_execute'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/backends/abstract.rb:61:in `test'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/capistrano-passenger-0.2.1/lib/capistrano/tasks/passenger.cap:43:in `block (3 levels) in <top (required)>'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/backends/abstract.rb:31:in `instance_exec'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/backends/abstract.rb:31:in `run'
from /home/runner/work/api/api/vendor/bundle/ruby/2.6.0/gems/sshkit-1.21.3/lib/sshkit/runners/parallel.rb:12:in `block (2 levels) in execute'
(Backtrace restricted to imported tasks)
cap aborted!
SSHKit::Runner::ExecuteError: Exception while executing as deployer-bot@stage.api.example.com: Inappropriate ioctl for device
Caused by:
Errno::ENOTTY: Inappropriate ioctl for device
Tasks: TOP => rvm:hook => passenger:rvm:hook => passenger:test_which_passenger
(See full trace by running task with --trace)
deployer-bot@stage.api.example.com's password:
Error: Process completed with exit code 1.
Я потратил довольно много времени, пытаясь понять, что Неподходящий ioctl для устройства означает. Я сэкономлю вам немного времени. Это значит: Мне требуется ввод с терминала, а терминал не подключен к этому устройству. Если бы я видел строку внизу, которая говорит deployer-bot@stage.api.example.com's password:
, я, вероятно, решил бы это немного быстрее, но я этого не заметил, потому что был слишком занят просмотром трассировки стека. Честно говоря, в любом случае это немного отвлекающий маневр, потому что настоящая ошибка должна была сказать что-то вроде git@github.com:FoamFactory/my-repo's password:
.
Решение
Происходило то, что на GitHub runner команда SSH для stage.api.example.com
работал нормально. Что не было Работал, однако, тот шаг, на котором Капистрано stage.api.example.com
, проверял кодовую базу с GitHub. Это связано с тем, что открытый ключ для deployer-bot@stage.api.example.com
не был зарегистрирован на GitHub под моей учетной записью пользователя, и для рассматриваемого проекта не было ключа развертывания.
Итак, кажется, что все, что нам нужно сделать, это добавить ключ развертывания на GitHub для соответствующего репозитория с содержимым файла id_rsa.pub
в .ssh
каталог для deployer-bot
на stage.api.example.com
, верно? Неправильный.
Чтобы усложнить дело, на stage.api.example.com
файл ~/.ssh/config
для deployer-bot
выглядело так:
Host github.com
HostName github.com
IdentityFile ~/.ssh/github-actions
Это означает, что для каждого хоста кроме GitHubон использовал id_rsa
файл, расположенный в ~/.ssh
. Однако для GitHub он использовал github-actions
. Это означает, что мне нужно добавить ключ развертывания для github-actions.pub
в том же каталоге. Верьте или нет, это все еще не был полным ответом.
Капистрано также нужно было сказать использовать github-actions
. В частности, эта строка в config/deploy/staging.rb
файл:
keys: %w(~/.ssh/id_rsa),
необходимо изменить на это:
keys: %w(/.ssh/github-actions),
После этого, как по волшебству, все как по волшебству заработало.
Заключение
Я надеюсь, что документирование этого поможет вам в будущем. Честно говоря, я думаю, что это довольно специфический случай неправильной настройки на моей стороне (я знаю это, потому что я также разместил это на Переполнение стека и не получил ответа в течение 6 месяцев, прежде чем я понял это самостоятельно). Тем не менее, документирование этого здесь, по крайней мере, поможет мне вспомнить, что происходит, и, может быть, просто поможет кому-то еще, потерявшемуся в море перенаправлений SSH.