EC2へのSSH接続をPythonからする方法

## １．はじめに

複数EC2インスタンスを立ち上げているときのEC2の管理って皆さんどうされていますか？
私はAWSを数ヶ月前から勉強し始めましたが知識が乏しく、SSH接続する際にまずマネジメントコンソールにアクセスしてEC2インスタンスのIPアドレスを控えておき停止/起動するたびアドレスを控え直していました。
（かなり効率が悪いとは思いつつもそれ以外の方法を思いつかなかっので・・・）

ElasticIPを使うことでアドレスを固定化することはできますが、停止しているインスタンスに設定していると僅かながら料金が発生してしまうため基本的には利用していませんでした。

本記事では私がこの面倒くさい管理方法を卒業した話をまとめて行こうと思います！！


## ２．動作環境

- OS：macOS Catalina 10.15.3
- Python 3.8.1

勉強用に2つのEC2インスタンスを起動中
- web-1a
- aws-ec2
(Nameというタグを設定しEC2を識別できるようにしています)
![EC2インスタンスの情報.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187797/172ec4b3-48f2-d7be-2fcb-21dcd963ade1.png)


## ３．事前準備
### ３−１．PublicDnsNameの表示設定（すでに表示されている場合は不要）

冒頭で書いたとおりEC2のアドレスは起動するたびに変更されるためPublicDnsNameを使ってSSHできるようにします。
設定方法は以下を参考にさせていただきました。

