Serverless com Crossplane composition no EKS + GitOps [Lab Session]
Tags: community, crossplane, aws, gitops
Published: 2024-01-30 10:41:26

![intro](https://miro.medium.com/v2/resize:fit:640/format:webp/1*cZGTebNS79Uxutlk4_iyWA.png)

> Build control planes without needing to write code. Crossplane has a highly extensible backend that enables you to orchestrate applications and infrastructure no matter where they run, and a highly configurable frontend that lets you define the declarative API it offers.
> [https://www.crossplane.io/](https://www.crossplane.io/)

Neste lab vamos provisionar a arquitetura *serverless* abaixo na AWS utilizando uma *composition* do Crossplane. Tudo é provisionado como código e com o Crossplane, os componentes/serviços AWS se tornam recursos dentro do Kubernetes e são gerenciados como tal.

Além da _composition_ que serve como um _template_ para criação de vários recursos gerenciados de uma só vez (*stack* na imagem abaixo), criamos também os _managed resources_, que são os recursos gerenciados de uma forma mais 'individual'. Adicionamos as funcionalidades do ArgoCD para GitOps neste cenário de _managed resources_.

![Serverless architecture managed by crossplane controller in kubernetes](https://cdn-images-1.medium.com/max/3218/1*xhoCWuv7s9rnyxCNg330KQ.png)

O código node.js das funções lambda que utilizamos bem como o *pattern*, é fruto do trabalho sensacional realizado pela galera que contribui no [https://serverlessland.com/](https://serverlessland.com/).

### Crossplane Introduction

![](https://cdn-images-1.medium.com/max/2000/1*mRBuvLmY1Q25335nGqa9mw.png)

*Source: [https://docs.crossplane.io/latest/getting-started/introduction/](https://docs.crossplane.io/latest/getting-started/introduction/)*

---

[Repositório.](https://github.com/paulofponciano/EKS-Crossplane-ArgoCD.git)

### Deploy Amazon EKS — Cluster de gerenciamento

- O cluster EKS e toda infra necessária para ele (vpc, nlb, etc.) nós subimos com terraform. Nesse deploy também subimos o crossplane via helm:

`terraform init`
`terraform plan --var-file variables.tfvars`
`terraform apply --var-file variables.tfvars`

![](https://cdn-images-1.medium.com/max/2118/1*syHU9-vFIFyU3rULK72nsQ.png)

- Conectando no cluster provisionado:

`aws eks --region us-east-2 update-kubeconfig --name pegasus`

![](https://cdn-images-1.medium.com/max/2146/1*WNFMS5NqD01qn-fTQAmQzg.png)

- Listando os pods do crossplane e providers:

`kubectl get pods -n crossplane-system`

![](https://cdn-images-1.medium.com/max/2156/1*-4Swz6al-OBF8Pnr5AYKGg.png)

### Deploy Crossplane composition

- Primeiro criamos um _secret_ para utilizar no _ProviderConfig_ do crossplane:


`kubectl create secret \
generic aws-secret \
-n crossplane-system \
--from-file=creds=./cred.txt`

No arquivo *cred.txt* existente na raiz do repositório, temos as informações de *access key* e *secret key* da AWS. O crossplane precisa dessas credenciais para provisionar os recursos.

- Criando o *ProviderConfig:*

```yaml
cat <<EOF | kubectl apply -f -
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: aws-secret
      key: creds
EOF
```

*Reference: [https://docs.crossplane.io/latest/getting-started/provider-aws/](https://docs.crossplane.io/latest/getting-started/provider-aws/)*

- Agora vamos aplicar no _cluster_ a _composition_ e _definition_ da _stack serverless_:

`kubectl apply -f composition.yaml`
`kubectl apply -f definition.yaml`

![](https://cdn-images-1.medium.com/max/2730/1*xVC7-3NxJdrBRxzc7bFPkA.png)

![](https://cdn-images-1.medium.com/max/2732/1*5PtKawBlFm5BZc602r89Ww.png)

Neste ponto, temos nosso *template* pronto no *cluster* para ser acionado. Fazemos isso através do _claim_. O _claim_ pode ser a única interação necessária para o usuário/desenvolvedor, sendo um _yaml_ extremamente enxuto. No final das contas, todas as definições e _guardrails_ estão no *template* (*composition.yaml* e *definition.yaml*).

*composition.yaml*

```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: order.pauloponciano.pro
spec: 
  compositeTypeRef:
    apiVersion: api.pauloponciano.pro/v1alpha1
    kind: XCustomOrder
  patchSets:
    - name: common-fields
      patches:
        - type: FromCompositeFieldPath
          fromFieldPath: spec.resourceConfig.providerConfigName
          toFieldPath: spec.providerConfigRef.name
        - type: FromCompositeFieldPath
          fromFieldPath: spec.resourceConfig.region
          toFieldPath: spec.forProvider.region
  resources:
## API GATEWAY
    - name: order-api-gateway
      base:
        apiVersion: apigateway.aws.upbound.io/v1beta1
        kind: RestAPI
# Removed for brevity
```

*definition.yaml*

```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xcustomorders.api.pauloponciano.pro
spec:
  group: api.pauloponciano.pro
  names: 
    kind: XCustomOrder
    plural: xcustomorders
  claimNames:
    kind: CustomOrder
    plural: customorders
  versions:
  - name: v1alpha1
    served: true
    referenceable: true
    schema:
      openAPIV3Schema:
        type: object
        properties: 
          spec: 
            type: object
            properties:
              resourceConfig:
                  description: ResourceConfig defines general properties of this AWS
                    resource.
                  properties:
                    providerConfigName:
                      type: string
                    region: 
                      type: string
                      oneOf:
                        - pattern: 'us-east-2'
                        - pattern: 'us-east-1'
# Removed for brevity
```
- Aplicando o *claim.yaml*:

```yaml
apiVersion: api.pauloponciano.pro/v1alpha1
kind: CustomOrder
metadata:
  name: order-composition
  namespace: default
spec:
  resourceConfig:
    providerConfigName: default
    region: us-east-1
    tags:
      ownerName: SRE
      projectName: PE
```

`kubectl apply -f claim.yaml`

![](https://cdn-images-1.medium.com/max/2700/1*40BIUZrZRPqcJ0BRtAO7-g.png)

Agora a *composite* *CustomOrder* será provisionada. Podemos ver:

`kubectl get composite`

![](https://cdn-images-1.medium.com/max/2730/1*DMpUSCAxhlFUpz4amKjQHA.png)

`kubectl describe composite order-composition-qmfjw`

![](https://cdn-images-1.medium.com/max/2000/1*VnOx53MKTjF_mCvdKt3_Mg.png)

`kubectl get managed`

![](https://cdn-images-1.medium.com/max/3372/1*MihRIvaxLtrQ5q8xI-sjDw.png)

Caso algum desses recursos seja deletado, via console AWS por exemplo, o crossplane vai identificar e provisionar novamente… *reconciliation process!*

### Testando a stack serverless

- Vamos fazer um *describe* para identificar a URL de invocação da API Gateway:

`kubectl describe deployment.apigateway.aws.upbound.io/order-composition-qmfjw-k9p95`

![](https://cdn-images-1.medium.com/max/2000/1*3tAJn46v-JwALsxoU3xadg.png)

- Fazemos vários POST's na API Gateway no **/order**, neste caso usando Thunder Client no VS Code:

![](https://cdn-images-1.medium.com/max/2454/1*k9yryuPUc630Pqc2dbk8Zg.png)

```json
{
  "restaurantName": "calabresoPizza",
  "order": "samplePizza",
  "customerName": "Paulo",
  "amount": "10"
}
```

![Dashboard API Gateway](https://cdn-images-1.medium.com/max/3148/1*I5TxHbMrcRF4RuAiK9MWRg.png)

- Podemos ver que a lambda de *putOrder* foi invocada pela API Gateway:

![](https://cdn-images-1.medium.com/max/3258/1*kjUEOC5eHQzjiKrbhbK67g.png)

Logs:

![](https://cdn-images-1.medium.com/max/3178/1*URzvuerQ-Wrg8LgeMPRoEQ.png)

- Na *rule* do EventBridge, estamos esperando o *event pattern* abaixo para invocar a outra lambda, definida como *target*:

![Event pattern](https://cdn-images-1.medium.com/max/2000/1*FWN9U5vh2hGkyjDAqV-x4g.png)

![Event targets](https://cdn-images-1.medium.com/max/2400/1*KvdYGf-4F-iQeOEOo9UIbw.png)

Podemos ver em 'Monitoring' da *rule*, que ela foi acionada após nossos POST's na API Gateway e também invocou a outra lambda:

![Event monitoring](https://cdn-images-1.medium.com/max/3216/1*1PxOW5Z95IzaPIuay5vcwA.png)

- Lambda consumer:

![](https://cdn-images-1.medium.com/max/3246/1*yS_TPx7_lm-x-a7tspC7HA.png)

Logs:

![](https://cdn-images-1.medium.com/max/3186/1*V0NjDlKrkfg0LzRPliqh4g.png)

### *Crossplane managed resources — GitOps com ArgoCD*

Adicionamos uma *application* no Argo, baseada no mesmo repositório que usamos até agora, porém no path ***crossplane_managed/vpc.***

![](https://cdn-images-1.medium.com/max/2000/1*57NSo9Qm0U_pxwnqziOp9Q.png)

São os recursos para construção de uma VPC, mas dessa vez não como uma 'composition' do crossplane.

Para os passos mais detalhados de *deploy* do ArgoCD no EKS, podem dar uma olhada no post: [https://medium.com/@paulofponciano/gitops-no-amazon-eks-com-argocd-lab-session-b4b957b7a84c](https://medium.com/@paulofponciano/gitops-no-amazon-eks-com-argocd-lab-session-b4b957b7a84c).

- Deixamos o Argo fazer o *deploy*:

![](https://cdn-images-1.medium.com/max/2194/1*Hggny3AHmw8bK40_Y767hw.png)

![](https://cdn-images-1.medium.com/max/2000/1*P4N4pJiKRue1wiTimpIz6g.png)

- Vamos olhar no kubernetes:

`kubectl get vpc`

![](https://cdn-images-1.medium.com/max/2716/1*2p2V4K_vgkzfq4ngBJkE-w.png)

`kubectl get managed`

![](https://cdn-images-1.medium.com/max/2772/1*_am0b8J32itHzwrGAAgpbA.png)

Além do *controller* do crossplane, contamos com o Argo para sempre manter o estado desejado dos recursos no _cluster_.

Happy building!