11/27/2025

Hands-On Guide: Running WSO2 API Manager + APK Gateway on Minikube

 

This guide walks you through deploying a fully working API management platform locally using:

  • WSO2 API Manager (APIM)Control Plane

  • WSO2 API Platform for Kubernetes (APK)Kubernetes-native API Gateway

  • Minikube — local Kubernetes cluster

In this tutorial, you'll:

  • Deploy APIM control plane

  • Deploy APK gateway (Envoy-based)

  • Publish APIs from APIM

  • Deploy them to APK

  • Call them securely using HTTPS

  • Troubleshoot real-world issues (TLS, NodePorts, hostnames, routing)

This post is based on a real, complete setup — not a theoretical guide.


📘 Architecture



https://apk.docs.wso2.com/en/1.1.0/assets/img/deployment-patterns/APK_DP_TO_CP_K8s.png


Your final working architecture looks like this:

Client → APK Gateway (Envoy) → Backend (httpbin)

                ↑

        APIM Control Plane (Publisher/Devportal)



🧰 Prerequisites

  • Minikube (v1.30+)

  • kubectl

  • Docker

  • 4+ CPUs, 8–16GB RAM

  • Linux/macOS/WSL recommended


🟦 Step 1 — Start Minikube

minikube start --cpus=4 --memory=8192


Set namespace:

kubectl create namespace apk

kubectl config set-context --current --namespace=apk



🟩 Step 2 — Enable Ingress

APK requires ingress for hostname-based routing.

minikube addons enable ingress

minikube addons enable metallb

Verify:


$ kubectl get pods -n ingress-nginx

NAME                                       READY   STATUS    RESTARTS   AGE

ingress-nginx-controller-67c5cb88f-vff6x   1/1     Running   0          18h



🟪 Step 3 — Install APIM Control Plane + APK Agent

Again directly from the Quick Start With Control Plane: apk.docs.wso2.com

# Add APK Helm repo

helm repo add wso2apk https://github.com/wso2/apk/releases/download/1.3.0-1


helm repo update


# Install Kubernetes Gateway data plane

helm install apk wso2apk/apk-helm \

  --version 1.3.0-1 \

  -f https://raw.githubusercontent.com/wso2/apk/refs/tags/1.3.0-1/helm-charts/samples/apk/1.3.0-1-cp-enabled-values.yaml \

  -n apk

NAME: apk

LAST DEPLOYED: Thu Nov 27 09:49:57 2025

NAMESPACE: apk

STATUS: deployed

REVISION: 1

TEST SUITE: None

NOTES:

Welcome to the WSO2 API Platform for Kubernetes!


Congratulations. You've successfully deployed WSO2 APK using Helm, you'll need to monitor and manage the deployment to ensure everything is running smoothly.


   - Monitor Pods:

     Check the status of the pods to ensure they are up and running:

     ---

     kubectl get pods

     ---

   

   - Monitor Services:

     Verify that the services are running and find their external IPs to access the APIs:

     ---

     kubectl get services

     ---


For more detailed information, troubleshooting, and advanced configurations, we encourage you to explore the official WSO2 documentation.


