boto3에서 거의 대부분의 기능은 AWS API를 사용하고, 자원에 대한 자격을 증명하기 위해 AWS IAM에서 얻어낼 수 있는 AWS access key IDAWS secret access key, 또는 임시 자격 증명을 위한 aws_session_token을 사용한다. 일반적으로 awscli 등의 도구를 통해 ~/.aws/credentials에 자격 증명 데이터를 설정하고, boto3는 여기에 접근해 자격 증명을 진행한다. 그러나 boto3를 사용하는 어플리케이션에서 여러 개의 자격 증명을 동시에 수행해야 하는 경우가 생길 수 있다. 데이터를 수집하여 원하는 곳으로 서빙해주는 B2B 서비스인 Segment의 Amazon S3 Integration 기능을 예로 들 수 있다.

다행히도, boto3는 자격 증명 데이터를 얻어낼 때까지 여기저기에 순서대로 접근한다.

client, resource 함수에서 자격 증명 데이터를 매개변수로 전달

client, resource, Session 등을 얻을 때 aws_access_key_id, aws_secret_access_key, aws_session_token이라는 문자열 타입의 선택 인자를 사용할 수 있다. 시스템 설정을 무시하고, 자격 증명 데이터를 직접 전달하고 싶을 때 대부분 이 방법을 사용한다.

환경 변수

client, resource, Session에 자격 증명 정보가 인자로 전달되지 않으면, 환경 변수를 확인한다.

AWS_ACCESS_KEY_ID

AWS 계정의 access key

AWS_SECRET_ACCESS_KEY

AWS 계정의 secret key

AWS_SESSION_TOKEN

AWS 계정의 세션 키

공유 자격 증명(Shared credentials) 파일

그 다음부턴 파일들에 접근하기 시작한다. 공유 자격 증명 파일의 기본 위치는 ~/.aws/credentials이고, AWS_SHARED_CREDENTIALS_FILE 환경 변수를 설정하여 위치를 변경할 수 있다. credentials는 .ini 형식의 파일이고, 각 섹션마다 자격 증명을 위한 세 가지의 변수를 지정할 수 있다.

섹션 각각을 profile이라고 부른다. session을 생성할 때 AWS_PROFILE 환경 변수를 설정하거나 profile_name 인자에 이름을 전달하여 어떤 profile을 사용할 지 명시할 수 있다. 여기서 session은 client나 resource를 생성할 수 있도록 자격 증명에 대한 '상태'를 저장하고 있는 객체다.

별도로 명시하지 않으면, [default] profile을 사용한다. 보통은 이렇게 credentials를 사용하는 방식을 쓰는 것 같다.

AWS Config 파일

기본 위치는 ~/.aws/config이고, AWS_CONFIG_FILE 환경 변수를 설정하여 위치를 변경할 수 있다. config 파일은 credentials 파일과 동일하게 작성된다. 유일한 차이점은, profile 섹션을 [profile [name]] 포맷으로 표현한다는 것이다.

AssumeRole을 호출시키기

AWS_CONFIG_FILE 환경 변수에 명시된 파일이나, 기본 경로인 ~/.aws/config 파일에서 role에 대한 정보를 명시하여, boto3가 자동으로 AWS STS에 AssumeRole을 호출하도록 할수도 있다. role_arn, source_profile, role_session_name 등의 파라미터가 필요하다.

boto2 config

그 후에는, boto2 config 파일에서 자격 증명을 로드한다. BOTO_CONFIG 환경 변수가 나타내는 경로의 파일을 먼저 검사하고, 그렇지 않으면 /etc/boto.cfg~/.boto 파일을 검사한다. [Credentials] 섹션만 사용한다.

IAM Role

Amazon EC2에서 실행 중이며 위의 방법들로 자격 증명을 찾지 못한 경우, boto3는 EC2 인스턴스의 메타 데이터 서비스에서 자격 증명을 로드한다. EC2 인스턴스를 시작할 때 사용할 IAM role만 지정하면, 별도의 명시적인 구성을 하지 않아도 boto3가 알아서 인스턴스의 자격 증명을 사용한다.

Best Practice

