Lab 02: 한 덩어리 앱 (1-Tier)
이 Lab의 흐름
여기부터는 EC2에서 진행합니다. 브라우저로
:9080code-server에 접속한 상태여야 합니다.
학습 목표
여기서는 AI를 배포 도구로 씁니다. 콘솔을 클릭해 방화벽을 찾아 헤매는 대신, 말로 시켜서 앱을 밖에서 닿게 만듭니다.
- 화면·API·데이터가 한 코드에 있는 구조를 직접 실행해봅니다
- 코드가 돌아가는 것과 밖에서 닿는 것이 다른 문제임을 확인합니다
- 액세스 키 없이 IAM Role로 AWS를 조작합니다
- 이 구조로 어디까지 갈 수 있고 어디서 멈추는지 확인합니다
Step 1: 앱 실행하기
2.single-app 은 화면·API·데이터가 한 코드에 있는 Next.js 앱입니다. AI에게 그냥 만들어달라고 했을 때 나오는 첫 앱의 전형적인 모습입니다.
cd /home/ec2-user/kiro-on-cloud/2.single-app
npm install
npm run dev0.0.0.0:8080 에서 화면과 API를 한 프로세스가 서빙합니다. 이제 브라우저에서 접속해봅니다.
http://내-EC2-퍼블릭-IP:8080안 열립니다. 서버는 분명히 떠 있는데 보안그룹이 막고 있습니다.
지금 열려 있는 인바운드는 9080 하나뿐입니다. 그 포트로 code-server에 들어와 작업하고 있으면서, 정작 8080 은 규칙이 없어 막힙니다. 나가는 방향(아웃바운드)은 전부 열려 있어서 npm install 은 잘 되는데도 그렇습니다. 들어오는 문과 나가는 문은 따로입니다.
- 터미널에서 서버가 떴다는 로그를 확인했다
- 브라우저에서는 열리지 않는 것을 확인했다
Step 2: 미션 ① — 8080 포트 열기
Kiro에게 아래를 그대로 붙여넣습니다. 콘솔에 들어가 클릭하지 않고, 말로 시켜서 인프라를 바꿔봅니다.
이 EC2에 붙은 보안그룹에 TCP 8080 인바운드를 열어줘.액세스 키를 따로 알려주지 않았는데도 Kiro가 AWS를 조작합니다. 이 EC2에 IAM Role이 붙어 있어서 임시 자격증명을 자동으로 받아 쓰기 때문입니다. 키를 만들어 파일에 적으면 그 파일이 유출되는 순간 계정 전체가 열리지만, 이 방식은 유출될 키 자체가 없습니다.
작업이 끝나면 다시 접속합니다. 명언 받기 / 추가가 동작하면 성공입니다.

