dev-ops/Pulumi

[Pulumi] index.ts에 정의된 값을 쉘 스크립트로 동적 치환 방법

heemang_e 2025. 4. 13. 14:08

 

📌  개요

Pulumi를 기반으로 AWS 인프라를 코드로 관리하면서 EC2 인스턴스의 초기화 스크립트(user-data.sh)에 EFS ID를 동적으로 삽입하는 방식을 구현했다. 이 과정에서 IMDSv2 방식으로는 EFS 정보를 조회할 수 없다는 문제점이 존재해, Pulumi의 apply() 함수를 활용한 동적 치환(interpolation) 기법을 적용했다.

 

문제점: EC2 메타데이터에서 EFS 정보 미제공

CloudWatch 대시보드에서 EFS 관련 지표를 시각화하기 위해 EFS의 ID가 필요했다. 그러나 EC2 인스턴스 내에서 IMDSv2를 통해 제공되는 메타데이터에는 EFS 정보가 포함되어 있지 않기 때문에, 해당 정보를 직접적으로 조회할 수 없었다. 따라서, EC2 인스턴스가 시작될 때 필요한 EFS ID를 외부에서 주입하는 방식이 필요했다.

 

해결 방안: 1. 동적 치환 방식을 사용하여 쉘 스크립트의 값을 치환한다.

Pulumi에서는 apply() 함수를 사용하여 다른 스택의 출력값을 참조하고, 이를 기반으로 문자열 내의 플레이스홀더를 동적으로 치환할 수 있다. 이를 통해 user-data.sh 스크립트 내의 $EFS_ID 플레이스홀더를 실제 EFS ID로 대체할 수 있다.

 

아래 코드는 officemail-efs-test 스택에서 생성된 EFS의 ID를 가져와 로컬에 저장된 user-data.sh 스크립트 내의 $EFS_ID를 해당 ID로 치환한다.

const config = new pulumi.Config();
const efsStack = new pulumi.StackReference(`nai/efs/test`);

// Read the init script content.
const initScriptPath = config.require("initScriptPath"); 
const initFile = fs.readFileSync(initScriptPath, "utf-8");
const initScript = efsStack.getOutput("efsId").apply(efsId => {
  return initFile.replaceAll("$EFS_ID", efsId);
});

 

efsStack 값은 아래 JSON 형태이며 apply()를 사용하여 Outputs 값으로 존재하는 efsId를 참조할 수 있다.

efsStack: {
    @isPulumiResource: true
    id               : "nai/efs/test"
    name             : "nai/efs/test"
    outputs          : {
        efsId        : "hidden"
        mountTarget  : "hidden"
        mountTargetIP: "hidden"
    }
    secretOutputNames: []
    urn              : "urn:pulumi:test::officemail::pulumi:pulumi:StackReference::nai/efs/test"
}

 

2. EC2 인스턴스가 생성되면서 S3 버킷 및 객체가 생성된다.

CloudWatch 대시보드 생성을 위한 스크립트(create-cloudwatch-dashboard.sh)를 S3 버킷에 저장하고, EC2 인스턴스에서 해당 스크립트를 다운로드하여 실행하도록 구성하였다.

// Create S3 Bucket
const cloudWatchDashboardBucket = new aws.s3.Bucket("cloudWatchDashboardBucket", {
  bucket: "cloudwatch-dashboard-scripts",
  acl: "private",
  versioning: {
    enabled: true
  },
});

// Create S3 Bucket Object (create cloudwatch dashboard script)
new aws.s3.BucketObject("createCloudWatchDashboardScript", {
  bucket: cloudWatchDashboardBucket.id,
  key: "create-cloudwatch-dashboard.sh",
  source: new pulumi.asset.FileAsset("../../scripts/create_cloudwatch_dashboard.sh"),
  acl: "private"
});

 

3. EC2 인스턴스 초기화 시 스크립트 실행

EC2 인스턴스가 시작될 때 user-data.sh 스크립트를 통해 S3에서 create-cloudwatch-dashboard.sh 스크립트를 EC2 내부(/opt 경로)로 복사한 후 실행한다. 이때, EFS ID를 포함한 여러 인자를 전달하여 대시보드를 생성한다.

 

여기서 ${EFS_ID}는 앞서 Pulumi를 통해 동적으로 치환된 실제 EFS ID 값이다.

# ..생략

aws s3 cp s3://officemail-infra-cloudwatch-dashboard-scripts/create-cloudwatch-dashboard.sh /opt/create-cloudwatch-dashboard.sh
chmod +x /opt/create-cloudwatch-dashboard.sh
/opt/create-cloudwatch-dashboard.sh "${INSTANCE_ID}" "${DASHBOARD_NAME}" "${DASHBOARD_TITLE}" "${ENVIRONMENT}" "${REGION}" "${ROOT_VOLUME_ID}" "${DATA_VOLUME_ID}" "${EFS_ID}"

 

최종 인프라 구조

CloudWatch 대시보드에서 EFS 지표를 수집하고 있으므로 EFS_ID 값이 필요하다. 따라서 user-data.sh에서 EFS_ID 값을 필요로 하는데, 이를 "2. EFS Stack의 Output (efsId) 가져오기" 방법으로 해결할 수 있다.