본문 바로가기
인턴

[Azure] Azure 학습 및 고객사 아키텍처 설계 문서

by 칙칙폭폭 땡땡 2026. 6. 7.
반응형

문서 작성 목적

현재 새로운 고객사 프로젝트를 단독으로 담당하게 되면서 Azure 환경 구성과 서비스 배포 구조를 직접 설계해야 하는 상황이 되었다.

새로운 고객사는 이하 파이로라고 부른다.

기존에는 Azure를 사용하는 프로젝트에 참여한 경험은 있었지만, 이미 만들어진 Azure 구조 안에서 필요한 부분을 확인하거나 수정하는 것에 가까웠다. 이번에는 어떤 리소스를 만들지, 어떤 방식으로 배포할지, 프론트엔드와 백엔드를 어디에 둘지, Dataverse와는 어떻게 연결할지까지 직접 판단해야 한다.

그래서 이 문서는 Azure 서비스를 그냥 기능별로 외우려고 쓰는 문서가 아니다.

실제로 파이로를 구축한다고 생각했을 때, 각 Azure 서비스가 어떤 역할을 하는지 이해하고, 나중에 어떤 구조를 선택해야 하는지 판단하기 위한 기준을 남기기 위해 작성한다.

특히 이 문서에서는 각 서비스마다 바로 “파이로에 쓸지 말지”를 적지 않는다. 먼저 기술 자체를 충분히 정리하고, 최종적으로 파이로에 어떤 구조가 맞는지는 마지막 장에서 한 번에 정리한다.


1. Azure 개요

Azure는 Microsoft에서 제공하는 클라우드 플랫폼이다.

클라우드 플랫폼이라는 말이 처음에는 좀 추상적인데, 쉽게 말하면 서버, 저장소, 데이터베이스, 네트워크, 인증, 모니터링 같은 것들을 직접 장비 사서 구축하지 않고 필요할 때마다 빌려 쓰는 환경이다.

예전 방식으로 서비스를 운영한다고 생각하면 서버를 직접 구매하거나, IDC에 서버를 넣거나, 사내에 서버실을 두고 관리해야 했다. 그러면 하드웨어 고장, 전원, 네트워크, OS 설치, 보안 패치, 백업, 장애 대응까지 다 직접 신경 써야 한다.

Azure를 사용하면 이런 인프라 영역의 상당 부분을 Microsoft가 대신 관리해준다. 개발자는 필요한 리소스를 만들고, 그 위에 애플리케이션을 올리고, 설정을 맞추고, 운영 상태를 확인하면 된다.

물론 Azure가 모든 걸 알아서 해준다는 뜻은 아니다. 어떤 서비스를 선택하느냐에 따라 내가 관리해야 하는 범위가 달라진다. VM을 쓰면 서버를 거의 직접 관리해야 하고, Function App을 쓰면 함수 코드 중심으로 관리하게 된다. 그래서 Azure 공부에서 중요한 건 “무슨 서비스가 있다”가 아니라 “이 서비스는 내가 어디까지 관리해야 하고, Azure가 어디까지 대신해주는가”라고 생각한다.

1.1 Azure를 쓴다는 것

Azure를 쓴다는 건 단순히 서버 하나를 빌린다는 의미가 아니다.

웹 서비스를 하나 운영하려고 해도 생각보다 많은 요소가 필요하다. 사용자가 접속할 주소가 필요하고, 프론트엔드를 제공할 공간이 필요하고, API를 실행할 환경이 필요하고, 데이터를 저장할 곳이 필요하고, 인증과 권한 처리가 필요하고, 운영 중 문제가 생겼을 때 로그를 확인할 방법도 필요하다.

Azure는 이런 것들을 각각 서비스로 제공한다.

그래서 Azure 구조를 잡을 때는 “어떤 서비스 하나를 쓸까?”가 아니라 “필요한 역할을 어떤 Azure 서비스들로 나눠서 구성할까?”에 가깝다.

예를 들어 프론트엔드 정적 파일은 Storage Account에 둘 수도 있고, Static Web Apps에 둘 수도 있다. 백엔드 API는 App Service에 둘 수도 있고, Function App에 둘 수도 있다. Docker 이미지를 쓴다면 ACR이 필요할 수 있고, 인증 비밀값을 안전하게 관리하려면 Key Vault를 붙일 수도 있다.

결국 Azure는 필요한 부품을 선택해서 조합하는 플랫폼이다.

1.2 Azure에서 자주 나오는 기본 단위

Azure를 쓰다 보면 Subscription, Resource Group, Resource라는 단어가 계속 나온다.

Subscription은 결제와 사용 범위를 묶는 단위다. 회사에서 Azure를 쓴다면 보통 회사 명의의 구독이 있고, 그 안에 여러 리소스 그룹과 리소스가 생긴다. 개발용 구독과 운영용 구독을 따로 두는 경우도 있다.

Resource Group은 관련 있는 Azure 리소스를 묶는 단위다. 예를 들어 하나의 프로젝트에 필요한 Function App, Storage Account, Application Insights 등을 한 리소스 그룹 안에 넣을 수 있다. 이렇게 하면 프로젝트 단위로 리소스를 보기 쉽고, 나중에 정리할 때도 편하다.

Resource는 실제로 만들어지는 Azure 서비스 하나하나를 말한다. VM도 리소스고, App Service도 리소스고, Storage Account도 리소스고, Function App도 리소스다.

처음 Azure 포털을 보면 리소스가 너무 많아서 복잡해 보이는데, 결국은 “이 프로젝트에 필요한 리소스를 어떤 리소스 그룹에 만들 것인가”로 시작하면 된다.

1.3 Azure 서비스를 보는 기준

Azure에는 서비스가 너무 많다. 처음부터 전부 보려고 하면 답이 없다.

그래서 이 문서에서는 파이로 구축에 필요할 가능성이 있는 범위만 본다.

컴퓨팅 서비스는 내 코드가 실행되는 곳이다. VM, App Service, Function App, Container Apps, AKS가 여기에 들어간다.

배포와 컨테이너 쪽에서는 Docker, ACR, 이미지, 태그 같은 개념을 본다. 기존에 확인했던 다른 프로젝트에서 ACR과 App Service가 연결되어 있었기 때문에 이 부분은 이해가 필요했다.

정적 웹사이트 배포 쪽에서는 Storage Account, Static Web Apps, CDN을 본다. React 같은 프론트엔드만 배포할 때 어떤 선택지가 있는지 보기 위해서다.

데이터 저장소 쪽에서는 Dataverse, Azure SQL, Storage Account, Blob Storage를 본다. CRM과 연결되는 프로젝트라 Dataverse가 중요하다.

인증과 보안 쪽에서는 JWT, Entra ID, Managed Identity, Key Vault를 본다. 로그인과 토큰 발급을 백엔드에서 처리할 예정이라 JWT와 비밀값 관리는 꼭 이해해야 한다.

네트워크 쪽에서는 도메인, DNS, HTTPS, 포트, VNet을 본다. 이 부분은 배포하고 나서 실제 사용자가 어떻게 접속하는지 이해하는 데 필요하다.

모니터링과 운영 쪽에서는 Application Insights, Log Analytics, 진단 로그를 본다. 작게 만들더라도 운영 중 문제가 생기면 결국 로그를 봐야 한다.

CI/CD 쪽에서는 GitHub Actions, Azure DevOps, 배포 파이프라인, 자동 배포 전략을 본다. 결국 수동으로 매번 배포할 수는 없기 때문이다.


2. Azure 컴퓨팅 서비스

컴퓨팅 서비스는 “내 코드가 어디서 실행되느냐”의 문제다.

웹 서비스를 만든다고 하면 결국 어딘가에서 코드가 실행되어야 한다. 프론트엔드 React 코드는 사용자 브라우저에서 실행되지만, 백엔드 API 코드는 서버 쪽에서 실행되어야 한다. Azure 컴퓨팅 서비스는 그 서버 쪽 실행 환경을 어떻게 가져갈지 정하는 선택지다.

Azure에서 대표적으로 볼 수 있는 선택지는 VM, App Service, Function App, Container Apps, AKS다.

이 다섯 개는 전부 “코드를 실행한다”는 점에서는 같지만 성격이 완전히 다르다. VM은 서버를 거의 통째로 직접 관리하는 방식이고, Function App은 함수만 작성하고 서버 관리는 최대한 Azure에 맡기는 방식이다. App Service는 그 중간쯤이고, Container Apps와 AKS는 컨테이너 운영 방식에 가깝다.

