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
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
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