@Takumi0v0

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

paramikoを用いて、SSHClient.connectするときのエラーについて

背景

Discordから、ARKサーバーの起動・停止を行いたいです。
下記Qiitaページを参考に、作業していましたがエラーが発生しました。
DiscordからMinecraftサーバーを立ち上げた話
また、下記ページは実施済みで、正常にssh接続できました。
EC2インスタンス間でSSHパスワードなしでログインする方法

自分なりに調べ尽くしましたが、肝心のpythonやssh接続の知識が不足しているため、ご協力をお願いしたいです。

環境

  • ARKサーバー:AWS ec2 t2.large
  • Discord bot:AWS ec2 t2.micro
  • python 3.7.8
  • paramiko 2.7.1

ソースコード

Discord botのソースコードは下記になります。

discordbot.py
    import discord
    import subprocess
    import paramiko
    import time

    # 自分のBotのアクセストークンに置き換えてください
    TOKEN = 'GbQ8(以下略)'
    # game-serverのインスタンスidを指定してください
    INSTANCEID = 'i-5c(以下略)'

    # 接続に必要なオブジェクトを生成
    client = discord.Client()

    # ***************************
    # ***    処理関数
    # ***************************
    class DiscordBOT:
        def __init__(self, client):
            self.SSHClient = None

        async def main(self, discord_event):
            get_text = discord_event.content
            print('init完了')

            if "$start ark" in get_text:
                print('start ark 受け付けました')
                # インスタンスの起動
                subprocess.call("aws ec2 start-instances --instance-ids {}".format(INSTANCEID), shell=True)
                time.sleep(3)
                print('インスタンス起動処理終了')

                # インスタンスが起動するまで待機
                subprocess.call("aws ec2 wait instance-status-ok --instance-ids {}".format(INSTANCEID), shell=True)
                time.sleep(3)
                print('インスタンス起動待機終了')

                # インスタンスのIPアドレスを取得
                proc = subprocess.run(["aws ec2 describe-instances --instance-ids {} --query 'Reservations[*].Instances[*].PublicIpAddress' --output text".format(INSTANCEID)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
                ip_add = proc.stdout.decode("utf-8")
                ip_add = ip_add.replace(".", "-").replace("\n", "")
                self.server_ip = ip_add
                time.sleep(3)
                print('インスタンスのIPアドレス取得終了')
                print(ip_add)
                print(self)

                # SSH接続クライアント作成
                self.SSHClient = paramiko.SSHClient()
                self.SSHClient.set_missing_host_key_policy(paramiko.WarningPolicy())
                self.SSHClient.connect('ec2-{}.compute-1.amazonaws.com'.format(ip_add), username='root', password='')
                time.sleep(2)
                print('SSH接続クライアント作成終了')

                # SSHでarkサーバー起動
                print('********実行su-steam')
                stdin, stdout, stderr = self.SSHClient.exec_command("su - steam")
                for x in stdout:
                print(x)
                for x in stderr:
                print(x)
                time.sleep(1)
                print('********実行パスワード入力')
                stdin, stdout, stderr = self.SSHClient.exec_command("password")
                for x in stdout:
                    print(x)
                for x in stderr:
                    print(x)
                time.sleep(1)
                print('********実行arkmanagerstart')
                stdin, stdout, stderr = self.SSHClient.exec_command("arkmanager start")
                for x in stdout:
                    print(x)
                for x in stderr:
                    print(x)
                time.sleep(2)

                # 接続用のIPアドレスをdiscordに送信
                send_text = "インスタンスの起動とARKサーバーへの接続に成功しました。\n サーバー起動までお待ちください。".format(ip_add.replace("-", "."))

            elif "$stop ark" in get_text:
                # サーバーの停止
                self.SSHClient.connect('ec2-{}.compute-1.amazonaws.com'.format(ip_add), username='root', password='')
                time.sleep(2)
                stdin, stdout, stderr = self.SSHClient.exec_command("arkmanager stop")
                self.SSHClient.close()

                # インスタンスの停止
                subprocess.call("aws ec2 stop-instances --instance-ids {}".format(INSTANCEID), shell=True)
                send_text = "サーバーの停止が完了しました。"

            if send_text:
                await discord_event.channel.send(send_text)



    discordbot = DiscordBOT(client)

    @client.event
    async def on_ready():
        print('ログインしました')

    # on get message
    @client.event
    async def on_message(message):
        if message.author.bot:
            return

        await discordbot.main(message)

    # Botの起動とDiscordサーバーへの接続
    client.run(TOKEN)

エラー内容

python3 discordbot.pyを実行し、
ログインしましたと返ってきたら、discordにて$start arkと入力します。
結果は下記のようになります。

[ec2-user@ip-172-XX-XX-XXX ~]$ python3 discordbot.py
ログインしました
init完了
start ark 受け付けました
{
    "StartingInstances": [
        {
            "InstanceId": "i-5c(以下略)",
            "CurrentState": {
                "Code": 16,
                "Name": "running"
            },
            "PreviousState": {
                "Code": 16,
                "Name": "running"
            }
        }
    ]
}
インスタンス起動処理終了
インスタンス起動待機終了
インスタンスのIPアドレス取得終了
18-183-75-150
<__main__.DiscordBOT object at 0x7f1a35b83810>
Ignoring exception in on_message
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/discord/client.py", line 312, in _run_event
    await coro(*args, **kwargs)
  File "discordbot.py", line 107, in on_message
    await discordbot.main(message)
  File "discordbot.py", line 50, in main
    self.SSHClient.connect('ec2-{}.compute-1.amazonaws.com'.format(ip_add), username='root', password='')
  File "/usr/local/lib/python3.7/site-packages/paramiko/client.py", line 340, in connect
    to_try = list(self._families_and_addresses(hostname, port))
  File "/usr/local/lib/python3.7/site-packages/paramiko/client.py", line 204, in _families_and_addresses
    hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM
  File "/usr/lib64/python3.7/socket.py", line 752, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -2] Name or service not known

インスタンスは正常に起動しますが、その後のssh接続の部分にて、
socket.gaierror: [Errno -2] Name or service not known
が発生しています。


追記 2020/08/31

uasiさんご回答ありがとうございます。
当初の問題であるsocket.gaierror: [Errno -2] Name or service not knownは解決しました。
本当にありがとうございます!

新たなエラーが発生しましたので、追記いたします。
上記ソースコードより修正部分を抜粋

discordbot.py
            # インスタンスのホストネームを取得
            proc = subprocess.run(["aws ec2 describe-instances --instance-ids {} --query 'Reservations[*].Instances[*].PublicDnsName' --output text".format(INSTANCEID)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
            time.sleep(3)
            print('インスタンスのホスト名取得終了')
            proc = proc.stdout.decode("utf-8")
            proc = proc.replace("\n","")
            print(proc)
            print('上記がホスト名になります')

            # SSH接続クライアント作成
            self.SSHClient = paramiko.SSHClient()
            self.SSHClient.set_missing_host_key_policy(paramiko.WarningPolicy())
            print('connect開始')
            self.SSHClient.connect(proc, username='ec2-user', password='')
            time.sleep(2)
            print('SSH接続クライアント作成終了')

発生したエラー

[ec2-user@ip-172-XX-XX-XXX ~]$ python3 discordbot.py
ログインしました
init完了
start ark 受け付けました
{
    "StartingInstances": [
        {
            "InstanceId": "i-5c(以下略)",
            "CurrentState": {
                "Code": 16,
                "Name": "running"
            },
            "PreviousState": {
                "Code": 16,
                "Name": "running"
            }
        }
    ]
}
インスタンス起動処理終了
インスタンス起動待機終了
インスタンスのホスト名取得終了
ec2-18-181-177-242.ap-northeast-1.compute.amazonaws.com
上記がホスト名になります
connect開始
/usr/local/lib/python3.7/site-packages/paramiko/client.py:837: UserWarning: Unknown ssh-ed25519 host key for ec2-18-181-177-242.ap-northeast-1.compute.amazonaws.com: b'06dc52b1da6f592a8477c7d623994ca5'
  key.get_name(), hostname, hexlify(key.get_fingerprint())
Ignoring exception in on_message
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/discord/client.py", line 312, in _run_event
    await coro(*args, **kwargs)
  File "discordbot.py", line 113, in on_message
    await discordbot.main(message)
  File "discordbot.py", line 56, in main
    self.SSHClient.connect(proc, username='ec2-user', password='')
  File "/usr/local/lib/python3.7/site-packages/paramiko/client.py", line 446, in connect
    passphrase,
  File "/usr/local/lib/python3.7/site-packages/paramiko/client.py", line 764, in _auth
    raise saved_exception
  File "/usr/local/lib/python3.7/site-packages/paramiko/client.py", line 751, in _auth
    self._transport.auth_password(username, password)
  File "/usr/local/lib/python3.7/site-packages/paramiko/transport.py", line 1509, in auth_password
    return self.auth_handler.wait_for_response(my_event)
  File "/usr/local/lib/python3.7/site-packages/paramiko/auth_handler.py", line 250, in wait_for_response
    raise e
paramiko.ssh_exception.BadAuthenticationType: Bad authentication type; allowed types: ['publickey']

procに代入されたホストネームは、ec2のARKサーバーのパブリックDNSと一致していたので正しくホストネームを取得できていると思います。
ARKサーバーにて、下記のようにパーミッションの変更は実施しました。

chmod 700 .ssh
chmod 600 ~/.ssh/authorized_keys

Bad authentication type; allowed types: ['publickey']から、公開鍵を使った接続をしていないと推測しましたが、修正方法が分かりません。


追記 2020/09/01

uasiさん再びご回答いただきまして、本当にありがとうございます!
「追記 2020/08/31」に記載しましたエラーは解決できました。
その後も新たなエラーはでましたが、なんやかんや調べて直し、botを完成させることができました!
下記に、完成したdiscordBotの機能やソースコードを記載します。
どなたかの参考になれば幸いです。


完成したdiscordBotについて(クリックで展開)

botの機能

・Discordにて、$start arkを送信
 →ec2のARKサーバー インスタンスを起動
 →ARKサーバー インスタンスにSSH接続し、ARKサーバーを起動
・Discordにて、$stop arkを送信
 →ARKサーバー インスタンスにSSH接続し、ARKサーバーを停止
 →ec2のARKサーバー インスタンスを停止

Discord上での動作の様子

pc-gamer-botの動作は、ARKサーバーで使っているARK: Survival Evolved Linux Server Toolsによるものです。
※インスタンスが正常に起動・停止できていることは、ec2の管理画面で確認済みです。
discordbot動作.png

ソースコード

discordbot.py
import discord
import subprocess
import paramiko
import time

# 自分のDiscord Botのアクセストークンに置き換えてください
TOKEN = 'GbQ8(以下略)'
# ARKサーバーのインスタンスidを指定してください
INSTANCEID = 'i-5c(以下略)'

# 接続に必要なオブジェクトを生成
client = discord.Client()

# ***************************
# ***    処理関数
# ***************************
class DiscordBOT:
    def __init__(self, client):
        self.SSHClient = None

    async def main(self, discord_event):
        get_text = discord_event.content
        print('init完了')

        if "$start ark" in get_text:
            print('start ark 受け付けました')
            # ARKサーバー インスタンスの起動
            subprocess.call("aws ec2 start-instances --instance-ids {}".format(INSTANCEID), shell=True)
            time.sleep(3)
            print('インスタンス起動処理完了')

            # ARKサーバー インスタンスが起動するまで待機
            subprocess.call("aws ec2 wait instance-status-ok --instance-ids {}".format(INSTANCEID), shell=True)
            time.sleep(3)
            print('インスタンス起動待機終了')

            # ARKサーバー インスタンスのホスト名を取得
            proc = subprocess.run(["aws ec2 describe-instances --instance-ids {} --query 'Reservations[*].Instances[*].PublicDnsName' --output text".format(INSTANCEID)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
            time.sleep(3)
            print('ARKサーバー インスタンスのホスト名取得完了')
            proc = proc.stdout.decode("utf-8")
            proc = proc.replace("\n","")
            print('ARKサーバー インスタンスのホスト名:', proc)

            # SSH接続クライアント作成
            self.SSHClient = paramiko.SSHClient()
            self.SSHClient.set_missing_host_key_policy(paramiko.WarningPolicy())
            self.SSHClient.connect(proc, username='ec2-user', password='', key_filename='.ssh/discordbot_key')
            time.sleep(2)
            print('SSH接続クライアント作成終了')

            # SSHでarkサーバー起動
            print('********su-steam実行')
            stdin, stdout, stderr = self.SSHClient.exec_command('su - steam')
            time.sleep(2)
            print('********パスワード入力実行')
            stdin.write('password\n')
            stdin.flush()
            time.sleep(2)
            print('********arkmanagerstart実行')
            stdin.write('arkmanager start\n')
            stdin.flush()
            time.sleep(2)
            print('********ARKサーバー起動処理完了')

            # サーバー起動処理完了のメッセージをdiscordに送信
            send_text = "<@&746619641706709003> インスタンスの起動とARKサーバーへの接続に成功しました。\n サーバー起動までお待ちください。"


        elif "$stop ark" in get_text:
            # ARKサーバー インスタンスのホスト名を取得
            proc = subprocess.run(["aws ec2 describe-instances --instance-ids {} --query 'Reservations[*].Instances[*].PublicDnsName' --output text".format(INSTANCEID)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
            time.sleep(3)
            print('ARKサーバー インスタンスのホスト名取得完了')
            proc = proc.stdout.decode("utf-8")
            proc = proc.replace("\n","")
            print('ARKサーバー インスタンスのホスト名:', proc)

            # SSH接続クライアント作成
            #self.SSHClient = paramiko.SSHClient()
            #self.SSHClient.set_missing_host_key_policy(paramiko.WarningPolicy())
            self.SSHClient.connect(proc, username='ec2-user', password='', key_filename='.ssh/discordbot_key')
            time.sleep(2)
            print('SSH接続クライアント作成終了')

            # SSHでarkサーバー停止
            print('********su-steam実行')
            stdin, stdout, stderr = self.SSHClient.exec_command('su - steam')
            time.sleep(2)
            print('********パスワード入力実行')
            stdin.write('password\n')
            stdin.flush()
            time.sleep(2)
            print('********arkmanagerstop実行')
            stdin.write('arkmanager stop\n')
            stdin.flush()
            time.sleep(2)
            print('********サーバー停止処理完了、15秒後にインスタンス停止実行')
            time.sleep(11)
            stdin.write('exit\n')
            stdin.flush()
            time.sleep(2)

            #SSH接続終了
            self.SSHClient.close()
            time.sleep(2)
            print('SSH接続終了')

            # インスタンスの停止
            subprocess.call("aws ec2 stop-instances --instance-ids {}".format(INSTANCEID), shell=True)
            send_text = "<@&746619641706709003> サーバー及びインスタンスの停止が完了しました。\nまたのご来訪をお待ちしております。"

        if send_text:
            await discord_event.channel.send(send_text)


discordbot = DiscordBOT(client)

@client.event
async def on_ready():
    print('ログインしました')

# on get message
@client.event
async def on_message(message):
    if message.author.bot:
        return

    await discordbot.main(message)

# Botの起動とDiscordサーバーへの接続
client.run(TOKEN)


1 likes

2Answer

Name or service not known とは ec2-18-183-75-150.compute-1.amazonaws.com が見つからないエラーです。

ホスト名が .compute-1.amazonaws.com になるのは AWS のリージョンが us-east-1 のときだけのようです。他のリージョンなら、たとえば ap-northeast-1 なら .ap-northeast-1.compute.amazonaws.com になります。

self.SSHClient.connect('ec2-{}.compute-1.amazonaws.com'.format(ip_add), username='root', password='')

この行のホスト名をご自分の環境に合わせて書き換えればよさそうです。(また username='root' ではなく username='ec2-user' だと思います。)

あるいは aws ec2 describe-instances で IP アドレスを取得する代わりにホスト名を直接取得してもいいでしょう。

$ aws ec2 describe-instances --instance-ids $ID --query 'Reservations[*].Instances[*].PublicDnsName' --output text
ec2-XXX-XXX-XXX-XXX.ap-northeast-1.compute.amazonaws.com
1Like

Comments

  1. @Takumi0v0

    Questioner

    uasiさんご回答頂きありがとうございます!
    発生していたエラーは解決でき、一歩前進することができました。
    ただ、新たなエラーが発生し、調べながら色々試しましたが手詰まりになってしまいました、、
    お手すきの際に、追記の部分についてもお力添え頂けると幸いです。

SSH で公開鍵認証をするには、秘密鍵を接続元のマシンに、対応する公開鍵を接続先のマシンに置く必要があります。

手元のマシンからは Discord bot サーバにも ARK サーバにも SSH できるはずですから、手元で鍵ペアを作って鍵を両サーバに送り込めばいいと思います。

# 手元で鍵を作成
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/uasi/.ssh/id_rsa): discordbot_key # 名前をセット
Enter passphrase (empty for no passphrase): # パスフレーズなし
Enter same passphrase again:
Your identification has been saved in discordbot_key.
Your public key has been saved in discordbot_key.pub.

# Discord bot サーバに秘密鍵をコピー
$ scp discordbot_key ${bot サーバのホスト}:~/.ssh/discordbot_key

# ARK サーバの authorized_keys に公開鍵を追記
$ cat discordbot_key.pub | ssh ${ARK サーバのホスト} 'cat >> ~/.ssh/authorized_keys'

# 手元の鍵を消すか安全な場所に保管
$ rm discordbot_key discordbot_key.pub

# 手元から bot サーバに SSH して `chmod 400 秘密鍵` する
[ec2-user@ip-172-XX-XX-XXX ~]$ chmod 400 ~/.ssh/discordbot_key

後は SSH クライアントの接続時に秘密鍵のファイル名を指定します。

self.SSHClient.connect(, key_filename='.ssh/discordbot_key')
1Like

Comments

  1. @Takumi0v0

    Questioner

    uasiさん、二度目のご回答ありがとうございます!
    無事エラーは解決し、discord botが完成しました。
    uasiさんの非常に丁寧で分かりやすい回答のおかげさまで、諦めずに実装できました!
    (お礼の気持ちはLGTMボタンでよろしいんでしょうか)
  2. LGTM で大丈夫です、嬉しいです!

Your answer might help someone💌