Micro Kong

Jan. 2020

Micro Kong

Recently, I had the joy of putting together an environment for an API. I wanted to do things differently and securely. To do so, I decided to utilize Kong as an API gateway to the micro.mu API gateway. To get a better idea, I have implemented a structure like Nginx (reverse proxy) -> Kong (gateway)-> Micro API (gateway) -> APIs -> Services -> Database(s). To accomplish this system, using Docker-compose, I did the following:

version: '3.7'

services:
    #######################################
    # Etcd: Discovery service
    #######################################
    etcd:
        image: bitnami/etcd:latest
        ports:
            - "2379:2379"
            - "2380:2380"
        environment:
            ALLOW_NONE_AUTHENTICATION: "yes"
            ETCD_ADVERTISE_CLIENT_URLS: http://etcd:2379
        restart: unless-stopped

    #######################################
    # Microapi: The API gateway for micro
    #######################################
    microapi:
        image: microhq/micro:latest
        command: "--registry=etcd --registry_address=etcd:2379 api --handler=http"
        depends_on:
            - etcd
        ports:
            - 8080:8080
        restart: unless-stopped

    #######################################
    # Nginx: Proxy
    #######################################
    nginx-proxy:
        restart: unless-stopped
        image: jwilder/nginx-proxy
        ports:
            - "80:80"
            - "443:443"
        security_opt:
            - label:type:docker_t
        volumes:
            - ./docker/public:/usr/share/nginx/html
            - ./docker/certs:/etc/nginx/certs:ro
            - vhost:/etc/nginx/vhost.d
            - /var/run/docker.sock:/tmp/docker.sock:ro
        labels:
            com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true"
        depends_on:
            - kong

    #######################################
    # Kong: Database migration
    #######################################
    kong-migration:
        image: kong:1.4.3-alpine
        command: "kong migrations bootstrap"
        restart: on-failure
        environment:
            KONG_DATABASE: postgres
            KONG_PG_DATABASE: kong
            KONG_PG_HOST: microapi-database
            KONG_PG_USER: kong
            KONG_PG_PASSWORD: xio9alaitavooR5f
            KONG_PG_PORT: 5432
        depends_on:
            - microapi-database

    #######################################
    # Kong: The API Gateway
    #######################################
    kong:
        image: kong:1.4.3-alpine
        restart: unless-stopped
        environment:
            KONG_DATABASE: postgres
            KONG_PG_HOST: microapi-database
            KONG_PG_DATABASE: kong
            KONG_PG_USER: kong
            KONG_PG_PORT: 5432
            KONG_PG_PASSWORD: xio9alaitavooR5f
            KONG_PROXY_LISTEN: 0.0.0.0:8000
            KONG_PROXY_LISTEN_SSL: 0.0.0.0:8443
            KONG_ADMIN_LISTEN: 0.0.0.0:8001
            VIRTUAL_HOST: lin.ks
            VIRTUAL_PORT: 8000
        depends_on:
            - kong-migration
            - microapi-database
            - microapi
        healthcheck:
            test: ["CMD", "kong", "health"]
            interval: 5s
            timeout: 2s
            retries: 15
        ports:
            - "8001:8001"
            - "8000:8000"

    #######################################
    # Konga: Database prepare
    #######################################
    konga-prepare:
        image: pantsel/konga:next
        command: "-c prepare -a postgres -u postgresql://kong:xio9alaitavooR5f@microapi-database:5432/kong"
        restart: on-failure
        depends_on:
            - kong
            - microapi-database

    #######################################
    # Konga: GUI
    #######################################
    konga:
        image: pantsel/konga:next
        restart: unless-stopped
        environment:
            DB_ADAPTER: postgres
            DB_HOST: microapi-database
            DB_DATABASE: kong
            DB_USER: kong
            DB_PASSWORD: xio9alaitavooR5f
            TOKEN_SECRET: km1GUr4RkcQD7DewhJPNXrCuZwcKmqjb
            NODE_ENV: production
        depends_on:
            - konga-prepare
            - microapi-database
        ports:
            - "1337:1337"

    #######################################
    # Postgres: Common database
    #######################################
    microapi-database:
        container_name: postgres
        #        image: postgres:12.1
        image: postgres:9.6 # konga requires 9.6
        restart: unless-stopped
        environment:
            POSTGRES_USER: microapi
            POSTGRES_PASSWORD: xio9alaitavooR5f
            POSTGRES_MULTIPLE_DATABASES: kong,kong
        volumes:
            - ./docker/postgres/init:/docker-entrypoint-initdb.d
        ports:
            - "5432:5432"
        healthcheck:
            test: ["CMD", "pg_isready", "-U", "microapi"]
            interval: 5s
            timeout: 5s
            retries: 5

volumes:
    vhost:

So to briefly explain, Etcd is used as a discovery service for any micro related stuff. In the past, micro used Consul, and locally MDNS. If you decide to add any Golang services/APIs written with the micro framework, you will need to add --registry=etcd --registry_address=etcd:2379 to the command segment. This makes your app discoverable by Etcd. To utilize the micro api it is simple. Pull the micro image and append api --handler=http. We technically could use the nginx-proxy to the microapi; however, from what I read, thus far, micro does not have any security incorporated into this API gateway. Therefore, Kong was an obvious choice. To get Kong up and running we need to have postgres or cassandra running. I opted for postgres. In addition, we need to make a migration. I have also included prepartion for Konga, an admin GUI interface for Kong. This as well requires a preparation container.

On a side note, you may have noticed that I am using a volume for postgres. This is because I needed several databases. To accomplished this, I am loading a script into /docker-entrypoint-initdb.d

#!/bin/bash

set -e
set -u

function create_user_and_database() {
    local database=$(echo $1 | tr ',' ' ' | awk  '{print $1}')
    local owner=$(echo $1 | tr ',' ' ' | awk  '{print $2}')
    echo "  Creating user and database '$database'"
    psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
        CREATE ROLE $owner LOGIN PASSWORD '$POSTGRES_PASSWORD';
        CREATE DATABASE $database;
        GRANT ALL PRIVILEGES ON DATABASE $database TO $owner;
EOSQL
}

if [ -n "$POSTGRES_MULTIPLE_DATABASES" ]; then
    echo "Multiple database creation requested: $POSTGRES_MULTIPLE_DATABASES"
    for db in $(echo $POSTGRES_MULTIPLE_DATABASES | tr ':' ' '); do
        create_user_and_database $db
    done
    echo "Multiple databases created"
fi

Back