AWS로 워드프레스 구축하기

  1. AWS로 워드프레스 구축하기(1) VPC,서브넷 등 네트워크 환경 구축하기

예전에 AWS로 웹서버 배포환경을 구축하였지만, 실제 서비스를 배포하지 않아 아쉬웠던(?) 경험이 있었습니다. 그래서 이번 기회에 한번 나만의 서비스를 배포하고 싶어서, AWS로 워드프레스를 구축하기로 했습니다!! 저는 lightsail을 활용하지 않을 겁니다.

구축한 블로그 주소 : https://blog.jwitstudy.life

※ Lightsail : 보다 간단한 AWS 버전.

※ 부족한 부분은 지속적으로 추가할 예정.

참조 블로그 AWS | AWS 클라우드를 이용한 고가용성 WordPress 서비스 배포 (tistory.com)

  1. VPC 환경 구성

(1) VPC: 논리적으로 격리된 네트워크 공간으로 자신이 원하는 서버 리소스들을 격리된 네트워크 환경 내에서 구성 가능

  • VPC는 제가 서버를 배포할 환경을 마련해줍니다. 해당 VPC는 다른 VPC와 영향을 주지 않습니다. 그래서 논리적으로 격리되고 독립적인 네트워크 환경인 것이죠. 마치 한 가상화서버에서 여러 개의 논리 서버가 존재하지만, 서로 영향을 주지 않는다고 생각하면 편한 거 같아요.
  • VPC 생성(아래와 같이 생성)
  • 저는 우선 VPC만 생성했어요. VPC 등으로 하면 서브넷이나 NAT GATEWAY 등을 알아서 잘 생성해주는데, 저는 우선 하나 하나 짚어가면서 만들고 싶어서 따로 했습니다.
  • IP CIDR : 해당 VPC 내에서 생성되는 서버 리소스들의 IP 대역을 설정하는 것입니다. 10.0.0.0/16으로 설정하면 해당 VPC 서버내에 모든 존재들은 10.0.X.X IP대역 내로 존재하는 거죠 .
  • 그리고 만들기 앞서 꼭 자신의 리전 확인하세요! (서울 ap-northeast-2)

(2) 서브넷 : VPC 내에 네트워크 구간을 나누어 자신이 원하는 서버들을 할당하게 해준다.

  • 10.0.0.0/16 구간을 총 네 구간으로 할당하여 2개의 public subnet, 2개의 private subnet으로 사용할 겁니다.

  • 그리고 VPC 서비스에서 Subnet 탭을 들어가 서브넷 생성을 하면 되는데, 아래와 같이 설정한다.
  • 각 subnet들을 ip, 이름을 맞추어서 설정하면 됩니다.
    • public A,B : 10.0.0.0/24, 10.0.1.0/24
    • private A,B : 10.0.2.0/24, 10.0.3.0/24
  • 그리고 각 A 서브넷의 가용지역을 A , B서브넷을 C 가용지역으로 설정하였다. 이는 물리적인 서버를 따로 떨어뜨려 놓아, 한 물리서버에서 고장나면 다른 AZ 서버에서 Active되도록 하여 고가용성을 확보하기 위함이다.
  • Public에는 bastion host(Nat instance), 웹서버 등 인터넷과 접근하는 서버를 구축할 거이며, Private에는 DB를 구축할 것이다.

(3) 라우팅 테이블

  • public, private 서브넷이 정상적으로 패킷을 보내기 위해 라우팅 테이블을 설정한다.
  • 총 두 개의 라우팅 테이블 Public, Private 라우팅 테이블을 설정할 것이다. 각 라우팅 테이블은 Public 서브넷, Private 서브넷의 패킷들의 도착지를 설정해줄 것이다.
  • 해당 테이블생성은 VPC 서비스 -> 라우팅테이블 -> 라우팅테이블 생성을 통해 할 수 있다.
  • 우선 Public 의 경우
    • 10.0.0.0/16 -> local
    • 0.0.0.0/0 -> IGW (public이므로, 외부 인터넷으로 향해야 한다. 따라서 모든 패킷이 인터넷 게이트웨이로 가도록 한다.)
  • 해당 인터넷 게이트 또한 VPC 탭에서 바로 생성가능하다.
  • 해당 라우팅 테이블을 생성하면 서브넷 연결 편집을 통해 public subnet A,B가 연결되도록 한다.
  • 그 다음 Private Subnet이다
    • 10.0.0.0/16 –> local
    • 0.0.0.0/0 -> Bastion host(=Nat Instance) (private 영역이 외부와 통신하기 위해서는 사설 ip를 공인 ip 로 전환하는 NAT 서버를 통해서 간다.)
  • 보통 NAT -GW를 NAT IP 전환을 한다. 하지만, NAT-GW는 돈이 (많이) 든다. 그래서 비용이 저렴한 NAT-Instance를 생성할 겁니다.
  • NAT Instance : 일반 서버이지만, NAT 기능도 수행한다.
  • 해당 인스턴스는 EC2 서비스를 통해 생성할 수 있다.
