본문 바로가기
WIKI 기술 지식 베이스

Compositions

원문 보기 위키 갱신

Composition은 여러 Kubernetes 리소스를 하나의 컴포지트 리소스로 만드는 템플릿이에요. Composition은 개별 리소스들을 더 크고 재사용 가능한 솔루션으로 조합해요.

예를 들어 한 Composition이 가상 머신, 저장소 리소스, 네트워킹 정책을 결합할 수 있어요. Composition 템플릿은 이 모든 개별 리소스를 함께 연결해요.

출처: 문서

본문

다음은 예시 Composition이에요. 이 Composition을 사용하는 AcmeBucket 컴포지트 리소스(XR)를 만들면, Crossplane은 템플릿을 사용해 Amazon S3 Bucket 관리 리소스를 만들어요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: example
spec:
  compositeTypeRef:
    apiVersion: custom-api.example.org/v1alpha1
    kind: AcmeBucket
  mode: Pipeline
  pipeline:
  - step: patch-and-transform
    functionRef:
      name: function-patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      resources:
      - name: storage-bucket
        base:
          apiVersion: s3.aws.m.upbound.io/v1beta1
          kind: Bucket
          spec:
            forProvider:
              region: "us-east-2"

XR, XRD, Composition이 뭘까요? 컴포지트 리소스(XR)는 커스텀 API예요. 새 커스텀 API를 만들려면 두 가지 Crossplane 타입을 사용해요.

  • Composite Resource Definition (XRD) — XR의 스키마를 정의해요.
  • Composition — 이 페이지. XR이 다른 리소스를 어떻게 생성하는지 구성해요.

Composition 만들기 (Create a composition)

Composition을 만드는 것은 다음으로 구성돼요.

  • 컴포지션 함수를 사용해 만들 리소스를 정의
  • 컴포지트 리소스가 Composition 템플릿을 사용할 수 있게 활성화

Composition은 컴포지션 함수(composition functions)의 파이프라인이에요. 컴포지션 함수(간단히 함수)는 Crossplane 리소스를 템플릿화하는 Crossplane 확장이에요. 컴포지트 리소스(XR)를 만들 때 Crossplane이 어떤 리소스를 만들지 결정하기 위해 컴포지션 함수를 호출해요.

Crossplane에는 YAML 패치·트랜스폼, Helm 같은 YAML 템플릿, CUE, KCL, Python으로 컴포즈드 리소스를 템플릿화할 수 있게 해주는 함수가 있어요. Go나 Python으로 직접 함수를 작성할 수도 있어요.

컴포지션 함수 설치 (Install a composition function)

Function을 설치하면 함수 파드가 생성돼요. 컴포지트 리소스를 만들 때 Crossplane은 이 파드에 어떤 리소스를 만들지 요청을 보내요.

Function은 Crossplane Function 객체로 설치하며, spec.package 값을 함수 패키지의 위치로 설정해요. 예를 들어 Function Patch and Transform을 설치하려면 다음과 같이 해요.

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: function-patch-and-transform
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2

Functions는 Crossplane 패키지예요. 패키지에 대한 자세한 내용은 Packages 문서를 읽어보세요. 기본적으로 Function 파드는 Crossplane과 같은 네임스페이스(crossplane-system)에 설치돼요.

컴포지션 함수 검증 (Verify a composition function)

kubectl get functions로 Function의 상태를 볼 수 있어요. 설치 중에는 Function이 INSTALLED를 True로, HEALTHY를 Unknown으로 보고해요.

kubectl get functions
NAME                              INSTALLED   HEALTHY   PACKAGE                                                                  AGE
function-patch-and-transform      True        Unknown   xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2   10s

Function 설치가 완료되고 사용 준비가 되면 HEALTHY 상태가 True를 보고해요.

Composition에서 함수 사용 (Use a function in a composition)

컴포지트 리소스를 만들 때 Crossplane이 어떤 리소스를 만들지 결정하기 위해 Function을 호출해요. Function은 또한 컴포지트 리소스를 업데이트할 때 이 리소스들을 어떻게 처리할지도 Crossplane에 알려줘요.

컴포지션 함수는 컴포지트 리소스를 삭제할 때는 실행되지 않아요. Crossplane이 컴포즈드 리소스의 삭제를 자동으로 처리해요.

Crossplane이 Function을 호출하면 컴포지트 리소스의 현재 상태와 컴포지트 리소스가 소유한 모든 리소스의 현재 상태를 보내요. Crossplane은 컴포지트 리소스가 사용하는 Composition을 보고 컴포지트 리소스가 변경될 때 어떤 Function을 호출할지 알아요.

컴포지션 함수를 사용하려면 Composition mode를 Pipeline로 설정해요. 그런 다음 steps의 pipeline을 정의해요. 각 step은 Function을 호출해요. 각 step은 functionRef로 호출할 Function의 name을 참조해요.