2.1 Virtual Machine

Virtual Machine은 Azure에서 제공하는 가상 서버다.

AWS를 기준으로 생각하면 EC2와 거의 같은 개념이다. Azure가 물리 서버 위에 가상화된 서버 하나를 만들어주고, 사용자는 그 서버에 접속해서 필요한 걸 직접 설치하고 운영한다.

VM을 만들 때는 운영체제를 선택한다. Windows Server를 쓸 수도 있고 Ubuntu 같은 Linux를 쓸 수도 있다. VM이 만들어지면 SSH나 원격 데스크톱으로 접속해서 내가 필요한 런타임과 프로그램을 설치한다.

Node.js 서버를 올리고 싶으면 Node를 설치해야 한다. Nginx를 앞단에 두고 싶으면 Nginx도 직접 설치해야 한다. Docker를 쓰고 싶으면 Docker도 직접 설치해야 한다. 방화벽도 보고, 포트도 열고, 프로세스가 죽으면 다시 살리는 방식도 정해야 한다.

VM은 진짜 서버 한 대를 빌린 느낌에 가깝다.

VM의 장점

VM의 가장 큰 장점은 자유도다.

사실상 내가 서버 한 대를 빌린 것이기 때문에 원하는 걸 거의 다 할 수 있다. 특정 버전의 라이브러리가 필요하거나, 운영체제 설정을 직접 바꿔야 하거나, 레거시 프로그램을 그대로 올려야 하거나, 일반적인 PaaS 환경에서 지원하지 않는 실행 환경이 필요하다면 VM이 편할 수 있다.

예를 들어 어떤 프로그램이 특정 OS 패키지에 강하게 의존한다거나, 서버 내부에 여러 프로세스를 직접 구성해야 한다거나, Nginx 설정을 아주 세밀하게 만져야 한다면 VM이 가장 자유롭다.

또 VM은 기존 온프레미스 서버 운영 방식과도 비슷하다. 이미 Linux 서버 운영에 익숙한 사람이라면 App Service나 Function App보다 VM이 더 직관적으로 느껴질 수도 있다.

VM의 단점

자유도가 높다는 건 그만큼 책임도 크다는 뜻이다.

OS 보안 패치를 해야 하고, 사용하지 않는 포트는 닫아야 하고, SSH 접근도 관리해야 하고, 서버가 느려지거나 죽으면 직접 확인해야 한다. 로그도 직접 모아야 하고, 백업도 직접 신경 써야 한다.

단순히 코드를 짜는 것보다 서버 운영에 가까운 일이 늘어난다.

작은 웹 서비스 하나 만들 때 VM을 쓰면 처음에는 익숙해 보여도, 운영이 길어질수록 귀찮은 일이 계속 생긴다. 서버 디스크 용량, 메모리 부족, 프로세스 죽음, 보안 업데이트, 인증서 갱신, Nginx 설정 같은 것들이 전부 개발자 책임이 된다.

그래서 VM은 “내가 서버를 직접 통제해야 하는 이유가 있을 때” 쓰는 게 맞다. 그냥 API 몇 개 띄우고 싶다, React 배포하고 싶다, Dataverse에 데이터 넣고 싶다 정도라면 VM까지 갈 필요가 없다.

2.2 App Service

App Service는 Azure에서 제공하는 웹 애플리케이션 호스팅 서비스다.

VM과의 가장 큰 차이는 서버를 직접 관리하지 않아도 된다는 점이다. VM은 서버에 접속해서 Node 설치하고 Nginx 설치하고 직접 프로세스를 띄우는 방식이라면, App Service는 Azure가 웹 애플리케이션을 실행할 수 있는 환경을 제공하고 사용자는 코드나 Docker 이미지를 배포한다.

App Service는 Node.js, .NET, Java, Python 같은 런타임을 선택해서 코드 기반으로 배포할 수 있다. 또는 Docker 이미지를 만들어서 App Service가 그 이미지를 실행하게 할 수도 있다.

App Service를 쓰면 VM에서 직접 해야 하던 많은 일들이 줄어든다. OS 패치, 서버 관리, 기본적인 HTTPS 연결, 스케일링 설정, 배포 슬롯 같은 기능을 Azure가 제공한다. 일반적인 웹 서버나 API 서버를 운영하기에는 꽤 편한 선택지다.

App Service의 동작 방식

App Service는 크게 두 가지 방식으로 배포할 수 있다.

첫 번째는 코드 배포다. 예를 들어 Node.js 런타임을 선택하고, GitHub에서 코드를 가져오거나 zip 배포를 해서 App Service가 직접 Node 애플리케이션을 실행하게 할 수 있다.

두 번째는 컨테이너 배포다. Docker 이미지를 ACR 같은 레지스트리에 올려두고, App Service가 그 이미지를 pull해서 컨테이너로 실행한다.

컨테이너 배포에서는 포트 설정이 중요해진다. 사용자는 보통 HTTPS 443으로 접속하지만, 컨테이너 내부 애플리케이션은 3000이나 8080 같은 포트에서 실행될 수 있다. App Service는 외부 요청을 받은 뒤 컨테이너 내부의 어느 포트로 전달해야 하는지 알아야 한다.

App Service의 장점

App Service는 일반적인 웹 애플리케이션 운영에 편하다.

Express 서버, NestJS 서버, Spring Boot 서버, Next.js SSR 앱처럼 계속 떠 있어야 하는 웹 애플리케이션은 App Service와 잘 맞는다. 요청이 들어올 때마다 새로 실행되는 게 아니라 서버 프로세스가 계속 떠 있으면서 요청을 받는 구조이기 때문이다.

또 VM보다 운영 부담이 훨씬 적다. 직접 SSH로 들어가서 패치하거나, 프로세스 매니저를 설정하거나, Nginx를 직접 구성하지 않아도 된다.

배포 슬롯도 장점이다. 운영 슬롯과 스테이징 슬롯을 나눠서 새 버전을 먼저 띄워보고, 문제가 없으면 슬롯을 교체하는 방식으로 배포할 수 있다.

App Service의 단점

App Service는 계속 떠 있는 서비스다. 사용자가 없어도 App Service Plan에 대한 비용이 발생한다.

물론 작은 요금제로 시작할 수 있지만, Function App처럼 호출될 때만 비용이 발생하는 구조는 아니다.

또 App Service가 VM보다 편하긴 하지만 그만큼 자유도는 낮다. OS를 완전히 내 마음대로 바꾸거나 시스템 레벨 설정을 세밀하게 만지는 데는 한계가 있다. 물론 컨테이너 방식으로 어느 정도 보완할 수 있지만, 그 경우에는 Docker와 ACR까지 같이 이해해야 한다.

정리하면 App Service는 “일반적인 웹 서버를 안정적으로 계속 띄워야 할 때” 잘 맞는다. 너무 작지도 않고, 너무 복잡하지도 않은 웹 서비스 운영에 적당한 선택지다.

2.3 Function App

Function App은 Azure의 서버리스 컴퓨팅 서비스다.

서버리스라는 말이 처음엔 헷갈리는데, 서버가 없다는 뜻은 아니다. 서버는 있지만 내가 서버를 직접 보고 관리하지 않는다는 뜻이다. 내가 작성하는 단위도 “서버 전체”라기보다는 “함수 하나”에 가깝다.

Function App에서는 특정 이벤트가 발생하면 함수가 실행된다. 가장 흔한 건 HTTP 요청이다. 사용자가 특정 URL로 요청을 보내면 그 요청을 트리거로 함수가 실행되고, 함수가 처리 결과를 응답한다.

예를 들어 로그인 API를 Function으로 만든다고 하면, 사용자가 이메일과 비밀번호를 보내고, Function은 Dataverse에서 해당 사용자를 조회하고, 비밀번호 검증 후 JWT를 발급해서 응답한다. 이게 끝나면 함수 실행도 끝난다.

App Service는 서버가 계속 떠 있는 느낌이고, Function App은 요청이 있을 때 필요한 함수가 실행되는 느낌이다.

Function App의 장점

Function App은 작은 API에 잘 맞는다.

로그인, 문의 등록, 문의 조회, 파일 처리, Webhook 처리, 예약 작업처럼 기능이 비교적 독립적이고 API 개수가 많지 않은 경우 Function App이 편하다.

