Skip to content

Components

컴포넌트는 앱의 기본 구성 요소입니다.

컴포넌트

모든 SST 앱은 컴포넌트로 구성됩니다. 컴포넌트는 앱의 기능을 나타내는 논리적 단위입니다. 프론트엔드, API, 데이터베이스, 큐 등이 이에 해당합니다.

SST에는 두 가지 유형의 컴포넌트가 있습니다:

  1. 내장 컴포넌트 — SST 팀이 만든 고수준 컴포넌트
  2. 프로바이더 컴포넌트 — 프로바이더에서 제공하는 저수준 컴포넌트

이제 각각을 살펴보겠습니다.


배경

AWS와 같은 대부분의 프로바이더는 저수준 리소스로 구성됩니다. 프론트엔드나 API 같은 것을 만들려면 이러한 리소스가 상당히 많이 필요합니다. 예를 들어, AWS에서 Next.js 앱을 만들려면 약 70개의 저수준 AWS 리소스가 필요합니다.

이 때문에 인프라스트럭처를 코드로 관리하는 것은 전통적으로 데브옵스나 플랫폼 엔지니어만 사용해 왔습니다.

이 문제를 해결하기 위해 SST는 앱에서 가장 일반적으로 사용되는 기능을 도와줄 수 있는 컴포넌트를 제공합니다.


내장 기능

SST의 내장 컴포넌트들은 사이드바에서 볼 수 있는 것들로, 여러분의 앱의 다양한 부분을 쉽게 만들 수 있도록 설계되었습니다.

예를 들어, Next.js 프론트엔드를 배포하기 위해 AWS의 세부 사항을 많이 알 필요가 없습니다:

sst.config.ts
new sst.aws.Nextjs("MyWeb");

이 모든 것이 코드로 이루어져 있기 때문에, 이를 더욱 쉽게 구성할 수 있습니다.

sst.config.ts
new sst.aws.Nextjs("MyWeb", {
domain: "my-app.com",
path: "packages/web",
imageOptimization: {
memory: "512 MB"
},
buildCommand: "npm run build"
});

여러분은 이를 한 단계 더 나아가서 저수준 리소스가 생성되는 방식을 완전히 변형할 수도 있습니다. 이에 대해서는 아래에서 살펴보겠습니다.

현재 SST는 두 개의 클라우드 프로바이더를 위한 내장 컴포넌트를 제공합니다.


AWS

AWS 내장 컴포넌트는 AWS 작업을 쉽게 할 수 있도록 설계되었습니다.

이 컴포넌트들은 sst.aws.* 네임스페이스 아래에 있으며, 사이드바의 AWS 섹션에서 확인할 수 있습니다. 내부적으로는 Pulumi의 AWS 프로바이더를 사용합니다.


Cloudflare

이 컴포넌트들은 sst.cloudflare.* 네임스페이스 아래에 있으며, 사이드바의 Cloudflare 섹션에서 확인할 수 있습니다. 내부적으로는 Pulumi의 Cloudflare 프로바이더를 사용합니다.


생성자

앱에 컴포넌트를 추가하려면 몇 가지 인자를 전달하여 인스턴스를 생성합니다. 예를 들어, Function 컴포넌트의 시그니처는 다음과 같습니다.

new sst.aws.Function(name: string, args: FunctionArgs, opts?: pulumi.ComponentResourceOptions)

각 컴포넌트는 다음을 받습니다:

  • name: 컴포넌트의 이름입니다. 이 이름은 앱 전체에서 고유해야 합니다.
  • args: 컴포넌트를 구성할 수 있는 속성 객체입니다.
  • opts?: Pulumi에서 이 컴포넌트를 구성할 수 있는 선택적 속성 객체입니다.

다음은 Function 컴포넌트를 생성하는 예제입니다:

sst.config.ts
const function = new sst.aws.Function("MyFunction", {
handler: "src/lambda.handler"
});

이름

