Search
Duplicate
🤍

API Gateway Part 2 - Kong 도입(1)

이전편 링크

시스템 요구사항 및 설정

시스템 요구사항

공식문서에 따르면, 최소 4 CPU cores, 16GB 램, 24GB의 디스크 저장공간을 필요로 한다. AWS 환경에서는 c4.2xlarge 인스턴스(8 CPU cores with 16GB RAM)을 추천한다.

설정

공식 패키지로 설치를 진행할 경우 /etc/kong/kong.conf.default 기본 설정 파일이 함께 제공된다. 실행시 /etc/kong/kong.conf/etc/kong.conf 파일을 찾지만, -c / --conf 옵션으로 설정 파일을 지정할 수 있다.
$ kong start --conf /path/to/kong.conf
Bash
kong.conf 파일의 설정 값은 KONG_ 으로 시작하는 환경변수를 지정해서 덮어쓸 수 있다. (Docker 구성등에 유용하다) 예를 들어
log_level = debug # in kong.conf
Plain Text
위의 kong.conf 항목은, 다음과 같은 환경변수로 덮어쓸 수 있다.
$ export KONG_LOG_LEVEL=error
Bash

Declarative Config and DB-less Mode

Kong 은 기본적으로 PostgreSQL 또는 Cassandra 와 함께 사용하도록 되어 있으나, DB-less mode 로 실행할 수 있다.
Kong 의 DB-less Mode 는 Kong 1.1 이후 버전에서 Declarative Config 와 함께 사용 가능하며, 이는 YAML 또는 JSON file 에 설정 정보를 저장하여 사용하는 방식이다. 이 방법의 장점은 아래와 같다.
장점: - Kong 설정 전체를 단일 YAML(또는 JSON) 파일에 저장하여 오류가 발생할 확률을 줄이고 관리를 간편하게 한다. - 설정 파일을 사용함으로서 배포 파이프라인을 자동화할 수 있다. - DB 를 사용하지 않음으로서 리소스를 절약할 수 있다.
단점: - 일부 DB에 의존적인 Plugin 을 사용할 수 없다. - RESTful API 를 이용해 Kong Cluster 를 관리할 수 있는 Admin API 를 사용할 수 없다. (읽기용도로만 사용가능) 설정을 변경하기 위해서는 설정파일을 수정한 후 재배포해야 한다.
DB-less Mode 를 사용함으로서 몇몇 Admin API 는 Read-Only 로만 사용가능하게 된다. (/services/plugins API) 그리고 aclbasic-authhmac-authjwtkey-auth 등은 일부 기능이 사용부가능 하게 되며, DB 기반으로 동작하는 rate-limitingresponse-ratelimiting 플러그인은 사용할 수 없게 된다.

도입 과정

도입 전 테스트

개발자의 로컬 환경에 Kong 을 설치하고 설정하며 목표했던 Routing 에 대한 테스트를 진행하였다. 맥 환경에서는 homebrew 를 이용해 어렵지 않게 설치할 수 있었으며, 추가로 Konga 대시보드를 설치하고 mock route 들을 연결해 기본적인 기능이 정상적으로 동작하는 것을 확인할 수 있었다. (윈도 환경의 경우 Docker 이미지를 이용해 쉽게 테스트해볼 수 있다)

개발 환경에 설치

OSX 환경에 Kong 을 설치하고 테스트하는 것은 굉장히 간단하다.

homebrew 를 이용한 설치

