
부트캠프에서 배운 내용을 정리한 글입니다.
Easyrec Architecture
위의 아키텍처를 예시로 보았을 때 개발자 관점의 Frontend와 Backend 그리고 보안 관점의 Client-side와 Server-side는 무엇을 기준으로 구분하느냐에 따라 비슷하지만 그 관점이 달라진다.
frontend는 사용자 경험을 향상시키기 위한 인터페이스 구현에 초점이 맞추어져 있으며, backend는 로직 안정성, 성능, 데이터 무결성, API 설계에 초점이 맞추어져 있다.
반면 보안 관점에서는
client-side는 공격자가 직접 접근하고 조작이 가능한 비신뢰 영역으로 보고있고, server-side에서는 공격자가 직접 접근할 수 없고 위조된 요청이나 취약점을 이용한 간접적인 접근만 가능한 부분으로 보고있다. 즉, 어디를 어디까지 신뢰 가능한가를 기준으로 나눈다고 볼 수 있다.
물론 보안에서 완벽하게 신뢰할 수 있는 구간이란 존재하지 않는다. 다만 중요한 점은, 동일한 시스템 구조라 할지라도 개발 관점에서는 기능적 역할을 중심으로, 보안의 관점에서는 신뢰 여부와 Trust Boundary 중심으로 구분한다는 것이다.
┌───────────────────────────────────┐
│ Single Application Server │
│ ┌───────────────────────────────┐ │
│ │ Presentation Layer (UI) │ │
│ └───────────────────────────────┘ │
│ ┌───────────────────────────────┐ │
│ │ Business Logic Layer │ │
│ └───────────────────────────────┘ │
│ ┌───────────────────────────────┐ │
│ │ Data Access Layer │ │
│ └───────────────────────────────┘ │
└──────────────────┬────────────────┘
▼
┌─────────────┐
│ Database │
└─────────────┘
초기 기업 인프라는 물리 하드웨어(서버, 스토리지, 네트워크) → 네트워크 연결 및 구성을 통한 통합 → 소프트웨어 설치 및 운영 순으로 진행되었다.
이 방식에서는 하드웨어 엔지니어, 네트워크 엔지니어, 소프트웨어 개발자가 각각 분리된 역할로 작업해야 했으며, 서버 자원 확보와 설치, 네트워크 구성 및 라우팅/방화벽 설정, 소프트웨어 배포/설정까지 모든 단계들이 순차적이고 의존적이어서 전체적으로 설정 시간이 매우 길었다.
또한, 서버 프로비저닝 후 변경이 어려운 이유로 리소스가 고정되며, 확장성이 낮고, 과도하게 높아지는 비용 등의 문제로 다음의 문제점이 있었다.
이러한 단점은 현재의 레거시 시스템까지 이어져 다음의 다양한 보안취약점을 일으키고 있다.
기업이 빠르게 변화하는 비즈니스 요구사항에 대응하려면 빠른 설정, 확장, 배포 속도가 필수적이었다. 기존 물리 인프라는 이런 유연성과 자동화 측면에서 한계가 있었고 AWS는 이 한계를 극복하기 위해 하드웨어, 네트워크, 소프트웨어 스택을 추상화하고 표준화된 API로 제공하는 모델로 전환했다.
그렇게 탄생한 것이 바로 클라우드 아키텍처이다.
외부 접속자에 따른 고가용성 로드밸런스를 해야한다.
가상화는 갑작스러운 대응에 어렵다. 이는 가상화의 한계다.
모노리식은 웹과 DB의 결합이 본래 목적이었고 갑작스러운 발전에 대응하지 못했다. 이 분명한 한계를 극복한 것이 바로 클라우드이다.
DevOps Pipline여담) 온프레미스 환경에서 클라우드 환경으로의 전환이 진행중이다. 온프레미스 환경보다는 클라우드 환경을 준비해야하고 앞으로의 프로젝트도 이렇게 준비하는 것이 맞다고 강사님이 이야기하셨다.
API TMI)
딕셔너리를 이용해서 API를 내부에서 동작하는 것과 외부에서 동작하는 것을 구분한다.
API 점검에서 API 팬테스트에서 딕셔너리를 요청하여 화이트박스 테스트를 하고 제공되지 않으면 블랙박스 테스트를 한다. 블랙박스는 말그대로 수단과 방법을 가리지 않고 어떻게든 뚫는 것이다.
C 언어의 구조체. 형식을 구조화해서 만든 것을 API라고 할 수 있다. 더 각광받은 것이 웹에서 모바일과 서드파티에서 딕셔너리를 만들고 전달하면서 시작했다. 일종의 모바일이 촉매제가 되어 API 개발에 너무 큰 프로젝트가 되므로 마이크로서비스 아키첵처가 더욱 주목받은 것이다.
람다 등을 이용해서 클라우드에 요청을 하는 방식. 그러니깐 처리 로직을 클라우드의 함수(?)에 던지고 클라우드에 있는 DB까지 이용료가 부과된다.
오버라이트 등의 문제가 발생하지 않도록 설계를 잘해야한다.
앞서 언급했듯이 가상화는 오토스케일링이 구조적으로 어렵다. 그래서 등장한 것이 컨테이너이다. 컨테이너는 가상화가 아니다. 따라서 가상화와 다르게 보안에 취약할 수 있다. 가상화는 명령을 실행할 수 있는 명령어 모음이다.
가상화는 기존 CPU에 VCPU를 추가로 만들고 우리가 쓰고있는 하드웨어에 이걸 붙이고 VRAM과 VDISK를 만들어서 올린다. 장점으로는 아이솔레이션이 되고 실제가 아닌 전부 가상이므로 모든 가상환경마다 OS를 올리고 하드웨어를 공유하여 사용한다. 디스크 IO가 발생하면 그 속도는 당연히 느려질 수 밖에 없다. 이 VDI를 많이 사용했지만 부하가 늘어날수록 하드웨어 성능은 계속 부족해지니 가상화를 사용하지 않는 것이 더 이득인 수준까지 온다.
격리하고 OS를 설치한다는 이 근본적인 문제를 컨테이너는 극복할 수 있었다. 현대 세상은 코드 베이스로 돌아가는 이 컨테이너가 주류라고 볼 수 있다.
https://docs.rockylinux.org/9/ko/gemstones/containers/docker/
도커 저장소를 추가한다.

