API-Management mit Kong – Ein Update und mehr

Keine Kommentare

Seit dem letzten Blogpost zu diesem Thema von Alexander Melnyk sind fast zwei Jahre vergangen, und es ist in Sachen „API-Management mit Kong“ eine Menge passiert. Daher war es an der Zeit, zum einen die Inhalte des Posts von Alexander zu aktualisieren und zum anderen das Thema „API-Management mit Kong“ und das Produkt Kong detaillierter zu betrachten. Dieser Post stellt den Beginn einer Serie von Beiträgen dar, welche in nächster Zeit erscheinen werden. Der Schwerpunkt der Serie wird erst einmal auf dem Open-Source-Produkt liegen, d.h. auf dem API-Gateway Kong.

Aufbau der Infrastruktur

Um euch nicht mit Inhalten aus den Release Notes zu langweilen, wird direkt technisch eingestiegen. Die Grundlage für die folgenden Betrachtungen stellt die Infrastruktur auf Basis eines docker-compose-File dar, welches im GitHub-Repo zum Artikel gefunden werden kann. Zudem benötigt man ein API. Hierfür wird das Python-Framework FastAPI verwendet, welches das API in Form von drei Endpunkten (/(root), service1, service2) in einem Container mittels Dockerfile bereitstellt. Auch das API ist ein Teil des Repos.

from fastapi import FastAPI
 
app = FastAPI()
 
@app.get("/")
def read_root():
    return {"Hello": "World"}
 
@app.get("/api/service1")
def read_service1():
    return {"status_code": 200, "message": "service1 is called"}
 
@app.get("/api/service2")
def read_service2():
    return {"status_code": 200, "message": "service2 is called"}
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
 
COPY ./app /app
version: '3'
networks:
  kong-blogposts:
    driver: bridge
 
services:
  api-service:
    build: ./api-service
    networks:
      - kong-blogposts
    expose:
      - 80
    ports:
      - "80:80"
 
  kong-database:
    container_name: kong-database
    image: postgres:9.6
    restart: always
    networks:
      - kong-blogposts
    environment:
      - POSTGRES_USER=kong
      - POSTGRES_DB=kong
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "kong"]
      interval: 10s
      timeout: 5s
      retries: 5
 
  kong-migration:
    image: kong
    depends_on:
      - "kong-database"
    container_name: kong-migration
    networks:
      - kong-blogposts
    restart: on-failure
    environment:
      - KONG_DATABASE=postgres
      - KONG_PG_HOST=kong-database
      - KONG_PG_DATABASE=kong
    command: kong migrations bootstrap
 
  kong:
    container_name: kong
    image: kong:latest
    depends_on:
      - "kong-migration"
      - "kong-database"
    restart: always
    networks:
      - kong-blogposts
    environment:
      - KONG_DATABASE=postgres
      - KONG_PG_HOST=kong-database
      - KONG_PG_DATABASE=kong
      - KONG_PROXY_LISTEN=0.0.0.0:8000
      - KONG_ADMIN_LISTEN=0.0.0.0:8001
    ports:
      - 8000:8000
      - 8001:8001
      - 8443:8443
      - 8444:8444
    healthcheck:
      test: ["CMD-SHELL","curl -I -s -L http://localhost:8000 || exit 1"]
      interval: 5s
      retries: 10

Die docker-compose-Definition erzeugt die drei Services kong, kong-database und kong-migration. Als Datenbank bzw. DataStore-Komponente werden wir PostgreSQL benutzen. Der kong-Service als API-Gateway stellt vier Ports für zwei Endpunkte bereit, zum einen den Consumer- und zum anderen den Admin-Endpunkt, jeweils für http und https.

Um den kong-Service zu betreiben, wird der kong-migration-Service zur initialen Generierung der Objekte in der kong-database verwendet. Das Befüllen der Datenbank wird leider nicht durch den kong-service erledigt. Die Services werden mit docker-compose up gestartet. Mit dem Befehl docker-compose ps erhalten wir nun eine Übersicht der laufenden Dienste.

Kong: Übersicht über laufende Dienste

Zuerst schaut man, ob Kong erreichbar ist. Hierzu überprüft man den Status des API-Gateways mit einer GET-Anfrage an das Admin-API. Der Aufruf http localhost:8001/status sollte den Statuscode 200 zurückliefern. Hierzu benutze ich persönlich das Tool httpie.

Von Services, Routen, Consumern und Plugins

Man sieht, dass der Zugriff auf das Kong-Admin-API funktioniert, aber bisher noch keine APIs konfiguriert sind. Nun wird ein API hinzugefügt, das aus einem Service und Route besteht. Hier zeigt sich eine Veränderung des Admin API. Ab der Version 0.13 wurden Routen und Services für eine bessere Abgrenzung und die Möglichkeit, Plugins auf bestimmte Endpunkte anzuwenden, eingeführt. Um einen Service anzulegen, sendet man einen POST-Request an das Gateway (localhost:8001/services).

http POST localhost:8001/services/ name=service1 url=http://host.docker.internal/api/service1