일부 Function은 input을 지정할 수도 있어요. 함수가 입력의 kind를 정의해요.

이 예는 Function Patch and Transform을 사용해요. Function Patch and Transform은 Crossplane 리소스 템플릿을 구현해요. 입력 kind는 Resources이고 resources를 입력으로 받아요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
# Removed for Brevity
spec:
  # Removed for Brevity
  mode: Pipeline
  pipeline:
  - step: patch-and-transform
    functionRef:
      name: function-patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      resources:
      - name: storage-bucket
        base:
          apiVersion: s3.aws.m.upbound.io/v1beta1
          kind: Bucket
          spec:
            forProvider:
              region: "us-east-2"

Composition에서 함수 파이프라인 사용 (Use a pipeline of functions)

Crossplane은 컴포지트 리소스가 변경될 때 하나 이상의 Function에 물어볼 수 있어요. Composition에 두 개 이상의 step 파이프라인이 있으면 Crossplane은 모두 호출해요. 파이프라인에 나타난 순서대로 호출한답니다.

Crossplane은 파이프라인의 각 Function에 이전 Function의 결과를 전달해요. 이는 강력한 함수 조합을 가능하게 해요. 이 예에서 Crossplane은 function-cue를 호출해 S3 버킷을 만들고, 그 버킷을 function-auto-ready에 전달하며, 버킷이 준비되면 컴포지트 리소스를 준비됨으로 표시해요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
# Removed for Brevity
spec:
  # Removed for Brevity
  mode: Pipeline
  pipeline:
  - step: cue-export-resources
    functionRef:
      name: function-cue
    input:
      apiVersion: cue.fn.crossplane.io/v1beta1
      kind: CUEInput
      name: storage-bucket
      export:
        target: Resources
        value: |
          apiVersion: "s3.aws.m.upbound.io/v1beta1"
          kind: "Bucket"
          spec: forProvider: region: "us-east-2"
  - step: automatically-detect-readiness
    functionRef:
      name: function-auto-ready

컴포지트 리소스 일치 (Match composite resources)

Composition은 컴포즈드 리소스를 만드는 방법을 정의하는 템플릿일 뿐이에요. Composition은 어떤 종류의 컴포지트 리소스(XR)가 이 템플릿을 사용할 수 있는지 제한해요.

Composition의 compositeTypeRef는 어떤 Composite Resource 타입이 이 Composition을 사용할 수 있는지 정의해요. (Composite Resource에 대한 자세한 내용은 Composite Resources 페이지를 읽어보세요.)

Composition의 spec 안에서 이 템플릿을 사용하도록 허용하는 Composite Resource의 apiVersion과 kind를 정의해요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: dynamodb-with-bucket
spec:
  compositeTypeRef:
    apiVersion: custom-api.example.org/v1alpha1
    kind: database
  # Removed for brevity

컴포즈드 리소스 접근 부여 (Grant access to composed resources)

Crossplane은 자체 서비스 계정(service account)을 사용해 함수 파이프라인이 반환한 컴포즈드 리소스를 만들어요.

Crossplane의 서비스 계정은 Provider가 설치하거나 XRD가 정의한 어떤 리소스든 생성·업데이트·삭제할 수 있어요. 여기에는 모든 MR과 XR이 포함돼요. 또한 동작에 필요한 일부 Kubernetes 리소스 타입(예: deployment 생성)에 대한 접근도 있어요.

다른 종류의 리소스를 컴포즈하려면 Crossplane에 접근 권한을 부여해야 해요. RBAC ClusterRole을 만들어서요. ClusterRole은 ClusterRole 집계를 사용해 Crossplane의 기본 ClusterRole에 집계되어야 해요.

다음은 CloudNativePG PostgreSQL 클러스터를 관리할 Crossplane 접근을 부여하는 ClusterRole이에요.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cnpg:aggregate-to-crossplane
  labels:
    rbac.crossplane.io/aggregate-to-crossplane: "true"
rules:
- apiGroups:
  - postgresql.cnpg.io
  resources:
  - clusters
  verbs:
  - "*"

rbac.crossplane.io/aggregate-to-crossplane: "true" 라벨이 중요해요. 이 라벨은 역할을 Crossplane의 기본 클러스터 역할에 집계하도록 구성해요.

RBAC 매니저는 MR과 XR에 대한 Crossplane 접근을 자동으로 부여해요. RBAC 매니저는 escalate 접근을 사용해 RBAC 매니저가 갖지 못한 Crossplane 접근을 부여해요. RBAC 매니저는 기본적으로 활성화되는 선택적 Crossplane 컴포넌트예요. RBAC 매니저를 비활성화하면 XR과 MR을 포함해 컴포즈하려는 어떤 종류의 리소스에든 Crossplane 접근을 수동으로 부여해야 해요.

