Skip to content

Deploy APIs from CRDs

This page explains how to publish APIs with CRDs (Custom Resource Definitions).


Introduction

In this tutorial, you'll learn how to use Kubernetes Custom Resource Definitions with Traefik Hub to manage and publish APIs.

You will create different deployments of APIs (Flight API, Ticket API, etc.) and OpenAPI (OAS) specifications.

The YAML files are loaded into a static server deployment via a ConfigMap to avoid creating a Persistent Volume or baking the files into the static JSON server container image.

The OAS spec is then served on the /openapi.yaml path that you'll specify on the API resource to let the Portal display the file.

For the purpose of this tutorial, you will use domains generated by Traefik Hub for the API Gateway and Portal.

By the end of this tutorial, you'll have learned how to:

  • Deploy APIs with CRDs
  • Configures access and permissions of APIs and API collections
  • Configure an API Gateway
  • Expose an API Portal

Before you begin

Please make sure to read the reference document about managing APIs from CRDs to get more familiar with API management via CRDs.

This tutorial assumes that you followed our tutorial about user management.

It is not possible to access API Gateways and Portals without authentication.


1. Prepare the demo

Create a directory called api-demo, this will be your working directory for this tutorial.

All APIs will be deployed into the apps namespace.
In this step, you will create this namespace.

Create a file with the name namespace.yaml in your api-demo directory with the following content:

---
apiVersion: v1
kind: Namespace
metadata:
  name: apps

Use kubectl to deploy it:

kubectl apply -f namespace.yaml

2. Deploy the demo APIs

Now, that you have created the namespace for your API apps, it's time to deploy the APIs.

Customers API

In your api-demo directory, create a new directory called customer and change into it.

Now create the following YAML files:

api.yaml
---
apiVersion: hub.traefik.io/v1alpha1
kind: API
metadata:
  name: customer-api
  namespace: apps
  labels:
    area: customers
    module: crm
spec:
  pathPrefix: "/customers"
  service:
    openApiSpec:
      path: /openapi.yaml
      port:
        number: 3000
    name: customer-app
    port:
      number: 3000
customer.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: customer-data
  namespace: apps
data:
  api.json: |
    {
      "customers": [
        { "id": 1, "firstName": "John", "lastName": "Doe", "points": 100, "status": "bronze" },
        { "id": 2, "firstName": "Jane", "lastName": "Doe", "points": 200, "status": "silver" },
        { "id": 3, "firstName": "John", "lastName": "Smith", "points": 300, "status": "gold" }
      ]
    }

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: customer-app
  namespace: apps
spec:
  replicas: 1
  selector:
    matchLabels:
      app: customer-app
  template:
    metadata:
      labels:
        app: customer-app
    spec:
      containers:
        - name: api
          image: douglasdtm/json-server:openapi
          args: ["--watch", "/api/api.json", "--static", "/public"]
          imagePullPolicy: IfNotPresent
          volumeMounts:
          - name: api-data
            mountPath: /api
          - name: openapi
            mountPath: /public
      volumes:
        - name: api-data
          configMap:
            name: customer-data
        - name: openapi
          configMap:
            name: customer-apispec

---
apiVersion: v1
kind: Service
metadata:
  name: customer-app
  namespace: apps
  labels:
    app: customer-app
spec:
  type: ClusterIP
  ports:
    - port: 3000
      name: api
  selector:
    app: customer-app
spec.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: customer-apispec
  namespace: apps