비용 측면에서도 장점이 있다. 사용량 기반 과금 방식이면 호출이 적을 때 비용이 낮다. 사용자가 많지 않은 고객사 포털이나 내부용 서비스에서는 이게 꽤 큰 장점이다.

그리고 서버 관리 부담도 적다. OS 패치나 런타임 서버 운영을 신경 쓰는 것보다 함수 코드와 설정에 집중하면 된다.

Function App의 단점

가장 많이 나오는 단점은 Cold Start다.

한동안 호출이 없다가 첫 요청이 들어오면 함수 실행 환경이 준비되느라 응답이 느릴 수 있다. 사용자가 많지 않은 서비스일수록 오히려 첫 요청이 느릴 가능성이 있다. 플랜을 어떻게 잡느냐에 따라 줄일 수는 있지만, 서버리스 구조에서 자주 고려해야 하는 부분이다.

또 Function App은 상태를 오래 들고 있는 구조가 아니다. 함수 실행이 끝나면 끝이다. 그래서 메모리에 로그인 세션을 저장해두고 계속 쓰는 방식과는 안 맞는다. 인증도 서버 세션보다는 JWT처럼 요청마다 검증 가능한 방식이 잘 맞는다.

대규모 백엔드 프레임워크를 통째로 올리거나, WebSocket처럼 연결을 오래 유지해야 하거나, 복잡한 서버 내부 상태가 필요한 경우에는 App Service나 Container Apps 쪽이 더 자연스러울 수 있다.

Function App은 “작은 API를 필요한 만큼만 실행하고 싶을 때” 가장 빛나는 서비스라고 보면 된다.

2.4 Container Apps

Container Apps는 Azure에서 컨테이너를 실행하기 위한 서비스다.

App Service도 컨테이너 실행이 가능하고, AKS도 컨테이너 실행이 가능하다. 그래서 처음 보면 Container Apps가 왜 따로 있는지 헷갈린다.

Container Apps는 “컨테이너 기반으로 배포하고 싶은데, AKS처럼 Kubernetes를 직접 다루고 싶지는 않을 때” 쓰는 서비스에 가깝다.

Docker 이미지로 애플리케이션을 만들고, 그 이미지를 ACR 같은 레지스트리에 올린 다음, Container Apps가 그 이미지를 가져와 실행한다. 여기까지 보면 App Service for Containers와 비슷해 보인다.

차이는 Container Apps가 좀 더 컨테이너와 마이크로서비스 운영에 맞춰져 있다는 점이다. HTTP 요청 기반으로 스케일링할 수도 있고, 이벤트 기반으로 스케일링할 수도 있다. 백그라운드 워커나 여러 개의 작은 서비스들을 컨테이너 단위로 운영할 때도 잘 맞는다.

예를 들어 API 컨테이너, 작업 처리 컨테이너, 큐 소비 컨테이너 같은 식으로 나눠서 운영하고 싶다면 Container Apps가 괜찮은 선택지가 될 수 있다. AKS까지 가기에는 부담스럽고, App Service 하나로 단순 웹앱만 띄우기에는 구조가 컨테이너 중심이라면 Container Apps를 볼 만하다.

Container Apps의 장점

컨테이너의 장점을 가져가면서 Kubernetes 운영 부담은 많이 줄일 수 있다.

직접 Pod, Deployment, Service, Ingress를 다 구성하지 않아도 된다. 컨테이너 이미지를 기준으로 배포하고, 필요한 스케일링 규칙을 설정해서 운영할 수 있다.

마이크로서비스처럼 여러 컨테이너를 나눠서 운영하거나, 백그라운드 작업을 별도 컨테이너로 돌리고 싶을 때 좋다.

Container Apps의 단점

그래도 컨테이너 기반이라는 점은 변하지 않는다.

Dockerfile, 이미지 빌드, 태그, 레지스트리, 환경변수, 스케일링 설정 등을 알아야 한다. Function App 코드 배포보다 구조가 복잡하다.

서비스가 작고 API가 몇 개 없다면 Container Apps는 오히려 과할 수 있다. 반대로 컨테이너로 묶어야 할 이유가 분명하거나, 여러 컨테이너 작업을 나누어 운영해야 하면 좋은 선택지가 된다.

2.5 AKS

AKS는 Azure Kubernetes Service다.

Kubernetes를 Azure에서 관리형으로 사용할 수 있게 해주는 서비스다. 여기서 중요한 건 AKS가 단순히 “컨테이너 실행 서비스”가 아니라는 점이다. 컨테이너를 대규모로 배포하고 관리하기 위한 Kubernetes 클러스터를 운영하는 서비스다.

Kubernetes는 컨테이너 오케스트레이션 도구다. 컨테이너를 몇 개 띄울지, 죽으면 다시 띄울지, 어떤 컨테이너로 트래픽을 보낼지, 배포를 어떻게 굴릴지, 설정값은 어떻게 주입할지 같은 걸 관리한다.

AKS를 쓰면 Pod, Deployment, Service, Ingress, ConfigMap, Secret, Namespace 같은 Kubernetes 개념을 이해해야 한다. 배포 설정도 YAML로 관리하는 경우가 많다.

AKS의 장점

AKS는 익숙한 팀에게는 굉장히 강력하다.

서비스가 많고, 컨테이너도 많고, 배포 전략이 복잡하고, 무중단 배포나 세밀한 스케일링이 필요하면 AKS가 맞을 수 있다.

예를 들어 백엔드가 여러 마이크로서비스로 쪼개져 있고, 각 서비스마다 배포 주기가 다르고, 내부 통신이 많고, 트래픽도 크고, 운영팀이 Kubernetes 경험이 있다면 AKS는 좋은 선택지가 된다.

AKS의 단점

작은 서비스에서는 거의 항상 과하다.

학습 비용도 크고, 운영 비용도 크고, 관리해야 할 것도 많다. Kubernetes를 쓰면 멋있어 보일 수는 있는데, 작은 프로젝트에서 쓰면 기능보다 인프라가 훨씬 커지는 상황이 생긴다.

AKS는 “컨테이너 운영의 최종 보스” 같은 느낌으로 이해하면 된다. 강력하지만, 그만큼 무겁다.


3. 컨테이너와 배포

배포를 이해하려면 소스코드와 빌드 산출물을 구분해야 한다.

GitHub에는 보통 소스코드가 있다. 그런데 서버가 실제로 실행하는 것은 소스코드 그 자체가 아니라, 실행 가능한 형태로 준비된 결과물이다. Node 프로젝트라면 의존성을 설치하고 빌드해야 하고, React라면 정적 파일로 빌드해야 하고, Docker를 쓰면 이미지로 빌드해야 한다.

컨테이너 기반 배포에서는 이 빌드 산출물이 Docker 이미지가 된다.

3.1 Docker

Docker는 애플리케이션과 실행 환경을 하나로 묶어서 실행할 수 있게 해주는 기술이다.

예를 들어 Node.js 애플리케이션을 운영 서버에 올린다고 생각하면, 서버에 Node 버전이 맞아야 하고, npm install이 되어야 하고, 환경변수가 있어야 하고, 실행 명령도 맞아야 한다. 개발자 PC와 운영 서버의 환경이 다르면 예상치 못한 문제가 생길 수 있다.

Docker는 이런 실행 환경을 이미지로 묶는다. 이미지 안에는 애플리케이션 코드뿐 아니라 런타임, 의존성, 실행 명령 같은 정보가 들어간다. 그래서 같은 이미지를 실행하면 어디서든 비슷한 방식으로 컨테이너가 뜬다.

Docker에서 이미지는 실행 가능한 템플릿이고, 컨테이너는 그 이미지를 실제로 실행한 상태다.

정확히 말하면 이미지는 파일 시스템과 실행 설정을 포함한 실행 단위이고, 컨테이너는 그 이미지를 기반으로 만들어진 격리된 프로세스 실행 환경이다.

Docker를 쓰면 배포 흐름이 명확해진다. 소스코드를 가져와서 이미지를 빌드하고, 그 이미지를 레지스트리에 올리고, 실행 환경에서 이미지를 가져와 컨테이너로 실행한다.

이 구조의 장점은 환경을 고정할 수 있다는 점이다. 개발 환경과 운영 환경의 차이를 줄일 수 있고, 배포 산출물이 명확하다.