Composition 테스트 (Test a composition)

Crossplane CLI를 사용해 어떤 Composition의 출력이든 미리 볼 수 있어요. 이때 Crossplane 컨트롤 플레인이 필요하지 않아요. Crossplane CLI는 Docker Engine을 사용해 함수를 실행해요.

Crossplane CLI 설치와 사용법은 Crossplane CLI 문서를 참고하세요. crossplane composition render를 실행하려면 Docker가 필요해요.

컴포지트 리소스, 컴포지션, 컴포지션 함수를 제공해 로컬에서 출력을 렌더링해요.

crossplane composition render xr.yaml composition.yaml functions.yaml

crossplane composition render는 리소스를 YAML로 stdout에 출력해요. 먼저 컴포지트 리소스를, 그다음 컴포지션 함수가 만든 리소스들을 출력해요.

---
apiVersion: example.crossplane.io/v1
kind: Bucket
metadata:
  name: example-render
---
apiVersion: s3.aws.m.upbound.io/v1beta1
kind: Bucket
metadata:
  annotations:
    crossplane.io/composition-resource-name: storage-bucket
  generateName: example-render-
  labels:
    crossplane.io/composite: example-render
  ownerReferences:
  - apiVersion: example.crossplane.io/v1
    blockOwnerDeletion: true
    controller: true
    kind: Bucket
    name: example-render
    uid: ""
spec:
  forProvider:
    region: us-east-2

예에서 사용된 xr.yaml, composition.yaml, function.yaml 파일들은 이 파일들을 사용해 crossplane composition render를 실행하면 아래 출력을 재현할 수 있어요.

xr.yaml 파일은 렌더링할 컴포지트 리소스를 담고 있어요.

apiVersion: example.crossplane.io/v1
kind: Bucket
metadata:
  name: example-render
spec:
  bucketRegion: us-east-2

composition.yaml 파일은 컴포지트 리소스를 렌더링하는 데 사용할 Composition을 담고 있어요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: example-render
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: Bucket
  mode: Pipeline
  pipeline:
  - step: patch-and-transform
    functionRef:
      name: function-patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      resources:
      - name: storage-bucket
        base:
          apiVersion: s3.aws.m.upbound.io/v1beta1
          kind: Bucket
        patches:
        - type: FromCompositeFieldPath
          fromFieldPath: spec.bucketRegion
          toFieldPath: spec.forProvider.region

functions.yaml 파일은 Composition이 파이프라인 step에서 참조하는 Functions를 담고 있어요.

---
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: function-patch-and-transform
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2

Crossplane CLI는 Docker Engine을 사용해 함수를 실행해요. functions.yaml에 어노테이션을 추가해 Crossplane CLI가 함수를 실행하는 방식을 바꿀 수 있어요. Function에 render.crossplane.io/runtime 어노테이션을 추가해 실행 방식을 변경해요.

crossplane composition render는 두 가지 render.crossplane.io/runtime 값을 지원해요.

  • Docker (기본값) — Docker Engine에 연결해요. Docker를 사용해 함수 런타임을 pull하고 실행해요.
  • Development — 수동으로 실행한 함수 런타임에 연결해요.

Development 런타임을 사용하면 Crossplane CLI는 Function의 package를 무시해요. 대신 함수가 localhost 포트 9443에서 수신 대기 중이어야 해요. 함수는 gRPC 전송 보안 없이 수신 대기해야 해요. 대부분의 함수 SDK는 --insecure 플래그로 전송 보안을 끌 수 있게 해줘요. 예를 들어 Go 함수는 go run . --insecure로 로컬 실행할 수 있어요.

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: function-patch-and-transform
  annotations:
    render.crossplane.io/runtime: Development
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2

Development 런타임은 컴포지션 함수를 작성할 때 함수를 end-to-end로 테스트하는 데 유용해요.

crossplane composition render는 다음 Function 어노테이션도 지원해요. 이 어노테이션들은 Function 실행 방식에 영향을 줘요.

  • render.crossplane.io/runtime-docker-cleanup — Docker 런타임 사용 시 CLI가 함수 호출 후 함수 컨테이너를 중지할지 지정해요. Stop(컨테이너 중지), Orphan(계속 실행)을 지원해요.
  • render.crossplane.io/runtime-docker-pull-policy — Docker 런타임 사용 시 CLI가 Function 패키지를 언제 pull할지 지정해요. Always, Never, IfNotPresent를 지원해요.
  • render.crossplane.io/runtime-development-target — Development 런타임 사용 시 CLI가 지정된 target에서 실행 중인 Function에 연결하도록 지시해요. gRPC target 구문을 사용해요.