컴포넌트 이름을 지을 때 따라야 할 두 가지 가이드라인이 있습니다:

  1. SST의 내장 컴포넌트와 Linkable.wrap로 확장된 컴포넌트의 이름은 앱 전체에서 전역적으로 유일해야 합니다.

    이렇게 하면 리소스 링크가 런타임에 이러한 리소스를 조회할 수 있습니다.

  2. 선택적으로, 컴포넌트 이름에 PascalCase를 사용할 수 있습니다.

    예를 들어, 여러분의 버킷을 MyBucket이라고 이름 짓고 리소스 링크를 통해 Resource.MyBucket으로 조회할 수 있습니다.

    하지만 이는 순전히 미적인 선택입니다. 케밥 케이스를 사용할 수도 있습니다. 따라서 my-bucket이라고 이름 짓고 Resource['my-bucket']을 사용하여 조회할 수 있습니다.


Args

각 컴포넌트는 여러분이 설정할 수 있는 일련의 args를 받습니다. 이 args는 각 컴포넌트에 따라 다릅니다. 예를 들어, Function 컴포넌트는 FunctionArgs를 받습니다.

대부분의 args는 선택 사항입니다. 즉, 대부분의 컴포넌트는 시작하기 위해 매우 적은 설정만 필요로 합니다. 일반적으로 가장 일반적인 설정 옵션은 최상위 레벨로 끌어올려집니다. 컴포넌트를 더 세부적으로 설정하려면 transform prop을 사용해야 합니다.

args는 보통 기본 타입을 받습니다. 하지만 기본 타입의 특별한 버전도 받습니다. 예를 들어 _Input<string>_과 같은 형태입니다. 이에 대해 아래에서 자세히 살펴보겠습니다.


Transform

대부분의 컴포넌트는 생성자나 메서드의 일부로 transform prop을 받습니다. 이는 해당 컴포넌트의 인프라가 생성되는 방식을 변형할 수 있는 콜백을 포함하는 객체입니다.

예를 들어, Function 컴포넌트의 transform prop은 다음과 같습니다:

  • function: 기본 Lambda 함수를 변형하는 콜백
  • logGroup: Lambda의 LogGroup 리소스를 변형하는 콜백
  • role: Lambda 함수가 사용하는 역할을 변형하는 콜백

이 콜백들의 타입은 비슷합니다. role 콜백은 다음과 같습니다:

RoleArgs | (args: RoleArgs, opts: pulumi.ComponentResourceOptions, name: string) => void

이는 다음 중 하나를 받습니다:

  • RoleArgs 객체. 예를 들어:

    {
    transform: {
    role: {
    name: "MyRole"
    }
    }
    }

    이는 컴포넌트에 전달될 원래의 RoleArgs병합됩니다.

  • RoleArgs를 받는 함수. 함수 시그니처는 다음과 같습니다:

    (args: RoleArgs, opts: pulumi.ComponentResourceOptions, name: string) => void

    여기서 args, opts, name은 Pulumi에 전달된 Role 생성자의 인자입니다.

    따라서 현재 RoleArgs를 받아 변형하는 콜백을 전달할 수 있습니다.

    {
    transform: {
    role: (args, opts) => {
    args.name = `${args.name}-MyRole`;
    opts.retainOnDelete = true;
    }
    }
    }

$transform

컴포넌트 변환과 유사하게, 전역 $transform이 있습니다. 이를 통해 특정 타입의 컴포넌트가 생성되는 방식을 변환할 수 있습니다.

예를 들어, 함수에 대한 기본 runtime을 설정할 수 있습니다.

sst.config.ts
$transform(sst.aws.Function, (args, opts) => {
// 컴포넌트가 설정하지 않은 경우 기본값 설정
args.runtime ??= "nodejs18.x";
});

이 코드는 이 호출 이후에 생성될 모든 Function 컴포넌트의 런타임을 설정합니다.

args.runtime을 확인하는 이유는 컴포넌트가 기본값을 재정의할 수 있도록 하기 위함입니다. 컴포넌트가 자체 runtime을 지정하지 않은 경우에만 기본값을 설정합니다.

sst.config.ts
new sst.aws.Function("MyFunctionA", {
handler: "src/lambdaA.handler"
});
new sst.aws.Function("MyFunctionB", {
handler: "src/lambdaB.handler",
runtime: "nodejs20.x"
});