이 이미지는 대체 속성이 비어있습니다. 그 파일 이름은 image-8.png입니다
  • 우선 EC2 -> 인스턴스 -> 인스턴스 생성을 들어간다. 그리고 아마존 이미지 검색에 nat라고 지면 위와 같이 amzn-ami-vpc-nat 이미지를 선택한다.
  • 키페어는 putty 원격제어할 때 기본적으로 요구된다. 키페어 생성을 눌러 키페어를 생성하고, 거기서 키를 다운 받을 수 있는데, 그 키를 간직하도록 한다. 나중에 필요하다.
  • 그리고 네트워크 설정을 하는데, 해당 nat-instance는 퍼블릭 서브넷에 배치하여 기본적으로 외부 트래픽을 여기서 거쳐가도록 할 것이다. 그리고 보안그룹설정을 해야하는데
  • 위와 같이 보안그룹을 설정해주어야 합니다. 우선 NAT-instance를 통해서 원격제어를 수행할 겁니다. 따라서 원격제어 관련 포트(22)를 내 IP에서 허용하도록 설정합니다. 그리고 이후에 FTP를 통해 파일을 이동하기 위해 FTP 포트 21번도 허용해둡니다.
  • 이후, 인스턴스 생성을 완료하고 추가적인 수정이 더 필요합니다.
  • 소스/대상 확인 변경입니다. 소스/대상 확인변경을 허용해두면, 패킷의 출처를 확인하여, 본인과 관련없는 패킷의 경우 드랍합니다. 따라서, 해당 패킷이 유실되지 않도록 하기 위해서 소스/대상 확인 변경을 중지합니다. 이를 통해 내/외부로 패킷을 오갈 수 있도록 설정하는 겁니다.
  • 그리고 탄력적 IP를 설정해야 합니다. 탄력적 IP는 공인 IP를 할당하는 것으로, 외부에서 접근하기 위한 IP를 NAT-instance에 할당합니다. 이를 통해 외부에서도 저희 서비스에 접근할 수 있게 합니다.
  • 이렇게 NAT-Instance를 설정하면 라우팅테이블을 설정할 수 있게 됩니다
  • 그리고 해당 라우팅 테이블이 Private 서브넷과 연결까지 하면 기본 네트워크 설정은 마무리가 됩니다.

2. AWS로 워드프레스 구축하기(2) 웹서버, DB 구축하기

네트환경을 설정하였으니 이젠 웹서비스를 배포할 서버를 구축해봅시다!

  1. 웹서버 구축

1) 우선 웹서버 설정은 아래와 같이 진행할 겁니다

  • 중요한점은 기존 default 리눅스 ami 버전인 amazon linux 3 이 아닌 2로 진행할 겁니다.
  • 왜냐하면 이후 워드프레스 배포할 때 php 8.x 버전하고 계속 충돌이 일어납니다.