crossplane composition render CLI 명령은 Crossplane 컨트롤러 바이너리의 crossplane internal render 명령을 실행해요. 기본적으로 Docker 컨테이너에서 최신 안정 버전의 Crossplane을 실행하므로 컨트롤러 바이너리를 다운로드할 필요가 없어요.

다른 Crossplane 버전으로 렌더링하려면 다음 플래그를 사용할 수 있어요.

  • --crossplane-version — Docker에서 다른 Crossplane 버전을 실행
  • --crossplane-image — 임의의 Crossplane Docker 이미지 선택 (예: 로컬 미러)
  • --crossplane-binary — 임의의 Crossplane 컨트롤러 바이너리 제공 (예: Crossplane의 로컬 변경 사항 테스트)

Composition 검증 (Verify a composition)

kubectl get composition로 사용 가능한 모든 Compositions를 볼 수 있어요.

kubectl get composition
NAME                                       XR-KIND        XR-APIVERSION                         AGE
xapps.aws.platformref.upbound.io           XApp           aws.platformref.upbound.io/v1alpha1   123m
xclusters.aws.platformref.upbound.io       XCluster       aws.platformref.upbound.io/v1alpha1   123m
xeks.aws.platformref.upbound.io            XEKS           aws.platformref.upbound.io/v1alpha1   123m
xnetworks.aws.platformref.upbound.io       XNetwork       aws.platformref.upbound.io/v1alpha1   123m
xservices.aws.platformref.upbound.io       XServices      aws.platformref.upbound.io/v1alpha1   123m
xsqlinstances.aws.platformref.upbound.io   XSQLInstance   aws.platformref.upbound.io/v1alpha1   123m

XR-KIND은 Composition 템플릿을 사용할 수 있는 Composite Resource kind를 나열해요. XR-APIVERSION은 Composition 템플릿을 사용할 수 있는 Composite Resource API 버전을 나열해요.

kubectl get composition의 출력은 kubectl get composite와 달라요. kubectl get composition은 사용 가능한 모든 Compositions를 나열하고, kubectl get composite는 생성된 모든 Composite Resources와 관련 Composition을 나열해요.

컴포지션 함수 작성 (Write a composition function)

컴포지션 함수를 사용하면 복잡한 Composition을 선호하는 프로그래밍 언어로 작성한 코드로 대체할 수 있어요. Crossplane에는 함수 작성을 돕는 도구, SDK, 템플릿이 있어요.

다음은 Go로 작성된 간단한 hello world 함수 예시예요.

func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) (*fnv1.RunFunctionResponse, error) {
        rsp := response.To(req, response.DefaultTTL)
        response.Normal(rsp, "Hello world!")
        return rsp, nil
}

Crossplane에는 컴포지션 함수 작성에 대한 언어별 가이드가 있어요. 선호하는 언어의 가이드를 참고해 컴포지션 함수 작성법을 배우세요. 컴포지션 함수를 작성할 때는 함수가 어떻게 동작하는지 아는 것이 유용해요. 다음 섹션에서 컴포지션 함수가 어떻게 동작하는지 배워볼게요.

컴포지션 함수가 동작하는 방식 (How composition functions work)

각 컴포지션 함수는 실제로 gRPC 서버예요. gRPC는 고성능 오픈소스 원격 프로시저 호출(RPC) 프레임워크예요. 함수를 설치하면 Crossplane이 함수를 gRPC 서버로 배포해요. Crossplane은 모든 gRPC 통신을 암호화하고 인증해요.

함수를 작성하기 위해 gRPC 전문가일 필요는 없어요. Crossplane 함수 SDK가 gRPC를 설정해줘요. 다만 Crossplane이 함수를 호출하는 방식과 함수가 응답해야 하는 방식을 이해하는 것은 유용해요.

sequenceDiagram
    User->>+API Server: Create composite resource
    Crossplane Pod->>+API Server: Observe composite resource
    Crossplane Pod->>+Function Pod: gRPC RunFunctionRequest
    Function Pod->>+Crossplane Pod: gRPC RunFunctionResponse
    loop Extra resources needed?
      Crossplane Pod->>+API Server: Get Extra resources
      Crossplane Pod->>+Function Pod: gRPC RunFunctionRequest
      Function Pod->>+Crossplane Pod: gRPC RunFunctionResponse
    end
    Crossplane Pod->>+API Server: Apply desired composed resources

컴포지션 함수를 사용하는 컴포지트 리소스를 만들거나 업데이트하면 Crossplane은 Composition의 파이프라인에 나타난 순서대로 각 함수를 호출해요. Crossplane은 각 함수에 gRPC RunFunctionRequest를 보내 호출하고, 함수는 gRPC RunFunctionResponse로 응답해야 해요. RunFunctionRequest와 RunFunctionResponse RPC의 상세 스키마는 Buf Schema Registry에서 찾을 수 있어요.