Für dieses kurze Einführungsbeispiel werden die anderen möglichen Parameter nicht betrachtet. Da man Kong innerhalb von Docker einsetzt, ist es wichtig die host.docker.internal IP zu verwenden. Sonst kommt es zu Schwierigkeiten beim Aufruf des API über das API-Gateway.

Nachdem der Service angelegt wurde, muss außerdem noch eine Route erstellt werden.

http POST localhost:8001/services/service1/routes paths:='["/service1"]' name=service1_route methods:='["GET"]'

Über http localhost:8000/service1 lässt sich der Service aufrufen und liefert das folgende Ergebnis zurück.

HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 50
Content-Type: application/json
Via: kong/1.3.0
X-Kong-Proxy-Latency: 7
X-Kong-Upstream-Latency: 5
server: uvicorn
 
{
   "message": "service1 is called",
   "status_code": 200
}

Im Regelfall möchte man ja sein API vor unberechtigten Zugriffen schützen bzw. nur dezidierten Usern Zugriff gewähren. In Kong verwendet man hierfür Plugins, die während eines Requests ausgeführt werden. Um entsprechende technische User zur Verfügung zu stellen, bietet Kong eine weitere Entität an: die Consumer.

http post localhost:8001/consumers username=api-user

HTTP/1.1 201 Created
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 120
Content-Type: application/json; charset=utf-8
Date: Sun, 01 Sep 2019 10:02:54 GMT
Server: kong/1.3.0
 
{
    "created_at": 1567332174,
    "custom_id": null,
    "id": "a37333ea-c346-488b-a1f0-1a0b078ea152",
    "tags": null,
    "username": "api-user"
}

Man fügt dem API das Key Authentication Plugin hinzu und verbindet ebenfalls den Consumer damit. Dadurch wird sichergestellt, dass eben nur dieser Consumer mit einem bestimmten Schlüssel auf das API zugreifen kann.

http post localhost:8001/services/service1/plugins name=key-auth

HTTP/1.1 201 Created
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 380
Content-Type: application/json; charset=utf-8
Server: kong/1.3.0
 
{
    "config": {
        "anonymous": null,
        "hide_credentials": false,
        "key_in_body": false,
        "key_names": [
            "apikey"
        ],
        "run_on_preflight": true
    },
    "consumer": null,
    "created_at": 1567332763,
    "enabled": true,
    "id": "a3b0ea80-98ba-43ed-a3dd-9fb5e1b0bbad",
    "name": "key-auth",
    "protocols": [
        "grpc",
        "grpcs",
        "http",
        "https"
    ],
    "route": null,
    "run_on": "first",
    "service": {
        "id": "3d0d837d-8d42-4764-9111-16f195baf762"
    },
    "tags": null
}

Es folgt die Überprüfung, ob das Plugin eingerichtet ist.

http localhost:8000/service1

HTTP/1.1 401 Unauthorized
Connection: keep-alive
Content-Length: 41
Content-Type: application/json; charset=utf-8
Server: kong/1.3.0
WWW-Authenticate: Key realm="kong"
 
{
    "message": "No API key found in request"
}

Nun muss noch ein Schlüssel für den Consumer api-user erstellt werden. Falls kein key angegeben ist, wird dieser automatisch erstellt.

http post localhost:8001/consumers/api-user/key-auth key=secret_key

HTTP/1.1 201 Created
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 145
Content-Type: application/json; charset=utf-8
Server: kong/1.3.0
 
{
    "consumer": {
        "id": "a37333ea-c346-488b-a1f0-1a0b078ea152"
    },
    "created_at": 1567333309,
    "id": "e65bcc7a-9478-40cc-be6c-0df43bad5b03",
    "key": "secret_key"
}

Der Aufruf des API erfolgt mit dem api-key und liefert das folgende Ergebnis zurück.

http localhost:8000/service1 apikey:secret_key

HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 50
Content-Type: application/json
Via: kong/1.3.0
X-Kong-Proxy-Latency: 8
X-Kong-Upstream-Latency: 35
date: Sun, 01 Sep 2019 10:27:25 GMT
server: uvicorn
 
{
    "message": "service1 is called",
    "status_code": 200
}

Alle gezeigten Schritte lassen sich jetzt auch auf den zweiten Service (service2) anwenden. Wenn dies vollzogen ist, sind beide Services durch Kong abgesichert.

Ausblick

Schließlich haben wir auch das Ende des ersten Teils der Serie zu Kong erreicht. Ich hoffe, dass Euch dieser erste Post einen Einblick in die Funktionsweise und erste Änderung des Kong-Admin-API gegeben hat. In den folgenden Teilen werde ich neben Kong auch auf Kong Enterprise, die Service Control Platform, eingehen. Seid also gespannt!

Daniel Kocot

Von Oktober 2016 an ist Daniel ein Teil des Teams der codecentric am Standort in Solingen. Schon seit Anfang der 2000er Jahre widmet er sich dem Thema der „Digitalen Transformation“. Neben den aktuellen Schwerpunkten API-Management, Application Lifecycle Management Tooling, Continuous Documentation und VoiceUI, ist er auch Experte für den Einsatz von Produktinformationssystemen (PIM) und Database-Publishing mit Hilfe von Rendering-Technologien.

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.