주소창에 도메인 없이 44.201.3.52:8080 같은 IP가 그대로 보입니다. 내 EC2에서 도는 앱을 밖에서 연 것입니다. 다른 명언 보기 를 누르고, 문장을 하나 추가해봅니다.
바뀐 것은 인바운드 규칙 한 줄뿐입니다. 앱은 아까와 똑같이 0.0.0.0:8080 에 떠 있었고, 코드는 한 글자도 고치지 않았습니다.
코드가 돌아가는 것과 밖에서 닿는 것은 다른 문제입니다. 앱을 아무리 잘 만들어도 인프라가 막혀 있으면 아무도 못 씁니다. 개발자도 클라우드를 알아야 하는 이유가 여기 있습니다.
포트 개방이 실패했다면
UnauthorizedOperation 이 나오면 내 것이 아닌 보안그룹을 고치려 한 것입니다. 인스턴스에 붙은 보안그룹이 kiro-handson-내사용자명 인지 확인합니다. 사전 준비에서 새 보안그룹을 만들었다면 여기서 막힙니다.
- Kiro가 8080 인바운드를 추가했다
- 브라우저에서 앱이 열리고 명언 추가가 동작한다
정적 배포와 동적 배포
Lab 01에서도 무언가를 인터넷에 올렸습니다. 게임 HTML 파일 하나를 공유 사이트에 올려 주소를 받았습니다. 방금 한 일도 인터넷에 올린 것입니다. 그런데 둘은 전혀 다른 종류의 배포입니다.
| 정적 배포 | 동적 배포 | |
|---|---|---|
| 올리는 것 | 완성된 파일 (HTML·CSS·JS) | 실행할 코드 |
| 서버가 하는 일 | 파일을 그대로 내어준다 | 프로그램을 계속 돌린다 |
| 누가 언제 만드나 | 올리기 전에 이미 다 만들어져 있다 | 요청이 올 때마다 그 자리에서 만든다 |
| 꺼지면 | 파일은 그대로 있다 | 아무것도 안 된다 |
| 오늘 | Lab 01 게임 · Lab 03 화면 | Lab 02 이 앱 |
Lab 01의 게임은 정적이었습니다. 파일을 올려두면 끝이고, 누가 열든 같은 파일이 그대로 갑니다. 올린 쪽에서 돌아가고 있는 프로그램은 없습니다.
방금 띄운 명언집은 동적입니다. npm run dev 로 띄운 프로세스가 계속 돌고 있고, 요청이 올 때마다 그 프로세스가 응답을 만들어냅니다. 터미널에서 Ctrl+C 를 누르면 그 즉시 사이트가 죽습니다. 파일이 사라져서가 아니라 일할 사람이 없어져서입니다.
이 차이가 오늘 남은 실습을 가릅니다. 3-Tier에서 화면은 정적으로, 서버는 동적으로 배포합니다. 하나로 묶여 있던 것을 성격에 맞게 갈라놓는 것이 계층을 나눈다는 말의 실체입니다.
정적인 것과 동적인 것 중 정답은 없습니다. 어느 쪽이 낫다가 아니라 무엇을 어느 쪽에 둘 것인가의 문제입니다.
Step 3: 코드 들여다보기
동작하는 것을 봤으니, 이제 왜 이 구조가 문제인지 알기 위해 코드를 봅니다. 직접 읽지 않고 Kiro에게 물어봅니다.
`app/page.js`(화면)와 `app/api/text/route.js`(API)가 한 프로젝트에 같이 있지?
데이터는 어디에 저장돼?Kiro의 답에서 세 가지를 확인합니다.
- 화면과 API가 같은 프로젝트, 같은 프로세스에 있다
- 데이터는
app/api/quotes-store.js의 메모리 배열에 있다 - 그래서 화면이 API를 부를 때
/api/text라는 상대경로만으로 충분하다
세 번째가 나중에 크게 달라집니다. 계층을 나누면 상대경로가 더 이상 통하지 않습니다.
Step 4: 어디까지 되고 어디서 멈추는지 확인
여기까지 왔다면 이미 성공했습니다. 인터넷에 주소가 생겼고, 남이 열 수 있습니다. 이제 이대로 계속 써도 되는지를 확인할 차례입니다. 두 가지를 직접 눈으로 봅니다.
① 화면과 서버가 한 몸이다
app/page.js는 화면, app/api/text/route.js는 서버 로직입니다. 둘이 한 앱, 한 서버에 묶여 있습니다. 빠르게 만들기에는 이만한 구조가 없습니다. 대신 서버 하나에 모든 게 걸려 있어서, 그 서버를 건드릴 때마다 화면까지 함께 흔들립니다.
② 데이터가 서버 안에 있다
직접 해봅니다.
- 명언을 하나 추가합니다
- 새로고침합니다 — 남아 있습니다 (서버 메모리에 있으니까)
- 터미널에서
Ctrl+C를 눌러 서버를 끄고, 다시npm run dev합니다 - 명언 받기를 눌러봅니다 — 방금 추가한 게 사라졌습니다
데이터가 서버 메모리에만 있기 때문입니다. 아직 DB가 없습니다.
여기까지가 한 덩어리로 갈 수 있는 거리입니다. 오래 버티게 만들려면 데이터와 화면을 서버 밖으로 꺼내야 합니다. 그게 다음에 할 일입니다.
- 명언을 추가하고 새로고침해서 남아 있는 것을 확인했다
- 서버를 재시작하니 추가한 명언이 사라지는 것을 확인했다
다음 단계
배포는 이미 해냈습니다. 이제 같은 앱을 화면(S3) · 로직(EC2 FastAPI) · 데이터(MySQL) 로 나눠 오래 버티는 구조로 다시 세웁니다. 화면부터 떼어냅니다. 정적으로 올릴 수 있는 부분이라 가장 먼저 자기 자리를 찾아갈 수 있습니다.
NxtCloud Workshop