- APK Documentation: [https://apk.docs.wso2.com/en/latest/get-started/quick-start-guide/]


This is just the beginning of your APK journey. Feel free to customize and tailor your deployment to match your organization's specific needs.


For any questions or assistance, don't hesitate to reach out to our discord channel.


Happy API management with WSO2 APK!



$ kubectl get pod

NAME                                                         READY   STATUS      RESTARTS        AGE

apim-wso2am-acp-deployment-1-7f868d47b8-hbl7l                1/1     Running     0               4h13m




🟧 Step 4 — Install WSO2 APK Gateway


Add repo:

# Helm repo for the APIM–APK agent

helm repo add wso2apkagent https://github.com/wso2/product-apim-tooling/releases/download/1.3.0

helm repo update


Install APK:

# Install the agent

helm install apim-apk-agent wso2apkagent/apim-apk-agent \

  --version 1.3.0 \

  -f https://raw.githubusercontent.com/wso2/apk/main/helm-charts/samples/apim-apk-agent/1.3.0-values.yaml \

  -n apk


Verify:

kubectl get pods -n apk


Expected components:

  • apk-wso2-apk-adapter

  • apk-wso2-apk-gateway-runtime (Envoy + enforcer)

  • apk-wso2-apk-common-controller

  • apk-wso2-apk-config-ds

  • apk-wso2-apk-idp

  • redis

  • cert-manager

APK gateway is now running.



🟫 Step 5 — Configure Local Hostnames

Find Minikube IP:

minikube ip


http://192.168.58.2:30349


$ kubectl get httproutes -n apk



Add these to /etc/hosts:

192.168.58.2 am.wso2.com

192.168.58.2 api.am.wso2.com

192.168.58.2 idp.am.wso2.com


These hostnames are used by APIM and APK.


🟩 Step 6 — Access APIM


Disable HSTS for that domain (Chrome advanced)

Not recommended
⚠ Works only if Chrome does NOT preload HSTS for that domain
(am.wso2.com is in preload → so this won’t work)

But for future reference:

chrome://net-internals/#hsts

Delete domain under “Delete domain security policies”.


Publisher:

https://am.wso2.com/publisher


Devportal:

https://am.wso2.com/devportal


Login:

admin / admin




🟥 Step 7 — Create & Deploy an API (EchoAPI)

1. Create API in Publisher

Publisher → Create API → REST API

  • Name: EchoAPI

  • Context: /echo

  • Version: 1.0.0

  • Backend URL: https://httpbin.org/anything

Click Create.

2. Deploy to APK

Go to Deployments → Deploy New Revision
Select Kubernetes Gateway environment.


3. Publish API


  • Save this generated/default key

Go to Lifecycle → Publish.



🟦 Step 8 — Verify Deployment in APK

kubectl get httproutes -n apk



You should see:

["default.gw.wso2.com"]


That means APIM → APK sync is working.

Make sure DNS is fine


Include gateway host entry in /etc/hosts.


192.168.58.2  default.gw.wso2.com




🟨 Step 9 — How to call the API from host (externally)

minikube service apk-wso2-apk-gateway-service -n apk --url


Example:

http://192.168.58.2:32513


🔹 Inside the pod → port 9095 is HTTPS
🔹 NodePort 32513 also expects HTTPS


Check logs for the gateway

kubectl logs deploy/apk-wso2-apk-gateway-runtime-deployment -n apk -c router | tail -n 40


kubectl logs deploy/apk-wso2-apk-gateway-runtime-deployment -n apk -c enforcer | tail -n 40


Call the gateway with HTTPS + hostname

Use the hostname in the URL so SNI matches:

# Internal key from Devportal

TOKEN="<your_internal_key_here>"


curl -v -k "https://default.gw.wso2.com:32513/echo/1.0.0" \

  -H "Internal-Key: $TOKEN"


Notes:

  • https:// because the listener is HTTPS.

  • default.gw.wso2.com in URL so TLS SNI matches the cert/vHost.

  • -k to ignore self-signed cert.

  • Use the Internal-Key header because your JWT has "token_type":"InternalKey".

If instead you generate a Production OAuth token in Devportal, then:

OAUTH_TOKEN="<your_oauth_access_token>"


curl -v -k "https://default.gw.wso2.com:32513/echo/1.0.0" \

  -H "Authorization: Bearer $OAUTH_TOKEN"


You should see a 200 OK and the JSON echoed from https://httpbin.org/anything.




🟩 Step 10 Why this will work (quick recap)


  • Service: apk-wso2-apk-gateway-service
    9095:32513/TCP (HTTPS_9095_listener in Envoy)

  • NodePort: 32513 speaks HTTPS, not HTTP.

  • httproutes show your API is bound to default.gw.wso2.com.

  • Router/enforcer logs show they eventually connect to the control-plane and load config successfully.

So the only missing piece was:

use HTTPS + correct hostname + correct auth header.


$ curl -v -k "https://default.gw.wso2.com:32513/echo/1.0.0" \

  -H "Internal-Key: $TOKEN"

*   Trying 192.168.58.2:32513...

* Connected to default.gw.wso2.com (192.168.58.2) port 32513 (#0)

* ALPN, offering h2

* ALPN, offering http/1.1

* TLSv1.0 (OUT), TLS header, Certificate Status (22):

* TLSv1.3 (OUT), TLS handshake, Client hello (1):

* TLSv1.2 (IN), TLS header, Certificate Status (22):

* TLSv1.3 (IN), TLS handshake, Server hello (2):

* TLSv1.2 (IN), TLS header, Finished (20):

* TLSv1.2 (IN), TLS header, Supplemental data (23):

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

* TLSv1.3 (IN), TLS handshake, Certificate (11):

* TLSv1.3 (IN), TLS handshake, CERT verify (15):

* TLSv1.3 (IN), TLS handshake, Finished (20):

* TLSv1.2 (OUT), TLS header, Finished (20):

* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

* TLSv1.3 (OUT), TLS handshake, Finished (20):

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

* ALPN, server accepted to use h2

* Server certificate:

*  subject: [NONE]

*  start date: Nov 27 09:34:55 2025 GMT

*  expire date: Feb 25 09:34:55 2026 GMT

*  issuer: CN=apk

*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.

* Using HTTP2, server supports multiplexing

* Connection state changed (HTTP/2 confirmed)

* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

* Using Stream ID: 1 (easy handle 0x5d7a71ba39f0)

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

> GET /echo/1.0.0 HTTP/2

> Host: default.gw.wso2.com:32513

> user-agent: curl/7.81.0

> accept: */*

> internal-key: eyJraWQiOiJnYXRld2F5X2NlcnRpZmljYXRlX2FsaWFzIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJhZG1pbkBjYXJib24uc3VwZXIiLCJhdWQiOiI0OTczZWU4ZC01ZWZlLTQ5NWItYjhiOC01ZDY3ZjU2Yzk4NjIiLCJpc3MiOiJodHRwOi8vYW0ud3NvMi5jb206NDQzL3Rva2VuIiwia2V5dHlwZSI6IlBST0RVQ1RJT04iLCJzdWJzY3JpYmVkQVBJcyI6W3sibmFtZSI6IkVjaG9BUEkiLCJjb250ZXh0IjoiL2VjaG8vMS4wLjAiLCJ2ZXJzaW9uIjoiMS4wLjAiLCJwdWJsaXNoZXIiOiJhZG1pbiIsInN1YnNjcmlwdGlvblRpZXIiOm51bGwsInN1YnNjcmliZXJUZW5hbnREb21haW4iOm51bGx9XSwiZXhwIjoxNzY0Mjk4MTIwLCJ0b2tlbl90eXBlIjoiSW50ZXJuYWxLZXkiLCJpYXQiOjE3NjQyMzgxMjAsImp0aSI6IjkxOTlmNjk2LTVmMzctNGMyNi1hZDIxLTlkZjE3MmI3MTRmMiJ9.mJ6_qwcu6oX5YMpuB7aUeoAl9P-q3yPr_0YukbWq_hTBd2mxvn8jNfUDbCig19DyX8UQ4O5cUfPV3-Id2_Q23K0rdCqCoNKZGX-AsYU54eI-hPjPWvrz3UIlrsiMmMu_fpWdbOE8WXbKRhRE44JRVY5W8NWLAlunZf5Z9bWni0qZbdBhqszJ55OWDRgtmIoxE1TVW_9wt-eYsY-g5yk8uyTdV0oICFbkcMKPN_MPkMsibciP88nRrXaniXGZDE8xVtMSbaKt9NYaRgttveoA4Wru8UKTAku3e6rOJNavRTsMh1lUIn7h39A53UtkmoEn-LOraHqjavA9ObkBatiYpQ

* TLSv1.2 (IN), TLS header, Supplemental data (23):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* old SSL session ID is stale, removing

* TLSv1.2 (IN), TLS header, Supplemental data (23):

* Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)!

* TLSv1.2 (OUT), TLS header, Supplemental data (23):

* TLSv1.2 (IN), TLS header, Supplemental data (23):

< HTTP/2 200 

< date: Thu, 27 Nov 2025 10:45:02 GMT

< content-type: application/json

< content-length: 341

< server: envoy

< access-control-allow-origin: *

< access-control-allow-credentials: true

< vary: Accept-Encoding

{

  "args": {}, 

  "data": "", 

  "files": {}, 

  "form": {}, 

  "headers": {

    "Accept": "*/*", 

    "Host": "httpbin.org", 

    "User-Agent": "curl/7.81.0", 

    "X-Amzn-Trace-Id": "Root=1-69282bad-019b76fc70de68d24dc4234b"

  }, 

  "json": null, 

  "method": "GET", 

  "origin": "92.244.7.43", 

  "url": "https://httpbin.org/anything"

}

* Connection #0 to host default.gw.wso2.com left intact



🎉 Conclusion

You now have a fully working:

  • WSO2 API Manager control plane

  • WSO2 APK gateway (Envoy)

  • API deployment + publishing

  • HTTPS API invocation via NodePort

  • End-to-end Envoy routing

This is the recommended WSO2 architecture for Kubernetes.


No comments:

Post a Comment