data:
  openapi.yaml: |
    openapi: "3.0.0"
    info:
      version: 1.0.0
      title: Customers
      description: OpenAPI specification based on Swagger Petstore (https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore.yaml).
    servers:
      - url: https://api.traefik.localhost
    paths:
      /customers:
        get:
          summary: Get customers
          operationId: getCustomers
          tags:
            - customers
          parameters:
            - name: limit
              in: query
              description: How many items to return at one time (max 100)
              required: false
              schema:
                type: integer
                maximum: 100
                format: int32
          responses:
            '200':
              description: A paged array of customers
              headers:
                x-next:
                  description: A link to the next page of responses
                  schema:
                    type: string
              content:
                application/json:    
                  schema:
                    $ref: "#/components/schemas/Customers"
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        post:
          summary: Create a customer
          operationId: createCustomer
          tags:
            - customers
          responses:
            '201':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
      /customers/{customerId}:
        get:
          summary: Info for a specific customer
          operationId: showCustomerById
          tags:
            - customers
          parameters:
            - name: customerId
              in: path
              required: true
              description: The id of the customer
              schema:
                type: string
          responses:
            '200':
              description: Expected response to a valid request
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Customer"
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        put:
          summary: Update a customer
          operationId: updateCustomer
          tags:
            - customers
          parameters:
            - name: customerId
              in: path
              required: true
              description: The id of the customer
              schema:
                type: string
          responses:
            '200':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        delete:
          summary: Delete a customer
          operationId: deleteCustomer
          tags:
            - customers
          parameters:
            - name: customerId
              in: path
              required: true
              description: The id of the customer
              schema:
                type: string
          responses:
            '200':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
    components:
      schemas:
        Customer:
          type: object
          required:
            - id
            - firstName
            - lastName
          properties:
            id:
              type: integer
              format: int64
            firstName:
              type: string
            lastName:
              type: string
            points:
              type: integer
              format: int64
            status:
              type: string
        Customers:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/Customer"
        Error:
          type: object
          required:
            - message
          properties:
            message:
              type: string

The following kubectl commands will deploy the API:

kubectl apply -f api.yaml \
              -f customer.yaml \
              -f spec.yaml

Employee API

Go back into your api-demo directory, and create a new directory called employee.

Once you have created it, change into it and create the following files:

api.yaml
---
apiVersion: hub.traefik.io/v1alpha1
kind: API
metadata:
  name: employee-api
  namespace: apps
  labels:
    area: internal
    module: crm
spec:
  pathPrefix: "/employees"
  service:
    openApiSpec:
      path: /openapi.yaml
      port:
        number: 3000
    name: employee-app
    port:
      number: 3000
employee.yaml

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: employee-data
  namespace: apps
data:
  api.json: |
    {
      "employees": [
        { "id": 1, "firstName": "John", "lastName": "Doe", "role": "pilot", "homeAirport": "RIC" },
        { "id": 2, "firstName": "Jane", "lastName": "Doe", "role": "engineer", "homeAirport": "CDG" },
        { "id": 3, "firstName": "John", "lastName": "Smith", "role": "attendant", "homeAirport": "DTW" }
      ]
    }

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: employee-app
  namespace: apps
spec:
  replicas: 1
  selector:
    matchLabels:
      app: employee-app
  template:
    metadata:
      labels:
        app: employee-app
    spec:
      containers:
        - name: api
          image: douglasdtm/json-server:openapi
          args: ["--watch", "/api/api.json", "--static", "/public"]
          imagePullPolicy: IfNotPresent
          volumeMounts:
          - name: api-data
            mountPath: /api
          - name: openapi
            mountPath: /public
      volumes:
        - name: api-data
          configMap:
            name: employee-data
        - name: openapi
          configMap:
            name: employee-apispec

---
apiVersion: v1
kind: Service
metadata:
  name: employee-app
  namespace: apps
  labels:
    app: employee-app
spec:
  type: ClusterIP
  ports:
    - port: 3000
      name: api
  selector:
    app: employee-app
spec.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: employee-apispec
  namespace: apps