※ 워드프레스가 php 8.x 버전을 사용할 때, mysqli(php 모듈)을 찾을 수 없다는 오류가 계속 발생합니다. 관련 설정을 바꾸어도 자체적으로 모듈 경로를 아예 못찾더라고요. 하지만, aws linux 3 는 php 8.x만 지원합니다. 그래서 php7.x버전을 지원하는 aws linux 2를 사용해서 진행하셔야 워드프레스가 정상적으로 돌아갑니다.

  • 키페어 설정합니다. 웹서버로 원격제어를 통해 들어가서 각종 파일을 설치할거에요.
  • 구성도를 보면 이렇게 구성할 겁니다. 외부에서 HTTP request를 ELB의 리스너를 통해서 받아서 처리할 겁니다. 그리고 해당 로드밸런서는 웹서버에 트래픽을 분배해서 처리할 겁니다. 그리고 관리자는 Bastion-Host(NAT Instance)를 통해 접근하여 서버 설정을 해줄 겁니다.
  • Web_server 보안 그룹 설정은 아래와 같습니다.
  • 우선 웹서버이기 때문에 외부에서 접근하는 모든 http request(80번 포트)는 오픈할겁니다.
  • 그리고 웹서버 작업을 위해 바스티온 호스트로 접근해오는 ssh 접근도 오픈할 겁니다.

2. 워드프레스 배포를 위한 설정 작업

1) 원격제어 설정 (putty)

  • 원격제어는 bastion host에 할당된 공인 ip를 통해 들어갈겁니다. ssh 인증에 필요한 ppk는 이전에 생성한 키페어로 진행할 겁니다.
  • 위와 같이 putty 를 설정하여 들어갑니다.
  • .ppk는 이전에 키페어 생성하면서 만든 .pem key를 putty key generator를 통해 만들 수 있습니다.

Windows Putty 환경에서 AWS EC2 ssh 접속 :: 데브웁스 (devoops.kr)

  • 위와 같이 원격으로 bastion host에 들어가면 id를 입력해야 됩니다. 우선, 기본 aws id는 ec2-user입니다.
  • 이렇게 잘들어가면 OK입니다.
  • 자 그럼 지금은 bastion host에 들어간거에요. 아직 웹서버에 들어간 건 아니에요. 저희는 웹서버로 들어가서 작업을 해야하는 겁니다.
  • Bastion Host에 원격제어를 위한 키를 옮기고, 그 원격제어키를 가지고 직접 ssh 명령어를 웹서버 ip로 입력하여 원격제어를 진행할 겁니다.                  
  1. 웹서버 원격제어 들어가기
  • 파일질라 설치
    • 갑자기 왠 파일질라? 왜냐하면 웹서버에 ssh 들어가기 위해서는 원격제어를 위한 키가 필요합니다. 따라서 파일질라를 활용하여 해당서버에 키를 넣을겁니다.
  • 파일질라 클라이언트를 설치하고 실행합니다. 그리고 파일탭에 사이트 관리자 들어가시면 해당 창이 뜹니다.
  • 새 사이트를 누르시고 호스트에 들어갈 항목은 nat-instance(bastion host)의 IPv4 DNS를 넣으시면 됩니다.(포트는 비워두세요)
  • ipv4 dns 정보는 EC2 -> Nat instance 정보에서 볼 수 있어요.

  • 이렇게 설정해서 연결하면…!
  • 잘 됩니다. 자 그럼 본인이 편한 디렉토리에 인증키파일을 옮기시면 됩니다.

*사용자가 ec2-user이기 때문에, 권한문제가 발생할 수 있기 때문에, ec2-user home에 넣는 걸 추천드립니다.

  • 그리고 본인 디렉토리에 제대로 되었는지 확인하세요! (해당 디렉토리에서 ls -al)
  • 자 그럼 웹서버로 원격제어 들어갈 겁니다.
  • 우선 바스티온 호스트에서 해당 명령어를 수행합니다.

sudo -i // 관리자 권한 변경

ssh -i [옮긴키이름].pem ec2-user@웹서버ip

  • 이렇게 해서 웹서버로 원격제어가 들어가질 겁니다.

4. RDS

1) RDS 생성

  •  워드프레스가 저장될 데이터베이스를 생성할 겁니다. RDS가 위치할 서브넷그룹을 생성할 겁니다.
  • 이전과 서브넷을 생성했던 같은 방법으로
    • wp-db-1(서브넷이름)/10.0.4.0/16(ip대역)/ap-northeast-2a(가용지역)
    • wp-db-2/ 10.0.5.0/16 / ap-northeast-2c
  • 이렇게 두 서브넷 영역을 생성할 겁니다. 
  • 그리고 RDS 서비스에 들어가서 아래와 같이 RDS를 생성할 겁니다.
  • 보안그룹의 경우,
  • Bastion-host, web서버 보안그룹을 통해 3306(mysql포트)가 통하도록 설정하면 됩니다.

