목차

  1. 시작하며
  2. 문제점들

    2.1 환경변수 일원화

    2.2 환경 변수를 만들기까지의 과정

  3. 쉘 스크립트를 만들어보자

  4. CLI를 만들어보자

  5. 마무리



1. 시작하며

안녕하세요 common resource 팀의 카인입니다. common resource 팀은 개발자들의 일상적인 작업을 돕는 여러 도구들을 지속적으로 개발하고 있습니다.

우리는 프로젝트를 진행하면서 환경 변수를 다루는 상황에 직면하게 됩니다. 지금까지 쏘카의 프론트엔드 조직에서는 AWS Amplify environment, GitHub secrets 등 상황에 맞는 적절한 도구를 선택하여 환경 변수를 관리하고 있었습니다.

그러나 서비스의 종류가 늘어나면서 프로젝트의 수가 증가하고, 각 팀에서 자율적으로 관리되던 환경 변수들에 대해 여러 가지 불편함이 생겨나기 시작했습니다.

2. 문제점들

  • GitHub Secrets로 환경 변수를 관리할 때는 값을 다시 확인하기 어렵습니다.
    • 저장 후, 값을 다시 확인하려고 해도 비어 있는 상태로 표시되며 업데이트만 가능합니다.
      github-secret.png
  • 환경 변수가 파편화되어 관리가 어렵습니다.
    • 문서를 통해서 어떻게 환경 변수를 관리하고 있는지 설명해 주어야 했습니다. env-explain.png
  • 프로젝트 수가 많아지면서, 프로젝트 설정에도 비효율이 발생하고 있었습니다.

2.1. 환경 변수 일원화

프론트엔드 조직에서는 AWS의 다양한 서비스들을 적극적으로 사용하고 있습니다. 따라서 IAM Role을 통해 서비스별로 환경 변수를 쉽게 관리하고 세부적인 접근 권한을 설정할 수 있는 AWS Secrets Manager를 환경 변수 관리 도구로 채택했습니다.

2.2. 환경 변수를 만들기까지의 과정

환경 변수 관리 도구를 AWS Secrets Manager로 통일하였지만, 이를 사용할 때는 여전히 불편한 점이 있었습니다. 새로운 프로젝트를 진행하다 보면 한 번에 모든 환경 변수를 한 번에 등록하는 것이 아니라 필요할 때마다 작업을 하는 사람이 추가하게 됩니다. 예를 들어 제가 지도 관련한 작업을 할 때 이와 관련된 환경변수를 AWS Secrets Manager에 추가하게 됩니다. 다음으로 팀에 Secrets Manager를 업데이트했다고 알리고, 팀은 Secrets Manager에서 해당 값을 Secrets Manager에서 확인하여 .env 파일 형식에 맞게 복사/붙여넣기 해야 했습니다. 이 과정에서 휴먼 에러가 날 수 있으며, 시간 또한 소요가 되었으며 이러한 일련의 과정들이 매우 번거롭게 느껴졌습니다. 이러한 과정들을 자동화해 .env 파일을 손쉽게 생성하는 방법을 고민하게 되었습니다. process.png

3. 쉘 스크립트를 만들어보자

이 문제를 빠르게 해결하고자, 쉘 스크립트를 작성하여 AWS Secrets Manager의 값을 가져와 .env파일 형식으로 변환했습니다. 별도의 설정 없이 바로 실행할 수 있어 컴파일이 필요 없는 쉘 스크립트 방식을 채택했습니다. 쉘 스크립트 일부 예시는 다음과 같습니다.

