AWS 채팅앱 인프라 배포 (1/2)

이 글은 2021년 2월2일 AWS 콘솔 페이지를 통해 배포하는 인프라 구성 노트다. MVP 용 채팅앱 인프라를 구성하면서 틈틈히 적은 내용이다. 구축하고자 하는 구성은 크게 라라벨 앱과 Node.js 앱으로 나뉜다.

🖥 Laravel App

라라벨 앱은 Elastic Beanstalk 의 Webserver 환경과 Worker 환경으로 배포되어, 전자는 앱을 위한 API 서버와 관리자 페이지 및 앱소개 페이지를 위해 사용되고, 후자는 라라벨 앱의 백그라운드 작업 및 채팅서버로부터 SQS 로 전달되는 푸시알림을 위한 큐워커로서 동작하는데 조만간 MySQL 과 Elasticsearch 와의 동기화 작업도 담당할 예정이다.

사용 인프라는, Elastic Beanstalk Worker, MySQL, Redis, SQS, DynamoDB, Elasticsearch(예정), S3, CloudFront, ACM, Route53, SNS, SMS, SES 등이다.

📙 RDS

DB instance size

  • db.t3.small

Storage

  • General Purpose (SSD)
  • 20 GiB

Connectivity

  • Default VPC
  • Public access : NO
  • VPC security group : default, RDB group

Database options

  • initial database name : wanderinghog

Monitoring

  • Audit log
  • Error log
  • General log
  • Slow query log

📙 Redis

라라벨 앱을 위한 1개 인스턴스 (cache.t2.micro 노드) 와 채팅 앱을 위한 1개 인스턴스 (cache.t2.small 노드) 를 생성한다. 주의할 점은 EB 환경과 같은 subnet group 선택후, Security groups 은 default security group 과 Redis 6379 port 를 위한 security group 을 지정한다. 다른 옵션들은 모두 leave as default.

📙 SQS

Worker 환경을 만들면서 Auto-generated queue 를 선택하면 Worker 환경을 위한 AWSEBWorkerQueue/AWSEBWorkerDeadLetterQueue 콤보가 자동으로 생성된다. 물론, 환경에 attach 된 queue 를 갖는 다는 것은 장점도 있지만 단점도 있다. 일단, 이름에 랜덤 스트링을 포함해 지저분한 건 차치하더라도, 행여나 환경 리빌딩이라도 하는 경우엔 .env 에 지정한 queue 이름과 달라져 오류를 만들어낼 수 밖에 없다. 따라서 수동으로 큐를 생성하는 것이 더 낫다. 이후 채팅 앱을 위한 queue 와 Elasticsearch 동기화를 위한 queue 각각 1개씩을 생성할 것이다. 생성후 Access policy (Permissions) 내용을 삭제하면, 아래와 같은 기본 퍼미션이 자동으로 만들어진다.

{
  "Version": "2012-10-17",
  "Id": "arn:aws:sqs:ap-northeast-2:206020602060:lousydomain-queue/SQSDefaultPolicy"
}

📙 S3

Private bucket for configuration files

production 용 .env 파일을 비롯하여, Google API 사용을 위하여 필요한 credentials 을 비공개로 저장하고 있다가 .ebextentions 배포 스크립트가 코드베이스의 특정위치에 복사하기 위한 파일들을 저장하는 버킷이 필요하다. 버킷 이름은 environment 라 명명했고 permissions 은 당연히 Block all public access 옵션을 선택했다.

Public bucket for Laravel app

라라벨앱에서 저장할 이미지 버킷의 objects 들은 누구나 볼 수 있어야 하기 때문에 퍼미션을 아래와 같은 설정했다.

Block public access options

Bucket policy 는 아래와 같고,

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::206020602060:user/admin"
            },
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::pub-images",
                "arn:aws:s3:::pub-images/*"
            ]
        }
    ]
}

아래 그림과 같이, ACL 에서 Everyone 에게 Read 퍼미션을 등록했다.

S3 Bucket ACL

📙 Elastic Beanstalk (Web server)

make sure all the scripts in .ebextensions and .platform are written correctly for the latest Elastic Beanstalk requirements.

우선, Select environment tier 항목에서 Web server environment 를 선택 후 Configure more options 버튼을 클릭한다. Presets 는 Auto scaling 을 위한 High availability 옵션을 선택한다.

Software

Document root 는 /public

Instance log streaming to CloudWatch Logs

  • enabled
  • 5 days retention
  • delete logs upon termination

Environment properties 에 아래 entries 추가

  • COMPOSER_HOME : /root
  • WORKER : false
  • TZ : Asia/Seoul

Instances

EC2 security groups

  • default security group
  • eb-80/22 : VPC security group

Capacity

Leave as default

Load balancer

Leave as default

Rolling updates and deployments

  • Deployment policy : Rolling
  • Batch size : 30%
  • Rolling based on Health
  • Batch size : 1
  • Minimum capacity : 1

