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

Composition으로 시작하기

원문 보기 위키 갱신

Composition으로 시작하기 (Get Started with Composition)

이 가이드는 App이라는 새 종류의 커스텀 리소스를 만드는 방법을 보여줍니다. 사용자가 커스텀 리소스 API를 호출해 App을 생성하면, Crossplane이 Deployment와 Service를 만듭니다.

출처: 문서

본문

Crossplane은 이것을 composition이라고 부릅니다. App은 Deployment와 Service로 구성(compose)됩니다.

💡 Tip: 이 가이드는 YAML, YAML+CEL, templated YAML, Python, KCL로 composition을 구성하는 방법을 보여줍니다. 원하는 언어를 선택하면 돼요.

App 커스텀 리소스는 이렇게 생겼습니다.

apiVersion: example.crossplane.io/v1
kind: App
metadata:
  namespace: default
  name: my-app
spec:
  image: nginx
status:
  replicas: 2  # Copied from the Deployment's status
  address: 10.0.0.1  # Copied from the Service's status

App은 Crossplane 사용자가 앱을 구성하는 데 사용하는 커스텀 API입니다. 사용자가 App을 만들면 Crossplane은 이 Deployment와 Service를 생성합니다.

---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: my-app-dhj3a
  labels:
    example.crossplane.io/app: my-app  # Copied from the App's name
spec:
  replicas: 2
  selector:
    matchLabels:
      example.crossplane.io/app: my-app  # Copied from the App's name
  template:
    metadata:
      labels:
        example.crossplane.io/app: my-app  # Copied from the App's name
    spec:
      containers:
      - name: app
        image: nginx  # Copied from the App's spec
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: default
  name: my-app-03mda
  labels:
    example.crossplane.io/app: my-app  # Copied from the App's name
spec:
  selector:
    example.crossplane.io/app: my-app  # Copied from the App's name
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 80

Crossplane은 Kubernetes 위에 구축되므로 사용자는 kubectl이나 Kubernetes 생태계의 다른 도구로 앱을 다룰 수 있습니다.

💡 Tip: Kubernetes 커스텀 리소스는 단지 JSON REST API이므로, 사용자는 REST API를 지원하는 어떤 도구로도 앱을 다룰 수 있어요.

사전 요구 사항 (Prerequisites)

이 가이드에는 다음이 필요합니다.

  • 최소 2 GB RAM 이상의 Kubernetes 클러스터
  • Kubernetes 클러스터에 설치된 Crossplane v2

커스텀 리소스 만들기 (Create the custom resource)

Crossplane으로 새 종류의 커스텀 리소스를 만드는 단계는 다음과 같습니다.

  • App 커스텀 리소스의 스키마 정의
  • Crossplane이 앱을 구성하는 방식을 설정하는 데 사용할 함수 설치
  • Crossplane이 앱을 구성하는 방식 구성

이 단계를 완료하면 새 App 커스텀 리소스를 사용할 수 있습니다.

스키마 정의하기 (Define the schema)

Crossplane은 composition으로 구동되는 커스텀 리소스를 복합 리소스(composite resource, XR)라고 부릅니다.

📝 참고: Kubernetes는 사용자 정의 API 리소스를 커스텀 리소스라고 부릅니다. Crossplane은 composition을 사용하는 사용자 정의 API 리소스를 복합 리소스라고 부릅니다. 복합 리소스는 커스텀 리소스의 한 종류예요.

새 App 복합 리소스(XR)의 스키마를 정의하려면 이 복합 리소스 정의(Composite Resource Definition, XRD)를 만드세요.

apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition
metadata:
  name: apps.example.crossplane.io
spec:
  scope: Namespaced
  group: example.crossplane.io
  names:
    kind: App
    plural: apps
  versions:
  - name: v1
    served: true
    referenceable: true
    schema:
     openAPIV3Schema:
       type: object
       properties:
        spec:
          type: object
          properties:
            image:
              description: The app's OCI container image.
              type: string
          required:
          - image
        status:
          type: object
          properties:
            replicas:
              description: The number of available app replicas.
              type: integer
            address:
              description: The app's IP address.
              type: string
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/xrd.yaml

Crossplane이 XRD를 설정했는지 확인하세요.

