CloudWatch Logs에 느린 로그를 게시하려고 할 때 CloudFormation에서 발생하는 오류를 해결하려면 어떻게 해야 합니까?

최종 업데이트 날짜: 2021년 3월 16일

Amazon CloudWatch Logs에 느린 로그를 게시하려고 할 때 AWS CloudFormation에서 발생하는 오류를 해결하려고 합니다. 오류는 다음과 같습니다. "CloudWatch Logs 로그 그룹 /aws/aes/domains/search/search-logs에 대해 지정된 리소스 액세스 정책이 Amazon Elasticsearch Service에 로그 스트림을 생성할 수 있는 충분한 권한을 부여하지 않습니다."

간략한 설명

이 오류를 해결하려면 로그 그룹 수준에서 별도의 정책을 사용하여 Amazon Elasticsearch Service(Amazon ES)가 CloudWatch Logs에 로그를 푸시하도록 허용합니다. 그런 다음, AWS::Elasticsearch::Domain 리소스에서 AccessPolicies를 사용하여 Amazon ES 도메인에 대한 권한을 설정합니다.

다음 단계는 Python 3.6에서 생성된 AWS Lambda 지원 사용자 지정 리소스를 사용하여 CloudFormation에서 CloudWatch에 느린 로그를 게시하는 방법을 보여줍니다. 사용자 지정 리소스는 Lambda 함수를 트리거하고, 이 함수는 PutResourcePolicy API를 트리거하여 느린 로그를 게시합니다.

참고: CloudFormation이 CloudWatch에 대한 로그 게시를 활성화하는 경우 AWS::Logs::LogGroup 리소스에는 리소스 액세스 정책을 할당할 속성이 없습니다. CloudWatch Logs 로그 그룹에 지정된 리소스 액세스 정책은 Amazon ES에 로그 스트림을 게시할 수 있는 충분한 권한을 부여해야 합니다. CloudFormation 리소스를 사용하여 직접 액세스 정책 권한을 생성할 수 없습니다. AWS::Logs::LogGroup 리소스에 대한 PutResourcePolicy API 호출이 CloudFormation에서 지원되지 않기 때문입니다.

해결 방법

참고: AWS Command Line Interface(AWS CLI) 명령을 실행할 때 오류가 발생할 경우 AWS CLI의 최신 버전을 사용하고 있는지 확인하세요.

다음 CloudFormation 템플릿은 사용자 지정 리소스를 사용하여 로그 그룹의 이름을 가져옵니다. 그런 다음, 템플릿은 Amazon ES 서비스가 로그 그룹에서 API를 호출할 수 있도록 허용하는 정책을 적용합니다.

1.    ESlogsPermission.yaml이라고 하는 CloudFormation 템플릿을 생성합니다.

Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

AWSTemplateFormatVersion: 2010-09-09
Description: AWS cloudFormation template to publish slow logs to Amazon CloudWatch Logs.
Parameters:
  LogGroupName:
    Type: String
    Description: Please don't change the log group name while updating
  ESDomainName:
    Description: A name for the Amazon Elastic Search domain
    Type: String
  LambdaFunctionName:
    Description: Lambda Function Name
    Type: String
