API Gatewayの1つであるKong Gatewayは設定内容をYAMLで定義でき、deck
コマンドというCLIで反映することが出来る。
またdeck
コマンドはOpen API Specification(以下OAS)形式で書かれたAPI仕様をKong Gatewayの設定値に変更することが出来る。
これを利用すると、OAS形式でAPIの仕様を作成することでKong Gatewayの設計を個別にしなくてもそのままGatewayに反映する事ができる。
またAPIクライアントであるInsomnia上でOASを作成すると、そのCLIであるinso
コマンドでテストすることが出来る
これにより、API開発者が実装したAPIの仕様をテストからデプロイまでコマンドラインのみで行う事ができる。
これをGitOpsの仕組みと組み合わせると、以下のような感じでPush契機で反映させることも可能。
このフロー(APIOps)を実現する際、GitHub Actionsを使った例は見かけるのだがGitLabを使った例はなかったので試してみた。
なお、検証に使ったファイルはこちらに置いている。
検証内容
ここでは以下を実現するパイプラインを作成し、動作確認を行う。
Merge Request(PR)時
- Linterの実行(
inso lint spec
) - OASをdeck形式のYAMLに変換(
deck file openapi2kong
) - YAMLの検証(
deck gateway validate
) - 現環境とのdiffの取得(
deck gateway diff
)
Merge時
- OASをdeck形式のYAMLに変換(
deck file openapi2kong
) - YAMLの検証(
deck gateway validate
) - 現環境とのdiffの取得(
deck gateway diff
) - Kong Gatewayに設定を反映(
deck gateway sync
)
本来はInsomniaによるOASのテストなども含めることが出来るが、パイプラインが肥大化するためテストはシンプルにLintのみに留めた。
前提
検証環境については以下が既に用意されているものとする。
- Kubernetes
- GitLab
- GitLab Runner
- Kong Gateway
GitLab、GitLab Runnerの準備についてはこちらの方法で用意した。
ただし、RunnerについてはRun untagged jobs
を指定してタグなしでも動くようにしている。
Kong Gatewayについてはこちらの方法で用意した。
検証時のファイル構成
以下のファイルを作成して検証する。
.
├── .gitlab-ci.yml
├── k8s
│ ├── 00_httpbin-ns.yaml
│ ├── 01_httpbin-deploy.yaml
│ └── 02_httpbin-svc.yaml
└── openapi
└── httpbin.yaml
ファイルはそれぞれ以下の通り。
-
.gitlab-ci.yml
:GitLabで使うパイプラインの定義 -
k8s/
:APIの発行先となるサンプルアプリケーション(httpbin)のManifest(アプリのコードは含まない) -
openapi/httpbin.yaml
:OAS形式のAPI仕様書
k8s/
に関しては、今回はManifest修正契機でのGitOpsの動作確認は対象外なのでこのリポジトリ内に含める必要はない。(将来的にこちらもGitOpsで使いたいので入れただけ)
ファイルの作成
リポジトリ内にManifestとOASのYAML、パイプラインのYAMLを作成する。
リポジトリとしては今回以下のリポジトリを作成した。
- リポジトリ名:
gitlab-apiops-test
- 公開範囲:
Private
リポジトリのメンバーにRunnerのトークンと紐づくメンバー(Administrator含む)を追加しておくこと。
追加していない場合、Runnerが以下のメッセージを出力して失敗する。
remote: You are not allowed to download code from this project.
fatal: unable to access 'https://gitlab.eks.xxxx.com/imurata/gitlab-apiops-test.git/': The requested URL returned error: 403
作成後、リポジトリをCloneしてディレクトリ内に移動する。
MYHOST=gitlab.eks.xxxx.com
git clone https://${MYHOST}/imurata/gitlab-apiops-test.git
cd gitlab-apiops-test
サンプルAPIアプリのManifestの作成
APIを使ったアプリケーションを用意する。
ここではhttpbinと同等の動きをするコンテナを利用し、このPodをAPIアプリケーションとして利用する。
k8s
というディレクトリを作成し、Namespace
,Deployment
,Service
を作成する。
mkdir k8s
kubectl create ns httpbin --dry-run=client -o yaml | kubectl neat -f - > k8s/00_httpbin-ns.yaml
kubectl create deploy -n httpbin --image kennethreitz/httpbin httpbin -r 1 --dry-run=client -o yaml | kubectl neat -f - > k8s/01_httpbin-deploy.yaml
kubectl create svc clusterip -n httpbin httpbin --tcp=80:80 --dry-run=client -o yaml | kubectl neat -f - > k8s/02_httpbin-svc.yaml
上記のManifestにより、httpbin
というDeployment
がhttpbin
というNamespace
上に作成され、Service httpbin
によって公開される
起動して動作確認する。
最初にクラスタ上にリソースを展開する。
kubectl apply -f k8s/
適当なPodを立ち上げてhttpbin.httpbin.svc
にアクセスしてみる。
$ kubectl run curl --image centos --rm -it --restart=Never -- curl httpbin.httpbin.svc/ip
{
"origin": "192.168.55.194"
}
httpbinは/ip
にアクセスするとアクセス元のIPを返すので問題なさそうだ。
OASの作成
シンプルにGET
とPOST
のみ確認するためのエンドポイントを定義する。
mkdir ./openapi
cat <<EOF > ./openapi/httpbin.yaml
openapi: 3.0.0
info:
title: httpbin
version: "0.1"
paths:
/get:
get:
operationId: /get
responses:
"200":
description: "get: response 200"
/post:
post:
operationId: /post
responses:
"200":
description: "post: response 200"
servers:
- url: http://httpbin.httpbin.svc
EOF
servers.url
はKong GatewayのDataPlaneがKubernetes内にいることを想定してService
経由でアクセスしているが、Kubernetesクラスタ外に存在する場合はtype: LoadBalancer
やIngress
等を利用してData Plane(Proxy)を外部に公開し、そのURLを指定する必要がある。
responses
については記載がなくてもdeck
コマンドでconvert出来るが、inso lint
でエラーになってパイプラインが止まるため足している。
作成したらinso lint
、deck file openapi2kong
が通ることを確認する。
inso lint spec openapi/httpbin.yaml
deck file openapi2kong -s openapi/httpbin.yaml
inso lint
に関してはWarningが表示されるが、コマンドの戻り値はゼロなのでパイプライン的には問題ない。
パイプラインの定義(.gitlab-ci.yml
)の作成
以下のパイプラインを作成する。
以下、.gitlab-ci.yml
の内容となる。
cat <<EOF > ./.gitlab-ci.yml
stages:
- test
- convert
- valid
- deploy
variables:
DECK_KONG_ADDR: https://kong.aws.unohude.info/api
DECK_TLS_SKIP_VERIFY: true
DECK_HEADERS: "kong-admin-token:$CICD_KONG_ADMIN_TOKEN"
WORKSPACE: "httpbin"
lint:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- openapi/**/*
image: kong/inso
stage: test
script:
- |
set -x
echo "Lint OAS spec."
inso lint spec openapi/httpbin.yaml
convert-deck:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- openapi/**/*
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
changes:
- openapi/**/*
image: kong/deck
stage: convert
script:
- |
set -x
echo "Convert OAS to deck YAML."
deck file openapi2kong -s openapi/httpbin.yaml -o deck.yaml
artifacts:
paths:
- deck.yaml
valid-deck:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- openapi/**/*
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
changes:
- openapi/**/*
image: kong/deck
stage: valid
script:
- |
set -x
echo "Validate deck.yaml"
deck gateway validate deck.yaml
diff-deck:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- openapi/**/*
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
changes:
- openapi/**/*
image: kong/deck
stage: valid
script:
- |
set -x
echo "Diff deck.yaml and Gateway"
deck gateway diff deck.yaml --workspace $WORKSPACE
deploy:
rules:
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
changes:
- openapi/**/*
image: kong/deck
stage: deploy
script:
- |
set -x
echo "Deploy"
deck gateway sync deck.yaml --workspace $WORKSPACE
以下、.gitlab-ci.yml
内のポイントを解説する。
variables:
DECK_KONG_ADDR: https://kong.aws.unohude.info/api
DECK_TLS_SKIP_VERIFY: true
DECK_HEADERS: "kong-admin-token:$CICD_KONG_ADMIN_TOKEN"
WORKSPACE: "httpbin"
variables
ではdeck
コマンド利用時に必要な環境変数を定義する。DECK_KONG_ADDR
はAdminAPIのURLで、コマンドの引数やファイルでも渡すことが可能だが、環境変数でも渡せるためここでは環境変数で渡している。
DECK_TLS_SKIP_VERIFY
についてはKong Ingress Controller(KIC)で自己証明書を使って構築しているため、証明書のチェックをスキップするために指定する。
DECK_HEADERS
はKong GatewayをRBACを有効化して起動しており、認証を突破するためにkong-admin-token:
でAdminのTokenを渡している。
TokenはCICD_KONG_ADMIN_TOKEN
というリポジトリのCI/CD設定(Settings
->CI/CD
->Variables
)で指定して、リポジトリのファイル内に残らないようにしている。
RBACを使っていない人はDECK_HEADERS
は指定しなくてもよい。
WORKSPACE
はKong GatewayのWorkspace名を設定している。
事前に作っておく必要はなく、なければdeck gateway sync
時に自動で生成される。
lint:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- openapi/**/*
Lint処理はMerge Request発生時のみ動かしたいので"merge_request_event"
の条件で発動条件を絞っている。
また、changes
で更新対象のディレクトリを絞り、OASの更新以外では動かないようにしている。
convert-deck:
:(省略)
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
changes:
- openapi/**/*
Lint以外のJobはmain
ブランチへのMergeのタイミングでも動いて欲しいため、条件を足している。
artifacts:
paths:
- deck.yaml
deck file openapi2kong
で変換したdeck用のYAMLは後続のJobでも利用するため、Artifactsで引き継ぐようにしている。
パイプラインからデプロイした環境に問題があった場合はこのArtifactsをDownloadしてトラブルシュートに利用することになる。
全てのファイルを作成し終わったらファイルをPushする。
git add -A
git commit -m "initial commit"
git push
動作確認
ブラウザでササッと確認する。
GitLabのUIからリポジトリのhttpbin.yamlを開き、Edit
->Edit single file
から編集モードに移行し、
version: "0.1"
をversion: "0.2"
に変更する。
Commitの際にTarget Branch
をmain
から適当なブランチ名に変更し、Start a new merge request with these changes
にチェックを入れてCommit changes
をクリックする。
次にMerge Requestの作成画面に移行するのでCreate merge request
をクリックする。
クリックするとパイプラインが動き出す。
しばらく待つと問題なければpassed
という表示とともに全てがグリーンとなる。
それぞれのJobをクリックすれば、実行内容も確認できる。
また、OASからdeck形式に変換されたYAMLはJobconvert-deck
からのartifactsから確認できる。
Merge Requestのパイプライン実行後、Merge Requestの画面からMerge
ボタンをクリックしてMergeを実行する。
すると今度はMerge用のパイプラインが起動する。
こちらもしばらく待てば全てグリーンになり、Jobdeploy
の結果を見ることでdeck gateway sync
によりKong Gateway側に反映されたことが分かる。
Kong GatewayのUIからもRouteが設定されていることが確認できる。
実際にアクセスしてみる。
PROXY_URL=xxx.us-east-1.elb.amazonaws.com
curl -X GET $PROXY_URL/get
curl -X POST $PROXY_URL/post
それぞれ正常なレスポンスが取得できると思う。
また、今回は/get
と/post
しか実装しなかったため、/ip
などでは以下の様にRouteが見つからずエラーとなる。
$ curl -X GET $PROXY_URL/ip
{
"message":"no Route matched with those values",
"request_id":"b6b4a717abd07c19b6d6a873d6a27849"
}
こちらも期待した動作となった。
ということで、GitLabのCI/CDの仕組みを使ってKong GatewayのAPIOpsが実現出来ることが確認できた。