하지만 Docker가 항상 필요한 건 아니다. 작은 Function App API를 만들거나 React 정적 파일만 배포하는 경우에는 Docker 없이도 충분히 배포할 수 있다. Docker는 실행 환경까지 묶어야 할 이유가 있을 때 쓰는 게 좋다.

3.2 Azure Container Registry, ACR

ACR은 Azure Container Registry의 약자다.

Docker 이미지를 저장하는 Azure의 이미지 저장소다. GitHub가 소스코드를 저장하는 곳이라면, ACR은 빌드된 Docker 이미지를 저장하는 곳이다.

컨테이너 배포에서는 이미지를 어딘가에 올려두어야 한다. App Service, Container Apps, AKS 같은 실행 환경이 그 이미지를 가져와서 실행하기 때문이다. 이때 Azure 안에서 주로 쓰는 이미지 저장소가 ACR이다.

ACR 안에는 Repository가 있고, Repository 안에는 여러 태그가 있다.

예를 들어 myregistry.azurecr.io/backend:v1.0.0 같은 이미지 주소를 보면 myregistry.azurecr.io는 레지스트리 주소고, backend는 이미지 Repository 이름이고, v1.0.0은 태그다.

ACR에 이미지를 올렸다고 해서 배포가 끝나는 건 아니다. ACR은 말 그대로 저장소다. 실제 배포는 App Service나 Container Apps나 AKS 같은 실행 서비스가 한다. ACR은 “이미지를 보관하고 꺼내갈 수 있게 해주는 곳”이다.

해맑음에서 봤던 구조도 이렇다. ACR에 haemalgeumweb 이미지가 있고, App Service가 그 이미지를 가져와 컨테이너로 실행했다. 이때 App Service 설정에서 어떤 이미지와 태그를 사용할지 지정해주는 식이었다.

ACR의 장점은 Azure 서비스들과 연동이 좋다는 점이다. 인증, 권한, 배포 자동화도 Azure 안에서 연결하기 쉽다.

하지만 Docker 이미지를 쓰지 않는 프로젝트라면 ACR이 필요 없다. Function App을 코드로 배포하거나, React를 Blob Storage로 올리는 경우에는 이미지 저장소 자체가 필요하지 않다.

3.3 이미지와 태그

Docker 이미지에서 태그는 버전 이름이다.

같은 Repository라도 태그가 다르면 다른 버전의 이미지로 관리할 수 있다. 예를 들어 backend:v1.0.0, backend:v1.0.1, backend:20260605, backend:latest처럼 쓸 수 있다.

태그가 중요한 이유는 배포와 롤백 때문이다. 운영 중 문제가 생겼을 때 “이전에 잘 되던 이미지”로 돌아갈 수 있어야 한다. 만약 계속 같은 태그만 덮어쓴다면 지금 운영 중인 버전이 정확히 어떤 코드로 만들어진 건지 추적하기 어려워진다.

latest 태그는 특히 조심해야 한다. 이름만 보면 최신 버전처럼 보이지만, 실제로는 그냥 latest라는 이름의 태그일 뿐이다. 누군가 옛날 이미지를 latest로 다시 push할 수도 있다. 그래서 운영에서는 가능하면 커밋 해시나 날짜, 버전 번호처럼 추적 가능한 태그를 쓰는 게 낫다.

예를 들어 GitHub Actions에서 배포한다면 커밋 SHA를 태그로 쓰는 방식이 흔하다. 그러면 “현재 운영은 어떤 커밋 기반 이미지인가”를 추적하기 쉽다.

작은 서비스에서는 처음에 latest 하나로도 굴러갈 수는 있다. 하지만 운영 안정성을 생각하면 언젠가는 태그 전략을 잡아야 한다.

3.4 배포 방식 비교

Azure에서 배포 방식은 크게 정적 파일 배포, 코드 배포, 컨테이너 배포로 나눠서 보면 이해가 쉽다.

정적 파일 배포는 React 같은 프론트엔드 빌드 결과물을 Storage Account나 Static Web Apps에 올리는 방식이다. 이 경우 서버에서 Node가 계속 실행되는 게 아니라 HTML, CSS, JS 파일만 제공된다. 실제 React 코드는 사용자의 브라우저에서 실행된다.

코드 배포는 App Service나 Function App에 코드를 직접 배포하는 방식이다. 예를 들어 Function App에 API 코드를 배포하면 Azure가 해당 런타임에서 함수를 실행해준다. Docker 이미지를 만들지 않아도 된다.

컨테이너 배포는 Docker 이미지를 빌드해서 ACR에 올리고, App Service나 Container Apps나 AKS가 그 이미지를 실행하는 방식이다. 실행 환경을 명확히 고정할 수 있지만 Docker, 이미지 태그, 레지스트리, 컨테이너 설정까지 관리해야 한다.

작은 API 서비스는 코드 배포가 단순하다. 복잡한 실행 환경이 필요하거나, 로컬과 운영의 환경 차이를 줄이고 싶거나, 이미 팀 표준이 컨테이너라면 컨테이너 배포가 좋다.

배포 방식은 기술 취향으로 고르는 게 아니라, 프로젝트 규모와 운영 방식에 맞춰야 한다.


4. 정적 웹사이트 배포

프론트엔드가 React SPA라면 꼭 App Service 같은 서버가 필요한 것은 아니다.

React를 빌드하면 결국 HTML, CSS, JS 파일이 나온다. 이 파일들은 서버에서 실행되는 게 아니라 사용자 브라우저로 전달된 뒤 브라우저에서 실행된다. 그래서 단순 React SPA는 정적 파일을 제공할 수 있는 저장소만 있어도 배포가 가능하다.

4.1 Storage Account

Storage Account는 Azure의 저장소 계정이다.

처음에는 이름 때문에 단순히 파일 저장소처럼 보이지만, 실제로는 Blob, File Share, Queue, Table 같은 여러 저장 기능을 제공하는 큰 단위다.

정적 웹사이트 배포에서 주로 보는 건 Blob Storage다. Blob Storage 안에 React 빌드 결과물을 올리고 정적 웹사이트 기능을 켜면 사용자가 해당 URL로 접근할 수 있다.

이 방식에서는 Storage Account가 코드를 실행하지 않는다. Storage Account는 index.html, JS 파일, CSS 파일, 이미지 파일을 브라우저에 전달할 뿐이다. JS를 실행하는 건 사용자의 브라우저다.

그래서 백엔드 API는 Storage Account에 올릴 수 없다. 예를 들어 Express 서버의 app.get 같은 코드는 서버에서 실행되어야 하는데, Storage Account는 그런 실행 환경이 아니다. Storage Account는 요청받은 파일을 반환하는 쪽에 가깝다.

장점은 단순하고 저렴하다는 점이다. React SPA처럼 정적 파일만 있으면 충분한 경우 App Service보다 훨씬 가볍다.

단점은 서버 기능이 없다는 점이다. 로그인 검증, Dataverse 조회, 문의 등록 같은 백엔드 로직은 별도 Function App이나 App Service에서 처리해야 한다.

4.2 Static Web Apps

Static Web Apps는 Azure에서 정적 웹사이트 배포를 위해 만든 서비스다.

Storage Account도 정적 웹사이트 배포가 가능하지만, Static Web Apps는 프론트엔드 프로젝트 배포 흐름에 좀 더 맞춰져 있다. GitHub와 연결하면 push할 때 자동으로 빌드하고 배포하는 구조를 쉽게 만들 수 있다.

React, Vue, Angular 같은 프론트엔드 프레임워크와 잘 맞고, PR 미리보기 환경 같은 기능도 제공한다. 또한 Azure Functions와 붙여서 간단한 API까지 함께 구성할 수 있다.

Storage Account가 “정적 파일 저장소에 웹 호스팅 기능을 켠다”에 가깝다면, Static Web Apps는 “정적 프론트엔드 앱 배포 서비스”에 가깝다.

장점은 개발 경험이 좋다는 점이다. GitHub 연동이 편하고, 프론트엔드 배포에 필요한 기능이 잘 묶여 있다.

단점은 회사나 프로젝트에서 이미 Storage Account 기반 배포 표준을 쓰고 있다면 굳이 새 서비스를 도입할 이유가 적을 수 있다는 점이다. 또한 구조를 아주 단순하게 가져가고 싶다면 Storage Account가 더 직관적일 수 있다.

4.3 CDN

CDN은 Content Delivery Network다.

정적 파일을 사용자와 가까운 위치의 캐시 서버에서 제공해주는 서비스다. JS 파일, CSS 파일, 이미지 같은 정적 리소스를 더 빠르게 내려주고 원본 서버의 부담을 줄일 수 있다.

