【Python】EC2インスタンスの起動からIP取得・応用まで

# はじめに
はじめまして。私は執筆時大学2年生で、インフラ系の企業で実習中でした。
その際に初めてAWSの機能の一つであるEC2に触れました。そこで、
「いちいち起動して、IP取得してTeratarmにIP打ってってやるのめんどくね…？」と思い居ても立っても居られなくなった瞬間VScordを開きプログラムを組んでいました。同じ気持ちになった人も何人かいるのではないでしょうか…？
今回は初投稿でまだ未熟なところもあるかもしれませんが一人でも多くの悩みが解決できれば幸いです。
それではやってみましょう！

# この記事の対象
・ AWSに初めてもしくは久々に触った人
・ Pythonが少し読めるくらいの人
・ めんどくさいけど楽にする気がある人

# 環境

- EC2側
    - インスタンスタイプ ： t2.micro(無料使用枠)

- Host側
    - OS ： Windows11
    - Pythonバージョン : 3.9.10

# 簡単なフロー

今回私は、

1. パス、名前、boto3の設定
1. EC2の起動
1. グローバルIPの取得
1. WクリックするだけでSSH接続が完了するTeratarmのショートカットを作成する。
1. ホストファイル(簡単に言うとDNSサーバの代わり)の編集を自動化

をクラス化し実行できる形にしました。
私は現在3つのEC2サーバを実習で使っているためクラス化したほうがきれいに見えるためです。
(少しあやふやだったから復習の意も込めて…笑)

:::note warn
警告
このプログラムではWindowsの構成ファイル(etc/hosts)を変更します。ご自身の環境でプログラムを実行する際には万が一に備えてバックアップをとることをお勧めします。

また構成ファイルを変更するため管理者権限で実行してください。

当ページに掲載されている情報を利用することで発生した損害に対し、著者は責任を負わないものとします。予めご了承ください。
:::
## 実践!

### 必要な設定

#### ライブラリ
まずは必要なライブラリをインストールします。コマンドプロンプトにて
```shell:cmd
> pip install boto3

> pip install dotenv
```
各ライブラリの解説は後々。

#### IAM ユーザー作成
次にAWSで権限を取得します。 AWSへログインし、「サービス」から「IAM」に入ってください。
次にユーザーグループを作成していきます。画像のようなメニューが左にあるため選択していきます。
EC2と検索して一番上のほうに出てくる「AmazonEC2FullAccess」を選択しグループを作成していきます。
(本当は必要な権限のみを取得するのがよいのでしょうが、個人用なのでフルアクセスで今回は作成します。)
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f333132303734302f63356264623130352d643132322d346561622d383631382d3362653538636632393330642e706e6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3120740/f48b08be-9999-7796-e373-e570dbcf0602.png)

#### IAM ユーザーグループ作成
次にユーザーを作成します。 右のメニューから「ユーザー」を選択し、「ユーザーを追加」をクリックしていきます。
名前を決めたら先ほど作ったユーザグループ選択し、次へを押していきます。

#### シークレットキー ・ アクセスキー 取得
作成できたらキーを取得します。 もう一度右のメニューから「ユーザー」を選択し自分がさっき作ったユーザーをクリックします。
![Screenshot 2023-02-02 234715.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3120740/4e4f6f85-77fd-ffc8-ecb6-41e3ee22a247.png)
クリックした画面から「セキュリティ認証情報」→ 「アクセスキーの作成」を選択します。
![Screenshot 2023-02-02 235423.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3120740/a3004ea0-e32b-0eee-7490-efaf7bc14343.png)
その後「AWS の外部で実行されるアプリケーション」にチェックを入れ次へを押すとアクセスキーとシークレットキーが表示されます。
これをcsvファイルに保存するかメモ帳に保存するかして控えておきます。
![Screenshot 2023-02-02 234715.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3120740/52a5ca77-7e26-3685-3a3b-87625f87d426.png)

