• 돌아가기
  • 아래로
  • 위로
  • 목록
  • 댓글

AWS EC2 인스턴스 스케쥴러

DarkAcid 200

1

3

AWS EC 2 인스턴스 시작/종료 자동화하기

출처: YT: AWS강의실   [AWS 강좌] 퇴근 후 꺼지고 출근 후 켜지는 EC2:SSM으로 EC2 시간별 자동 정지/시작 

 

안녕하세요,

 

제가 일하는 팀은 딱히 IT쪽 사람들이 아니라 AWS Console들어가서 인스턴스를 시작하는것조차 어려운 멤버들입니다. EC2의 실행을 저희 팀의 사용시간에 맟춰 자동으로 실행하는 것에 목적을 두고 구글링하던중 좋은 유튜브 채널로부터 도움받고 글로 정리하는게 더보기 편할듯하여 공부한 내용을 공유하려고 합니다.

 

먼저 디자이너의 모델을 보도록하겠습니다

image.png.jpg

 

image.png.jpg

 

 개요는 CloudWatch (Amazon EventBridge 통합) 특정시간 (cron)이되면 람다를 호출하여 AWS에 SSM을 automation을 트리거 하여 AWS-Document에 적용된 코드로 특정 태그를 가진 EC2 인스턴스를 시작하거나 정지하는 역활을 수행하게 됩니다.

 

아래 클라우드 포메이션 스택파일을 적용하시면 원스톱으로 적용하실 수있지만 각ENV도 소개할겸 혹시 에러가 있을 경우 디버깅을 돕기위해 차례차례 설명해보겠습니다.

1. 먼저 Stack코드로 IAM role을 2개 만들게 됩니다.

 

첫째 role은 Lamda를 사용해 SSM의 오토메이션에 권한이 있는 역활을 하나 만들어줍니다

AutomationServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ssm.amazonaws.com
                - ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
      Path: "/"

 

해당 코드를 간단히 분석해보자면 ssm과 ec2서비스에 접근권한을 가진 IAM을 생성하고 AmazonSSMAutomationRole Policy를 적용하라는 명령어입니다.

 

EC2OnOffLambdaExeRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "ssm:*"
                  - "tag:*"
                Resource: "*"
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - "iam:PassRole"

                Resource: !Sub
                  - "${AutomationServiceRoleArn}"
                  - AutomationServiceRoleArn:
                      Fn::GetAtt:
                        - "AutomationServiceRole"
                        - "Arn"

 

해당 명령문은 Lamda의 코드를 실행할 수있는 권한을 가진 IAM 역활을 만드는 명령문입니다. 해당 IAM 역활은 AWS Policy 적용되어 있지 않고 permission에서 인라인 정책만 추가합니다. 위 코드의 PolicyName 이후의 코드가 전부 json으로 이루어진 코드인데요 람다에서 ssm을 접속할 수 있고 ec2의 태그를 볼수있으며 람다에로깅이 가능한 역활이 부여되며 먼저 생성되었던 Recource 태그를 SSMAutomation 역활도 사용할 수 있게됩니다.

 

2. Lamda 세팅

아래 명령어는 Lamda에 함수를 만드는 내용인데요

StopStartEC2Lambda:
    Type: "AWS::Lambda::Function"
    Properties:
      Handler: "index.handler"
      Role:
        Fn::GetAtt:
          - "EC2OnOffLambdaExeRole"
          - "Arn"
      Runtime: "nodejs12.x"
      # we give the function a large timeout
      # so we can wait for the bucket to be empty
      Timeout: 600
      Code:
        ZipFile: |
          var AWS = require("aws-sdk");
           AWS.config.update({
               region: "ap-northeast-2"
           });
           exports.handler = async function (event, context) {
             console.log("event:",event);
             var ssm = new AWS.SSM();
             var params = {
             DocumentName: (event.action=="stop")?'AWS-StopEC2Instance':'AWS-StartEC2Instance',
             Parameters: {
              'AutomationAssumeRole': [event.rolearn ],
             
            },
             MaxConcurrency: '10' ,
             MaxErrors: '25%',
             TargetParameterName:"InstanceId",
             Targets: [
               {
                 Key: `tag:${event.key}`,
                 Values: [event.value]
               },
             ]
             };
             try
             {
               const result=await ssm.startAutomationExecution(params).promise();
               console.log(result);
             }
             catch(e)
             {
               console.log(e);
             }
           };

 

Node.js 액션이 stop이라면 AWS-StopEC2Instance 문서를 불러오고 start라면 AWS-StartEC2Instance 문서를 불러와 오토메이션을 실행하는 부분입니다. 여기서는 영상에 나와있는 'AutomationAssumeRole'의 값를 맨처음 만들었던 SSMautomation 정책이 적용되어있는 IAM 역활의 arn으로 지정해주게 됩니다. 영상에 나와있지 않은 중요한 부분은 미리 지역이 서울로 선택이 되어있기 때문에 저처럼 미국에서 쓰시는분들은 region: "ap-northeast-2" 의 값을 해당하는 지역을 yml에서 바꿔주셔야합니다.

 