2) 웹서버에 mysql , wordpress, apache 설치

  • putty로 다시 웹서버 접속 후 명령어 실행

yum update -y

yum install mysql

mysql -u admin -p wordpress(db이름) -h 엔드포인트

  • 엔드포인트는 RDS 정보에서 볼 수 있다.
  • 이후 mysql이 실행이 되면 show databases; 명령어 실행하여 wordpress db가 있는지 확인한다.
  • 다음 명령을 추가적으로 실행한다.

CREATE USER adminuser@’%’ IDENTIFIED BY ‘qwer1234’;  //사용자 생성

GRANT ALL PRIVILEGES ON wordpress.* TO adminuser@’%’;  // 게시판 사용 권한 부여

FLUSH PRIVILEGES; //권한 적용

2) 아파치 설치

yum -y install httpd //아파치 설치

service httpd start //아파치데몬 실행

systemctl enable httpd.service

systemctl status httpd // 상태확인, acitve여야 함

3) 워드프레스 설치

Amazon-linux-extras install php72 // php 8.x 는 잘안되므로 php7.x

wget https://wordpress.org/latest.tar.gz // 워드프레스 최신버전 다운

tar -xvzf latest.tar.gz -C /var/www/html // tar 압축 해제 후 html폴더로 이동

rm -f latest.tar.gz // 사용 파일 삭제

chown -R apache:apache /var/www/html/wordpress // wordpress 파일 소유자 변경

cd /var/www/html/wordpress/ //워드프레스 디렉토리로 이

cp wp-config-sample.php wp-config.php /워드프레스 설정파일 백업

vi wp-config.php // 파일 수정

  • 워드프레스 설정파일을 이렇게 고쳐야함 , 중간 호스트네임은 db 엔드포인트로 설정
  • Httpd.conf 파일을 바꾼다 .

vi /etc/httpd/conf/httpd.conf

그리고 systemctl restart http를 실행하여 아파치를 재시작한다.

이제 기본적인 웹 설정은 완료되었습니다!! 이젠 해당 웹서버로 접근하기 위한 Auto Scaling, https 등을 구현할 겁니다!

3. AWS로 워드프레스 구축하기(3) 워드프레스 배포

웹서버에 워드프레스 서비스를 기동하기 위한 환경을 마련했습니다. 그럼 외부에서 웹서버로 접근하기 위한 요소들을 설정할 겁니다.

앞에 글에서 언급한 바와 같이, 로드밸랜서를 통해 HTTP request를 받아 web server를 전달할 겁니다.

그 해당 로드밸런서를 어떻게 구성하는지를 확인해볼 예정입니다.

로드밸런서의 경우, 오토 스케일링이라는 서비스를 통해 같이 만들겁니다.

오토스케일링 설정하면, 로드밸런스를 같이 생성할 수 있습니다.

  1. Auto Scailing : 시스템 자원들을 모니터링하여 필요에 따라 서비스를 확, 축소하는 서비스. 웹서비스 필요에 따라서 웹서버를 필요만큼 확장할 수 있습니다. 이를 통해 운영의 탄력성을 확보할 수 있습니다. 우선 오토스케일링을 하기 위해서는 인스턴스의 템플릿이 필요합니다. 해당 서비스는 인스턴스 template을 통해 인스턴스를 유연하게 늘리개 때문에 템플릿을 설정해야 합니다.

  • 이를 위해 기존 웹서버에서 1) 이미지 생성 2) 탬플릿 생성을 진행해야 합니다.