Security

  • Virtual machine permissions : EC2 key pair : aws-eb

Monitoring

Health monitoring rule customization

  • Ignore application 4xx : enabled

Managed Updates

Weekly update window

  • Tuesday 19:00 UTC (04:00am)

Notifications

Network

Load balancer settings : set Visibility to Internal

Load balancer subnets (everything but ap-northeast-2b/2d, which has no support for t2.micro instance type)

  • ap-northeast-2a
  • ap-northeast-2c

Instance subnets

  • ap-northeast-2a
  • ap-northeast-2c

Database

leave as default

Tags

leave as default

📙 Elastic Beanstalk (Worker)

Note that I’d used Elastic Beanstalk Worker environment with this package (https://github.com/dusterio/laravel-aws-worker) for my earlier projects because of the less complexity. However, convenience comes with a price. With the approach, having multiple queue connections and dedicated workers must go through extra overheads. So, instead, I decided to go with supervisord this time. Here’s how.

이젠 Worker 환경을 셋업하는데, Select environment tier 항목에서 Worker environment 를 선택 후 Configure more options 버튼을 클릭한다. Presets 는 Auto scaling 을 위한 High availability 옵션을 선택한다.

Software

Document root 는 /public

Instance log streaming to CloudWatch Logs

  • enabled
  • 5 days retention
  • delete logs upon termination

Environment properties 에 아래 entries 추가

  • COMPOSER_HOME : /root
  • WORKER : true
  • TZ : Asia/Seoul

Instances

EC2 security groups (원칙적으로 Worker 의 경우 22 만 열면 된다. worker-daemon 에서 localhost:80 으로 요청을 보낸다. https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html 또는 https://github.com/dusterio/laravel-aws-worker 참고)

  • default security group
  • eb-22 : VPC security group

Capacity

Leave as default

Worker

  • Worker 환경은 원칙적으로 웹서버 프로세스가 필요없지만, Elastic Beanstalk 의 경우, aws-sqsd 데몬 프로세스와 puma 를 사용하여 SQS 에서 읽은 아이템 내용을 localhost:80/path 로 POST 호출하여 전달하는 기능을 기본으로 제공한다. aws-sqsd 프로세스를 없애면, health 이상으로 보고 되기 때문에, aws-sqsd 가 사용하지 않는 빈 SQS 큐를 바라보도록 우회시켰다. 대신 다중 큐 커넥션처리를 위하여 supervisord 를 설치하여 사용한다. (이런 낭비를 막기위해선 결국 라라벨앱 역시 Docker image 를 만들어서 ECS/Fargate 로 운영하는 방법이 답인듯).
  • Worker queue : aws-sqsd 데몬 프로세스가 바라볼 큐를 지정하는 옵션이므로, lousy-void-queue 를 만들어 queue 를 지정한다. (SQS 의 경우, 이처럼 만들어만 놓고 사용하지 않는 경우 사용료가 발생하지 않는다.) aws-sqsd 우회가 목적이므로, HTTP path 는 /void 로, 최대 동시 접속 HTTP connections 을 1로 설정했다.

Rolling updates and deployments

Leave as default

Security

  • Virtual machine permissions : EC2 key pair : aws-eb

Monitoring

Health monitoring rule customization

  • Ignore application 4xx : enabled

Managed Updates

Weekly update window

  • Tuesday 19:00 UTC (04:00am)

Notifications

Network

Load balancer subnets (everything but ap-northeast-2b/2d, which has no support for t2.micro instance type)

  • ap-northeast-2a
  • ap-northeast-2c

Instance subnets

  • ap-northeast-2a
  • ap-northeast-2c

Database

leave as default

Tags

leave as default

Tip) 만일 라라벨 db:seed 명령중에 메모리 부족 오류가 난다면 커맨드라인 상에서 아래와 같이 swap 메모리를 2G 정도 잡아두고 재실행해볼 수 있겠다. (실제론 Worker 환경의 인스턴스를 t2-tiny (1G) 에서 t2-small (2G) 로 변경했다.)

sudo dd if=/dev/zero of=/swapfile bs=128M count=16
sudo chown root:root /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo swapon -a

📙 Route53

MySQL, Redis, SQS 가 배포된 상태에서 동작되는 라라벨 앱을 확인할 수 있으면 도메인명을 연결할 차례이다. Route53 > Hosted zones 에 도메인명을 생성 후 NS 레코드에 나오는 4개의 name servers 정보를 godaddy 와 같은 domain name registrar 콘솔 페이지에 name servers 로 등록한다.

A record

EB 환경 셋업시 생성한 Load Balancer 를 지정하는 방법은 새로운 콘솔화면에서, Alias 스위치를 켜고 Alias to Elastic Beanstalk environment 를 선택하고 지역과 LB 이름을 셀렉션박스에서 고르도록 되어있다.

CNAME record

www.lousydomain.com to lousydomain.com

📙 Certificate Manager

ACM 의 역할은 자동으로 https certificate 를 DNS 에 연결할 Route53 레코드 자동생성 및 주기별 자동 갱신이다. Request a certificate 버튼을 누르고 이후 페이지에서 request a public certificate 을 선택 후 도메인명을 지정하면, Route53 에 CNAME 레코드를 자동으로 생성시킬 수 있다.

List 에 Imported type 의 server 라는 도메인이 나오는데, 이는 뭔가 모르겠다. In use? 가 Yes 던데…

📙 EC2

Load balancers

기본적으로 생성된 로드밸런서의 경우, HTTP 를 위한 80 포워딩 리스너 밖에 등록이 되어 있지 않으므로, HTTPS 를 위한 443 리스너 등록이 필요하다. 일단, 80 리스너는 HTTPS 로의 리다이렉팅, 443 리스너는 EB 타겟그룹으로의 포워딩을 지정하고, Security policy 에는 ELBSecurityPolicy-2016-08 옵션을 Default SSL certificate 에는 From ACM 과 도메인명을 선택한다.

아래의 이미지와 비슷한 셋팅이 이뤄졌으면 정상이다.

EC2 > Load balancer 설정화면

참고로 2번째 HTTPS 리스너의 경우 오랜지색 exclamation mark 가 있는데, 이는 security group 에 443 포트가 허용되어 있지 않다는 경고이므로, LB 의 SG 을 선택하여 수정한다.

📙 CloudFront

이제 CDN 설정한다. Create Distribution 버튼을 클릭하고 Get started 버튼을 클릭하면, 다양한 옵션들을 입력하게끔 되어있는데, 스크린샷으로 설명을 대처하겠다. 참고로 use-case 는 App 에 제공할 이미지 CDN 설정이다.

Origin Settings

Default Cache Behavior Settings

Distribution Settings

Tip) 깨알글씨를 읽어보면 알 수 있듯 cdn.lousydomain.com 와 같은 주소로 CloudFront 를 사용하고 싶다면 해당 도메인명과 Custom SSL certificate request 를 미동부 리젼(us-east-1) 의 ACM 에 해야만 하는 조건이 있다. 따라서, 다른 디테일 옵션들을 지정하기 전, 반드시 먼저 SSL certificate 신청을 완료하고 CloudFront 신청 페이지를 refresh 해야만 cdn.lousydomain.com 이라는 옵션이 Custom SSL Certificate 아래 입력박스안에 나타난다.