boto3는 자격 증명 정보를 얻기 위해, 결론적으로 아래의 순서를 따른다.

  1. client/resource/Session에 전달되는 자격 증명 정보
  2. 환경 변수
  3. AWS_SHARED_CREDENTIALS_FILE에 명시된 파일 또는 ~/.aws/credentials에 접근
  4. AWS_CONFIG_FILE에 명시된 파일 또는 ~/.aws/config에 접근
  5. AWS_CONFIG_FILE에 명시된 파일 또는 ~/.aws/config에 접근하여 role 데이터(arn 등)로 AWS STS에 AssumeRole
  6. BOTO_CONFIG에 명시된 파일에 접근
  7. /etc/boto.cfg에 접근
  8. ~/.boto에 접근
  9. EC2 인스턴스의 IAM role 설정에 접근

EC2 인스턴스에서 boto3를 사용하는 경우 IAM role을 사용하는 것이 좋고, 그렇지 않은 경우라면 공유 자격 증명 파일(~/.aws/credentials)을 쓰는 것이 좋다고 한다.

'Python 계열 > boto3' 카테고리의 다른 글

boto3.resource와 boto3.client의 차이  (1) 2019.02.12
S3에서 객체의 용량을 알아내기  (0) 2019.02.12

이번엔 서비스 운영을 위한 메인 데이터베이스를 결정하고, AWS 클라우드 위에서 해당 데이터베이스 엔진을 사용하는 인스턴스를 하나 띄워보자. 가상 컴퓨팅 환경 하나 단위를 AWS에서는 인스턴스라고 부른다.

도입 이유

데이터베이스는 엑셀을 떠올리면 된다. 데이터베이스의 가장 중요한 특성은 구조화된 데이터를 관리한다는 점이다. 'ID', '비밀번호'처럼 컬럼에 따라 데이터를 정리하고, 어떤 기준을 통해 데이터를 정렬하고, 필터링할 수 있다. 굳이 엑셀 안 쓰고 데이터베이스를 쓰는 이유는, 데이터베이스라는 것 자체가 데이터를 추가/변경/삭제/조회하는 작업이 더 구조화되어 있고 빠르기 때문이다. 잘 모르겠다면 이고잉님의 강의인 데이터베이스란?을 보고 오자.

이 세상엔 수많은 형태와 종류의 데이터베이스가 있다. 형태로 치자면 RDB(Relational Database)NoSQL(Not only SQL)로 나눌 수 있고, 종류로 치자면 빅데이터 쿼리를 분산 처리하기 위해 개발된 PrestoDB, key-value 형태의 구조로 캐싱 용도로 자주 쓰이는 redis나 memcached, 시계열성 데이터를 저장하는 데에 특화되어 있는 InfluxDB같은 특별한 제품들이나 MySQL, PostgreSQL 등 전형적인 RDBMS가 있을 수 있다.

위에서 이야기했듯 이번 의사결정은 서비스 운영을 위한 메인 데이터베이스를 결정하는 것이라, PrestoDB, druid, InfluxDB같이 특별한 목적을 위해 만들어진 데이터베이스를 사용하진 않을 것이다.

의사결정 - RDB vs NoSQL

배경과 요구사항

  • 모든 스키마가 고정되어 있다.
  • 엄청나게 빠른 속도를 요하진 않는다.
  • 되도록이면 AWS가 관리형으로 제공하는 인프라에서 사용할 수 있어야 한다. EC2에서 데이터베이스 서버를 직접 관리해야 하는 일이 없도록 만들고 싶다.

선택지

  • RDB
  • NoSQL

의사결정

RDB를 선택하겠다. 그 이유는,

  • 'NoSQL은 schemaless여서 더욱 유연한 형태로 데이터를 저장할 수 있다'고 하지만, 우리가 개발하려는 것을 포함해 대부분의 서비스는 스키마가 유동적인 경우가 거의 없다.
  • NoSQL은 'scale out(접속된 서버의 대수를 늘려 처리 능력을 향상시키는 것)이 가능해서 scale up만 가능한 RDB에 비해 단일 장애 지점도 없고 비용도 적고 클러스터에서 잘 동작하도록 만들어져 있다'라며 퍼포먼스와 장애 대응에 관한 메리트를 어필하곤 하는데, RDB에서도 master-slave 모델을 사용하면 이런 문제들을 충분히 커버할 수 있다. 이는 읽기 전용 인스턴스(slave)를 만들어 데이터를 복제시키는 것인데, 이정도만 해도 웬만한 수준의 서비스 트래픽을 다 감당할 수 있다고 판단했다. master가 죽은 경우 slave가 master로 승격하는 구조를 만들면 장애 복구도 잘 되고 말이다.
  • 평균적으로 NoSQL이 RDB보다 빠른 것이 맞고, 동일한 비용을 사용했을 때 퍼포먼스로 치면 웬만하면 NoSQL이 가성비가 좋다. 그러나 이건 우리가 어떤 RDB 제품을 사용하느냐에 따라 다르다.
  • NoSQL 데이터베이스 서버를 AWS에서 운영하려면, 대부분의 경우 EC2 위에서 직접 돌려줘야 한다. MongoDB의 경우 최근에 출시된 DocumentDB를 사용해볼 수 있는데, 아시아 쪽에선 아직 서비스가 오픈되지 않았다. 결국은 관리 포인트가 늘어날 것이며 이를 감당하기 어려운 상태다.
  • 나도 NoSQL을 좋아하지만, '많이 써봤으니 쓰기 편해서'라는 이유밖에 얘기하지 못 하겠다.

