새소식

AWS/Serverless

Jenkins 를 이용하여 AWS SAM 교차 계정 배포하기

  • -

이번 포스팅에서는 AWS 에서 제공하는

Serverless Framework 인 AWS SAM (Serverless Application Model) 의 교차 계정 배포에 대해 알아보겠습니다.

 

 

1. AWS SAM 개요

 

AWS SAM 은 AWS 환경에서 Serverless Application (e.g. Lambda, API Gateway 등) 을 쉽게 개발하고 배포하는 데 특화된 Serverless Framework 입니다.

 

AWS SAM 은 CloudFormation 기반으로 개발되었기 때문에 CloudFormation 의 모든 문법을 사용할 수 있으며,

그렇기에 IaC (Infrastructure as a Code) 성격도 보유하고 있습니다.

 

규모가 작은 조직에서는 교차 계정 (Cross Account) 기능의 필요성을 느끼지 못할 수 있지만,

규모가 큰 조직에서는 AWS 리소스 관리를 위한 Managed Account(Central)

실제 리소스가 배포되는 Development & Production Account 가 별도로 존재할 것입니다.

 

이런 환경에서 바로 SAM 교차 계정 배포 방법이 필요하게 됩니다.


이번 포스팅에서는 Jenkins 를 사용하여 교차 계정에 SAM 리소스를 배포할 것이며,

이를 실행하기 위해 필요한 정책은 무엇이 있는 지 알아보겠습니다.

 

 

1.1. SAM --role-arn 옵션에 대해

 

AWS SAM 을 사용해보신 분들은 SAM deploy 명령어 옵션 중, --role-arn 이라는 옵션이 있다는 것을 알고 계실 겁니다.

 

 

다른 AWS 서비스에 --role-arn 옵션이 있다면 보통 Assume Role 에 관한 내용이지만, AWS SAM 은 다릅니다.

해당 옵션을 사용하여 타 계정의 IAM Role 을 사용하고자 할 때, 다음과 같은 오류를 볼 수 있습니다.

An error occurred (AccessDenied) when calling the CreateChangeSet operation: Cross-account pass role is not allowed.

 

 

이를 해결하기 위해 AWS SAM 에서는 STS 서비스를 통한 단기 자격 증명을 이용합니다.
이제부터 하나씩 그 단계를 밟아 보겠습니다.

 

MGMT 는 중앙 계정 (Jenkins 존재),
DEV 는 실제 SAM 리소스를 배포할 계정 (교차 계정) 을 지칭합니다.

 

 

1.2. 아키텍처

 

  1. Serverless Application 개발자가 SAM Template 을 작성하여 CodeCommit 에 Push 합니다.
  2. CodeCommit 과 연동된 Jenkins 에서 Git Source 를 가져와 Pipeline 을 실행합니다.
  3. 실제 AWS 리소스가 배포될 계정의 권한을 위임받아 CloudFormation 을 실행합니다.
  4. CloudFormation 리소스 정보가 저장된 S3 버킷을 통해 리소스 변경사항을 확인합니다.
  5. AWS SAM Template 에 지정된 리소스가 배포됩니다.

 

 

2. AWS SAM Template

 

AWS SAM 예제 템플릿은 제 GitHub 에 올려두었습니다.
HelloWorld 를 출력하는 Lambda Function 1개를 생성하는 아주 간단한 SAM 템플릿입니다.
이번 포스팅은 SAM 리소스에 대해서는 자세히 다루지 않겠습니다.

 

 

2.1. Git Clone

 

샘플 SAM Template 코드와 Jenkinsfile 이 저장되어 있습니다.
해당 Git 리포를 Pull 하여, AWS CodeCommit 에 업로드하면 됩니다.

 

git clone https://github.com/AlarmKimKB/aws-sam-deploy-cross-account.git

 

 

3. MGMT 계정 (중앙 계정) 설정

 

Jenkins 가 실행되고 SAM 리소스의 정보가 저장될 S3 버킷이 있는 계정입니다.

 

 

3.1. S3 버킷 구성

 

가장 먼저 AWS SAM 배포 시, 리소스 정보가 저장될 S3 버킷을 중앙 계정(MGMT)에 생성합니다.
AWS SAM 에서 자동으로 S3 버킷을 생성할 수도 있으나, 버킷명을 지정하기 위해 직접 생성합니다.

 

이 때, 버전 활성화퍼블릭 액세스 차단을 잊지 마시길 바랍니다.

 

보안상 S3 버킷 정책은 넣어주는 것이 좋습니다.