$ kubectl get -f https://docs.crossplane.io/latest/manifests/get-started/composition/xrd.yaml
NAME                         ESTABLISHED   OFFERED   AGE
apps.example.crossplane.io   True                    21s

Crossplane이 XRD를 설정했으므로 Kubernetes는 새 App XR에 대한 API 요청을 처리합니다. 이제 Crossplane은 새 App XR에 대한 책임을 알지만, 생성하거나 업데이트할 때 무엇을 해야 하는지는 모릅니다. 함수를 설치하고 composition을 구성해서 Crossplane에 무엇을 해야 하는지 알려줍니다.

함수 설치하기 (Install the function)

누군가 복합 리소스(XR)를 만들거나 업데이트할 때 Crossplane이 무엇을 하는지 구성하기 위해 서로 다른 composition 함수를 사용할 수 있습니다. Composition 함수는 구성 언어 플러그인과 같습니다.

App XR을 Deployment와 Service로 바꾸는 방법을 구성하는 데 사용할 언어를 선택하세요.

  • Templated YAML
  • YAML
  • YAML+CEL
  • Python
  • KCL
  • Pythonic

Helm 차트를 작성해 본 적이 있다면 Templated YAML이 좋은 선택입니다.

templated YAML 지원을 설치하려면 이 composition 함수를 만드세요.

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-go-templating
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-go-templating.yaml

Crossplane이 함수를 설치했는지 확인하세요.

$ kubectl get -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-go-templating.yaml
NAME                                        INSTALLED   HEALTHY   PACKAGE                                                               AGE
crossplane-contrib-function-go-templating   True        True      xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2   9s

YAML은 작고 정적인 composition에 좋은 선택입니다. 루프나 조건문을 지원하지 않습니다. YAML 지원을 설치하려면 이 composition 함수를 만드세요.

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-patch-and-transform
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-patch-and-transform.yaml

Crossplane이 함수를 설치했는지 확인하세요.

$ kubectl get -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-patch-and-transform.yaml
NAME                                              INSTALLED   HEALTHY   PACKAGE                                                                     AGE
crossplane-contrib-function-patch-and-transform   True        True      xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2   10s

YAML+CEL은 리소스를 일반 YAML로 정의하고 CEL 표현식으로 연결하는 데 좋은 선택입니다. 함수는 리소스 간 의존성을 자동으로 해결합니다. YAML+CEL 지원을 설치하려면 이 composition 함수를 만드세요.

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-kro
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-kro:v0.3.0
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-kro.yaml

Crossplane이 함수를 설치했는지 확인하세요.

$ kubectl get -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-kro.yaml
NAME                              INSTALLED   HEALTHY   PACKAGE                                                     AGE
crossplane-contrib-function-kro   True        True      xpkg.crossplane.io/crossplane-contrib/function-kro:v0.3.0   6s

Python은 동적 로직이 있는 composition에 좋은 선택입니다. Python 표준 라이브러리를 모두 사용할 수 있어요. Python 지원을 설치하려면 이 composition 함수를 만드세요.

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-python
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-python.yaml

Crossplane이 함수를 설치했는지 확인하세요.

$ kubectl get -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-python.yaml
NAME                                 INSTALLED   HEALTHY   PACKAGE                                                        AGE
crossplane-contrib-function-python   True        True      xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0   12s

KCL은 동적 로직이 있는 composition에 좋은 선택입니다. 빠르고 샌드박스 처리됩니다. KCL 지원을 설치하려면 이 composition 함수를 만드세요.

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: crossplane-contrib-function-kcl
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-kcl.yaml

Crossplane이 함수를 설치했는지 확인하세요.

$ kubectl get -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-kcl.yaml
NAME                              INSTALLED   HEALTHY   PACKAGE                                                      AGE
crossplane-contrib-function-kcl   True        True      xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2   6s