위 변환을 적용하면, MyFunctionAnodejs18.x 런타임을 사용하고, MyFunctionBnodejs20.x 런타임을 사용합니다.

$transform 콜백의 argsoptsFunction 컴포넌트에 전달할 인자들입니다. Function 컴포넌트의 시그니처를 다시 살펴보면:

sst.config.ts
new sst.aws.Function(name: string, args: FunctionArgs, opts?: pulumi.ComponentResourceOptions)

전역 $transform에 대해 더 알아보세요.


속성

컴포넌트 인스턴스는 여러 속성을 제공합니다. 예를 들어, Function 컴포넌트는 arn, name, url, nodes와 같은 속성을 제공합니다.

const functionArn = function.arn;

이러한 속성들은 앱에 대한 정보를 출력하거나 다른 컴포넌트의 인자로 사용할 수 있습니다.

이 속성들은 일반적으로 기본 타입입니다. 하지만 기본 타입의 특수 버전일 수도 있습니다. 예를 들어 _Output<string>_과 같은 형태로 나타납니다. 이에 대해 자세히 살펴보겠습니다.


링크

이러한 속성 중 일부는 리소스 링크를 통해 제공됩니다. 이를 통해 여러분은 타입 안전한 방식으로 함수와 프론트엔드에서 이 속성들에 접근할 수 있습니다.

예를 들어, 함수는 링크를 통해 name 속성을 노출합니다.


노드

컴포넌트가 노출하는 nodes 속성을 통해 기본 인프라에 접근할 수 있습니다. 이 속성은 생성된 Pulumi 컴포넌트에 대한 참조를 포함하는 객체입니다.

예를 들어, Function 컴포넌트는 다음과 같은 노드를 노출합니다 — function, logGroup, 그리고 role.


출력(Outputs)

컴포넌트의 속성은 일반적으로 _Output<primitive>_와 같은 특별한 타입을 가집니다.

이 값들은 아직 사용할 수 없으며, 배포가 진행되면서 해결될 것입니다. 그러나 이 출력 값들은 다른 컴포넌트의 인자로 사용할 수 있습니다.

이를 통해 여러분의 앱 일부가 블로킹되지 않고, 모든 리소스가 가능한 한 동시에 배포될 수 있습니다.

예를 들어, URL이 있는 함수를 만들어 보겠습니다.

sst.config.ts
const myFunction = new sst.aws.Function("MyFunction", {
url: true,
handler: "src/lambda.handler"
});

여기서 myFunction.urlOutput<string> 타입입니다. 이 함수 URL을 라우터의 라우트로 사용하고 싶습니다.

sst.config.ts
new sst.aws.Router("MyRouter", {
routes: {
"/api": myFunction.url
}
});

라우트 인자는 Input<string>을 받습니다. 이는 문자열이나 출력 값을 받을 수 있다는 의미입니다. 이렇게 하면 내부적으로 의존성이 생성됩니다. 따라서 라우터는 함수가 배포된 후에 배포될 것입니다. 그러나 이 함수에 의존하지 않는 다른 컴포넌트들은 동시에 배포될 수 있습니다.

Pulumi 문서에서 Input과 Output 타입에 대해 더 읽어보실 수 있습니다.


Apply

출력값은 아직 해결되지 않은 값이므로 일반적인 연산에 사용할 수 없습니다. 먼저 이를 해결해야 합니다.

예를 들어, 위에서 언급한 url 함수를 살펴보겠습니다. 다음과 같은 작업은 할 수 없습니다.

sst.config.ts
const newUrl = myFunction.url + "/foo";

이 연산 시점에는 출력값이 알려져 있지 않기 때문입니다. 이를 해결해야 합니다.

출력값을 다루는 가장 쉬운 방법은 .apply를 사용하는 것입니다. 이를 통해 출력값에 연산을 적용하고 새로운 출력값을 반환할 수 있습니다.

sst.config.ts
const newUrl = myFunction.url.apply((value) => value + "/foo");

이 경우, newUrlOutput<string> 타입입니다.

헬퍼 함수

출력 작업을 조금 더 쉽게 하기 위해 다음과 같은 전역 헬퍼 함수를 제공합니다.