Crossplane이 함수를 처음 호출할 때 RunFunctionRequest에 네 가지 중요한 내용을 포함해요.

  • 컴포지트 리소스와 컴포즈드 리소스의 관측된 상태(observed state)
  • 컴포지트 리소스와 컴포즈드 리소스의 원하는 상태(desired state)
  • 함수의 입력(input)
  • 함수 파이프라인의 컨텍스트(context)

Crossplane은 또한 지원하는 프로토콜 기능 집합(예: required resources, credentials, conditions, required schemas)을 요청의 meta.capabilities에 채워요. 함수는 기능에 의존하기 전에 그 능력을 테스트하고, 지원하지 않는 더 오래된 Crossplane에서 실행할 때는 대체(fallback)할 수 있어요. 아래 Capability advertisement를 참고하세요.

함수의 주요 역할은 원하는 상태를 업데이트하고 Crossplane에 반환하는 것이에요. RunFunctionResponse를 반환해 이를 수행해요.

대부분의 컴포지션 함수는 컴포지트 리소스의 관측된 상태를 읽고, 이를 사용해 원하는 상태에 컴포즈드 리소스를 추가해요. 이는 Crossplane에 어떤 컴포즈드 리소스를 만들거나 업데이트해야 하는지 알려줘요.

함수가 원하는 상태를 결정하기 위해 필요한 리소스가 있으면, 반환된 RunFunctionResponse를 통해 이름이나 라벨로 Crossplane이 이미 접근할 수 있는 클러스터 스코프 또는 네임스페이스 리소스를 요청할 수 있어요. 그러면 Crossplane은 요청한 리소스와 함수가 반환한 컨텍스트, 그리고 이전 RunFunctionRequest의 입력·관측·원하는 상태를 함께 포함해 함수를 다시 호출해요. 함수는 필요하면 필요한 리소스를 반복 요청할 수 있지만, 무한 루프를 막기 위해 Crossplane은 반복 횟수를 5로 제한해요. 함수가 두 번 연속으로 같은 요청을 반환하면 필요한 리소스 요청이 안정적이 된 것으로 보고 함수가 충족된 것으로 간주해요. 5회 반복 후에도 안정성에 도달하지 못하면 Crossplane은 오류를 반환해요.

컴포즈드 리소스는 컴포지트 리소스가 만든 리소스예요. 컴포즈드 리소스는 어떤 종류의 Kubernetes 리소스든 될 수 있어요.

관측된 상태 (Observed state)

다음과 같은 컴포지트 리소스를 만들면 Crossplane이 그것을 관측하고 관측된 상태의 일부로 컴포지션 함수에 보내요.

apiVersion: example.crossplane.io/v1
kind: Bucket
metadata:
  name: example-render
spec:
  bucketRegion: us-east-2

컴포즈드 리소스가 이미 존재하면 Crossplane은 그것들을 관측하고 관측된 상태의 일부로 함수에 보내요. Crossplane은 컴포지트 리소스와 컴포즈드 리소스의 connection details도 관측해 관측된 상태의 일부로 보내요.

Crossplane은 파이프라인의 함수 호출을 시작하기 직전에 컴포지트 리소스와 컴포즈드 리소스를 한 번 관측해요. 즉, 파이프라인의 모든 함수에 같은 관측된 상태를 보낸다는 뜻이에요.

원하는 상태 (Desired state)

원하는 상태는 함수 파이프라인이 컴포지트 리소스와 컴포즈드 리소스에 적용하려는 변경 집합이에요. 함수가 원하는 상태에 컴포즈드 리소스를 추가하면 Crossplane이 그것을 만들어요.

함수는 다음을 변경할 수 있어요.

  • 컴포지트 리소스의 상태(status)
  • 컴포즈드 리소스의 메타데이터와 spec

함수는 컴포지트 리소스의 준비 상태(readiness)도 변경할 수 있어요. 함수는 컴포즈드 리소스가 준비됐는지 Crossplane에 알려줌으로써 컴포지트 리소스의 준비 여부를 나타내요. 함수 파이프라인이 모든 컴포즈드 리소스가 준비됐다고 Crossplane에 알리면, Crossplane은 컴포지트 리소스를 준비됨으로 표시해요.

함수는 변경할 수 없어요.

  • 컴포지트 리소스의 메타데이터나 spec
  • 컴포즈드 리소스의 상태(status)
  • 컴포즈드 리소스의 connection details

함수 파이프라인은 원하는 상태를 누적해요. 즉 각 함수는 파이프라인에서 이전 함수들의 원하는 상태 위에 구축해요. Crossplane은 이전 함수들이 누적한 원하는 상태를 함수에 보내고, 함수는 그것을 추가하거나 업데이트한 뒤 다음으로 넘겨요. 파이프라인의 마지막 함수가 실행되면 Crossplane이 반환된 원하는 상태를 적용해요.