Resources:
  AwsLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: !Ref LogGroupName
  LambdaLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: !Sub '/aws/lambda/${LambdaFunctionName}'
  LambdaExecutionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: root1
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: !Sub >-
                  arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}:log-stream:*
        - PolicyName: root2
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                Resource:
                  - !Sub >-
                    arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}
                  - !Sub >-
                    arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/${LogGroupName}
              - Effect: Allow
                Action:
                  - 'logs:PutResourcePolicy'
                  - 'logs:DeleteResourcePolicy'
                Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*'
  logGroupPolicyFunction:
    DependsOn: LambdaLogGroup
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: !Ref LambdaFunctionName
      Code:
        ZipFile: >
          import urllib3

          import json

          import boto3

          http = urllib3.PoolManager()

          SUCCESS = "SUCCESS"

          FAILED = "FAILED"

          def send(event, context, responseStatus, responseData,
          physicalResourceId=None, noEcho=False):
              responseUrl = event['ResponseURL']
              print(responseUrl)
              responseBody = {}
              responseBody['Status'] = responseStatus
              responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name
              responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name
              responseBody['StackId'] = event['StackId']
              responseBody['RequestId'] = event['RequestId']
              responseBody['LogicalResourceId'] = event['LogicalResourceId']
              responseBody['NoEcho'] = noEcho
              responseBody['Data'] = responseData
              json_responseBody = json.dumps(responseBody)
              print("Response body:\n" + json_responseBody)
              headers = {
                  'content-type' : '',
                  'content-length' : str(len(json_responseBody))
              }
              try:
                  response = http.request('PUT',responseUrl,body=json_responseBody.encode('utf-8'),headers=headers)
                  print("Status code: " + response.reason)
              except Exception as e:
                  print("send(..) failed executing requests.put(..): " + str(e))
          def handler(event, context):
              logsgroup_policy_name=event['ResourceProperties']['CWLOGS_NAME']
              cw_log_group_arn=event['ResourceProperties']['CWLOG_ARN']
              cwlogs = boto3.client('logs')
              loggroup_policy={
                  "Version": "2012-10-17",
                  "Statement": [{
                  "Sid": "",
                  "Effect": "Allow",
                  "Principal": { "Service": "es.amazonaws.com"},
                  "Action":[
                  "logs:PutLogEvents",
                  " logs:PutLogEventsBatch",
                  "logs:CreateLogStream"
                      ],
                  'Resource': f'{cw_log_group_arn}'
                  }]
                  }
              loggroup_policy = json.dumps(loggroup_policy)
              if(event['RequestType'] == 'Delete'):
                  print("Request Type:",event['RequestType'])
                  cwlogs.delete_resource_policy(
                  policyName=logsgroup_policy_name
                 )
                  responseData={}
                  send(event, context, SUCCESS, responseData)
              elif(event['RequestType'] == 'Create'):
                  try:
                      cwlogs.put_resource_policy(
                      policyName = logsgroup_policy_name,
                      policyDocument = loggroup_policy
                       )
                      responseData={}
                      print("Sending response to custom resource")
                      send(event, context, SUCCESS, responseData)
                  except Exception as  e:
                      print('Failed to process:', e)
                      send(event, context, FAILED, responseData)
              elif(event['RequestType'] == 'Update'):
                  try:
                      responseData={}
                      print("Update is not supported on this resource")
                      send(event, context, SUCCESS, responseData)
                  except Exception as  e:
                      print('Failed to process:', e)
                      send(event, context, FAILED, responseData)
      Handler: index.handler
      Role: !GetAtt 
        - LambdaExecutionRole
        - Arn
      Runtime: python3.6
  logGroupPolicycustomresource:
    Type: 'Custom::LogGroupPolicy'
    Properties:
      ServiceToken: !GetAtt 
        - logGroupPolicyFunction
        - Arn
      CWLOGS_NAME: !Ref LogGroupName
      CWLOG_ARN: !GetAtt 
        - AwsLogGroup
        - Arn
  ElasticsearchDomain:
    Type: 'AWS::Elasticsearch::Domain'
    DependsOn: logGroupPolicycustomresource
    Properties:
      DomainName: !Ref ESDomainName
      ElasticsearchVersion: '6.2'
      EBSOptions:
        EBSEnabled: true
        VolumeSize: 10
        VolumeType: gp2
      LogPublishingOptions:
        SEARCH_SLOW_LOGS:
          CloudWatchLogsLogGroupArn: !GetAtt 
            - AwsLogGroup
            - Arn
          Enabled: true

2.    ESlogsPermission.yaml 파일을 사용하여 CloudFormation 스택을 시작하려면 CloudFormation 콘솔 또는 다음 AWS CLI 명령을 사용합니다.

aws cloudformation create-stack --stack-name yourStackName --template-body file://yourTemplateName --parameters ParameterKey=LogGroupName,ParameterValue=Your-LogGroup-Name, ParameterKey=ESDomainName,ParameterValue=Your-ES-Name --capabilities CAPABILITY_NAMED_IAM --region yourRegion

참고: yourStackName, yourTemplateName, Your-LogGroup-Name, Your-ES-NameyourRegion을 사용자 값으로 바꿉니다.

CloudFormation 템플릿은 다음을 수행합니다.

1.    로그 그룹을 생성합니다.

2.    Lambda 함수를 생성합니다. Lambda 함수는 사용자 지정 리소스를 사용하여 CloudFormation 템플릿의 [파라미터(Parameters)] 섹션에서 로그 그룹 이름을 가져옵니다. Lambda 함수는 로그 그룹 이름에 대해 PutResourcePolicy API를 호출합니다. 로그 그룹에는 Amazon ES 도메인에서 로그를 넣도록 허용하는 정책이 있어야 합니다.

3.    Lambda 지원 사용자 지정 리소스를 생성하여 2단계에서 생성한 Lambda 함수를 호출합니다. 사용자 지정 리소스를 통해 Amazon ES에서 로그를 스트리밍할 수 있도록 로그 그룹 Amazon 리소스 이름(ARN)에서 PutResourcePolicy를 적용할 수 있습니다. CloudFormation은 템플릿에서 사용자 지정 리소스를 사용하여 LogPublishingOption을 통해 Amazon ES 도메인을 생성합니다.


이 문서가 도움이 되었나요?


결제 또는 기술 지원이 필요하세요?