CloudFront Distributions 리스트에서 아이디를 클릭하면 General 탭에 정보가 나타나는데, d312cokiv00ltd.cloudfront.net 와 같은 값을 갖는 Domain Name 을 찾아 Route 53에서, cdn.lousydomain.com 에 대한 CNAME 레코드 값으로 반드시 등록해야 한다.

📙 SES

Simple Email Service 는 이메일 발송만 할 것이고, 이메일수신은 ForwardEmail 서비스를 이용하여, 다른 계정으로 포워딩되도록 셋팅했다. 이를 위해선 MX 와 TXT 레코드를 Route53 에 설정해야하는데, 그 과정 설명은 해당 서비스 페이지에 상세한 설명이 나오므로 생략한다.

Identity Management

이메일 발송을 위해서는 도메인명과 이메일을 확인해야한다. Verify a New Domain 버튼을 클릭 후 도메인명을 입력한다. Generate DKIM Settings 를 선택하면, 자동으로 Route 53 에 레코드들을 자동으로 생성할 수 있다. 이렇게 도메인명을 확인했으면 발송 이메일의 주소를 입력하여 소유권을 확인한다.

Tip) 이메일 Notifications 셋업은 email forwarding 을 enable 시키도록 한다. 그러면 complaints, bounce 등에 대한 보고를 이메일로 받을 수 있다.

Email Sending

Sending Statistics 메뉴를 클릭후 Sandbox 모드를 프로덕션모드로 변경하는 요청을 하면 수시간내에 프로덕션 모드로 기본 1일 5만건의 발송을 quota 를 할당받을 수 있는데, 여기까지하면 이메일을 발송할 수 있다.

📙 SNS

SNS 의 경우, EB 환경 생성시 Notification 이메일 주소를 지정하면 자동 생성되며, endpoint 는 email 로 설정되어 있으므로 Request confirmation 버튼을 눌러서 수신 확인을 한다. 이 email 로 EB 환경 상태 변경시 마다 알림이 전달된다.

SNS 의 Push Notification 도 필요하므로 Firebase FCM 을 설정한 뒤, arn:aws:sns:ap-northeast-2:000000000666:app/GCM/lousy-domain 과 같은 arn 생성하여 라라벨앱에서 사용하도록 한다.

Leave a Reply