함수는 RunFunctionRequest의 모든 원하는 상태를 RunFunctionResponse로 복사해야 해요. 함수가 리소스를 원하는 상태에 추가하면 다음 함수는 그것을 자기의 원하는 상태에 복사해야 해요. 복사하지 않으면 Crossplane은 그 리소스를 적용하지 않아요. 리소스가 이미 존재하면 Crossplane은 그것을 삭제해요.

함수는 의도적으로 원하는 상태의 일부를 복사하지 않기로 선택할 수 있어요. 예를 들어 함수는 특정 리소스가 존재하지 않도록 원하는 리소스를 복사하지 않기로 선택할 수 있어요. 대부분의 함수 SDK는 원하는 상태 복사를 자동으로 처리해요.

함수는 자신이 신경 쓰는 필드만 원하는 상태에 추가해야 해요. Crossplane이 그것을 호출할 때마다 이 필드들을 추가해야 해요. 함수가 한 번 원하는 상태에 필드를 추가했지만 다음 호출 때는 추가하지 않으면 Crossplane은 그 필드를 삭제해요. 컴포즈드 리소스도 마찬가지예요. 함수가 컴포즈드 리소스를 원하는 상태에 추가했지만 다음 호출 때는 추가하지 않으면 Crossplane은 그 컴포즈드 리소스를 삭제해요.

Crossplane은 함수 파이프라인이 반환한 원하는 상태를 적용하는 데 서버 사이드 어플라이(server side apply)를 사용해요. 서버 사이드 어플라이 용어로 원하는 상태는 완전히 명시된 의도예요.

예를 들어 함수가 us-east-2 리전에 S3 버킷이 존재하도록 하는 것이 전부라면, 이 리소스를 원하는 컴포즈드 리소스에 추가해야 해요.

apiVersion: s3.aws.m.upbound.io/v1beta1
kind: Bucket
spec:
  forProvider:
    region: us-east-2

버킷이 이미 존재하고 다른 spec 필드, status, name, labels 등이 있더라도 함수는 그것들을 생략해야 해요. 함수는 자신이 의견을 가진 필드만 포함해야 해요. Crossplane은 함수가 신경 쓰는 필드를 적용하고 기존 Bucket과 병합하는 작업을 처리해요.

컴포지션 함수는 실제로 원하는/관측된 리소스에 YAML을 사용하지 않아요. 이 예는 설명 목적으로만 YAML을 사용한 거예요.

함수 입력 (Function input)

Composition이 input을 포함하면 Crossplane이 그것을 함수에 보내요. 입력은 함수에 추가 구성을 제공하는 유용한 방법이에요. 입력 지원은 선택 사항이며 모든 함수가 입력을 지원하진 않아요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: example-render
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: Bucket
  mode: Pipeline
  pipeline:
  - step: patch-and-transform
    functionRef:
      name: function-patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      resources:
      - name: storage-bucket
        base:
          apiVersion: s3.aws.m.upbound.io/v1beta1
          kind: Bucket
        patches:
        - type: FromCompositeFieldPath
          fromFieldPath: spec.bucketRegion
          toFieldPath: spec.forProvider.region

Crossplane은 함수 입력을 검증하지 않아요. 함수가 자체 입력을 검증하는 것이 좋아요.

필수 리소스 (Required resources)

Crossplane v1은 이 기능을 "extra resources"라고 불렀어요. v2 API는 "required resources"라는 이름을 사용하고 부트스트랩 요구사항(bootstrap requirements) 지원을 추가해요.

함수는 원하는 상태를 결정하는 데 도움이 되도록 기존 Kubernetes 리소스에 대한 접근을 요청할 수 있어요. 함수는 이 능력을 사용해 ConfigMap에서 구성을 읽고, 다른 리소스의 상태를 선택하거나, 기존 클러스터 상태를 바탕으로 결정을 내려요.

함수는 두 가지 방식으로 필수 리소스를 받을 수 있어요.

부트스트랩 요구사항 (Bootstrap requirements)

Composition 파이프라인 step에 필수 리소스를 제공할 수 있어요. 이 방식은 함수 런타임 중 리소스를 요청하는 것보다 성능이 좋아요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-with-config
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-from-config
    functionRef:
      name: crossplane-contrib-function-python
    requirements:
      requiredResources:
      - requirementName: app-config
        apiVersion: v1
        kind: ConfigMap
        name: app-configuration
        namespace: default
    input:
      apiVersion: python.fn.crossplane.io/v1beta1
      kind: Script
      script: |
        from crossplane.function import request

        def compose(req, rsp):
            observed_xr = req.observed.composite.resource

            # Access the required ConfigMap using the helper function
            config_map = request.get_required_resource(req, "app-config")

            if not config_map:
                # Fallback image if ConfigMap not found
                image = "nginx:latest"
            else:
                # Read image from ConfigMap data
                image = config_map.get("data", {}).get("image", "nginx:latest")

            # Create deployment with the configured image
            rsp.desired.resources["deployment"].resource.update({
                "apiVersion": "apps/v1",
                "kind": "Deployment",
                "metadata": {
                    "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
                },
                "spec": {
                    "replicas": 2,
                    "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}},
                    "template": {
                        "metadata": {
                            "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
                        },
                        "spec": {
                            "containers": [{
                                "name": "app",
                                "image": image,
                                "ports": [{"containerPort": 80}]
                            }],
                        },
                    },
                },
            })