예를 들어 사용자가 많고 지역이 여러 곳으로 퍼져 있다면, 매번 Storage Account 원본으로 요청을 보내는 것보다 CDN 캐시에서 가져오는 게 더 빠를 수 있다.

하지만 CDN은 항상 필요한 건 아니다. 사용자 수가 적고, 특정 고객사 내부에서만 쓰는 포털이라면 CDN을 붙여도 체감 이득이 크지 않을 수 있다.

또 캐시가 생기면 배포 후 반영 타이밍을 신경 써야 한다. 파일을 새로 올렸는데 CDN에 이전 파일이 남아 있으면 사용자가 예전 JS를 받는 일이 생길 수 있다. 물론 캐시 무효화나 파일명 해시로 해결할 수 있지만, 관리 포인트가 늘어나는 건 맞다.

CDN은 정적 파일 트래픽이 많아지고 성능 최적화가 필요할 때 붙이는 쪽이 자연스럽다.


5. 데이터 저장소

데이터 저장소는 말 그대로 데이터를 어디에 저장할지의 문제다.

하지만 Azure에서 데이터 저장소라고 해서 전부 같은 성격이 아니다. Dataverse는 CRM과 Power Platform 중심의 데이터 플랫폼이고, Azure SQL은 일반적인 관계형 데이터베이스고, Storage Account와 Blob Storage는 파일 저장소에 가깝다.

5.1 Dataverse

Dataverse는 Microsoft Power Platform과 Dynamics 365에서 사용하는 데이터 플랫폼이다.

테이블을 만들고, 컬럼을 만들고, 관계를 설정하고, 권한을 관리할 수 있다. 겉으로 보면 데이터베이스처럼 보이지만, 단순 DB라기보다는 비즈니스 애플리케이션용 데이터 플랫폼에 가깝다.

Dynamics 365를 사용하면 그 안의 고객, 영업, 서비스, 문의 같은 데이터들이 Dataverse 테이블 기반으로 관리된다. Power Apps나 Power Automate도 Dataverse와 자연스럽게 연결된다.

Dataverse의 장점은 CRM과 직접 연결된다는 점이다. 문의접수 데이터를 CRM에서 처리해야 한다면 별도 DB에 저장하고 다시 CRM으로 옮기는 것보다 Dataverse에 바로 넣는 편이 자연스럽다.

또 권한, 감사, 관계, 선택 항목, Lookup 같은 비즈니스 데이터 모델링 기능이 이미 있다. 단순히 JSON 저장하는 DB보다 업무 시스템에 필요한 요소가 더 많다.

하지만 개발자 입장에서는 일반 SQL DB처럼 마음대로 쿼리하고 조인하고 튜닝하는 방식은 아니다. Dataverse Web API, OData, FetchXML 같은 방식에 맞춰 접근해야 하고, 테이블 이름이나 Lookup, Option Set, 권한 구조도 Dataverse 방식에 익숙해져야 한다.

외부 사용자 인증을 직접 구현할 때도 조심해야 한다. Dataverse에 사용자 이메일과 비밀번호를 저장한다면 비밀번호를 평문으로 넣으면 안 된다. 최소한 hash/salt 방식으로 저장해야 하고, 관리자가 비밀번호를 직접 볼 수 있어야 한다는 요구사항이 있으면 보안적으로 설계가 더 복잡해진다.

Dataverse는 “CRM과 연결되는 업무 데이터”를 저장할 때 가장 자연스럽다.

5.2 Azure SQL

Azure SQL은 Azure에서 제공하는 관리형 SQL Server 데이터베이스다.

전통적인 관계형 DB가 필요할 때 사용한다. 직접 SQL Server를 설치하고 운영하지 않아도 Azure가 DB 서버 운영의 많은 부분을 관리해준다.

일반적인 백엔드 서비스에서 사용자, 주문, 게시글, 결제, 로그 같은 데이터를 관계형으로 저장하고 싶다면 Azure SQL이 좋은 선택지가 될 수 있다.

SQL을 직접 작성할 수 있고, 기존 SQL Server 경험이 있다면 이해하기 쉽다. 트랜잭션, 인덱스, 조인, 저장 프로시저 같은 전통적인 DB 기능도 사용할 수 있다.

하지만 Dynamics 365와 연결되는 프로젝트에서는 고민이 필요하다. 데이터의 최종 사용처가 CRM이라면 Azure SQL에 별도 저장소를 만들고 Dataverse와 동기화하는 구조가 될 수 있다. 그러면 데이터가 두 군데에 생기고, 동기화 실패나 중복 관리 문제가 생길 수 있다.

Azure SQL은 애플리케이션 자체 DB가 필요할 때 좋다. 하지만 CRM이 중심이고 Dataverse가 이미 있는 상황에서는 굳이 추가하지 않는 편이 단순할 수 있다.

5.3 Storage Account

Storage Account는 파일, 객체, 큐, 테이블 같은 저장 기능을 담는 큰 저장소 계정이다.

데이터 저장소 장에서 Storage Account를 볼 때 중요한 건, 이것이 일반적인 애플리케이션 DB와 다르다는 점이다.

Storage Account는 보통 파일 저장, 정적 웹사이트 파일 저장, 로그 저장, 백업 파일 저장, 첨부파일 저장 등에 사용한다.

예를 들어 문의접수에서 사용자가 PDF나 이미지를 첨부한다면 그 파일을 Storage Account의 Blob Storage에 저장할 수 있다. DB에는 파일 자체를 넣기보다 파일 URL이나 메타데이터만 저장하는 식으로 구성할 수 있다.

Storage Account는 싸고 단순하게 대용량 파일을 저장하기 좋다. 하지만 “사용자 목록을 조건별로 조회하고 관계를 따라가며 비즈니스 데이터를 처리한다” 같은 용도에는 맞지 않는다.

즉 Storage Account는 업무 데이터 DB라기보다는 파일 저장소로 이해하는 게 맞다.

5.4 Blob Storage

Blob Storage는 Storage Account 안에서 객체 파일을 저장하는 서비스다.

Blob은 Binary Large Object의 줄임말인데, 그냥 파일 단위 저장소라고 생각하면 된다. 이미지, PDF, ZIP, 동영상, 정적 웹사이트 파일 같은 것들을 저장한다.

Blob Storage에는 Container라는 단위가 있다. 이 Container 안에 파일들이 들어간다. 이름이 Docker 컨테이너랑 같아서 헷갈릴 수 있는데 완전히 다른 개념이다. Blob Container는 파일을 담는 논리적 공간이고, Docker Container는 이미지를 실행한 프로세스 환경이다.

Blob Storage는 프론트엔드 정적 배포에도 사용할 수 있고, 첨부파일 저장에도 사용할 수 있다.

정적 웹사이트 배포로 쓰면 React 빌드 결과물을 Blob에 올려두고 사용자가 해당 파일을 내려받는다.

첨부파일 저장소로 쓰면 사용자가 업로드한 파일을 Blob에 저장하고, Dataverse에는 파일 이름, 경로, URL, 업로드 시간 같은 메타데이터를 저장할 수 있다.

다만 CRM/Dynamics 쪽에서 파일을 SharePoint에 저장하는 흐름을 이미 사용하고 있다면 Blob Storage를 새로 쓰는 것이 맞는지 확인해야 한다. 파일 저장은 기술적으로 가능하다는 것과 운영상 적합하다는 것이 다를 수 있다.


6. 인증 및 보안

인증과 보안은 파이로에서 꽤 중요하다.

사용자가 이메일과 비밀번호로 로그인하고, 백엔드가 토큰을 발급하고, 그 토큰으로 다른 요청을 허용하는 구조를 생각하고 있기 때문이다.

이때 중요한 건 “로그인 성공 여부”만이 아니다. 비밀번호를 어떻게 저장할지, 토큰을 어떻게 발급할지, 토큰이 유출되면 어떻게 할지, 백엔드가 Dataverse에 접근할 때 필요한 비밀값을 어디에 둘지도 같이 봐야 한다.

6.1 JWT

JWT는 JSON Web Token의 약자다.

로그인 성공 후 백엔드가 발급해주는 토큰이다. 사용자는 이후 API 요청마다 이 토큰을 함께 보내고, 백엔드는 토큰을 검증해서 요청을 허용할지 판단한다.

