이전 시간에는 Netlify로 테스트 배포해본 반면
이번 시간에는 Github Actions를 활용하여 AWS S3로 화면단을 자동 배포할 예정이다.

Amazon Simple Storage Service의 약자로써 클라우드 기반 객체 스토리지 서비스를 제공한다. 클라우드란 인터넷을 통해 접근 가능한 서버, 저장 공간, 소프트웨어, 데이터베이스 등의 IT 자원을 제공하는 서비스를 의미하며 객체 스토리지란 객체 형식으로 데이터를 저장하고 관리하는 기술을 말한다. 이 의미를 종합하면, 클라우드 환경에서, 즉 인터넷을 통해 접근 가능한 서버로 key-value pair 형식 or 메타 데이터의 객체 형식으로 데이터를 저장하고 관리하는 서비스를 제공한다는 것이다. S3는 현재 파일, 데이터 및 다양한 유형의 미디어 등을 저장하고 관리하는 데 사용되고 있다.
사전에 AWS 서비스에 회원가입을 해야 진행할 수 있다. 신용카드 등록이 있어 약간의 불안감을 가지고 진행했지만?! google에 'AWS 프리티어'라 검색하면 해당 AWS 서비스에서 각 서비스에서 프리티어 무료 사용한도에 대해 나와 있으니, 허용 한도 내에서 진행하면 충분히 무료로 사용할 수 있다는 생각이 들었다.
개인적으로는 처음 사용하다보니 서비스를 찾는데서부터 약간 애를 먹었는데
AWS는 클라우드 기반 웹서비스를 제공하기에 여러 서비스를 제공하고 있고
AWS S3가 아닌 AWS의 다른 서비스라도 기본적으로 콘솔에서 관리하고 있다.
콘솔은 AWS 사이트에서 로그인 후 아래 보이는 것처럼 "콘솔에 로그인" 버튼을 클릭한다.

콘솔에 로그인 버튼을 클릭하면 Root 사용자로 로그인할 것인지 I AM 사용자로 로그인할 것인지 선택하고 로그인을 진행한다.
AWS Root 사용자는 회원가입 시 만든 계정으로서 모든 AWS 권한을 갖고 있는 사용자를 의미한다.
반면 AWS I AM 사용자는 AWS 관리를 제외한 서비스 관리용 계정이 필요한데, 이러한 계정이 바로 I AM 사용자이다.
현재는 bucket을 생성하기 위해 S3 서비스에 접근해야 하므로 root 계정으로 로그인했다.
※ Root 계정으로 로그인시 MFA 등록은 필수이며, 이 MFA는 인증 수단을 의미하며 등록하지 않았다면 "콘솔에 로그인"시 MFA 등록화면으로 넘어간다.
이렇게 로그인을 진행하게 되면 다음과 같이 콘솔화면으로 이동할 수 있는데 이 때 AWS S3 서비스로 이동할 수 있는 2가지 방법이 있다.
ㄱ) 메뉴로 이동하기
스토리지에서 S3로 이동할 수 있다.
ㄴ) 검색으로 이동하기
/이후 service 이름을 입력해서(예시) /s3) S3로 이동할 수 있다.
S3로 배포하기 위해서는 우선 버킷을 만들어야 한다.
버킷이란 개념을 이해하기에 앞서 S3는 기본적으로 Object(객체)와 Bucket(버킷)으로 구성되어 있다는 것을 알 필요가 있다.
Object는 AWS S3에 저장되는 데이터 하나하나를 Object라 부르며 데이터와 메타데이터를 구성하고 있는 저장 단위이다.
Bucket은 이러한 Object를 저장하고 관리하는 역할을 한다.
즉 Object를 파일로 생각하면, Bucket은 연관된 객체들을 grouping한 최상위 디렉토리로 생각할 수 있다.
참조 사이트
S3 개념 - Inpa dev
이러한 Bucket을 구성하기 위해서는 이전 단계에서 S3로 이동한 후 다음과 같은 화면을 볼 수 있다.