동적 리소스 요청 (Dynamic resource requests)

함수는 런타임 중 RunFunctionResponse를 통해 리소스를 요청할 수도 있어요. Crossplane은 요청된 리소스와 함께 함수를 다시 호출해요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-dynamic-config
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-from-dynamic-config
    functionRef:
      name: crossplane-contrib-function-python
    input:
      apiVersion: python.fn.crossplane.io/v1beta1
      kind: Script
      script: |
        from crossplane.function import request, response

        def compose(req, rsp):
            observed_xr = req.observed.composite.resource

            # Always request the ConfigMap to ensure stable requirements
            config_name = observed_xr["spec"].get("configName", "default-config")
            namespace = observed_xr["metadata"].get("namespace", "default")

            response.require_resources(
                rsp,
                name="dynamic-config",
                api_version="v1",
                kind="ConfigMap",
                match_name=config_name,
                namespace=namespace
            )

            # Check if we have the required ConfigMap
            config_map = request.get_required_resource(req, "dynamic-config")

            if not config_map:
                # ConfigMap not found yet - Crossplane will call us again
                return

            # ConfigMap found - use the image data to create deployment
            image = config_map.get("data", {}).get("image", "nginx:latest")

            rsp.desired.resources["deployment"].resource.update({
                "apiVersion": "apps/v1",
                "kind": "Deployment",
                "metadata": {
                    "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
                },
                "spec": {
                    "replicas": 2,
                    "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}},
                    "template": {
                        "metadata": {
                            "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
                        },
                        "spec": {
                            "containers": [{
                                "name": "app",
                                "image": image,
                                "ports": [{"containerPort": 80}]
                            }],
                        },
                    },
                },
            })

가능하면 더 나은 성능을 위해 부트스트랩 요구사항을 사용해요. 동적 요청은 함수 호출을 더 많이 필요로 하며, 필수 리소스가 관측된 상태나 이전 함수 결과에 의존할 때 가장 잘 동작해요.

함수는 다음으로 리소스를 요청할 수 있어요.

  • 이름 — name: "my-configmap" 으로 특정 리소스
  • 라벨 — matchLabels: {"env": "prod"} 로 여러 리소스
  • 네임스페이스 — 네임스페이스 리소스에 namespace: "production" 포함

Crossplane은 무한 루프를 막기 위해 동적 리소스 요청을 5회 반복으로 제한해요. 함수는 두 번 연속으로 같은 리소스 요구사항을 반환하여 완료를 알려요.

필수 스키마 (Required schemas)

Crossplane v2.2 이상이 required schemas를 지원해요. 함수는 이 기능을 사용하기 전에 CAPABILITY_REQUIRED_SCHEMAS 능력을 테스트할 수 있어요.

컴포지션 함수는 때로 리소스 kind의 OpenAPI 스키마가 필요해요. 예를 들어 리소스를 검증하거나, 올바른 필드 타입으로 리소스를 생성하거나, 스키마를 사용하는 도구를 구축할 때요. 함수 명세에 따라 함수는 네트워크 접근을 가정할 수 없으므로 API 서버에서 스키마를 직접 가져올 수 없어요.

Crossplane은 required resources에 사용된 것과 같은 패턴을 스키마 요청에 확장해요.

  • 함수는 RunFunctionResponse에 requirements.schemas를 반환해 필요한 각 스키마의 API 버전과 kind를 지정해요 (예: apps/v1, Deployment).
  • Crossplane은 클러스터에서 OpenAPI 스키마를 가져와 required_schemas를 채운 RunFunctionRequest로 함수를 다시 호출해요.
  • 스키마를 찾지 못하면(예: kind가 없음) Crossplane은 그 맵 항목을 빈 Schema 메시지로 설정해, 함수가 "Crossplane이 시도했지만 아무것도 못 찾음"과 "아직 처리 안 됨"을 구분할 수 있게 해요.

Composition 파이프라인 step에서 필수 스키마를 제공(부트스트랩)하거나 함수 응답에서 동적으로 요청할 수 있어요. 부트스트랩이 더 효율적이에요.

Composition에서 필수 스키마 부트스트랩:

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: example-with-schemas
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: validate-and-compose
    functionRef:
      name: my-function
    requirements:
      requiredSchemas:
      - requirementName: deployment-schema
        apiVersion: apps/v1
        kind: Deployment

