[DevOps] python boto3 s3 upload 작성

안녕하세요? 정리하는 개발자 워니즈입니다. 이번시간에는 간단한 python script에 대해서 소개를 하려고 합니다. 파이썬은 필자가 관심도 많고 즐겨서 사용하는 언어입니다. 그러다보니 손쉽게 활용해서 무엇인가를 만들때 자주 사용하고 있습니다.

next.js를 빌드하고나서 나오는 결과물(build asset)에 대해서 s3에 upload를 하고 그것을 CDN(ex. cloudfront)과 같은 서비스를 연결하여 서빙을 하는 구조로 만들었습니다.

1. 빌드 배포 구성

필자가 사전에 구성하는 프로임워크는 Next.js로 기존에 직접 구성을 해보거나, 개발을 해본적은 없었습니다. 하지만 React.js를 이용해서 개발을 진행해봤었고, 소스상으로는 큰 차이는 보이지가 않았었습니다.

필자는 S3 정적 웹 호스팅을 통해서 웹을 작성한 적이 있었습니다.

필자가 현재 고민하는 구성에 대해서 또다시 고민을 하고 있다니.. 위의 포스팅을 보고나서, 아차! 저런질문을 했었고 여저런 결론을 내렸구나 싶었습니다.

  • npm start(Dev server)로 구성할지
  • S3 web hosting을 사용할지
  • nginx로 web serving을 할지

위의 선택지가 있었고, 이번에는 간단한 웹페이지를 구성하는 형식이라 npm start로 구성을 하기로했습니다.

위와 같이 구성을 할생각이고, 필자 같은 경우는 kubernetes를 직접 클러스터링으로 구성하였고, 트래픽의 흐름은 다음과 같습니다.

  • Route 53(DNS Resolving)
  • Akamai CDN edge -> LB(Origin)
  • k8s : Ingress Layer -> Application SVC -> Application POD
  • build asset(css, js, images) : Cloudfront -> S3 reference

간단한 웹페이지(서비스 티저용)이지만, S3 웹호스팅으로 구성을 할까 하다가 dev server 구성으로 한번 해보고 어떤 부분이 구성상에 더 좋은 지 경험을 해보고 싶었습니다.

오늘 소개하고자 하는 부분은 위의 영역중에서도 build asset을 올려둘 S3 upload와 관련된 내용이고, 이부분을 어떤식으로 구성을 했는지에 대해서 정리를 하려고 합니다.

2. s3 upload script 작성

application에 대한 dockerfile 작성 시, next.js는 build를 수행하고 나면 특정 폴더에 asset들이 export됩니다.
next build를 수행하게 되면 결과물들이 .next폴더에 생성되게 됩니다.

.next/static/chunks/pages
.next/static/media
.next/static/css

필자는 이중에서도 위와 같이 .next/static영역에 출력이 되는 부분을 S3로 업로드를 하고 싶었습니다. 그래서 python을 활용하여 s3 upload를 할 수 있는 스크립트를 작성하기로 했습니다.

import boto3
from botocore.client import Config
import os
import sys

access_key = 'xxxxxxxxxxxxxx'
secret_key = 'xxxxxxxxxxxxxx'
s3_host = 'https://test-dev.com'
source = '.next/static'
target = '/assets/_next/static'
rootfolder="teaser"
bucket = 'test-dev'

config = Config(signature_version='s3',
        s3={'addressing_style': 'path'})

#connect to S3
s3 = boto3.client('s3',
        aws_access_key_id = access_key,
        aws_secret_access_key = secret_key,
        endpoint_url = s3_host,
        config=config)

위의 코드는 S3에 연결하는 내용입니다.

필자는 boto3패키지를 이용해서 S3에 간단하게 연결을 하기로했고, 여기서 가장 중요한것은 S3로 접근하기 위한 Access key, Secret key 입니다. 이부분은 AWS console상에서 확인 할 수 있습니다.

def s3upload(appName):
    uploadFileNames = []
    for root, dirs, files in os.walk(appName + "/" + source, topdown=False):
       for name in files:
          fname=os.path.join(root, name)
          print fname
          uploadFileNames.append(fname)

    # start uploading
    for filename in uploadFileNames:
        mimetype = mimetypes.guess_type(filename)
        sourcepath = filename
        destpath = filename
        targetpath = sourcepath.replace(source, target).replace("./", rootfolder)
        print 'Uploading %s to VERDA VOS bucket %s, mimeType : %s' % \
              (sourcepath, bucket, mimetype[0])
        s3.upload_file(sourcepath, bucket, targetpath, ExtraArgs={'ACL': 'public-read', 'ContentType': mimetype[0]})