DEV CloudFormation 실행 역할이 버킷에 접근할 수 있도록 다음과 같은 정책을 넣어줍니다.
이 때, DEV CloudFormation 역할은 아직 생성하지 않았기 때문에 추후 추가해줘도 무방합니다.

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "CloudFormationStackUpdate",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::<<MGMT_S3_BUCKET_NAME>>/*",
                "arn:aws:s3:::<<MGMT_S3_BUCKET_NAME>>"
            ],
            "Condition": {
                "ArnEquals": {
                    "aws:PrincipalArn": [
                         "<<DEV_ACCOUNT_CLOUDFORMATION_ROLE_ARN>>"
                    ]
                }
            }
        }
    ]
}

 

 

3.2. Jenkins IAM Role & Policy 구성

 

Jenkins 에서 실행할 AWS IAM Role 입니다.
현재 EC2 에 Jenkins 가 구성되어 있기 때문에 IAM Role 을 사용할 수 있지만,

온프레미스 Jenkins 의 경우 별도의 권한 부여 작업이 필요합니다.

 

다음과 같이 IAM Role 과 Policy 를 구성합니다.

 

IAM Role 신뢰 관계

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ec2.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

 

 

IAM Policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowAssumeRole",
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole",
                "iam:PassRole"
            ],
            "Resource": [
                "<<DEV_ACCOUNT_CLOUDFORMATION_ROLE_ARN>>"
            ]
        },
        {
            "Sid": "S3BucketAccess",
            "Effect": "Allow",
            "Action": [
                "s3:DeleteObject",
                "s3:GetObject*",
                "s3:PutObject*",
                "s3:GetBucket*",
                "s3:List*"
            ],
            "Resource": [
                "<<MGMT_S3_BUCKET_NAME>>/*",
                "<<MGMT_S3_BUCKET_NAME>>"
            ]
        }
    ]
}

 

 

4. DEV 계정 (교차 계정) 설정

 

실제 AWS SAM 템플릿에 의해 CloudFormation 이 실행되고 리소스가 배포되는 계정입니다.

 

 

4.1. IAM Role & Policy 구성

 

실제 CloudFormation 을 실행할 AWS IAM Role 입니다.
Jenkins 에서 해당 IAM Role 을 사용할 수 있도록 신뢰 관계 정책을 부여해줍니다.

 

다음과 같이 IAM Role 과 Policy 를 구성합니다.

 

IAM Role 신뢰 관계

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "CloudFormationServiceRole",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudformation.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        },
        {
            "Sid": "AllowAssumeRoleforMGMT",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::<<MGMT_ACCOUNT>>:root"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

 

 

IAM Policy

CloudFormation 을 실행할 수 있는 권한을 부여하고, SAM 템플릿 구성에 따라 필요한 권한을 부여합니다.
이후, MGMT (중앙계정) 의 S3 버킷에 AWS SAM 리소스 정보를 넣을 수 있는 권한도 부여합니다.

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "CloudFormationStackPolicy",
            "Effect": "Allow",
            "Action": [
                "cloudformation:CreateChangeSet",
                "cloudformation:DescribeChangeSet",
                "cloudformation:ExecuteChangeSet",
                "cloudformation:DeleteStack",
                "cloudformation:DescribeStackEvents",
                "cloudformation:DescribeStacks",
                "cloudformation:GetTemplate",
                "cloudformation:GetTemplateSummary",
                "cloudformation:DescribeStackResource"
            ],
            "Resource": "*"
        },
        {
            "Sid": "CloudFormationResource",
            "Effect": "Allow",
            "Action": [
                "iam:*",
                "lambda:*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "MGMTS3Allow",
            "Effect": "Allow",
            "Action": [
                "s3:Get*",
                "s3:Put*",
                "s3:ListBucket"
            ],
            "Resource": "arn:aws:s3:::<<MGMT_S3_BUCKET_NAME>>/*"
        }
    ]
}

 

 

현재까지 설정된 IAM 구성도는 다음과 같습니다.

 

 

5. Jenkins 설정

 

5.1. Utility 설치

 

Jenkins Server 에서 AWS CLI 와 SAM CLI 를 사용할 수 있도록 유틸리티를 설치합니다.
제 Jenkins Server 는 Ubuntu 20.04 버전 기준입니다.

 

AWS CLI v2 설치

# AWS CLI Version 2 다운 및 압축해제
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" \
    -o "awscliv2.zip" && unzip awscliv2.zip

# 설치
sudo ./aws/install

# 경로 설정
export PATH=/usr/local/bin:$PATH

 

SAM CLI 설치

# Unzip Util 다운로드
sudo apt install unzip

mkdir samcli && cd samcli

# Zip download
wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip

# 압축 해제
unzip aws-sam-cli-linux-x86_64.zip -d sam-installation

# Install
sudo ./sam-installation/install

# Version Check
sam --version

 

Lambda Layer 빌드를 위한 make 및 docker 설치

sudo apt update
sudo apt install make

# Docker 설치
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

 

5.2. Jenkins Plugin 설치

 

Jenkins Pipeline 을 실행시킬 때 필요한 Plug-in 은 다음과 같습니다.

 

Docker

Docker Pipeline

 

5.3. CodeCommit 연동

 

CodeCommit 에 접속을 위한 Credential 발급

 

Jenkins Credential 에 자격 증명 입력

 

Jenkins Pipeline Project 생성

 

Polling 주기 설정

Jenkins 에서는 기본적으로 CodeCommit Trigger 연동이 되지 않습니다.
특정 플러그인을 통해 Trigger 설정이 가능하나, 여기서는 Polling 방식으로 사용하겠습니다.

 

Pipeline 설정

연동할 CodeCommit 의 URL 입력 후, 앞서 기입했던 Jenkins Credential 을 선택합니다.

 

5.3. Jenkinsfile 구성

 

Jenkins Pipeline 을 실행 시키기 위한 Jenkinsfile 을 작성합니다.
이후 CodeCommit 에 Push 합니다.

 

pipeline {
  agent {
    docker {
    image 'public.ecr.aws/sam/build-python3.9:latest'
    args '-u root:root'
    }
  }
  environment {
    DEV_SAM_CONFIGFILE = 'samconfig.toml'
    DEV_SAM_TEMPLATE = 'template.yaml'
    DEV_STACK_NAME = 'cross-account-sam'
    // DEV_PIPELINE_EXECUTION_ROLE = Use Jenkins EC2 IAM Role
    DEV_CLOUDFORMATION_EXECUTION_ROLE = 'arn:aws:iam::##########:role/role-cfn-deploy-dev' // Dev Account IAM Role
    MGMT_S3_BUCKET = 's3-an2-sam-cross-account-mgmt'
    MGMT_S3_BUCKET_PREFIX = 'sam-hello-function'
    DEV_REGION = 'ap-northeast-2'
  }
  stages {

    stage('Environment Check') {
      steps {
        sh '''
          env

          echo \"# AWS Version\"
          aws --version

          echo \"# AWS STS Token Check\"
          aws sts get-caller-identity

          echo \"# SAM Version\"
          sam --version

          echo \"# Python Version\"
          python3 --version
          pip3 --version

          echo \"# Working Directory\"
          pwd
        '''
      }
    }

    stage('SAM Validation') {
      steps {
        sh '''
        echo \"# SAM Template Validation Check.\"

        sam validate --lint --template ${DEV_SAM_TEMPLATE}
        '''
      }
    }

    stage('SAM Build - DEV') {
      steps {
        sh '''
        echo \"# SAM Template Build to CloudFormation Format.\"

        sam build --template ${DEV_SAM_TEMPLATE}
        '''
        }
      }

    stage('SAM Deploy - DEV') {
      steps {
        sh '''
        echo \"# Get AWS STS Credential from Cross Account.\"
        CREDENTIAL=\$(aws sts assume-role \
          --duration-seconds 900 \
          --role-arn ${DEV_CLOUDFORMATION_EXECUTION_ROLE} \
          --role-session-name SAMSession \
          --output text \
          --query \'Credentials.[AccessKeyId,SecretAccessKey,SessionToken,Expiration]\')

        export AWS_ACCESS_KEY_ID=$(echo $CREDENTIAL | awk '{print $1}')
        export AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIAL | awk '{print $2}')
        export AWS_SESSION_TOKEN=$(echo $CREDENTIAL | awk '{print $3}')
        export SESSION_EXPIRATION=$(echo $CREDENTIAL | awk '{print $4}')

        aws sts get-caller-identity

        echo \"# SAM Template Deploy to CloudFormation Stacks.\"

        sam deploy \
          --config-file ${DEV_SAM_CONFIGFILE} \
          --template ${DEV_SAM_TEMPLATE} \
          --region ${DEV_REGION} \
          --s3-bucket ${MGMT_S3_BUCKET} \
          --s3-prefix ${MGMT_S3_BUCKET_PREFIX}
        '''
      }
    }
  }
}

 

5.3. Jenkins Pipeline 확인

 

Jenkins 에서 Build 및 Deploy 되는 과정을 확인합니다.

 

로그를 확인해봅니다.

 

리소스가 배포된 DEV 계정 (교차 계정) 에 접속하여 리소스를 확인해봅니다.

 

 


 

대규모 조직에서 AWS 를 사용하면, Assume Role 을 사용할 기회가 많이 있습니다.

Assume Role 을 이해하고 있다면, 이렇게 다른 계정에 리소스를 배포하고 접근하는 법을 쉽게 찾아낼 수 있을 것입니다.

Contents

포스팅 주소를 복사했습니다