Heesung Yang
[Python] AWS EC2 정보를 Google Spreadsheets 문서에 정리하기
서론
사용 중인 AWS EC2 리소스에 대해 엑셀 문서로 정리해서 보안팀에 제출해야 하는 일이 생겼다. 1년에 한번씩 보안 심사를 받는데, 그 때마다 같은 일을 계속해야 하는 귀찮음을 해결하고자 삽질한 기록을 남긴다.
- 사용 언어 : Python 3.6
- 사용한 라이브러리
- AWS
- boto3 : 1.18.21
- Google Sheets (Link to Official Document)
- google-api-python-client : 2.15.0
- google-auth-httplib2 : 0.1.0
- google-auth-oauthlib : 0.4.5
- AWS
AWS
boto3는 AWS에서 공식지원하는 파이썬 라이브러리다. (공식 홈페이지) 해당 라이브러리 사용의 큰 흐름은 아래와 같다.
- boto3 session 생성 (profile, region)
- session 으로부터 client 생성
- 생성한 client 로 필요한 리소스 조회/생성/삭제/업데이트
엑세스 키 생성
우선 자신의 AWS 계정에서 사용할 Key를 생성하자. 아래 두 정보가 필요하다. 이미 사용 중인 key가 있다면 아래 내용은 넘어가자.
- ACCESS_KEY
- SECRET_ACCESS_KEY
-
AWS Console 페이지 접속 후 IAM 서비스 선택한다.
-
왼쪽 사이드바 메뉴에서
사용자
선택 후 자신의 계정명을 클릭한다. -
보안 자격 증명
탭을 클릭한다. -
화면을 아래로 스크롤한 후
엑세스 키 만들기
버튼을 클릭한다. -
자동으로 생성된
엑세스 키 ID
와비밀 엑세스 키
(표시
버튼을 눌러서 확인)를 적어둔다. 주의할 점은비밀 엑세스 키
값은 해당 창을 닫으면 이 후 확인할 수 있는 방법이 없다.! 잊어버릴 경우 재생성 해야한다. -
아래와 같이 엑세스 키가 생성됨을 확인할 수 있다. 해당 키는 언제든지
비활성화
/삭제
가 가능하므로 혹시나 외부에 노출된 경우 바로 삭제하자. (가장 오른쪽의x
버튼을 누르면 삭제할 수 있다.)
boto3 라이브러리
우선 라이브러리 설치부터 하자. 필자는 파이썬 3.6을 사용하였다. 아래 명령어를 터미널 창에 입력한다.
pip install boto3
그 후 아래 코드를 입력하여 ec2 인스턴스 정보를 받아와보자.
#!/usr/bin/env python3
import boto3
from botocore.config import Config
# 아래처럼 코드 내에 키를 삽입하는건 굉장히 위험하다 !!!
# 코드가 github public repository에 올라가는 순간...
# 좀 더 나은 방법은 이 포스팅 하단부에 나오니 해당 부분을 참고하자.
AWS_ACCESS_KEY='YOUR_ACCESS_KEY'
AWS_SECRET_KEY='YOUR_SECRET_ACCESS_KEY'
client = boto3.client('ec2',
aws_access_key_id=AWS_ACCESS_KEY,
aws_secret_access_key=AWS_SECRET_KEY,
config=Config(region_name='ap-northeast-2'),
)
# running 상태인 ec2 인스턴스 목록 얻기
response = client.describe_instances(
Filters=[
{'Name': 'instance-state-name', 'Values': ['running']}
]
)
# `response`를 출력하면 굉장히 많은 정보가 출력됨을 알 수 있는데
# 내용이 너무 길어서 여기에서는 필자가 필요했던 내용만 추려서 출력해보았다.
# print(response)
for obj in response['Reservations']:
for tag in obj['Instances'][0]['Tags']:
if tag['Key'] == 'Name':
name = tag['Value']
print("{} {} {} {} {} {} {}".format(
obj['Instances'][0]['InstanceId'],
name,
obj['Instances'][0]['InstanceType'],
obj['Instances'][0].get('PrivateIpAddress'),
obj['Instances'][0].get('PublicIpAddress'),
obj['Instances'][0]['State']['Name'],
obj['Instances'][0]['Placement']['AvailabilityZone'],
))
아래는 출력 결과다. 이제 이 내용을 구글 시트로 생성해보자.
i-0d6d659b8e7745462 test-ec2-02 t3a.nano 172.31.8.172 3.34.95.108 running ap-northeast-2a
i-0735e4c70bd06ed42 test-ec2-01 t3a.nano 172.31.7.145 3.38.96.11 running ap-northeast-2a
구글 시트 API
API 이용을 위해 우선 아래 과정을 통해 API 이용 권한을 획득하자.
구글 프로젝트 생성
-
구글 Console 페이지에 접속하여 새로운 프로젝트를 생성한다.
-
구글 API Library 페이지에 접속 후
google sheets
를 검색한다. 이 때 위에서 생성한 프로젝트가 선택된 상태인지 체크한다. -
API 사용을 위한 Credentials을 생성한다.
다운로드한 파일은 잠시 후 사용해야 하니 잘 보관할 것.
-
현재 이 API는
Testing
단계이다.PUBLISH APP
버튼을 눌러Production
단계로 변경할 수 있는데 절차가 복잡하고 시간도 2~3일 더 필요하다. (구글에서 인증을 해줘야 한다.) -
이 App은 나만 사용할 것이므로 Testing 단계로도 충분하다. 단, Testing 단계에서는 Test users 목록에 등록되어 있는 구글 계정들만 해당 API를 사용할 수 있으므로 본인의 구글 계정을 Test users 목록에 추가하자.
자. 이제 API를 사용할 준비를 마쳤다. 이제 본격적으로 코드를 작성해보자. (API 코드 작성보다 API 사용 준비하는 과정이 더 복잡해 보이는 이유는 단지 느낌인 걸까…)
구글 시트 API 사용 전 알아둘 내용
코드 작성 전에 구글 시트에서 사용하는 용어와 URL 규칙에 대해 알고 가면 더 좋다.
- Spreadsheet : 구글 시트 문서를 의미한다. 엑셀로 비유하면 엑셀파일이라고 보면 된다.
- Sheet : 구글 시트 하단에 표기되는 탭 각각을 의미한다.
- Cell : 각 칸을 의미한다.
- A1 notation
- 구글 시트의 범위를 나타내는 표현식이다.
- 예)
- Sheet1!A1:B2 : 이름이
Sheet1
인 시트의 A1,A2,B1,B2 Cell - Sheet1!A:A : 이름이
Sheet1
인 시트의 A열 전체 - Sheet1!1:2 : 이름이
Sheet1
인 시트의 1,2 번째 행 전체
- Sheet1!A1:B2 : 이름이
URL은 아래와 같이 생겼는데 Spreadsheet ID
와 Sheet ID
가 포함되어 있다.
이 ID값은 파이썬 코드상에서 사용된다.
https://docs.google.com/spreadsheets/d/spreadsheetId/edit#gid=sheetId
예를 들어, 아래와 같은 URL이 있다고 할때 SpreadsheetId
와 SheetId
는 각각 다음과 같다.
https://docs.google.com/spreadsheets/d/1xuAfx7X9g2Nd5DikD-sLM4iA_lk45H0AKYJ0BS0sqfA/edit#gid=0
- SpreadsheetId :
1xuAfx7X9g2Nd5DikD-sLM4iA_lk45H0AKYJ0BS0sqfA
- SheetId :
0
- Spreadsheet를 생성할 때 기본으로 생성되는 sheet의 ID는 항상
0
이다. - 추가 생성되는 sheet의 ID는 꽤 큰 숫자값이다. 예)
1784376386
- Spreadsheet를 생성할 때 기본으로 생성되는 sheet의 ID는 항상
구글 시트 API 활용한 파이썬 코드
-
우선 늘 그렇듯 설치부터 하자. 아래 명령어를 터미널에서 입력한다.
pip install --upgrade google-api-python-client \ google-auth-httplib2 \ google-auth-oauthlib
-
이전 과정에서 다운로드 받은
client_secret_xxxxxxxxxxxx.json
파일의 이름을credentials.json
으로 변경한다. -
아래 코드를 붙여넣는다.
import os from googleapiclient.discovery import build from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials SCOPES = ['https://www.googleapis.com/auth/spreadsheets'] # 아래 코드는 구글 공식 페이지에서 가져왔다. # https://developers.google.com/sheets/api/quickstart/python def init_token(): creds = None if os.path.exists('token.json'): creds = Credentials.from_authorized_user_file('token.json', SCOPES) if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES) creds = flow.run_local_server(port=0) with open('token.json', 'w') as token: token.write(creds.to_json()) return creds service = build('sheets', 'v4', credentials=init_token()) # 구글 시트에 입력할 값들 # 리스트 안에 리스트 형태로 표현하며, 내부 리스트가 각 행을 의미한다. values = [ ['ID', 'Name', 'Type', 'PrivateIP', 'PublicIP', 'State', 'AZ'], ] # 새로운 Spreadsheet 생성 방법 # spreadsheet = { # 'properties': { # 'title': '새 시트' # } # } # sheet = service.spreadsheets().create(body=spreadsheet).execute() # SPREADSHEET_ID = sheet.get('spreadsheetId') # 이 예제에서는 이미 생성되어 있는 문서의 spreadsheetId 값을 이용하였다. # 기존에 생성되어 있던 문서의 spreadsheetId를 확인하는 방법은 위에 이미 설명했다. SPREADSHEET_ID = '1xuAfx7X9g2Nd5DikD-sLM4iA_lk45H0AKYJ0BS0sqfA' # Sheet1 이라는 이름의 sheet에 값을 업데이트 하였다. service.spreadsheets().values().update( spreadsheetId=SPREADSHEET_ID, range="Sheet1!A1:Z{}".format(len(values)), valueInputOption='RAW', body={'values': values} ).execute()
-
위 코드를 최초 실행 시 아래와 같이 브라우저가 자동으로 실행되며 어디서 많이 본 화면이 뜬다. (
구글계정으로 로그인하기
를 해본적이 있다면, 해당 절차와 동일하다고 생각하면 된다.) 이 절차는 로컬에서만 가능하기 때문에 원격으로 접속한 리눅스 서버에서 이 코드를 실행시키면 에러가 발생한다. 원격에서 실행하고 싶으면 로컬에서 우선 한번 실행한 후, 그 뒤에 생성된token.json
파일을 원격 서버로 복사한 후 실행하자. -
성공이다. 이제 AWS 코드와 구글 시트 코드를 하나로 합쳐보자.
AWS + 구글 시트 API
일단 코드부터 보자.
import os
import boto3
from botocore.config import Config
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
AWS_ACCESS_KEY='AKIAYAMQOPDIXEGGW5PB'
AWS_SECRET_KEY='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'
client = boto3.client('ec2',
aws_access_key_id=AWS_ACCESS_KEY,
aws_secret_access_key=AWS_SECRET_KEY,
config=Config(region_name='ap-northeast-2'),
)
# running 상태인 ec2 인스턴스 목록 얻기
response = client.describe_instances(
Filters=[
{'Name': 'instance-state-name', 'Values': ['running']}
]
)
ec2_list = []
for obj in response['Reservations']:
for tag in obj['Instances'][0]['Tags']:
if tag['Key'] == 'Name':
name = tag['Value']
ec2_list.append([
obj['Instances'][0]['InstanceId'],
name,
obj['Instances'][0]['InstanceType'],
obj['Instances'][0].get('PrivateIpAddress'),
obj['Instances'][0].get('PublicIpAddress'),
obj['Instances'][0]['State']['Name'],
obj['Instances'][0]['Placement']['AvailabilityZone'],
])
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
def init_token():
creds = None
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
with open('token.json', 'w') as token:
token.write(creds.to_json())
return creds
service = build('sheets', 'v4', credentials=init_token())
values = [
['ID', 'Name', 'Type', 'PrivateIP', 'PublicIP', 'State', 'AZ'],
]
values.extend(ec2_list)
SPREADSHEET_ID = '1HLpvoAc2Cntxy1bMBu0ZaxTbmhz5Z7Ek_uH4K9RTiRE'
service.spreadsheets().values().update(
spreadsheetId=SPREADSHEET_ID,
range="Sheet1!A1:G{}".format(len(values)),
valueInputOption='RAW',
body={'values': values}
).execute()
결과는… 두둥! 성공이다.
추가 기능들
위 예제는 아주 기본적인 내용만을 다루고 있는데, 필자의 경우 아래와 같은 상황이라 조금 더 내용을 추가하였다.
aws 계정별/지역별 접속 방법
사용 중인 AWS Root 계정이 n개이고 각 계정의 모든 region에 대한 전수 조사가 필요하다. 이럴 때는 아래와 같이 가능하다.
사용자 홈 폴더/.aws/
폴더 하위에 config
, credentials
파일을 만들고 아래와 같이 파일 내용을 적어둔다.
(사실 awscli
를 설치 후 config 설정을 하면 아래 파일들이 자동으로 생성된다. 자세한 내용은 이 글을 참고하자.)
-
~/.aws/config
[default] region = ap-northeast-2 output = json [profile dev] region = ap-northeast-2 output = json [profile prod] region = ap-northeast-2 output = json
-
~/.aws/credentials
: 위config
파일에 작성한 profile 이름과 해당 profile의access key
,secret access key
정보를 적는다.[default] aws_access_key_id = AAAAAAAAAPDIV4INFU65 aws_secret_access_key = 9x3Yvt88+OmSSSSSSSSSSSSSSSSS [dev] aws_access_key_id = AAAAAAAAAL4246XEJPVU aws_secret_access_key = zpY22349347SSSSSSSSSSSSSSSSS [prod] aws_access_key_id = AAAAAAAAA2323YKSSPP aws_secret_access_key = 135-asgasrSSSSSSSSSSSSSSSSSS
그리고 코드에서 아래와 같이 사용한다.
REGION = [
"us-east-2",
"us-east-1",
"us-west-1",
"us-west-2",
"sa-east-1",
"ap-northeast-2",
"ap-south-1",
"ap-northeast-3",
"ap-southeast-1",
"ap-southeast-2",
"ap-northeast-1",
"ca-central-1",
"eu-central-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"eu-north-1",
]
profile_list = ['dev', 'prod']
for profile in profile_list:
for region in REGION:
session = boto3.session.Session(
profile_name=profile,
region_name=region
)
client = session.client('ec2')
response = client.describe_instances()
sheet 추가 및 이름 변경
구글 시트 생성 시 default sheet 이름은 Sheet1
이다. 이 이름을 변경하는 방법과 sheet를 추가하는 방법은 아래 코드를 참조하자.
service.spreadsheets().batchUpdate(spreadsheetId=SPREADSHEET_ID, body={
'requests': [
{
'updateSheetProperties': {
'properties': {
'title': 'EC2'
},
'fields': 'title'
}
},
{
'addSheet': {
'properties': {
'title': 'RDS'
}
}
}
]
}).execute()
마치며
코드는 얼마 안되는데, 코드를 사용하기 위한 준비과정이 훨씬 길었다… 구글 API 등록은 이제 눈감고도 할 것 같…
Previous post
[Python] MS Word 파일 수정하기Next post
[명령어] tcpdump