#### 環境変数設定
次に環境変数の設定をしていきます。
プログラムを書くディレクトリ(ファイル)に key.env(.envは拡張子) というファイルを作り次のように記述してください。
```env:key.env
ACCESS_KEY = "xxxxxxxxxxxxxxxxxxxxxxx" #先ほど取得したアクセスキーを xの代わりに入れる
SECRET_ACCESS_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" #同様にシークレットキーを入れる
```
このファイルを作っておくことによってほかの人がコードを見たときに誤ってアクセスキー等が漏れたりするのを防ぎます
(全権限あるから知られると大変なことになっちゃう)

続いて実際にコードを書いていきます。

### ① パス、名前、boto3の設定
```py:ec2Autoation.py
import subprocess   # Teratarmのショートカットを作るために使います。
import boto3        # AWSの機能を使うために使います。
import os 
from os.path import dirname,join
from dotenv import load_dotenv
                    #環境変数を使うために使用します。

load_dotenv(verbose=True)
dotenv_path = join(dirname(__file__),".env")
                    #ここでさっき作ったファイルを読み込んでいる
ACCESS_KEY = os.environ.get("ACCESS_KEY")
SECRET_ACCESS_KEY = os.environ.get("SECRET_ACCESS_KEY")
#環境変数を使ってアクセスキー及び、シークレットキーを使うためのコードです。

#################以下は各自の環境に沿って書き換えてください。#################
PATHTERATERM = r"<Teratarm実行ファイル(ttermpro.exe)の絶対パス>"
PATHOPENKEY  = r"<SSH用公開鍵の絶対パス>"
PATHSHORTCUTDIR = r"<ショートカットを作成するディレクトリの絶対パス>"
hostPath = "C:\Windows\System32\drivers\etc\hosts" #←はデフォルトの位置です万が一変わっていたら変更してください。
##############################################################################
ec2 = boto3.resource('ec2',
                        aws_access_key_id=ACCESS_KEY,
                        aws_secret_access_key=SECRET_ACCESS_KEY,
                        region_name= "ap-northeast-1"
                        #region_nameは各環境で変えてください。
                        )
```
ここのセクションではEC2を動かすための設定をしています。
### ② EC2の起動・グローバルIPの取得
続いて先ほどの続きに次のコードを記述していきます。
```py:ec2Automation.py
class ec2Automation:

    def __init__(self,id,nameInstance):
        self.instanceID = id
        self.instance = ec2.Instance(self.instanceID)
        self.instanceName = nameInstance
                        #インスタンス生成時に設定するインスタンスID、インスタンス名を
                        #メンバー変数に設定します。

    def startEC2(self):

        self.instance.start()
                        #指定したインスタンスの起動
        self.instance.wait_until_running()
                        #インスタンスが正常起動するまで待機
    def getEC2Address(self):
        self.groIP = self.instance.public_ip_address
                        #IPアドレスを設定したEC2インスタンスから回収、変数に入れる
        print(f"{self.instanceName} Global IP Address : {self.groIP}")
                        #結果を出力
```
インスタンスの初期化と起動、グローバルIPの取得までをここまでで行っています。
起動後、正常起動まで待機するのは、設定しないと後で設定するIPが取得できなかったためです。

### ③ Teratarmのショートカットを作成
```py:ec2Automation.py
    def makeTeratarmShortCut(self):
        wshText = \
f'Set shell = WScript.CreateObject("WScript.Shell")\n\
fil =  "{PATHSHORTCUTDIR}\{self.instanceName}.lnk"\n\
Set shortCut = shell.CreateShortcut(fil)\n\
shortCut.TargetPath = "{PATHTERATERM}"\n\
shortCut.Arguments = "{self.groIP}:22 /auth=publickey /user=ec2-user /keyfile={PATHOPENKEY}"\n\
shortCut.Save'
                        #vbsの中身を記述

        with open(f"{self.instanceName}.vbs", mode = "w") as t:
            t.write(wshText)
                        #vbsファイルを作成(ec2Automation.pyと同じディレクトリに作成)
        subprocess.call(f'{self.instanceName}.vbs',shell = True)
                        #作成したファイルを実行
```
今回は少し遠回りかもしれませんがvbsの中身を都度変更して、実行することによってショートカットを作成(上書き)する手法をとっています。
上半分(wshText)の部分でvbsの中身をつくって下半分で作ったファイルを実行しているといった流れです。