data:
  openapi.yaml: |
    openapi: "3.0.0"
    info:
      version: 1.0.0
      title: Employees
      description: OpenAPI specification based on Swagger Petstore (https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore.yaml).
    servers:
      - url: https://api.traefik.localhost
    paths:
      /employees:
        get:
          summary: Get employees
          operationId: getEmployees
          tags:
            - employees
          parameters:
            - name: limit
              in: query
              description: How many items to return at one time (max 100)
              required: false
              schema:
                type: integer
                maximum: 100
                format: int32
          responses:
            '200':
              description: A paged array of employees
              headers:
                x-next:
                  description: A link to the next page of responses
                  schema:
                    type: string
              content:
                application/json:    
                  schema:
                    $ref: "#/components/schemas/Employees"
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        post:
          summary: Create a employee
          operationId: createEmployee
          tags:
            - employees
          responses:
            '201':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
      /employees/{employeeId}:
        get:
          summary: Info for a specific employee
          operationId: showEmployeeById
          tags:
            - employees
          parameters:
            - name: employeeId
              in: path
              required: true
              description: The id of the employee
              schema:
                type: string
          responses:
            '200':
              description: Expected response to a valid request
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Employee"
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        put:
          summary: Update an employee
          operationId: updateEmployee
          tags:
            - employees
          parameters:
            - name: employeeId
              in: path
              required: true
              description: The id of the employee
              schema:
                type: string
          responses:
            '200':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        delete:
          summary: Delete a employee
          operationId: deleteEmployee
          tags:
            - employees
          parameters:
            - name: employeeId
              in: path
              required: true
              description: The id of the employee
              schema:
                type: string
          responses:
            '200':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
    components:
      schemas:
        Employee:
          type: object
          required:
            - id
            - firstName
            - lastName
          properties:
            id:
              type: integer
              format: int64
            firstName:
              type: string
            lastName:
              type: string
            role:
              type: string
            homeAirport:
              type: string
        Employees:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/Employee"
        Error:
          type: object
          required:
            - message
          properties:
            message:
              type: string

The following kubectl commands will deploy the API:

kubectl apply -f api.yaml \
              -f employee.yaml \
              -f spec.yaml

Flight API

Go back into your api-demo directory, and create a new directory called flight.

Once you have created it, change into it and create the following files:

api.yaml
---
apiVersion: hub.traefik.io/v1alpha1
kind: API
metadata:
  name: flight-api
  namespace: apps
  labels:
    area: flights
    module: erp
spec:
  pathPrefix: "/flights"
  service:
    openApiSpec:
      path: /openapi.yaml
      port:
        number: 3000
    name: flight-app
    port:
      number: 3000
flight.yaml

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: flight-data
  namespace: apps
data:
  api.json: |
    {
      "flights": [
        { "id": 1, "code": "TL123", "src": "JFK", "dest": "CDG" },
        { "id": 2, "code": "TL234", "src": "CDG", "dest": "JFK" },
        { "id": 3, "code": "TL345", "src": "CDG", "dest": "LYS" }
      ]
    }

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flight-app
  namespace: apps
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flight-app
  template:
    metadata:
      labels:
        app: flight-app
    spec:
      containers:
        - name: api
          image: douglasdtm/json-server:openapi
          args: ["--watch", "/api/api.json", "--static", "/public"]
          imagePullPolicy: IfNotPresent
          volumeMounts:
          - name: api-data
            mountPath: /api
          - name: openapi
            mountPath: /public
      volumes:
        - name: api-data
          configMap:
            name: flight-data
        - name: openapi
          configMap:
            name: flight-apispec

---
apiVersion: v1
kind: Service
metadata:
  name: flight-app
  namespace: apps
  labels:
    app: flight-app
spec:
  type: ClusterIP
  ports:
    - port: 3000
      name: api
  selector:
    app: flight-app
spec.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: flight-apispec
  namespace: apps
data:
  openapi.yaml: |
    openapi: "3.0.0"
    info:
      version: 1.0.0
      title: Flights
      description: OpenAPI specification based on Swagger Petstore (https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore.yaml).
    servers:
      - url: https://api.traefik.localhost
    paths:
      /flights:
        get:
          summary: Get flights
          operationId: gettFlights
          tags:
            - flights
          parameters:
            - name: limit
              in: query
              description: How many items to return at one time (max 100)
              required: false
              schema:
                type: integer
                maximum: 100
                format: int32
          responses:
            '200':
              description: A paged array of flights
              headers:
                x-next:
                  description: A link to the next page of responses
                  schema:
                    type: string
              content:
                application/json:    
                  schema:
                    $ref: "#/components/schemas/Flights"
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        post:
          summary: Create a flight
          operationId: createFlight
          tags:
            - flights
          responses:
            '201':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
      /flights/{flightId}:
        get:
          summary: Info for a specific flight
          operationId: showFlightById
          tags:
            - flights
          parameters:
            - name: flightId
              in: path
              required: true
              description: The id of the flight
              schema:
                type: string
          responses:
            '200':
              description: Expected response to a valid request
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Flight"
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        put:
          summary: Update a flight
          operationId: updateFlight
          tags:
            - flights
          parameters:
            - name: flightId
              in: path
              required: true
              description: The id of the flight
              schema:
                type: string
          responses:
            '200':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        delete:
          summary: Delete a flight
          operationId: deleteFlight
          tags:
            - flights
          parameters:
            - name: flightId
              in: path
              required: true
              description: The id of the flight
              schema:
                type: string
          responses:
            '200':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
    components:
      schemas:
        Flight:
          type: object
          required:
            - id
            - name
          properties:
            id:
              type: integer
              format: int64
            code:
              type: string
            source:
              type: string
            destination:
              type: string
        Flights:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/Flight"
        Error:
          type: object
          required:
            - message
          properties:
            message:
              type: string