하나씩 살펴보면
AWS 리전
호스팅 되는 지역, 즉 서버 공간 대여 지역을 의미한다.
다시말해 AWS 인프라를 지리적으로 나누어 배포하겠다는 것을 뜻한다.
과거에는 버킷 만들기에서 직접 select box로 수정할 수 있었던 것 같으나,
현재는 오른쪽 상단에서 select box로 수정할 수 있는 것으로 보인다.
사용자와 리전(region)이 가까울수록 네트워크 지연을 최소화할 수 있다.
기본적으로 내가 있는 곳, 즉 아시아 태평양(서울)에 맞추었다.
버킷 이름
bucket 이름을 설정하는 란으로 버킷이름에 따라 url이 생성된다.
예를 들어 bovo라는 이름으로 명명하면, http://bovo.s3.amazonaws.com/profile.png 로 url이 생성된다.
url은 http://[bucket-name].[region].[amazonaws.com]/[objectkey.file] 로 명명된다.
기존 버킷에서 설정 복사
기존에 만들어두었던 bucket의 설정을 그대로 따오고 싶다면, 사용할 수 있는 기능이다. 하지만 만들어둔 bucket이 없기에 넘어갔다.
객체 소유권에 관련된 ACL은 AWS의 다른 계정으로 해당 파일에 대해 읽기와 쓰기 권한을 부여할 것인지 권한을 관리하는 설정이다.
AWS 공식 문서를 살펴보면 최근에는 ACL 비활성화를 권장하고 개별 객체에 대해 각각 제어하는 것이라면 ACL을 활성화하라고 설명되어 있다.
따라서 "ACL 비활성화"를 선택하고 스크롤을 내린다.
public access는 bucket에 저장된 객체에 대해 외부(인터넷 브라우저, 외부 프로그램)에서 인증되지 않은 접근을 막겠다는 설정이다.
물론 최상위의 모든 퍼블릭 액세스를 차단하겠다는 설정을 체크하면 어떠한 접근도 막겠다는 의미이다.
새 ACL(액세스 제어 목록)을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단
해당 부분을 체크한다면 앞으로 해당 버킷을 통해 공개설정을 해둔 파일을 업로드하려 한다면 업로드가 거절된다.
임의의 ACL(액세스 제어 목록)을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단
설사 첫번째 "새 ACL ~ 액세스 차단"부분이 해제하였더라도, ACL이 허용되든 말든, 비공개로 인식해서 public access를 차단한다는 의미이다.
새 퍼블릭 버킷 또는 액세스 지점 정책을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단
AWS S3에 새로운 Bucket Policy가 적용된다고 하더라도 기존 규정을 유지하여 public access를 차단하겠다는 의미이다.
임의의 퍼블릭 버킷 또는 액세스 지점 정책을 통해 부여된 버킷 및 객체에 대한 퍼블릭 및 교차 계정 액세스 차단
임의의 bucket policy(기존 규정, 새 규정)을 통한 public access 및 다른 AWS 계정을 통한 접근을 차단한다.
나는 처음에 4번째 임의의 ~ 퍼블릭 및 교차 계정 액세스 차단만 있으면 모든 public access 차단이라는 최상위 선택사항이 필요없지 않나라는 생각을 했다. 그러나 조사하다보니 각각의 체크 항목들의 대상이 미묘하게 다르다는 것을 알 수 있다. 1~2번째 항목은 ACL에 대한 public access를 설정하는 것이고, 3~4번째는 bucket policy에 대한 public access를 설정하는 것으로 만일 4번만 체크할 경우 다른 팀원이 어떤 파일을 S3에 ACL을 활용하여 업로드하는 것을 막지 못한다는 것을 gemini를 통해 알게되었다.
다른 블로그의 글을 읽어보니 실무에서는 일반적으로 보안상 모든 public access를 차단하고 CloudFront를 활용하는 것으로 보였다.
이것이 코드와 관련된 사항이라면 모든 public access를 차단하는 것이 올바른 방법이나, 상황에 따라 다르게 설정해야 할 수 있다. bovo 프로젝트의 경우 Netlify로 테스트 배포를 진행했으니 만일 S3 버킷에 업로드한 파일이 정적 리소스(이미지)라면 2가지 방법이 있다. 1번째는 기존에 모든 public access를 차단하고 CloudFront로 해당 리소스에 대한 url을 받아 사용하는 방법, 2번째는 모든 public access를 차단하는 것이 아닌 3. 새 퍼블릭 버킷 또는 액세스 지점 정책을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단 4. 임의의 퍼블릭 버킷 또는 액세스 지점 정책을 통해 부여된 버킷 및 객체에 대한 퍼블릭 및 교차 계정 액세스 차단 체크를 해제하여 외부로의 접근을 차단하지 않는다.
설정은 나중에 변경할 수 있으니 우선 여기서는 모든 public access를 차단 하고 넘어갔다.
버킷의 버전 관리는 파일을 계속 수정함에 따라 업데이트 하더라도 업데이트 이전의 내용을 복구할 수 있다. 이렇게 복구할 수 있는 이유는 파일들을 버전별로 관리하기 때문이다. 이 때문에 더 많은 저장 공간을 사용하게 되므로 발생 비용이 증가할 수 있다.
따라서 이 부분은 '비활성화'를 선택하고 넘어갔다
여러 Bucket을 생성하다 보면 버킷을 그룹화하기 위해 이 태그 기능을 사용할 수 있다. AWS 문서를 살펴보면 최대 10개까지 가능하다고 명시되어 있으며, 독특한 것은 태그 입력시 key와 value를 입력해야 한다.
나는 초기 사용자이다보니 태그를 사용할 필요성은 없었으나 비용 구분이나, 사용 목적에 따라 태그를 추가하여 grouping할 수 있겠다는 생각이 들었다.
이 설정 해당 버킷에 새로 업로드하는 모든 객체에 대해 자동으로 암호화하여 저장하는 기능에 해당한다.
S3는 데이터를 저장하기 전에 암호화하고 데이터를 가져갈 때 자동으로 복호화한다.
암호화 유형에는 3가지가 있고 1번째 유형을 제외한 나머지 옵션은 비용이 발생한다.
그 아래 추가적인 옵션을 보면 "고급 설정"이 있는데 이 고급 설정은 객체 잠금, 즉 보안과 관련된 사항으로 객체의 삭제나 변경을 방지하는 설정이다. 이 부분은 건드리지 않고 지나갔다.
위와 같이 설정하고, "버킷 만들기" 버튼을 클릭하면 버킷이 생성되며 다음 화면과 같이 확인할 수 있다.