### ④ホストファイルの編集・実行
```py:ec2Automation.py
    def changeHostFile(self):
        flg = cont = 0
                        #変数の初期化
        tx = f"{self.groIP} {self.instanceName}\n"
                        #hostsファイルに書き込む定型文の用意
        with open(hostPath,"r") as h:
            tmp = h.readlines()
                        #元のhostsファイルを仮置き
                        #一行ごとリストになって変数に格納される

        for i in tmp:   #リストを順番にみていく
            if self.instanceName in i:
                        #もしインスタンス名と同じ文字列を検知したら
                tmp[cont] = tx
                        #文字列を書き換える
                flg = 1
                        #文字列を一度替えたフラグを立てる
                if flg > 1: del tmp[cont]
                        #もし2回目以降の検知ならその行を削除する
                        #無限にhostsファイルの中身が増えていくのを防ぐため
            cont += 1   #カウントを進める

        if flg == 0:    #一度もインスタンス名と同じ文字列を検知できなかったら
            tmp.append(tx)
                        #新しい行に定型文を追加する

        with open(hostPath,"w") as h:
            h.writelines(tmp)
                        #新しく上書き
    
    def ec2Automation(self):
                        #これまで作った順に実行する関数
        self.startEC2()
        self.getEC2Address()
        self.makeTeratarmShortCut()
        self.changeHostFile()

hogeInstance = ec2Automation("xxxxxxxx","yyyyyyyy")
                        #"xxxxxxxx"には起動したいインスタンスIDを記述
                        #"yyyyyyyy"には任意の名前を記述(hostsファイルのドメイン名、ショートカットの名前になります)
                        #短すぎるとバグるので3文字以上が推奨です。
hogeInstance.ec2Automation()
                        #実行 しばらく待つとIPアドレスが表示される
```
最後にhostsファイルを変更して終了です。
(※hostsファイルとはIPアドレスの代わりにドメインを用いて接続するのに必要なファイル。現在ではDNSサーバーが代わりを担っている。)
この動作をしておくと、ブラウザのブックマークによく見るページをドメインで設定できるため非常に便利です。

一番最後にインスタンスを
EC2のインスタンスIDを第一引数に、任意の名前を第二引数に渡しインタンスを生成し、
上のプログラム最終行のように `xxxxx.ec2Automation()`(xxxxは生成したインスタンス)を記述し、上の注意事項に記載のとおり管理者権限で実行すると
グローバルIPが表示し任意の名前( `http://任意の名前/` )でページを確認することができると思います！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3120740/07cdcfc4-efe3-d71f-10e9-aa921e1ddade.png)
↑同時に3つインスタンスを作り別々のEC2インスタンスを起動している

# 最後に
いかがだったでしょうか。
初投稿なのでまだまだとても見にくいと思うのですが温かい目で見ていただけるとたすかります！！！！
そして、何かお気づきの点がございましたらコメントいただけると幸いです。