$ brew tap kong/kong $ brew install kong
Bash
설치가 잘 되었다면, 재미삼아 roar 명령을 실행해보자. Kong, Monolith destroyer!!!
$ kong roar Kong, Monolith destroyer. /\ ____ <> ( oo ) <>_| ^^ |_ <> @ \ /~~\ . . _ | /~~~~\ | | /~~~~~~\/ _| | |[][][]/ / [m] |[][][[m] |[][][]| |[][][]| |[][][]| |[][][]| |[][][]| |[][][]| |[][][]| |[][][]| |[|--|]| |[| |]| ======== ========== |[[ ]]| ==========
Bash

DB-less 설정으로 시작하기

$ sudo mkdir -p /etc/kong $ cd /etc/kong $ kong config init # kong.yml 파일이 생성된다.
Bash
kong.conf 은 아래와 같이 작성한다.
database = off declarative_config = /etc/kong/kong.yml
Bash

테스트 요청

시작해보자.
$ kong start -c /etc/kong.conf --vv # --vv 옵션으로 debug 정보가 표시된다. 2019/09/24 11:27:46 [verbose] Kong: 1.2.1 2019/09/24 11:27:46 [debug] ngx_lua: 10013 2019/09/24 11:27:46 [debug] nginx: 1013006 2019/09/24 11:27:46 [debug] Lua: LuaJIT 2.1.0-beta3 2019/09/24 11:27:46 [verbose] reading config file at ./kong.conf 2019/09/24 11:27:46 [debug] reading environment variables 2019/09/24 11:27:46 [debug] admin_access_log = "logs/admin_access.log" 2019/09/24 11:27:46 [debug] admin_error_log = "logs/error.log" 2019/09/24 11:27:46 [debug] admin_listen = {"127.0.0.1:8001","127.0.0.1:8444 ssl"} 2019/09/24 11:27:46 [debug] anonymous_reports = true 2019/09/24 11:27:46 [debug] cassandra_consistency = "ONE" 2019/09/24 11:27:46 [debug] cassandra_contact_points = {"127.0.0.1"} 2019/09/24 11:27:46 [debug] cassandra_data_centers = {"dc1:2","dc2:3"} 2019/09/24 11:27:46 [debug] cassandra_keyspace = "kong" ......
Bash
homebrew 를 이용해 설치했을 때 로그 파일 경로는 /usr/local/opt/kong/logs 이다. 다른 터미널을 하나 더 열어, tail -f /usr/local/opt/kong/logs/*.log 명령을 실행하면 부가적인 정보들을 볼 수 있어서 편리하다. 기본 설정으로는 8000 포트에서 실행되며, 8001포트를 통해 관리용 API 에 접근할 수 있다. (HTTP 요청을 위해 httpie 를 사용했다)
$ http :8000/ HTTP/1.1 404 Not Found Connection: keep-alive Content-Length: 48 Content-Type: application/json; charset=utf-8 Date: Tue, 24 Sep 2019 02:32:03 GMT Server: kong/1.2.1 { "message": "no Route matched with those values" }
Bash
등록한 Route 가 없기 때문에 / 엔드포인트는 404 Not Found 응답을 리턴했다. 중간에 Server: kong/1.2.1 요청을 보면 서비스가 정상 동작하는 것을 확인할 수 있다. 관리 인터페이스를 호출해보자.
# 기본 설정 출력 $ http :8001/ HTTP/1.1 200 OK Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 6266 Content-Type: application/json; charset=utf-8 Date: Tue, 24 Sep 2019 02:34:58 GMT Server: kong/1.2.1 { "configuration": { "admin_acc_logs": "/usr/local/opt/kong/logs/admin_access.log", "admin_access_log": "logs/admin_access.log", "admin_error_log": "logs/error.log", "admin_listen": [ "127.0.0.1:8001", "127.0.0.1:8444 ssl" ], "admin_listeners": [ { "http2": false, "ip": "127.0.0.1", "listener": "127.0.0.1:8001", "port": 8001, "proxy_protocol": false, "ssl": false, "transparent": false }, { "http2": false, "ip": "127.0.0.1", "listener": "127.0.0.1:8444 ssl", "port": 8444, "proxy_protocol": false, "ssl": true, "transparent": false } ], ...... # Route 관련 설정 출력 $ http :8001/routes HTTP/1.1 200 OK Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 23 Content-Type: application/json; charset=utf-8 Date: Tue, 24 Sep 2019 02:35:35 GMT Server: kong/1.2.1 { "data": [], "next": null } # 등록된 Services 정보 출력 $ http :8001/services HTTP/1.1 200 OK Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 23 Content-Type: application/json; charset=utf-8 Date: Tue, 24 Sep 2019 02:36:12 GMT Server: kong/1.2.1 { "data": [], "next": null }
Bash
:8001/services 및 :8001/routes API를 호출해보면 등록된 서비스가 없음을 확인할 수 있다.

Routing 테스트

테스트를 위해 외부서비스의 응답을 리턴하는 서비스와 Route 를 등록해보자. 등록할 서비스는 https://jsonplaceholder.typicode.com 이다. 간단하게 살펴보면, 아래와 같은 application/json 응답을 Mock 해주는 서비스이다.
$ http https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1 200 OK Access-Control-Allow-Credentials: true ...... X-Content-Type-Options: nosniff X-Powered-By: Express { "completed": false, "id": 1, "title": "delectus aut autem", "userId": 1 }
Bash
이전에 자동생성된 kong.yml 파일을 열고 아래와 같이 수정한다.
_format_version: "1.1" services: - name: json-placeholder url: https://jsonplaceholder.typicode.com routes: - name: json-placeholder-route paths: - /json
Bash
그리고 Kong 서비스를 재시작한다.
$ kong stop $ kong start -c /etc/kong.conf --vv
Bash
등록된 Services 와 Routes 를 볼 수 있는 관리용 API 를 테스트하면 아래와 같이 변경된 것을 확인할 수 있다.
$ http :8001/services HTTP/1.1 200 OK Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 313 Content-Type: application/json; charset=utf-8 Date: Tue, 24 Sep 2019 02:45:16 GMT Server: kong/1.2.1 { "data": [ { "connect_timeout": 60000, "created_at": 1569293109, "host": "jsonplaceholder.typicode.com", "id": "606b7048-c79b-5a39-8ee0-7a19ee6ab2a3", "name": "json-placeholder", "path": null, "port": 443, "protocol": "https", "read_timeout": 60000, "retries": 5, "tags": null, "updated_at": 1569293109, "write_timeout": 60000 } ], "next": null } $ http :8001/routes HTTP/1.1 200 OK Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 431 Content-Type: application/json; charset=utf-8 Date: Tue, 24 Sep 2019 02:45:21 GMT Server: kong/1.2.1 { "data": [ { "created_at": 1569293109, "destinations": null, "hosts": null, "https_redirect_status_code": 426, "id": "dc767ea0-e1dc-52d9-a0dc-32e55b1fec48", "methods": null, "name": "json-placeholder-route", "paths": [ "/json" ], "preserve_host": false, "protocols": [ "http", "https" ], "regex_priority": 0, "service": { "id": "606b7048-c79b-5a39-8ee0-7a19ee6ab2a3" }, "snis": null, "sources": null, "strip_path": true, "tags": null, "updated_at": 1569293109 } ], "next": null }
Bash
로컬에 설치된 Kong 서비스를 통해 jsonplaceholder 를 요청해보자.
$ http :8000/json/todos/1 HTTP/1.1 200 OK Access-Control-Allow-Credentials: true CF-Cache-Status: MISS CF-RAY: 51b16c612df39845-LAX Cache-Control: public, max-age=14400 Connection: keep-alive Content-Encoding: gzip Content-Type: application/json; charset=utf-8 Date: Tue, 24 Sep 2019 02:45:38 GMT Etag: W/"53-hfEnumeNh6YirfjyjaujcOPPT+s" Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" Expires: Tue, 24 Sep 2019 06:45:38 GMT Pragma: no-cache Server: cloudflare Set-Cookie: __cfduid=d316de6bcb239812cd0a96e2adaa6b4a31569293138; expires=Wed, 23-Sep-20 02:45:38 GMT; path=/; domain=.typicode.com; HttpOnly Transfer-Encoding: chunked Vary: Origin, Accept-Encoding Via: kong/1.2.1 X-Content-Type-Options: nosniff X-Kong-Proxy-Latency: 80 X-Kong-Upstream-Latency: 697 X-Powered-By: Express { "completed": false, "id": 1, "title": "delectus aut autem", "userId": 1 }
Bash
Kong 을 통해 jsonplaceholder 로 요청이 Route 되고, 응답을 받을 수 있는 것이 확인되었다!

Plugin 테스트

특정한 엔드포인트로 요청했을 때, Hello World 를 응답하는 서비스를 등록해보자. kong.yml 파일을 다음과 같이 수정한다.
_format_version: "1.1" services: - name: json-placeholder url: https://jsonplaceholder.typicode.com routes: - name: json-placeholder-route paths: - /json - name: static-response url: https://google.com plugins: - name: request-termination config: status_code: 200 message: "hello, world!" routes: - name: hello-world-route strip_path: false paths: - /hello
Bash
static-response 라는 서비스를 등록했으며, request-termination 이라는 플러그인을 통해 Request / Response 가 제어되도록 하였다. 플러그인의 설정으로 config 값을 전달했다. 이 요청은 /hello 로 요청했을 때 동작할 것이다. 실행해보자.
$ http :8000/hello HTTP/1.1 200 OK Connection: keep-alive Content-Length: 27 Content-Type: application/json; charset=utf-8 Date: Tue, 24 Sep 2019 02:53:24 GMT Server: kong/1.2.1 { "message": "hello, world!" }
Bash
예상한 대로 잘 동작한다. (위 플러그인을 활용하면 Kong 서비스 자체의 Health Check 를 수행하는 엔드포인트를 만들 수 있다)
추가적으로 관리 API 를 통해 플러그인 정보를 살펴보자.
$ http :8001/plugins HTTP/1.1 200 OK Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 366 Content-Type: application/json; charset=utf-8 Date: Tue, 24 Sep 2019 02:53:45 GMT Server: kong/1.2.1 { "data": [ { "config": { "body": null, "content_type": null, "message": "hello, world!", "status_code": 200 }, "consumer": null, "created_at": 1569293600, "enabled": true, "id": "4e19e4a0-b229-5e90-a85c-6669c3480b20", "name": "request-termination", "protocols": [ "http", "https" ], "route": null, "run_on": "first", "service": { "id": "c0cfe2ac-661f-5012-8028-eca3871a43d0" }, "tags": null } ], "next": null }
Bash
service.id = c0cfe2ac-661f-5012-8028-eca3871a43d0 에 해당하는 서비스에 대해 request-termination 이라는 플러그인이 등록해둔 설정으로 등록되어 있다는 것을 확인할 수 있다. Kong 의 플러그인 등록 및 설정은 이러한 방식으로 이루어진다.

실서비스를 위한 환경 선택

현재 개발 배포환경은 AWS EC2, Beanstalk, ECS 등이 혼합되어 있으나 Docker Container 기반으로 통합하고 이후 추가적인 기술 조사를 통해 Docker Swarm, EKS 또는 다른 클라우드 서비스의 Kubenetes 환경 등의 Container 기반 통합환경으로 이동하는 것을 계획하고 있다. 현재 이중 중간단계로 AWS 의 ECS 를 사용하고 있다.
이에 Kong API Gateway 역시 Docker Container 로 만들어 ECS 에 배포하는 것으로 결정되었다.
Kong 서비스는 서비스 데이터베이스로 PostgreSQL 또는 Cassandra 를 사용한다. 하지만, 데이터베이스 없이 Kong 을 사용할 수 있는 DB-less Mode 방식이 존재하는 것을 확인하고 내용을 확인한 후 우리에게 필요한 서비스요건에 부합하는 것을 확인하여 DB-less Mode 로 사용하기로 결정하였다.

Declarative Config and DB-less Mode

Kong Cluster 에 DB 구성을 사용하지 않고 Declarative Config / DB-less 설정으로 사용하는 것으로 결정하고 진행하였다. 결정하게 된 이유는 아래와 같다.
YAML 파일을 이용하여 설정하고 설정 파일을 버전관리하여 히스토리로 남기는 것이 도움이 될 것 같다.
설정 변경시 서비스를 중단없이 배포할 수 있다면 문제가 되지 않는다.
내부 API 이기 때문에 rate-limit 등의 기능은 사용하지 않을 계획이다.
별도의 PostgreSQL 이나 Cassandra 데이터베이스를 운용하는 비용과 노력을 줄일 수 있게 된다.

Routing 설계

API endpoint 의 최상단에는 ALB 가 배치되어 있다. 그리고 현재 서비스의 대부분은 플랫폼 서버가 처리한다. 이 중 일부 규칙에 맞는 요청이 Kong 서비스로 전달되며, 이후 Kong Routing 규칙에 따라 분기된다. 이를 구현하는 Declarative Config 는 다음과 같다.
_format_version: '1.1' services: - name: glam-svc1-public url: 'http://svc1.com' routes: - name: glam-svc1-public-route strip_path: false paths: - /public/svc1 - name: glam-svc2-public url: 'http://svc2.com' routes: - name: glam-svc2-public-route strip_path: false paths: - /public/svc2 - name: health-check url: 'https://example.com' plugins: - name: request-termination config: status_code: 200 message: 'health check response' routes: - name: health-check-route strip_path: false paths: - /health-check
Bash
이제 하위 서비스에 인증 로직을 통한 보안을 적용할 수 있도록 Kong Plugin 을 만들어보자.

다음편 링크