다만 이 버킷에서 정적 웹사이트 호스팅을 바로 할 수 있는 것이 아니라 추가적인 설정이 필요하다. 해당 버킷을 클릭하여 [속성] 탭 - [정적 웹사이트 호스팅] 메뉴의 '편집'을 클릭한다.
기본적인 설정은 다음과 같이 진행하였다.
위에서 정적 웹사이트 호스팅을 활성화하였다면 해당 버킷의 속성탭의 정적 웹사이트 호스팅 부분에 버킷 웹 사이트 엔드포인트가 생성되었을 것이다.
해당 엔드포인트를 클릭해보면

403 Forbidden 페이지가 보이게 될 것이다.
이는 우리가 앞서 bucket을 생성할 때 모든 public access를 차단하였기에 발생된 결과이다.
그렇다면 모든 public access 차단 해제가 필수적인 것이 아닌가? 라고 할 수 있겠지만, 사실 여기서 외부에서 해당 사이트로 접근하기 위한 선택지가 2개로 나누어진다.
1번째는 public access 차단을 해제하거나
2번째는 버킷으로 호스팅 웹사이트 url로 직접 요청하는 것이 아닌 cloudFront를 활용하여 우회적으로 진입하게 하는 방법이다.
사실 보안상 2번째가 적절한 방법이나, 현재의 프로젝트는 어디까지나 가상의 서비스를 목저으로 둔 프로젝트이기에 public access 차단을 해제하는 1번째 방법으로 진행할 것이다.
해당 페이지의 "퍼블릭 엑세스 차단(버킷 설정)" 메뉴의 "편집" 버튼을 클릭한다.
아까 모든 퍼블릭 액세스 차단 체크를 해두었던 것을 해제하고 "변경 사항 저장" 버튼을 클릭한다.
버튼을 클릭했다면 위와 같은 모달 창이 나타나고 확인을 입력하고 "확인" 버튼을 클릭한다.
이전 4-1단계에서 퍼블릭 액세스 차단을 해제하기 위해 진입했던 버킷의 [권한]탭에서 퍼블릭 액세스 아래쪽에 버킷 정책을 설정하는 부분이 있다.