의사결정 - 데이터베이스 호스팅 위치

배경과 요구사항

  • 우리는 RDB를 사용하기로 했다.
  • EC2에서 직접 데이터베이스 서버를 관리하는 것은 설정, 패치, 백업과 같은 시간 소모적인 관리 작업이 많아지며 안정성을 보장하기 어렵다. 따라서 AWS에 인프라 관리를 양도하는 관리형 서비스가 있다면 이를 사용하고자 한다.

선택지

데이터베이스 서버 관리를 직접 하느냐, AWS에게 관리를 양도하느냐의 차이다.

  • EC2
  • RDS(Relational Database Service)

의사결정

RDS를 선택하겠다. 그 이유는,

  • 동일한 워크로드(하드웨어 요구량)를 기준으로 했을 때 EC2의 비용이 더 적을 수는 있겠으나, 데이터베이스 서버를 직접 운영하는 것은 정말 큰 관리 포인트다. 백업과 복구 정도만 생각해도 아득하다. 이런 건 그냥 돈 좀 더 내는 게 낫다고 생각한다(물론 여기선 어차피 free tier를 쓸테니 이런게 딱히 상관은 없겠지만, 의사결정의 이유니까 알아두고 넘어가자).
  • 관리형 서비스가 없는 데이터베이스를 선택한 경우 어쩔수 없이 EC2에 올려야겠지만, RDS는 대부분의 메이저한 RDB 엔진을 지원하며 그 외의 RDB 중에서는 굳이 EC2에서 직접 프로비저닝할 정도의 메리트가 있는 것도 딱히 없다.

의사결정 - 데이터베이스 엔진

배경과 요구사항

  • 우리는 Amazon RDS를 사용하기로 했으므로, RDS에서 사용 가능한 데이터베이스 엔진을 선택지로 두어야 한다.
  • 조직이 MySQL에 익숙한 상태다.
  • Python에서 주로 쓰는 ORM 라이브러리인 SQLAlchemy나 Peewee에서 지원하는 데이터베이스여야 한다.
  • 서비스 초기이므로, 한동안이라도 비용이 발생하지 않는 방향이면 더 좋다.

선택지

  • Aurora
  • MySQL
  • MariaDB
  • PostgreSQL
  • Oracle
  • SQL Server

의사결정

MySQL을 선택하고, 추후 Aurora로 마이그레이션하겠다. 그 이유는,

  • Aurora는 AWS가 개발한 RDBMS인데, 클라우드에서 잘 구동되도록 설계되어 비교적 빠르고 비용도 싸다. 하지만 다시 역으로 비용 문제가 있는데, Aurora를 RDS에 띄우는 경우 free tier가 지원되지 않는다.
  • 조직이 MySQL에 익숙하며, 메인 데이터베이스로 MySQL을 사용하는 것에 딱히 걸리는 점이 없다.
  • 어차피 AWS 가입 후 1년이 지나면 free tier가 지원되지 않으므로 비용 효율이 좋은 엔진으로 마이그레이션을 고려하게 될텐데, Aurora는 MySQL과 호환되며 MySQL보다 3~4배 정도 빠르므로 그 대상이 된다. 사실 Amazon 인프라에선 Aurora만한 RDB가 없는데 free tier 때문에 잠시 동안만 MySQL에 머물러 있는 것이라고 생각하면 된다.

작업 - 인스턴스 시작

Amazon RDS 자습서를 보고 따라하자.

SQLAlchemy에서 ORM을 사용하기 위해, 스키마를 class로 정의하는 방법이 있다.