이렇게 Lamda의 함수를 정하는 단계에서 코드를 테스트를 해볼 수있습니다. 이 명령어가 SSM을 트리거하기 때문에 SSM에 자동화 탭에보시면 실행중인 명령어들이 보이십니다. 이것도 지역이 틀리게 설정되어있으면 안보일 수있으니 필히 확인해주세요.  클라우드 포메이션으로 실행하신 후 무조건 제일 먼저 람다에서 테스트 진행 해보시기바랍니다.

 

3. Cloudwatch (Amazon EventBridge)

리눅스에서 cron을 사용해서 특정 명령어를 반복사용하시는 분들이라면 익숙하실겁니다. 특정 이벤트 저희 같은경우는 람다의 함수를 parameter들을 특정해서 실행하는 툴입니다.

 

  CloudWatchEventStartRule:
    Type: AWS::Events::Rule
    Properties:
      Description: startrule
      ScheduleExpression: !Sub
        - "cron(0 ${StartTimeInGMT} ? * 1-5 *)"
        - StartTimeInGMT: !Ref StartTimeInGMT
      Targets:
        - Arn: !GetAtt StopStartEC2Lambda.Arn
          Id: start-ec2
          Input: !Sub
            - '{"key":"${EC2TagKey}","value":"${EC2TagValue}","rolearn":"${AutomationServiceRoleArn}","action":"start"}'
            - AutomationServiceRoleArn:
                Fn::GetAtt:
                  - "AutomationServiceRole"
                  - "Arn"
  CloudWatchEventStopRule:
    Type: AWS::Events::Rule
    Properties:
      Description: stoprule
      ScheduleExpression: !Sub
        - "cron(0 ${StopTimeInGMT} ? * 1-5 *)" #오전 9시 이전일 경우, 0 ${StopTimeInGMT} ? * 2-6 *
        - StopTimeInGMT: !Ref StopTimeInGMT
      Targets:
        - Arn: !GetAtt StopStartEC2Lambda.Arn
          Id: stop-ec2
          Input: !Sub
            - '{"key":"${EC2TagKey}","value":"${EC2TagValue}","rolearn":"${AutomationServiceRoleArn}","action":"stop"}'
            - AutomationServiceRoleArn:
                Fn::GetAtt:
                  - "AutomationServiceRole"
                  - "Arn"

  PermissionForEventsCallStartLambda:
    Type: AWS::Lambda::Permission
    DependsOn: CloudWatchEventStartRule
    Properties:
      FunctionName:
        Ref: StopStartEC2Lambda
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn:
        Fn::GetAtt:
          - "CloudWatchEventStartRule"
          - "Arn"
  PermissionForEventsCallStopLambda:
    Type: AWS::Lambda::Permission
    DependsOn: CloudWatchEventStopRule
    Properties:
      FunctionName:
        Ref: StopStartEC2Lambda
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn:
        Fn::GetAtt:
          - "CloudWatchEventStopRule"
          - "Arn"

 

각각의 액션에 따라 (시작/정지) 해당 이벤트를 시작할 수 있습니다. 특히 '{"key":"${EC2TagKey}","value":"${EC2TagValue}","rolearn":"${AutomationServiceRoleArn}","action":"stop"}' 이 명령어는 Lamda에 Json함수로 설정된 값을 보낼 수 있게 되어있습니다. action을 보시면 아시겠지만 stop과 start 두개로 나뉘어 있습니다. Cron은 GMT로 조금 골때리기는 하지만 로컬시간으로 스케쥴 확인이 가능합니다. 입력은 여전히 GMT입니다.

 

해당 유튜버의 영상을 이해해보고 글로 풀어내보려고합니다. 잘 부탁드립니다.

 

P.S 해당 yml은 다운받으실 수 있습니다 cloudformation에서 create stack->with new  ->upload template file을 이용하셔서 yml파일을 적용하시면 한방에 가능하십니다. 주의 하셔야할 ENV를 시간은 GMT라는것과 region이 서울로 세팅되있는 것만 변경하시면 다양하게 활용하실 수있습니다.

 

클라우드 포메이션 Stack [startStopEC2WithSSM.yml]

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Resource Id
        Parameters:
          - EC2TagKey
          - EC2TagValue
      - Label:
          default: Start/Stop Time
        Parameters:
          - StartTimeInGMT
          - StopTimeInGMT
Parameters:
  EC2TagKey:
    Description: EC2TagKey
    Type: String
    Default: Service
  EC2TagValue:
    Description: EC2TagValue
    Type: String
    Default: test
  StartTimeInGMT:
    Description: Start hour in GMT
    Default: 0
    Type: Number
  StopTimeInGMT:
    Description: Stop hour in GMT
    Default: 9
    Type: Number