JWT는 보통 세 부분으로 구성된다. Header, Payload, Signature다. Header에는 토큰 타입과 서명 알고리즘 정보가 들어가고, Payload에는 사용자 식별자나 권한 같은 정보가 들어간다. Signature는 이 토큰이 서버가 발급한 토큰인지 검증하기 위한 서명이다.

중요한 건 JWT의 Payload는 암호화가 아니라 인코딩에 가깝다는 점이다. 그래서 Payload에 비밀번호 같은 민감한 값을 넣으면 안 된다. 토큰을 열어보면 내용이 보일 수 있다.

JWT를 사용하는 흐름은 이렇다. 사용자가 로그인 정보를 보낸다. 백엔드가 이메일과 비밀번호를 검증한다. 맞으면 사용자 ID, 역할, 만료 시간 등을 담은 JWT를 발급한다. 프론트엔드는 이 토큰을 저장하고, 이후 API 요청 시 Authorization 헤더에 넣어 보낸다.

Function App처럼 상태를 오래 들고 있지 않는 백엔드와 JWT는 잘 맞는다. 서버 메모리에 세션을 저장하지 않아도 되기 때문이다. 요청마다 토큰을 검증하면 된다.

하지만 JWT도 단점이 있다. 토큰이 유출되면 만료 전까지는 사용할 수 있다. 그래서 만료 시간을 너무 길게 잡으면 위험하다. Access Token은 짧게, Refresh Token은 더 조심해서 관리하는 식의 설계가 필요할 수 있다.

또 JWT 서명에 사용하는 Secret은 절대 코드에 하드코딩하면 안 된다. GitHub에 올라가면 끝이다. 환경변수나 Key Vault 같은 곳에 저장해야 한다.

6.2 Entra ID

Entra ID는 예전 Azure AD의 새 이름이다.

Microsoft 계정 기반의 인증과 권한 관리를 담당한다. 회사 계정으로 로그인하거나, Azure 리소스 접근 권한을 관리하거나, Microsoft 365 계정을 기반으로 인증할 때 사용한다.

내부 직원용 시스템이라면 Entra ID를 쓰는 게 자연스러울 수 있다. 사용자는 회사 계정으로 로그인하고, 조직의 보안 정책과 MFA를 그대로 적용할 수 있다.

하지만 외부 고객이 별도 이메일과 비밀번호로 로그인하는 커스텀 포털이라면 Entra ID가 바로 맞지 않을 수 있다. 물론 B2C나 외부 ID 같은 선택지도 있지만, 구조가 더 커진다.

Entra ID는 Azure 리소스 권한 관리에도 중요하다. 누가 Azure 포털에 접근할 수 있는지, 누가 특정 리소스를 수정할 수 있는지, 어떤 앱이 어떤 API 권한을 갖는지 같은 것들이 Entra ID와 연결된다.

즉 Entra ID는 단순 로그인 서비스라기보다는 Microsoft 클라우드 전체의 신원 관리 체계로 보는 게 맞다.

6.3 Managed Identity

Managed Identity는 Azure 리소스에 신원을 부여하는 기능이다.

보통 어떤 서비스가 다른 서비스에 접근하려면 client id, client secret 같은 비밀값이 필요하다. 그런데 secret을 직접 만들고 저장하고 교체하는 건 번거롭고 위험하다.

Managed Identity를 쓰면 Azure 리소스 자체가 하나의 신원을 갖는다. 예를 들어 Function App에 Managed Identity를 켜고, Key Vault에 대해 읽기 권한을 주면, Function App은 별도의 비밀번호 없이 Key Vault에 접근할 수 있다.

이 방식의 장점은 secret 관리가 줄어든다는 점이다. 코드에도 secret이 없고, 환경변수에도 secret을 덜 넣을 수 있다.

다만 모든 상황에서 바로 쓸 수 있는 건 아니다. 접근하려는 대상 서비스가 Managed Identity를 지원해야 하고, 권한 설정도 제대로 해야 한다. 처음 설정할 때는 오히려 헷갈릴 수 있다.

작은 프로젝트에서는 처음부터 Managed Identity까지 붙이지 않아도 되지만, Azure 리소스 간 연결이 많아지고 보안 요구사항이 높아지면 점점 필요해진다.

6.4 Key Vault

Key Vault는 Azure의 비밀값 저장소다.

JWT Secret, Dataverse Client Secret, API Key, 인증서 같은 값을 저장할 수 있다.

작은 프로젝트에서는 Function App의 Application Settings에 환경변수로 넣어도 어느 정도 충분하다. 하지만 비밀값이 많아지고, 여러 서비스가 같은 비밀값을 쓰고, 접근 권한을 사람별로 나눠야 하고, 누가 언제 값을 읽었는지 감사가 필요해지면 Key Vault가 더 적합하다.

환경변수와 Key Vault의 차이는 관리 수준이다.

환경변수는 서비스 설정 안에 값을 넣어두고 코드에서 읽는 방식이다. 단순하고 빠르다. 하지만 여러 서비스가 같은 값을 공유하면 각각 복사해서 넣어야 하고, 누가 언제 접근했는지 세밀하게 관리하기 어렵다.

Key Vault는 비밀값을 중앙에서 관리한다. 필요한 서비스에만 읽기 권한을 주고, 값 교체도 한 곳에서 할 수 있다. Managed Identity와 같이 쓰면 Function App이 비밀번호 없이 Key Vault에서 값을 읽는 구조를 만들 수 있다.

단점은 설정이 늘어난다는 점이다. Key Vault 리소스를 만들고, 권한을 설정하고, 앱에서 값을 읽는 구조를 만들어야 한다. 작은 MVP에서는 오히려 개발 속도를 늦출 수 있다.

보안이 중요한 운영 시스템에서는 Key Vault가 맞지만, 프로젝트 초기에는 환경변수로 시작하고 나중에 Key Vault로 옮기는 것도 현실적인 선택이다.


7. 네트워크

네트워크는 사용자가 서비스에 어떻게 접속하고, Azure 내부 리소스들이 서로 어떻게 연결되는지에 대한 영역이다.

처음에는 도메인만 연결하면 끝 같지만, 실제로는 DNS, HTTPS, 포트, 내부 네트워크 같은 개념이 계속 나온다.

7.1 도메인

도메인은 사용자가 접속하는 주소다.

Azure 리소스는 기본 도메인을 제공한다. App Service를 만들면 서비스명.azurewebsites.net 같은 주소가 생기고, Static Web Apps나 Storage Account도 기본 URL이 있다.

하지만 실제 고객사 프로젝트에서는 보통 고객사 도메인을 연결한다. 예를 들어 portal.company.com이나 support.company.com 같은 주소를 쓰게 된다.

도메인은 프론트엔드와 API를 나눌 수도 있다. 프론트는 portal.company.com, API는 api.company.com처럼 구성할 수 있다. 또는 프론트에서 같은 도메인을 쓰고 API 경로만 다르게 프록시할 수도 있다.

도메인 설계는 단순히 주소 예쁘게 정하는 문제가 아니다. 쿠키, CORS, HTTPS 인증서, API 호출 경로에도 영향을 준다.

7.2 DNS

DNS는 도메인을 실제 서비스 주소로 연결해주는 시스템이다.

사용자가 브라우저에 도메인을 입력하면, 브라우저는 DNS를 통해 이 도메인이 어디를 가리키는지 찾는다. Azure 리소스에 커스텀 도메인을 붙일 때도 DNS 설정이 필요하다.

자주 나오는 레코드는 A, CNAME, TXT다.

A 레코드는 도메인을 IP 주소에 연결한다. CNAME은 도메인을 다른 도메인에 연결한다. Azure App Service나 Static Web Apps 같은 서비스는 CNAME으로 기본 도메인에 연결하는 경우가 많다. TXT 레코드는 도메인 소유권 확인에 자주 쓰인다.

DNS 설정은 바로 반영되지 않을 수 있다. TTL 때문에 시간이 걸릴 수 있고, 회사 DNS 관리자가 따로 있으면 요청해서 설정해야 할 수도 있다.

7.3 HTTPS

HTTPS는 암호화된 HTTP 통신이다.

로그인, 비밀번호, 토큰, 개인정보가 오가는 서비스에서는 필수다. HTTP로 로그인 정보를 보내면 중간에서 내용이 노출될 수 있다.