Pythonic (https://github.com/crossplane-contrib/function-pythonic?tab=readme-ov-file#function-pythonic)은 동적 로직이 있는 composition에 뛰어난 선택입니다. 저수준 Crossplane 함수 API의 세부 사항을 숨기는 우아하고 간결한 문법의 Python 클래스 집합을 사용해 Python의 완전한 유연성과 힘을 사용할 수 있어요. Pythonic 지원을 설치하려면 이 composition 함수를 만드세요.

apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
  name: function-pythonic
spec:
  package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-pythonic.yaml

Crossplane이 함수를 설치했는지 확인하세요.

$ kubectl get -f https://docs.crossplane.io/latest/manifests/get-started/composition/fn-pythonic.yaml
NAME               INSTALLED  HEALTHY  PACKAGE                                                         AGE
function-pythonic  True       True     xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0  1m

composition 구성하기 (Configure the composition)

Composition은 복합 리소스(XR)를 만들거나 업데이트할 때 Crossplane이 어떤 함수를 호출할지 알려줍니다. App XR을 만들거나 업데이트할 때 Crossplane이 무엇을 해야 하는지 알려주는 composition을 만드세요.

  • Templated YAML
  • YAML
  • YAML+CEL
  • Python
  • KCL
  • Pythonic

templated YAML을 사용해 Crossplane을 구성하는 composition을 만드세요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-templated-yaml
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: crossplane-contrib-function-go-templating
    input:
      apiVersion: gotemplating.fn.crossplane.io/v1beta1
      kind: GoTemplate
      source: Inline
      inline:
        template: |
          ---
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            annotations:
              gotemplating.fn.crossplane.io/composition-resource-name: deployment
              {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }}
              gotemplating.fn.crossplane.io/ready: "True"
              {{ end }}
            labels:
              example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
          spec:
            replicas: 2
            selector:
              matchLabels:
                example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
            template:
              metadata:
                labels:
                  example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
              spec:
                containers:
                - name: app
                  image: {{ .observed.composite.resource.spec.image }}
                  ports:
                  - containerPort: 80
          ---
          apiVersion: v1
          kind: Service
          metadata:
            annotations:
              gotemplating.fn.crossplane.io/composition-resource-name: service
              {{ if (get (getComposedResource . "service").spec "clusterIP") }}
              gotemplating.fn.crossplane.io/ready: "True"
              {{ end }}
            labels:
              example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
          spec:
            selector:
              example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
            ports:
            - protocol: TCP
              port: 8080
              targetPort: 80
          ---
          apiVersion: example.crossplane.io/v1
          kind: App
          status:
            replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }}
            address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }}
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/composition-templated-yaml.yaml

YAML을 사용해 Crossplane을 구성하는 composition을 만드세요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-yaml
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: crossplane-contrib-function-patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      resources:
      - name: deployment
        base:
          apiVersion: apps/v1
          kind: Deployment
          spec:
            replicas: 2
            template:
              spec:
                containers:
                - name: app
                  ports:
                  - containerPort: 80
        patches:
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: metadata.labels[example.crossplane.io/app]
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.selector.matchLabels[example.crossplane.io/app]
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.template.metadata.labels[example.crossplane.io/app]
        - type: FromCompositeFieldPath
          fromFieldPath: spec.image
          toFieldPath: spec.template.spec.containers[0].image
        - type: ToCompositeFieldPath
          fromFieldPath: status.availableReplicas
          toFieldPath: status.replicas
        readinessChecks:
        - type: MatchCondition
          matchCondition:
            type: Available
            status: "True"
      - name: service
        base:
          apiVersion: v1
          kind: Service
          spec:
            ports:
            - protocol: TCP
              port: 8080
              targetPort: 80
        patches:
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: metadata.labels[example.crossplane.io/app]
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: spec.selector[example.crossplane.io/app]
        - type: ToCompositeFieldPath
          fromFieldPath: spec.clusterIP
          toFieldPath: status.address
        readinessChecks:
        - type: NonEmpty
          fieldPath: spec.clusterIP
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/composition-yaml.yaml