# aws-vault를 활용해서 AWS Secrets Manager의 값을 가져옵니다.
rawSecrets=$(aws-vault exec <profile> -- aws secretsmasnager get-secret-vault --secret-id <secret-id> --query SecretString --output text)
# 가져온 값들윽 key=value형태로 파싱합니다.
formattedSecrets=$(echo $rawSecrets | jq -r 'to_entries
  | map("\(.key)=\(.value)")
  | .[]')

3.1 해당 방식의 문제점

그러나 이 방식은 각자의 레포지토리에 해당 쉘 스크립트를 복사해 넣어야 했고, package.json에 해당 스크립트를 실행하는 명령어를 추가해야 했습니다.

// package.json
{
  "download-env": "chmod +x ./scripts/download-env.sh && ./scripts/download-env.sh"
}

또한 쉘 스크립트로 작성된 만큼 문서를 통해 해당 코드와 과정에 대한 설명을 자세히 해주었지만, 다른 팀원들에게 익숙한 언어가 아니기 때문에 유지 보수하기 어렵다는 단점이 있었습니다. shell-explain.png

몇 줄 안 되는 쉘 스크립트로 사람이 계속 환경 변수의 값을 확인하고 .env 형태에 맞게 변환해 주는 번거로움은 해결했지만 위와 같은 새로운 문제들이 발생하게 되었습니다.

4. CLI를 만들어보자

위 문제들을 해결하기 위해 고민하던 중 마땅한 해결책이 쉽게 떠오르지 않아 고민하던 부분을 적어보기로 했습니다.

  1. 쉘 스크립트가 러닝 커브가 있다.
  2. 해당 쉘 파일이 모든 레포지토리에 있어야 한다.

문제를 적고 나니 해결 방법이 조금씩 떠오르기 시작했습니다.

  1. 쉘 스크립트를 JavaScript 코드로 변경한다.
  2. 원격 저장소에 코드를 올리고 원격 저장소의 코드를 Node.js 환경에서 실행한다.

예를 들어 보통 Next.js 앱을 만들 때 npx create-next-app 명령어를 실행합니다. 이후 CLI를 통해 TypeScript 사용 여부, Tailwind CSS 적용 여부 등 여러 설정을 선택한 후, Next.js 앱이 생성됩니다. 이처럼 npx를 활용한다면 원격 저장소에 있는 코드를 로컬 환경에서 실행하고, CLI를 통해 친숙한 인터페이스로 원하는 결과물을 만들어낼 수 있을 것 같습니다. 이를 구현하기 위해 CommanderPrompts 라이브러리의 도움을 받았으며, 간략한 코드는 아래와 같습니다.

import { Command } from 'commander'
import prompts from 'prompts'

// Commander라이브러리를 통해 사용자들에게 CLI를 제공할 수 있습니다.
const downloadSecret = new Command()
  .name('download-secret')
  .description('입력한 secret-manager를 읽어 .env.local에 저장합니다.')
  .argument(
    '[secret-manager]',
    `다운받을 시크릿매니저를 입력해주세요.`
  )
  .option(
    '--profile <secrets-manager-profile>',
    'secrets manager profile입니다.',
  )
  .option(
    '--move <path>',
    '현재 경로가 아닌 다른 경로에서 시크릿매니저를 다운받습니다.',
  )
  .action(async function createEnvFile(secretManager, options) {
    // 사용자가 입력한 option들을 기반으로 secrets manager의 값을 받아와 .env.local을 만듭니다.
    // prompts를 통해서 이미 .env가 존재한다면 덮어쓸지, 업데이트를 할지 선택합니다.
    prompts(
       ...
    )
  })

// 완성된 커맨드
Options:
-v, --version 버전을 확인합니다.
-h, --help display help for command
Commands:
download-secret [options] [secrets-manager] 입력한 secret-manager를 읽어 .env.local에 저장합니다.
help [command] display help for command

// 옵션들
--profile [secrets-manager-profile] default aws dev profile이 아닌 다른 profile을 사용하고 싶은 경우 사용할 수 있습니다.
--move [path] 현재 working directory의 위치가 아닌 아닌 path에 시크릿매니저를 다운받습니다.

이제 npx <라이브러리 이름> download-secret을 하게 된다면 ~/.aws/config에 sso 설정이 있는 사람은 sso 인증을, mfa 설정이 있는 사람은 mfa 인증을 하게 됩니다. 그다음 해당 패키지가 AWS Secrets manager를 읽고 자동으로 .env를 만들어주게 됩니다.

env-result.png

최종 결과물 이미지


5. 마무리

이제는 단 한 줄의 명령어를 통해 환경 변수를 담고 있는 파일을 쉽게 생성할 수 있게 되었습니다. 여러 시행착오를 거치면서 많은 것을 배울 수 있었다는 점에서 의미 있는 과정이었다고 생각합니다.

문제 해결에만 집중하다 보면 본질에서 벗어나기 쉽습니다. 하지만 문제의 근본 원인을 다시 정리해 보면 해결책이 자연스럽게 나온다는 점과 주변의 라이브러리들을 탐색하다 보면 문제 해결의 실마리를 발견할 때가 많다는 점을 알 수 있었습니다.

.env파일을 매번 만드는 과정이 귀찮다는 생각에서 시작하여 사내 CLI 도구를 만들까지의 과정을 적어보았습니다. 귀찮은 일들을 해결하기 위해 고민하고 있는 분들에게 작은 도움이 되었으면 좋겠습니다.