This blogpost explains our CD (Continuous Deployment) pipeline, use it for
your own convenience and as always tips and remarks are welcome!
We're using the Kong API Gateway to handle all the
REST and SOAP calls to middleware and back-end (micro-)services.
Each API should be described as an Open API Specification file, with all the details of the API, Kong plugins and in case it's a REST service also the request and response schema's.
All these Open API Specifications (OAS files) are
stored in our on-premise Gitlab Repository server, combined with a Gitlab
Runners (agents) server to execute the pipelines for deployment.
Now let me describe our pipeline set-up, it consists
of eight steps:
1. Get the Open API Specification
2. Validate the Open API Specification
3. Generate the Kong decK file
4. Replace project specific variables
5. Validate Kong decK file
6. Synchronize (deploy) Kong artefacts
7. Remove Kong plugins from Open API Specification
8. Deploy Open API Specification to Portal or API Marketplace/Platform
An API Project in Gitlab consists of the pipeline (.gitlab-ci.yml file), which includes file variables.yml and the actual
pipeline in Project "library" and thirdly the API Design (OAS).
The OAS is either a yml file in the Gitlab project specified in file variables.yml or included in the Insomnia project in directory .insomnia. With Insomnia 2023.5.8 our teams can still Git Sync for free.
Step 1) Get the OAS, either variable OAS is present
in file variables.yml or it's exported from Insomnia executing inso export
spec within docker image kong-inso-8.4.5.
Results from this step are the oas spec name and the
actual oas.yml file.
Step 2) Validate OAS, within docker image
stoplight/spectral we download using curl the .spectral.yml ruleset from our
library project and execute spectral lint.
Our .spectral.yml extends: [[spectral:oas, all], [spectral:asyncapi, all]] and we've added some specific errors, like:
- we need contact name and email present, email should be a company email address
- the oas file need x-kong-plugin-application-registration present with value auto_approve set to false
Step 3) Generate Kong decK (decK is derived from the combination of words ‘declarative’ and ‘Kong’) file within docker image
kong-deck-1.36.1, after creating decK file we set the service protocol, host
and port from environment specific variables in variables.yml file, and we add
the project tag:
only:
- development
script:
- deck file openapi2kong --inso-compatible -s oas.yaml -o kong.yaml
- deck file patch -s kong.yaml -o kong.yaml
--selector="$..services[*]"
--value='protocol:"'"$DEV_PROTOCOL"'"'
- deck file patch -s kong.yaml -o kong.yaml
--selector="$..services[*]"
--value='host:"'"$DEV_HOST"'"'
- deck file patch -s kong.yaml -o kong.yaml
--selector="$..services[*]" --value='port:'$DEV_PORT''
- deck file add-tags -s kong.yaml -o kong.yaml $projectname
Some years ago we were adding upstream with targets,
but as we have a dedicated load balancer we don't need Kong to balance over
targets, also setting endpoint on service gives better overview of upstream systems in
Kong Manager.
Step 4) If the decK file contains project specific
placeholders which should be replaced by environment specific values we
add replace steps to the project .gitlab-ci.yml file.
The replacements can be done with simple linux commands within basic docker image linux-alpine-3.18.
Step 5) DecK validation, this step validates the Kong
decK file within image kong-deck-1.36.1, executing both deck gateway validate
and deck gateway diff.
We noticed that if a service has an existing
application_instance and service is renamed this leads to deletion of old and
creation of new service, validation step will pass but sync fails due to
existing reference.
Step 6) Synchronize (deploy) to Kong, executing deck
gateway sync within docker image kong-deck-1.36.1
Step 7) As the OAS contains Kong specific plugins that
we don't want to expose in the Developer Portal, or any API Platform, we remove
all the plugins.
For now we use hashtags within the OAS to specify
begin and end of a plugin, using linux script within docker image
linux-alpine-3.18 to remove everything between and including the hashtags.
In the future we might add smarter plugin removal
using yq, as Kong plugins are well defined objects starting with x-kong-plugin.
Step 8) Using docker image linux-alpine-3.18 with curl
included we can post the censored OAS to our Developer Portal.
Time for a small example, see the following snapshot of a
single API design, where there is a single path on Kong: /orders with the
following rules:
- If the optional HTTP Header field X-Order-Version
contains v2 the request should be routed to upstream
system ORDER_V2_HOST with path v2
- Else the request should be routed to default upstream system with path v1
This can be achieved with different projects/OAS, but
sometimes this is requested within the same spec:
/orders:
get:
#BEGIN_KONG_PLUGINS_1
x-kong-plugin-request-transformer-advanced:
name: request-transformer-advanced
config:
replace:
uri: /v1/orders
#END_KONG_PLUGINS_1
...
#BEGIN_KONG_PLUGINS_2
/orders[REMOVEME]:
get:
x-kong-route-defaults:
headers:
X-Order-Version:
- v2
x-kong-plugin-route-transformer-advanced:
name: route-transformer-advanced
config:
host: ORDER_V2_HOST
path: /v2/orders
#END_KONG_PLUGINS_2
This OAS is valid in all OAS editors like
Insomnia, Swagger etc.
The duplicated path is extended with [REMOVEME] to make the design a valid Open API Specification. After creating the decK file this [REMOVEME] will be removed in step 4. The resulted decK file remains valid for Kong and contains identical paths but with different HTTP Header configuration. ORDER_V2_HOST is an environment specific placeholder
which is replaced in step 4 by the value set in variables.yml.
In step 6 the service is deployed to Kong.
In step 7 the script removes everything between
#BEGIN_KONG_PLUGINS_1 and #END_KONG_PLUGINS_1 which is the Kong plugin changing the upstream uri. Also everything between
#BEGIN_KONG_PLUGINS_2 and #END_KONG_PLUGINS_2 is removed, which is the duplicated path.
Removal includes the lines starting with #, including additional comments starting with #.
After deployment in step 8 the result is a
single path visible in the design on the Developer Portal. The API consumers won't see the upstream systems and the
technical routing based on X-Order-Version. Surely the HTTP Header X-Order-Version
should be described as an optional header field with it's purpose.