ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ECS 기반 시스템 자동 멘테넌스 모드 작성기
    Technique/AWS 2022. 8. 19. 00:16
    반응형

    본인이 작성한 qiita 의 게시글을 한국어로 재 작성한 페이지 입니다.

    들어가기 전

    안녕하세요 :)

    오늘은 현재 회사에서 운용 중인 ECS Fargate 기반 시스템에 새롭게 멘테넌스 모드를 만들어 본 경험담을 남겨보고자 합니다.

    다양한 세련된 방법들을 이미 도입해서 사용하고 계시는 분들도 많겠지만, 다만 이런 방법을 통해 만드는 방법도 있구나 정도로만 생각해 주시면 감사하겠습니다.

     

    전제조건

    현재 저와 저희 팀이 운용 중인 서비스의 경우, 기본적으론 메인테넌스 모드가 굳이 존재할 필요가 없는 구조입니다.

    이번에 이런 작업을 하게 된 배경은, 저희 서비스보다 상위에 있는 서비스가 일정 기간 베이스로 메인테넌스 작업을 실시하고 있는데, 이때 영향을 받는 몇몇 상황이 발생했기 때문에 저희 팀에서도 해당 대응이 필요하게 되었습니다.

    때문에 거대한 서비스, 애플리케이션에 비해 비교적 간단히 작업할 수 있는 방법을 채택했다는 점을 미리 알려 드립니다.

     

    시스템 운용 상황

    현재 이런 식으로 하나의 ALB를 통해 접속 경로별 리스너를 작성하여 3 분할하여 운용하고 있습니다.

    컨테이너는 fargate로 운용 중이고, 타겟 그룹을 지정하여 전송시키고 있는 상황 입니다.

     

    하고 싶었던 것

    위의 현재 상황에서 멘테넌스 모드의 역할은 애플리케이션으로의 접속을 막아두는 것이 목적입니다.

    때문에 위의 시스템은 지정된 시간 동안에는 

    • 프론트엔드
      • 메인테넌스 중 안내 페이지로 리다이렉션
    • BFF
    • API
      • 503 시스템 에러 리턴
    • 메인테넌스 작업 중일 때 발생한 액세스 확인

    정도의 작업을 원했고, 이 작업은 AWS Lambda와 Event Bridge의 설정을 통해 작성할 수 있었습니다.

     

    실행 내용

    위의 내용을 바탕으로 아래의 구상도를 작성했습니다.

    이벤트 브리지에서 시간을 지정해두면 제일 먼저 update listner priority 람다 함수가 실행되어, 4번에 있는 리스너 정보를 최우선 순위로 조정시킵니다. 이 리스너의 경우 모든 리퀘스트를 maintenance_function이라는 람다 함수로 보내고 있으며, 이 함수는 내부에서 패스를 확인하여 /a의 경우 멘테넌스 안내 페이지로,  /b,/c의 경우 503 에러를 반환하는 작업을 실행합니다.

     

    자 등장인물들은 모두 등장하였습니다. 정리해보자면.

    1. 람다 함수 작성
      1. update listner priority
      2. maintenance_function
    2. Event Bridge 설정
    3. 기타 AWS 설정

    람다 함수 작성 1 

    import json
    import boto3
    import logging
    # logger 준비
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    
    def lambda_handler(event, context):
        # event bridge가 호출할 때에 파라메터 취득, 기본값은 start
        type = event['type']
        str = '시작'
        if type == 'end':
            str = '종료'
    
        logger.info( '메인테넌스 작업' + str )
        logger.info( 'ALB교체 작업 실시' )
        client = boto3.client('elbv2')
    
        # 리스너 룰의 우선순위를 교체작업
        if type == 'start':
            # 시작일 경우 priority를 1로 설정
            responce = client.set_rule_priorities(
            RulePriorities=[
                    {
                        'RuleArn': {메인테넌스 용 리스너ARN},
                        'Priority': 1
                    }
                ]
            )
        else:
            # 종료시에는 proiority를 99로 설정 (하려고 해도 현재 리스너가 얼마나 있냐에 따라 배치됨 현 재를 기준으로 한다면 4)
            responce = client.set_rule_priorities(
            RulePriorities=[
                    {
                        'RuleArn': {메인테넌스 용 리스너ARN},
                        'Priority': 99
                    },
                ]
            )
    
    
        # 작업 변경뒤 확인
        result = client.describe_rules(
            ListenerArn={리스너 자체의 ARN},
        )
        logger.debug( 'ALB교환 작업 완료' )
    
        return result

    ALB의 경우 리스너를의 우선 순위를 통해 전송 대상을 분활시킬 수 있습니다.

    패스를 통해 분활을 할 경우 최상위 권한을 가지고 있는 /* 를 메인테넌스용으로 별도 준비해둔 뒤, 이 항목으로 메인테넌스가 진행되는 시간대 동안 가장 최상위로 올려두는 것으로 대응을 할 수 있습니다.

    이벤트 브릿지에서 멘테넌스 시작, 종료 상황에 맞춰 파라메터를 넘겨주는 방식으로 람다함수를 구성했습니다.

    • 시작 → 우선순위를 1위로
    • 종료 → 우선순위를 최하위로 (현 상황에선 99로 지정해도 실제론 4가 됩니다 )

    람다 함수 작성 2

    import json
    import logging
    import re
    # logger 설정
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    
    
    # coding: UTF-8
    def lambda_handler(event, context):
        # 액세스 로그 기록
        logger.info('액세스 패스: ' + event['path'])
        # /a 로의 액세스 인가를 판단하기 위한 체크
        p = re.compile('^\/a*')
        match1 = p.match(event['path'])
    
        if match1 is not None:
            # /a 에 접속했을 경우, 준비된 메인테넌스 페이지로 리다이렉션 준비
            response = {
                "isBase64Encoded": False,
                "statusCode": 303,
                "headers": {
                    "Location": "https://example.com/a/maintenance"
                },
                "multiValueHeaders": {},
            }
        else:
            # 그 외의 접속일 경우
            # 503에러를 반환
            response = {
                "statusCode": 503,
                "statusDescription": "503 Service Unavailable",
                "isBase64Encoded": False,
                "headers": {
                    "Content-Type": "application/json; charset=utf-8"
                },
                "body": json.dumps({"message": "Service Unavailable"})
            }
    
        return response

    해당함수를 작성한뒤, 타겟 그룹에 설정해 둠으로써, 멘테넌스 모드가 실행중일 시에 이 람다 함수가 호출되며, 패스를 확인하여 위의 요건 사항을 만족 시키고자 합니다.

    더하여 이곳에 액세스 함으로써 어떤 패스로 접속하였는지를 CloudWatch 에 로그로서 남길 수 있기 때문에 이 조건 또한 클리어 할 수 있게 됩니다.

     

    /a 인 경우 프론트엔드에 미리 준비된 메인테넌스용 화면 으로의 리다이렉션을 /b,/c 의경우 json 을 반환하는 처리를 담당합니다.

    프론트 엔드측이 실제로 메인테넌스 중인 경우가 있지 않냐는 질문엔 현행 구조상으론 멘테넌스 시간을 굳이 확보할 필요가 없는 구조라는 것을 재차 말씀 드리겠습니다.

    Event Bridge 설정

    브릿지 설정은 크론형식으로 지정시간대 실행으로 룰을 생성합니다.

    로컬 타임존으로 언제 시작되는지 확인할 수 있다는 것 입니다.

    기본적으로 UTC로 설정이 필요하지만 아래의 현지 시간대로 확인 버튼을 이용하여 현지 시간대가 어느정도 인지 (도쿄의 경우 +9) 를 확인할 수 있습니다.

    파라메터로 json 을 설정하고 위의 값을 제시한다는 의미 입니다.

    그리고 파라메터는 아래의 설정을 통해 이용할 수 있습니다.

    저는 json을 주로 활용하는 편입니다. 아래와 같이 설정한다면 위에 작성한 람다 함수에서 해당 키로 액세스 하여 습득할 수 있습니다.

     

    기타 AWS 설정

    앞서 설명한 ALB에 maintenance_function 으로 전달하는 Target Group 을 사전에 등록해 둘 필요가 있습니다. update listner priority 함수에서 해당 리스너의 ARN 을 원하기 때문입니다.

    이정도만 해둔다면, 위의 모든 설정을 만족할 것입니다.

     

    테스트

    // 開始テスト
    {
      "type": "start"
    }
    
    // 開始終了
    {
      "type": "end"
    }

    테스트는 update listner priority 람다 함수의 테스트 기능을 이용해 확인할 수 있습니다.

    테스트 코드는 아래와 같이 2가지 패턴을 작성합니다.

    테스트 버튼을 눌러 제대로 ALB의 리스너가 갱신되었는지의 확인과, 각 경로로의 접속을 확인해봅시다 

     

    마무리

    제가 제시하고 있는 방법은 수많은 방법들 중 하나에 불과하며, 저도 이 작업을 실행하기에 앞서 수 많은 방법들을 조사했습니다.

    다만 저와 저희 팀이 원하는 것들을 만족하는 방법은 이 방법이 최선이었기에 이 방법을 선택하여 운용 중에 있습니다.

    물론 이 방법 외에도 조금 더 좋은 방법들은 무엇이든 존재한다고 생각합니다. 

    조금 더 좋은 방법이 있다면 코멘트를 통해 의견을 남겨주시면 감사하겠습니다 :) 

     

    제 글이 누군가에게 조금이라도 힌트가 되었다면 좋겠습니다.

    반응형

    'Technique > AWS' 카테고리의 다른 글

    9. 개발자 툴  (0) 2021.05.06
    8. AWS 의 애플리케이션 서비스  (0) 2021.05.04
    7. 세큐리티와 아이덴티티  (0) 2021.05.04
    6. AWS 의 데이터 베이스 서비스  (0) 2021.04.23
    5. AWS의 스토리지 서비스  (0) 2021.04.20

    댓글

Designed by Tistory.