만약 명령어가 작동하지 않는다면 dnf 업데이트가 필요하므로 아래의 명령어 입력.
sudo dnf update -y
이후 위의 링크를 따라서 필요한 패키지를 설치하고 docker 서비스를 시작과 동시에 활성화하는 명령어를 입력한다.

[user@localhost ~]$ sudo usermod -aG docker user
docker 사용자 추가. docker 명령을 쓸 때 매번 sudo를 치지 않기 위해서, 즉 해당 사용자에게 Docker 데몬에 접근할 수 있는 권한을 부여하기 위해서 위의 명령어를 입력했다.
다만, docker 그룹은 사실상 root와 거의 동일한 권한을 의미한다. 따라서 오직 신뢰 가능한 사용자만 docker 그룹에 넣어야 하며 가능하면 rootless docker를 고려해야 한다.
이후 잠시 exit하고 docker 그룹을 확인한다.
[user@localhost ~]$ id
uid=1000(user) gid=1000(user) groups= ... 996(docker) ...
[user@localhost ~]$ docker -v
Docker version 28.5.1, build e180ab8
[user@localhost ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
[user@localhost ~]$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
[user@localhost ~]$ docker search ubuntu
NAME DESCRIPTION STARS OFFICIAL
ubuntu Ubuntu is a Debian-based Linux operating sys… 17709 [OK]
ubuntu/squid Squid is a caching proxy for the Web. Long-t… 119
...
ubuntu/telegraf Telegraf collects, processes, aggregates & w… 4
ubuntu/chiselled-jre [MOVED TO ubuntu/jre] Chiselled JRE: distrol… 3
[user@localhost ~]$ docker pull ubuntu:latest
latest: Pulling from library/ubuntu
4b3ffd8ccb52: Pull complete
Digest: sha256:6646..
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest
위에서 도커에서 버전을 확인하고 이미지를 검색한다음 ubuntu 이미지를 다운로드 했다.
[user@localhost ~]$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[user@localhost ~]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[user@localhost ~]$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 97bed23a3497 2 weeks ago 78.1MB
이미지만 다운로드 받고 아직 컨테이너에 넣지는 않았다.
자식 프로세스를 하나 더 만들어서 그 안에서 명령어를 실행시켜준다.
inspect 명령어로 JSON포맷의 이미지 세부정보를 확인할 수 있다. 이 뜻은 파이썬 같은 언어로 해당 데이터를 다룰 수 있다는 뜻이다.
[user@localhost ~]$ docker container run --name first-ubuntu ubuntu:latest
[user@localhost ~]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
51e2b2e6e71b ubuntu:latest "/bin/bash" 23 seconds ago Exited (0) 22 seconds ago first-ubuntu
[user@localhost ~]$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
STATUS가 Exited (0)인 것과 ls 했을 때 표시되지 않는 것을 확인할 수 있다. Ubuntu 기본 이미지(위의 ubuntu:latest)는 실행할 명령을 지정하지 않으면 /bin/bash 를 기본 entrypoint로 실행한다. 하지만 tty(-it) 옵션 없이 bash가 실행되면 즉시 종료된다. 따라서 컨테이너는 해야 할 작업 없으니 종료 상태가 된다.
따라서 현재 컨테이너 상태는 Exited이기 때문에 docker container ls 명령어인 Running 중인 컨테이너에 보이지 않고 종료된 컨테이너 포함 전체를 표시하는 docker ps -a 명령어에 표시가 되는 것이다.
키 값이나 접속 정보가 든 코드를 허브에 올렸을 때 생기는 보안 문제가 발생한 적이 있으며 이는 매우 위험하다.
Docker Hub에 올린 이미지 안에 민감정보가 포함되면 보안 사고가 일어난다. 특히 설정 파일, ssh private key, db 접속 정보, api key/token, 인증서 등이 도커 이미지 안에 포함될 수 있고 Docker Hub에 push하면 이 곳이 공개 저장소면 누구나 pull 가능하고, 프라이빗 저장소라도 내부 인원이 실수로 유출할 수 있다. 따라서 주의가 필요하다.
아무튼 아래에서 우분투 이미지를 이용해서 명령어로 열었다. 우분투 이미지로 만들면서 ubuntu_1 이라는 컨테이너를 만들면서 /bin/bash라는 쉘을 만들었다.
[user@localhost ~]$ docker container run -it --name ubuntu_1 ubuntu /bin/bash
root@9980049dd25e:/#
root@9980049dd25e:/# id
uid=0(root) gid=0(root) groups=0(root)
이후 다른 터미널로 접속해서 다음의 명령어를 작성한다.
[user@localhost ~]$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9980049dd25e ubuntu "/bin/bash" 2 minutes ago Up 2 minutes ubuntu_1
[user@localhost ~]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9980049dd25e ubuntu "/bin/bash" 2 minutes ago Up 2 minutes ubuntu_1
[user@localhost ~]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9980049dd25e ubuntu "/bin/bash" 2 minutes ago Up 2 minutes ubuntu_1
51e2b2e6e71b ubuntu:latest "/bin/bash" 17 minutes ago Exited (0) 17 minutes ago first-ubuntu
[user@localhost ~]$
이제 서로 다른 터미널에서 ps -ef 명령어를 실행한다.
root@9980049dd25e:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 06:07 pts/0 00:00:00 /bin/bash
root 11 1 0 06:12 pts/0 00:00:00 ps -ef
[user@localhost ~]$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 12:58 ? 00:00:07 /usr/lib/systemd/systemd --switc
root 2 0 0 12:58 ? 00:00:00 [kthreadd]
...
root 59657 2 0 15:12 ? 00:00:00 [kworker/1:0-ata_sff]
user 59906 59290 0 15:13 pts/1 00:00:00 ps -ef
[user@localhost ~]$
위의 예시가 바로 컨테이너가 가상화가 아니라는 증거이다. 일반 사용자 계정으로 ps -ef 하면 무수히 많은 프로세스가 표시되지만 컨테이너는 접속하니깐 쉘과 ps -ef 프로세스 단 2개만 동작하고 있다. 이것은 최소한의 운영체제로서 동작하기 위한 필요충분 조건이 전혀 아니라고 보여진다. 즉, 해당 환경에서 명령어를 실행할 수 있는 라이브러리를 제공하는 것이 바로 컨테이너이다.
이렇듯 우분투 이미지만 올릴 수 있듯이 웹 소스코드를 올려놓고 컨테이너로 마운트하는 등의 작업을 할 수 있다. 스케일링하는 데에 이만한 장점이 없다.
해커 관점에서도 컨테이너에 접근해서 쉘 권한을 얻으려고 하거나 얻더라도 할 수 있는 활동이 매우 제한적이고 어렵다. 이건 전체 시스템을 컨테이너로 세분화할 수록 그 난이도가 증가할 수 밖에 없다. 물론 라이브러리 자체의 취약점이 존재한다면 이건 어쩔 수 없다..
TMI) EOL (End of Life)
EOL은 제품 및 소프트웨어의 공식 지원이 종료되는 시점을 의미한다.
제품이 EOL 되었다는 뜻은 CVE나 다양한 잠재적 보안 취약점에 노출되어있을 가능성이 높다. EOL은 다음의 사이트에서 찾아볼 수 있다. https://endoflife.date/docker-engine
EOL 된 버전을 사용하지 않는 것이 좋지만 피치못할 경우 위의 이유로 버전 정보를 숨기는 것은 매우 중요하다.
[user@localhost ~]$ docker container run -it --name ubuntu_2 ubuntu /bin/bash
root@927f12fef889:/# [user@localhost ~]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
927f12fef889 ubuntu "/bin/bash" 13 seconds ago Up 13 seconds ubuntu_2
9980049dd25e ubuntu "/bin/bash" 40 minutes ago Exited (130) 2 minutes ago ubuntu_1
51e2b2e6e71b ubuntu:latest "/bin/bash" 54 minutes ago Exited (0) 54 minutes ago first-ubuntu
Ctrl+p+q로 빠져나오면 컨테이너가 중지되지 않고 서비스가 지속된다.
[user@localhost ~]$ docker container start ubuntu_1
ubuntu_1
[user@localhost ~]$ docker attach ubuntu_1
root@9980049dd25e:/# exit
[user@localhost ~]$ docker attach ubuntu_2
root@927f12fef889:/# exit
exit
[user@localhost ~]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
927f12fef889 ubuntu "/bin/bash" 6 minutes ago Exited (0) 6 seconds ago ubuntu_2
9980049dd25e ubuntu "/bin/bash" 46 minutes ago Exited (127) 23 seconds ago ubuntu_1
51e2b2e6e71b ubuntu:latest "/bin/bash" About an hour ago Exited (0) About an hour ago first-ubuntu
exit 후엔 start하고 attach로 들어갈 수 있고 Ctrl+p+q 로 중지하지 않고 나갈 수 있다. 사용하지 않는 컨테이너를 stop하지 않고 나가고 이게 쌓이다보면 리소스가 낭비된다.
root@9980049dd25e:/# cat /etc/*release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu 24.04.3 LTS"
PRETTY_NAME="Ubuntu 24.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.3 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
해당 명령어는 어느 환경에서든 운영체제 종류와 버전을 확인할 수 있는 명령어이다. uname 이라던가 이런거 사용하지말고 이거 쓰는게 좋다.
간혹 쉘도 ps명령어도 없는 그런 종류의 컨테이너도 있지만 이건 작정하고 은닉한거라 확인하는 게 어렵다.
[user@localhost ~]$ ps -ef | grep ssh[d]
root 782 1 0 12:59 ? 00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root 42039 782 0 14:20 ? 00:00:00 sshd: user [priv]
root 42043 782 0 14:20 ? 00:00:00 sshd: user [priv]
...
[user@localhost ~]$ cat /var/run/sshd.pid
782
[user@localhost ~]$ pgrep sshd
782
42039
42043
...
[user@localhost ~]$ pstree -p 782
sshd(782)─┬─sshd(42039)───sshd(42044)───bash(42068)───docker(74931)─┬─{docker}(7493+
│ ├─{docker}(7493+
│ ├─{docker}(7493+
│ ├─{docker}(7493+
│ ├─{docker}(7493+
│ ├─{docker}(7493+
│ ├─{docker}(7493+
│ └─{docker}(7698+
├─sshd(42043)───sshd(42048)───sftp-server(42049)
├─sshd(59261)───sshd(59266)───bash(59290)───pstree(76982)
└─sshd(59265)───sshd(59270)───sftp-server(59271)
[user@localhost ~]$
위의 ps 명령어를 사용하지 못할 때는 다음의 방법을 사용한다.
[user@localhost ~]$ sudo ls -l /proc/782/exe
[sudo] password for user:
lrwxrwxrwx. 1 root root 0 Oct 20 12:59 /proc/782/exe -> /usr/sbin/sshd
커널이 로드되는 위치의 proc 아래에 번호가 PID 번호이고 exe가 있으면 찾을 수 있다. ps 명령어에 의존하지 않고도 사용자가 동작시키는 프로세스를 찾을 수 있다.
[user@localhost ~]$ for i in $(sudo ls /proc | grep ^[0-9] | sort -n); do
> sudo ls -l /proc/$i/exe 2> /dev/null
> done
[sudo] password for user:
lrwxrwxrwx. 1 root root 0 Oct 20 12:59 /proc/1/exe -> /usr/lib/systemd/systemd
lrwxrwxrwx. 1 root root 0 Oct 20 16:21 /proc/2/exe
lrwxrwxrwx. 1 root root 0 Oct 20 16:21 /proc/3/exe
lrwxrwxrwx. 1 root root 0 Oct 20 16:21 /proc/4/exe
lrwxrwxrwx. 1 root root 0 Oct 20 16:21 /proc/5/exe
...
lrwxrwxrwx. 1 root root 0 Oct 20 16:22 /proc/77029/exe
lrwxrwxrwx. 1 root root 0 Oct 20 16:22 /proc/77543/exe
# 또는
[user@localhost ~]$ for i in `sudo ls /proc | grep ^[0-9] | sort -n`
> do
> sudo ls -l /proc/$i/exe 2> /dev/null
> done
PID 중 exe로 되어있는 리스트를 가져올 때
컨테이너 환경 보안 점검에서 잘 사용할 수 있다. 아주 중요한 스킬이며 꼭 기억해두는 것이 좋다.
/proc 디렉터리는 커널이 현재 실행 중인 프로세스마다 PID 이름으로 만든 디렉터리를 포함하고 있다.grep ^[0-9] 는 이름이 숫자로 시작하는 디렉터리만 추출한다. 이는 /proc 에는 cpuinfo , meminfo , sys, net 등의 비프로세스 디렉터리도 존재하기 때문이다.sort -n 으로 PID를 숫자 크기로 정렬한다.for i in $( ... ) 반복문으로 각각의 프로세스 디렉터리를 검색한다.[user@localhost ~]$ docker container run --privileged -it --name web2 -p 172.17.0.1:8008:80 ubuntu:latest /bin/bash
root@6e0f0739721c:/# apt-get update
root@6e0f0739721c:/# apt-get install -y apache2
root@6e0f0739721c:/# service apache2 start
root@6e0f0739721c:/# echo “ubuntu test page” > /var/www/html/index.html
<ctrl + p + q>
[user@localhost ~]$ curl http://172.17.0.1:8008
ubuntu test page

attach 후 명령어를 입력했다.
[user@localhost ~]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6e0f0739721c ubuntu:latest "/bin/bash" 9 minutes ago Up 9 minutes 10.0.2.130:8008->80/tcp web2
http를 볼 때 두 가지를 알고있어야 한다.
https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=linux&ackey=lzb0lovb
도메인과 파일경로 | 변수와 값
------------------------------------------------------------
where=nexearch
& <<<<< 구분 기호 (chatgpt 자세한 설명 필요
sm=top_hty
&
fbm=0
&
ie=utf8
&
query=linux
&
ackey=lzb0lovb
이를 설계한 내부의 개발자가 DB나 Web Application으로 넘기는 값이 있을 것이고, 이를 파라미터라고 부른다. 파라미터의 값이 필터링이 제대로 되지 않아 공격이 가능하면 이것은 인젝션 공격인 것이고 이 공격지점을 공격벡터라고 부른다.
API 딕셔너리를 통해 우리가 호출할 수 있는 목록이 정해져있고 브라우저에서 사이트에 접근한 경로의 흐름이 중요한게 아니라 변수 값에 어떤 것을 넘기는 게 중요하다. 파라미터가 네이버에서 알아서 붙여준것을 API라고는 볼 수는 없다. 물론 내부자 입장에서는 API로 볼수도 있을 것이다.
외부에서 사용하는 API는 인증만 된다면 사용할 수 있도록 인증하고 Auth를 이용하여 API 키를 이용한 인증 대행으로 처리해주는 로직으로 얻을 수 있는 보안적 이점이 있다.
또한 인증과 계정에 필요한 보안 솔루션에 들어가는 비용이 api를 사용하는 비용보다 훨씬 많으 들어가므로 이것도 금전적 이점이 있다.
Python과 Flask를 준비한다.

Python 실습코드를 준비한다.
# app.py
from flask import Flask, request, jsonify
app = Flask(__name__)
# 1. 기본 Hello API
@app.route("/hello")
def hello():
return jsonify(message="Hello, API!")
# 2. 경로 파라미터
@app.route("/hello/<name>")
def hello_name(name):
return jsonify(message=f"Hello, {name}!")
# 3. 쿼리 파라미터
@app.route("/greet")
def greet():
lang = request.args.get("lang", "en")
if lang == "ko":
return jsonify(message="안녕하세요!")
elif lang == "es":
return jsonify(message="¡Hola!")
else:
return jsonify(message="Hello!")
# 4. POST 요청 처리
@app.route("/echo", methods=["POST"])
def echo():
data = request.get_json(silent=True) # 파싱 실패 시 None 반환
if data is None:
return jsonify(error="Invalid or missing JSON"), 400
return jsonify(you_sent=data)
if __name__ == "__main__":
app.run(debug=True)
소스코드를 실행한다.

루프백으로 접속해서 이것저것 입력해 본다.




브라우저의 URL에 파라미터를 넘기는 GET방식이 보인다.
일반적으로 GET으로 민감정보를 넘기면 URL에 그대로 노출되기 때문에 절대 권장하지 않는 방식이라고 한다. 그렇다고 POST 방식은 안전하냐고 한다면 그것도 절대 안전하지 않다. 결국 GET방식이든 POST방식이든 안전한 건 없고 http 그 자체가 안전하지 않은 것이 핵심이다. 그래서 https를 사용해야 한다.
이건 POST방식이다.



postman으로 테스트 해보았다.
REST API는 HTTP 기반에서 자원을 URI로 표현하고, 해당 자원에 대한 행위를 HTTP 메서드(GET, POST, PUT, DELETE 등)로 구분하는 방식의 API 구조이다.
주요 특징은 다음과 같다:
/users/1, /products/123GET : 조회POST : 생성PUT : 전체 수정PATCH : 부분 수정DELETE : 삭제이러한 표준화된 방식 덕분에 다양한 플랫폼·언어에서 공통된 방식으로 API를 사용할 수 있다.







REST API는 브라우저에서 직접 테스트하기 어려운 경우가 많기 때문에, Postman과 같은 API 테스트 도구를 사용한다. GET 요청 테스트, POST 요청 테스트 등의 다양한 REST API 요청을 보낼 수 있으며, raw 포맷으로 파라미터를 넣을 수 있다. POST 요청의 Body를 JSON 형태로 직접 작성할 수 있다는 뜻이다.

msa-docker-manual/
├── user-service/
│ ├── app.py
│ └── Dockerfile
├── product-service/
│ ├── app.py
│ └── Dockerfile
├── order-service/
│ ├── app.py
│ └── Dockerfile
/srv/에 msa-docker-manual 디렉토리를 만들고 그 하위에 실습을 구성하였다. 이후 vi 편집기를 통해 코드를 집어넣었다.
FROM python:3.9
WORKDIR /app
COPY app.py .
RUN pip install flask requests
CMD ["python", "app.py"]
user-service/users.py
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/users")
def get_users():
return jsonify([{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}])
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001)
product-service/products.py
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/products")
def get_products():
return jsonify([{"id": 101, "name": "Laptop"}, {"id": 102, "name": "Phone"}])
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5002)
order-service/orders.py
from flask import Flask, jsonify
import requests
app = Flask(__name__)
@app.route("/orders")
def get_orders():
users = requests.get("http://user-service:5001/users").json()
products = requests.get("http://product-service:5002/products").json()
return jsonify({
"order_id": 9001,
"user": users[0],
"product": products[1]
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5003)

docker network create msa-net
docker build -t user-service ./user-service
docker build -t product-service ./product-service
docker build -t order-service ./order-service
네트워크 생성 후 각 서비스 빌드
docker run -d --name user-service --network msa-net -p 5001:5001 user-service
docker run -d --name product-service --network msa-net -p 5002:5002 product-service
docker run -d --name order-service --network msa-net -p 5003:5003 order-service
-network msa-net 옵션을 통해 컨테이너들이 같은 네트워크에서 통신 가능하게 설정한다.
curl http://localhost:5003/orders

VM 외부 웹 브라우저를 통해 접속한 결과는 위와 같다.
이후 아래의 명령어를 통해
docker network inspect msa-net
실제로 어떤 네트워크 설정이 되었는지 확인한다.
이 명령은 Docker 네트워크의 상세한 설정을 JSON 형태로 보여주는데, 주요 확인 포인트만 고르면 다음과 같다.
[user@localhost msa-docker-manual]$ docker network inspect msa-net
"Name": "msa-net",
"Driver": "bridge",
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
즉, msa-net 안의 컨테이너들은 모두 172.18.0.0/16 범위의 IP를 부여받는다.
inspect 결과의 핵심은 아래와 같이 요약했다.
"Containers": {
"0c9846...": {
"Name": "user-service",
"IPv4Address": "172.18.0.2/16"
},
"771c82...": {
"Name": "product-service",
"IPv4Address": "172.18.0.3/16"
},
"adcd06...": {
"Name": "order-service",
"IPv4Address": "172.18.0.4/16"
}
}
inspect 명령어는 각 컨테이너가 내부 네트워크에서 어떤 IP로 동작하고 있는지, 어떻게 서로 통신하는지를 확인하는 정보이며 MSA 구조에서 서비스 간 연동이 정상적으로 이루어지고 있음을 검증한다.
먼저 이미 네이버에 로그인된 웹브라우저의 값을 복사하고 새로운 브라우저를 시크릿모드로 네이버에 접속한다.


이후 F5를 통해 새로고침하면 로그인이 된다.

이후 다음날 장소까지 바꾸어 실습했는데 로그인이 된 것처럼 보였지만, 로그인 됐을 때처럼 정상적인 동작은 하지 않았다. 이게 만약 정상 작동했으면 분위기 싸해질뻔 했다..

※ 본 글은 비영리적 목적에 한해 자유롭게 이용 가능합니다. 단, 동일한 라이선스를 적용해야 하며, 상업적 이용은 금지됩니다.