버킷 정책을 활용하면 읽기 권한을 부여할 수 있다.
해당 관련 설명은 아래 AWS 공식 문서를 통해 확인할 수 있다.
버킷 정책에 추가할 코드는 위의 공식문서를 통해 확인할 수 있으며 다음과 같다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::Bucket-Name/*"
]
}
]
}
따라서 버킷 정책 메뉴에서 편집을 클릭한 후 위 코드를 입력한다.

단, 반드시 "변경 사항 저장" 버튼을 클릭하기 전 위 JSON 코드에서 Bucket-Name 부분을 반드시 본인 bucket 이름으로 수정한 후 변경 사항 저장 버튼을 클릭한다.
위와 같이 설정해준뒤 버킷의 속성에서 다시 버킷 웹 사이트 엔드포인트로 진입하게 되면

이전과는 다르게 403 Forbiden이 아닌 404 Not Found로 변경된다.
즉, access는 가능하지만 현재 파일을 업로드하지 않아 파일을 읽을 수 없는 상황인 것이다.
AWS S3에 직접 파일을 업로드하는 방식도 있지만, 그보다는 Github Action을 활용하여 CI/CD를 구축을 통해 AWS S3에 자동 배포하도록 설정할 수 있다.
이를 위해서는 우선 Github Action을 위해 yaml파일을 구성해야 한다.
yaml 파일은 배포하려는 repository의 Action탭에서 확인할 수 있다.

여러 구성이 있겠지만 그 중에서도 Node JS 모듈 위로 파일이 돌아가기 때문에 Continuous Integration의 NodeJS를 선택해주어야 한다.

여기서 Configure를 클릭하면 기본적인 Workflow가 구성된 yaml파일을 볼 수 있다.

# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm test
잠시 yaml 파일 구성을 살펴보면
여기서 변경해 주어야 할 부분은 run의 npm ci를 npm i로 수정해주었다.
초기 설정시에는 npm ci로 설정하는 것이 올바르나 어느정도 package.json이 일치되었을 것이라 생각하고 npm i로 설정해주었다.
또한 여러 node 버전에서 workflow를 진행할 필요가 없고(build 시간이 증가됨), 프론트엔드 팀원들이 가장 많이 사용하는 node 버전인 22.x를 제외한 모든 node 버전을 삭제해주었다.
또는 프로젝트의 root directory에 .github/workflows/node.js.yml 파일을 생성한 후 코드를 복사 및 수정한 후 github repository에 push하여도 가능하다.
아직 yaml 파일이 완성된 것은 아니다.
위의 yaml 파일 구성을 보면 하위 step들 중 마지막 npm test를 마치면, 해당 소스 코드를 이전에 생성했던 AWS S3 버킷에 전달해주어야 하기 때문이다.
https://github.com/awact/s3-action 해당 사이트에 접속하면 S3에 배포를 위한 S3-action 레퍼런스를 찾아볼 수 있다.
name: Sync S3 Bucket
on: push
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: awact/s3-action@master
with:
args: --acl public-read --follow-symlinks --delete
env:
SOURCE_DIR: './public'
AWS_REGION: 'us-east-1'
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
위의 것을 다 복사한다기 보다는
- uses: awact/s3-action@master
with:
args: --acl public-read --follow-symlinks --delete
env:
SOURCE_DIR: './public'
AWS_REGION: 'us-east-1'
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
이 부분이 필요하다.
이 부분을 가져와서 방금 구성한 yaml 파일 아래쪽에 붙여 넣어준다.
그러면 아래와 같이 yaml 파일을 구성할 수 있다.
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm i
- run: npm run build --if-present
- run: npm test
- uses: awact/s3-action@master
with:
args: --acl public-read --follow-symlinks --delete
env:
SOURCE_DIR: './public'
AWS_REGION: 'us-east-1'
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
위 yaml 중 env 구성을 살펴보면
SOURCE_DIR은 build 후 build 파일이 어느 directory에 있는지 설정하는 것이다.
따라서 SOURCE_DIR: './bovo_client/dist'로 설정해주어야 했다.
../../bovo_client/dist가 아닐까 생각했었는데
gihub Actions에서 기본 작업 directory는 Github 저장소의 root이다.
이에 따라 yaml 파일의 위치와 상관없이 action들이 uses에 의해 실행될 때 working-directory에 대한 설정이 없다면 기본 root directory에서 수행된다.
그다음으로 AWS_REGION은 이미 S3 버킷에서 설정했듯 ap-northeast-2 (서울)로 설정해준다.
다음으로 AWS_S3_BUCKET, ACCESS_KEY_ID, SECRET_ACCESS_KEY 와 같은 환경 변수들이 있는데, 이것은 yaml 파일에서 직접 쓴다기 보다는 나중에 github의 settings에서 설정해주어야 한다.
다만, 환경변수들을 설정하기 전에 I AM 설정을 해주어야 한다.
설정 전에 I AM에 대해 잠시 살펴보면
AWS 리소스에 대한 액세스를 안전하게 제어할 수 있는 웹 서비스이다.
현재 버킷을 생성할 때는 모든 AWS 서비스 및 리소스에 대한 접근 권한이 있는 root 계정을 사용했지만, 우리 프로젝트 웹사이트에 접근하는 사람이 모든 권한을 가지고 있으면 안 되기 때문에 I AM 유저를 사용해서 root 계정이 부여한 권한만을 사용할 수 있도록 해야 한다.
콘솔에서 IAM을 검색하여 IAM 대시보드로 이동한다.
이후 왼쪽에 보이는 [사용자] 메뉴로 들어간 후 오른쪽 상단에 위치한 "사용자 생성"버튼을 클릭해준다.
단계를 거쳤다면 위 이미지와 같은 설정 화면이 보일 것이고 사용자 이름을 규칙에 따라 임의로 설정해준다.
다음 단계는 I AM 유저에 대한 권한을 설정하는 단계이다.
이전 I AM 유저 설정에서 설정하였던 사항을 검토하는 단계로 별다른 이상이 없으면 "사용자 생성" 버튼을 클릭한다.
위와 같은 단계를 거쳤다면 I AM 대시보드에 방금 전에 생성한 I AM 유저가 보일 것이고 이를 클릭한다.

① 해당 I AM 유저의 정보에서 [액세스 키 만들기] 선택

② 그 다음으로 [액세스 키 모범 사례 및 대안] 유형을 선택해야 하는데 이 중 어느 것도 해당되지 않으므로 "기타"를 선택하고 다음 단계를 진행한다.

③ 그 다음으로 설명 태그 설정인데 이 액세스 키를 어떠한 목적으로 사용할 것인지에 대한 설명을 붙이라는 의미인데 이 부분은 생략하고 바로 "액세스 키 만들기" 버튼을 클릭하였다.

마지막으로 액세스 키 검색 부분인데

유의해야할 점은 .csv 파일 다운로드하지 않으면 해당 액세스 키와 비밀 액세스 키를 다시 확인할 수 없다는 점이다.
이 과정에서 얻은 액세스 키는 yaml 파일의 secrets.AWS_ACCESS_KEY_ID에 해당하고 비밀 액세스 키는 secrets.AWS_SECRET_ACCESS_KEY에 해당한다.
③ 이제 환경 변수 입력 란이 나온다.
Name에는 각각 AWS_S3_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 넣어주고 그에 맞는 secret을 넣어주어야 한다.
AWS_S3_BUCKET은 버킷을 생성할때 넣어주었던 이름을 넣어준다.
④ 그럼 위 이미지처럼 3가지의 secret 키를 생성해야 한다.
⑤ 그런 다음 Github의 action으로 돌아오면 fail이 떠 있을 것이다. 이는 환경변수가 설정되지 않고 진행됐기 때문이다. 따라서 Re-run jobs의 Re-run all jobs 버튼을 클릭한다.
Github repo의 Actions탭으로 들어가면 PR이후 위 이미지와 같이 build가 돌아가는 것을 확인할 수 있고, 성공 이후 AWS S3 서비스의 해당 버킷으로 들어가보면 아래 이미지처럼 업로드가 되어 있는것을 확인할 수 있다.
그렇다면 해당 버킷은 어떤 주소에 올라가 있는가?
그것은 이전 단계에서 확인했든 버킷 웹사이트 엔드 포인트가 해당 주소인 것이다.