HTTPS는 TLS 인증서를 사용한다. Azure 서비스들은 커스텀 도메인에 대해 인증서를 쉽게 연결할 수 있는 기능을 제공하는 경우가 많다. App Service도 Managed Certificate를 제공하고, Static Web Apps도 커스텀 도메인 HTTPS를 지원한다.

중요한 건 개발 환경에서는 HTTP로 테스트할 수 있어도 운영에서는 반드시 HTTPS를 써야 한다는 점이다. 특히 JWT 기반 인증을 사용할 경우 토큰이 Authorization 헤더로 오가므로 HTTPS가 없으면 위험하다.

7.4 포트

포트는 네트워크 요청을 받는 통로다.

웹에서 외부 사용자는 보통 HTTP 80, HTTPS 443으로 접속한다. 사용자가 https://example.com으로 접속하면 기본적으로 443 포트로 들어온다.

하지만 애플리케이션 내부에서는 다른 포트를 쓸 수 있다. Node 서버는 3000번, Spring Boot는 8080번을 많이 쓴다.

컨테이너 배포에서 포트가 특히 중요하다. App Service가 외부 HTTPS 요청을 받았다고 해도, 컨테이너 내부 애플리케이션이 몇 번 포트에서 요청을 받고 있는지 알아야 그쪽으로 전달할 수 있다.

예를 들어 컨테이너 안의 Node 서버가 3000번에서 실행 중이면 App Service에 컨테이너 포트를 3000으로 알려줘야 한다. 사용자는 443으로 접속하지만, App Service 내부에서는 컨테이너의 3000번으로 요청을 넘기는 구조다.

Function App이나 Static Web Apps를 쓰면 이런 포트 설정을 직접 신경 쓸 일이 거의 없다. 포트는 주로 VM, App Service 컨테이너, Container Apps 같은 배포에서 중요해진다.

7.5 VNet

VNet은 Azure의 가상 네트워크다.

Azure 리소스들을 외부 인터넷에 모두 노출하지 않고, 내부 네트워크 안에서 통신하게 만들 수 있다.

예를 들어 외부에서 직접 접근하면 안 되는 DB가 있고, 특정 App Service나 VM에서만 접근하게 하고 싶다면 VNet 구성을 고려한다. 사내망과 Azure를 연결하거나, VPN/ExpressRoute를 붙이는 경우에도 VNet이 중요하다.

작은 웹 서비스에서는 처음부터 VNet까지 필요하지 않을 수 있다. 하지만 보안 요구사항이 높은 고객사나 내부망 연동이 필요한 프로젝트에서는 VNet이 필수에 가까워질 수 있다.

VNet은 네트워크 지식이 꽤 필요하다. Subnet, Private Endpoint, NSG, Route Table 같은 개념이 같이 따라오기 때문이다.


8. 모니터링 및 운영

서비스를 배포했다고 끝이 아니다.

운영 중에는 반드시 문제가 생긴다. 로그인 실패, API 에러, Dataverse 연결 실패, 배포 실패, 사용자가 “안 된다”고 말하는 상황이 생긴다. 이때 로그와 모니터링이 없으면 원인을 찾기가 너무 힘들다.

8.1 Application Insights

Application Insights는 애플리케이션 모니터링 서비스다.

App Service나 Function App과 연결해서 요청 수, 응답 시간, 실패율, 예외 로그 등을 볼 수 있다.

백엔드 API를 만들면 최소한 다음 정도는 보고 싶어진다. 어떤 API가 얼마나 호출됐는지, 어떤 요청이 실패했는지, 실패 원인이 예외인지 외부 API 문제인지, Dataverse 호출이 느린지 같은 것들이다.

Application Insights는 이런 정보를 모아서 보여준다. 특히 Function App과 같이 쓰면 함수 실행 로그와 실패 내역을 확인하는 데 도움이 된다.

작은 서비스라도 Application Insights는 붙여두는 게 좋다. 비용이 아주 크게 나오지 않는 범위에서 로그를 볼 수 있는 창구가 생기기 때문이다.

8.2 Log Analytics

Log Analytics는 Azure 로그를 모아서 쿼리하고 분석하는 서비스다.

Application Insights의 로그도 Log Analytics Workspace와 연결될 수 있다. 단순히 화면에서 에러 몇 개 보는 것보다, 특정 조건으로 로그를 검색하거나 기간별로 분석해야 할 때 필요하다.

예를 들어 특정 사용자 이메일로 로그인 실패가 반복되는지, 특정 시간대에 문의 등록 API가 실패했는지, Dataverse 응답 시간이 갑자기 늘었는지 같은 걸 추적할 수 있다.

처음에는 어렵게 느껴질 수 있지만, 운영 중 문제가 생겼을 때는 결국 로그를 검색해야 한다.

8.3 진단 로그

진단 로그는 Azure 리소스 자체에서 발생하는 이벤트나 상태 로그다.

애플리케이션 코드 로그와는 조금 다르다. 배포 실패, 인증 실패, 플랫폼 오류, 스케일링 이벤트, 런타임 문제 같은 것들을 확인할 때 본다.

예를 들어 Function App 배포는 성공한 것 같은데 실제 함수가 안 뜬다거나, App Service가 컨테이너를 못 가져온다거나, Storage Account 접근 권한 문제가 생기는 경우 진단 로그가 필요하다.

운영에서는 “코드 로그”와 “Azure 리소스 로그”를 둘 다 봐야 한다. 코드가 문제인지, Azure 설정이 문제인지, 외부 서비스가 문제인지 구분해야 하기 때문이다.


9. CI/CD

CI/CD는 코드를 자동으로 빌드하고 배포하는 흐름이다.

수동 배포는 처음에는 괜찮아 보이지만, 반복되면 실수도 많아지고 시간도 오래 걸린다. 특히 프론트와 백엔드를 따로 배포해야 하면 자동화가 필요하다.

9.1 GitHub Actions

GitHub Actions는 GitHub에서 제공하는 자동화 도구다.

코드가 push되거나 PR이 생성될 때 특정 작업을 자동으로 실행할 수 있다. 테스트, 빌드, 린트, 배포 같은 작업을 workflow로 정의한다.

프론트엔드라면 push 시 npm install, npm run build를 실행하고 빌드 결과물을 Storage Account에 업로드할 수 있다.

백엔드 Function App이라면 push 시 필요한 패키지를 설치하고 Azure Function App에 배포할 수 있다.

GitHub를 저장소로 쓰고 있다면 GitHub Actions가 가장 자연스럽다. 별도 도구를 도입하지 않아도 되고, 레포 안에 workflow 파일을 두면 된다.

9.2 Azure DevOps

Azure DevOps는 Microsoft의 DevOps 플랫폼이다.

Repos, Pipelines, Boards, Artifacts 같은 기능을 제공한다. 회사 단위로 Azure DevOps를 표준으로 쓰는 경우도 많다.

GitHub Actions와 역할이 겹치는 부분이 많다. 둘 다 빌드와 배포 자동화를 할 수 있다.

어떤 걸 쓸지는 기술적으로만 결정하지 않는다. 회사가 어디에 코드를 두는지, 보안 정책이 어떤지, 기존 프로젝트들이 어떤 파이프라인을 쓰는지에 따라 달라진다.

이미 Azure DevOps 기반으로 운영 중인 회사라면 그쪽을 따르는 게 맞을 수 있다. 반대로 GitHub 레포 중심이면 GitHub Actions가 더 빠르다.

9.3 배포 파이프라인

배포 파이프라인은 코드를 운영 환경에 반영하는 일련의 자동화 흐름이다.

프론트엔드 정적 배포라면 보통 의존성 설치, 빌드, 산출물 업로드 순서다.

백엔드 Function App 배포라면 의존성 설치, 함수 빌드 또는 패키징, Azure 배포 순서가 된다.

컨테이너 배포라면 여기에 Docker Build, ACR Push, 실행 서비스 업데이트가 추가된다.

중요한 건 배포 파이프라인이 단순해야 한다는 점이다. 프로젝트가 작은데 파이프라인만 너무 복잡하면 유지보수가 힘들다.

9.4 자동 배포 전략

자동 배포 전략은 어떤 브랜치가 어떤 환경으로 배포되는지 정하는 것이다.

가장 단순하게는 develop 브랜치는 개발 환경, main 브랜치는 운영 환경으로 둘 수 있다.

작은 프로젝트라도 개발 환경과 운영 환경은 분리하는 게 좋다. 개발 중 실수로 운영 Dataverse에 데이터를 넣거나, 테스트 배포가 운영에 바로 반영되면 위험하다.