위의 코드는 S3로 실제로 업로드를 하는 내용의 코드 입니다. S3는 object storage이기 때문에 요소 한개한개를 개별로 올려줘야 합니다. 위의 코드는 굉장히 심플합니다.

for root, dirs, files in os.walk(appName + "/" + source, topdown=False):

특정 경로 ( ./.next/static)과 같은 경로에 빌드의 산출물이 출력이 됩니다. 그곳에 있는 모든 파일들을 하나씩 순차적으로 순회하면서 특정 변수 uploadFileNames에다가 해당 내용들을 담습니다.

for filename in uploadFileNames:

그리고 해당 변수에서 하나씩 path들을 꺼내서 실제 S3로 업로드를 하는 method를 호출합니다.

s3.upload_file(sourcepath, bucket, targetpath, ExtraArgs={'ACL': 'public-read', 'ContentType': mimetype[0]})

3. CDN 연결

S3에 정상적으로 업로드가 됐으면, 이제 이러한 파일들을 빠르게 엣지를 연결하여 서빙 해줄 수 있도록 CDN을 연결합니다.

사내의 private cloud에서는 손쉽게 S3와 CDN을 붙일 수 있는 서비스를 제공해주고 있습니다. 따라서 이 섹션에서는 별도로 정리할 내용이 없으나, S3 앞단에 CDN을 연결한다는 것을 알아 둘 필요가 있습니다.

  • build asset(css, js, images) : Cloudfront -> S3 reference

이제 어플리케이션에서서 요청하는 resource(css, js 등)은 cloudfront의 URL을 참조하게 되고 클라이언트 입장에서는 빠르게 엣지에서 리소스들을 받기 때문에 어플리케이션 퍼포먼스가 좋아지고 자연스럽게 유저에게도 만족도를 줄 수 있습니다.

4. application path 수정

next.js 에서는 asset prefix라는 것을 제공해주고 있습니다. 따라서 위에 올린 S3 앞단의 CDN 도메인으로 호출을 할 수 있도록 적절하게 어플리케이션에서는 path를 수정해줘야 합니다.

CDN Support with Asset Prefix

위의 내용에 따라 runtime시에 argument를 전달하게 되고 어플리케이션에서는 전달받은 argument를 기반으로 셋팅하게 됩니다.

const path = require('path')
const isProduction = process.env.NODE_ENV === 'production'

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  publicRuntimeConfig: {
    DOSI_CDN: process.env.DOSI_CDN
  },
  sassOptions: {
    includePaths: [path.join(__dirname, 'styles')],
    prependData: `@import "src/styles/mixins";`
  },
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack']
    })

    /**
     * next.js doesn't know alias, so extends webpack config
     * {@link https://webpack.js.org/configuration/resolve}
     */
    config.resolve.alias['~'] = path.join(__dirname, 'src')

    return config
  },
  assetPrefix: isProduction ? `${process.env.S3_CDN}/teaser/assets` : ''
}

module.exports = nextConfig

필자는 S3에 업로드 할 당시 teaser/assets/_next/static라는 경로로 올렸고 앞의 S3_CDN 도메인을 붙여서 엣지로부터 리소스를 가져오도록 구성했습니다.

 assetPrefix: isProduction ? `${process.env.S3_CDN}/teaser/assets` : ''

위의 부분에서 어플리케이션 runtime시에 할당받은 변수를 넣어줌으로써 CDN url로 path를 수정하게 됩니다.

5. 마치며..

S3 업로드와 관련된 스크립트 작성과 더불어 next.js 빌드는 어떤식으로 되는지 그 산출물이 어떤형태로 떨어지는지에 대해서 정리를 해보았습니다. DevOps 업무를 하면서 기존에는 주로 Back-end의 어플리케이션에 대해서만 CI / CD를 구성해줬었는데, Front-end에 대해서도 구성을 하면서 많은 부분을 배울 수 있는 기회였던 것 같습니다.

6. 참조

Python boto3 복사, 업로드, 동기화 유용한 기능

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다