YAML과 CEL을 사용해 Crossplane을 구성하는 composition을 만드세요. 리소스를 YAML로 정의하고 CEL 표현식으로 연결하면, 나머지는 Crossplane이 처리합니다.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-yaml-cel
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: crossplane-contrib-function-kro
    input:
      apiVersion: kro.fn.crossplane.io/v1alpha1
      kind: ResourceGraph
      status:
        replicas: ${deployment.status.?availableReplicas.orValue(0)}
        address: ${service.spec.?clusterIP.orValue("")}
      resources:
      - id: deployment
        template:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            labels:
              example.crossplane.io/app: ${schema.metadata.name}
          spec:
            replicas: 2
            selector:
              matchLabels:
                example.crossplane.io/app: ${schema.metadata.name}
            template:
              metadata:
                labels:
                  example.crossplane.io/app: ${schema.metadata.name}
              spec:
                containers:
                - name: app
                  image: ${schema.spec.image}
                  ports:
                  - containerPort: 80
        readyWhen:
        - ${deployment.status.?conditions.orValue([]).exists(c, c.type == "Available" && c.status == "True")}
      - id: service
        template:
          apiVersion: v1
          kind: Service
          metadata:
            labels:
              example.crossplane.io/app: ${schema.metadata.name}
          spec:
            selector:
              example.crossplane.io/app: ${schema.metadata.name}
            ports:
            - protocol: TCP
              port: 8080
              targetPort: 80
        readyWhen:
        - ${service.spec.?clusterIP.hasValue()}
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/composition-yaml-cel.yaml

💡 Tip: 이 함수는 kro와 동일한 리소스 그래프 문법을 사용하므로 기존 kro 리소스 정의가 수정 없이 동작해요.

Python을 사용해 Crossplane을 구성하는 composition을 만드세요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-python
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: crossplane-contrib-function-python
    input:
      apiVersion: python.fn.crossplane.io/v1beta1
      kind: Script
      script: |
        def compose(req, rsp):
            observed_xr = req.observed.composite.resource

            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": observed_xr["spec"]["image"],
                          "ports": [{"containerPort": 80}]
                        }],
                      },
                    },
                },
            })

            observed_deployment = req.observed.resources["deployment"].resource
            if "status" in observed_deployment:
              if "availableReplicas" in observed_deployment["status"]:
                rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"]
              if "conditions" in observed_deployment["status"]:
                for condition in observed_deployment["status"]["conditions"]:
                  if condition["type"] == "Available" and condition["status"] == "True":
                    rsp.desired.resources["deployment"].ready = True

            rsp.desired.resources["service"].resource.update({
                "apiVersion": "v1",
                "kind": "Service",
                "metadata": {
                  "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
                },
                "spec": {
                  "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
                  "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}],
                },
            })

            observed_service = req.observed.resources["service"].resource
            if "spec" in observed_service and "clusterIP" in observed_service["spec"]:
              rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"]
              rsp.desired.resources["service"].ready = True
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/composition-python.yaml

💡 Tip: Python으로 직접 함수를 작성할 수도 있어요. 더 큰 구성에는 직접 함수를 작성하는 것이 좋습니다. 직접 함수를 작성하면 여러 Python 파일을 작성할 수 있고, YAML에 Python을 내장하지 않으므로 Python IDE를 사용하기 더 편리해요. Python으로 composition function 작성 가이드를 읽어보세요.

KCL을 사용해 Crossplane을 구성하는 composition을 만드세요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-kcl
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: crossplane-contrib-function-kcl
    input:
      apiVersion: krm.kcl.dev/v1alpha1
      kind: KCLInput
      spec:
        source: |
          observed_xr = option("params").oxr

          _desired_deployment = {
            apiVersion = "apps/v1"
            kind = "Deployment"
            metadata = {
              annotations = {
                "krm.kcl.dev/composition-resource-name" = "deployment"
              }
              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 = observed_xr.spec.image
                  ports = [{containerPort = 80}]
                }]
              }
            }
          }

          observed_deployment = option("params").ocds["deployment"]?.Resource
          if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]):
            _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True"

          _desired_service = {
            apiVersion = "v1"
            kind = "Service"
            metadata = {
              annotations = {
                "krm.kcl.dev/composition-resource-name" = "service"
              }
              labels = {"example.crossplane.io/app" = observed_xr.metadata.name}
            }
            spec = {
              selector = {"example.crossplane.io/app" = observed_xr.metadata.name}
              ports = [{protocol = "TCP", port = 8080, targetPort = 80}]
            }
          }

          observed_service = option("params").ocds["service"]?.Resource
          if observed_service?.spec?.clusterIP:
            _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True"

          _desired_xr = {
            **option("params").dxr

            status.address = observed_service?.spec?.clusterIP or ""
            status.replicas = observed_deployment?.status?.availableReplicas or 0
          }

          items = [_desired_deployment, _desired_service, _desired_xr]
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/composition-kcl.yaml