처음부터 배포 전략을 너무 복잡하게 가져갈 필요는 없지만, 최소한 개발과 운영은 분리해야 한다.


10. 파이로 적용 기준 정리

이 장에서만 파이로 적용 여부를 한 번에 정리한다.

앞에서는 각 기술 자체를 이해하는 데 집중했고, 여기서는 파이로 요구사항 기준으로 어떤 구조가 맞는지 판단한다.

10.1 파이로 요구사항 정리

현재 파이로는 고객 문의접수용 커스텀 포털을 만드는 프로젝트다.

예상되는 핵심 기능은 다음과 같다.

  • 외부 고객이 문의접수 페이지에 접속한다.
  • 사용자가 이메일과 비밀번호로 로그인한다.
  • 백엔드는 Dataverse에서 사용자 정보를 확인한다.
  • 인증이 성공하면 백엔드가 JWT를 발급한다.
  • 사용자는 JWT가 만료되기 전까지 해당 토큰으로 API를 호출한다.
  • 문의접수 데이터는 Dataverse, 즉 CRM 쪽에 등록된다.
  • API 개수는 인증 포함 10개 미만으로 예상된다.
  • 고객 문의가 아주 자주 발생하는 서비스는 아닐 가능성이 높다.
  • 사용자가 폭발적으로 늘어나는 서비스도 아닐 가능성이 높다.

이 요구사항을 보면 대규모 웹서비스라기보다는 “고객사 업무용 커스텀 포털 + CRM 연동 API”에 가깝다.

그래서 처음부터 과한 인프라를 잡는 것보다 단순하고 운영 부담이 적은 구조가 맞다.

10.2 프론트엔드 배포 선택

프론트엔드는 React 기반 문의접수 페이지가 될 가능성이 높다.

React SPA라면 빌드 결과물은 정적 파일이다. 서버에서 Node가 계속 떠 있을 필요가 없다. 사용자가 접속하면 HTML, JS, CSS 파일을 받고, React는 브라우저에서 실행된다.

따라서 프론트엔드는 Blob Storage 정적 웹사이트 배포가 가장 현실적이다.

Static Web Apps도 가능하지만, Storage Account로 충분해 보인다. 이미 회사에서 Blob Storage 기반 정적 배포를 쓰고 있거나, 구조를 단순하게 가져가고 싶다면 Storage Account 쪽이 더 낫다.

CDN은 초기에는 필요 없어 보인다. 사용자가 많지 않고 특정 고객사용 포털이라면 CDN까지 붙여도 얻는 이득이 크지 않다.

10.3 백엔드 선택

백엔드는 Function App이 가장 적합해 보인다.

이유는 API 개수가 적고, 각 API가 독립적인 작업에 가깝기 때문이다. 로그인, 토큰 재발급, 문의 등록, 문의 조회 정도라면 계속 떠 있는 서버보다 Function 단위가 더 단순하다.

App Service도 가능은 하다. Express나 NestJS로 백엔드 서버를 만들고 App Service에 올리면 된다. 하지만 서버가 계속 떠 있어야 하고, 현재 규모에서는 비용과 구조가 조금 무겁다.

Container Apps는 Docker 기반 배포를 해야 할 이유가 생기면 볼 수 있다. 하지만 지금은 Docker 이미지를 만들 필요가 없다.

AKS는 전혀 필요 없어 보인다. 컨테이너가 많지도 않고, 마이크로서비스 구조도 아니고, Kubernetes 운영을 할 이유도 없다.

VM도 필요 없다. API 몇 개 만들려고 OS부터 직접 관리하는 건 너무 무겁다.

10.4 데이터 저장소 선택

데이터 저장소는 Dataverse가 맞다.

파이로는 고객 문의를 CRM에 등록하는 프로젝트다. 데이터가 결국 CRM에서 관리되어야 한다면 Dataverse에 바로 저장하는 것이 자연스럽다.

Azure SQL을 따로 두면 데이터가 분산된다. 문의 데이터는 Azure SQL에 있고 CRM에는 다시 동기화해야 하는 구조가 될 수 있다. 그러면 개발과 운영이 더 복잡해진다.

첨부파일이 있다면 저장 위치는 별도로 검토해야 한다. Blob Storage에 저장할 수도 있고, CRM/Dynamics 운영 방식에 따라 SharePoint를 쓰는 게 나을 수도 있다. 이건 고객사 파일 관리 정책을 확인하고 정해야 한다.

10.5 인증 선택

인증은 JWT 기반으로 가는 것이 맞아 보인다.

Function App은 서버 세션을 들고 있는 구조와 잘 맞지 않는다. 요청마다 Authorization 헤더의 JWT를 검증하는 방식이 더 자연스럽다.

로그인 시 사용자가 이메일과 비밀번호를 입력하면, Function App이 Dataverse에서 사용자 정보를 조회하고 비밀번호를 검증한다. 성공하면 JWT를 발급한다. 이후 문의 등록이나 조회 요청은 JWT 검증 후 처리한다.

여기서 가장 중요한 건 비밀번호 저장 방식이다.

Dataverse에 비밀번호를 평문으로 저장하면 안 된다. 최소한 hash/salt 방식으로 저장해야 한다. 관리자가 비밀번호를 확인할 수 있어야 한다는 요구사항이 있다면 단순 해시로는 불가능하므로 요구사항 자체를 다시 봐야 한다. 보안상 관리자가 사용자의 원문 비밀번호를 보는 구조는 추천하기 어렵다.

JWT Secret과 Dataverse Client Secret 같은 값은 초기에는 Function App 환경변수에 두면 충분해 보인다.

사용자 수가 많지 않고 서비스도 작다면 Key Vault까지 처음부터 붙이는 건 조금 무거울 수 있다. 다만 운영 보안 요구사항이 높거나 비밀값이 많아지면 Key Vault를 도입할 수 있다.

10.6 모니터링 선택

Application Insights는 붙이는 게 좋다.

작은 서비스라도 운영 중 문제가 생기면 로그가 필요하다. 특히 로그인 실패, JWT 검증 실패, Dataverse API 실패, 문의 등록 실패 같은 부분은 반드시 확인할 수 있어야 한다.

처음부터 복잡한 대시보드를 만들 필요는 없지만, 최소한 Function App 로그와 실패 요청을 볼 수 있게 해두는 것이 좋다.

Log Analytics는 Application Insights와 함께 연결되는 경우가 많으므로 운영 중 필요하면 같이 보면 된다.

10.7 CI/CD 선택

GitHub를 쓴다면 GitHub Actions가 가장 단순하다.

프론트엔드는 main 브랜치에 push되면 빌드 후 Blob Storage에 업로드한다.

백엔드는 main 브랜치에 push되면 Function App에 배포한다.

개발 환경과 운영 환경은 분리하는 것이 좋다. 처음에는 단순하게 develop은 개발, main은 운영 정도로 나눌 수 있다.

컨테이너 배포가 아니므로 ACR은 필요 없다.

10.8 최종 구조

현재 기준 파이로에 가장 맞는 구조는 다음과 같다.

프론트엔드는 React를 빌드해서 Blob Storage 정적 웹사이트로 배포한다.

백엔드는 Azure Function App으로 만든다.

데이터는 Dataverse에 저장한다.

인증은 JWT로 처리한다.

비밀값은 초기에는 Function App Application Settings에 둔다.

모니터링은 Application Insights를 붙인다.

CI/CD는 GitHub Actions를 사용한다.

정리하면 다음과 같다.

  • Frontend: React + Blob Storage 정적 웹사이트
  • Backend: Azure Function App
  • Data: Dataverse
  • Auth: JWT
  • Secret: Function App 환경변수
  • Monitoring: Application Insights
  • CI/CD: GitHub Actions
  • 불필요: VM, App Service, ACR, Container Apps, AKS, Azure SQL, CDN

이 구조가 현재 요구사항 대비 가장 단순하고, 비용도 적고, 운영 부담도 낮다.

나중에 백엔드가 커지고 API가 많아지거나, 컨테이너 배포가 필요해지거나, 여러 서비스로 쪼개야 하는 상황이 오면 그때 App Service나 Container Apps를 다시 검토하면 된다.

지금 단계에서 중요한 건 모든 Azure 서비스를 다 쓰는 게 아니라, 파이로에 필요한 만큼만 쓰는 것이다.

반응형