본문 바로가기
HANDS-ON PROJECTS/DEVOPS

[DEVOPS] Architected 3-tier Web App on IaaS by using Jenkins & Ansible CICD pipeline with logging, Backup and Monitoring/Alarming Solution

by junthetechguy 2024. 6. 26.
 

TABLE OF CONTENTS

 

    1. Making VPC

    아래의 2가지 아키텍쳐를 구현한다.

    위의 아키텍쳐에서 베스쳔 호스트로 OpenVPN용 EC2를 public subnet에 생성하도록 하자.

     

    VPC 생성 후 그 안에 Subnet을 생성한 후 routing table 만들어보기. 여기서 만들 아키텍쳐이다.

    AWS Console에서 vpc를 CIDR 10.1.0.0/16 대역으로 만들어주자.

    이후 VPC에서 Subnet을 6개를 생성해주자. 위의 subnet 이름에서 뒤에 붙은 알파벳(a, c)은 해당 Region의 AZ으로 ap-northeast-2a와 ap-northeast-2c 중에 하나를 선택하는 것이다.

    언제나 순서는 Region 선택 → VPC 생성(CIDR 대역 만들기) → Subnet 생성하면서 해당 Region의 AZ 선택으로 진행된다.

    이후 IGW를 생성해주고 내가 생성했던 VPC로 연결해주자(IGW는 무조건 VPC와 연결이 되고(VPC의 Endpoint 역할을 하는게 이 IGW이다), NAT GW는 무조건 Public Subnet에 존재하게 된다)

    이후 Routing Table을 public용과 private용으로 총 2개를 생성해주자. 이 Routing Table의 vpc로도 내가 생성했던 VPC로 설정해주고 이 public용 Routing Table에서는 Routing 규칙으로 global(0.0.0.0/0) Destination으로 IGW가 Target이 되도록 연결하자.

    이후 NAT GW를 public-subnet-a에다가 생성해주고 이 NAT GW를 private용 routing table 규칙으로 global(0.0.0.0/0) Destination으로 NAT GW가 Target이 되도록 연결하자.

    이후 public용 routing table에는 public subnet 2개(a, c)를 연결해주고 private용 routing table에는 private subnet 4개를 연결해주자.

     

    Bastion host 구성 후 NAT GW 확인해보기Private EC2로의 inbound는 Bastion Host(OpenVPN)으로 이루어지고, Private EC2에서의 outbound는 NAT Gateway를 통해서 나간다(NAT Gateway란 Private Subnet에 존재하는 EC2 같은 것들이 외부(Internet)으로 나가기 위한 Gateway일 뿐이란 것이다. 이때 당연히 NAT table에 어떤 Private IP가 나갔는지를 적어놓고 NAT에 할당되어 있는 Public IP로 갈아낀 다음에 나가도록 한다). 여기서 만들 아키텍쳐이다.

    제일 먼저 SG을 생성해주되 inbound 규칙으로는 SSH(22)로 설정하고, Source(허용할 Source IP)는 내 PC의 IP를 입력해주고 SG를 생성해주자.

    이후 EC2 instance를 Amazon Linux image로 사용을 하고, key pair를 설정하고(내 pc에 있는 pem key(linux → linux로 ssh 접속할때 사용하는 key)나 ppk key(windows → linux로 ssh 접속할때 사용하는 key)), public-subnet-a로 설정하고, 내가 만든 SG를 연결해주자. 글면 베스챤 host가 생성이 완료된거다.

    이후 Elastic IP를 하나 생성 후 내 바스챤 host에 이 Elastic IP를 할당해주자.

    이후 WinSCP로 해당 바스챤 host ip로 ssh로 내 local에 있는 ppk key를 이용해서 접속해준 후 여기에 내가 다운로드 받았던 pem key(private EC2에 ssh로 접근할때 사용하는 key)를 upload해주자.

    이후 private EC2에 접속하기 위한 SG를 하나 더 만들어주자. 이때 inbound policy로는 SSH(22)로 하고, source는 bation host로 설정해주자.

    그리고 private EC2를 역시 amazon linux AMI로 생성해주자.

    이후 바스쳔 호스트에 putty로 ppk key를 이용해서 내 local에서 접속한 후 다시 여기서 ssh로 pem key를 이용해서 private ec2로 접속하기 위해 pem key 권한을 chmod 600으로 축소해준 다음에 ssh 접속해주자.

    이후 curl을 날려서 해당 private ec2에서 internet으로 통신이 잘 되는지 확인해보자.

     

    OpenVPN 구성 후 NAT GW 확인해보기

    매번 바스쳔으로 Proxy 역할을 하듯이 먼저 바스쳔에 SSH로 들어온 후 다시 옮겨 놓은 pem key를 가지고 private EC2에 SSH로 들어가기 귀찮으므로 오픈 소스인 OpenVPN을 사용해서 손쉽게 접속이 되도록 하자

     

    openVPN용 EC2를 AMI를 OpenVPN Access Server로 선택해서 만들어주자. SG는 이 EC2를 생성할때 설정해주자. TCP(22; SSH), TCP(443; HTTPS), TCP(943; 사용자 지정 TCP), TCP(945; 사용자 지정 TCP), UDP(1194; 사용자 지정 UDP) 모두 다 내 IP만 source로 허용하도록 설정 후 생성해주자.

    이후 Elastic IP를 생성 후 이 openVPN용 EC2에 할당해주자.

    그리고 뿌띠로 ppk key로 openVPN용 EC2에 접속한 후 default 설정들을 진행해준 후 나오게되는 Admin URL을 가지고 내 local web browser로 admin user로 접속 후 해당 Web UI에서 내 private ec2로 접속하는 user를 만들어준 후 이 user로 다시 Web UI에 로그인을 해주면 아래와 같이 OpenVPN을 Connect하는 것을 다운로드 하라고 나온다.

     

    그러면 이제 private ec2의 private ip를 가지고 putty로 ppk로 바로 접속할 수 있게 된다.

     

    VPC peering 아래의 아키텍쳐를 구현한다

    일단 Region을 tokyo region으로 변경 후 VPC를 만들어주자. 주의점 : 언제나 vpc끼리는 CIDR가 겹치지 않아야 한다.

    그리고 이 도쿄 Region의 VPC는 VPC Endpoint(=Internet GW)와 NAT GW는 만들지 않는다.

    이후 EC2 instance를 AWS linux로 AMI를 설정하고 이 EC2에 직접 접근하지 않으므로 굳이 key pair를 설정해줄 필요가 없다. 오로지 EC2 인스턴스에 내 local에서부터 직접 접근할때만 미리 만들어둔 key pair를 사용하여 EC2 인스턴스를 만든다.

    이후 이 EC2의 SG의 inbound policy를 seoul region에서 진행하는 ping test를 허용해주도록하기 위해서 모든 ICMP에 대해서 Seoul Region의 VPC 대역인 10.1.0.0/16의 Source에서부터 오는 트래픽은 허용해준다.

    이후 이 Tokyo Region에서 Seoul Region의 VPC ID를 통해서 VPC Peering을 생성해준 후 Seoul Region에서 수락해준다.

    이후 Seoul Region에 만들어 놓은 private routing table에 Destination으로 Tokyo Region의 VPC의 CIDR인 192.168.10.0/24를 넣어주고, Target은 내가 만들어뒀던 VPC peering으로 설정해두면 routing 규칙이 제대로 잡히게 되는 것이다. Tokyo Region에 만들어 놓은 VPC의 라우팅 테이블에도 Destination은 Seoul Region의 VPC의 CIDR인 10.1.0.0/16으로 하고, Target은 내가 만들어둔 VPC Peering으로 설정하게 되면 이 두 VPC가 Peering이 연결되어 통신이 가능하게 되는 것이다. 물론 Seoul Region의 EC2의 SG 역시 inbound policy를 Tokyo region에서 진행하는 ping test를 허용해주도록하기 위해서 모든 ICMP에 대해서 Tokyo Region의 VPC 대역인 192.168.10.0/24의 Source에서부터 들어오는 트래픽은 허용해준다.

    이후 putty로 seoul region의 private IP로 ppk key로 ssh 접속한 후 Tokyo region의 private ec2의 private IP로 ping을 날리면 날라가게 된다.

     

    2. Architecting 3-tier Web App on IaaS 

    Routing Table과 SG의 차이점은? -Routing Table = Destination과 Target만 지정해서 패킷을 이 Subnet이 보낼때 어느 것을 경유해서(Target) 결국 어디에 도달할지(Destination)을 정의한 것 => Subnet 단위로 설정 -SG = 해당 Instance(EC2 등)가 어떤 Source로부터 오는 Packet을 어떤 Port(22, 443 등)만 열어서 받을지 결정 => Instance 단위로 설정

     

    목표 아키텍쳐 = Public Sebnet에 있는 ALB에 ACM을 붙여서 HTTPS 인증이 가능하도록 만들어서 HTTPS로 통신이 가능하게 해주고, 라우트 53을 제일 앞단에 붙여서 도메인을 등록한 후 Domain 쿼리를 통해 Service로 연결이 되게끔 구성을 하자

    Route53과 ACM 생성하기

    먼저 Route53에 내가 원하는 도메인 이름으로 도메인 등록(여기선 fastcampus-dragon.net)부터 해주자.

    이후 ACM에 들어가서 퍼블릭 인증서(SSL 인증서)를 요청해주기 위해서 먼저 어떤 도메인에 발급할건지에 대해 fastcampus-dragon.net이라는 domain과 *.fastcampus-dragon.net이라는 domain에 퍼블릭 인증서(SSL 인증서)를 요청해준다.

    이후 Route53에서 해당 DNS 레코드를 생성해주자. 그러면 ACM 인증이 Route53의 해당 레코드에 등록이 되게 된다. 즉, Route 53의 호스팅 영역에 ACM 관련 레코드가 등록이 되게 하여 이제 Route53에 접속할때 이 인증서를 통해서 HTTPS로 통신이 되도록 해주게 된 것이다.

     

    ALB, EC2, RDS 용 SG 생성하기

    아래가 이번에 만들 총 4개의 SG이다.

    -이미 my-openvpn-sg은 앞서서 생성을 했기 때문에 이번엔 생략한다.

    -alb sg는 80(HTTP)과 443(HTTPS)만 global source로부터의 inbound를 허용해주자

    -ec2의 sg는 당연하게 openvpn으로부터는 SSH 포트인 22번과 React 서비스 포트인 3000번과 Spring Boot 서비스 포트인 8080으로 접근할 수 있게 설정해주고 alb로부터의 health checking을 위한 8080과 HTTP와 HTTPS를 위한 80과 443번 포트를 오픈해주자. 80과 443은 internet에서 HTTP와 HTTPS 통신으로 접속할때 사용하는 포트넘버(프록시 동작으로 뒷단으로 넘겨준다)이고, 3000과 8080은 실제로 해당 react app(Node.js WAS의 default port)과 spring boot app(Tomcat WAS의 default port)이 올라가는 서비스 포트넘버이다.

    -rds의 sg는 MySQL 서비스 포트인 3306을 두개의 인스턴스 모두에게 열어주도록 하자

    그리고 이런 인스턴스의 sg끼리 엮이게 할 경우에는(가령, openvpn으로 react의 ec2의 3000번 포트로 접속할때) 아래와 같이 인스턴스 id가 아니라 sg id를 가지고 source를 작성해준다는 점을 기억하자

     

    참고로 아래와 같이 보안 그룹(SG)만 생성하면 Name이 default로 Null로 설정이 되게 되는데 이걸 편하게 식별하기 위해서 Name Tag를 아래 표시한 부분과 같이 보안 그룹 이름과 동일하게 설정해주자

     

     

    EC2/RDS/ALB생성하기 => 이번에 만들 아키텍쳐이다.

    먼저 EC2 my-dragon-app-ec2를 private subnet에 생성해주자. 스토리지는 넉넉하게 20GB로 해주자.

    이후 DB Subnet Group을 생성해주자. RDS의 경우 언제나 이중화를 설정해주도록 하기 위해 RDS를 만들 때 부터 아래와 같이 AZ를 2곳으로 설정해두고 아래 아래와 같이 두 곳에다가 Subnet을 각각 하나씩(db-subnet-a와 db-subnet-b)를 만들어서 여기에다가 동일한 RDS를 두도록 하자

    이후 DB를 MySQL 엔진 옵션으로, 템플릿은 개발/테스트 용도로 선택 후 배포 옵션은 단일 DB 인스턴스로 선택 후 DB 인스턴스 식별자를 my-dragon-app-rds로 설정한 후 마스터 사용자 이름을 root로 설정한 후 마스터 암호를 입력해준 후 DB 인스턴스 클래스를 그대로 가장 저렴한 용도인 t3.micro가 되도록 버스터블 클래스(t 클래스 포함)으로 선택해주자. 그리고 DB로만 사용할 것이므로 컴퓨팅 리소스로는 EC2 컴퓨팅 리소스에 연결 안함을 선택해주고, public access는 허용하지 않도록 하고 DB를 생성해주자.

     

    이후 Target Group을 생성해주자. 대상 유형으로 EC2 인스턴스를 선택해주고, 이 TG의 이름으로 my-dragon-app-tg-80으로 설정 후 열어둘 프로토콜과 포트는 HTTP:80으로 설정 후 상태검사는 내가 열어둔 HTTP:80으로 설정해주고, 대상 등록으로 내가 만들어둔 EC2(my-dragon-app-ec2)를 선택해주자.

    이후 또다른 Target Group(Spring Boot로 만들 API Server port)을 생성해주자. my-dragon-app-tg-8080으로 설정 후 HTTP:8080을 열어준 상태로 대상 등록으로 내가 만들어둔 EC2(my-dragon-app-ec2)를 선택해주자.

    이후 ELB 생성을 하되 LB 유형을 ALB(my-dragon-app-alb)로 해주자. 그리고 이 ALB의 Schema를 Internet 경계로 선택 후 network mapping으로 내가 가지고 있는 AZ 2개(a Zone, c Zone) 모두 다 선택 후 그 안에 있는 public subnet으로 network mapping할 subnet을 선택해주자. SG는 내가 만들어둔 ALB용 SG로 선택 후 SSL/TLS 인증서는 내가 ACM에서 생성해뒀던 fastcampus-dragon.net으로 설정해두자.

    이후 이 ALB의 listner tab에서 HTTP:80 Listener를 THEN 부분에서 HTTPS 443으로 Redirection하도록 설정해두고, HTTPS:443 Listener는 IF 부분에 host header로 api.fastcampus-dragon.net으로 설정 후 THEN 부분에서 전달대상으로 my-dragon-app-tg-8080으로 전달이 되도록 해주고, 이 Listner의 기본 동작으로는 my-dragon-app-tg-80으로 전달되도록 해주자.

     

    Frontend 소스코드 수동 배포

    이제 FE 소스코드를 아래와 같이 수동으로 배포해주자.

    일단 OpenVPN을 On을 시키고 putty로 my-dragon-app-ec2에 ppk key로 SSH 접속을 해주자. 그리고 sudo -i로 root 권한으로 바꾸고 모든 다운로드를 진행한다.

    이후 nginx를 설치해주자.

    이후 nodejs를 aws 공홈의 가이드로 설치해주자.

    이후 java와 mvn을 yum install java java-devel maven으로 설치해주자.

    이후 yum install mysql로 mysql을 설치해주자.

    이후 yum install git으로 git을 설치해주자.

    이후 git clone으로 frontend source와 backend source를 끌어온 다음 npm의 경우 디렉토리 단위로 실행이 되기 때문에(Dockerfile같은 느낌으로) 해당 directory로 이동한 후 npm install로 react를 해당 directory부터 시작해서 쭉 dependency 와꾸(node_modules)를 잡을 수 있도록 해주고 npm run build 명령어로 build를 진행한 후 nohup npm start & 으로 Background로 실행시키도록 해주자.

    이후 나의 web browser에서(OpenVPN에 자동으로 연결됨; 이미 OpenVPN(기존에 이 OpenVPN용 EC2 인스턴스의 sg에 내 local의 ip에서 443으로 오는 것은 다 허용해준다고 함)이 터널링으로 연결을 해주고 있으니까 바로 Private IP로 연결이 되게 된다. 더군다나 해당 Web App이 동작하고 있는 EC2의 3000번 포트에 걸려있는 SG는 OpenVPN용 EC2 인스턴스를 인바운드를 허용해주고 있으므로 접속이 가능하다) my-dragon-app-ec2의 private IP로 접속해줘서 화면이 잘 출력되는지 확인해주자.

    이후 이 프론트엔드 소스를 Nginx에 복사를 해주기 위해서 프론트엔드 소스코드 directory의 /conf에 있는 conf.d에 들어가서 default.conf를 확인해보면 아래와 같이 Nginx 설정이 들어가 있다.

    대충 80으로 들어와서 nginx/html을 호출하라는 의미.

    이제 이 default.conf를 이 EC2의 /etc/nginx/conf.d로 cp를 해준 후 프론트엔드 소스코드 directory의 /build/*의 모든 것을 이 EC2의 /usr/share/nginx/html로 cp -r로 cp를 해주자. 이후 systemctl start nginx로 nginx를 실행해준 후 systemctl status nginx로 active(running)인지 상태를 확인 후 EC2를 reboot 했을때도 이 nginx가 동작할 수 있도록 systemctl enable nginx로 설정해두자.

    이후 이 EC2에서 localhost:80으로 curl(=API Test linux command)을 날려서 제대로 호출이 되는지 확인한다.

    이후 내 local web browser로 (private ip 주소):80으로 하면 접속이 불가능한데 그 이유는 이 80 Port의 sg는 alb에서만 접속이 가능하도록 설정이 되어 있기 때문이다. 따라서 이때는 아래와 같이 이 ALB로 직접 접근을 해야하는 것이다.

     

    위의 ‘주의 요함’에 나와있듯이 현재 https로 접속할 수 없는 이유는 이 ALB에 Domain을 할당하지 않고 곧바로 이 주소로 접속을 했기 때문이다. 따라서 Route53에서 해당 Domain에 이 ALB를 등록해줘야하는데 그러기 위해서 미리 생성했던 Domain에서 Application Load Balancer에 대한 별칭(Aliasing)을 만들어주도록 하자.

    이후 이 alb의 HTTPS:443 Listner의 규칙을 수정해서 내가 위에서 설정한 레코드 이름에 해당하는 호스트 헤더로 이 alb에 접근하게 되면 front end(Nginx)가 있는 80-tg으로 전달이 되도록 해주자

    근데 지금은 프론트 엔드 소스(Nginx에 올라가 있는 HCJ Static Contents)는 보이지만 아무런 동작을 하지 않는다. 왜냐하면 뒤에 Tomcat WAS가 떠있지 않은 상태라서 API Reqeust-Response가 되지 않고 있기 때문이다.

     

    이제 백엔드 소스를 수동으로 배포하도록 해보자

    해당 SpringBoot Project의 application.properties에는 정상적으로 RDS를 connection하기 위해서 RDS의 endpoint(RDS가 생성될 때 부터 자동으로 생성되어 할당됨)가 spring.datasource.url로 들어가 있다. 그리고 이 Spring Boot Application의 Controller에는 /healthz로 up이라고 return하는 아래의 API도 들어가 있다.

    이후 ec2로 ssh 접속을 한 후 아래와 같이 mysql url로 DB url(=endpoint)로 들어가자.

    이후 employee라는 db를 만들어주자.

    그 후 mysql의 root 계정에 대한 외부 접속 권한 부여를 위해서 아래와 같이 GRANT 작업을 해주자

    그 후 EC2로 돌아와서 sudo -i로 root 권한으로 git clone으로 백엔드 소스를 땡겨받은 후

    SpringBoot Project의 application.properties(Web App Configuration)에 내가 생성한 RDS의 endpoint를 아래와 같이 입력해주면 된다

     

    그 후 mvn clean 커맨드을 통해서 mvn을 initalize 시킨 후 mvn package 커맨드를 통해서 빌드를 하자.

    그렇게 되면 아래와 같이 project폴더/target directory에 .jar 파일이 생성이 되게 되고

    이걸 아래의 nohup java -jar (SNAPSHOT.jar 파일명) & 명령어로 백그라운드로 실행시키자.

    이후 netstat -ntlp로 up이 되었는지 확인 후 curl을 localhost:8080/api/v1/healthz로 날려서 200:OK가 뜨는지 확인해주자.

    이후 내 local web browser에서도 (private ip address):8080/api/v1/healthz로 날려서 UP이 뜨는지 확인해주자.

    이후 ALB에서 HTTP:8080의 Target group으로 들어가는 상태 검사 경로가 root(/)로 되어 있어서 unhealthy로 되어있는데 이것을 /api/v1/healthz로 갈수 있게 설정을 변경하자.

    이제 alb를 통해서 Backend tg로 진입하려면 반드시 아래의 호스트 이름인 api.fastcampus-dragon.net으로 들어와야 하므로 아래의 호스트 이름을 Route53에서 도메인으로 등록을 해주면 된다.

    아래와 같이 레코드 이름을 등록해주면 끝이다.

    이제 브라우저에서 해당 레코드(api.fastcampus-dragon.net)로 진입하게 되면 ALB를 통해서 backend tg로 트래픽이 전달된 결과를 확인하자.

    이제 CloudFront와 ALB를 연동시켜보자

    지금까지는 ALB 뒷단에 EC2 instance를 만든 후 여기에 프론트엔드 소스와 백엔드 소스를 배포를 한 후 Route 53에 ALB의 도메인을 등록을 한 후 해당 도메인으로 접근하여 프론트엔드 소스와 백엔드 소스가 접근이 잘되는지를 파악했다면 이제 ALB 앞단에 Cloudfront를 연결해서 실제로 User가 Cloudfront를 통해서 프론트엔드 서비스와 백엔드 서비스와 통신할 수 있도록 할 예정이다.

    Cloudfront에 ACM을 연결해서 기존에 ALB에서 처리했던 SSL/TLS 인증서를 Cloudfront에서 설정할 수 있도록 하자.

    헌데 이 Cloudfront는 AWS Global Service이므로 Seoul Region에 ACM을 생성하는게 아니라 Virginia Region(AWS Global Service의 실제 Region)에 ACM을 생성해야 함

    Virginia Region에서 ACM을 만든 후 Route53에서 DNS레코드 인증이 끝났다면 이제 다시 Seoul Region으로 돌아와서 ALB 설정을 변경해주자.

    HTTP 80에서 HTTPS 443으로 redirect하던 설정을 아래와 같이 바로 my-dragon-app-tg-80으로 가도록 해주자 왜냐하면 ACM이 Cloudfront(ALB 앞에 위치)에 위치해있으므로 이미 ALB에 오기도 전에 HTTPS 인증이 끝나서 안전하게 걸러진 것들만 들어오기 때문에 HTTP 80으로 ALB에 꽂히는 애들은 그대로 Frontend(Nginx)로 보내줘도 된다.

     

    호스트 헤더 역시 www.fastcampus-dragon.net header로 들어오는 놈들은 싹 다 Cloudfront에서 이미 걸러진 놈들이므로 ALB에서는 그대로 어서오시오-가시오 느낌으로 Frontend(Nginx)로 넘겨주도록 해주자  

    이제 Cloudfront를 만들어보자. Origin은 당연하게 내 ALB를 선택을 해주자

    그 후 뷰어 프로토콜에서 HTTP로 받으면 HTTPS로 redirect하라고 설정을 해준다

    그리고 대체 도메인(CNAME)에 아래와 같이 원래는 ALB에서 사용하던 호스트 헤더를 입력해주자.

    그다음 SSL 인증서를 ACM에서 생성한 것으로 설정해주자

    그 후 Route 53에서 fastcampus-dragon.net 호스팅 영역에 설정되어 있는 레코드 이름(www.fastcampus-dragon.net)에 해당되는게 ALB였는데 지금은 Cloudfront이므로 내가 생성한 Cloudfront로 바꿔주자

    이제 웹 브라우저에서 해당 레코드 이름(www.fastcampus-dragon.net)으로 접속한 후 인증서 뷰어를 보게 되면 인증서가 발급되어 있는 것을 볼 수 있게 된다

    그리고 또 F12를 눌러서 Network tab을 선택하면 Cloudfront에서 hit가 되어 있는지 확인할 수 있다. 유일하게 아래에서 해당 레코드 이름(www.fastcampus-dragon.net)에 대해서만 컨텐츠가 hit 되었다는 것을 볼 수 있다.

    CloudFront에 ACM(AWS Certificate Manager)으로 SSL 인증서를 붙여서 HTTPS 트래픽을 받아들이는 경우, CloudFront에서 원본 서버로의 트래픽 전달 방식은 설정에 따라 달라집니다. CloudFront 배포 설정에서 "원본 프로토콜 정책"을 지정할 수 있으며, 이 정책에 따라 원본 서버로의 트래픽이 HTTP 또는 HTTPS로 전달됩니다.

    CloudFront의 원본 프로토콜 정책 옵션은 다음과 같습니다:

    1. HTTP Only: CloudFront가 원본 서버와 통신할 때 항상 HTTP를 사용합니다.
    2. HTTPS Only: CloudFront가 원본 서버와 통신할 때 항상 HTTPS를 사용합니다.
    3. Match Viewer: CloudFront가 뷰어(Viewer)와 통신한 프로토콜과 동일한 프로토콜을 사용하여 원본 서버와 통신합니다. 즉, 뷰어가 HTTPS로 요청을 보냈다면 CloudFront도 HTTPS로 원본 서버에 요청을 보냅니다.

    따라서, 원본 서버로 전달되는 트래픽이 항상 HTTP가 되도록 하려면 "HTTP Only"를 선택해야 합니다. 반면, CloudFront와 원본 서버 간의 통신을 보안하기 위해 HTTPS를 사용하려면 "HTTPS Only"를 선택하거나 "Match Viewer"를 선택하여 뷰어의 요청 프로토콜과 일치시킬 수 있습니다.

    예시로, CloudFront 배포 설정에서 다음과 같이 원본 프로토콜 정책을 선택할 수 있습니다:

    json코드 
    {
      "Origins": [
        {
          "Id": "S3-Origin",
          "DomainName": "example-bucket.s3.amazonaws.com",
          "OriginPath": "",
          "CustomHeaders": {
            "Quantity": 0
          },
          "S3OriginConfig": {
            "OriginAccessIdentity": ""
          },
          "OriginShield": {
            "Enabled": false},
          "OriginProtocolPolicy": "http-only",  // 또는 "https-only", "match-viewer"
          "OriginSslProtocols": {
            "Quantity": 1,
            "Items": [
              "TLSv1.2"
            ]
          },
          "OriginReadTimeout": 30,
          "OriginKeepaliveTimeout": 5
        }
      ]
    }
    
    

    여기서 OriginProtocolPolicy 값을 http-only로 설정하면 CloudFront가 항상 HTTP로 원본 서버와 통신하게 된다. 

     

     

     

    최종적으로 만든 서비스를 테스트 한번 해보자. 아래는 지금까지 만든 서비스 아키텍쳐이다

    내 local web browser로 정상 동작하는지 확인해주자.

     

     

    3. Automating CICD Pipeline 

    지금까지는 수동으로 프론트 엔드 코드와 백엔드 코드를 배포했지만 이제는 Jenkins + Ansible(지금은 EC2 기반이니까 그냥 Ansible로 CD를 구현하자)를 붙여서 이러한 코드 배포를 자동화 해보자.

    전체적인 아키텍쳐이다.

    DevOps 담당자가 소스를 수정한 후 GitHub에 Commit과 Push를 한 후 Jenkins에 Job을 trigger해주면 Jenkins는 업데이트된 GitHub Repo를 Build한 후 빌드된 소스를 Ansible을 통해서 프론트엔드와 백엔드로 배포를 하는 구조이다.

     

    그러면 이제 Ansible code를 확인해보자.

    vars에는 변수 값들이 정의되어 있고 실제로 playbook을 실행시키는 부분은 deploy_backend.yml과 deploy_frontend.yml이다

    그리고 위와 같이 roles에 backend와 frontend라는 디렉토리에 tasks가 들어가있는데 여기에는 정의된 playbook이 존재하게된다. 가령 백엔드의 경우 정의된 playbook은 아래와 같이 일단 build를 통해 떨어진 jar file을 copy한 후 기존에 떠 있던 java process를 kill해준 후 copy해놨던 jar file로 java process를 실행시켜주자. 이러한 동작을 하는 각각의 모듈에는 Bash shell script가 들어가 있다.

    이제 Jenkins용 EC2(EC2 깡통에 Jenkins Process를 올리자)를 만들어주자

    Jenkins용 EC2는 현재 소스가 배포되는 EC2가 Private Subnet에 존재하므로 통신을 위해서 동일한 Subnet에 만들어준 후 SG를 만들되 아래와 같이 OpenVPN EC2가 ssh로 들어올 수 있게 해주자

    또한 Jenkins 서비스 포트인 8080으로 OpenVPN이 들어올 수 있게 sg를 설정 해주자(미래에 Jenkins 관리자가 자신의 웹 브라우저로 8080 포트를 통해서 Jenkins UI에 접속해서 작업을 진행하도록하는 목적)

    ALB 역시 Jenkins 서비스 포트인 8080으로 들어올 수 있게 해주자

    이제 내 Putty로 jenkins ec2의 private ip로 ppk key로 접속해서 sudo -i로 변경 후 jenkins 패키지를 깔아두자.

    또한 프론트 코드는 node js이므로 jenkins EC2에 프론트 코드 빌드를 위해서 npm을 깔아주고, 백단 코드는 java maven이므로 백단 코드 빌드를 위해서 java와 maven을 깔아주자.

    또한 git으로 source를 가져올 것이기 때문에 git 또한 install 해주고,

    Ansible을 통해서 배포를 해줄 것이기 때문에 Ansible 또한 install 해주자.

    자 이제 이 서버에 systemctl start jenkins를 해줘서 jenkins process를 시작시키자.

    그 후 local web browser에서 Jenkins ec2의 private ip로 접속을 해서 로그인 인증을 완료한 후

    기본 plugin 설치 후 추가적으로 수동으로 NodeJS plugin을 설치한 후 Global Tool Configuration에서 인스턴스 자체에 설치해둔 nodejs를 등록해주자(보통 인스턴스 자체에 nodejs를 깔아도 되고 아니면 이처럼 Global Tool Configuration에서 NodeJS Plugin 설치 후 NodeJS Installation 이 2가지의 방법으로 설치할 수 가 있는데 이번에는 둘 다 사용해보자)

    이제 Jenkins Pipeline을 만들어보자.

    그 후 Pipeline Script에는 앞서 살펴봤던 Jenkinsfile을 넣어주도록 하자

    MY_KEYPAIR_NAME은 내가 기존에 AWS에서 설정해뒀던 keypair로 설정하고 MY_APP_PRIVATE_IP에는 실제로 배포를 진행할 ec2(여기선 프론트랑 백 소스 둘 다 존재하는 ec2; my-dragon-app-ec2)의 private IP를 넣어주자

    이제 백엔드에 Deploy하는 Pipeline도 만들어주자

    아래에 생성된 2개의 Jenkins Pipeline을 확인해보자.

    이제 pem key를 jenkins ec2에 WinSCP로 넣어줘서 자동으로 이 jenkins ec2가 소스코드가 존재하고 배포되고 있는 EC2(my-dragon-app-ec2)에 접속이 될 수 있도록 해주자.

    당연히 해당 pem key의 권한도 아래와 같이 600으로 수정해주자.

    그리고 이제 이 pem key를 jenkins도 사용할 수 있도록 /usr/local/share로 cp 시키고, jenkins가 사용할 수 있게 ownership을 root → jenkins로 수정해주자

    이후 jenkins의 sg가 22번 port로 my-dragon-app-ec2로 들어올 수 있도록 inbound policy에 아래를 추가해주자.

    그리고 나서 이제 Jenkins에 생성된 두 Pipeline을 빌드(frontend-pipeline은 Jenkins가 npm run build부터 Ansible 배포까지 진행됨; backend-pipeline은 maven build부터 Ansible 배포까지 진행됨) 시켜주자.

    근데 지금은 이 Jenkins가 앞단에 Domain이 없고, PrivateIP 주소로 OpenVPN을 통해서 접근하므로 도메인 주소를 붙여서 비록 private EC2 이지만 도메인 네임으로 ALB를 통해서 들어올 수 있게 해주자.

    이후 아래와 같이 jenkins ec2를 tg로 만든 후 ALB(my-dragon-app-alb)에 규칙(health check는 /login으로 해주자)을 추가해서 HTTPS 443으로 들어올 경우 트래픽이 전달되도록 하자.

    아래와 같이 내가 설정해둔 jenkins 도메인 record로 진입시 jenkins로 들어갈 수 있는 것을 확인하자.

    근데 지금 문제점은 만약 코드 수정을 해주면 내가 먼저 git push를 해준 후 GitHub 코드가 변경이 되면 내가 직접 Jenkins pipeline에서 Build하기라는 일종의 Job trigger를 수동으로 줘야지만 이 Jenkins가 Build를 진행하게 된다. 따라서 내가 git push를 눌렀을 때 이런 CI/CD가 자동으로 진행된다면 얼마나 좋을까?

    이에 대한 해답이 바로 Webhooks로 GitHub에 이벤트 발생시 Jenkins Pipeline이 동작하도록 한다. 즉, 일단 Jenkins에 GitHub Plugin을 설치한다 → GitHub의 settings의 Webhook 설정에 들어가서 Jenkins Server URL을 입력한 후 서로 간에 Authentication 인증을 해주면 이게 자동으로 일어나게 된다.

     

     

     

     

    4. Implementing Auto Scaling Group for HA

    아키텍쳐

    EC2를 만들고 이걸로 AMI를 만들어서 Launch template을 만든 후 ASG를 만들어서 그 앞에 ALB를 붙이도록 한다. 실습 환경은 그대로 기존에 VPC 세팅했던거 재사용한다.

     

    sg 2개를 만들어주자 ⇒ ASG에 사용되는 alb를 위한 SG(my-asg-alb-sg; global에서 접속하는 80 port만 inbound 규칙 설정) + ASG에 들어있는 ec2를 위한 SG(my-asg-ec2-sg; 3개의 inbound rule(OpenVPN으로 들어올 22번 + OpenVPN으로 들어올 80 + ALB로 들어올 80)을 설정) ⇒ SG의 경우 언제나 생성이 되게 되면 Name Tag와 보안 그룹 이름이 다르므로 항상 이 둘을 동일하게 설정해주자.

     

    이제 EC2 Instance(my-asg-ec2)를 만들고 이 안에 필요한 Package를 설치 후 AMI Image로 만들어보자.

    EC2를 생성 이후 putty로 접속하여 sudo -i로 변환 후 여기다가 Apache(httpd) php mysql 이라는 국룰 3-tier APM과 git을 설치해준 후 Apache(httpd)를 실행해 준 후 이 Apache(httpd)가 static contents를 읽어내는 directory인 /var/www/html에 git clone으로 소스(php라는 server side script 언어로 만들어져 있음)를 받아오고 Apache(httpd)를 systemctl restart httpd 해주면 제대로 반영된다.

    이후 내 local web browser에서 (해당 EC2의 private ip 주소)/(git hub repo 이름directory)/index.php로 접속해줘서 정상적으로 출력되는지 확인해보자.

     

    이후 이 EC2로 아래와 같이 image 생성을 해주자.

    재부팅 안 함을 눌러야 현재 떠있는 EC2가 재부팅을 안하게 되므로 눌러주자

    Launch template 생성하기당연히 OS Image로는 내가 만들어둔 AMI를 선택하고 나머지 설정(SG, key pair 등)은 모두 다 기존 EC2와 동일하게 설정해주자. 이제 이 AMI를 가지고 Launch Template(my-asg-lt)을 만들어주자.

     

    이후 ALB를 생성 하자(my-asg-ec2-alb)

    이후 이 ALB에 Route53을 설정해주기 위해 먼저 내가 생성해놨던 호스팅 영역인 fastcampus-dragon.net을 선택 후 이 안에 레코드를 아래와 같이 생성해주자.

    TG을 생성해두자(my-asg-ec2-tg-80). 이때 State check PATH는 /ec2meta-wbpage/index.php(내 local web browser로 정상 동작 확인한 PATH)로 설정하고 내가 생성했던 my-asg-ec2를 포함시키자.

    이후 해당 레코드로 local web browser로 접속해서 정상 동작 여부를 확인해주자.

    이제 ASG를 만들어서 설정해두자. 시작 template은 앞서 만들어뒀던 template으로 설정해두자.

    그 후 VPC로 내가 만들어둔 VPC를 설정하고 AZ과 subnet을 아래와 같이 내가 미리 만들어뒀던 mapping pair로 2개를 선택해두자(나중에 혹시 모를 이중화를 위해서)

    해당 ASG에서 만들 instance에 붙일 Name Tag까지 아래와 같이 붙여주자

    아래와 같이 해당 ASG를 용량 설정까지 해주자

    이후 동적 크기 조정 정책을 생성해주자

     

    자 이제 EC2 안에 들어가서 stress test를 진행하여 CPU 사용량을 늘리면서 옳바르게 scale out이 되는지 확인해보자.

    일단 Putty로 EC2에 들어가서 stress 패키지를 설치 후 stress 명령어로 부하주면 CPU가 99% 사용된다고 load가 걸리게 되고

    이게 CloudWatch로 정보가 전달이 되기 때문에 CloudWatch 모니터링으로 들어가서 해당 ASG에 존재하는 인스턴스 갯수의 변화를 관찰해주자

    아래와 같이 EC2가 하나 더 생성된 것을 확인해보자.

    이후 내 local web broswer로 해당 ALB의 PATH로 접속하면 자동적으로 ASG내에서 Round Robin의 형식으로 Load balancing이 이루어지게 된다.

     

    마지막으로 해당 stress를 없앴을 때의 Scale In이 정상적으로 되는지도 확인해보자.

    또한 아래와 같이 해당 ASG의 고급 구성에서 종료 정책으로 Scale In이 될때 어떤 인스턴스부터 먼저 Terminating을 시킬지도 정할 수 있다. Default는 가장 오래된 인스턴스부터 지우는 것이다.

    또한 ASG에서 등록 취소 지연의 시간을 설정해주자. 10초로 설정해서 빠르게 drain이 이루어지도록 해주자.

     

    5. Adding Logging & Backup & Monitoirng/Alarming Solution

    여기서는 AWS 서비스 운영에 필요한 작업들을 실습해보자

    1. Logging

    AWS에서는 다양한 서비스에서 발생하는 Log를 S3에 저장한 후 Athena라는 빅데이터 쿼리 서비스를 이용해서 필요한 정보를 뽑아낼 수 있게 되는데 여기서는 3가지 서비스의 log만 S3에 저장하고 실습해보자.

     

    (1) CloudTrail의 Audit Log

    일단 아래와 같이 CloudTrail부터 하나 생성해두자

    그리고 같이 생성된 S3 버킷에 들어가보면 해당 S3 버킷에 CloudTrail directory에 Audit Log가 남는 것을 볼 수 있게 된다

    그 후 이 S3에 대해 Athena에서 테이블(AWS 공홈에 있는 CloudTrail Audit Athena table 예제 활용)을 생성하여 이 S3에 대해서 쿼리할 수 있게 해주자.

    쿼리를 진행하기 전에 먼저 쿼리가 저장될 S3 위치부터 설정을 해줘야 하므로 아래와 같이 그 위치를 설정해주자

    이제 쿼리를 아래와 같이 해주자

     

    (2) ELB의 Access log

    기존에 만들어뒀던 ALB의 속성에서 엑세스 로그 활성화를 누른 후 이 Access log가 저장될 S3 위치를 잡아준 후 이에 대한 쿼리를 진행할 Athena table(AWS 공홈에 있는 ELB Athena table 예제 활용)을 만들어준 후 쿼리를 마찬가지 방법으로 실행해서 내가 필요한 정보들만 아래와 같이 뽑아내면 된다

     

    (3) VPC의 Flow log

    기존에 사용하던 VPC에서 flow log 생성을 누른 후 1분으로 짧게 log 집계가 이루어지도록 설정 한 후 S3 버킷으로 전송하도록 하자. VPC의 Flow log는 모든 EC2의 모든 네트워크 인터페이스(NI Card)에서 발생하는 모든 트래픽의 패킷 정보를 저장한다.

    그 후 마찬가지로 Athena table(AWS 공홈에 있는 vpc flow log table 예제 활용)을 만든 후 쿼리를 진행하자.

     

    2. Backup

    (1) EC2 AMI Backup

    일단 AWS Backup부터 하나 만들어주기 위해서 Backup Vault(my-fastcampus-vault)부터 만들어준 후 온디멘드 백업 생성을 아래와 같이 먼저 EC2(my-dragon-app-ec2)에 대한 Backup부터 진행시켜주자.

    이렇게 Backup을 하나 생성해두면 그 즉시 EC2의 AMI가 아래와 같이 하나(백업본) 생성이 되게 된다

    또한 아래와 같이 이러한 AMI가 생성이 되게 되면서 이 EC2 Instance에 대한 EBS Snapshot 역시 하나 새롭게 생성이 되게 된다

    만약 내가 복원을 하고 싶다면 내가 만들어뒀던 백업 볼트에서 아래와 같이 복원을 누르면 이 AMI에 들어있던 기존 EC2의 정보대로 default setting이 되게 된다.

    이후 EC2 인스턴스 하나가 아래와 같이 생성이 되게 된다

    이후 Name tag를 restore-my-dragon-app-ec2로 설정해준 후 이것의 private ip로 ppk key로 putty로 접속되는지 확인해보자.

     

    (2) EBS Backup

    이제 EC2(my-dragon-app-ec2)에 달려있는 EBS에 대한 Backup을 진행해보자.

    EC2의 Storage의 Volume ID를 아래와 같이 복사 한다.

    그 후 AWS BackUp에서 아래와 같이 EBS 볼륨 백업으로 온디맨드 백업을 설정하여 백업을 진행하도록 한다. 백업 볼트는 기존에 만들었던 백업 볼트를 사용하도록 하자

    그러면 백업 볼트에 이 EBS Volume 백업본이 Snapshot으로 아래와 같이 생성되어 있다

    그 후 동일하게 이 Snapshot으로 복원을 하면 EBS 볼륨에 생성이 되게 된다

    그 후 이 복원한 EBS Volume을 복원했던 EC2(restore-my-dragon-app-ec2)에 붙여보도록 해보자.

    일단 아래의 현재 붙어있는 Volume부터 떼어내야한다.

    그러기 위해서는 일단 해당 EC2를 중지시킨 후 이 EBS 볼륨을 아래와 같이 분리시키면 된다.

    그 후 새로운 EBS 볼륨을 달아주도록 해보자

    이후 EBS 볼륨을 붙인 EC2 인스턴스를 다시 시작한 후 이 EC2로 Putty로 접속한 다음 해당 디스크 디바이스가 잘 붙었는지 확인해보자.

     

    (3) RDS 백업

    기존에 사용하던 RDS를 백업하기 위해서 온디맨드 백업을 아래와 같이 생성해주면서 백업 볼트는 기존에 사용했던 vault를 선택해주자

    또한 RDS 백업을 진행할때는 백업 시간이 현재 시간과 너무 가깝진 않은지 판단한 후 가까우면(가까우면 백업이 실패가 난다) RDS에 대한 백업 시간을 아래와 같이 바꿔주도록 해보자

    아래의 RDS 백업이 Vault에 저장되어 있는 것을 확인하자.

    이 또한 이것을 이용해서 복원을 하게 되면 아래와 같이 정상적으로 복원이 되게 된다

    이때 이 복원된 RDS는 SG이 Default SG로 설정이 되게 되는데 내가 설정해줬던 SG로 다시 바꿔주기만 하면 된다.

    그 후 아래와 같이 복원해 놓은 EC2에서 다시 복원한 RDS로 접근하도록 해보자.

     

    3. Monitoring & Alarming

    OpenVPN으로 SSH를 통해 EC2(my-dragon-app-ec2)에 ppk key로 접근한 후 여기에 Cloudwatch Agent를 install해준 후 config wizard 실행파일을 그 안에서 실행시켜서 이 Agent가 어떤 Port로 어느 정도 시간 간격으로 Daemon으로 돌려서 Metric 정보를 보낼 건지 등에 대한 configuration을 진행해주면 Config.json 파일에 내가 Configuration을 한 모든 내용들이 아래와 같이 정의되게 된다

    그 후 아래의 명령어로 cloudwatch agent를 실행시켜보도록 하자

    이제 이러한 CloudWatch Agent의 Metric 정보들을 CloudWatch로 쏠 수 있도록 Agent가 돌아가고 있는 EC2에 할당할 IAM role을 아래와 같이 만들어두자

    이후 해당 EC2에서 IAM 역할을 아래와 같이 지금 만든 IAM role로 수정해주자

    CloudWatch Agent와 CloudWatch는 자동적으로 연결이 되어 있으므로 이렇게 IAM role 설정만 완료하면 이제 CloudWatch에서 Metric 정보들을 볼 수 있게 된다

    이제 CloudWatch dashboard를 생성해보자. 내가 원하는 Metric이 나오도록 생성하면 된다.

    이제 이 CloudWatch에 Metric에 대한 Condition 기능을 아래와 같이 추가해보도록 하자.

    그 후 알림 기능을 아래와 같이 새로 SNS topic을 만든 후에 이 topic에서 email(endpoint)를 통해서 알림을 보내도록 설정하자.

    아래에 CloudWatch alarm이 설정되어 있는 것을 확인하자.

    이후 SSH로 ppk key로 my-dragon-app-ec2에 접속해서 stress test를 해보자. 그러기 위해서 먼저 cat /proc/cpuinfo로 전체 cpu cores가 몇개인지 확인한다(아래의 경우에는 1이다). 이후 sudo stress -c 1 &으로 이 1개의 core에 background로 stress load를 줘보자.

    그리고 top으로 cpu utilization를 찍어본 후 cloudwatch dashboard로 한번 CPU Utilization이 제대로 찍히나 확인 후 이메일로 alarm이 잘 왔는지 확인해보자.

     

     

    [Reference]

    1. https://aws.amazon.com/ko/what-is/iaas/

     

    IaaS란 무엇인가요? - 서비스형 인프라 설명 - AWS

    서비스형 인프라란 무엇인가요? 서비스형 인프라(IaaS)는 인터넷을 통해 종량제 방식으로 컴퓨팅, 스토리지, 네트워크 리소스 같은 IT 인프라를 제공하는 비즈니스 모델입니다. IaaS를 사용하여 애

    aws.amazon.com

    2. https://www.jenkins.io/doc/tutorials/tutorial-for-installing-jenkins-on-AWS/

     

    Jenkins on AWS

    Jenkins – an open source automation server which enables developers around the world to reliably build, test, and deploy their software

    www.jenkins.io

    3. https://aws.amazon.com/ko/getting-started/hands-on/setup-jenkins-build-server/faq/

     

    FAQ

    Q: Jenkins란 무엇입니까? Jenkins는 Java로 작성된 오픈 소스 지속적 통합 도구로서, 소프트웨어 개발을 위한 사용자 지정 통합 서비스를 제공합니다. 많은 개발 팀에서 사용하는 서버 기반 시스템입

    aws.amazon.com