# プログラム全景
```py:ec2Automation.py
import subprocess   # Teratarmのショートカットを作るために使います。
import boto3        # AWSの機能を使うために使います。
import os 
from os.path import dirname,join
from dotenv import load_dotenv
                    #環境変数を使うために使用します。

load_dotenv(verbose=True)
dotenv_path = join(dirname(__file__),".env")
                    #ここでさっき作ったファイルを読み込んでいる
ACCESS_KEY = os.environ.get("ACCESS_KEY")
SECRET_ACCESS_KEY = os.environ.get("SECRET_ACCESS_KEY")
#環境変数を使ってアクセスキー及び、シークレットキーを使うためのコードです。

#################以下は各自の環境に沿って書き換えてください。#################
PATHTERATERM = r"<Teratarm実行ファイル(ttermpro.exe)の絶対パス>"
PATHOPENKEY  = r"<SSH用公開鍵の絶対パス>"
PATHSHORTCUTDIR = r"<ショートカットを作成するディレクトリの絶対パス>"
hostPath = "C:\Windows\System32\drivers\etc\hosts" #←はデフォルトの位置です万が一変わっていたら変更してください。
##############################################################################
ec2 = boto3.resource('ec2',
                        aws_access_key_id=ACCESS_KEY,
                        aws_secret_access_key=SECRET_ACCESS_KEY,
                        region_name= "ap-northeast-1"
                        #region_nameは各環境で変えてください。
                        )
class ec2Automation:

    def __init__(self,id,nameInstance):
        self.instanceID = id
        self.instance = ec2.Instance(self.instanceID)
        self.instanceName = nameInstance
                        #インスタンス生成時に設定するインスタンスID、インスタンス名を
                        #メンバー変数に設定します。

    def startEC2(self):

        self.instance.start()
                        #指定したインスタンスの起動
        self.instance.wait_until_running()
                        #インスタンスが正常起動するまで待機
    def getEC2Address(self):
        self.groIP = self.instance.public_ip_address
                        #IPアドレスを設定したEC2インスタンスから回収、変数に入れる
        print(f"{self.instanceName} Global IP Address : {self.groIP}")
                        #結果を出力

    def makeTeratarmShortCut(self):
        wshText = \
f'Set shell = WScript.CreateObject("WScript.Shell")\n\
fil =  "{PATHSHORTCUTDIR}\{self.instanceName}.lnk"\n\
Set shortCut = shell.CreateShortcut(fil)\n\
shortCut.TargetPath = "{PATHTERATERM}"\n\
shortCut.Arguments = "{self.groIP}:22 /auth=publickey /user=ec2-user /keyfile={PATHOPENKEY}"\n\
shortCut.Save'
                        #vbsの中身を記述

        with open(f"{self.instanceName}.vbs", mode = "w") as t:
            t.write(wshText)
                        #vbsファイルを作成(ec2Automation.pyと同じディレクトリに作成)
        subprocess.call(f'{self.instanceName}.vbs',shell = True)
                        #作成したファイルを実行

    def changeHostFile(self):
        flg = cont = 0
                        #変数の初期化
        tx = f"{self.groIP} {self.instanceName}\n"
                        #hostsファイルに書き込む定型文の用意
        with open(hostPath,"r") as h:
            tmp = h.readlines()
                        #元のhostsファイルを仮置き
                        #一行ごとリストになって変数に格納される

        for i in tmp:   #リストを順番にみていく
            if self.instanceName in i:
                        #もしインスタンス名と同じ文字列を検知したら
                tmp[cont] = tx
                        #文字列を書き換える
                flg = 1
                        #文字列を一度替えたフラグを立てる
                if flg > 1: del tmp[cont]
                        #もし2回目以降の検知ならその行を削除する
                        #無限にhostsファイルの中身が増えていくのを防ぐため
            cont += 1   #カウントを進める

        if flg == 0:    #一度もインスタンス名と同じ文字列を検知できなかったら
            tmp.append(tx)
                        #新しい行に定型文を追加する

        with open(hostPath,"w") as h:
            h.writelines(tmp)
                        #新しく上書き
    
    def ec2Automation(self):
                        #これまで作った順に実行する関数
        self.startEC2()
        self.getEC2Address()
        self.makeTeratarmShortCut()
        self.changeHostFile()

hogeInstance = ec2Automation("xxxxxxxx","yyyyyyyy")
                        #"xxxxxxxx"には起動したいインスタンスIDを記述
                        #"yyyyyyyy"には任意の名前を記述(hostsファイルのドメイン名、ショートカットの名前になります)
                        #短すぎるとバグるので3文字以上が推奨です。
hogeInstance.ec2Automation()
                        #実行 しばらく待つとIPアドレスが表示される
```
# 参考

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html

↑boto3のドキュメント。基本的にboto3の関数はここから調べました。

https://maku77.github.io/python/env/dotenv.html

↑環境変数を使う際に参考にさせていただきました。

https://qiita.com/kimihiro_n/items/f3ce86472152b2676004

↑boto3のオブジェクト作成の際に参考にさせて頂きました。

https://www.projectgroup.info/tips/Windows/vbs_0001.html

↑ショートカットを自動作成する機構にて参考にさせていただきました。

https://chiritsumo-blog.com/teraterm-shortcut/

↑ショートカット作成の際に参考にさせていただきました。

https://qiita.com/shizen-shin/items/a997bc228fa2850c9fce

↑記事の書きかたで参考にさせていただきました。

皆さまありがとうございました！