NACL과 Security group 모두 보안을 위한 녀석들이다. AWS에서 크게 인스턴스를 보호하는 방법은 두가지인데 그 두 가지가 이 NACL과 Security group이다. 이 두 녀석으로 외부의 특정 클라이언트와 통신을 허가할지 거부할지를 결정하게 된다. 그럼 이 둘의 차이는 무엇일까?
NACL (Network Access Control List)
NACL은 Stateless한 보안 체계이다. 즉, 요청을 한 번 받으면 그 요청에 대해 기억하고 있지 않다는 의미이다. 그래서 다음 그림을 보자.
NACL로 보안 체계를 걸어둔 VPC가 있는데 이 NACL이 들어오는 포트는 80으로 허가했고 나가는 포트는 아무것도 허가한게 없을 때, 외부에 있는 클라이언트에서 요청이 들어오면 클라이언트는 응답을 받을 수 있을까?
요청은 가능하지만 응답은 할 수 없다. 왜냐하면 NACL은 Outbound로 허가한 포트가 없고 NACL은 Stateless 보안 체계이기 때문이다.
그래서 NACL은 Inbound, Outbound 규칙이 명확하게 명시되어야 한다. 오로지 규칙만을 보고 요청과 응답을 허가하거나 거부하기 때문이다.
그렇다면 Security group은 어떻게 다를까?
Security group
Security group은 Stateful한 보안 체계이다. 즉, 들어오는 요청을 기억하고 있다가 응답한다는 의미이다. 결론부터 말하면 이 Security group에 위 NACL처럼 Inbound에 80, Outbound에 아무것도 허가하지 않더라도 요청을 80으로 했다면 클라이언트는 요청에 대한 응답을 받을 수 있다.
클라이언트가 특정 VPC내 80 포트에 매핑된 어떤 서버에 접속할 때 이 EC2 보안 그룹에 인바운드 아웃바운드 규칙이 그림처럼 되어있다면 아웃바운드로 아무것도 허가하지 않았지만 응답을 받을 수 있다. 이는 Security group이 Stateful 보안 체계라 들어온 요청을 기억하고 있다가 응답을 보내주기 때문이다.
NACL 설정해보기
NACL을 직접 설정해서 어떻게 동작하는지 확인해보자. 우선 VPC > Security > Network ACLs로 들어가야 한다.
이렇게 보다시피 화면 우측 상단 Create network ACL 버튼이 보인다. 클릭.
나는 아래와 같이 설정한 뒤 만들었다.
- Name: private-nacl
- VPC: 사용중인 VPC
만들고 나면 리스트에 만든 NACL이 보이는데, 클릭해서 우선 서브넷을 연동하자.
연동하고자 하는 서브넷을 선택하고 'Save changes' 클릭
이제 Inbound, Outbound 설정을 해야한다. 인바운드는 들어오는 트래픽이고 아웃바운드는 여기서 나가는 트래픽을 말한다.
예를 들어, Bastion host에서 이 private EC2로 SSH로 접속하는것을 허용하고 싶다면 Inbound에는 SSH 또는 포트(22) 선택을 한 후 Source에 Bastion host가 위치한 IPv4 CIDR를 입력하면 된다.
중요한건 이제 밖으로 나가는 Outbound인데 여기서 나가는 포트는 22가 아니다. 왜냐하면 들어오는 포트가 22인거지 나를 호출한 곳은 22가 아니란 얘기다. Outbound에 22로 설정하면 이 private EC2에서 어딘가로 SSH를 접속하는것을 의미하게 된다. 난 그것을 원하는게 아니고 나의 22번 포트로 들어온 요청에 대한 응답을 보내고자 한다. 그래서 Outbound는 임시포트(1024 - 65535) 중 아무거나를 허용해야 한다. 다음 그림처럼 말이다.
자, 이렇게 설정하고 Bastion host에서 이 private EC2에 접속해보자.
위 그림에서 10.0.0.208이 private EC2이고, 10.0.0.24가 Bastion host이다. 접속해보자.
다음과 같이 잘 접속된다.
NACL은 Stateless 보안 체계라고 했다. 그래서 규칙이 절대적인데 그 말은 이 상태에서 Outbound 규칙을 제거하면 접속이 불가능해야 한다. 한번 해보자. 보다시피 Outbound 규칙이 전부 Deny 상태이다.
다음 화면처럼 접속되지 않는다. 이런 보안 체계가 NACL이다. 만약, Security group으로 같은 행위를 했다면 정상적으로 접속이 될 것이다. 왜냐하면 Security group은 Statefull한 보안 체계니까.
나머지는 기본값으로 세팅 후 "Restore backup" 버튼을 클릭해서 복원한다. 완료가 되면 해당 스냅샷 ID를 가져와서 EC2 > Elastic Block Store > Volume 으로 간다. (해당 스냅샷 ID는 Backup vault에서 확인 가능하다. 아래 사진 참고)
Volume 리스트에 스냅샷으로부터 복원된 EBS가 보인다.
이제 이 복원한 EBS를 EC2에 붙여보자. 그러기 위해선 일단 EC2의 Volume을 떼어내야 한다. 그러기 위해선 EC2를 중지해야 한다. 먼저 중지를 하자. 중지가 완료되면 해당 인스턴스의 볼륨을 떼어낸다. 떼어내고 싶은 인스턴스를 클릭 후 하단 "Storage" 탭에서 볼륨을 클릭한다.
볼륨을 클릭하면 볼륨 리스트에서 해당 볼륨이 보이는데 선택 후 우측 Actions > Detach volume 클릭
잘 떼어내면 상태가 In-Use에서 Available로 변경된다.
이제 백업한 볼륨을 붙여보자. 다시 볼륨리스트로 가서 백업한 볼륨을 선택하고 Actions > Attach volume 클릭
그러면 복원 설정 화면에서 다음을 수정한다.
- Instance: 볼륨에 붙일 인스턴스
- Device name: /dev/xvda
Attach volume을 클릭하면 백업한 볼륨이 인스턴스에 붙는다. 붙었는지 확인하려면 상태를 보자. 해당 볼륨이 Available이 아니고 In Use로 보여진다.
인스턴스에서도 확인 가능하다. 백업한 볼륨을 붙인 인스턴스를 클릭해서 하단 Storage 탭을 클릭하면 볼륨 ID가 보인다. 백업한 볼륨의 ID인지 확인.
RDS 백업
이번에는 RDS를 백업해보자. AWS Backup 서비스로 가서 Dashboard 화면에서 "Create on-demand backup" 버튼 클릭
설정 부분은 다음과 같다.
- Resource type: RDS
- Database name: 백업할 RDS
- Backup window: Create backup now
- Total retention period: Forever
- Backup vault: 위에서 사용했던 vault
- IAM Role: Default role
이렇게 설정한 후 백업을 만든다. 그러면 Job 리스트에서 백업이 진행중인 것을 확인 가능하다.
RDS 백업은 시간이 조금 소요되므로 여유롭게 기다려보자. 시간을 좀 기다리고 나니 다음과 같이 백업이 완료됐다. Status가 Completed 상태가 된 것을 확인할 수 있다.
그리고 이 백업된 스냅샷은 RDS 서비스 내 Snapshots 화면에서도 확인할 수 있다. Backup service 탭을 눌러보면 다음과 같이 스냅샷이 보인다.
여기서 한 가지 알고 가야하는건 RDS 서비스를 만들면 시스템에서 자동으로 백업을 하는데 그 백업 시간이 있다. 이 백업 시간과 내가 직접 백업을 하려는 백업 시간이 너무 가까우면 위 작업을 따라했을 때 백업이 되지 않는다. 이를 참고하자. RDS의 백업 시간은 아래 사진처럼 확인 가능하다.
백업 시간을 확인하고자 하는 RDS를 클릭 > Maintenance & backups 탭 클릭후 Backup 섹션을 보자.
여기서 Backup window 항목을 보면 백업 시간을 확인할 수 있다. 이 시간을 변경할수도 있다. 참고하자.
이제 백업한 RDS로 복원을 해보자. 백업 볼트로 가서 백업한 RDS를 선택해서 Actions > Restore
복원 설정 화면에서 수정할 부분은 다음과 같다.
- DB Instance class: 원하는 클래스 선택
- DB Instance Identifier: 적절하게 입력
그 외는 기본으로 세팅된 원래 RDS 서비스와 동일한 설정을 따르고 복원을 해보면 된다.
근데 위 설정처럼 하면 다음과 같은 에러를 마주하게 된다.
t2.micro는 암호화가 지원이 안된다고 하니 살짝 더 큰 클래스를 선택해보자. (본인은 t2.small을 선택) 이 부분도 시간이 꽤 소요되니까 잘 기다려보자. 다 끝나면 다음과 같이 상태가 Completed로 변경된다.
그리고 RDS 서비스 내 Databases 화면으로 가면 백업한 RDS가 생성됐음을 확인할 수 있다.
백업한 RDS 세부 정보를 보면 Security group이 default로 설정되어 있다. 이것을 바꿔보자. 기존 RDS가 사용하는 보안 그룹으로.
테이블을 만들면 버킷에서 로그가 잘 쌓여있는지 확인해보자. 아까 트래픽을 발생시켰으니까 시간이 좀 지나면서 로그가 쌓일것이다.
아래 사진과 같이 오늘 날짜로 된 폴더 내 로그가 보인다.
이제 해당 로그 파일에서 원하는 쿼리를 날려보자. 다음은 위 링크에서 가져올 수 있는 예시 쿼리이다.
이 쿼리는 클라이언트 IP 주소별로 그룹화된 로드 밸런서가 수신한 HTTP GET 요청의 개수를 계산한다. 날려보자.
SELECT COUNT(request_verb) AS
count,
request_verb,
client_ip
FROM alb_logs
GROUP BY request_verb, client_ip
LIMIT 100;
쿼리 결과는 다음처럼 나온다. 내 IP는 11회 조회를 했고 그 외 나머지는 아마도 AWS의 서울 각 가용영역에서 접근한 것으로 보여진다.
ELB Access Log 중간정리
ELB의 Access Log를 Athena를 통해 쿼리문을 날려 원하는 데이터만 뽑아봤다. 이렇게 접근 관련 로그도 잘 남길 수 있게 됐다.
Amazon VPC Flow Log
이번에는 VPC Flow Log를 활성화해서 Athena에서 확인해보자.
AWS Console에서 VPC를 검색해서 서비스에 들어가고 화면 좌측 VPC를 클릭한다.
Flow log를 활성화 시킬 VPC를 선택한 다음에 하단 Flow logs 탭을 클릭해서 Create flow log 버튼을 누른다.
진입 화면에서 다음 사진에서 빨간 박스 부분을 수정해야 한다.
- Name: cwchoiit-vpc-flow-logs
- Destination: S3 bucket
- S3 bucket ARN: S3 버킷의 ARN을 입력 (S3 Bucket은 직접 만들어서 ARN 입력란에 입력해야 한다)
입력해서 만들면 Flow logs 탭에 만든 플로우 로그가 보인다. (나는 Maximum aggregation interval을 1분으로 수정했다)
이렇게 생성한 후 S3 버킷으로 가보면 폴더가 자동으로 아래처럼 만들어진다. (AWSLogs/AWSAccountID 경로)
아직 로그가 생성되지 않았고 이 로그를 생성하기 위해 트래픽을 발생시켜보자. 이 VPC 플로우 로그는 EC2에 붙어있는 네트워크 인터페이스에서 발생하는 모든 트래픽을 트래킹한다. 아래 사진이 EC2의 네트워크 인터페이스에 들어가면 보여지는 모든 네트워크 인터페이스이다. 모두 동일한 VPC(위에서 설정한 VPC)에 붙어있는 네트워크 인터페이스이고 이 네트워크를 통한 트래픽에 대한 로그를 남긴다.
트래픽을 발생시켜서 S3 버킷에 로그가 쌓이는지 확인해보자. 다음과 같이 오늘 날짜 경로에 로그가 저장된다.
이 로그를 Athena를 통해 쿼리로 데이터를 추출해보자. Athena를 통해 쿼리를 날리려면 VPC 플로우 로그에 대한 테이블이 만들어져야 한다. 테이블 만드는 쿼리는 아래 링크를 참조.
이렇게 해서 테이블을 생성한 다음 한 가지 더 해줄게 있다. 테이블을 수정해야 하는데 데이터를 읽을 수 있도록 파티션을 생성해야 한다. 왜냐하면 테이블 만들 때 쿼리를 잘 보면 PARTITIONED BY ('date' date)라고 입력한 부분이 있다. 날짜로 파티션을 나눴기 때문에 하단 쿼리처럼 원하는 날짜에 대한 파티션을 추가해야 그 날짜에 대한 쿼리에 대한 결과를 출력할 수 있다.
ALTER TABLE vpc_flow_logs
ADD PARTITION (`date`='YYYY-MM-dd')
LOCATION 's3://DOC-EXAMPLE-BUCKET/prefix/AWSLogs/{account_id}/vpcflowlogs/{region_code}/YYYY/MM/dd';
그래서 위 코드에서 파티션을 생성할 날짜를 입력해야 한다. 날짜는 당연히 원하는 쿼리에 해당하는 날짜를 입력해야 한다. 아래는 그 예시다.
ALTER TABLE vpc_flow_logs
ADD PARTITION (`date`='2023-01-28')
LOCATION 's3://cwchoiit-vpc-flow-logs/AWSLogs/135149110460/vpcflowlogs/ap-northeast-2/2023/01/28';
이렇게 파티션을 만들었으면 이제 예시 쿼리를 날려보면 된다. 아래는 예시 쿼리이고 지정한 날짜에 최대 100개의 흐름을 출력한다.
SELECT *
FROM vpc_flow_logs
WHERE date = DATE('2020-05-04')
LIMIT 100;
그럼 다음과 같이 결과가 쭉 나온다.
또 다른 예시는 해당 날짜에 거절된 트래픽이다. 결과가 잘 출력되는 모습이다.
VPC Flow Log 정리
VPC Flow log와 Athena를 통해 원하는 쿼리문을 작성해서 로그를 출력할 수 있었다.
AWS에서 제공해주는 CodeBuild, CodeDeploy를 이용해서 CI/CD 자동화를 구현해보자.
CodePipeline
우선 AWS에서 제공하는 CodePipeline이라는 게 있다. CD를 위한 서비스인데 이 서비스 내 CodeBuild, CodeDeploy 서비스를 사용할 수 있다. 그래서 이 서비스를 이용할건데 이 서비스를 이용해서 CD 파이프라인을 만드려면 buildspec.yaml 파일이란게 필요하다. 그리고 그 yaml파일은 어떤 형식을 가지고 있는데 이 형식에 대한 설명이나 예시는 다음 링크에서 확인할 수 있다.
위 buildspec.yaml 파일에서 이미지 태그가 제대로 동작하지 않는 경우가 있다. 그래서 CodeBuild로 Image를 빌드하면 Image는 만들어 지는데 그 이미지를 가져다가 클러스터의 서비스를 만들어서 실행하는게 안되는 경우가 있다. 그 때는 이미지 태그를 다르게 직접 명시 해줘야한다.
- Image version: Always use the latest image for this runtime version
- Service role: New service role
그 다음 하단 Additional configuration을 눌러 다음 화면과 같이 Privileged를 체크한다. 이는 도커 이미지를 빌드하기 위함이다.
그 외 나머지는 기본값으로 설정 후 생성한다. 그럼 다음과 같이 리스트에 잘 노출된다.
CodeBuild의 Role 수정하기 (ECR을 컨트롤 할 수 있게)
이 다음 이 CodeBuild를 만들면서 위에 Environment 섹션에서 서비스 역할(Service role)을 새로이 만드는 작업(New service role)을 했다. 이 역할을 IAM에 가서 확인해보자.
IAM > Access management > Roles 진입 후 'codebuild'라고만 쳐도 나올 것이다.
저렇게 잘 나오면 저 역할을 선택한다. 이 역할에게 ECR을 컨트롤 할 수 있는 권한을 추가해줘야 한다.
그런 다음 나오는 화면에서 'ECR'이라고 검색 후 다음 정책을 선택하여 추가한다.
추가를 다 하면 이제 CodeBuild로부터 ECR을 컨트롤 할 수 있게 된다. 그럼 이제 CodeBuild에서 Build를 해보자.
CodeBuild로 빌드하기
CodeBuild > Build projects 화면에서 방금 만든 프로젝트를 빌드해보자. 우측 상단 'Start build > Start now'
'Start now'를 클릭하면 다음 화면처럼 진행 중인 모습을 확인할 수 있다.
다 끝나면 다음과 같이 상태가 'Succeeded'로 변경된다.
그럼 정말 잘 됐는지 ECR로 가서 이미지 빌드 상태를 보자. ECR > CodeBuild로 적용한 레포지토리(frontend)
확인하면 다음과 같이 CodeBuild로부터 빌드된 이미지가 잘 업로드됐다.
CodeDeploy 생성하기
CodeBuild로부터 이미지를 잘 빌드했으니 이제 CodeDeploy를 이용해서 클러스터에 배포하는것까지 해보자. 그 전에 이 CodeDeploy가 클러스터에 배포를 할 때 서비스 자원을 건드리기 때문에 관련 IAM 역할을 생성해야 한다.
CodeDeploy용 역할 생성하기
IAM > Roles 화면에서 우측 상단 'Create role'
Select trusted entity 화면이다.
- Trusted entity type: AWS service
- Use case: CodeDeploy
Use case를 CodeDeploy로 선택하면 다음 3가지 경우가 나오는데 'ECS'로 선택 후 'Next'
기본값으로 세팅된대로 쭉쭉 넘어가고 Role Name만 적어주자.
역할 생성하기 버튼을 눌러 생성하면 역할 리스트에서 위에서 설정한 Role name으로 검색하면 잘 나와야한다.
CodeDeploy용 대상 그룹 생성하기
이 대상 그룹은 왜 생성하냐면, 배포를 하면 배포 전과 배포 후 새로운 서비스로 호스팅을 해줘야 하니까 그 새로운 서비스에 대한 대상 그룹을 만드는 것이다.
그 외 값은 기본값으로 만들고 'Next' 이 대상 등록 화면에서는 아무것도 등록하지 않고 만든다.
대상 그룹을 만들었으면 로드밸런서에서 이 대상 그룹을 사용할 수 있도록 추가해주자. ECS용 로드밸런서를 선택하고 리스너 추가를 한다.
Listener details 화면에서는 다음 부분을 수정한다.
- Protocal:Port: HTTPS:8443
- Routing actions: Forward to target groups - 방금 만든 타겟 그룹
Secure listener settings에서는 Certificate source를 From ACM으로, Certificate은 만들어진 도메인으로 선택한다.
이렇게 설정 후 새로운 리스너 규칙을 만들면 다음과 같이 리스트에 잘 노출된다.
Deployment Type이 Blue/green deployment인 클러스터의 서비스 만들기
이제 ECS로 가서 클러스터에서 서비스를 새로 만들어야 한다. 이 서비스는 CodeDeploy와 연동하는 서비스가 될 것. 다음과 같이 새 서비스를 만들기 원하는 클러스터에서 서비스 탭의 생성 버튼을 누른다.
Environment는 동일하게 EC2로 Launch type을 설정한다.
Deployment configuration은 다음과 같이 수정이 필요하다.
- Application type: Service
- Family: 프론트 엔드 용 Task Definition
- Service name: frontend
하단에 Deployment options에서 다음과 같이 수정한다.
- Deployment type: Blue/green deployment
- Service role for CodeDeploy: 방금 위에서 만든 CodeDeploy용 서비스 역할
Load balancing에서 다음과 같이 수정한다.
- Load balancer type: Application Load banalcer
- Load balancer: ECS용 로드밸런서
- Listener: Production listener> 443:HTTPS / Test listener > 8443:HTTPS
대상 그룹은 1번을 기존 frontend-80으로, 2번을 방금 막 만든 bluegreen 80으로 설정한다.
그리고 서비스를 생성한다. 서비스를 생성하면 자동으로 CodeDeploy의 Application이 생성된다. (물론 서비스를 생성하고 정상적으로 Task가 Running 상태로 띄워져야 한다. 그렇게 되면 아래 사진처럼 CodeDeploy의 Application이 자동 생성된다.)
appspec.yaml 파일 수정하기
CodeDeploy에서 사용할 appspec.yaml 파일을 손보자. 이 파일 형식 역시 다음 링크를 참조한다.
지금 이 파일에서 수정할 부분은 "어떤 Task Definition을 사용해서 서비스를 띄울건데?" 이다. 그러니까 지금까지 한 내용은 프론트 소스코드에 대해서 CodeBuild와 CodeDeploy를 진행했으니까 역시 Task Definition도 프론트 소스에 대해서 배포해야 한다.
ECS > Task definitions > 원하는 Task definition 화면에서 ARN 정보를 복사한다.
그리고 이 Task definition의 컨테이너가 있다. 그 컨테이너 이름을 가져오면 된다. 다음 화면 참고.
저 이름으로 아래 코드에 적용.
ContainerName: 'frontend'
이렇게 appspec.yaml 파일이 다 수정이 끝났으면 이 파일을 S3에 올려야 한다.
S3에 appspec.yaml 파일 업로드하기
이미 있는 S3나 새로 만들어서 S3에 appspec.yaml 파일을 넣어보자.
CodeDeploy Deployment 만들기
S3에 appspec.yaml 파일을 넣었으면 CodeDeploy에 가서 Deployment를 만들자. Application은 위 클러스터의 서비스를 Blue/green deployment type으로 만들 경우 알아서 만들어진다.
CodeDeploy > Applications > 자동으로 만들어진 Application 선택
안에 들어가면 다음처럼 Deployment 탭이 있다. 해당 탭에서 "Create deployment" 클릭
Deployment settings를 다음과 같이 설정한다.
-Deployment group:방금 클러스터의 Blue/green 배포 타입의 서비스를 만들면서 자동 생성된 배포 그룹 선택
-Revision type:S3
-Revision location:S3의 appspec.yaml 파일 경로
-Revision file type:.yaml
그 외 기본값으로 배포 생성을 하면 다음과 같은 화면이 보인다. 배포가 진행중이다.
그리고 우측을 보면 Original 100% Replacement 0%로 나와있는데 이는 새 버전을 배포하기 전 모든 트래픽을 담당하는 쪽은 아직 구 버전의 배포판이라는 것을 말한다. 즉, 새 버전의 배포가 완료되면 모든 외부와의 트래픽 담당은 새 버전의 배포판이 담당하게 된다. (당연히 그래야 한다)
모든 배포가 끝났고 이제 Replacement가 모든 트래픽을 담당한다. 여기서 Step 4, Step 5가 아직 남았는데 이는 뭐냐면 구 버전의 배포판을 종료하는데 1시간 정도 대기 시간을 두는것이다. 1시간의 대기시간이 끝나면 구 버전의 배포판은 완전히 Terminated된다. 만약 이걸 기다리기 싫다면 우측 상단 "Terminate original task set"을 누르면 곧바로 구 버전의 배포 인스턴스가 종료된다.
중간 정리
이렇게 하면 소스 업데이트 후 새로운 배포를 CodeBuild, CodeDeploy를 통해 반 자동화 할 수 있다. 당연히 아직 완전하지 않다. 이 모든 과정을 자동화하고 싶다. 그것을 CodePipeline에서 할 수 있다.
CodePipeline을 사용해서 Build - Deploy 자동화
이제 CodeBuild, CodeDeploy를 각각 따로 실행해 보았으니 이 둘을 한번에 진행해보자. 이를 위해 CodePipeline 서비스를 사용하면 된다. 근데 그 전에 우리의 buildspec.yaml, appspec.yaml 파일이 있는 프로젝트의 taskdef.json 이라는 파일을 수정해야한다.
위 사진과 같이 taskdef.json 파일이 있는데 이 파일은 어떤 값이 들어가면 되냐? ECS - Task Definition의 Blue/green으로 배포하는 Task Definition의 JSON 값을 넣어주면 된다.
JSON 탭의 JSON을 복사해서 taskdef.json 파일에 넣어주자.
그리고 한가지 더 확인할 것은 buildspec.yaml 파일에서 다음 코드와 같이 artifacts 부분이다.
최종 생성을 하면 아래처럼 CodePipeline이 바로 진행이 된다. 즉, 위에서 순서대로 Source, Build, Deploy를 구성한 것을 토대로 하나씩 진행이 된다. Github로부터 소스를 받아오고, CodeBuild로부터 Build가 진행되고, CodeDeploy로부터 Deploy가 진행된다.
근데 진행중에 마지막 Deploy 단계에서 이런 에러가 난다면 .. "Tags can not be empty"
taskdef.json 파일에서 "tags"를 지워주면 된다. 본인의 경우 이런 빈 값으로 들어가 있었다.
저번에는 Docker를 사용하지 않고 그냥 EC2에 서비스를 올려서 실행하는것까지 물론 DB도 있고 CloudFront도 있고 Route 53, VPC, Subnet, S3도 있고 이것저것 다 있었지만 Docker를 사용하지는 않았다. 이번에는 Docker와 연동해보자.
그래서 최종적으로 만들고자 하는 구조는 다음과 같다.
이전과 다른 부분은 ECS와 Cluster, ECR 같은 개념이 새로이 도입됐다. 이것들이 뭔지 알아보자.
ECS란
Elastic Container Service의 약자로 컨테이너를 오케스트레이션 해주는 서비스이다. ECS를 사용하면 클러스터 구성이 가능하고 클러스터에서 컨테이너를 실행, 중지, 배포, 모니터링 등을 관리할 수 있다.
ECS Cluster에는 ECS Container instance(EC2라고 생각하면 된다)가 있는데 EC2 내부에 ECS Container Agent가 설치된 상태로 생성이 되고 이 EC2는 내부적으로 Auto Scaling Group으로 구성되어 있다. 저 Container instance안에 컨테이너들은 Task로 실행이 되고 Task Definition이라는 설정값에 의해 배포가 된다. 이 Task들은 서비스에 의해 생성이 되고 서비스는 ELB와 연동해서 외부에서 접근이 가능하도록 할 수 있다. 직접 만들어보면 이해가 빨리 된다. 이후에 직접 만들어보자.
ECS Cluster는 두 가지 형태로 구성이 가능하다.
Fargate Type: 인스턴스가 없는 서버리스 형태의 서비스. 실제 컨테이너에서 사용한 CPU와 메모리 사용량에 의해서만 과금이 된다.
EC2 Type: 위 Container instance로 구성한 그림이 EC2 Type.
이 두개 중 하나를 선택하는 기준은 가격을 중심으로 두고 보면 되는데, Fargate Type이 기본 가격이 더 비싸지만 네트워크 트래픽이 일정하지 않고 들쭉날쭉한 서비스를 배포하고자 하는거라면 Fargate Type이 더 유리할 수 있다.
ECR이란
Elastic Container Registry의 약자로 쉽게 생각하면 Docker Hub(Registry)라고 생각하면 된다. Docker Image를 저장하고 관리하는 서비스이다. AWS IAM으로 리소스 기반 권한을 컨트롤할 수 있다는 장점이 있다. 외부에서 접근할 수 없는 프라이빗 레포지토리를 만들 수 있고 따라서 IAM 권한이 있는 지정된 사용자 또는 EC2 인스턴스가 컨테이너 레포지토리에 접근할 수 있도록 설정할 수 있다. AWS CLI를 사용해서 Docker Image의 Push, Pull, Tag와 같은 작업이 가능하다.
자자, 여기까지가 새로운 개념에 대한 설명이고 바로 저 아키텍쳐를 직접 구성해보자.
ECR 레포지토리 만들기
AWS Console에서 'ECR'을 검색한다. 화면과 같이 Elastic Container Registry를 클릭한다.
'Create repository'를 클릭해서 레포지토리를 하나 만들자.
Visibility는 프라이빗, Repository name은 적절하게 작성하고 그 외 모두 기본값으로 적용해서 만든다.
같은 방식으로 백엔드용 레포지토리를 하나 더 만들자. 그렇게 두 개를 만들면 다음과 같이 리스트에 두 개가 잘 보인다.
여기서 우선 Jar 파일을 만들어내자. (사실 이 Jar 파일 만들어내는 과정도 자동화 할 수 있다.)
mvn clean package
이 상태에서 시작하자. 아까랑 동일하게 ECR에 가서 원하는 레포지토리에 푸시하는 방법을 보여주는대로 실행하면 된다.
마찬가지로 잘 푸시된 모습이다.
이제 백엔드와 프론트엔드 각각에 대한 이미지가 만들어졌고 AWS ECR에 올라가 있기때문에 이 이미지들을 이용해서 ECS를 이용할 수 있게 됐다. 계속 진행해보자.
필요한 보안 그룹 생성 및 수정
우선, ECS 인스턴스도 보안 그룹이 필요하고 이 인스턴스들을 묶는 클러스터와 통신하는 ALB도 보안 그룹이 필요하다. 그래서 보안그룹을 만들자. 우선 ALB에 대한 보안 그룹 먼저.
ALB 보안그룹의 인바운드 규칙은 80과 443에 대해서 모든 IP로부터 열어둔다.
이 다음은 ECS Instance 보안 그룹.
이름과 설명은 적절하게 입력을 하고 VPC는 이제 말하기도 귀찮다. 항상 사용하는 VPC. 그리고 인바운드 규칙을 좀 유심히 봐야한다. 얘는 ALB가 들어올 수 있게 해줘야 하는데 그 때 포트는 전체(All TCP)로 열어둔다. 그 이유는 ECS를 동적 포트 할당으로 그 때 그 때 달라지기 때문이다.
그 다음은 RDS 보안 그룹에 대한 수정이다.
이건 뭐냐면 데이터베이스를 사용하는데 이 RDS는 기존에 사용했던 것 그대로 사용을 할거다. 근데 지금 만든 ECS Instance가 접근하는 것에 대한 허용은 안 해놓은 상태니까 그 허용이 필요하다.
기존에 사용하던 RDS 보안 그룹에서 인바운드 규칙 수정을 누른다.
MySQL로의 접근에 대한 허용을 방금 만든 ECS 보안 그룹 추가해주기.
여기까지하면 일단 필요한 보안 그룹 작업은 끝났다.
ALB 생성
이제 로드밸런서를 만들어보자.
EC2 > Load Balancing > Load Balancers
타입은 ALB로 정한다.
Basic configuration에서는 Name을 설정한다. 그 외는 기본값.
Network mapping은 항상 하던 그대로 설정한다.
Security groups는 앞전에 만든 ECS-ALB용으로 선택한다.
Listeners and routing은 HTTP:80에 적용할건 맞는데 대상 그룹을 생성하지 않았기 때문에, 대상 그룹 생성 링크를 눌러 생성하자.
대상 그룹 생성 화면이다. 인스턴스 타입으로 선택하고, 대상 그룹 이름을 지정한다음 HTTP:80으로 설정.
그 외 모든 값은 기본값으로 설정한다. VPC는 이미 내가 만든 VPC로 선택되어 있다.
대상 등록 화면에서는 아무런 대상도 선택하지 않고 대상 그룹만 만든다.
그 다음 백엔드용 대상 그룹도 만들어야 한다.
이렇게만 변경사항을 적용하고 이 또한 대상 등록을 하지 않고 만든다.
이제 다시 로드밸런서를 만드는 화면으로 다시 돌아와서 다음과 같이 적용한다. 프론트엔드는 HTTP:80, 백엔드는 HTTPS:443으로 적용한다.
Security policy는 다음과 같이 적용한다.
Security category는 All security policies, Policy name은 기본값, ACM의 만든 도메인을 적용.
그리고 로드밸런서를 최종적으로 생성한다. 최종적으로 생성하면 다음과 같이 로드밸런서 하나가 추가된다.
만든 로드밸런서에 들어가서 HTTP:80 규칙을 수정한다. 수정 내용은 80으로 들어오면 443으로 리다이렉션한다.
이번엔 HTTPS:443을 수정한다. 443으로 들어오면 80 대상 그룹(프론트엔드 대상 그룹)으로 전달한다.
그 다음, 443은 규칙 하나를 추가한다. 백엔드 도메인으로 들어왔을 때 8080 대상 그룹(백엔드 대상 그룹)으로 보내는 것을 추가.
이렇게 규칙을 만들면 된다. 이제 로드밸런서는 다 끝났고, Route 53에서 이 로드밸런서로 레코드를 추가와 변경하자. (기존에 만들었던 소규모 아키텍쳐에서 이미 사용했기 때문에 이 ECS용으로 변경해줘야 한다).
우선, "api.cwchoiit.net"에 대한 레코드를 ECS ALB로 변경한다.
그 다음, 레코드 하나를 다음과 같이 추가한다. "ecs.cwchoiit.net"으로 진입하면 방금 만든 로드밸런서인 ECS-ALB로 적용.
이 두가지 작업을 해주면 다음과 같이 ECS-ALB로 잘 적용됐는지 확인하자.
ECS 클러스터 생성
이제 클러스터를 만들어보자. 우선 처음보는 개념이기 때문에 천천히 하나씩 해보자.
AWS Console에서 Elastic Container Service 또는 ECS를 검색한다. 검색해서 나오는 서비스를 클릭
좌측 'Clusters'를 클릭해서 Create cluster를 선택한다.
1. 클러스터 이름과 설명을 작성한다.
2. 인프라를 구성한다.
우선 Fargate이 아닌 EC2 Instance 유형의 클러스터를 만든다. 그리고 내부적으로 Auto Scaling Group으로 동작한다고 했던것을 기억하는가? 그래서 Auto Scaling Group을 선택하는데 기존에 있는것으로 선택하거나 따로 준비한게 없다면 새로 만드는 옵션을 선택한다.
On-demand는 과금 방식을 말하는 것이고 이미지는 Amazon Linux 2023, 인스턴스 타입은 c5.large 선택, Desired capacity는 최소 최대 모두 2개, 키페어는 항상 사용하던 키페어로 선택하고, EBS 볼륨 사이즈는 기본값으로 설정한다.
3. Network를 구성한다.
VPC는 항상 사용하던 VPC를 선택하고, 서브넷은 Private App A, C를 선택 Security group은 위에서 만든 ECS용 보안 그룹을 선택한다.
4. Tag를 구성한다.
이 상태로 클러스터를 생성한다. 생성하고 나면 다음과 같이 클러스터가 리스트에 노출된다.
이제 이 클러스터가 사용할 서비스와 테스크를 만들어야 하는데 만들려면 테스크 정의가 필요하다. 그래서 테스크 정의를 만들어보자.
ECS > Task definitions 에서 'Create new task definition' 클릭
Task Definition (Frontend 용/Backend 용)
1. 우선 Task definition family는 다음과 같이 읽기 편하게 작성한다.
2. Infrastructure는 Launch type을 Amazon EC2 Instances, OS는 Linux, Network mode는 bridge, CPU는 할당하지 않고, Memory는 1GB, Task role은 없음에 Task execution role은 새로운 롤을 만드는 것으로 선택한다.
3. Container 정보를 입력한다.
컨테이너 이름을 입력하고 Image URI는 ECR에 올려놓은 이미지의 URI를 가져온다. Host port 0이 의미하는건 동적 할당이다. Container port는 80으로 할당했다. 왜냐하면 프론트니까. 그 외 나머지는 기본값으로 설정한다. 그리고 하단에 여러 옵션들이 있는데 아무것도 하지 않는다 왜냐하면 지금은 필요가 없으니까.
이 상태로 Task definition을 생성한다. 즉, Task definition은 결국 Docker Container를 생성하기 위한 정보들을 입력한 메타데이터라고 보면 된다. 그리고 그 정의들로 Task가 만들어지는 것.
같은 맥락으로 한 개의 Task definition이 더 필요하겠지? 프론트랑 백엔드니까 백엔드를 만들어줘야 한다.
백엔드는 메모리를 2GB로 설정했다.
백엔드용 이미지 URI를 붙여넣고, 컨테이너 포트는 8080이다. 나머지는 위 프론트용과 동일하다.
두개가 잘 만들어졌는지 리스트에서 확인.
클러스터의 서비스 만들기
Cluster도 만들고 Task definition도 만들었다. 이제 클러스터의 서비스까지 만들어보자.
내가 만든 클러스터로 들어가면 하단에 Service 탭이 있다. 여기서 Create 클릭
프론트엔드의 서비스부터 생성해보자.
프론트엔드 서비스 만들기
생성 화면의 첫 부분인 Environment는 다음과 같이 설정한다.
- Compute options: Launch type
- Launch type: EC2
그 다음 Deployment configuration 설정 부분은 다음과 같다.
- Application type: Service
- Family: 이전에 만든 Frontend용 Task definition
- Service name: frontend
- Service type: Replica
- Desire tasks: 2
그 다음 Load balancing 설정이다.
- Load balancer type: ALB
- Load balancer: cwchoiit-ecs-alb
- Container: frontend 80:80
- Listener: 443:HTTPS
- Target group: cwchoiit-ecs-frontend-80
이렇게 하고 그 외 값은 기본값 세팅으로 설정해서 만든다. 그렇게 만들면 좀 기다려서 다음과 같은 서비스를 볼 수 있다.
그렇게 서비스가 잘 올라오면 이 서비스에 해당하는 프론트엔드 URI(ecs.cwchoiit.net)으로 들어가보자. 우리의 프론트엔드 소스가 잘 보이는 것을 확인할 수 있다.
이제 백엔드 서비스를 띄워야 한다. 그래야 완성된 전체 서비스를 이용할 수 있으니까. 이제 백엔드 서비스를 클러스터에서 만들어보자.
백엔드 서비스 만들기
백엔드 서비스도 위 프론트엔드 서비스와 거의 동일하다. 그러니까 다른 부분만 확인해보자.
Task definition이 당연히 달라진다. 백엔드용 Task definition을 선택하자.
컨테이너는 백엔드용으로 만든 컨테이너를 사용해야 한다.
대상 그룹은 백엔드용 대상 그룹을 선택하고 그 외 나머지는 기본값으로 생성하기를 누르자.
근데 이렇게 위에서 쭉 따라오면 다음과 같은 에러를 마주할것이다.
무슨 에러냐면 지금 새로 만드려는 서비스의 Task Definition으로 만든 컨테이너는 포트가 동적할당으로 되어 있는데 로드밸런서 설정에서 컨테이너에 대한 대상 그룹을 우리가 직접 지정했다 백엔드용 대상 그룹으로. 지정하는것 까지는 맞는데 지정한 대상 그룹의 Health check 포트가 동적할당이 아닌 고정값으로 설정했다. 위에서 보면 Override port해서 8080으로 지정을 했단 말이지. 그래서 8080이 아니면 Health check가 불가능한데 지금 띄우는 컨테이너가 동적 포트 할당이라 서비스를 생성할 수 없다는 에러다.
그래서 만든 백엔드용 대상 그룹의 Health check 부분을 다음과 같이 수정한다.
이렇게 대상 그룹을 수정해주고 다시 서비스를 만들자. 다시 만들면 다음과 같이 정상적으로 서비스가 만들어졌다.
그럼, DB가 붙었기 때문에 프론트에서 데이터를 생성하면 정상 생성이 되어야 한다. 테스트해보자. 다음과 같이 Employee를 추가하고 'Save'를 누르면 리스트에 방금 생성한 Employee가 잘 노출된다.
중간 정리
이제 클러스터를 이용해서 백엔드, 프론트엔드 서비스를 같이 올릴 수 있는 상태가 됐고 그 각각의 서비스는 ECR(Elastic Container Registry)에 등록한 도커 이미지를 통해서 서비스를 빌드하고 띄울 수 있게 됐다.
CloudFront를 클러스터에 연결
이제 CloudFront를 클러스터에 연결해서 앞 단에서 CloudFront로 요청과 응답을 처리해보자.
CloudFront로 가서 새로 하나 만들어보자.
Origin 부분에서 변경할 부분은 다음밖에 없다.
- Origin domain: ECS용으로 만든 Load Balancer
Default cache behavior는 이 부분만 수정하면 된다.
- Viewer protocol policy: Redirect HTTP to HTTPS
- Cache policy: CachingOptimized
Settings 부분은 다음을 수정하자.
- CNAME: ecs.cwchoiit.net
- Custom SSL certificate: cwchoiit.net
이렇게 적용하고 CloudFront를 배포하자. 배포가 진행되는 동안 이제 로드밸런서가 이 CloudFront와 통신해야 하므로 로드밸런서의 리스너 규칙을 수정해보자. ECS용 로드밸런서를 찾아 들어가보면 다음과 같이 HTTP:80에 대한 규칙이 있다.
저 기본 규칙은 443으로 리다이렉션하는데 이를 대상 그룹 80으로 변경하자. 어차피 CloudFront가 HTTP로 들어오면 HTTPS로 변환해주고 변환해서 들어오는 이 로드밸런서 안은 HTTP만 관심 대상으로 두면 된다.
HTTP:80에 대한 규칙을 하나 더 추가할건데 이제 호스트 헤더가 ecs.cwchoiit.net인 경우 80 대상 그룹으로 포워딩 하는 것도 추가하자.
이렇게 로드밸런서의 리스너 규칙도 다 변경 및 수정이 끝났고 CloudFront 배포도 정상 처리됐음을 확인했다.
ALB로 연결되어 있던 Route 53 레코드를 CloudFront로 연결 바꾸기
Route 53으로 들어가서 우리 도메인의 레코드로 ecs.cwchoiit.net을 만들었다. 근데 이 레코드가 연결된 건 방금 만든 CloudFront가 아니라 ALB로 연결된 상태이기 때문에 CloudFront로 변경해주자.
Route 53 > Hosted zones > 만든 도메인 내 레코드 편집
변경 후 한번 다시 ecs.cwchoiit.net으로 들어가서 우리 서비스가 정상 응답하는지 확인해보자. 아래처럼 잘 응답하고 있다.
응답을 한것만 확인하지 말고 이게 CloudFront로 부터 Hit된 것인지 확인해보자. CloudFront가 잘 동작하고 있다.
Auto Scaling은 자동으로 EC2 인스턴스의 개수가 줄거나 늘거나 하는 기능을 말한다. Auto Scaling은 두 가지 경우로 나눌 수 있다.
Scaling Up/Down: 인스턴스 타입의 크기를 크거나 작게 만드는 경우
Scaling Out/In: 인스턴스의 개수를 늘리거나 줄이는 경우
Auto Scaling Group
이 Auto Scaling 기능을 이용하기 위해 Auto Scaling Group이 만들어져야 하는데 여기서 최소, 희망, 최대 인스턴스 개수를 설정할 수 있다.
그리고 사용자가 설정한 Auto Scaling 조정 정책, 즉 EC2가 감내할 수 있는 부하의 기준을 정하면 인스턴스 개수가 최대 최소의 인스턴스 내에서 자동으로 조절이 된다. 이런 설정을 하는 것이 Auto Scaling Group이라고 보면 된다.
Launch Template, Golden AMI
Launch Template은 인스턴스를 생성할 때 결정하는 네트워크 정보, 타입, 키페어, AMI(Amazon Machine Image)와 같은 설정값들에 대한 템플릿을 의미한다.
AMI는 인스턴스 생성할 때 지금까지 사용했던 Amazon Linux Image와 같은 그 이미지를 말한다.
Auto Scaling Group을 사용할 때 이 Launch Template을 사용해서 추가적으로 생성될 인스턴스를 만들 수 있게 구성할 수 있다. 이 때, Launch Template을 통해서 사용되는 이미지를 Golden AMI라고 한다. 이 Golden AMI는 OS를 포함해서 필요한 필수 패키지들이 설치되어 있는 표준 이미지를 말한다. Golden 이라는 단어의 의미는 딱히 중요하지 않다. 그냥 완성적이고 필수 패키지들이 잘 적용이 된 상태라서 Golden 이라는 이름이 붙어졌다. 이후에 직접 만들어보면서 더 이해를 하겠지만 직접 나만의 이미지를 만들어서 기존에 사용했던 동일한 인스턴스 상태와 환경을 구성할 수 있게 해준다. 그리고 이런 방식이 앞서 얘기한 Immutable Infra를 만들게 해준다.
Autoscaling Policy
위에서 Auto Scaling 조정 정책이라는 말을 했는데 그것을 알아보자. 이 조정 정책이란 사용자가 "이런 조건에 도달하면 Auto Scaling 기능을 활성화해줘!"라고 정의해놓은 정책을 말한다. 그리고 크게 3가지가 있다.
단순 조정 정책 (Simple Scaling)
Cloudwatch 경보 위반을 기준으로 사용
Cloudwatch 경보 발생 시 특정 인스턴스 개수만큼 스케일 인/아웃 설정이 가능
또는 Cloudwatch 경보 발생 시 Auto Scaling Group의 퍼센티지(%)만큼 스케일 인/아웃
단점은 확장에 대한 세분화된 제어를 제공하지 않음
대상 추적 조정 정책 (Target Tracking Scaling)
Auto Scaling Group이 항상 유지해야 하는 조정 지표 및 지표값을 지정한다. 예를 들어, CPU 80%로 설정하면 항상 그만큼을 유지하도록 자동 스케일 인/아웃 실행
4가지 지표 선택이 가능
ASG Average CPUUtilization: Auto Scaling Group의 평균 CPU 사용률
ASG Average NetworkIn: Auto Scaling Group이 모든 네트워크 인터페이스에서 수신한 평균 바이트 수
ASG Average NetworkOut: Auto Scaling Group이 모든 네트워크 인터페이스에서 보낸 평균 바이트 수
ALB Request CountPerTarget: Auto Scaling Group이 ALB 대상 그룹과 연결된 경우 대상 그룹의 대상 당 완료된 요청 수
단계 조정 정책 (Step Scaling)
위 단순 조정 정책의 단점인 세분화된 제어를 보완하기 위한 정책
Cloudwatch 경보 위반의 정도에 따라 인스턴스 개수를 단계적으로 조정하는 작업을 설정 가능
단계적 조정을 통해 정책은 경보 위반 이벤트 도중에도 추가 경보에 계속 응답할 수 있게 된다.
단계 조정 정책의 예시
Auto Scaling Group의 Lifecycle
Auto Scaling Group으로 인스턴스를 만들고 지울 때 인스턴스의 생명주기가 있다. 그림을 보자.
우선 Auto Scaling Group으로부터 인스턴스가 새로 생성되는 Scale Out일 때 인스턴스 상태는 'Pending'이다. Pending에서 인스턴스가 완전히 띄워졌을 때 'InService'상태가 된다. InService상태에서 Health Check가 실패하거나 Scale In이 될 때 'Terminating'상태가 된다. 이 Terminating에서 완전히 인스턴스가 제거된 상태는 'Terminated'이다.
그 외 InService상태에서 인스턴스를 떼어내면 'Detaching'상태가 되고 InService상태에서 인스턴스를 Standby로 설정하면 'EnteringStandby'상태가 된다.
Auto Scaling 직접 사용해보기
이제 Auto Scaling 기능을 실제로 사용해보자. 우선 어떤 형태의 모습이 그려질지는 다음과 같다.
기존에 가지고 있는 EC2 인스턴스가 있고 그 인스턴스를 가지고 Auto Scaling Group을 만들어서 Launch Template을 만든다. 그리고 정책을 설정해서 정책 조건에 도달하면 자동으로 인스턴스가 늘어나는 모습을 확인할 예정. 앞단에는 LB를 붙여서 인스턴스가 3개가 만들어지면 부하 분산 처리가 잘 되는지도 확인해보자.
필요한 보안 그룹 생성
위 그림에서 LB가 있다. LB에 관련된 보안 그룹을 먼저 생성하자.
EC2 > Security Groups에서 보안 그룹 생성.
이름과 VPC를 설정하고, 인바운드 규칙에는 80에 모두(0.0.0.0/0)가 들어올 수 있게 설정한다. 이 상태로 생성.
이번엔 새로 만들 EC2에 대한 보안 그룹을 생성한다. 위 그림에서 총 3개의 EC2가 있는데 이들의 표본이 될 EC2 하나는 만들어야한다. 그래서 보안 그룹 하나를 더 생성.
하단에 보면 3개의 인바운드 규칙이 있다.
22는 우리가 항상 사용하던 OpenVPN으로 SSH 접속할 수 있게 설정
80도 역시 OpenVPN을 통해 접속할 수 있게 설정
80중 또다른 하나는 방금 막 만든 LB 보안 그룹으로 설정
표본 EC2 생성
위에서 말한 표본 EC2를 생성한다. Name 설정은 자유롭게하고 AMI는 Amazon Linux로 선택, 타입은 디폴트인 t2.micro, 키페어는 계속 사용해왔던 키페어로 선택, 네트워크 설정에서는 계속 사용했던 VPC, Private Subnet App A로 하고 보안 그룹은 방금 만든 보안 그룹을 선택하고 생성한다. (이제 EC2 만드는 건 이렇게 말로만 하겠다.)
이제 OpenVPN에 접속해서 이 방금 만든 EC2를 SSH로 접속하자. 접속했으면 여기에 필요한 패키지들을 설치해야 한다. 그것들을 정리한 README.md 파일 경로는 다음과 같다.
여기 부분이 중요한데, AMI를 선택할 때 My AMIs 탭을 선택하고 Owned by me를 클릭한다. 그리고 방금 만든 AMI를 선택
그 다음, 인스턴스 유형과 키페어는 다음과 같이 설정한다. 키페어는 기존에 사용했던 키페어를 계속 사용한다.
그 다음, 네트워크 설정이다. Subnet은 프라이빗 서브넷 App A에 그러니까 표본 EC2가 있는 같은 서브넷으로 선택하고 보안 그룹은 저 EC2와 같은 보안 그룹을 선택한다.
나머지는 기본값으로 설정한 후 만들어보자. 만들고 나면 리스트에 만든 Launch Template이 보여진다.
ALB 생성
이제 앞단에 쓰일 로드밸런서를 만든다. EC2 > Load Balancers에서 로드밸런서 하나를 만들자.
유형은 다음처럼 ALB를 선택한다.
Basic configuration은 다음과 같다.
네트워크 설정은 다음과 같다. AZ-a, AZ-c 두개를 사용한다.
보안 그룹은 앞에서 만든 ALB용 보안 그룹을 선택한다.
그리고 이 ALB를 만들기 전 대상 그룹을 먼저 만들어야 했는데 만들지 않았다. 그래서 Listerners and routing 섹션에 다음처럼 Create target group 링크를 클릭해서 만들자.
대상 그룹을 만들 때 유형은 인스턴스, 프로토콜과 포트는 HTTP:80용으로 한다.
그리고 VPC는 계속 사용했던 것을 선택하면 되고 Health check는 다음 경로로 설정한다.
다음으로 넘어가면 대상을 등록하면 된다. 위에 만든 Auto Scaling을 위한 EC2를 선택하고 'Includ as pending below' 클릭
그리고 만들면 이제 다시 로드밸런서 만들었던 화면으로 돌아가서 방금 만든 대상 그룹을 선택한다.
이렇게 설정하고 로드밸런서를 생성한다. 생성하면 리스트에 다음과 같이 노출된다.
이 만든 로드밸런서를 Route 53에 연결하자. Route 53 > Hosted zones로 가서 우리의 도메인을 선택하고 레코드 하나를 추가하자.
레코드를 다음과 같이 생성한다. subdomain은 autoscaling으로 설정했고 방금 만든 ALB에 붙인다.
이제 방금 만든 도메인으로 접속해보자. 그런데, HTTPS로 접속 가능하게 해보자. 우선 로드밸런서로 가서 규칙 하나를 추가해주자.
이렇게 설정하고 규칙하나를 추가해주자. 그리고 우리가 ALB에 적용한 보안 그룹 역시 443은 열려있지 않기 때문에 인바운드 규칙 하나를 추가해줘야 한다. 이 ALB에 적용한 보안 그룹으로 가서 인바운드 규칙 추가를 누르고 다음 443에 대해 설정해주자.
다 설정이 끝났으면 접속해보자. 잘 보여야 한다.
Auto Scaling Group 생성하기
이제 Auto Scaling Group을 만들어보자. EC2 > Auto Scaling > Auto Scaling Groups 로 들어가자.
생성하기를 누르고 이름을 지정해준다. 그리고 Launch template을 우리가 위에서 만든 것으로 지정한다. 버전은 그냥 Default로 하면 된다. 그리고 Next
VPC는 계속 사용하던 VPC로 설정하고 가용 영역과 서브넷은 A존 C존에 App용 서브넷을 선택한다. 그리고 Next
다음은 로드밸런서를 선택한다. 만든 로드밸런서를 선택하고 그에 맞게 설정한 대상 그룹을 지정한다.
Health Check 주기는 10초로 변경하자. 300초는 너무 길다. 그리고 Next
계속 Next로 넘어가고 태그를 만드는 화면에서 태그 하나를 생성하자. 태그의 키는 Name, Value는 적절한 값을 넣어서 태그 하나를 생성하고 Next
그러면 요약 부분이 나오고 확인 후 생성하면 된다. 생성하면 다음처럼 리스트에 잘 나온다.
들어가서 확인해보면 현재 Desired, Minimum, Maximum capacity가 전부 '1'로 되어 있다. 변경해보자. 우측 Edit 버튼 클릭
최대 용량을 3으로 변경해보자.
이제 Automatic scaling에서 정책을 생성해야 한다. Automatic scaling 탭에서 Dynamic scaling policies 섹션에 생성 버튼을 누른다.
정책 타입은 Target tracking scaling으로 하고 Metric type은 Average CPU utilization으로 선택하자. Target value는 70으로 설정하고 Instance warmup은 10초로 설정하고 만들어보자.
생성하면 다음과 같이 정책 하나가 추가됨을 확인할 수 있다.
정책은 정책이고 이렇게 Auto Scaling Group을 정상적으로 만들면 이 그룹에 의해 자동으로 인스턴스가 만들어진다. 확인해보자.
Auto Scaling Group의 Instance management 탭으로 가면 다음과 같이 인스턴스 하나가 'InService'상태인 것을 확인할 수 있다. 이는 Auto Scaling Group으로부터 자동으로 만들어진 인스턴스다.
실제로 인스턴스에 가서 리스트를 확인해보면 다음처럼 우리가 만든 표본 EC2가 있고 Auto Scaling Group이 만든 EC2가 둘 다 구동중이다.
Auto Scaling Group으로 만들어진 인스턴스들만 로드밸런서가 취급을 할 수 있도록 로드밸런서 쪽 대상 그룹에서 우리가 만든 표본 EC2는 날려주자. EC2 > Load Balancing > Target Groups에서 위에서 만든 로드밸런서에 적용할 대상 그룹에 들어가보면 다음과 같이 타겟이 두개다. 하나는 앞서 만든 표본 EC2, 하나는 Auto Scaling Group으로부터 만들어진 EC2. 표본 EC2를 대상 그룹에서 Deregister하자.
Deregister를 하면 다음처럼 상태가 Draining으로 변하고 이 대상 그룹에 타겟에서 빠진다.
Auto Scaling 확인하기
이제 우리가 만든 Auto Scaling Group의 정책에 따라 CPU 사용률이 70이 넘어가면 Scale Out이 발생하는지 확인해보자. 그러기 위해 Auto Scaling Group이 만들어준 EC2로 들어가서 스트레스를 부여하자.
우선 해당 EC2로 들어가야 한다. 해당 EC2의 프라이빗 IP를 확인해서 접속해보자. 내부로 들어오면 루트 유저로 변경하자.
이러면 Auto Scaling Group에서 만든 정책 조건에 부합한다. 다음을 보자. 이 부분이 Auto Scaling Group에서 설정한 Dynamic scaling policies중 하나다. 저기 보면 'As required to maintain Average CPU utilization at 70' 이런 문장이 있는데 이게 CPU 평균 사용률을 70으로 유지하는데 필요한 경우이다. 우리가 99%까지 CPU 사용률을 높였기 때문에 이 조건에 따라 EC2가 늘어날 것이다.
CPU Policy
Target tracking scaling
Enabled
As required to maintain Average CPU utilization at 70
Add or remove capacity units as required
10 seconds to warm up before including in metric
Enabled
Activity 탭에서 이 부분을 유심히 봐보자.
당장 만들어지지 않을 수 있다. CPU 지표가 Cloudwatch로 전달이 되고 Cloudwatch에 있는 수치를 Auto Scaling Group에서 확인하는데까지 시간이 걸릴 수 있기 때문에 실시간 반영은 되지 않을 수 있다.
조금 기다리면 다음과 같이 새로운 인스턴스가 Launching 된다는 활동 내역이 나온다.
우리가 설정한 최대 3개까지 모두 만들어진 모습이다.
이렇게 3개까지 만들어지면 이제 로드밸런서는 이 세개의 인스턴스를 부하분산 처리해주면서 라운드로빈 방식으로 한번씩 돌아가면서 요청을 전달한다. 이렇게 Scale Out 현상을 잘 확인해보았다. 그럼 반대로 CPU 사용률에 대한 스트레스 프로세스를 죽였을 때 인스턴스가 하나씩 사라져야한다. Scale In. 그것을 확인해보자.
우선 stress 프로세스를 죽이자. 프로세스 ID를 확인해서 kill.
kill -9 stress-pID
스트레스 프로세스가 띄워져 있는지 확인
ps -ef | grep stress
자 이제 Auto Scaling Group으로 가서 인스턴스가 하나씩 종료되는지 확인하자. 다음처럼 하나씩 인스턴스가 종료된다고 나온다.
이 인스턴스가 종료되는 방식은 Auto Scaling Group의 Details 탭에 가보면 Advanced configurations 섹션이 있다.
거기에 Termination policies가 Default인데, 이 Default는 가장 먼저 생성된 인스턴스를 가장 먼저 지우는 방식이다.
Edit 버튼을 눌러서 Terminate policies를 쭉 보면 다음과 같이 여러 방식이 있다.
AWS에서 도메인을 하나 파고 그 도메인 위에 서비스를 구축해보자. AWS Console에서 'Route53'을 검색해서 나오는 서비스를 클릭한다.
좌측 사이드바에 Domains > Registered domains 메인 화면에 Register domains 클릭한다.
도메인 명을 원하는 도메인으로 검색해서 선택 가능한 옵션을 살펴보고 선택한다.
본인은 .net을 사용해서 진행한다.
기간을 설정하는 화면에서 원하는 기간으로 선택하고 'Next'
그 다음은 개인 정보를 입력하는 화면이다. 적절하게 입력하고 다음으로 넘어가면 된다.
적절하게 입력하고 다음으로 넘어가면 Review 화면이 나온다. 확인 후 'Submit'을 누르면 시간이 좀 지나고 인증될 부분들이 다 인증이 되면 도메인이 만들어진다.
어느 정도 시간이 지나고 나면 Registered domains에 다음처럼 내가 만든 도메인이 등록된다.
SSL/TLS Certificates
HTTPS 프로토콜을 사용하기 위해 인증을 받아보자. AWS Console에 ACM을 입력하면 다음 'Certificate Manager'가 나온다.
Request certificate을 진행한다.
Domain names에 다음 두 개를 입력한다.
나머지는 기본값으로 세팅 후 'Request'를 클릭
요청이 완료되면 다음처럼 List certificates 리스트에 내가 방금 요청한 레코드 하나가 보인다.
선택해서 들어가보면 다음 Domains 섹션이 있는데 여기가 방금 위에서 요청한 도메인의 HTTPS 인증이다. 이제 요청을 했으니까 Route53에서 이 Records를 만들면 된다. Domains 섹션에 우측 상단 'Create records in Route53을 클릭하자.
Route53에 방금 요청한 레코드들이 생성됐는지 확인하기 위해 'Route53'으로 넘어가서 확인한다.
Hosted zones에 내가 만든 도메인을 클릭해보자.
다음처럼 레코드가 자동으로 등록됐다.
이제 레코드 생성은 확인했으니 검증이 됐는지 다시 한번 확인해보자. 시간이 좀 지나면 검증이 된다.
Certificate Manager > List certificates에서 검증이 완료됨을 확인할 수 있다.
보안 그룹 만들기
APP, RDS, ALB(Application Load Balancer)에 대한 보안 그룹을 생성한다.
인바운드 규칙: OpenVPN으로 들어오는 포트 3306 트래픽, APP으로 들어오는 포트 3306 트래픽(앱과 OpenVPN을 통해서 DB접근을 하겠다는 의미)
APP용 EC2 생성
이제 프론트와 백엔드 서버를 띄울 EC2 인스턴스를 만들 예정이다. 바로 만들어보자.
EC2는 여러번 만들어봤으니 Name은 원하는 Name으로, OS Image는 Amazon Linux를 선택, 그 외 나머지 모두 디폴트 값으로 선택하고 Key pair는 앞에서 계속 사용했던 그 키페어를 사용한다.
Network settings는 만든 VPC와 Private Subnet App A에 설정하고 보안 그룹은 방금 만든 보안 그룹 중 APP용 보안 그룹을 선택 그리고 생성버튼을 눌러서 생성하면 된다.
RDS 생성
이제 관계형 데이터베이스 서비스인 RDS를 만들어보자. AWS Console에 'RDS'를 입력해서 서비스를 찾는다.
RDS 서비스 메인화면에 들어와서 먼저 할 것은 서브넷 그룹을 생성하는 것이다.
좌측 사이드바에 Subnet groups를 클릭하여 나온 화면에서 'Create DB subnet group' 클릭
Name, Description, VPC를 설정한다.
Add subnets 섹션에서는 AZ와 서브넷을 선택하는데 AZ는 ap-northeast-2a와 ap-northeast-2c로, Subnet은 DB용 서브넷을 선택한다. (가용영역은 반드시 두 개 이상이어야 하므로 두개를 선택하고, 참고로 각 가용영역에 DB용 서브넷이 실제로 있어야 한다.)
서브넷 그룹이 잘 생성됐는지 리스트에서 확인
이제 데이터베이스를 생성하자. RDS > Databases에서 'Create database' 클릭
데이터베이스는 'MySQL'을 선택한다. 버전은 기본값으로 설정한다.
템플릿은 Free tier로 하면 된다. 그럼 자동으로 싱글 DB 인스턴스가 고정값으로 된다.
Settings는 다음 항목을 설정한다. DB Instance Identifier, Master username, Master Password
Connectivity는 다음처럼 설정한다.
VPC는 만들었던 VPC, DB subnet group은 방금 만든 subnet group으로 설정한다. Public access는 'No'로 설정한다.
VPC security groups는 일전에 만든 security group으로하고 AZ는 ap-northeast-2a. 나머지는 모두 기본값으로 설정한다.
이제 모든 세팅이 끝나고 생성을 하면 다음처럼 리스트에 보여진다. 시간이 지나 최종 생성이 완료될 것으로 보인다.
Load Balancer 생성
로드 밸런서를 생성하기 전 대상 그룹을 먼저 생성하자. EC2 > Load Balancing > Target Groups로 이동해서 대상 그룹을 생성하자. 대상 그룹은 쉽게 말해서 로드 밸런서가 요청을 받으면 누구한테 그 요청을 전달할건지를 정의하는 것이다. 만들고 직접 사용해보면 이해가 더 빨리 될 것 같다.
대상은 Instance로 설정하고, 그룹 이름은 cwchoiit-app-tg-80으로 설정하고 프로토콜은 HTTP, 포트는 80으로 설정한다.
VPC는 당연히 우리가 사용할 VPC를 선택한다.
그 다음 Health check 포트를 80으로 재정의한다.
그 외 모든 값은 기본값 그대로 'Next'를 클릭. 그러면 다음 화면이 나온다. 여기서 방금 만든 대상 그룹을 어떤 Instance에 적용할지를 선택해서 'Include as pending below' 버튼을 클릭한다. 그 후 'Create target group' 클릭
dnf -y localinstall https://dev.mysql.com/get/mysql80-community-release-el9-4.noarch.rpm
dnf -y install mysql mysql-community-client
# If you are installing MySQL Server as well as MySQL Client, please also execute the following commands.
dnf install mysql-community-server
Creating an optimized production build...
Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:68:19)
at Object.createHash (node:crypto:138:10)
at module.exports (/root/aws-frontend/node_modules/webpack/lib/util/createHash.js:135:53)
at NormalModule._initBuildHash (/root/aws-frontend/node_modules/webpack/lib/NormalModule.js:417:16)
at handleParseError (/root/aws-frontend/node_modules/webpack/lib/NormalModule.js:471:10)
at /root/aws-frontend/node_modules/webpack/lib/NormalModule.js:503:5
at /root/aws-frontend/node_modules/webpack/lib/NormalModule.js:358:12
at /root/aws-frontend/node_modules/loader-runner/lib/LoaderRunner.js:373:3
at iterateNormalLoaders (/root/aws-frontend/node_modules/loader-runner/lib/LoaderRunner.js:214:10)
at iterateNormalLoaders (/root/aws-frontend/node_modules/loader-runner/lib/LoaderRunner.js:221:10)
at /root/aws-frontend/node_modules/loader-runner/lib/LoaderRunner.js:236:3
at runSyncOrAsync (/root/aws-frontend/node_modules/loader-runner/lib/LoaderRunner.js:130:11)
at iterateNormalLoaders (/root/aws-frontend/node_modules/loader-runner/lib/LoaderRunner.js:232:2)
at Array.<anonymous> (/root/aws-frontend/node_modules/loader-runner/lib/LoaderRunner.js:205:4)
at Storage.finished (/root/aws-frontend/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:55:16)
at /root/aws-frontend/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:91:9
/root/aws-frontend/node_modules/react-scripts/scripts/build.js:19
throw err;
^
다음 명령어 실행
npm uninstall react-scripts # 낮은 버전의 react-scripts 삭제
npm install react-scripts # 제일 최신 버전으로 react-scripts 설치
빌드 성공 후 서비스 실행하기
백그라운드로 서비스 실행
nohup npm start &
서비스 실행중인지 확인
netstat -ltnp
결과로 3000번 포트가 실행중이어야 한다.
Nginx로 프록시 세우기
소스 폴더를 보면 project root/conf/conf.d 경로에 default.conf 파일이 있다. 이 파일이 nginx 설정 파일인데 이 파일을 설치한 nginx의 설정 파일 넣는곳에 복사하자.
cp default.conf /etc/nginx/conf.d/
그 후 방금 빌드하고 생성된 build 파일 하위 전체를 nginx가 실행 시 필요한 파일들을 두는 곳 내부로 이동시킨다.
cp -r build/* /usr/share/nginx/html/
Nginx 실행
systemctl start nginx
Nginx 실행 상태 확인
systemctl status nginx
실행해서 active상태이면 된다.
Nginx 활성화
systemctl enable nginx
이렇게 하면 80 포트로 접근해도 프론트 서비스가 띄워져야 한다. Nginx 설정이 그렇게 되어있으니. 그러나 EC2의 프라이빗 IPv4로 접근하면 안될것이다. VPN이 연결된 상태여도. 왜 그러냐면 서비스를 띄울 EC2 인스턴스에 대한 보안 그룹 설정을 할 때 OpenVPN 그룹은 80으로 접근할 수 없게 인바운드 규칙을 설정했기 때문이다. ALB를 통해서 접근해야 한다. 이 녀석은 인바운드 규칙을 할당했기 때문에.
그래서 로드밸런서를 통해 접속해보자.
EC2 > Load Balancers에서 만든 ALB를 클릭해보면 도메인 네임이 나온다. 그 녀석으로 접속해보자.
해당 페이지를 'www.cwchoiit.net'으로 접속하게 하기 위해 레코드 이름을 그렇게 작성하고 아래 별칭을 선택해서 ALB를 선택하고 서울을 선택한 다음 만들어진 ALB를 선택하자.
이제 EC2 > Load Balancers에서 HTTPS:443 리스너 규칙을 하나 추가해주면 된다. HTTPS:443의 규칙을 선택해서 추가해보자.
(물론 현재 두 개의 규칙이 있고 하나는 api.cwchoiit.net이면 8080, 그 외는 80으로 보내는 규칙이 있기 때문에 www.cwchoiit.net으로 진입하면 두번째 규칙인 80으로 보내고 80은 443으로 리다이렉션을 한다. 리다이렉션을하면 기본 홈인 로드밸런서로 들어가기 때문에 작동은 하겠지만 좀 정확할 수 있게 규칙을 하나 추가해보자.)
아래처럼 컨디션을 Host Header가 www.cwchoiit.net일 때의 조건을 추가한다.
그렇게 요청이 들어오면 80 대상 그룹으로 보낸다.
참고로 이 대상 그룹을 만들면서 HTTP:80이냐 HTTP:8080이냐를 설정했기 때문에 이 대상 그룹으로 설정해도 우리가 원하는 결과가 나오는 것이다.
이제, HTTPS로 www.cwchoiit.net으로 접속해보자.
HTTPS로 잘 들어오는 걸 확인할 수 있다.
여기까지가 프론트 서비스를 EC2에 올리고 로드밸런서와 도메인을 가지고 해당 서비스에 HTTPS로 접근하는 것을 확인해보았다. 이제 백엔드 소스도 EC2에 올려서 완전한 서비스를 만들어보자.
APP EC2 백엔드 서비스 띄우기
우선, App EC2에 접속하자.
ssh -i my-ec2-keypair.pem ec2-user@ec2-address
여기서 위에 만든 RDS 서비스의 데이터베이스에 접속한다. 그러려면 RDS 데이터베이스의 엔드포인트를 가져와야 한다.
AWS Console에서 RDS > Databases에 들어가면 위에서 만든 데이터베이스를 선택한다.
하단 Connectivity & security 섹션에 Endpoint 부분이 있다.
이 엔드포인트를 가지고 EC2에서 데이터베이스에 접속한다.
mysql -u <master 계정> -p -h <endpoint>
그럼 아래처럼 EC2에서 RDS로 접근할 수 있다.
데이터베이스를 만든다.
create database employee;
만든 데이터베이스를 확인한다.
'root'유저가 어디서 접근하든지 해당 데이터베이스에 권한을 부여한다.
GRANT ALL PRIVILEGES ON employee.* TO root@'%';
권한 부여한 것을 적용한다.
flush privileges;
이제 백엔드 소스를 내려받아야 한다.
sudo -i # root 유저로 변경
git clone https://github.com/chyoni/aws-backend.git
내려받으면 src/main/resources/application.properties 파일을 수정해야 하는데 데이터베이스의 엔드포인트를 수정해야한다.
다음 명령어로 8080 포트로 백엔드 서비스가 띄워졌는지 확인하자. (최초에는 좀 걸리기 때문에 느긋하게 기다리자.)
netstat -ltnp
잘 올라왔으면 브라우저에 다음 주소를 입력해보자.
http://10.1.1.152:8080/api/v1/healthz
그럼 다음처럼 결과가 나와야 EC2에 잘 띄워진 것
이제 로드밸런서에서 8080으로 들어오면 어떤 타켓 그룹으로 보내는지 다시 확인해보자.
EC2 > Load Balancing > Target Groups에 8080관련 대상 그룹이 있다.
이 녀석을 보면 다음처럼 Health check가 'Unhealthy'로 나온다.
이 이유에 대한 설명이 Health status details에 보여지는데 여기 보면 404로 나와있다. Health Check 경로가 어떻게 되는지 확인해보자. 다음처럼 Path가 '/'인데 여기말고 방금 위에서 접속한 '/api/v1/healthz'로 변경하자. 저 루트 경로로 보내는 컨트롤러가 없기 때문에 에러가 발생했던 것이니까.
변경하고 잠시 기다리면 Healthy로 변경된다.
그리고 이제 'Route53'으로 다시 가서 우리가 API Server로 등록하기로 한 도메인인 api.cwchoiit.net으로 들어와야 하기 때문에 이 레코드를 생성하자.
이렇게 설정하고 우리의 도메인으로 다시 접속해보자. 'https://api.cwchoiit.net/api/v1/healthz'
이제 백엔드 프론트엔드 모두 우리의 도메인에 연결을 정상적으로 끝냈다.
CloudFront 연결하기
앞단에 CloudFront를 연결해서 CloudFront가 주는 이점을 챙겨보자. 다음 그림처럼 CloudFront가 앞단에 생긴다.
이제 유저는 CloudFront를 통해서 서비스에 접근할 수 있게된다.
CloudFront를 사용하기 위해 위 그림처럼 ACM 생성이 필요하다(인증). 근데 CloudFront는 글로벌 서비스라서 서울 Region에 있는 ACM이 아니라 미국 동부(버지니아 북부)에서 ACM을 생성해야 한다.
그래서 지역을 버지니아로 변경하고 Certificate Manager 서비스로 들어와서 'Request' 버튼을 누른다.
퍼블릭 인증서로 요청을 한다.
이렇게 두 개의 도메인 이름을 추가하고 나머지는 기본 설정값으로 선택한다.
요청을하고 다시 ACM > List certificates로 가보면 인증 요청한 내역이 하나 있고 세부정보로 들어가보자.
Domains 영역에 요청한 레코드가 있고 이 레코드들이 Route 53에 만들어진 상태가 아니라면 Route 53에 추가하자. 나는 만들어져있는 상태니까 패스.
여튼 위 그림처럼 Status가 Success로 잘 인증이 된 상태다.
이제 서울 Region으로 다시 넘어가자.
이제 로드밸런서 설정을 바꿔줘야한다. 뭘 바꿔줘야 하나면 HTTP:80으로 들어올 때 HTTPS:443으로 보내게 해놨는데 그럴 필요 없다. CloudFront가 대신해줄테니. EC2 > Load Balancers에서 규칙 중 80에서 443으로 보내는 규칙을 수정하자.
HTTP:80으로 들어오면 80 대상 그룹으로 포워딩하는것으로 변경했다. 그리고 규칙 하나를 추가해줘야한다. 어떤거냐면 이제부턴 앞단에 CloudFront가 있기 때문에 앞단에서 HTTP를 HTTPS로 포워딩한다. 그래서 www.cwchoiit.net으로 들어온다는 것 자체가 이미 HTTPS로 들어왔다는 의미가 된다. 그래서 이 호스트 헤더로 들어왔을 때도 80 그룹으로 보내면 된다. 그래서 다음처럼 규칙 하나를 추가해주자.
이제 CloudFront를 생성하자.
AWS Console에서 CloudFront를 검색한다.
CloudFront 서비스로 들어왔으면 생성한다. 원본 도메인은 ALB를 선택한다.
프로토콜은 HTTP Only로 선택. 그리고 HTTP로 들어오면 HTTPS로 포워딩할 것
캐시 설정은 다음처럼 정책만 선택한다.
그리고 CNAME과 SSL Certificate은 위에서 버지니아에서 설정한 대로 입력하면 된다.
다 끝내고 배포버튼을 누르면 다음 화면이 나온다. 좀 기다리면 되니까 좀만 기다리자.
Route 53으로 가서 레코드 편집을 해야한다. 기존에 www.cwchoiit.net의 별칭을 만들 때 ALB로 만들었는데 그게 아니고 CloudFront로 만들어야 한다.
이렇게 다 설정을 하면 이제 CloudFront가 앞단에 생겨서 www.cwchoiit.net으로 들어가면 인스턴스의 HTTP:80 포트로 잡혀있는 프론트 서비스가 띄워진다. 확인해보자.
이게 CloudFront로부터 Hit된 것인지 개발자 도구에서 네트워크탭을 보면 확인할 수 있다.
여기까지 작업을 하면 만든 구조는 다음과 같다.
CI/CD 툴로 자동 배포하기
Jenkins를 구동할 EC2 인스턴스 하나를 생성하자. OS Images 정보는 기본값으로 세팅한다.
키페어도 동일하게 기존에 사용하던 키페어 그대로를 사용하고, Network 세팅은 우리가 사용하고 있는 VPC, Subnet은 private-subnet-app-a에 두고 Security Group은 새로 만들고 SSH 접속을 OpenVPN에서 접속 가능하도록 다음처럼 설정한다.
젠킨스 서버 포트인 8080에도 OpenVPN에서 접속할 수 있게 다음 설정을 추가한다.
젠킨스 서버 포트 8080에 ALB로도 접속할 수 있게 다음 설정도 추가한다.
그 외 나머지 설정은 그대로 두고 생성한다. 이렇게 생성하고 나면 OpenVPN을 통해 이 젠킨스 EC2에 접속할 수 있어야 한다.
위에서 보면 MY_KEYPAIR_NAME, MY_APP_PRIVATE_IP는 Jenkinsfile에 정의되어 있다.
여튼 .pem 파일을 카피해보자.
젠킨스가 구동되는 EC2 인스턴스로 들어가서 Copy
sudo cp my-ec2-keypair.pem /usr/local/share
파일의 소유자를 jenkins로 변경
chown jenkins:jenkins my-ec2-keypair.pem
여기까지 하면 젠킨스가 빌드하는데 모든 준비가 끝났다. 빌드를 해봐야 하는데 그 전에 한가지 보안 그룹에 추가할 게 있다.
젠킨스에서 APP EC2에 SSH로 접근할 수 있어야 한다. 그래서 다음과 같이 APP EC2의 보안 그룹의 인바운드 규칙을 수정하자.
이제 젠킨스에서 직접 빌드를 해보면 된다. 젠킨스로 들어가보자. 들어가서 대시보드에서 백엔드용 Job을 선택하고, 'Build Now'를 클릭
빌드를 하면 좌측 하단에 빌드 번호가 이렇게 나온다. 얘를 클릭해서 들어가보면 Console output이 있다 진행 과정을 확인해보자.
진행하다 마지막 스테이지인 Ansible로 배포하는 스테이지에서 혹시 아래와 같은 에러가 날 수 있다.
그러면, ansible 소스에서 hosts/hosts 파일에 다음과 같이 한 줄 추가해주자.
[app:vars]
ansible_ssh_private_key_file=/usr/local/share/MY_KEYPAIR_NAME.pem
ansible_user=ec2-user
ansible_ssh_common_args='-o StrictHostKeyChecking=no' #추가된 부분
[app]
MY_APP_PRIVATE_IP
다시 돌려보면 다음처럼 SUCCESS로 파이프라인이 끝난다.
우선적으로 빌드가 잘 동작하는것을 확인했다. 이제 소스를 살짝 변경하고 빌드해서 자동으로 EC2에 배포가 되는것까지 해볼건데 그전에 젠킨스에 도메인을 붙여주자. Route 53으로 가서 만든 도메인에 레코드를 하나 추가하자.
이 레코드를 로드 밸런서에게 알려줘야한다. 로드밸런서는 이제 저 URL(jenkins.cwchoiit.net)으로 들어오면 어디로 보내줘야할지?를 알아야하기 때문에 EC2 > Load Balancers > Target Groups으로가자.
우선은 젠킨스 EC2에 대한 대상 그룹을 먼저 만들어야한다.
인스턴스 타겟 타입을 선택하고, 타겟 그룹 이름은 적절하게 설정하고 HTTP:8080으로 적용한다.
VPC는 지금 사용중인 VPC로 설정해주고 Health Check는 다음과 같이 적용한다.
Health check path는 젠킨스의 로그인이 안된 상태에서 루트(/)로 이동하면 /login으로 리다이렉트하니까 경로를 저렇게 바꿔주자.
Health check port는 8080으로 재정의한다. 이제 젠킨스가 돌고있는 EC2를 선택하면 된다.
이제 로드밸런서에서 HTTPS:443 규칙을 하나 추가해주자.
호스트 헤더가 jenkins.cwchoiit.net일 때 jenkins 대상 그룹(젠킨스가 띄워져있는 EC2)으로 포워드해준다.
이렇게 설정하면 이제 우리 젠킨스는 도메인을 갖게 된다. 확인해보자.
이제 한번 소스를 변경해서 변경한 내용을 이 Ansible이 자동 배포를 해주는지 확인해보자.
그런데, 그 전에 이 소스 한정 작업해줄 부분이 있다. 내 application.properties 파일은 gitignore에 등록된 파일이라 워크스페이스를 Clean하고 다시 Clone을 하면 프로퍼티 파일이 안 들어온다. 그래서 pipeline에서 application.properties 파일을 sample 파일로부터 복사한 후 데이터베이스에 접근할 유저의 패스워드 정보를 넣어줘야 한다.
이제 백엔드 소스를 변경해보고 변경한 후 젠킨스 빌드를 했을 때 자동으로 배포되는지 확인해보자. 변경할 부분은 다음과 같다.
컨트롤러에 Health check에서 리턴하는 텍스트를 다음과 같이 변경한다.
그리고 Git에 Push한 뒤 젠킨스 빌드를 다시해보자. 젠킨스로 빌드하기 전은 결과는 동일하게 다음처럼 보인다.
젠킨스 빌드를 진행했고 결과는 다음처럼 변경 사항이 자동 배포됐다.
참고사항⭐️ Jenkins 에서 nohup으로 background 프로세스 수행시, Job이 끝나지 않는 경우가 있다. SSH를 통해 스크립트를 수행시, 표준 출력이 닫히거나 timeout이 발생할 때 까지 스크립트가 계속해서 열려있게 된다. 그래서 script로 백그라운드 작업 수행시에는, 아래와 같이 모든 output을 redirect 해줘야 스크립트가 바로 종료가 된다. (백그라운드 작업을 실행하는 코드가 포함된 script 를 jenkins에서 보내거나, 로컬 머신에 해당 script가 있거나 마찬가지)
그러니까 Ansible 코드에서 run_java.yml 파일을 다음 형식에 맞게 수정해줘야한다.
nohup ./program > /dev/null 2>&1 &
CloudFront와 S3 연동
CloudFront의 기능 중 하나인 캐싱 기술을 S3와 연동해서 이해해보자. S3는 정적 컨텐츠를 보관하고 관리하는 서비스다.
AWS Console에서 S3를 검색해서 버킷을 만들자.
버킷 이름은 원하는 이름으로 설정한다.
그리고 하단에 Block all public access를 해제한다.
나머지는 기본값 그대로 가져가고 만들어주자. 그럼 버킷이 다음처럼 만들어진다.
들어가서 이미지를 업로드해보자.
두 개의 파일을 다음처럼 업로드한다.
이제 CloudFront로 가서 새 CloudFront를 만들자.
이번에 만들 Cloudfront는 원본 도메인이 S3다. 저번에는 ALB였기에 다른 Cloudfront가 필요하다.
Origin access는 Origin access control settings를 선택하고 control setting을 만든다.
Create control setting 버튼을 클릭하면 다음 팝업이 나오는데 기본값으로 만들면 된다.
Viewer에서 Viewer protocol policy는 Redirect HTTP to HTTPS로 선택한다.
CNAME은 하나 생성하고, SSL certificate은 저번에 버지니아에서 인증받은 하나의 ACM certificate을 선택한다.
그리고 나머지값은 디폴트로하고 배포를 하자. 그러면 다음처럼 만들어지고 여기서 Origins 탭을 클릭
원본인 S3를 선택하고 Edit을 클릭
내려보면 Origin access에 정책을 복사하는 부분이 있다. 정책 복사를 클릭한다.
그리고 S3로 다시 가서 정책을 넣어주자. 버킷으로 가서 Permissions 탭을 클릭한다.
하단에 Bucket policy가 있는데 여기서 편집을 눌러 방금 복사한 정책을 넣어주면 된다.
여기까지 한 후에 CloudFront로 다시가서 배포가 다 끝났는지 확인하고 도메인 이름을 가져와서 한번 들어가보자. 올린 이미지가 정상 출력되는지.
다음과 같이 잘 보여진다. URL 확인.
여기서 이제 확인할 부분은 CloudFront로 부터 Hit된 상태인지 확인하는 것이다. 개발자 도구에서 Network로 가면 확인이 가능하다.
지금 확인해보면 Miss로 되어 있다.
조금 기다리고 다시 로드를 해보면 Hit으로 변경되어 있다.
그럼 확인하고 싶은건 CloudFront로부터 Hit이 된 상태면 이 이미지들을 S3에서 삭제했을 때 그대로 사진이 보여져야 한다.
삭제하고 다시 확인해보자.
두 이미지를 모두 날렸다. 이 상태에서 다시 확인해보자. 잘 보인다. 이게 원본 서버로부터 받아오는게 아닌 CloudFront가 캐시를 가지고 있기 때문에 보여지는 것이다.
그럼 CloudFront가 가지고 있는 캐시를 무효화(Invalidation)을 해서 다시 원본 서버로부터 가져오게끔 해보자. 무효화를 하면 원본 서버에 이미지가 없으니 나오는게 없어야 한다.
VPC는 Virtual Private Cloud의 약자로 AWS에서 논리적으로 생성하는 독립적인 네트워크를 말한다. VPC안에 여러 서브넷을 만들 수 있는데 서브넷이라 함은 네트워크 내부의 네트워크이다. 서브넷은 네트워크를 보다 효율적으로 만드는데 서브넷을 통해 네트워크 트래픽은 불필요한 라우터를 통과하지 않고 더 짧은 거리를 이동하여 대상에 도달할 수 있다.
VPC안에 구성할 수 있는 서브넷은 퍼블릭 서브넷과 프라이빗 서브넷이 있다.
퍼블릭 서브넷
퍼블릭 서브넷은 외부와의 자유로운 통신이 가능한, 외부 인터넷 구간과 직접적으로 통신할 수 있는 공공 네트워크이다.
프라이빗 서브넷
프라이빗 서브넷은 외부에서 직접 접근할 수 없는 네트워크이다. 프라이빗 서브넷에 실제 서버가 동작하게끔 설정하고 외부에서 이곳으로 직접 접근이 불가능하게 한 후 퍼블릿 서브넷을 통해 외부에서 요청이 들어오면 퍼블릿 서브넷과 프라이빗 서브넷 사이에 연결을 하여 통신한 후 프라이빗 서브넷에서 필요한 요청 데이터를 NAT Gateway를 통해 요청에 응답하게 설계할 수 있다.
이 개념을 이해하려면 직접 VPC, Public Subnet, Private Subnet, NAT Gateway 등 만들어보기로 하자.
설계할 네트워크 전체 그림은 다음과 같다.
VPC 생성
AWS Console에 'VPC'를 검색해서 나오는 서비스를 클릭한다.
메인 화면 좌측 사이드바에 Virtual private cloud 섹션에 'Your VPCs'를 클릭해서 우측 상단 'Create VPC' 클릭
VPC Name tag와 CIDR을 설정 후 나머지는 기본값으로 설정한 다음 'Create VPC'를 클릭
생성한 VPC 확인
이렇게 VPC가 생성됐으면 VPC에서 사용될 서브넷을 총 6개를 만든다. 그중 2개는 Public 나머지 4개는 Private으로 설정한다.
가용영역을 둘로 나누어서 AZ1에는 Public Subnet 1개, Private Subnet 2개로 만들고 AZ2는 Public Subnet 1개, Private Subnet 2개로 하여 전체 VPC에 두 개의 가용영역이 있고 그 각각의 가용영역에 Public 1, Private 2 서브넷을 각각 가지는 그림으로 만든다.
Subnet 생성
VPC 메인 화면에서 좌측 Subnets을 클릭하고 나온 화면의 우측 상단 'Create subnet' 클릭
서브넷 생성 화면에서 서브넷이 들어갈 VPC를 선택하는데 방금 만든 VPC를 선택
첫 번째 서브넷을 만들자. AZ-a에 public subnet을 만든다. 서브넷의 대역은10.1.1.0/26으로 설정한다.
서브넷 추가를 위해 'Add new subnet'을 클릭해서 또 다른 서브넷을 만든다. AZ-C에 생성될 퍼블릭 서브넷이다. 대역은 10.1.1.64/26으로 설정한다.
지금까지가 퍼블릭 서브넷을 만드는 내용이었다. 각 AZ에 한 개씩 퍼블릭 서브넷이 있고 각 AZ에 이제 프라이빗 서브넷 2개씩을 할당할 것. 또 서브넷을 만들자.
이제 프라이빗 서브넷이다. AZ-a에 10.1.1.128/27 대역으로 한 개를 생성한다.
AZ-c에 같은 APP용 프라이빗 서브넷을 만들자. 대역은 10.1.1.160/27이다.
AZ-a에 DB용 프라이빗 서브넷을 생성하자. 대역은 10.1.1.192/27이다.
마지막으로 AZ-c에 DB용 프라이빗 서브넷을 생성한다. 대역은 10.1.1.224/27이다.
이렇게 총 6개의 서브넷을 만들고 최종적으로 'Create subnet'을 클릭하자. 총 6개의 서브넷이 생성됨을 확인할 수 있다.
인터넷 게이트웨이 생성
이제 인터넷 게이트웨이를 생성해 보자. 인터넷 게이트웨이는 VPC가 인터넷과 통신할 수 있도록 해준다.
인터넷 게이트웨이를 생성하려면 Virtual Private Cloud 섹션에 Internet gateways를 선택하자.
우측 상단 'Create internet gateway'를 클릭해서 만들자. Name tag를 입력하고 생성을 끝마치면 된다.
생성하고 나면 인터넷 게이트웨이 메인 화면에서 우측 상단 'Actions' 셀렉트 박스가 있다. 거기에 Attach to VPC를 클릭해서 만든 VPC와 인터넷 게이트웨이를 연결하자.
위에서 만든 VPC와 연결을 하면 된다.
라우팅 테이블 생성
라우팅 테이블은 VPC 내 서브넷들이 특정 IP로 향할 때 어디로 가야 하는지에 대한 정보를 저장하고 있는 것으로 생각하면 된다.
우선 만들면서 이해하면 더 빠를 것 같다.
마찬가지로 VPC > Virtual private cloud > Route tables로 이동하자. 우측 상단의 Create route table을 클릭해서 생성하면 된다.
Name과 VPC를 설정한 후 생성하면 끝난다. 지금 생성하는 라우트 테이블은 Public Subnets을 위한 라우트 테이블이다.
하나 더 생성한다. 이번엔 Private Subnets을 위한 라우트 테이블이다. 이름만 'my-private-route'로 달리 생성하자.
그렇게 되면 방금 만든 두 개의 라우트 테이블이 있다.
public route는 public subnet을 위함이다. public subnet은 외부와 통신이 가능해야 한다. 그래서 외부와의 통신을 하기 위한 인터넷 게이트웨이를 만들었고, public route에게 해당 인터넷 게이트웨이를 알려줘야 한다. 그 작업을 위해 my-public-route 내부로 들어가자. 하단 Routes 탭에 Edit routes를 클릭하면 된다. 여기 설정된 기본 라우트 탭은 라우트 테이블을 만들면서 연결한 VPC가 기본으로 테이블에 등록되어 있다.
즉, 10.1.0.0/16은 로컬과의 통신을 하면 된다는 뜻이고 여기서 설정할 0.0.0.0/0은 외부로 나갈 인터넷 게이트웨이와 통신을 하게 설정하면 된다.
즉, 최종 public-route는 다음과 같은 설정값을 가진다.
이 뜻은 이 public route에 할당된 서브넷들은 VPC 내부(10.1.0.0/16)에서는 VPC 내부적으로 통신을 하고 그 외 모든 목적지(0.0.0.0/0)는 인터넷 게이트웨이와 통신을 할 것이라는 의미가 된다.
private route는 VPC 내부와 밖에서는 안으로 들어오지 못하게 하고 안에서 밖으로는 나갈 수 있어야 하기 때문에 NAT Gateway가 필요하다. NAT Gateway는 내부에서 외부로 나가는 것만 허용한다. 그래서 private route에 등록할 NAT Gateway를 먼저 만들어보자.
NAT Gateway 생성
NAT Gateway란 무엇일까?
퍼블릭 서브넷과 프라이빗 서브넷이 공존할 때 프라이빗 서브넷은 인터넷과 통신이 불가능한 네트워크 공간이고 퍼블릭은 인터넷과 통신이 가능한 네트워크 공간이다. 이때 프라이빗 서브넷은 말 그대로 프라이빗하게 즉, 보안에 있어 취약하면 안 되는 민감한 데이터를 다루는 곳이어야 한다. 예를 들면 데이터베이스가 있는 곳. 자, 데이터베이스를 프라이빗 서브넷에 위치한 EC2에 설치해 보자. 여기서 의문이 생긴다.
근데 인터넷과 통신이 안되는데 어떻게 설치를 하지?
그렇다. 인터넷과 통신이 불가능한데 설치를 할 수 있을 리 없다. 즉, 이 프라이빗 서브넷일지라도 인터넷과의 통신이 필요한 경우가 더러 있는데 이럴 때 인터넷과 통신을 가능하게 해주는 녀석이 바로 NAT Gateway이다.
위 그림에서 Private subnet App A에서 인터넷과 통신을 하고 싶으면 인터넷과 통신할 수 있는 Public subnet A에 있는 NAT Gateway에게 트래픽을 알려야 한다. 그래서 흐름은 다음과 같다.
중요! 1. Private subnet App A에서 인터넷(ex: 211.158.2.3)으로 요청을 보낸다. 2. Private subnet App A에 매핑된 route table을 보니 로컬로 등록된 IP가 아닌 IP들은 전부 NAT Gateway를 향한다. 3. NAT Gateway로 트래픽을 보낸다. 4. NAT Gateway는 받은 트래픽을 실제 요청지를 저장한 후 다시 Router로 보낸다. Router는 Public subnet A에 매핑된 route table을 확인한다. 5. 확인해 보니 0.0.0.0/0은 Intenet Gateway로 향하고 있다. Internet Gateway로 트래픽을 보낸다. 6. 인터넷으로의 요청에 대한 응답을 Internet Gateway가 받았다. 받으면 이 응답을 Router가 받아서 route table을 확인한다. 응답받을 곳은 NAT Gateway 이므로 NAT Gateway로 향한다. 7. NAT Gateway로 응답을 받고 이 녀석이 저장해 둔 원래 요청지인 Private subnet App A로 다시 응답을 보낸다. 8. Router는 응답을 보낼 주소를 route table에서 찾는다. 찾아보니 Private subnet App A를 가르키고 있다. 해당하는 곳으로 응답을 보낸다.
이 흐름이 NAT Gateway를 통한 Private subnet이 인터넷과 통신하는 방법이다. 이 흐름을 보니 왜 NAT Gateway가 Public subnet에 존재하는지 더 명확하게 알 수 있었다. 인터넷과 통신을 하기 위해서는 Public subnet에 매핑된 route table을 참조해야하기 때문이다.
Public subnet에 매핑된 route table은 0.0.0.0/0이 Internet Gateway를 향하고 있고 Private subnet에 매핑된 route table은 0.0.0.0/0이 NAT Gateway를 향하고 있기 때문이다.
프라이빗 서브넷의 인스턴스가 다른 VPC, 온 프레미스 네트워크 또는 인터넷의 서비스에 연결하는 데 사용할 수 있는 가용성이 뛰어난 관리형 NAT(Network Address Translation) 서비스를 말한다.
NAT Gateway는 두 가지 유형이 있다.
퍼블릭: 프라이빗 서브넷의 인스턴스는 퍼블릭 NAT Gateway를 통해 인터넷에 연결할 수 있지만 인터넷으로부터 원치 않는 인바운드 연결을 수신할 수 없다.
프라이빗: 프라이빗 서브넷의 인스턴스는 프라이빗 NAT 게이트웨이를 통해 다른 VPC 또는 온프레미스 네트워크에 연결할 수 있다.
다음은 my-private-route를 선택하고 서브넷을 연결한다. 여기에는 프라이빗 서브넷들은 선택하면 된다.
EC2 인스턴스 생성
이제 Public subnet A에 EC2 인스턴스를 만들어보자. 이 인스턴스는 Bastion(요새, 보루 즉 거쳐가는 곳을 의미) Host라는 개념으로 보면 된다. 여기를 통해서 AWS DevOps 유저가 직접 접근하여 Private subnet에 있는 EC2 인스턴스에 들어갈 수 있게 될 것.
Security group 생성
AWS Console에 EC2를 검색해서 EC2 Instances로 들어가자. 그래서 새로운 EC2 인스턴스를 만든다. 그러나 인스턴스를 만들기 전 한 가지 먼저 진행할 부분이 있는데 '보안 그룹' 생성을 먼저 하자. Security group을 생성해서 만들 EC2에 해당 보안 그룹을 적용할 것인데 이는 이 인스턴스에 SSH로 접속하는 인바운드 규칙을 만들기 위함이다. 위 큰 그림에서 볼 수 있듯 'AWS DevOps 유저는 Public subnet A에 있는 EC2에 직접 접근할 수 있어야 한다. 그것을 허용하게 하는 보안 그룹을 생성할 것이다.
생성하는 화면에서 Security group name, Description, VPC에 값을 지정한다. VPC는 위에서 만든 VPC를 선택한다.
하단 인바운드 규칙에서는 AWS DevOps 유저가 오로지 현재 본인 만이라고 가정하고 SSH 접속 허용 IP를 My IP로 할당하자.
그리고 생성하기를 누르면 리스트에 방금 만든 보안그룹이 보인다.
Bastion Host(EC2) 생성
이제 인스턴스를 생성해 보자. 생성 화면에서 인스턴스의 Name을 설정한다. 그리고 OS Image는 Amazon Linux를 사용한다.
Key pair는 새로 생성을 해도 되고 기존에 사용했던 key pair가 있으면 그대로 사용해도 된다. 지금은 새로 만들어 사용한다.
Network settings는 위에서 만든 VPC와 서브넷은 Public Subnet A로 설정하고 보안 그룹은 방금 막 만든 보안그룹으로 세팅한다.
나머지 값은 그대로 둔 채 생성을 한다. 인스턴스 리스트에 생성한 인스턴스가 잘 보인다.
Elastic IP를 생성한 EC2에 할당
이제 Elastic IP를 방금 생성한 EC2에 할당한다. Network & Security > Elastic IPs로 가서 우측 상단 Allocate Elastic IP address를 클릭한다.
모든 값은 기본값으로 두고 하단 'Allocate' 버튼을 누르면 방금 생성한 Elastic IP가 나오고 그 녀석을 선택해서 방금 만든 EC2에 할당한다.
EC2에 잘 할당됐는지 확인하기 위해 Instances로 가서 방금 만든 EC2에 저 Elastic IP가 잘 할당됐는지 확인해 보자. 생성한 EC2를 선택하면 하단에 정보가 나오는데 Public IPv4 주소를 보면 방금 생성한 Elastic IP가 잘 할당됐음을 확인할 수 있다.
Private Subnet App A의 인스턴스에 할당할 보안 그룹 생성
위 전체 네트워크 설계 그림에서 보면 Bastion Host(Public Subnet A의 EC2)에서 Private Subnet App A에 있는 인스턴스에 접속할 수 있는 상태이다. 이러기 위해서는 Private Subnet App A에 보안 그룹을 할당해서 Bastion Host로부터 SSH로 들어오는 인바운드 규칙을 할당해줘야 한다. 이 작업을 해주자.
EC2 > Network & Security > Security Groups > Create security group
Name, Description, VPC를 설정해 주고 (VPC는 위에서 만든 VPC) 인바운드 규칙에 SSH로 들어오는 것들 중 허용하는 소스는 위에서 Bastion Host에 할당한 보안 그룹만 할당한다. 즉 Bastion Host에 SSH로 접속 가능한 호스트는 이 Private Subnet App A의 인스턴스에도 SSH로 접속이 가능하다는 의미가 된다.
생성이 잘 됐으면 Private Subnet App A에 생성할 EC2를 만들자.
Private Subnet App A EC2 생성
Name을 적절히 설정해주고 OS Image는 Amazon Linux, 그 외 다 기본값으로 설정하고 Key pair는 위에서 만든 EC2에 사용한 것을 그대로 사용한다.
Network settings는 VPC는 위에서 만든 VPC, Subnet은 Private Subnet App a, Security group은 방금 만든 보안 그룹을 선택하고 생성을 완료한다.
생성이 완료되면 인스턴스 리스트에 잘 보이면 된다.
Copy file over SSH
이렇게 생성이 다 됐으면 Bastion Host에서 Private EC2에 SSH로 접속이 가능해야 한다. 보안 그룹을 그렇게 세팅했으니까.
근데 여기서 한 가지 더 해줄 작업이 있는데 보안 그룹은 Bastion Host로부터 SSH로 접속 가능하게 인바운드 규칙을 만들었는데 그때 사용할 Key pair는 그대로 같은 Key pair를 사용했기 때문에 Bastion Host에 접속할 키를 Bastion Host에도 저장해놔야 한다. 그러기 위해 로컬에서 SSH를 이용해서 다른 서버로 파일을 카피하는 명령어로 .pem키를 우선적으로 보내자.
Connect 'Public Subnet A EC2' to 'Private Subnet App A EC2' via SSH
이제 진짜 Bastion host에서 private EC2에 접속해 보자. 우선 Private EC2의 Private IPv4 Address를 확인하자.
Bastion host에 들어간 후 다음 명령어로 private EC2에 들어가 보자. 당연히 다음처럼 명령어를 사용하려면 .pem 파일이 있는 경로에서 실행해야 한다.
ssh -i my-ec2-keypair.pem ec2-user@10.1.1.156
그러면 이와 같이 정상적으로 접속이 됐음을 확인할 수 있다.
들어와서 한 가지 더 확인할 게 있다. 바로 NAT gateway를 통해 내부에서 외부로 통신이 가능한지를 확인하는 것. 다음 명령어를 통해 외부 인터넷으로 통신이 가능한지 확인해 보자.
curl -v www.google.com
결과는 다음처럼 200 OK가 나와야 한다.
OpenVPN을 이용해 Bastion Host 대신 Private Subnet EC2에 접속해 보기
위 전체 네트워크 설계 그림에서 변경되는 부분이다.
Public subnet A에는 OpenVPN용 EC2가 만들어질 예정이다. 그리고 그 OpenVPN을 통해서 Private EC2에 접속해 보자.
OpenVPN EC2 만들기
Name은 my-openvpn-ec2로 설정한다.
OS Image는 'openvpn'으로 검색
보이는 화면에서 AWS Marketplace AMIs에 가장 상위에 있는 OpenVPN Access Server 이미지를 선택한다.
나머지 설정은 그대로 하고 Key pair는 위에서 사용한 그대로 사용한다.
이제 Network settings은 VPC는 위에 만든 VPC, Subnet은 public subnet A, 보안 그룹은 기본으로 설정된 보안 그룹 그대로 가져가되, 아래 규칙은 모두 My IP로만 가능하게 변경하자. 아래 사진 말고 더 있다. 나오는 거 전부 My IP로 선택.
이렇게 설정한 다음 생성해서 EC2를 만든다. 이렇게 만들었으면 Elastic IP를 이 EC2에 할당해줘야 한다. Elastic IP는 생성하고 할당하는 것까지 위에서 해봤으니 그대로 하면 된다. 할당했으면 EC2에 그 할당된 IP를 확인하여 SSH로 접속한다. 본인의 경우 Elastic IP는 다음과 같다. 접속 유저명은 'openvpnas'로 한다.
ssh -i my-ec2-keypair.pem openvpnas@3.34.163.7
접속하면 이러한 OpenVPN 설정 화면이 노출된다.
모두 기본값 설정으로 적용하고 설정이 완료되면 다음처럼 Admin URL이 보인다.
이 경로로 접속해 보자. 접속하면 다음처럼 경고 화면이 나오는데 '안전하지 않음'으로 접속하면 된다.
그러하면 드디어 이러한 화면이 나온다.
로그인하기 위해 유저 정보를 가져와야 하는데 기본값은 최초 OpenVPN 설정하는 부분에서 알려준다.
유저명은 'openvpn' 패스워드는 'aPlXOBiWw0RL'로 되어있다. 이렇게 로그인해 보자.
이렇게 로그인이 잘 되고 'Agree'를 클릭
로그인이 되면 좌측 USER MANAGEMENT > User Permissions에 들어가서 유저를 새로 만들어보자.
자동 로그인 설정과 패스워드를 입력하고 저장하자.
이제 이 유저로 로그인이 잘 되는지 Admin 패널이 아닌 일반 URL로 들어가서 로그인해 보자.
Sign In을 클릭하면 다음 화면이 나온다.
위 화면에서 OpenVPN Connect for all Platforms 섹션에서 본인의 OS에 맞게 설치를 하자.
본인의 경우 macOS라 다음처럼 보인다. 활성화 버튼을 토글 하면Connect가 된다.
이제 OpenVPN으로 Public Subnet의 EC2에 접속이 되어 있는 상태이므로 로컬에서 Private Subnet App A의 EC2 인스턴스로 SSH로 바로 접속이 되는지 확인해 보자. 그러나 그전에, 위에서는 설정 안 한 게 있는데 OpenVPN EC2는 Bastion host랑은 다른 EC2이기 때문에 이 OpenVPN EC2를 생성했을 때 적용한 보안 그룹 또한 Private EC2에 인바운드 규칙으로 할당해줘야 한다.
Security Groups > my-private-ec2-sg에서 인바운드 규칙에 다음처럼 OpenVPN EC2를 생성했을 때 만든 보안 그룹을 추가한다.
당연히 위에서 테스트 한 Private EC2라서 NAT gateway를 통해 외부로 통신이 가능하다.
VPC Peering
이제 한 발 더 나아가서 Region이 다른 VPC간 Peering을 통해 서로 통신이 가능하도록 만들어보자.
그림은 다음과 같다.
서울과 도쿄에 있는 VPC간 Peering을 해서 서로 통신이 가능하게 해보자.
도쿄에서 VPC 생성
우선 Region을 Tokyo로 변경하자.
우측 상단 Region을 선택해서 원하는 Region으로 변경할 수 있다. 그리고 VPC를 만들어보자.
생성 화면에서 VPC settings에 'VPC and more'를 선택하면 좀 더 템플릿 느낌으로 VPC와 그 외 Subnet, Network, Route tables등을 만들어준다.
여기서 추가 설정을 해주자. 우선 VPC IPv4 CIDR을 192.168.10.0/24로 설정한다.
그 다음 AZ는 2개, Public subnets 2개, Private subnets 2개, NAT gateway는 생성하지 않고, VPC endpoint도 생성하지 않는다.
그 외 나머지는 전부 기본 설정으로 하고 생성을 마친다. 생성이 끝나면 리스트에서 확인할 수 있다.
도쿄에서 EC2 인스턴스 생성
이제 EC2 인스턴스를 생성해보자. Name은 my-tokyo-private-ec2로 설정하고 Amazon Linux 이미지를 사용한다.
여기서는 서버에 직접 접근하지는 않을거니까 Key pair 생성도 생략한다.
네트워크는 방금 만든 도쿄의 VPC와 private subnet-1을 선택하면 된다.
그리고 생성 버튼을 누르면 키페어 없이 생성하는게 맞냐는 안내 팝업이 뜨는데 맞다고 해주고 생성을 진행하자.
보안 그룹 인바운드 규칙 수정
이제 생성된 인스턴스에 적용된 보안 그룹을 수정해야 한다. 서울에서 들어올 수 있게 서울 대역을 허용해주자.
VPC Peering connection
이제 도쿄 VPC에 피어링을 걸어보자. VPC > Virtual private cloud > Peering connections에서 우측 상단 'Create peering connection'을 클릭
Peering connection settings에서 로컬 VPC는 도쿄니까 도쿄의 VPC를 선택하고 연결할 VPC는 일단 내 계정의 다른 지역을 선택해서 서울을 선택하고 서울의 내가 가지고 있는 특정 VPC ID를 입력해주면 된다. 그리고 생성하자.
생성하고 나면 다음처럼 상단에 서울 지역으로 가서 이 요청을 수락해야한다라는 안내 토스트가 나온다.
서울의 피어링 연결로 가서 요청 대기중인 녀석을 수락해주자.
이제 거의 끝났다. 이제 확인만 해보면 되는데 SSH로 서울의 EC2 인스턴스 내부로 들어가자. 그러나 그 전에, 서울의 라우트 테이블에서 프라이빗 용 라우트 테이블에 도쿄 IP를 추가해줘야한다. 반대로도 마찬가지.
서울에서 도쿄로 Ping 날려보기
이제 모든 준비가 끝났다. 위에서 OpenVPN을 커넥트하면 로컬에서 바로 프라이빗 EC2 인스턴스에 SSH로 접속이 가능하다. 접속한 후 도쿄의 EC2 인스턴스 Private IPv4 Address로 핑 명령어를 입력하자.
ping 192.168.10.134
이처럼 핑을 날려서 잘 받으면 된다.
결론
VPC를 최초 생성해 보는것에 시작해서 AZ별 서브넷을 구축하고 퍼블릭 서브넷과 프라이빗 서브넷을 각각의 가용영역에 만들어서 퍼블릭 서브넷은 인터넷 게이트웨이에 프라이빗 서브넷은 NAT 게이트웨이에 연결하여 외부로 통신할 수 있게 하고 그 정의를 라우트 테이블에 해보았다. 더 나아가서 퍼블릭 서브넷에 EC2 인스턴스(Bastion host)를 만들어서 로컬에서 이 곳으로 접근하고 이 곳에서 프라이빗 서브넷에 있는 EC2에 접근하는것을 해보았고 그 과정에서 필요했던 보안 그룹 설정도 만들어보았다. 더 나아가서 Bastion Host에 접근해서 또 다른 EC2에 접근하는 게 아닌 OpenVPN EC2 인스턴스를 만들어서 이 인스턴스가 프라이빗 EC2에 연결할 수 있게 설정하고 로컬에서 OpenVPN을 이용해서 다이렉트로 프라이빗 서브넷의 EC2 인스턴스에 접속할 수 있게도 해보았다. 더 나아가서 서로 다른 Region에 VPC끼리 Peering을 해서 서울에서 도쿄로 통신하는 것까지.