sqlalchemy.declarative_base 함수를 통해 Base를 얻고, 이를 상속받는 형태로 모델을 구현한다. 클래스의 내부에는 __tablename__이라는 클래스 필드로 테이블 이름을 명시하며, Column 클래스의 생성자에 sqlalchemy.types 모듈 하위에 구현되어 있는, 타입을 나타내는 클래스를 넘겨 Column 타입의 객체들로 컬럼을 명시한다. 타입 정보를 생성자에 전달할 때, 클래스 자체를 넘겨도 되고 클래스의 인스턴스를 넘겨도 된다. Column(Integer)Column(Integer())가 동일하다는 것이다. sqlalchemy의 타입 클래스, SQL DDL에서의 타입, Python의 빌트인 타입 간 매핑은 다음과 같다. 자주 사용하는 타입 위주로 작성했다.

  • sqlalchemy.types.BigInteger - BIGINT - int
  • sqlalchemy.types.SmallInteger - SMALLINT - int
  • sqlalchemy.types.Integer - INT - int
  • sqlalchemy.types.Boolean - BOOLEAN or SMALLINT or TINYINT - bool
  • sqlalchemy.types.DateTime - TIME or TIMESTAMP - datetime.datetime
  • sqlalchemy.types.Float : FLOAT or REAL - float | asdecimal 인자를 True로 설정하면, decimal.Decimal 타입으로 다뤄진다.
  • sqlalchemy.types.PickleType - BLOB or TINYBLOB or MEDIUMBLOB or LONGBLOB - pickle로 직렬화 가능한 모든 객체 | list나 dict를 관리할 때 편리하게 사용할 수 있다.
  • sqlalchemy.types.String - CHAR or VARCHAR or TEXT - str
  • sqlalchemy.types.Text - CLOB or TEXT - str

BigInteger와 BIGINT, Boolean과 BOOLEAN, Text와 TEXT, ...

SQLAlchemy로 모델을 정의하다 보면, uppercase된 알파벳으로만 정의된 타입을 확인할 수 있다. SQLAlchemy 문서에서는 BigInteger, Boolean, Text 등을 Generic Type이라 부르고, BIGINT, BOOLEAN, TEXT 등을 SQL Standard and Multiple Vendor Type이라고 부른다.

Generic Type

BigInteger, Boolean, Text, DateTime, Float, Integer, ...

'Python 타입'의 데이터를 읽고, 쓰고, 저장할 수 있는 column을 명시한다. SQLAlchemy는 Generic Type의 컬럼에 대해 CREATE TABLE문을 만들어낼 때, 대상 데이터베이스에서 사용할 수 있는 최상의 타입을 알아서 선택한다. VARCHAR로 정의해 뒀는데 데이터베이스가 VARCHAR를 지원하지 않는다면, 그를 대체할 수 있는 타입으로 변경하여 DDL을 작성한다. 대충 써두면, 알아서 테이블을 생성해준다는 의미다. 코드 레벨에서 조금 더 편하다.

SQL Standard and Multiple Vendor Type

BIGINT, BOOLEAN, TEXT, BLOB, CHAR, CLOB, ...

SQLAlchemy에서 여기에 속하는 타입의 컬럼을 통해 CREATE TABLE문을 만들 때는, 데이터베이스 엔진에 상관 없이 컬럼의 타입을 항상 동일하게 만들어낸다. SQLAlchemy가 원하는 타입으로 정확한 DDL을 작성하도록 만들 때 유용할 수 있으나, Generic type과 달리 이들은 모든 데이터베이스에서 잘 작동한다는 보장이 없다. 따라서, 대충 'VARCHAR'라고 했다가 데이터베이스가 VARCHAR를 지원하지 않으면 DDL에 실패한다.

Vendor-Specific Type

mysql.BIGINT, mysql.BINARY, postgresql.REAL, oracle.VARCHAR2, ...

'벤더별로 명시되어 있는 타입'을 의미하는데, 여기서 벤더는 MySQL, PostgreSQL 등을 떠올리면 된다. sqlalchemy.dialects 패키지 하위에 데이터베이스별로 패키지가 존재하고, 여기에 명시되어 있는 타입을 사용하는 방식이다.


'Python 계열 > SQLAlchemy' 카테고리의 다른 글

Query 객체의 WHERE절 작성  (0) 2019.02.12
text()  (0) 2019.02.12
Column.like, Column.ilike, not_, ~expr  (0) 2019.02.12
특정 컬럼만 SELECT  (0) 2019.02.12
aliasing과 함수  (0) 2019.02.12

+ Recent posts