The following kubectl commands will deploy the API:

kubectl apply -f api.yaml \
              -f flight.yaml \
              -f spec.yaml

Ticket API

Go back into your api-demo directory, and create a new directory called ticket.

Once you have created it, change into it and create the following files:

api.yaml
---
apiVersion: hub.traefik.io/v1alpha1
kind: API
metadata:
  name: ticket-api
  namespace: apps
  labels:
    area: support
    module: crm
spec:
  pathPrefix: "/tickets"
  service:
    openApiSpec:
      path: /openapi.yaml
      port:
        number: 3000
    name: ticket-app
    port:
      number: 3000
ticket.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: ticket-data
  namespace: apps
data:
  api.json: |
    {
      "tickets": [
        { "id": 1, "flightCode": "TL123", "fare": 500, "class": "first", "available": 5, "total": 20 },
        { "id": 2, "flightCode": "TL234", "fare": 200, "class": "economy", "available": 2, "total": 5 },
        { "id": 3, "flightCode": "TL345", "fare": 300, "class": "business", "available": 3, "total": 10 }
      ]
    }

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ticket-app
  namespace: apps
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ticket-app
  template:
    metadata:
      labels:
        app: ticket-app
    spec:
      containers:
        - name: api
          image: douglasdtm/json-server:openapi
          args: ["--watch", "/api/api.json", "--static", "/public"]
          imagePullPolicy: IfNotPresent
          volumeMounts:
          - name: api-data
            mountPath: /api
          - name: openapi
            mountPath: /public
      volumes:
        - name: api-data
          configMap:
            name: ticket-data
        - name: openapi
          configMap:
            name: ticket-openapi

---
apiVersion: v1
kind: Service
metadata:
  name: ticket-app
  namespace: apps
  labels:
    app: ticket-app
spec:
  type: ClusterIP
  ports:
    - port: 3000
      name: api
  selector:
    app: ticket-app
spec.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: ticket-openapi
  namespace: apps
data:
  openapi.yaml: |
    openapi: "3.0.0"
    info:
      version: 1.0.0
      title: Tickets
      description: OpenAPI specification based on Swagger Petstore (https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore.yaml).
    servers:
      - url: https://api.traefik.localhost
    paths:
      /tickets:
        get:
          summary: Get tickets
          operationId: getTickets
          tags:
            - tickets
          parameters:
            - name: limit
              in: query
              description: How many items to return at one time (max 100)
              required: false
              schema:
                type: integer
                maximum: 100
                format: int32
          responses:
            '200':
              description: A paged array of tickets
              headers:
                x-next:
                  description: A link to the next page of responses
                  schema:
                    type: string
              content:
                application/json:    
                  schema:
                    $ref: "#/components/schemas/Tickets"
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        post:
          summary: Create a ticket
          operationId: createTicket
          tags:
            - tickets
          responses:
            '201':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
      /tickets/{ticketId}:
        get:
          summary: Info for a specific ticket
          operationId: showTicketById
          tags:
            - tickets
          parameters:
            - name: ticketId
              in: path
              required: true
              description: The id of the ticket
              schema:
                type: string
          responses:
            '200':
              description: Expected response to a valid request
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Ticket"
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        put:
          summary: Update a ticket
          operationId: updateTicket
          tags:
            - tickets
          parameters:
            - name: ticketId
              in: path
              required: true
              description: The id of the ticket
              schema:
                type: string
          responses:
            '200':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
        delete:
          summary: Delete a ticket
          operationId: deleteTicket
          tags:
            - tickets
          parameters:
            - name: ticketId
              in: path
              required: true
              description: The id of the ticket
              schema:
                type: string
          responses:
            '200':
              description: Null response
            default:
              description: unexpected error
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Error"
    components:
      schemas:
        Ticket:
          type: object
          required:
            - id
            - flightCode
          properties:
            id:
              type: integer
              format: int64
            flightCode:
              type: string
            fare:
              type: integer
              format: int64
            class:
              type: string
            available:
              type: integer
              format: int64
            total:
              type: integer
              format: int64
        Tickets:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/Ticket"
        Error:
          type: object
          required:
            - message
          properties:
            message:
              type: string

