AWS는 서버, 네트워크, 저장공간, 데이터베이스 등 다양한 IT 인프라를 인터넷을 통해 제공하는 클라우드 플랫폼이다
직접 물리 서버를 구매하고 구축하지 않아도 필요한 만큼 서버를 생성해서 사용할 수 있다는 점이 특징이다
오늘 실습에서 사용한 EC2는 AWS에서 제공하는 가상 서버 서비스다
전체적인 구조는 다음과 같이 이해할 수 있다
Host PC
↓
Internet
↓
AWS EC2 Instance
↓
Docker
↓
Spring Boot Container
EC2 인스턴스를 생성한 뒤 SSH로 접속하면 로컬 Windows가 아닌 AWS에 존재하는 Linux 서버를 직접 제어하게 된다
EC2에 Docker를 설치한 뒤 일반 사용자 계정으로 Docker 명령어를 사용할 수 있도록 다음 명령어를 실행했다
sudo usermod -aG docker ec2-user
-aG docker 옵션은 기존 그룹 정보를 유지하면서 ec2-user를 docker 그룹에 추가한다는 의미다
하지만 이 명령을 실행한 직후 Docker 명령을 사용했을 때 permission denied가 발생할 수 있다
이는 그룹 설정이 현재 로그인된 세션에 즉시 반영되지 않기 때문이다
따라서 SSH 연결을 종료한 뒤 다시 접속하거나 다음 명령을 이용해 새로운 그룹 설정을 적용할 수 있다
newgrp docker
현재 사용자의 그룹은 다음 명령으로 확인할 수 있다
groups
정상적으로 적용되었다면 결과에 docker가 포함되어 있어야 한다
Docker 정상 동작 여부는 다음과 같이 확인할 수 있다
docker ps
로컬에서 생성하여 Docker Hub에 업로드해둔 catalog-service 이미지를 EC2로 가져왔다
Docker Hub 계정은 duckfairywak이며, 이미지가 다음과 같이 올라가 있다고 가정하면
duckfairywak/catalog-service:k8s
EC2에서 다음 명령을 실행한다
docker pull duckfairywak/catalog-service:k8s
다운로드된 이미지는 다음 명령으로 확인한다
docker images
이 과정을 통해 개발 PC에 있던 Docker 이미지를 AWS EC2에서도 동일하게 사용할 수 있다는 것을 확인할 수 있다
즉, Docker 이미지는 실행 환경을 하나의 패키지로 만들어 다른 서버에서도 동일한 환경을 재현할 수 있도록 해준다
다운로드한 Docker 이미지를 기반으로 컨테이너를 생성한다
예를 들어 Spring Boot 애플리케이션이 컨테이너 내부에서 8088 포트를 사용하고, EC2에서는 8080 포트로 접근하려는 경우 다음과 같이 실행할 수 있다
docker run -d \
--name catalog-service \
-p 8080:8088 \
duckfairywak/catalog-service:k8s
한 줄로 작성하면 다음과 같다
docker run -d --name catalog-service -p 8080:8088 duckfairywak/catalog-service:k8s
여기서
8080:8088
↑ ↑
EC2 Container
의 구조로 포트가 연결된다
컨테이너 내부 Spring Boot가 8080으로 동작한다면 다음과 같이 설정해야 한다
docker run -d --name catalog-service -p 8080:8080 duckfairywak/catalog-service:k8s
따라서 Docker 포트 매핑을 할 때는 반드시 애플리케이션이 실제로 사용하는 내부 포트를 확인해야 한다
외부 브라우저에서 접근하기 전에 먼저 EC2 내부에서 서비스가 정상적으로 동작하는지 확인한다
다음과 같이 curl 명령을 사용했다
curl -X GET http://localhost:8080/catalog-service/catalogs
또는 다음처럼 간단히 작성할 수도 있다
curl http://localhost:8080/catalog-service/catalogs
이 요청이 성공하면 적어도 다음 구간은 정상적으로 연결되었다는 의미다
EC2 localhost:8080
↓
Docker Port Mapping
↓
Container
↓
Spring Boot catalog-service
curl 요청 과정에서 다음과 같은 오류가 발생했다
Recv failure: Connection reset by peer
이는 해당 포트로 연결 자체를 시도했지만 연결된 서버 또는 컨테이너 쪽에서 연결이 종료된 상태를 의미한다
이 경우 가장 먼저 Docker 컨테이너 상태를 확인해야 한다
docker ps -a
컨테이너가 Exited 상태라면 Spring Boot가 정상적으로 실행되지 않은 것이다
애플리케이션 로그는 다음과 같이 확인할 수 있다
docker logs catalog-service
특히 Spring Boot가 실제로 어떤 포트에서 실행되는지 확인하는 것이 중요하다
예를 들어 로그에서
Tomcat started on port 8080
이라고 나온다면 Docker 실행 시 컨테이너 포트도 8080을 지정해야 한다
docker run -d --name catalog-service -p 8080:8080 ...
반대로 Spring Boot가 8088에서 실행된다면
docker run -d --name catalog-service -p 8080:8088 ...
처럼 연결해야 한다
따라서 이번 오류를 통해 Docker의 Host Port와 Container Port를 단순히 임의로 설정하는 것이 아니라 실제 애플리케이션 포트와 정확히 일치시켜야 한다는 점을 확인했다
EC2 내부에서 서비스 접근이 성공했다면 다음 단계는 Host PC에서 EC2에 접근하는 것이다
AWS EC2에는 인터넷에서 접근할 수 있는 Public IPv4 DNS 또는 Public IP가 존재한다
예를 들면 다음과 같은 형태다
ec2-xx-xx-xx-xx.ap-northeast-2.compute.amazonaws.com
외부 브라우저에서는 다음과 같이 접근할 수 있다
http://<EC2-Public-DNS>:8080/catalog-service/catalogs
하지만 EC2에서 Docker의 8080 포트를 열었다고 해서 바로 인터넷에서 접근할 수 있는 것은 아니다
AWS에는 Security Group이라는 네트워크 방화벽 설정이 존재한다
따라서 EC2의 인바운드 규칙에서 8080 포트를 허용해야 한다
Type : Custom TCP
Port : 8080
Source : My IP
실습 상황에서는 모든 IP에 개방할 수도 있지만, 실제 환경에서는 필요한 IP에만 제한하는 것이 안전하다
전체 접근 흐름은 다음과 같다
Host PC Browser
↓
Internet
↓
AWS Security Group
↓
EC2 Public DNS : 8080
↓
Docker Port Mapping
↓
catalog-service Container
↓
Spring Boot
AWS 실습을 진행하기 전에 Kubernetes에서 사용했던 NodePort도 다시 정리했다
Kubernetes에서는 Spring Boot 애플리케이션을 일반적으로 Pod 내부에서 실행한다
Deployment
↓
Pod
↓
Container
↓
Spring Boot
Pod는 Kubernetes 내부 IP를 사용하기 때문에 외부의 브라우저나 Postman이 Pod에 직접 접근하기 어렵다
이를 위해 Kubernetes에서는 Service를 사용한다
외부 사용자
↓
Service
↓
Pod
그중 NodePort는 Kubernetes Node의 특정 포트를 외부에 공개하는 방식이다
예를 들어 user-service를 다음과 같이 구성할 수 있다
ports:
- port: 8081
targetPort: 8081
nodePort: 32081
각 포트는 다음 의미를 가진다
nodePort 32081
↓
Kubernetes Node의 외부 공개 포트
port 8081
↓
Service가 사용하는 포트
targetPort 8081
↓
실제 Pod 내부 애플리케이션 포트
외부 요청의 흐름은 다음과 같다
Postman
↓
Node IP : 32081
↓
NodePort
↓
Service
↓
targetPort 8081
↓
Pod
↓
Spring Boot user-service
따라서 NodePort를 이용하면 다음과 같은 방식으로 접근할 수 있다
http://<Node-IP>:32081/users
NodePort와 AWS EC2의 포트 공개 방식은 외부에서 특정 포트로 서버에 접근한다는 점에서는 비슷하게 느껴질 수 있지만, 역할은 다르다
NodePort는 Kubernetes 내부 Service 기능이고, AWS Security Group은 EC2 수준에서 외부 네트워크 접근을 허용하거나 차단하는 방화벽 역할을 한다
오늘 실습은 결국 다음 흐름으로 정리할 수 있다
Docker Image 생성
↓
Docker Hub Push
↓
AWS EC2 생성
↓
EC2 Docker 설치
↓
docker pull
↓
Container 실행
↓
EC2 내부 curl 테스트
↓
Security Group 8080 허용
↓
Public DNS 확인
↓
Host PC에서 접근
기존에는 내 PC에서 Docker 컨테이너를 실행했다면, 오늘은 AWS EC2라는 원격 서버에서 동일한 Docker 이미지를 실행했다는 차이가 있다
Docker를 이용하면 애플리케이션과 실행 환경을 이미지로 만들어두고 로컬 PC, 다른 서버, 클라우드 환경에서도 동일하게 실행할 수 있다
또한 서비스를 외부에 공개할 때는 애플리케이션의 포트만 고려하는 것이 아니라 Docker 포트 매핑, Kubernetes Service, AWS Security Group 등 각 계층의 네트워크 설정이 모두 맞아야 한다는 점이 중요했다