[AWS] Secret Manager => S3 => ECS 통합 파이프라인

[원본 링크]

관련 포스트
https://blog.naver.com/sssang97/224204769869
AWS ECS에서 환경변수를 좀 깔쌈하게 관리해보고 싶어서 이래저래 뒤틀어서 세팅해둔 방법을 기록한다.




개별 구성요소

Secret Manager는 환경변수를 키/값 단위로 편리하게 수정할 수 있는 UI 기능을 제공한다.
근데 문제는 ECS에는 잘 통합되지 않는다는 것이다. Secret에 들어있는 모든 값을 자동으로 전부 환경변수로 부어주거나 하진 않는다.

S3는 반대로, ECS에 아주 잘 통합된다.
그냥 표준형 .env 파일 올려둔 다음에 경로만 꼽아두면 그거 로드해서 전부 환경변수로 주입해준다.

근데 문제는 수정하기가 매우 번거롭다는 것이다.
매번 다운받아서 수정하고 업로드해야하는데, 하다보면 피로가 쌓인다.

AWS가 만들어놓은 것들이 대개 이렇다. 뭔가 다 어설프고 3%가 빠져있다.




.env 파이프라인 설계

그래서 저걸 좀 인체공학적으로 바꿔보려고 만든 구조가 대충 이런거다.

Secret Manager에서 수정하면 감지해서 올리기.





세팅하기

Lambda 코드는 다음과 같다. 환경변수만 적절히 채워준다.

// index.mjs
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const sm = new SecretsManagerClient();
const s3 = new S3Client();

const SECRET_ID = process.env.SECRET_ID;       // "myapp/production"
const S3_BUCKET = process.env.S3_BUCKET;       // "my-config-bucket"
const S3_KEY    = process.env.S3_KEY ?? ".env";

function toEnvFormat(obj) {
  return Object.entries(obj)
    .map(([k, v]) => {
      // 값에 공백이나 특수문자 있으면 따옴표로 감싸기
      const needsQuote = /[\s"'\\#]/.test(String(v));
      const escaped = needsQuote ? `"${String(v).replace(/"/g, '\\"')}"` : v;
      return `${k}=${escaped}`;
    })
    .join("\n");
}

export const handler = async (event) => {
  const secretArn = event?.detail?.requestParameters?.secretId;

  // 이벤트에서 온 secret이 관리 대상과 다르면 스킵
  if (secretArn && !secretArn.includes(SECRET_ID)) {
    console.log(`무관한 secret 이벤트 스킵: ${secretArn}`);
    return;
  }

  const { SecretString } = await sm.send(
    new GetSecretValueCommand({ SecretId: SECRET_ID })
  );

  const values = JSON.parse(SecretString);
  const envContent = toEnvFormat(values);

  await s3.send(
    new PutObjectCommand({
      Bucket: S3_BUCKET,
      Key: S3_KEY,
      Body: envContent,
      ContentType: "text/plain",
    })
  );

  console.log(`S3 업로드 완료: s3://${S3_BUCKET}/${S3_KEY}`);
};

Lambda에 필요한 권한은 이 정도

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": "시크릿 ARN"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::..."
        }
    ]
}

그리고 Cloudtrail에 기본 세팅을 초기화해준다.

적당히 이렇게 기본값으로 추적이 하나 만들어지면 된다. 이게 있어야 트리거가 된다.

마지막으로 Secret이 변경될때 Lambda를 트리거할 EventBridge Rule만 있으면 된다.

이벤트 소스 패턴은 적당히 이렇게

{
  "source": ["aws.secretsmanager"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventSource": ["secretsmanager.amazonaws.com"],
    "eventName": ["PutSecretValue"],
    "requestParameters": {
      "secretId": ["시크릿ARN"]
    }
  }
}

그리고 아까 만든 Lambda를 호출만 하게 하면 된다.

끝이다.

트리거 속도는 매우 빨라서 딜레이는 대개 몇초 이내다.