The following kubectl commands will deploy the API:

kubectl apply -f api.yaml \
              -f ticket.yaml \
              -f spec.yaml

3. Configure API access

In step five, you will configure the access permissions for the API Gateway.
You will enable access for the group crm.

Follow our tutorial about user management to learn more about user and groups.

For doing so, change back into the top level directory of this tutorial, api-demo and create a file called api-access.yaml with the following content:

api-access.yaml
---
apiVersion: hub.traefik.io/v1alpha1
kind: APIAccess
metadata:
  name: customer-admin
spec:
  groups:
    - crm
  apiSelector:
    matchLabels:
      area: customers

---
apiVersion: hub.traefik.io/v1alpha1
kind: APIAccess
metadata:
  name: crm-apis
spec:
  groups:
    - crm
  apiSelector:
    matchLabels:
      module: crm

---
apiVersion: hub.traefik.io/v1alpha1
kind: APIAccess
metadata:
  name: crm-collections
spec:
  groups:
    - crm
  apiCollectionSelector:
    matchLabels:
      module: crm

---
apiVersion: hub.traefik.io/v1alpha1
kind: APIAccess
metadata:
  name: custom-pick
spec:
  groups:
    - crm
  apiSelector:
    matchExpressions:
      - key: area
        operator: In
        values:
          - flights
          - support

Use kubectl to deploy it:

kubectl apply -f api-access.yaml

4. Configure the API Gateway

In Traefik Hub, an API Gateway is the main entry point to all your APIs.

This is where you define the public domains for your APIs and which APIs and API collections you want to expose via an APIAccess CRD.

For doing so, change back into the top level directory of this tutorial, api-demo and create a file called api-gateway.yaml with the following content:

api-gateway.yaml
---
apiVersion: hub.traefik.io/v1alpha1
kind: APIGateway
metadata:
  name: my-gateway
  labels:
    area: crm
spec:
  apiAccesses:
    - customer-admin
    - crm-collections
    - crm-apis
    - custom-pick

Use kubectl to deploy it:

kubectl apply -f api-gateway.yaml

4.1 Access an API via the Gateway

kubectl get apigateway -n apps

Follow the URL in the output plus the API prefix you want to reach, for example

API Gateway

API Gateway

5. Configure the API Portal

In Traefik Hub, the API Portal is the landing page of an API or an API collection.

In the Portal, the consumer can view a representation of the OpenAPI specification and effortlessly interact and try out every API operation.

For doing so, change back into the top level directory of this tutorial, api-demo and create a file called api-portal.yaml with the following content:

api-portal.yaml
---
apiVersion: hub.traefik.io/v1alpha1
kind: APIPortal
metadata:
  name: my-preview-portal
spec:
  title: "Traefik Airlines"
  description: "Traefik Airlines API Portal"
  apiGateway: my-gateway

Use kubectl to deploy it:

kubectl apply -f api-portal.yaml

5.1 Access the API Portal

kubectl get apiportal -n apps

Follow the URL in the output

API Portal

API Portal

Congratulations, you deployed four APIs and set up an API Portal!


Summary

In this tutorial, you learned how to:

  • Deploy APIs with CRDs
  • Configures access and permissions of APIs and API collections
  • Configure an API Gateway
  • Expose an API Portal

What's next