FastAPI + Streamlit + DocumentDB 구축으로 간이 고객 정보 입력 폼 만들기(1)
시작하며
업무 상 AWS를 사용하다보면 고객으로부터 서비스 및 구성에 대한 상담 요청을 받게 됩니다.
제 담당은 인프라이지만, 애플리케이션을 모르면 본질적인 부분까지 이해하기는 어렵다고 줄곧 생각 해 왔습니다. 그래서 이번 1주일 동안 시간을 내어 FastAPI와 Streamlit라는 프레임워크에 대해 공부 하였는데, 이 내용을 공유 하고자 합니다.
이번 게시글에서는 ‘FastAPI + Streamlit + DocumentDB 구성’으로 간단한 고객 정보 입력폼을 만들어 보고, Document DB와의 데이터 연계를 시험 해 보고자 합니다.
대상자
- AWS 사용 중인 분
- DynamoDB나 DocumentDB등 데이터 베이스의 움직임을 확인 해 보고 싶지만, 코딩 능력이 없어 검증이 불가능 한 분
- 스킬을 넓혀가고 싶지만, 프론트엔드까지만 손댈 수 있는 분
- MongoDB 호환의 DocumentDB를 체험해 보고 싶은 분
제 경우, 코딩 경험이 별로 없기 때문에 개발 경험이 많으신 분이 보시면 비효율적으로 코드를 쓰는 곳이 있을 수도 있습니다. 미리 감안하고 봐 주시면 감사하겠습니다.
개요
(★)이 붙어있는 섹션은 핸즈온으로 진행 하는 항목입니다.
- 이번 핸즈온 구성
- EC2에 FastAPI, Streamlit를 인스톨(★)
- FastAPI 간이 테스트 : GET리퀘스트 (★)
- Streamlit으로 입력 폼 생성 (★)
- DocumentDB구축(접속 테스트) MongoDB 움직임 확인 (★)
- 입력 폼 ⇒ DocumentDB
- Streamlit 으로 고객 정보 페이지 생성
- DocumentDB ⇒ 고객 정보 페이지
- 움직임 확인
각종 공식 문서를 참조하면서 진행 해 보겠습니다. 이번에는 (5)까지 진행하여 토대를 만들어 가고자 합니다.
사전 준비
- AWS 어카운트 생성
- AdministratorAccess를 부여한 IAM유저 생성
1. 이번 핸즈온 구성/사용 데이터
[구성]
- EC2
- DocumentDB
[사전 준비]
- VPC
- Public Subnet(Internet Gateway에 루트 설정 있음) / Private Subnet
2. EC2에 FastAPI, Streamlit를 인스톨
EC2생성
- AMI : Amazon Linux2
- 인스턴스 타입 : t2.micro
- Public Subnet에 배치
- 퍼블릭 IP 자동 할당 유효화
- 보안 그룹 (SSH/커스텀 TCP = 8000번 포트에서 인바운드 통신 허가)
※ 정보 표시 때문에 소스를 0.0.0.0으로 하고 있지만, 소스는 좁히는 편이 좋습니다.
상기 설정으로 EC2 인스턴스를 생성 합니다.
생성이 완료 되면 SSH접속이 되는 지 확인 합니다.
FastAPI와 Streamlit의 인스톨
- EC2에 SSH로그인
- Python 및 패키지 관리 시스템 인스톨 상황 확인
$ sudo yum update -y
$ python3 — version
$ pip3 — version
- FastAPI 및 Uvicorn 인스톨
$ pip3 install fastapi
$ pip3 install uvicorn
$ pip3 list
API 구축을 위한 Web프레임 워크인 ‘FastAPI’와 Python용 ASGI Web서버인 Uvicorn을 인스톨 합니다.
- Streamlit 인스톨
$ pip3 install streamlit
$ pip3 list
Python으로 프론트엔드 부분을 구축 가능한 프레임워크 ‘Streamlit’을 인스톨 합니다.
3. FastAPI 간이 테스트 : GET리퀘스트 보내기
FastAPI를 살펴 봅시다.
- main.py생성
$ mkdir fast-app
$ cd fast-app
$ vi main.py
여기에서 fast-app을 작업폴더로서 작업을 진행 해 보겠습니다.
main.py
#필요한 기능을 가져오기
from fastapi import FastAPI
import uvicorn
import random#FastAPI인스턴스 생성
app = FastAPI()#/test에 대해GET메소드가 실행 된다면 정의한test함수를 실행
@app.get(“/test”)
async def test():
return {
“id”: random.randint(1,10), #지정한 범위 안에서 랜덤한 수를 되돌려 준다
“name”: “name”,
“address”: “Tokyo”,
“age”: 30
}#python main.py에서 파일을 불러올 때 Uvicorn서버를 기동
if __name__ == “__main__”:
uvicorn.run(app, host=”0.0.0.0", port=8000)
여기에서 하고 싶은 것은 ‘지정한 URL에 대해 GET요청을 하면 함수가 실행 되어 정의한 4가지 값이 돌아온다’는 구조를 만드는 것 입니다.
구체적인 기술 방법은 공식 사이트를 참고 해 주세요.
- FastAPI는 API기능을 제공하는 Python클래스이며 가져오기를 해야 합니다.
- FastAPI는 클래스 인스턴스를 생성해야 하며, 추후 vicorn이 참조 합니다.
- API를 구축할 때는 HTTP 메소드를 사용하여 특정 액션을 실행 합니다.
POST: 데이터 생성
GET: 데이터 읽기
PUT: 데이터 갱신
DELETE: 데이터 삭제
- @app.get(“※패스”)은 아래의 함수가 하기 요청 처리를 담당하는 것을 FastAPI에 전합니다. /test에 Get리퀘스트가 진행 되면 함수가 실행 됩니다.
- 함수의 결과, 정의한 4가지 데이터를 돌려줍니다.
- 코드에서 직접 Uvicorn서버를 실행 하기 때문에 직접 Python프로그램 (FastAPI프로그램)을 불러낼 수 있습니다.
- Uvicorn인수에 FastAPI 인스턴스를 지정합니다. 또 호스트나 포트의 지정도 가능합니다.
여기까지 썼다면, main.py를 저장합니다.
fast-app폴더에서 하기 커맨드를 실행 해 봅시다.
$ python3 main.py
Uvicorn이 기동합니다.
다음으로 브라우저에서 검색을 URL에 액세스 해 봅시다.
http://[※EC2의 IP4어드레스]:8000
상기의 [※EC2의 IP4어드레스]부분은 자신의 EC2 퍼블릭 IP어드레스로 바꿔 넣어 주세요.
결과가 어떤가요?
URL에 GET리퀘스트를 진행했으므로 함수에 정의한 4가지 값이 되돌아 올 것 입니다.
그런데 돌아오지 않은 듯 하군요.
그건 /test에 액세스 하지 않았기 때문입니다.
http://[※EC2의 IP4어드레스]:8000/test
에 액세스 합니다.
4가지 값이 되돌아 왔습니다.
이렇게 간이로 API 테스트가 가능합니다.
끝나면 SSH로그인 하고 있는 서버로 돌아와 CTRL+C로 Uvicorn을 종료 시킵니다.
4. Streamlit으로 입력 폼 생성
Streamlit으로 입력 폼을 만듭니다.
먼저 완성 이미지를 공유 합니다.
Streamlit을 사용하면 심플한 입력 폼을 Python코드로 구성 할 수 있습니다.
사이드바에서 페이지를 선택할 수 있도록 되어 있습니다.
INPUT FORM을 선택하면 인풋 폼이 나타납니다.
입력 폼은 ‘텍스트 입력’, ‘라디오 버튼’, ‘체크 박스’로 Streamlit에서 표준으로 준비된 Input widgets를 이용하고 있습니다. python코드 1줄로 구성 가능합니다.
Send버튼을 누르면 풍선이 나타나고 입력한 정보가 아래와 같이 표시 됩니다.
완성 이미지도 확인 했고, 바로 만들어 봅시다 !
먼저 fast-app폴더 내에 새롭게 파일을 생성 합니다.
$ vi app.py
app.py
#가져오기
import streamlit as st#사이드 바와 선택 박스
page = st.sidebar.selectbox(‘Choose your page’, [‘INPUT FORM’, ‘RESULT’])#정보 입력 후 함수
def update_page():
st.balloons()
st.markdown(‘# Thank you for information’)
st.json(customer_information)#혹시 선택 박스에서선택한 페이지가INPUT FORM이라면
if page == ‘INPUT FORM’:
st.title(‘INPUT FORMATION’)#각종 입력 폼
with st.form(key=’customer’):
customer_name: str = st.text_input(‘NAME’, max_chars=15)
customer_age: int = st.text_input(‘AGE’, max_chars=3)
customer_gender = st.radio(“GENDER”,(‘MEN’, ‘Women’))
customer_address = st.selectbox(‘COUNTRY’,
(‘Hokkaido’, ‘Tohoku’, ‘Kanto’, ‘Chubu’, ‘Kinki’, ‘Kansai’, ‘Chugoku’, ‘Shikoku’, ‘Kyusyu’, ‘Okinawa’))
customer_mail: str = st.text_input(‘Mail Address’, max_chars = 30)#폼에 입력 결과를 정리
customer_information = {
‘customer_name’: customer_name,
‘customer_age’ : customer_age,
‘customer_gender’: customer_gender,
‘customer_address’: customer_address,
‘customer_mail’: customer_mail}
#폼에 입력 결과를 송신
submit_button = st.form_submit_button(label=’Send’)#submit_button가 송신 되면 함수를 실행
if submit_button:
update_page()
Streamlit API레퍼런스에 코드가 기재 되어 있습니다.
‘구현 하고 싶은 것’을 실현할 수 있는 기능을 레퍼런스 내에서 찾아, 그 코드를 참조하는 것이 좋을 것 같습니다.
대략적인 코드의 내용을 설명 드리겠습니다.
#가져오기
import streamlit as st#사이드바와 선택 박스
page = st.sidebar.selectbox(‘Choose your page’, [‘INPUT FORM’, ‘RESULT’])・・・・・・#혹시 선택 박스에서 선택한 페이지가 INPUT FORM이라면
if page == ‘INPUT FORM’:
st.title(‘INPUT FORMATION’)
selectbox는 페이지를 바꾸기 위해 사용합니다.
if문을 사용하여 선택한 페이지마다 표시하는 페이지를 나눌 수 있습니다.
코드는 레퍼런스에 나와 있습니다.
sidebar를 더하여 선택 박스가 사이드바의 위치에 표시 되도록 합니다.
#혹시 선택 박스에서 선택한 페이지가INPUT FORM이라면
if page == ‘INPUT FORM’:
st.title(‘INPUT FORMATION’)#각종 입력 폼
with st.form(key=’customer’):
customer_name: str = st.text_input(‘NAME’, max_chars=15)
customer_age: int = st.text_input(‘AGE’, max_chars=3)
customer_gender = st.radio(“GENDER”,(‘MEN’, ‘Women’))
customer_address = st.selectbox(‘COUNTRY’,
(‘Hokkaido’, ‘Tohoku’, ‘Kanto’, ‘Chubu’, ‘Kinki’, ‘Kansai’, ‘Chugoku’, ‘Shikoku’, ‘Kyusyu’, ‘Okinawa’))
customer_mail: str = st.text_input(‘Mail Address’, max_chars = 30)#폼 입력 결과를 정리
customer_information = {
‘customer_name’: customer_name,
‘customer_age’ : customer_age,
‘customer_gender’: customer_gender,
‘customer_address’: customer_address,
‘customer_mail’: customer_mail}
#폼에 입력 결과를 송신
submit_button = st.form_submit_button(label=’Send’)
입력 폼 생성 부분입니다.
제어 플로우인 st.form은 송신 버튼으로 요소를 정리하는 폼을 생성 합니다.
즉, 여러 타입의 입력 폼 위젯 값을 총괄하여 제출 버튼을 클릭 하면 정보를 Streamlit에 송신 됩니다.
텍스트 입력 , 라디오 버튼, 선택 박스 등 각종 위젯의 사용법은 레퍼런스를 참조 해 주세요.
각 입력 폼에 입력된 정보를 customer_information에 정리 합니다.
#정보 입력 후 함수
def update_page():
st.balloons()
st.markdown(‘# Thank you for information’)
st.json(customer_information)・・・・・・・#폼에 입력 결과를 송신
submit_button = st.form_submit_button(label=’Send’)#submit_button가 송신 되면 함수를 실행
if submit_button:
update_page()
제출 버튼이 송신 되면 정의한 update_pate함수를 실행 합니다.
함수의 내용은
- st.balloons를 사용하여 풍선 애니메이션을 실행
- 텍스트 표시
- customer_information에 정리한 정보를 JSON형식으로 일괄 표시
위와 같은 흐름으로 입력 폼 페이지를 표시하는 코드를 넣었습니다.
그러면 코드를 실행 해 보겠습니다.
$ streamlit run app.py
포트 번호를 확인하여 EC2 보안 그룹의 인바운드를 허가 합니다.
※ Streamlit을 기동하면 터미널에 URL이 표시 됩니다. 거기에서 포트 번호를 확인 해 주세요.
그 후 브라우저에서 URL에 액세스 합니다.
http://[※EC2퍼블릭IPv4어드레스]:[※포트 번호]
간이 입력 폼이 생성 되었을텐데, 일단 프론트엔드의 작업을 중단 합니다.
이번에 입력된 데이터를 데이터베이스에 쓰기 처리 해야 합니다.
5. DocumentDB의 구축 (접속 테스트) MongoDB 움직임 확인
AWS 구축으로 돌아가 보겠습니다.
이번에 사용할 데이터 베이스는 MongoDB호환 AWS 서비스인 DocumentDB를 사용 합니다.
・Amazon Aurora와 비슷한 클러스터 구성
⇒ compute(쿼리를 실행하는 노드)와 스토리지(데이터를 넣음)를 분리하여 개별 최적 스케일을 진행 할 수 있다.・compute
⇒ compute에서는 읽기 쓰기 가능한 프라이머리 인스턴스와 읽기만 가능한 리드 레플리카 구성으로 되어있음
⇒ 리드 레플리카는 15대까지 늘리는 것이 가능하여 읽기 성능을 매우 향상 시킬 수 있다
⇒ 프라이머리 인스턴스 장애 시에는 다른 리드 레플리카가 마스터로 승격하여 페일오버 할 수 있다. 페일오버가 발생 해도 항상 마스터를 참조하는 클러스터 엔드 포인트를 준비 하고 있다
⇒ 클러스터 엔드포인트/리더 엔드포인트가 있기 때문에 애플리케이션 측면에서 본다면 수신처를 바꾸지 않고 읽기 쓰기를 계속 할 수 있다.・스토리지
⇒ 스토리지는 3개의 AZ로 6개가 복사 된다. 데이터 장애에 강하다.
⇒ 스토리지의 자동 확장에도 대응
그러면 구축 해 봅시다.
DocumentDB용 보안 그룹 생성
EC2에서 인바운드 통신을 허가합니다.
인바운드 통신 허가 (커스텀 TCP, 포트:27017, 소스: EC2 보안 그룹)
서브넷 그룹 생성
- VPC: EC2와 같은 VPC
- 서브넷 : 프라이빗 서브넷을 복수 지정
DocumentDB클러스터 생성
(AWS매니지먼트 콘솔 상 조작) DocumentDB ⇒ 클러스터 ⇒ 생성
- 클러스터 식별자 : 임의
- 인스턴스 클래스: db.t3.medium
- 인스턴스 수 : 1
- 인증용 마스터 유저명/마스터 패스워드 : 임의
- VPC : EC2와 같은 VPC안으로
- 서브넷 그룹 : 생성한 서브넷 그룹 설정
- 보안 그룹 : 생성한 보안 그룹 설정
- 포트 : 27017
- 파라미터 그룹 : default
- 그 외 암호화 / 백업 등 설정은 임의로 해 주세요.
클러스터 내에 프라이머리 인스턴스가 1대 움직이고 있습니다.
이번에는 FastAPI ~ DocumentDB간 연계를 검증하는 목적이므로 인스턴스 클래스나 인스턴스 수를 최소로 하고 있습니다.
읽기 성능 향상이나 페일 오버를 고려할 경우, 인스턴스 수를 늘리는 것을 검토하면 좋습니다.
EC2 ~ DocumentDB간 접속 테스트
이번 데이터베이스 조작은 PyMongo를 이용할 예정이지만, 일단 mongo커맨드로 EC2 ~ DocumentDB간 접속을 시도하겠습니다.
접속 방법에 대해서는 공식 문서의 단계 4 이후에서 참조 할 수 있습니다.
- mongo커맨드 이용 가능한 상태로 함
DocumentDB에 mongo커맨드로 액세스 하기 위해서는 mongo쉘을 인스톨 해야 합니다.
리포지토리 파일 생성
$ echo -e “[mongodb-org-4.0] \nname=MongoDB Repository\nbaseurl=https://repo.mongodb.org/yum/amazon/2013.03/mongodb-org/4.0/x86_64/\ngpgcheck=1 \nenabled=1 \ngpgkey=https://www.mongodb.org/static/pgp/server-4.0.asc" | sudo tee /etc/yum.repos.d/mongodb-org-4.0.repo
mongo쉘 인스톨
$ sudo yum install -y mongodb-org-shell
AmazonDocumentDB 공개 키를 다운로드
DocumentDB⇒※생성한 클러스터 지정⇒접속과 시큐리티 탭여기에 접속 할 때에 필요한 커맨드가 기재 되어 있습니다.$ cd fast-app$ ※’인증에 필요한 Amazon DocumentDB 인증 기관(CA)증명서를 클러스터에 다운로드 하기’에 기재 되어 있는 커맨드를 실행 해 주세요
mongo쉘로 클러스터에 접속
접속과 보안 탭을 참조$ ※’mongo쉘로 이 클러스터에 접속하기’에 기재 되어 있는 커맨드를 실행 해 주세요. 패스워드는 자신이 설정한 마스터 패스워드로 바꿔 주세요.
무사히 접속이 완료 되었습니다.
mongo쉘 실행
데이터 베이스 구조 입니다.
DB
| — — — — — — — — -
| — Collection
| — Document
| — Document
| — Document
| — Document
|
| — Collection
| — Document
| — Document
| — Document
| — Document
- DB(데이터 베이스)에 Collection 생성
- Collection중에서 여러 형태의 Document (오브젝트)를 삽입 할 수 있습니다.
- 오브젝트 삽입
object = { ID : 1, Name : “test-taro”};
그럼 DocumentDB에 접속하여 mongo쉘을 실행 해 봅시다.
DB생성
#DB생성
> use dbtest#DB리스트 표시
> show dbs
‘use DB’로 DB생성 및 이동을 합니다.
이 시점에서 show dbs를 실행 해도 DB 리스트는 표시 되지 않습니다.
컬렉션이 없다면 DB 리스트에 표시 되지 않습니다.
Collection 생성
#컬렉션 생성
> db.createCollection(‘test1’);#DB리스트 표시
> show dbs
dbtest 0.000GB#컬렉션 리스트 확인
> use dbtest
> show collections
test1
‘db.createCollection(Collection명)’으로 컬렉션 생성을 진행 합니다.
‘show collections’로 데이터 베이스 내에 생성 된 컬렉션을 확인 할 수 있습니다.
또는 이 시점에서 ‘show dbs’를 실행하면 dbtest데이터 베이스가 표시 되어 있는 것을 알 수 있습니다.
문서 삽입
#test1컬렉션에 3가지 문서를 삽입
> db.test1.insert( { ID:’1', Name:’test taro’ });
> db.test1.insert( { ID:’1', Name:’test jiro’ });
> db.test1.insert( { ID:’1', Name:’test saburo’ });#test1컬렉션 내의 문서를 모두 취득
> db.test1.find();
{ “_id” : ObjectId(“627d1b3823a3bca6707fee93”), “ID” : “1”, “Name” : “test taro” }
{ “_id” : ObjectId(“627d1b4b23a3bca6707fee94”), “ID” : “1”, “Name” : “test jiro” }
{ “_id” : ObjectId(“627d1b5523a3bca6707fee95”), “ID” : “1”, “Name” : “test saburo” }#test1컬렉션 내의 ‘Name이 test taro’인 문서를 취득
> db.test1.find({Name:’test taro’});
{ “_id” : ObjectId(“627d1b3823a3bca6707fee93”), “ID” : “1”, “Name” : “test taro” }
db.collection.insert()로 1개 또는 복수 문서를 컬렉션에 삽입 합니다.
삽입 결과는 find()로 확인 할 수 있습니다. 컬렉션 내의 문서를 전부 취득 하거나 일부 문서를 취득 할 수 있습니다.
잘못 하여 ID를 모두 1로 해 버렸습니다.
ID를 변경 해 보겠습니다.
Document 갱신
#문서 갱신
> db.test1.update({‘Name’: ‘test jiro’},{$set: {ID:’2'}})`
> db.test1.update({‘Name’: ‘test saburo’},{$set: {ID:’3', Name:’saburo’}})#컬렉션 내의 문서 모두 취득
> db.test1.find();
{ “_id” : ObjectId(“627d1b3823a3bca6707fee93”), “ID” : “1”, “Name” : “test taro” }
{ “_id” : ObjectId(“627d1b4b23a3bca6707fee94”), “ID” : “2”, “Name” : “test jiro” }
{ “_id” : ObjectId(“627d1b5523a3bca6707fee95”), “ID” : “3”, “Name” : “test saburo” }
db.collection.update()는 단일 문서를 갱신 합니다.
무사히 ID 변경을 완료 했습니다.
그러면 본 섹션의 마지막 정리를 해 봅시다.
컬렉션 삭제와 데이터베이스 삭제
#test1컬렉션 삭제
> db.test1.drop();
true#데이터베이스 삭제
> use dbtest
> db.dropDatabase();
{ “ok” : 1, “operationTime” : Timestamp(1652368549, 1) }
간단하게나마 MongoDB를 체험 할 수 있었습니다.
글이 길어져서 일단 여기에서 본 게시글을 마치도록 하겠습니다.
마지막으로
다음 글에서는 Streamlit으로 구축한 입력 폼과 DocumentDB를 연결 해 보도록 하겠습니다.
지금까지 읽어 주셔서 감사합니다 !
원문 게시글: https://zenn.dev/megazone_japan/articles/6fa631e9f7a587#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB
메가존 일본 법인 블로그에 업로드 중인 게시글로 작성자 아가 (阿河)님의 동의를 얻어 번역한 게시글 입니다.