CircleCIでCI/CDパイプラインを構築していたところ、どうしてもcapistranoの実行プロセスでSSHエラーが出るので、憤慨。結果から言うと、セキュリティグループのポートがマイIPからしか解放されないように設定していた故のエラーだったのですが、変更がめんどくさそうなのでもっといい方法がないかと模索したところ、半日くらいこのデバックに費やしてしまいました(とほほ)。
ですが、デバッグの過程で各々のツールに関する知識が深まったので、結果オーライかなぁと思うわけです。
以下、備忘録。
ーーーーーーーーーーーーーーーーーーーーーーーーーーー
【原因のエラーメッセージ】
cap aborted!
SSHKit::Runner::ExecuteError: Exception while executing as ユーザー名@IPアドレス:
Net::SSH::ConnectionTimeout
Caused by:
Net::SSH::ConnectionTimeout: Net::SSH::ConnectionTimeout
Caused by:
Errno::ETIMEDOUT: Connection timed out - connect(2) for IPアドレス
Tasks: TOP => rbenv:validate
(See full trace by running task with --trace)
ムムム、Capistranoの設定の時に散々遭遇した覚えがある。
実際にこの処理が行われているのは、CircleCIのサーバー上に立てられたdockerコンテナなので(合ってますよね?)、どうにかこの中に入って原因を突き止めたい。
該当するワークフローの画面から、Rerun Job with SSH を選択し、
Enable SSH に表示されるIPをコピーする。
capistranoのデプロイ先であるEC2インスタンスから、github用の秘密鍵を用いて、circleciのコンテナにSSH接続します。
(ここら辺の動きがまだよくわからん。CiecleCIにはgithubとの連携の際に公開鍵が登録されてるってことですか?)
【EC2インスタンス】
$ ssh -i ~/.ssh/aws_git_rsa -p Enable SSHの値
The authenticity of host 'Enable SSHの値 (Enable SSHの値)' can't be established.
RSA key fingerprint is 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
RSA key fingerprint is 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
Are you sure you want to continue connecting (yes/no)?
確認されるので、yesと入力。
circleci@~~~~~~~~~ :~$
無事入れました。
コンテナ内を回遊してわかったんですが、結局ローカルでやろうとしてることをこのコンテナ内で再現しようとしているだけなんですよね。つまり、EC2に接続できなかったのは、ローカルの~/.sshに置かれている秘密鍵がcircleci@ :~/.sshには存在しないからなのです。思ったより単純でした。
ローカルの~/.sshにあるEC2用の秘密鍵をコピーします。
.ssh % pbcopy < ~/.ssh/himitsu_key_rsa
サークルCI->プロジェクトページ->右上設定タブ->project settings
サイドバーにあるSSHkeysを選択。
Add SSH Keyから、EC2用の秘密鍵を設定します。
Hostnameを、EC2のElasticIP。Private Keyに、先ほどコピーした鍵の値をCtrl+Vで貼り付ける。値がinvalidだと怒られる。PEM形式でないと鍵の値を保存できないみたい。
参考:https://amasuda.xyz/post/2019-07-27-ssh-keygen-openssh-to-pem/
上記の記事などを参考に、鍵の形式を変更します。
BEGIN OPENSSH PRIVATE KEY が BEGIN RSA PRIVATE KEY
に変わっていれば保存できるはずです。
登録を終えたら、上記画像のFingerprintの値をコピーして、.circleci/config.ymlのプロセスに記載します。
- add_ssh_keys:
fingerprints:
- "コピーしたFingerprint"
- deploy:
name: Capistrano deploy
command: |
if [ "${CIRCLE_BRANCH}" != "master" ]; then
exit 0
fi
bundle exec cap production deploy
ここでひとまず、githubにプッシュ。実行されたプロセスを確認してみると、
fingerprintをもとに鍵の値をインストールしてくれています。
一旦コンテナの中に入って、確認してみましょう。
circleci@~~~~~~~~~:~$ ls .ssh
config id_rsa id_rsa_11111111111111111111111111111111 known_hosts
【id_rsa + _fingerprintから:を抜いた値】と言う形式で保存されている鍵があります(ここでは例として111...を記載しています)。
このファイルを展開してみましょう。
circleci@~~~~~~~~~:~$ cat .ssh/id_rsa_11111111111111111111111111111111
-----BEGIN RSA PRIVATE KEY-----
【さっき登録した秘密鍵の値】
なるほど、さっき登録した秘密鍵は、【id_rsa + _fingerprintから:を抜いた値】という名前でCircleCIコンテナの.ssh配下に保存されているわけですね。
ローカルでは、環境変数を用いてこの鍵へのパスを指定していました。
【config/deploy/production.rb】
server 'ElasticIP',
user: "ユーザー名",
roles: %w{web db app},
ssh_options: {
port: 22,
keys: ["#{ENV.fetch('PRODUCTION_SSH_KEY')}"],
forward_agent: true
}
しかし、ローカルとCircleCIコンテナでは鍵の名前が異なりますし、環境変数は各々のサーバーで設定する必要がありますから、CircleCI側で改めて環境変数を指定し直さなくてはなりません。
サイドバーのEnvironment Variablesを選択。
環境変数を指定します。Nameはdeproy/production.rbで使用している変数名と同じものを指定。
valueはCircleCIコンテナにある秘密鍵へのパスを指定します。
ここまでくれば大丈夫なはず!再実行をしてみる!!!
cap aborted!
SSHKit::Runner::ExecuteError: Exception while executing as ユーザー名@IPアドレス:
Net::SSH::ConnectionTimeout
Caused by:
Net::SSH::ConnectionTimeout: Net::SSH::ConnectionTimeout
Caused by:
Errno::ETIMEDOUT: Connection timed out - connect(2) for IPアドレス
Tasks: TOP => rbenv:validate
(See full trace by running task with --trace)
ムカつくな、試しにEC2のインバウンドルールを変更します。22番ポートを完全に解放。
まんまと成功しやがって、畜生、マジで。まあそうだよな。マイIPからのSSHじゃなきゃ開けないって設定したの自分なのに、馬鹿みたいじゃないか。
CircleCIでは毎回新しくコンテナを立ち上げている(?)のでIPアドレスが流動的です。つまり、プロセスを開始するたびに、そのコンテナからの接続を許可し、処理が終わったらそれを破棄しなければならない。
参考:https://qiita.com/rintaro-ishikawa/items/02e6a63dbc90ea67a991
この記事を参考に次の記事で問題を解決してみます。
あぁ、早くReactさわりたい。