본문 바로가기

Infra/AWS

[AWS] AWS ALB를 통해 웹애플리케이션이 배포된 오토스케일링 그룹 접근하기

 

전통적인 웹애플리케이션 구조는 public - web(정적요소), private -was(동적요소), DB 3-tier로 되어있습니다.

하지만 최근에는 web/was 를 분리하지 않기 때문에 was 를 private 에 둡니다. AWS 도식도로 보면 다음과 같이 그릴 수있습니다.

 

(각각에 대한 설명은 생략하겠습니다. 본 포스팅에서는 ALB 를 언제 쓰는지, 그리고 그 사용법을 익힙니다.)


image


private subnet에 배포된 웹애플리케이션은 프라이빗 아이피만 가지고 있기 때문에 외부에서 접속할 수 없습니다.

그렇다면 어떻게 사용자는 이 서버가 제공하는 웹애플리케이션을 이용할 수 있을까요?

 

로드밸런서를 이용하면 외부에서 이 웹애플리케이션을 이용할 수 있습니다. 중요한 사실은 웹 애플리케이션을 이용할 수 있다는 것이지 이 프라이빗 인스턴스에 접속할 수는 없다는 것입니다. 즉, web/was 를 프라이빗에 두면서 접속은 하지 못하게하고(=인스턴스를 조작할 수없도록하고), 클라이언트에서 (일반적으로 80포트로) 접근할 수 있게 하기 때문에 퍼블릭 망에 web 서버를 두었던 레거시보다 보안 측면에서 좋다고 할 수 있습니다.

 

사용법을 익히기에 앞서 ALB 에 대해 알아보겠습니다. 그 전에 LB(LoadBalancer)에 대해 알아야 합니다.

 

로드밸런서는 이름에서 알 수 있다시피 부하분산 장치입니다. 외부에서 들어오는 요청에 대한 트래픽 부하를 규칙에 따라 분산하여 전달함으로써 특정 서버에 부하가 몰려 다운되는 현상 또, 속도가 저하되는 현상을 막습니다. 이 역할을 하는 AWS 서비스가 바로 ELB(Elastic Load Balancer) 입니다.

 

ALB는 ELB 서비스 중 한 종류입니다. ELB 는 Classic LoadBalancer, Network LoadBalancer, Application LoadBalancer 로 나누어져 있는데요, 각 특징은 다음 장표로 쉽게 이해할 수 있습니다.


image


여기에서 Classic LoadBalancer는 현재는 지원하지 않는 EC2 - classic 을 대상으로 합니다 VPC 없이 인스턴스만 생성하는 방식인데 VPC 로 마이그레이션하기를 권장하고 있기 때문에 사용하지 않습니다. 하지만 2013년 12월 4일 이전 생성 계정에서 이를 이미 생성하여 사용하고 있는 경우를 대비하여 아직 지원하고는 있습니다.

 

Application LoadBalancer는 7계층에서 동작합니다. HTTP, HTTPS 프로토콜을 수용하고 외부 요청을 받습니다. 즉, 애플리케이션 레벨의 로드밸런서라고 할 수 있겠습니다. 아래와 같은 라우팅 방법을 지원합니다.


image


 

Network LoadBalancer는 4계층에서 동작합니다. TCP, UDP 트래픽을 로드밸런싱하여 내부 인스턴스에 전달할 수 있습니다. NLB 는 ALB 와 달리 고유의 IP 를 가집니다. (ALB 는 DNS)

 

둘 중 어느 것이 더 좋냐가 아닌 적절한 사용처에 적절한 로드밸런서를 사용하는 것이 중요합니다. 클라이언트가 웹화면을 요청하는 것은 HTTP, HTTPS 프로토콜을 사용하여 애플리케이션 레벨에 접근하는 것이기 때문에 ALB 가 적합할 것입니다. 

 

반면, 내부로 들어온 트래픽을 처리하고, 내부의 인스턴스로 트래픽을 전송하는 것은 네트워크 계층에서 하는 일이기 때문에 NLB 가 적합할 것입니다.

 