$concat

이 기능을 사용하면 다음과 같이 할 수 있습니다.

sst.config.ts
const newUrl = $concat(myFunction.url, "/foo");

다음과 같이 apply를 사용하는 대신에요.

sst.config.ts
const newUrl = myFunction.url.apply((value) => value + "/foo");

$concat에 대해 더 알아보세요.

$interpolate

이 기능을 사용하면 다음과 같이 할 수 있습니다.

sst.config.ts
const newUrl = $interpolate`${myFunction.url}/foo`;

이렇게 하면 apply를 사용하지 않아도 됩니다.

sst.config.ts
const newUrl = myFunction.url.apply((value) => value + "/foo");

$interpolate에 대해 더 알아보세요.

$jsonParse

이 기능은 JSON 문자열인 출력을 다룰 때 사용합니다. 따라서 다음과 같이 작성하는 대신

sst.config.ts
const policy = policyStr.apply((policy) =>
JSON.parse(policy)
);

이렇게 간단하게 작성할 수 있습니다.

sst.config.ts
const policy = $jsonParse(policyStr);

$jsonParse에 대해 더 알아보세요.


$jsonStringify

JSON 객체를 출력할 때도 비슷하게 적용할 수 있습니다. apply 후에 stringify를 하는 대신에,

sst.config.ts
const policy = policyObj.apply((policy) =>
JSON.stringify(policy)
);

이렇게 할 수 있습니다.

sst.config.ts
const policy = $jsonStringify(policyObj);

$jsonStringify에 대해 더 알아보세요.

$resolve

마지막으로 여러 출력 목록을 함께 처리하고 싶을 때 사용할 수 있습니다.

sst.config.ts
$resolve([bucket.name, worker.url]).apply(([bucketName, workerUrl]) => {
console.log(`Bucket: ${bucketName}`);
console.log(`Worker: ${workerUrl}`);
})

$resolve에 대해 더 알아보세요.


버전 관리

SST 컴포넌트는 시간이 지남에 따라 발전하며, 때로는 호환성을 깨는 변경 사항을 도입하기도 합니다. 이전 버전과의 호환성을 유지하기 위해 컴포넌트 버전 관리 체계를 도입했습니다.

예를 들어, 기본적으로 NAT Gateway를 생성하지 않는 새로운 버전의 Vpc 컴포넌트를 출시했습니다. 이 변경 사항을 적용하기 위해 이전 버전의 Vpc 컴포넌트는 Vpc.v1로 이름이 변경되었습니다.

만약 여러분이 기존 Vpc 컴포넌트를 사용 중이고, SST를 업데이트한 후 배포를 시도한다면, 배포 중에 이 컴포넌트의 새 버전이 있다는 오류 메시지를 보게 될 것입니다.

이를 통해 여러분은 이 컴포넌트를 어떻게 처리할지 결정할 수 있습니다.

이전 버전 계속 사용하기

컴포넌트의 이전 버전을 계속 사용하려면 이름을 변경하면 됩니다.

sst.config.ts
const vpc = new sst.aws.Vpc("MyVpc");
const vpc = new sst.aws.Vpc.v1("MyVpc");

이제 다시 배포하면 SST는 여러분이 이전 버전을 사용하려는 것을 알고 오류를 발생시키지 않습니다.


최신 버전으로 업데이트하기

최신 버전으로 업데이트하려면 컴포넌트 이름을 변경해야 합니다.

sst.config.ts
const vpc = new sst.aws.Vpc("MyVpc");
const vpc = new sst.aws.Vpc("MyNewVpc");

이제 다시 배포하면 이전에 생성된 컴포넌트가 제거되고 새로운 이름과 최신 버전으로 다시 생성됩니다.

SST의 관점에서는 MyVpc 컴포넌트가 제거되고 MyNewVpc라는 새로운 컴포넌트가 추가된 것처럼 보이기 때문입니다.

이러한 리소스가 다시 생성되기 때문에 해당 리소스가 일시적으로 사용 불가능한 기간이 있을 수 있습니다. 리소스에 따라 다운타임이 발생할 수 있습니다.