1) 이미지 생성, 템플릿 생성

  • 이미지 생성 : 이름과 설명을 설정해준다 .
  • 탬플렛 생성
  • EC2-> Autoscaling -> 생성
  • 금방 만들었던 템플릿을 설정해준다.
  • 이후에 프라이빗 서브넷에 생성되도록 설정해준다.
  • 추가적인 설정으로 마무리 해준다.
  • 여기서 새로운 로드밸런서를 생성할 수 있다.
  • 그리고 웹서버(HTTP)를 로드밸런싱하는 것이기 때문에 Application Load Balancer(HTTP)로 설정한다.
  • 그리고 Internet-Facing으로 설정해서 인터넷으로 request를 받을 수 있게 해야한다.
  • 또한 외부에서 접속으로 해야하기 떄문에 퍼블릭 서브넷으로 설정해야한다.
  • 그리고 평소에는 2개 웹서버를 유지하고 최소 한개, 최대 4개가 되도록 설정한다.
  • 평소에는 서버가 2개로 유지되는 것이고, 필요에 따라서 최대 4개, 최소 1개로 줄어든다.
  • 이렇게 오토스케일링 그룹이 설정된다. 그리고 이후에 정상적으로 로드밸런서로 들어갈 수 있는지 확인해야 한다.
  • 이 DNS로 브라우저에 입력하면 워드프레스 페이지가 뜬다. 그럼 완성!

4. AWS로 워드프레스 구축하기(4) 도메인 설정