본 실습은 웹 애플리케이션을 외부에서 이용하는 것이기 때문에 Application LoadBalancer가 적합합니다.

 

이제 실습을 시작하겠습니다. 아키텍처는 다음과 같습니다. 접속이 제대로 되는지 확인하기 위해 배포해야 할 애플리케이션은 간단한 Hello Wolrd 출력 스프링 부트프로젝트로 생성방법은 생략하겠습니다. (https://github.com/csbsjy/hello-spring)

NAT Gateway, ALB, ASG(확장 시) 생성으로 인해 소량의 과금이 있습니다. 실습 후 생성한 모든 자원을 삭제해주세요(특히 NAT, ALB ..)


image


 

 

Step1. VPC 구성

 

각 자원을 생성하기 전에 VPC 를 구성하겠습니다.


image


구성도와 동일하게 VPC 사이더는 192.168.0.0/16 으로 지정합니다.

 

 

Subnet 구성


image


 

서브넷도 동일하게 생성합니다. AZ와 사이더대역을 잘 확인하며 구성해주세요,


image


 

위와 같이 구성을 완료헀다면 현재 아래와 같습니다.

 


image


 

 

Routing Table 구성

 

퍼블릭 서브넷은 인터넷에서 접근이 가능해야합니다. 그것을 가능하게 해주는 것이 인터넷 게이트웨이입니다.

VPC > Internet Gateway 에서 생성 후 현재 VPC 에 attach 해줍니다.(Action > attach VPC)

 


image


 

이제 라우팅 테이블을 구성합니다.
라우팅 테이블은 서브넷 레벨에서 트래픽을 라우팅하는 역할을 합니다.

 

총 네개의 서브넷에 각각 라우팅 테이블을 구성할 수도 있지만 본 포스팅에서는 Public subnet, Private subnet 으로만 나눠서 두개의 라우팅 테이블을 생성하겠습니다.


imageimage


 

위의 사진처럼 모든 요청에 대해 인터넷 게이트웨이로 라우팅하도록 설정합니다.

그리고 퍼블릭 서브넷에 이 라우팅 테이블을 적용합니다.

 

생성한 서브넷을 클릭한 뒤 Edit Subnet Associations 을 선택하고, Public Subnet 2개를 지정하여, 이 서브넷과 인터넷이 통신할 수 있도록 합니다.


image


 

이제 Private Subnet 의 라우팅 테이블을 생성합니다.

Private Subnet 은 인터넷과 직접 통신할 일이 없습니다. 통신한다면 그건 퍼블릭 서브넷이 됩니다. 프라이빗 서브넷에는 애플리케이션이 배포가 될 것이며, 배포에는 Java 가 필요합니다.

 

다시 말해 프라이빗 서브넷은 인터넷과 "통신"을 할 필요는 없지만 인터넷에서 jdk 라이브러리를 다운받을 수는 있어야 합니다.

 

이때 필요한 것이 NAT Gateway 입니다. NAT Gateway 는 퍼블릭 서브넷에 위치하여 인터넷에서 라이브러리를 가져와 프라이빗 서브넷에 전달해주는 역할을 합니다.

 

이 예제에서는 UserData라는 부트스크립트를 통해 외부 라이브러리를 다운받을 것이기 때문에 NAT 게이트웨이를 사용하지 않습니다. 하지만, 언제든 새로운 라이브러리를 받을 수 있기 때문에 생성하도록 하겠습니다.

 

 

VPC > NAT Gateway 에 들어갑니다.


image


 

서브넷은 Public Subnet 을 지정하고(둘 중 아무 서브넷을 지정해도 됩니다.), EIP 가 없다면 우측에서 생성하여 지정하면 됩니다. 생성에는 수분의 시간이 소요됩니다.

 

그동안 Routing table 을 조정합니다.

 

NAT Gateway 를 사용하는 것은 프라이빗 서브넷 입니다. 프라이빗 서브넷의 라우팅 테이블을 생성하고 프라이빗 서브넷으로 오는 모든 요청을 NAT Gateway 로 보내주도록 다음과 같이 설정합니다.


imageimage


 

그리고 퍼블릭 서브넷과 마찬가지로 Edit Subnet Associations 에서 프라이빗 서브넷 두개를 지정하면 라우팅 테이블 설정이 끝이 납니다.현재 이런 상황입니다.


image

 

 

Step2. 리소스 생성

 

 

bastion host 생성

 

스키마를 잡았으니 리소스를 생성하겠습니다.

먼저, Private 리소스에 접근할 일이 있을 수 있기 때문에 bastion host 를 생성하겠습니다. EC2 > Create Instance

 

AMI 는 Amazon Linux 2, Instance Type 은 t2.micro 로 지정합니다.


image


위 사진 처럼 VPC 는 생성한 VPC를 지정하고 외부에서 접근할 인스턴스이기 때문에 서브넷은 public subnet 중 하나를 지정합니다. 역시나 같은 이유로 public IP 도 Enable 옵션을 선택해줍니다.


imageimage


Name 태그를 지정하고 Security Group 에 SSH 접속 > 내 아이피 주소를 지정한 뒤 인스턴스를 시작하겠습니다.

 

 

App Server + LoadBalancer 구성

이제 프라이빗 서브넷에 앱을 배포할 인스턴스를 생성합니다.

 

로드밸런서는 이 앱 인스턴스를 타겟으로 잡고 부하를 분산합니다. 로드밸런서는 한 개 이상의 타겟그룹에 요청을 전달할 수 있습니다. 이 때 이 요청을 듣고 분배하는 역할을 하는 것이 리스너인데요, 사진을 보면 쉽게 이해 할 수 있습니다.


 

image


 

여기서는 80포트로 접속하여 8888로 배포된 애플리케이션으로 부하를 분산합니다.


즉, Listener 는 80 포트로 오는 요청을 계속 듣고 있다가 80 포트로 요청이 오면 타겟 그룹의 8888 포트로 요청을 전달합니다. 여기서 이 타겟 그룹은 EC2 오토스케일링 그룹이 되겠습니다.

 

오토스케일링 그룹은 Launch Configuration 을 기반으로 인스턴스를 생성합니다. 이때 인스턴스 유형 등의 조건을 실행합니다.

 

 

App Server 구성

웹 서비스를 로드밸런싱 하려면 스케일 아웃 시 생성되는 새로운 인스턴스에 애플리케이션이 자동으로 설치되어야 합니다. 그리고 그 애플리케이션을 실행하기 위해 java, git 등 필요한 라이브러리를 다운받아야 합니다.


image


 

도식화해서보면 Jdk, Git 은 불변의 대상입니다. 자바버전, 깃 버전이 변하지 않는 한 앞으로 생성되는 모든 이미지는 같은 jdk, git 을 사용할 것 입니다. 따라서 AMI 로 만들어 이 AMI로 생성되는 모든 인스턴스가 java와 git 을 사용할 수 있도록 하겠습니다.

 

하지만 소스코드는 그 성질이 다릅니다. 가장 최신의 소스코드를 clone 받아 빌드하고 실행해야합니다. 따라서, 부팅 때마다 새로운 소스를 가져올 수 있도록 launch configuration 의 userdata(=booting script)에 clone 및 빌드, deploy 소스를 추가하겠습니다.

 

App Server AMI 생성

먼저, jdk와 git 을 포함한 AMI 를 뜨기 위해 EC2 > Create Instance 를 선택합니다.


imageimageimage


AMI 용이기 때문에 Default VPC 를 지정해주었고, Public 접근 가능하도록 하겠습니다. 이 인스턴스는 AMI 생성 완료 후 삭제하도록 합니다.

 

아래 Advanced Details 옵션이 중요합니다.


image


 

위에서 잠시 언급된 User data 를 지정할 수 있습니다. 쉽게 말하면 boot script 과 같습니다. 여기 쓰여진 스크립트를 인스턴스 시작과 동시에 root 권한으로 실행합니다. 그렇기 때문에 sudo 를 생략해도 됩니다.

#!/bin/bash
echo "> yum update start"
yum update -y

echo "> jdk1.8 install"
yum install -y java-1.8.0-openjdk-devel.x86_64

echo "> git install"
yum install -y git 

 

순서대로 jdk 1.8 설치 > git 설치를 진행합니다.

 

Security Group 에 내 아이피 주소로 SSH 접근이 가능하게 설정하고 인스턴스를 실행해줍니다.


image


위와 같이 java, git 설치를 확인하였습니다.

 

설치한 인스턴스를 선택하고 Action > Image > create Image 에서 아래와 같은 화면을 볼 수 있습니다.


image


 

위와 같이 이름을 지정하고 볼륨 사이즈는 원하는대로 변경해도 됩니다. Create Image 를 하면 이제 이 AMI 는 앞으로 인스턴스 생성 시 사용할 수 있습니다. AMI가 생성되면 사용했던 인스턴스는 종료해도 됩니다.

 

AutoScaling Group 구성

 

이제 위에서 만든 AMI 를 가지고 Auto Scaling Group 을 만들기 위한 Launch Configuration 을 생성하기 전에 이 시작구성에 적용할 보안그룹을 먼저 생성하겠습니다.

 

EC2 > Security Group > Create Security Group

image

 

앱서버는 프라이빗 이기 때문에 bastion host 를 통해서만 접근할 수 있습니다. 22번 포트에 bastion host 에 적용한 sg 아이디를 넣어 생성해줍니다. VPC 또한 테스트용으로 생성한 VPC 를 지정해야합니다.

 

Launch Configuration 구성

1. Choose AMI - My AMI 에서 위에서 생성한 AMI 를 선택합니다.


image


2. Instance Type - t2.micro 로 해도 되지만 오토 스케일 기준을 너무 빨리 넘어버려 테스트가 어렵기 때문에 t3.small 로 지정하겠습니다.

 

3. Configure Details


image


이 곳의 Advanced Details 에서도 User data 를 선택할 수 있습니다. 사전에 언급했다시피 오토스케일링 될 때마다 최신의 소스를 불러와 빌드하고 실행해야합니다.

 

User data에 아래 스크립트를 입력합니다.

#!/bin/bash
echo "> create workspace directory"
mkdir /home/ec2-user/workspace

cd /home/ec2-user/workspace

echo "> git clone"
git clone https://github.com/csbsjy/hello-spring.git

cd hello-spring

echo "> start gradle build"
./gradlew clean build


cat <<EOF > /etc/systemd/system/sample-app.service 
[Unit]
Description=Java Application as a Service
[Service]
User=ec2-user
#change this directory into your workspace
#mkdir workspace 
WorkingDirectory=/home/ec2-user/workspace
#path to the executable bash script which executes the jar file
ExecStart=/bin/bash /home/ec2-user/workspace/deploy-app.sh
SuccessExitStatus=143
TimeoutStopSec=10
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF

echo "> create deploy script"
cat <<EOF > /home/ec2-user/workspace/deploy-app.sh
java -Dexternal.sys.properties=file:system.properties -Dexternal.app.properties=file:application.properties -jar /home/ec2-user/workspace/hello-spring/build/libs/*.jar
EOF

systemctl daemon-reload
systemctl enable sample-app.service
systemctl start sample-app

 

application 을 서비스로 만들어 배포하는 스크립트입니다. nohup 으로 jar를 직접 배포할 수도 있겠지만, reboot 시 재배포해줘야 하는 것이 귀찮아서 위와같이 했습니다. 실 서비스에서는 reboot나 stop할 일이 드물기 때문에 nohup 으로 배포해도 될 것 같습니다.

 

App server 는 늘 private 해야하기 때문에 아래 옵션에 체크하고 넘어갑니다.


image


 

보안 그룹은 위에서 appserver 용으로 생성한 보안그룹을 선택합니다.

Launch Configuration 생성을 완료했습니다.

 

Target Group 생성

이제 이 Launch Configuration을 배경으로 AutoScaling Group 을 생성해야하는데, 앞에서 ALB 동작 원리를 설명할 때 언급되었던 것처럼 ASG는 ALB 가 요청을 분산하는 "Target Group"이 됩니다. 그래서 AutoScaling Group을 Target Group 으로 지정해주어야 합니다.

EC2 > LoadBalancer의 TargetGroup 에서 대상그룹을 생성합니다.


image


 

Sample Application은 8888포트로 지정되었습니다. 따라서 대상그룹의 8888포트로 요청을 보내야합니다.

 

VPC도 현재 작업 중인 VPC 로 지정합니다.

 

Health check settings 가 중요합니다. ALB 는 여기서 지정한 경로로 Get 요청을 통해 Healthcheck 를 진행합니다. 저는 /home API 만 만들었기 때문에 이렇게 했지만 일반적으로는 /sys/healthcheck 와 같이 헬스체크용 200 응답을 반환하는 API 를 만들기도 합니다.

 

이제 AutoScalingGroup 을 만들 수 있습니다.

 

AutoScaling Group 생성

EC2 > AutoScalingGroup 에서 오토스케일링 그룹을 생성합니다.


image


 

일전에 만들어 둔 Launch Configuration 을 지정합니다.


image


 

VPC 를 지정하고 Subnet 은 Private 서브넷 두개를 지정합니다.

아래 Advanced Details 에서 로드밸런싱 옵션을 선택한 뒤 전에 만들었던 target group 을 선택합니다. 또, AutoScaling 테스트를 원활하게 하기 위해 Health Check Grace Period 기간도 줄여주었습니다.


image


 

오토스케일링 정책은 지정하지 않겠습니다. 실제로는 이 정책에 따라 스케일 아웃을 수행하기 때문에 매우 중요한 부분입니다.

 


image


네임태그는 위와같이 지정하겠습니다. 해당 네임태그로 인스턴스가 생성됩니다.

 

생성을 완료하면 아래처럼 인스턴스 현황을 확인할 수 있습니다.


image


 

2/2 Check 가 떴으면 올바르게 인스턴스가 생성됐는지 확인해봅니다.

 

private subnet 에 있기 때문에 bastion host 를 통해야합니다. pem 파일을 Sftp 하기 귀찮으니 터널링하겠습니다.

sudo ssh -i [pem파일이름].pem -L 22:[앱서버인스턴스 프라이빗아이피]:22 ec2-user@[bastion host 퍼블릭 아이피]

 

퍼미션 어쩌구 에러메시지가 나지 않으면 정상적으로 로컬 터널링이 된 것입니다. 퍼미션 관련 에러메시지가 출력되면 sudo 권한으로 실행했는지 다시 확인해봅니다.

 

이제 새로운 터미널에서

sudo ssh -i [pem 파일 이름].pem ec2-user@127.0.0.1

을 실행하면 appserver 에 접속할 수 있습니다.

 

+ 만일 아래와 같은 에러가 난다면 이미 127.0.0.1:22 를 점유하고 있는 것입니다. 해당 프로세스를 종료하거나 새로운 포트를 사용하는 등의 조치가 필요합니다.


image


 

터널링이 정상적으로 되었다면 로컬 22번 포트로 앱 서버에 접속하여 애플리케이션이 정상적으로 배포되었는지 확인합니다. curl 명령을 통해 version 정보를 받아오는 것을 확인할 수 있습니다.


image


 

Step 3. ALB 구성

이제 ALB 를 연결하고 오토스케일링 테스트를 해보겠습니다.

 

EC2 > LoadBalancers > Create LoadBalancer 에 접근합니다.

 

1. Select Load balancer type은 Application LoadBalancer 타입을 선택합니다.


image


 

 

2. Configure LoadBalancer: 현재 만드는 시스템의 ALB 는 클라이언트가 인터넷을 통해 웹애플리케이션에 접속할 수 있도록 하는 용도입니다. 따라서 Internet facing 옵션이 적절합니다.


image


- 80포트로 접속하기 위해 Listener는 위와같이 지정합니다. 추가로 건드린 것이 없다면 기본적으로 80포트가 추가되어있을 것입니다. 여기서는 진행하진 않지만 도메인 + ssl 인증서를 사용하는 웹애플리케이션이라면 당연히 443 포트도 추가해주어야 합니다. 

- ALB 는 최소 두개 이상의 가용역역에서 동작합니다. 역시나 인터넷 망 접속을 허용해야하기 때문에 각 가용영역의 public subnet을 지정합니다.

 

3. Configure Security Groups: 이 애플리케이션은 모든 사용자가 접근할 수 있도록 할 것입니다. 보안그룹을 위와 같이 지정합니다. 맨 처음에도 말했다시피 80포트만 열어주는 것이기 때문에 인스턴스 자체에 로드밸런서를 통해 접속 할 수는 없습니다.


image


 

4. Configure Routing: Step4 는 일전에 만들어 둔 타겟그룹을 지정하면 그 외 작업은 필요하지 않습니다.



 

5. Register Targets: 자동으로 오토스케일링 그룹의 인스턴스가 타겟으로 등록되어있습니다. 사전에 오토스케일링 그룹 생성 시 target 을 지정했기 때문입니다.



 

 

ALB 생성을 완료했습니다. 오토스케일링 그룹의 보안 그룹에는 22번포트, bastion host 만 추가되어있습니다. 여기서 생성한 alb 에서 오토스케일링 8888 포트로 요청이 가능하도록 추가해아합니다.

 

ALB 의 보안그룹 id는 ALB 세부사항 Security 부분에서 확인할 수 있습니다.


image


 

 

오토스케일링 그룹의 보안그룹의 8888포트 > ALB 보안그룹 규칙을 추가합니다.


image


이제 AutoScaling Group의 appserver 인스턴스는 alb 의 8888 요청을 수용할 수 있습니다.

 

 

 

다시 TargetGroup 으로 이동합니다. Target 탭에서 상태를 확인할 수 있습니다. health check 에 시간이 약간 소요되니 기다려줍니다.


image

 

image


healty가 되면 ALB의 DNS 주소를 이용해 애플리케이션에 접근합니다.

 

 

 

정상적으로 ALB 와 연동되었습니다.


image


 

 

ALB 동작 테스트

이제 세팅은 끝이 났습니다. 정상적으로 ALB 가 오토스케일링 그룹에 부하를 분산하는지 테스트하도록 하겠습니다.

소스코드를 업데이트하고 오토스케일링 그룹을 조작해보겠습니다. 소스 업데이트 부분은 생략하겠습니다. 아래와 같이 버전 정보를 변경했습니다.


image


 

EC2 > Instances 로 가서 appserver 를 Terminate 시킵니다.


image


 

 

AutoScaling Group 에서 수분 기다린 뒤 새로고침하면,


image


아래와 같이 기존 instance 가 unhealthy로 바뀝니다.


image


 

AutoScaling Group min 인스턴스는 1개이기 때문에 무조건 1개 이상의 인스턴스가 존재해야합니다. 따라서 자동으로 새로운 인스턴스가 pending 됩니다. 또, 처음에 지정한 A서브넷 중 한군데에 임의로 배포가됩니다. 위 상황은 우연히 연속으로 c AZ에 배포된 것입니다. 이제 이 인스턴스가 2/2 check 가 떨어지면, 다시 alb dns 로 접근해봅니다.


image


 

같은 alb dns 로 접속했지만 저절로 변경된 소스가 배포된 새 인스턴스로 접근하는 것을 확인할 수 있습니다. 


image

.

마무리

위와 같은 상황은 Down Time 이 존재할 수밖에 없습니다. 실제 AutoScaling 을 통해 무중단 서비스를 지원하기 위해서는 CoolDown 을 고려하여 미리미리 스케일아웃 되도록 해야합니다. AutoScaling 주기 정책을 명확하게 정립하는 것이 중요하겠습니다.