Pythonic을 사용해 Crossplane을 구성하는 composition을 만드세요.

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: app-pythonic
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: App
  mode: Pipeline
  pipeline:
  - step: create-deployment-and-service
    functionRef:
      name: function-pythonic
    input:
      apiVersion: pythonic.fn.crossplane.io/v1alpha1
      kind: Composite
      composite: |
        class Composite(BaseComposite):
          def compose(self):
            labels = {'example.crossplane.io/app': self.metadata.name}

            d = self.resources.deployment('apps/v1', 'Deployment')
            d.metadata.labels = labels
            d.spec.replicas = 2
            d.spec.selector.matchLabels = labels
            d.spec.template.metadata.labels = labels
            d.spec.template.spec.containers[0].name = 'app'
            d.spec.template.spec.containers[0].image = self.spec.image
            d.spec.template.spec.containers[0].ports[0].containerPort = 80

            s = self.resources.service('v1', 'Service')
            s.metadata.labels = labels
            s.spec.selector = labels
            s.spec.ports[0].protocol = 'TCP'
            s.spec.ports[0].port = 8080
            s.spec.ports[0].targetPort = 80

            self.status.replicas = d.status.availableReplicas
            self.status.address = s.observed.spec.clusterIP
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/composition-pythonic.yaml

📝 참고: Composition은 여러 함수를 포함할 수 있습니다. 함수는 파이프라인의 앞선 함수 결과를 변경할 수 있으며, Crossplane은 마지막 함수가 반환한 결과를 사용합니다.

💡 Tip: 이 composition을 편집해 다른 종류의 리소스를 포함하게 하려면, Crossplane에 이를 합성할 수 있는 접근 권한을 부여해야 할 수 있어요. 접근 권한 부여 방법은 composition 문서를 읽어보세요.

커스텀 리소스 사용하기 (Use the custom resource)

이제 Crossplane은 App 커스텀 리소스를 이해합니다. App을 만들어 보세요.

apiVersion: example.crossplane.io/v1
kind: App
metadata:
  namespace: default
  name: my-app
spec:
  image: nginx
$ kubectl apply -f https://docs.crossplane.io/latest/manifests/get-started/composition/app.yaml

App이 준비되었는지 확인하세요.

$ kubectl get -f https://docs.crossplane.io/latest/manifests/get-started/composition/app.yaml
NAME     SYNCED   READY   COMPOSITION   AGE
my-app   True     True    app-yaml      56s

📝 참고: COMPOSITION 열은 App이 사용 중인 composition을 보여줍니다. 각 XR 종류에 대해 여러 composition을 만들 수 있어요. XR 페이지에서 Crossplane이 사용할 composition을 선택하는 방법을 읽어보세요.

Crossplane이 Deployment와 Service를 만들었는지 확인하세요.

$ kubectl get deploy,service -l example.crossplane.io/app=my-app
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-app-2r2rk   2/2     2            2           11m

NAME                   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/my-app-xfkzg   ClusterIP   10.96.148.56           8080/TCP   11m

💡 Tip: App의 image를 편집해 보세요.

$ kubectl edit -f https://docs.crossplane.io/latest/manifests/get-started/composition/app.yaml

Crossplane이 Deployment의 image를 일치하도록 업데이트합니다. 그런 다음 App을 삭제하세요.

$ kubectl delete -f https://docs.crossplane.io/latest/manifests/get-started/composition/app.yaml

App을 삭제하면 Crossplane이 Deployment와 Service도 삭제합니다.

다음 단계 (Next steps)

관리 리소스(MR)는 바로 사용할 수 있는 Kubernetes 커스텀 리소스입니다. Crossplane에는 거의 모든 클라우드 제공자나 클라우드 네이티브 소프트웨어를 관리하는 데 사용할 수 있는 광범위한 관리 리소스 라이브러리가 있습니다. 관리 리소스 시작하기로 관리 리소스에 대해 더 알아보세요. MR을 composition과 함께 사용할 수 있어요. App composition에 MR을 포함하도록 업데이트해 보세요.

더 알아보기 (Learn more)