자 이제 워드프레스 배포도 되었습니다. 근데 매번 아마존 로드밸런서 DNS로 접속할 수는 없잖아요? 그래서 이젠 도메인 하나 구매해서, 진짜 웹사이트 이용하는 거 처럼 설정할 겁니다.

  1. 도메인 구매
  • 저는 여기서 도메인 구매를 하였습니다. 본인이 원하시는 도메인을 검색하셔서 구매를 진행합니다! (https://www.gabia.com/)

2. 구매한 도메인을 AWS서버와 연결

  • Route53 서비스로 들어갑니다.
    • Route53 : AWS에서 제공하는 도메인이름 웹서비스 (DNS), URL로 접속시 연결된 도메인, 웹서버로 연결되도록 돕는 서비스.
  • Route53-> 호스팅영역 -> 호스팅영역생성을 누룹니다.
  • 만들어진 이후, 해당 호스팅 영역을 들어가서 레코드 생성을 누릅니다.
  • 이후 아래와 같이 설정합니다. 레코드 이름의 경우, 본인이 원하는 레코드로 설정해도 됩니다!
  • 그리고 나서 해당 레코드가 호스트영역에 생성되는 것을 확인할 수 있습니다.
  • 그럼 다시 가비아로 가서, My 도메인 -> 도메인 관리 들어갑니다. 네임서버 설정에 들어가서, AWS에 있는 해당 4개의 주소를 가비아 네임서버에 붙여넣습니다.
  • 그리고 자신이 등록한 레코드(저의 경우 blog.jwitstudy.life)로 접속하면 본인이 배포한 워드프레스가 보일겁니다! 그럼 완성!

3. HTTPS 설정

  • 근데 아래와 같이 안전하지 못한 연결이라고 뜨면서 주의 요함 경고가 뜰겁니다.
  • 이는 HTTP로 하는 해당 접속이 암호화되지 않은 연결이라 보안에 취약하다는 것 입니다.
  • 이를 보완하기 HTTPS를 통해 암호화된 접속을 실행하고 있습니다.
  • HTTPS접속을 위해서는 해당 사이트의 신뢰성을 보증하는 인증서가 필요한데, 인증서 관련 AWS ACM(AWS Certificate Manager)를 통해서 진행할 수 있습니다.
  • ACM에 들어가서 인증서 요청을 합니다.
  • 퍼블릭인증서 요청 유형으로 선택한 다음, 아래와 같이 설정합니다.
  • 앞에 * 와일드카드를 사용하여, 자신의 도메인을 가지는 모든 도메인에 대한 인증도 할 수 있습니다. 그 외 설정은 그대로 진행합니다.
  • 그 이후에 해당 인증서를 클릭한 뒤 ‘Route S3에 레코드 생성’에 들어갑니다.
  • 그러면 바로 본인이 생성했던 레코드가 뜨고, 해당 레코드를 선택한 다음, 레코드 생성을 누르시면 됩니다.
  • 그리고 기다리시면 발급상태에 ‘발급됨’이 뜨면 완성입니다!
  • 그런 다음 해당 로드밸런서가 HTTPS를 요청을 처리할 수 있도록 설정해야합니다.
  • EC2->로드밸런서-> 리스너 및 규칙 -> 리스너 추가를 합니다.
  • 위와 같이, 리스너를 설정하여 추가합니다.
  • 인증서의 경우 본인이 만들었던 인증서를 불러오고, HTTPS 443포트로 설정합니다.

  • 이렇게 해서 다시 본인 블로그 주소로 접속하면,
  • https가 이제 되는 것을 확인할 수 있습니다.
  • 하지만 문제가 있습니다. ‘본인 블로그 주소/wp-admin’ 으로 해서 관리자 페이지에 들어가야 하는데, https의 경우 접속이 안되는 경우가 발생합니다. 그리고 본인이 만든 글을 들어가고자 하면, 주소가 등록한 주소가 아닌 임의의 주소로 변하는 등의 오류 현상이 발생합니다.
  • 현재 워드프레스에서 설정된 홈 주소가 제대로 설정되지 않아서 그런겁니다.
  • putty로 해서 본인 웹서버로 다시 접속합니다. 웹서버 /var/www/html 에 있는 wp-config.php 워드프레스 설정 파일을 바꿔야 하는데.
  • 23번째 줄 경에 해당 config를 본인 블로그 주소로 해서 추가하시면 됩니다.

define(‘WP_HOME’,’http://blog.jwitstudy.life’);
define(‘WP_SITEURL’,’http://blog.jwitstudy.life’);

  • 그리고 마지막 줄 // require_once가 포함되어있는 주석 전에 97번째 줄 전에

//Begin Really Simple SSL Server variable fix
$_SERVER[“HTTPS”] = “on”;
//END Really Simple SSL

  • 위에 해당 설정을 추가해주면 끝입니다!
  • 그러면 다음에는 멀쩡하게 https 접속이 되는걸 확인할 수 있습니다!

이상으로 워드프레스 배포는 끝입니다! 부족한 부분이나 개념적인 부분은 따로 올리거나 글에서 추가할 예정입니다!

5. AWS로 워드프레스 구축하기(5) 모니터링 설정

보통 큰 시스템 운영하면 EMS 등을 통해서 서버 이상 시, 담당자에게 문자가 보내집니다. AWS에 서버 이상시 메신저로 보낼 수 있도록 모니터링 기능을 설정하겠습니다.

사용한 메신저는 Slack입니다.

  1. 슬랙 회원가입

Slack은 생산성 플랫폼입니다 | Slack

해당 링크를 통해 Slack 회원가입을 진행합니다.

자 그럼 위와 같이 워크스페이스 생성하라고 나오는데, 워크스페이스 생성해줍니다.

  • 그리고 본인 워크스페이스 해당하는 곳에 클릭하고, 도구 및 설정 -> 앱관리로 들어갑니다.
  • 검색창에 web-hook 를 검색합니다.
  • 그리고 수신 웹후크를 추가해줍니다!
  • 채널은 본인이 아까 만든 채널에 연결합니다.
  • 자 그럼 웹후크 각종 설정이 나올겁니다.
  • 여기서 웹후크 URL은 따로 보관합니다. (나중에 쓸 일이 있습니다)

2. AWS SNS 설정

  • SNS : 구독형 알림 서비스 , 구독된 이벤트 발생시, 메세지를 전달하는 서비스
  • AWS Cloudwatch에서 서버 자원을 모니터링합니다.
  • 이상이 발생하면 SNS에서 경보가 전송되고 SNS는 람다를 호출합니다.
  • 람다에 정의된 Slack에 메세지를 보내는 함수가 실행됩니다.
  • SNS서비스 -> 주제 -> 주제 생성
  • 위와 같이 유형을 표준으로 두고, 원하는 이름으로 설정합니다.

3. CloudWatch

  • 이젠 CloudWatch에서 경보를 생성하겠습니다
  • CloudWatch -> 경보생성
  • 지표선택 누르시고 EC2 -> 유형은 CPU-utilization 이고 본인 웹서버 ec2로 해서 찾습니다.
  1. 그렇게 선택하고 나서, 밑에 임계 값을 원하는 설정으로 정합니다.
  • 그리고 나서 아까 만든 SNS 주제와 연결합니다.
  • 그런 다음 그대로 계속 다음 진행해서 생성합니다.

4. Lambda

  • Lambda는 서버리스 서비스로, 서버 설치 없이 서비스를 제공할 수 있는 서비스 입니다.
  • Lambda -> 함수 생성
  • 이름하고, 런타임 노드 버전만 바꿉니다. (최신 Node.js로 하면 안됩니다.)

자 그럼 밑에 코드가 있는데, 아래 코드를 추가합니다. 기존 꺼 밑에다가 붙여넣습니다.

// 구성 -> 환경변수로 webhook을 받도록 합니다.
const ENV = process.env
if (!ENV.webhook) throw new Error('Missing environment variable: webhook')

const webhook = ENV.webhook;
const https = require('https')

const statusColorsAndMessage = {
    ALARM: {"color": "danger", "message":"위험"},
    INSUFFICIENT_DATA: {"color": "warning", "message":"데이터 부족"},
    OK: {"color": "good", "message":"정상"}
}

const comparisonOperator = {
    "GreaterThanOrEqualToThreshold": ">=",
    "GreaterThanThreshold": ">",
    "LowerThanOrEqualToThreshold": "<=",
    "LessThanThreshold": "<",
}

exports.handler = async (event) => {
    await exports.processEvent(event);
}

exports.processEvent = async (event) => {
    const snsMessage = event.Records[0].Sns.Message;
    const postData = exports.buildSlackMessage(JSON.parse(snsMessage))
    await exports.postSlack(postData, webhook);
}

exports.buildSlackMessage = (data) => {
    const newState = statusColorsAndMessage[data.NewStateValue];
    const oldState = statusColorsAndMessage[data.OldStateValue];
    const executeTime = exports.toYyyymmddhhmmss(data.StateChangeTime);
    const description = data.AlarmDescription;
    const cause = exports.getCause(data);

    return {
        attachments: [
            {
                title: `[${data.AlarmName}]`,
                color: newState.color,
                fields: [
                    {
                        title: '언제',
                        value: executeTime
                    },
                    {
                        title: '설명',
                        value: description
                    },
                    {
                        title: '원인',
                        value: cause
                    },
                    {
                        title: '이전 상태',
                        value: oldState.message,
                        short: true
                    },
                    {
                        title: '현재 상태',
                        value: `*${newState.message}*`,
                        short: true
                    },
                    {
                        title: '바로가기',
                        value: exports.createLink(data)
                    }
                ]
            }
        ]
    }
}

// CloudWatch 알람 바로 가기 링크
exports.createLink = (data) => {
    return `https://console.aws.amazon.com/cloudwatch/home?region=${exports.exportRegionCode(data.AlarmArn)}#alarm:alarmFilter=ANY;name=${encodeURIComponent(data.AlarmName)}`;
}

exports.exportRegionCode = (arn) => {
    return  arn.replace("arn:aws:cloudwatch:", "").split(":")[0];
}

exports.getCause = (data) => {
    const trigger = data.Trigger;
    const evaluationPeriods = trigger.EvaluationPeriods;
    const minutes = Math.floor(trigger.Period / 60);

    if(data.Trigger.Metrics) {
        return exports.buildAnomalyDetectionBand(data, evaluationPeriods, minutes);
    }

    return exports.buildThresholdMessage(data, evaluationPeriods, minutes);
}

// 이상 지표 중 Band를 벗어나는 경우
exports.buildAnomalyDetectionBand = (data, evaluationPeriods, minutes) => {
    const metrics = data.Trigger.Metrics;
    const metric = metrics.find(metric => metric.Id === 'm1').MetricStat.Metric.MetricName;
    const expression = metrics.find(metric => metric.Id === 'ad1').Expression;
    const width = expression.split(',')[1].replace(')', '').trim();

    return `${evaluationPeriods * minutes} 분 동안 ${evaluationPeriods} 회 ${metric} 지표가 범위(약 ${width}배)를 벗어났습니다.`;
}

// 이상 지표 중 Threshold 벗어나는 경우 
exports.buildThresholdMessage = (data, evaluationPeriods, minutes) => {
    const trigger = data.Trigger;
    const threshold = trigger.Threshold;
    const metric = trigger.MetricName;
    const operator = comparisonOperator[trigger.ComparisonOperator];

    return `${evaluationPeriods * minutes} 분 동안 ${evaluationPeriods} 회 ${metric} ${operator} ${threshold}`;
}

// 타임존 UTC -> KST
exports.toYyyymmddhhmmss = (timeString) => {

    if(!timeString){
        return '';
    }

    const kstDate = new Date(new Date(timeString).getTime() + 32400000);

    function pad2(n) { return n < 10 ? '0' + n : n }

    return kstDate.getFullYear().toString()
        + '-'+ pad2(kstDate.getMonth() + 1)
        + '-'+ pad2(kstDate.getDate())
        + ' '+ pad2(kstDate.getHours())
        + ':'+ pad2(kstDate.getMinutes())
        + ':'+ pad2(kstDate.getSeconds());
}

exports.postSlack = async (message, slackUrl) => {
    return await request(exports.options(slackUrl), message);
}

exports.options = (slackUrl) => {
    const {host, pathname} = new URL(slackUrl);
    return {
        hostname: host,
        path: pathname,
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
    };
}

function request(options, data) {
    return new Promise((resolve, reject) => {
        const req = https.request(options, (res) => {
            res.setEncoding('utf8');
            let responseBody = '';

            res.on('data', (chunk) => {
                responseBody += chunk;
            });

            res.on('end', () => {
                resolve(responseBody);
            });
        });

        req.on('error', (err) => {
            reject(err);
        });

        req.write(JSON.stringify(data));
        req.end();
    });
}
  • 이후에 Deploy를 눌러 배포합니다.
  • 그리고 구성으로 들어가서 환경변수 탭에 들어가고, 아래와 같이 환경변수를 추가해줍니다.
  • 그리고 람다가 잘되는지 테스트해봅니다.
  • 테스트항목에 들어갑니다. 그리고 테스트 코드 넣는 곳에 해당 코들르 붙여넣습니다.
{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-2:981604548033:alarm-topic:test",
      "Sns": {
        "Type": "Notification",
        "MessageId": "test",
        "TopicArn": "arn:aws:sns:ap-northeast-2:123123:test-alarm-topic",
        "Subject": "ALARM: \"RDS-CPUUtilization-high\" in Asia Pacific (Seoul)",
        "Message": "{\"AlarmName\":\"TEST!!!\",\"AlarmDescription\":\"EC2 CPU 알람 (10% 이상 시)\",\"AlarmArn\":\"arn:aws:cloudwatch:ap-northeast-2:123123:alarm:ant-man-live-ALB-RequestCount-high\",\"AWSAccountId\":\"683308520328\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 datapoint (10.0) was greater than or equal to the threshold (1.0).\",\"StateChangeTime\":\"2021-07-14T23:20:50.708+0000\",\"Region\":\"Asia Pacific (Seoul)\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"CPUUtilization\",\"Namespace\":\"AWS/EC2\",\"StatisticType\":\"Statistic\",\"Statistic\":\"MAXIMUM\",\"Unit\":null,\"Dimensions\":[{\"value\":\"i-0e3e982bf1c7f0910\",\"name\":\"EngineName\"}],\"Period\":300,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\"Threshold\":1.0}}",
        "Timestamp": "2021-06-07T10:51:39.536Z",
        "SignatureVersion": "1",
        "MessageAttributes": {}
      }
    }
  ]
}

  • 그리고 테스트버튼을 누르고 함수실행 결과가 성공인지 확인합니다.
  • 그리고 슬랙에 들어가서 자신의 채널에 위와같은 메시지가 오면 성공입니다.
  • 그리고 아래와 같은 메시지를 보내게 하게끔 설정인 트리거를 설정합니다.
  • 해당 트리거 구성은 SNS 그리고 주제는 본인이 만든 SNS 주제로 설정합니다.
  • 이렇게 설정되면 완성입니다!

위와 같이 설정이 완료되었고, 앞으로 이상 발생시, Slack으로 바로 알람이 갈겁니다.

5. 부하테스트

  • putty로 웹서버에 접속합니다.
  • 해당 명령어를 입력합니다.

sudo -i

amazon-linux-extras install epel -y

yum install stress -y

//설치 완료 후

stress -c 4 (프로세스 4개를 띄워서 서버 cpu 99% 찍는겁니다.

  • Slack에 가서 확인하시면
  • 이렇게 경보가 옵니다!

게시됨

카테고리

작성자

태그:

댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다