함수는 req.required_schemas["deployment-schema"]에서 스키마를 받고 검증이나 코드 생성을 위해 사용할 수 있어요. 맵 키는 Composition이나 함수의 requirements.schemas 응답에 지정한 requirementName이에요.

능력 광고 (Capability advertisement)

v2.2보다 오래된 Crossplane 버전은 능력을 광고하지 않아요. meta.capabilities가 비어 있으면 함수는 Crossplane이 어떤 기능을 지원하는지 알 수 없어요. 함수 SDK는 이 경우를 감지해 대체 전략을 고를 수 있게 도우미(Python의 advertises_capabilities, Go의 AdvertisesCapabilities)를 제공해요.

Crossplane은 함수를 호출할 때 RequestMeta.capabilities에 지원하는 모든 프로토콜 기능을 채워요. 함수가 지원하지 않는 더 오래된 Crossplane에서 기능(예: required schemas나 conditions)을 사용하면 Crossplane은 알 수 없는 필드를 무시해요. 함수는 Crossplane이 자기의 요청을 존중했는지 알 방법이 없어요.

Capability advertisement가 이 문제를 해결해요. Crossplane은 모든 RunFunctionRequest에 지원 가능한 기능 목록을 보내요. 함수는 기능에 의존하기 전에 능력을 테스트하고 부재 시 대체해야 해요.

| Capability | Meaning | | CAPABILITY_CAPABILITIES | Crossplane이 능력을 광고함; 다른 능력이 없으면 Crossplane이 지원하지 않는다는 뜻 | | CAPABILITY_REQUIRED_RESOURCES | Crossplane이 requirements.resources를 지원하고 required_resources를 채움 | | CAPABILITY_CREDENTIALS | Crossplane이 Composition의 credentials를 지원함 | | CAPABILITY_CONDITIONS | Crossplane이 함수 응답의 status conditions를 지원함 | | CAPABILITY_REQUIRED_SCHEMAS | Crossplane이 requirements.schemas를 지원하고 required_schemas를 채움 |

이 Python 예는 기능을 사용하기 전에 능력을 테스트해요.

import crossplane.function.proto.v1.run_function_pb2 as fnv1
from crossplane.function import request, response

if request.has_capability(req, fnv1.CAPABILITY_REQUIRED_SCHEMAS):
    response.require_schema(rsp, "xr", "example.org/v1", "MyXR")
    schema = request.get_required_schema(req, "xr")
    if schema:
        # Use schema for validation or code generation
        pass
else:
    # Crossplane doesn't support required schemas; skip or use a fallback
    pass

함수 SDK는 has_capability, get_required_schema 같은 도우미를 제공해요. 정확한 API는 SDK 문서를 참고하세요.

함수 파이프라인 컨텍스트 (Function pipeline context)

때로 파이프라인의 두 함수가 원하는 상태가 아닌 정보를 서로 공유하고 싶어해요. 함수는 이를 위해 컨텍스트를 사용할 수 있어요. 어떤 함수든 파이프라인 컨텍스트에 쓸 수 있어요. Crossplane은 그 컨텍스트를 이후의 모든 함수에 전달해요. Crossplane이 모든 함수를 호출하면 파이프라인 컨텍스트를 버려요.

함수 응답 캐시 (Function response cache)

함수 응답 캐싱은 알파 기능이에요. --enable-function-response-cache 기능 플래그를 설정해 활성화해요.

Crossplane은 반복되는 함수 호출을 줄여 성능을 개선하기 위해 함수 응답을 캐시할 수 있어요. 활성화하면 Crossplane은 time to live(TTL) 값이 포함된 컴포지션 함수의 응답을 캐시해요.

캐시 작동 방식:

  • 요청 해시를 기반으로 함수 응답을 디스크에 저장
  • 0이 아닌 TTL이 있는 응답만 캐시
  • 만료된 캐시 항목을 자동으로 제거
  • 만료될 때까지 동일한 요청에 캐시된 응답을 재사용

이 기능은 다음 함수에 도움이 돼요.

  • 비용이 많이 드는 계산이나 외부 API 호출 수행
  • 같은 입력에 안정적인 결과 반환
  • 응답에 적절한 TTL 값 포함
캐시 구성 (Cache configuration)

이 Crossplane 파드 인자로 캐시 동작을 제어해요.

  • --xfn-cache-max-ttl — 최대 캐시 기간 (기본값: 24시간)

캐시는 Crossplane 파드의 /cache/xfn/ 디렉터리에 파일을 저장해요. 더 나은 성능을 위해 medium: Memory인 emptyDir 볼륨을 마운트해 인메모리 캐시를 사용하는 것을 고려해보세요.

더 알아보기 (Learn more)