Resources:
  AutomationServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ssm.amazonaws.com
                - ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
      Path: "/"

  EC2OnOffLambdaExeRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "ssm:*"
                  - "tag:*"
                Resource: "*"
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - "iam:PassRole"

                Resource: !Sub
                  - "${AutomationServiceRoleArn}"
                  - AutomationServiceRoleArn:
                      Fn::GetAtt:
                        - "AutomationServiceRole"
                        - "Arn"

  StopStartEC2Lambda:
    Type: "AWS::Lambda::Function"
    Properties:
      Handler: "index.handler"
      Role:
        Fn::GetAtt:
          - "EC2OnOffLambdaExeRole"
          - "Arn"
      Runtime: "nodejs12.x"
      # we give the function a large timeout
      # so we can wait for the bucket to be empty
      Timeout: 600
      Code:
        ZipFile: |
          var AWS = require("aws-sdk");
           AWS.config.update({
               region: "ap-northeast-2"
           });
           exports.handler = async function (event, context) {
             console.log("event:",event);
             var ssm = new AWS.SSM();
             var params = {
             DocumentName: (event.action=="stop")?'AWS-StopEC2Instance':'AWS-StartEC2Instance',
             Parameters: {
              'AutomationAssumeRole': [event.rolearn ],
             
            },
             MaxConcurrency: '10' ,
             MaxErrors: '25%',
             TargetParameterName:"InstanceId",
             Targets: [
               {
                 Key: `tag:${event.key}`,
                 Values: [event.value]
               },
             ]
             };
             try
             {
               const result=await ssm.startAutomationExecution(params).promise();
               console.log(result);
             }
             catch(e)
             {
               console.log(e);
             }
           };
  CloudWatchEventStartRule:
    Type: AWS::Events::Rule
    Properties:
      Description: startrule
      ScheduleExpression: !Sub
        - "cron(0 ${StartTimeInGMT} ? * 1-5 *)"
        - StartTimeInGMT: !Ref StartTimeInGMT
      Targets:
        - Arn: !GetAtt StopStartEC2Lambda.Arn
          Id: start-ec2
          Input: !Sub
            - '{"key":"${EC2TagKey}","value":"${EC2TagValue}","rolearn":"${AutomationServiceRoleArn}","action":"start"}'
            - AutomationServiceRoleArn:
                Fn::GetAtt:
                  - "AutomationServiceRole"
                  - "Arn"
  CloudWatchEventStopRule:
    Type: AWS::Events::Rule
    Properties:
      Description: stoprule
      ScheduleExpression: !Sub
        - "cron(0 ${StopTimeInGMT} ? * 1-5 *)" #오전 9시 이전일 경우, 0 ${StopTimeInGMT} ? * 2-6 *
        - StopTimeInGMT: !Ref StopTimeInGMT
      Targets:
        - Arn: !GetAtt StopStartEC2Lambda.Arn
          Id: stop-ec2
          Input: !Sub
            - '{"key":"${EC2TagKey}","value":"${EC2TagValue}","rolearn":"${AutomationServiceRoleArn}","action":"stop"}'
            - AutomationServiceRoleArn:
                Fn::GetAtt:
                  - "AutomationServiceRole"
                  - "Arn"

  PermissionForEventsCallStartLambda:
    Type: AWS::Lambda::Permission
    DependsOn: CloudWatchEventStartRule
    Properties:
      FunctionName:
        Ref: StopStartEC2Lambda
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn:
        Fn::GetAtt:
          - "CloudWatchEventStartRule"
          - "Arn"
  PermissionForEventsCallStopLambda:
    Type: AWS::Lambda::Permission
    DependsOn: CloudWatchEventStopRule
    Properties:
      FunctionName:
        Ref: StopStartEC2Lambda
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn:
        Fn::GetAtt:
          - "CloudWatchEventStopRule"
          - "Arn"

신고공유스크랩
3
profile image 1등
달소 2022.04.13. 15:14

캬.. 이렇게 스크립트까지 공유해주시다니 감사합니다 ㅎㅎ

AWS는 회사 업무용으로 사용하시나보군요 부럽습니닷

profile image 2등
Lamanus 2022.06.11. 12:51

ssm 사용하시면 혹시 모든 인스턴스에 스케쥴링이 잘 적용되나요? Automation은 관리형 인스턴스에만 적용되는줄 알았는데..

DarkAcid 글쓴이 2022.06.13. 11:43
Lamanus

람다에서 ssm 사용하는 방법을 취하고 있습니다. 아마존 이벤트 브릿지에서 폼을 람다로 넘겨서 쓰는 스케쥴러라 알고계신 방법과는 차이가 있을 수있습니다. EC2 태그로 켜고 끄는 이벤트만 람다로 실행하는거라 모든 EC2 인스턴스에 적용되더군요.

댓글 쓰기 권한이 없습니다. 로그인

취소 댓글 등록

cmt alert

신고

"님의 댓글"

이 댓글을 신고하시겠습니까?

댓글 삭제

"님의 댓글"

삭제하시겠습니까?


목록

공유

facebooktwitterpinterestbandkakao story

등록된 글이 없습니다.