들어가며

현재 투입되어있는 프로젝트에서는 최근 기존의 인프라 구성에서 많은 부분 전환된 일정이 있었습니다. 그 당시 Autoscaling을 적용하기 위해서 예상되는 문제점은 무엇이 있었는지, 그리고 그것을 어떻게 극복했는지 다음과 같이 정리하였습니다.

기존 환경

  • Autoscaling이 적용되어있지 않은 was EC2 2개 운영

  • 해당 서버의 Spring Framework 애플리케이션 내에는 @Scheduled 어노테이션을 통한 스케줄잡 사용

  • 일부 스케줄 관련 코드 :

@Scheduled(cron = "0 30 9 ? * *", zone = "GMT+9:00")
    public void notifyLongTermNoUseAdmin() {
        if("1".equals(GkConstant.WAS_NUMBER)) { // STG, PRD 의 1번 서버인 경우에만 Batch 실행
            try {
                List<LongTermNoUseAdminVO> notifyAdminDataList = longTermNoUseAdminSvc.getNotifyAdminDataList();

                for (LongTermNoUseAdminVO voNotifyAdmin : notifyAdminDataList) {
                    try {
                        List<String> mailReceiverIDList = new ArrayList<>(Arrays.asList(voNotifyAdmin.getAccountId())); // 메일 수신인 리스트에 장기 미사용자의 이메일 주소만 추출해 추가

                        // 대상자별 mail template parameter 가 다르기 때문에 for 문을 통해 개별 메일 발송
                        awssesClient.sendEmail(mailReceiverIDList, "[" + ospAppConfig.getGkServerType() + "]" + MailEnum.NOTIFICATION.getMailSubject(),
                                setMailHtmlBody(MailEnum.NOTIFICATION, voNotifyAdmin), null);

                        LOG.info("##### SUCCESS Sending Notification Email : {}, {}", voNotifyAdmin.getSaGuid(), voNotifyAdmin.getAccountId());
                    } catch (Exception e) {
                        e.printStackTrace();
                        LOG.info("##### FAIL Sending Notification Email : {}, {}", voNotifyAdmin.getSaGuid(), voNotifyAdmin.getAccountId());
                    }
                }
                LOG.info("##### SUCCESS notifyLongTermNoUseAdmin Batch");
            } catch (Exception e) {
                e.printStackTrace();
                LOG.info("##### FAIL notifyLongTermNoUseAdmin Batch");
            }
        }
    }
  • 위와 같이 “코드가 실행되는 두대의 서버 중 vm option의 특정 시스템 프로퍼티가 ‘1’로 되어있는 서버에서만 동작”하도록 조건이 설정 (if("1".equals(GkConstant.WAS_NUMBER)))

문제점

Autoscaling을 적용하게되면 서버가 동적으로 생성 및 삭제되기 때문에 어느서버가 1번 서버인지 기준이 사라지는 상황. 따라서 기존의 Scheduled 로직이 여러 서버에서 중복으로 실행되거나 혹은 전혀 실행될 수 없는 로직

수정 방향

※ 투입되어있는 프로젝트에서는 Spring framework의 버전이 낮은관계로 최신기술 적용하기에 어려움이 있었습니다. 예를들어 멀티서버 환경에서 Spring 스케줄 중복실행을 방지하기위해 사용되는 “@SchedulerLock"과 같은 어노케이션이 지원되지 않는 문제가 있었습니다. 따라서, 이를 해결하기 위해서 AWS 리소스를 활용한 API호출 방식으로 설계하였습니다.

  1. AWS EventBridge > Scheduler를 통해 매일 09:30 AM에 AWS Lambda 호출

  2. AWS Lambda에서 was에 연결된 Internal ALB DNS 호출

  3. Lambda의 Request를 받은 애플리케이션 서버에서 (수정된) 스케줄링 실행

작업 상세 내용

Python Code 생성

(Application에 API 호출하는 (Python) Lambda Code)

로컬파일에 lambda_function.py 파일 생성 후 아래의 파이썬 코드 내용 입력 및 저장

import json
import requests
import os
  
def lambda_handler(event, context):
    url = os.environ['ALB_URL']
    print("url : ", url)
    
    print(event)
    if "key" in event :
        print(event["key"])
        url = url + event["key"]
    
    headers = { "Content-Type": "application/json", "Connection" : "keep-alive" }
    response = requests.post(url, headers=headers, verify=False) # health check 테스트시 주석처리
    
    # ------CONNECTION TEST CODE------
    # health check 테스트시 아래 블록 주석해제
    # response = requests.get(os.environ['ALB_URL'] + 'common/health', headers=headers, verify=False)
    # content = response.content.decode()
    # print("content:", content)
    # ------CONNECTION TEST CODE------
    
    if response.status_code == 200:
        return {
            'statusCode': 200,
            'body': json.dumps('API Success')
        }
    else:
        return {
            'statusCode': response.status_code,
            'body': json.dumps('API Fail')
        }

라이브러리 및 패키지 생성

(HTTP 통신을 위한 request 라이브러리 구성 및 패키지 생성)

  1. 위의 Python Code가 있는 경로에서 requirements.txt 파일 생성

  2. requirements.txt 파일 내용 : requests==2.22.0

  3. requirements.txt 파일이 있는 경로에서 “pip3 install -r requirements.txt -t .” 실행 > 라이브러리 설치 완료

  4. “zip -r scheduler.zip .” 명령을 통해 Lambda에 업로드할 압축파일 형식의 패키지 생성

Lambda 구성

(Lambda 생성, 패키지 업로드, 환경변수 설정)

  1. Lambda 생성 (DEV 기준 “gk-dev-scheduling-lambda” Lambda 생성)

  2. 설정 1. 런타임 : Python 3.9

  3. 설정 2. 아키텍쳐 : x86_64

  4. 설정 3. VPC 설정 후 서브넷 설정 > was가 속한 private 서브넷 2EA

  5. 설정 4. 보안그룹 Gk_dev_lambda(dev alb에 대한 outbound(443포트)가 open된 SG // ※ alb쪽 SG에는 “Gk_dev_lambda” inbound 허용 필요)

  6. Lambda 생성 완료

  7. “.zip 파일” > “scheduler.zip” 업로드 > 소스 및 라이브러리 구성 완료

  1. ALB DNS 환경변수 설정 : [구성] 탭 > [환경 변수] > API 호출을 위한 도메인(로드밸런서 DNS) 환경변수 생성 → ex) “key” : “ALB_URL”, “value” : “{Internal-ALB-DNS}”

통신 테스트

AWS Lambda <-> Admin WAS 통신 테스트

  1. 1번 코드 주석 처리

  2. 2번 코드 주석 해제

  3. 변경사항 저장(Ctrl + S) 후

  4. 3번 [Deploy] 클릭하여 배포

  5. 배포 완료 후 4번 [Test] 버튼 클릭

  6. health check “up” 확인되는 경우 통신테스트 정상

✅

통신 테스트 완료되면 반드시 코드 원복

  1. 1번 코드 주석 해제

  2. 2번 코드 주석 처리

  3. 변경사항 저장(Ctrl + S) 후

  4. [Deploy] 클릭하여 배포

운영 환경 스케줄링 정상실행 확인 1 (Lambda Log)

AWS CloudWatch > 로그 그룹 > {설정된로그적재경로} > 9:30 AM에 실행된 스케줄링 이벤트 로그 확인 가능

운영 환경 스케줄링 정상실행 확인 2 (Application WAS Server Log)

AWS CloudWatch > 로그 그룹 > {설정된로그적재경로} > 9:30 AM에 실행된 스케줄링 이벤트 로그 확인 가능