[AWS EC2でWebサーバーを構築してみる](https://qiita.com/arashida/items/629aaed33401b8f2265c)

### ３−２．boto3のインストール(インストール済みの場合は不要）

Python3でAWS環境を操作するには、boto3と呼ばれるライブラリが必要なため、下記コマンドでインストールします。

```bash
pip install boto3
```

インストールできているかの確認

```bash
pip list         
Package         Version
--------------- -------
boto3           1.13.2 
```

boto3が一覧に表示されていればOK

### ３−３．IAMユーザの作成&権限の付与

AWSの各種情報取得するためにはそれ用のユーザを新たに作成するのが一般的なようです。以下ページを参考にさせていただきました。

[EC2へのSSH接続を簡単にしたい！ (2)コマンドラインでEC2のホスト名を取得してみる](https://www.subthread.co.jp/blog/20160412/)


## ４．プログラム
### ４−１．プログラムの作成
今回は2つのファイルに分けて作成していきます。
それぞれの役割は下記のとおりです。

- ec2_info.py：起動中のEC2インスタンスの情報を取得する
- aws-ssh：ec2_info.pyで取得した情報を基にSSHする<br>
（aws-sshはコマンドとして実装するため拡張子はつけていません）

```python:ec2_info.py
import os
try:
    from io import StringIO  # py3
except ImportError:
    from cStringIO import StringIO  # py2

import boto3
import six

# 取得対象アカウント
# ACCOUNTS = ['アクセスキー', 'シークレットアクセスキー', 'リージョン']
ACCOUNTS = [
    ('**********', '**********', '**********'),
]
# pemファイルの格納先
ssh_dir = '$HOME/.ssh/'
user_name = 'ec2-user'

def main():
    isinstance = get_instance()
    print(isinstance)

def get_instance():
    config_lists = []

    # インスタンス情報の取得
    for access_key_id, secret_access_key, region in ACCOUNTS:
        session = boto3.Session(aws_access_key_id=access_key_id,
                                aws_secret_access_key=secret_access_key,
                                region_name=region)
        client = session.client('ec2')

        # 起動中のインスタンス一覧を取得
        filters = [
            {'Name': 'instance-state-name', 'Values': ['running']}
        ]
        response = client.describe_instances(Filters=filters)

        # すべてのインスタンス一覧を取得
        # response = client.describe_instances()

        for reservation in response['Reservations']:
            for instance in reservation['Instances']:
                config = {}
                tags = {tag['Key']: tag['Value'] for tag in instance['Tags']}
                alias_name = tags.get('Name')
                public_dns_name = instance['PublicDnsName']
                key_name = instance['KeyName']

                config['alias_name'] = alias_name
                config['public_dns_name'] = public_dns_name
                config['user_name'] = user_name
                config['key_name'] = ssh_dir + key_name + '.pem'
                config['key_name'] = f"{ssh_dir}{key_name}.pem"
                config_lists.append(config)

    return config_lists

if __name__ == '__main__':
    main()
```

ssh_dirではpemファイルの格納先を設定しているためディレクトリ構成が異なる場合は適宜変更して下さい。
ACCOUNTS には３−３で作成したユーザアカウントのアクセスキー、シークレットアクセスキー、リージョン名の設定をする必要があります。

```python:aws-ssh
#!/usr/local/var/pyenv/shims/python
# coding: UTF-8

import sys
import subprocess
import ec2_info

def main():
    hosts_list = ec2_info.get_instance()
    selected_host = select_host(hosts_list)
    cmd = create_command(selected_host)
    try:
        subprocess.call(cmd, shell=True)
    except:
        print('ERROR: ログインできませんでした！')
        sys.exit()

####################
# method
####################
def select_host(hosts_list):
    """EC2インスタンスの表示及び選択"""
    while True:
        print('\n--- Hosts List ---')
        for index, host in enumerate(hosts_list, 1):
            print(f"{index})  {host['alias_name']}\t{host['public_dns_name']}")

        # ホストを選択
        print('\n終了するときは q を入力')
        num = input('\ninput) ')

        if num == 'q':
            sys.exit()
        try:
            if len(hosts_list) >= int(num) > 0:
                print('\n\n--- Selected Host ---')
                selected_host = hosts_list[int(num)-1]
                print(f"{selected_host['alias_name']} を選択しました\n")
                return selected_host
            else:
                print('ERROR: 範囲内の数値を入力して下さい！')
        except ValueError:
            print('ERROR: 範囲内の数値を入力して下さい！')

def create_command(selected_host):
    """ssh形式のコマンド作成"""
    cmd = f"ssh -i {selected_host['key_name']} {selected_host['user_name']}@{selected_host['public_dns_name']}"
    return cmd

if __name__ == '__main__':
    main()
```

### ４−２．ディレクトリ構成

/Users/******/programming/python/ 配下に今回作成した2つのファイルを格納

```bash
|--aws-ssh
   |--aws-ssh
   |--ec2_info.py
```

### ４−３．ファイルの実行権限を変更

aws-sshはコマンドとして実装するため権限を700へ変更

```bash
ll
-rw-r--r--  1 ******  staff  1594  5 12 12:36 aws-ssh
-rw-r--r--  1 ******  staff  1976  5 12 12:41 ec2_info.py

chmod 700 aws-ssh

ll
-rwx------  1 ******  staff  1594  5 12 12:36 aws-ssh
-rw-r--r--  1 ******  staff  1976  5 12 12:41 ec2_info.py
```

### ４−４．任意のディレクトリで実行できるようにパスを設定

ユーザディレクトリ配下にcommandというディレクトリを作成し以下のコマンドでパスを設定

```bash
❯ pwd
/Users/******/command

~/command
❯ ll 
total 0
lrwxr-xr-x  1 ******  staff  50  5 12 12:31 aws-ssh -> /Users/******/programming/python/aws-ssh/aws-ssh

❯ echo $PATH
/usr/local/var/pyenv/shims:/Users/******/command
```


## ５．実行結果
![実行結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187797/cabd102d-6611-4668-2160-f1e914cb17a1.png)

起動している2つのEC2の名前とPublicDnsNameが表示され、ログインしたい任意の数値を入力することでEC2へログインすることができました。(PublicDnsNameは伏せてます。)


## ６．おわりに
停止しているEC2インスタンスの起動もできればよかったのですがそれは次回の課題にしようと思います。