From 70aa217964d7c0a829962b6241127c8e4b47e63e Mon Sep 17 00:00:00 2001 From: Eugenio Marzo Date: Wed, 10 Feb 2021 09:22:26 +0100 Subject: [PATCH] added pure javascript version --- Dockerfile | 10 +- Dockerfile_legacy | 52 ++++++++++ README.md | 93 ++++++------------ chaos-node/chaos-node.lua | 162 +++++++++++++++++++++++++++++++ html5/kubeinvaders.js | 129 +++++++++++++++++++++--- kubeinvaders_cluster/Vagrantfile | 0 nginx/KubeInvaders.templ | 83 +++++++++++----- nginx/KubeInvaders_dev.templ | 79 ++++++++++----- nginx/nginx.conf | 3 + nginx/node.lua | 56 +++++++++++ nginx/pod.lua | 84 ++++++++++++++++ 11 files changed, 622 insertions(+), 129 deletions(-) create mode 100644 Dockerfile_legacy create mode 100644 chaos-node/chaos-node.lua delete mode 100644 kubeinvaders_cluster/Vagrantfile create mode 100644 nginx/node.lua create mode 100644 nginx/pod.lua diff --git a/Dockerfile b/Dockerfile index b998c75..e7a66f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,6 @@ FROM nginx # Install kubectl - RUN curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" RUN chmod +x ./kubectl RUN mv ./kubectl /usr/local/bin/kubectl @@ -12,8 +11,11 @@ RUN apt-get -y install --no-install-recommends wget gnupg ca-certificates jq ope RUN wget -O - https://openresty.org/package/pubkey.gpg | apt-key add - RUN codename=`grep -Po 'VERSION="[0-9]+ \(\K[^)]+' /etc/os-release` && echo "deb http://openresty.org/package/debian $codename openresty" | tee /etc/apt/sources.list.d/openresty.list RUN apt-get update -RUN apt-get -y install openresty +RUN apt-get -y install openresty luarocks RUN chmod 777 /usr/local/openresty/nginx +RUN apt-get -y install openresty luarocks libssl-dev git vim lua-json lua-socket +RUN luarocks install luasec +RUN luarocks install lunajson # Install kube-linter RUN curl -L -O https://github.com/stackrox/kube-linter/releases/download/0.1.5/kube-linter-linux.tar.gz @@ -41,13 +43,15 @@ RUN sed -i.bak 's/listen\(.*\)80;/listen 8081;/' /etc/nginx/conf.d/default.conf COPY nginx/nginx.conf /etc/nginx/nginx.conf COPY nginx/metrics.lua-script /tmp/metrics.lua +COPY nginx/pod.lua /tmp/pod.lua +COPY nginx/node.lua /tmp/node.lua +COPY chaos-node/chaos-node.lua /tmp/chaos-node.lua COPY nginx/KubeInvaders.templ /etc/nginx/conf.d/KubeInvaders.templ COPY nginx/KubeInvaders_dev.templ /etc/nginx/conf.d/KubeInvaders_dev.templ RUN chmod g+rwx /var/cache/nginx /var/run /var/log/nginx /var/www/html /etc/nginx/conf.d/KubeInvaders.templ /etc/nginx/conf.d - EXPOSE 8080 ENV PATH=/usr/local/openresty/nginx/sbin:$PATH diff --git a/Dockerfile_legacy b/Dockerfile_legacy new file mode 100644 index 0000000..5fcd581 --- /dev/null +++ b/Dockerfile_legacy @@ -0,0 +1,52 @@ +FROM nginx + +# Install kubectl + +RUN curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" +RUN chmod +x ./kubectl +RUN mv ./kubectl /usr/local/bin/kubectl + +# Install Openresty +RUN apt-get update +RUN apt-get -y install --no-install-recommends wget gnupg ca-certificates jq openssl task-spooler at +RUN wget -O - https://openresty.org/package/pubkey.gpg | apt-key add - +RUN codename=`grep -Po 'VERSION="[0-9]+ \(\K[^)]+' /etc/os-release` && echo "deb http://openresty.org/package/debian $codename openresty" | tee /etc/apt/sources.list.d/openresty.list +RUN apt-get update +RUN apt-get -y install openresty +RUN chmod 777 /usr/local/openresty/nginx + +# Install kube-linter +RUN curl -L -O https://github.com/stackrox/kube-linter/releases/download/0.1.5/kube-linter-linux.tar.gz +RUN tar -xvf kube-linter-linux.tar.gz +RUN rm -f kube-linter-linux.tar.gz +RUN cp kube-linter /usr/local/bin/ +RUN chmod 775 /usr/local/bin/kube-linter +COPY kube-linter/kube-linter-parser.sh /opt/kube-linter-parser.sh +RUN chmod +x /opt/kube-linter-parser.sh +# Install Redis +RUN apt-get install redis -y +COPY redis/redis.conf /etc/redis/redis.conf + +# Install KubeInvaders +COPY ./js-web/KubeInvaders /var/www/html + +# Configure Nginx +#RUN sed -i.bak 's/^user/#user/' /etc/nginx/nginx.conf +RUN sed -i.bak 's/listen\(.*\)80;/listen 8081;/' /etc/nginx/conf.d/default.conf + +COPY nginx/nginx.conf /etc/nginx/nginx.conf +COPY nginx/metrics.lua-script /tmp/metrics.lua +COPY chaos-node/chaos-node.lua-script /tmp/chaos-node.lua + +COPY nginx/KubeInvaders.templ /etc/nginx/conf.d/KubeInvaders.templ +COPY nginx/KubeInvaders_dev.templ /etc/nginx/conf.d/KubeInvaders_dev.templ + +RUN chmod g+rwx /var/cache/nginx /var/run /var/log/nginx /var/www/html /etc/nginx/conf.d/KubeInvaders.templ /etc/nginx/conf.d + + +EXPOSE 8080 + +ENV PATH=/usr/local/openresty/nginx/sbin:$PATH +COPY ./temporary_hack.sh / +RUN chmod a+rwx temporary_hack.sh +ENTRYPOINT ["/temporary_hack.sh"] diff --git a/README.md b/README.md index a72a4ff..6b414a6 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,44 @@ ![Alt Text](https://github.com/lucky-sideburn/KubeInvaders/blob/master/logo.png) -*Gamified chaos engineering and analysis tool for Kubernetes. It is like Space Invaders but the aliens are PODs.* +*Gamified chaos engineering and analysis tool for Kubernetes. It is like Space Invaders but the aliens are pods or worker nodes.* ![Alt Text](https://github.com/lucky-sideburn/KubeInvaders/blob/master/images/kubeinvaders.png) # Table of Contents 1. [Description](#Description) -2. [Special Input Keys and features](#Special-Input-Keys-and-features) -3. [Prometheus metrics and Grafana](#Metrics) -4. [Installation](#Installation) -5. [Notes for large clusters](#Notes-for-large-clusters) -6. [Configuration](#Configuration) +2. [New Version (game part in pure JS)](#new-version) +3. [Special Input Keys and features](#Special-Input-Keys-and-features) +4. [Prometheus metrics and Grafana](#Metrics) +5. [Installation](#Installation) +6. [Notes for large clusters](#Notes-for-large-clusters) +7. [Configuration](#Configuration) ## Description -KubeInvaders has been developed using [Defold](https://www.defold.com/). - Through KubeInvaders you can stress a Kubernetes cluster in a fun way and check how it is resilient. +## New Version -I added also new experimental features like a linter for the pods. The current latest image of the game include [kube-linter](https://github.com/stackrox/kube-linter) developed by [stackrox](https://github.com/stackrox). +KubeInvaders is going to be full open-source. Meanwhile it possibile choose 2 different version. + +Legacy version: Made with Defold as game engine +New version: It has been written in pure Javascript and it is totaly open-source + +The new version of KubeInvaders has fewer features than legacy but is optimized for chaos engineering because pods and nodes are rendered together and there is a shuffle for better random experiments. ## Special Input Keys and features -| Input | Action | -|-----------------|--------------------------------------------------------------------------------------------| -| n | Change namespace (you should define namespaces list. Ex: TARGET_NAMESPACE=foo1,foo2,foo3). | -| a | Switch to automatic mode. | -| m | Switch to manual mode. | -| h | Show special keys. | -| q | Hide help for special keys. | -| i | Show pod's name. Move the ship towards an alien. | -| r | Refresh log of a pod when spaceship is over the alien. | -| k | *(NEW)* Perform [kube-linter](https://github.com/stackrox/kube-linter) analysis for a pod. | -| w | *(NEW)* Chaos engineering against Kubernetes nodes. | +| Input | Action | Version (New or Legacy)| +|-----------------|--------------------------------------------------------------------------------------------|------------------------| +| n | Change namespace (you should define namespaces list. Ex: TARGET_NAMESPACE=foo1,foo2,foo3). | New, Legacy | +| a | Switch to automatic mode. | Legacy | +| m | Switch to manual mode. | Legacy | +| h | Show special keys. | New, Legacy | +| q | Hide help for special keys. | New, Legacy | +| i | Show pod's name. Move the ship towards an alien. | Legacy | +| r | Refresh log of a pod when spaceship is over the alien. | Legacy | +| k | *(NEW)* Perform [kube-linter](https://github.com/stackrox/kube-linter) analysis for a pod. | Legacy | +| w | *(NEW)* Chaos engineering against Kubernetes nodes. | New, Legacy | ### Known problems @@ -82,44 +87,15 @@ git clone https://github.com/lucky-sideburn/KubeInvaders.git kubectl create namespace kubeinvaders +# Install new and full open-source version helm install kubeinvaders --set-string target_namespace="namespace1\,namespace2" \ --namespace kubeinvaders ./helm-charts/kubeinvaders \ ---set ingress.hostName=kubeinvaders.io -``` -### Install client on your workstation +--set ingress.hostName=kubeinvaders.io --set image.tag=latest -The easiest way to install KubeInvaders is on your workstation but if you choose this method you cannot use kube-linter feature directly from the game. Follow this guide: - -1. Start KubeInvaders docker container locally - -```bash -docker rm kubeinvaders -f && docker run --env DEVELOPMENT=true --env ENDPOINT=https:// --env NAMESPACE=namespace1,namespace2 --env TOKEN= -p 8080:8080 --name kubeinvaders docker.io/luckysideburn/kubeinvaders -``` - -2. Create $HOME/.KubeInv.json like this - The endpoint is localhost:8080 because it is using KubeInvaders container as a proxy -to Kubernetes: - -```json -{ - "token": "", - "endpoint": "http://localhost:8080", - "namespace": "namespace1,namespace2" -} -``` - -Download the game from these locations: - -* [MacOS](https://github.com/lucky-sideburn/KubeInvaders/releases/download/1.0-metrics/x86_64-darwin.zip) -* [Linux](https://github.com/lucky-sideburn/KubeInvaders/releases/download/1.0-metrics/x86_64-linux.zip) -### Run directly from Docker - -This method can be used for developing KubeInvaders and testing the HTML5 bundle. -Using this method you can have problem of CORS: - -```bash -docker build . -t kubeinvaders_dev - -docker rm kubeinvaders -f && docker run --env DEVELOPMENT=true --env ENDPOINT=https://youk8scluster:8443 --env NAMESPACE=kubeinvadersdemo --env TOKEN=xxxx -p 8080:8080 --name kubeinvaders kubeinvaders_dev +# Install legacy version +helm install kubeinvaders --set-string target_namespace="namespace1\,namespace2" \ +--namespace kubeinvaders ./helm-charts/kubeinvaders \ +--set ingress.hostName=kubeinvaders.io --set image.tag=legacy ``` ### Install KubeInvaders on OpenShift @@ -147,11 +123,6 @@ KUBEINVADERS_SECRET=$(oc get secret -n kubeinvaders --field-selector=type==kuber oc process -f openshift/KubeInvaders.yaml -p ROUTE_HOST=$ROUTE_HOST -p TARGET_NAMESPACE=$TARGET_NAMESPACE -p KUBEINVADERS_SECRET=$KUBEINVADERS_SECRET | oc create -f - ``` - -#### How the configuration of KubeInvaders DeploymentConfig should be (remember to use your TARGET_NAMESPACE and ROUTE_HOST) - -![Alt Text](https://github.com/lucky-sideburn/KubeInvaders/blob/master/images/dcenv.png) - ## Notes for large clusters For clusters with many workers-nodes, KubeInvaders selects a subset of random items. @@ -160,7 +131,7 @@ For clusters with many workers-nodes, KubeInvaders selects a subset of random it |-----------|--------------| | Nodes | 15 | ## Configuration -### Environment Variables - Make the game more difficult to win! +### (Legacy Version) Environment Variables - Make the game more difficult to win! Set the following variables in Kubernetes Deployment or OpenShift DeploymentConfig: diff --git a/chaos-node/chaos-node.lua b/chaos-node/chaos-node.lua new file mode 100644 index 0000000..377bea4 --- /dev/null +++ b/chaos-node/chaos-node.lua @@ -0,0 +1,162 @@ +loadfile("/tmp/metrics.lua") + +local https = require "ssl.https" +local ltn12 = require "ltn12" +local json = require 'lunajson' + +function read_all(file) + local f = assert(io.open(file, "rb")) + local content = f:read("*all") + f:close() + return content +end + +local http = require("socket.http") +math.randomseed(os.clock()*100000000000) +local rand = math.random(999, 9999) +local arg = ngx.req.get_uri_args() + +local k8s_url = os.getenv("ENDPOINT") +local token = os.getenv("TOKEN") +local namespace = arg['namespace'] +local node_name = arg['node_name'] +local url = k8s_url .. "/apis/batch/v1/namespaces/" .. namespace .. "/jobs" +local resp = {} + +ngx.header['Access-Control-Allow-Origin'] = '*' +ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' +ngx.header['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' +ngx.header['Access-Control-Expose-Headers'] = 'Content-Length,Content-Range'; + +headers = { + ["Accept"] = "application/json", + ["Content-Type"] = "application/json", + ["Authorization"] = "Bearer " .. token, +} + +body = [[ +{ + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "kubeinvaders-chaos-]] .. rand .. [[", + "labels": { + "app": "kubeinvaders", + "approle": "chaosnode" + } + }, + "spec": { + "template": { + "metadata": { + "labels": { + "app": "kubeinvaders", + "approle": "chaosnode" + } + }, + "spec": { + "containers": [ + { + "name": "kubeinvaders-chaos-node", + "image": "docker.io/luckysideburn/kubeinvaders-stress-ng:latest", + "command": [ + "stress-ng", + "--cpu", + "4", + "--io", + "2", + "--vm", + "1", + "--vm-bytes", + "1G", + "--timeout", + "10s", + "--metrics-brief" + ] + } + ], + "restartPolicy": "Never", + "nodeSelector": { + "kubernetes.io/hostname": "wrk5-oc" + } + } + }, + "backoffLimit": null + } +} +]] + +local headers2 = { + ["Accept"] = "application/json", + ["Content-Type"] = "application/json", + ["Authorization"] = "Bearer " .. token, + ["Content-Length"] = string.len(body) +} + +url = k8s_url .. "/apis/batch/v1/namespaces/" .. namespace .. "/jobs" +ngx.log(ngx.ERR, "Creating chaos_node job kubeinvaders-chaos-" ..rand) + +local ok, statusCode, headers, statusText = https.request{ + url = url, + headers = headers2, + method = "POST", + sink = ltn12.sink.table(resp), + source = ltn12.source.string(body) +} + +ngx.log(ngx.ERR, ok) +ngx.log(ngx.ERR, statusCode) +ngx.log(ngx.ERR, statusText) + +local url = k8s_url.. "/apis/batch/v1/namespaces/" .. namespace .. "/jobs" +ngx.log(ngx.ERR, "Getting JobList" .. rand) + +local ok, statusCode, headers, statusText = https.request{ + url = url, + headers = headers, + method = "GET", + sink = ltn12.sink.table(resp) +} + +ngx.log(ngx.ERR, ok) +ngx.log(ngx.ERR, statusCode) +ngx.log(ngx.ERR, statusText) + +for k,v in ipairs(resp) do + decoded = json.decode(v) + if decoded["kind"] == "JobList" then + for k2,v2 in ipairs(decoded["items"]) do + if v2["status"]["succeeded"] == 1 and v2["metadata"]["labels"]["approle"] == "chaosnode" then + delete_job = "kubectl delete job " .. v2["metadata"]["name"] .. " --token=" .. token .. " --server=" .. k8s_url .. " --insecure-skip-tls-verify=true -n " .. namespace + ngx.log(ngx.ERR, delete_pod) + end + end + end +end + +local url = k8s_url.. "/api/v1/namespaces/" .. namespace .. "/pods" +ngx.log(ngx.ERR, "Getting PodList" .. rand) + +local ok, statusCode, headers, statusText = https.request{ + url = url, + headers = headers, + method = "GET", + sink = ltn12.sink.table(resp) +} + +ngx.log(ngx.ERR, ok) +ngx.log(ngx.ERR, statusCode) +ngx.log(ngx.ERR, statusText) + +for k,v in ipairs(resp) do + decoded = json.decode(v) + if decoded["kind"] == "PodList" then + for k2,v2 in ipairs(decoded["items"]) do + if v2["status"]["phase"] == "Succeeded" and v2["metadata"]["labels"]["approle"] == "chaosnode" then + delete_pod = "kubectl delete pod " .. v2["metadata"]["name"] .. " --token=" .. token .. " --server=" .. k8s_url .. " --insecure-skip-tls-verify=true -n " .. namespace + ngx.log(ngx.ERR, delete_pod) + end + end + end +end + +ngx.say("chaos node") diff --git a/html5/kubeinvaders.js b/html5/kubeinvaders.js index 26c299b..27a47ae 100644 --- a/html5/kubeinvaders.js +++ b/html5/kubeinvaders.js @@ -9,7 +9,11 @@ var spaceshipHeight = 60; var spaceshipWidth = 60; var spaceshipX = (canvas.width-spaceshipWidth)/2; var spaceshipY = (canvas.height-spaceshipHeight)/2; -var namespace = "kubeinvadersdemo"; +var cluster = "CLUSTER_PLACEOLDER"; +var namespaces = ["kubeinvadersdemo", "foobar"]; +var namespaces_index = 0; +var namespace = namespaces[namespaces_index]; +var endpoint = ""; // pods list from kubernetes var pods = []; @@ -64,14 +68,43 @@ var collisionDetected = false; var aliensY = []; var aliensIncrementY = 50; +var shuffle = true; +var help = false; + +function getEndpoint(){ + var oReq = new XMLHttpRequest(); + oReq.onload = function () { + endpoint = this.responseText; + };; + oReq.open("GET", "http://localhost:8080/kube/endpoint"); + oReq.send(); +} + +function startChaosNode(node_name){ + var oReq = new XMLHttpRequest(); + oReq.onload = function () { + console.log(JSON.parse(this.responseText)) + };; + oReq.open("GET", "http://localhost:8080/kube/chaos/nodes?nodename=node_name=" + node_name + "&namespace=" + namespace); + oReq.send(); +} + +function deletePods(pod_name){ + var oReq = new XMLHttpRequest(); + oReq.onload = function () { + console.log(JSON.parse(this.responseText)) + };; + oReq.open("GET", "http://localhost:8080/kube/pods?action=delete&pod_name=" + pod_name + "&namespace=" + namespace); + oReq.send(); +} + function getPods(){ - foo = pods; var oReq = new XMLHttpRequest(); oReq.onload = function () { json_parsed = JSON.parse(this.responseText) - pods = json_parsed["items"]; + pods = json_parsed["items"].concat(nodes); };; - oReq.open("GET", "http://localhost:8080/kube/pods?namespace=" + namespace); + oReq.open("GET", "http://localhost:8080/kube/pods?action=list&namespace=" + namespace); oReq.send(); } @@ -86,7 +119,8 @@ function getNodes(){ } function getKubeItems() { - getPods() + getNodes(); + getPods(); } function keyDownHandler(e) { @@ -114,10 +148,43 @@ function keyDownHandler(e) { console.log("Spaceship Y: " + spaceshipY); console.log("Spaceship X: " + spaceshipX); } + else if(e.keyCode == 83) { + if (shuffle) { + shuffle = false; + console.log("Deactivate shuffle"); + } + else { + shuffle = true + console.log("Activate shuffle"); + } + } else if(e.keyCode == 32) { console.log("Shot"); shot = true } + else if(e.keyCode == 78) { + console.log("Change Namespace"); + if (namespaces_index < namespaces.length-1) { + namespaces_index +=1 ; + } + else { + namespaces_index = 0; + } + namespace = namespaces[namespaces_index]; + aliens = []; + pods = []; + } + else if(e.keyCode == 72) { + console.log("Help"); + if (help) { + help = false; + console.log("Deactivate help"); + } + else { + help = true + console.log("Activate help"); + } + } } function keyUpHandler(e) { @@ -138,10 +205,17 @@ function keyUpHandler(e) { document.addEventListener("keydown", keyDownHandler, false); document.addEventListener("keyup", keyUpHandler, false); -function drawAlien(alienX, alienY) { +function drawAlien(alienX, alienY, name) { var image = new Image(); // Image constructor - image.src = './sprite_invader.png'; - ctx.drawImage(image, alienX, alienY, 40, 40); + if (nodes.includes(name)) { + image.src = './k8s_node.png'; + ctx.drawImage(image, alienX, alienY, 30, 40); + } + else { + image.src = './sprite_invader.png'; + ctx.drawImage(image, alienX, alienY, 40, 40); + } + ctx.closePath(); } @@ -152,8 +226,6 @@ function checkRocketAlienCollision(){ for (i=aliens.length - 1; i >= 0; i--) { if (aliens[i]["active"] && (rocketY - aliens[i]["y"] < 5)) { var rangeX = [] - //console.log(aliens[i]); - //console.log(aliens[i]["x"]); rangeX.push(aliens[i]["x"]); for (k=aliens[i]["x"]; k Math.random() - 0.5) +} function drawRocket() { var image = new Image(); // Image constructor image.src = './kuberocket.png'; ctx.drawImage(image, rocketX, rocketY, 20, 20); ctx.closePath(); - //console.log("Rocket X: " + rocketX + " Rocket Y: " + rocketY); if (checkRocketAlienCollision()) { rocketY = -100; @@ -215,6 +296,7 @@ function drawSpaceship() { } function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); drawSpaceship(); @@ -258,9 +340,22 @@ function draw() { for (i=0; i Activate or deactivate Help', 10, 320); + ctx.fillText('s => Activate or deactivate shuffle for aliens', 10, 340); + ctx.fillText('n => Change namespace', 10, 360); + } } function podExists(podName) { @@ -282,6 +377,11 @@ function findReplace() { } function setAliens() { + if (shuffle) { + pods = pods.sort(() => Math.random() - 0.5) + } + aliens = []; + console.log("Length of aliensY array: " + aliensY.length); if (pods.length > 0) { for (k=10; k>0; k--) { if (!aliensY.includes(k)) { @@ -301,7 +401,7 @@ function setAliens() { aliens.push({"name": pods[i], "x": x, "y": y, "active": true}); cnt =+ 1; } - if (aliens.length == 12) { + if (aliens.length % 12 == 0) { console.log("we need another line of aliens for Y="+aliensIncrementY); x = 10; y += aliensIncrementY; @@ -320,6 +420,7 @@ function setAliens() { } } +getEndpoint(); setInterval(draw, 10); setInterval(getKubeItems, 1000); setInterval(setAliens, 1000); diff --git a/kubeinvaders_cluster/Vagrantfile b/kubeinvaders_cluster/Vagrantfile deleted file mode 100644 index e69de29..0000000 diff --git a/nginx/KubeInvaders.templ b/nginx/KubeInvaders.templ index 43cb610..cee35f7 100644 --- a/nginx/KubeInvaders.templ +++ b/nginx/KubeInvaders.templ @@ -2,8 +2,12 @@ server { listen 8080 default_server; root /var/www/html/; index index.html; - + location / { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; try_files $uri $uri/ =404; add_header Last-Modified $date_gmt; add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; @@ -12,12 +16,63 @@ server { etag off; } + location /kube { + rewrite ^/kube(.*)$ /api$1 break; + proxy_pass ${ENDPOINT}; + access_by_lua_file /tmp/metrics.lua; + } + + location /kube/api { + rewrite ^/kube/api(.*)$ /api$1 break; + proxy_pass ${ENDPOINT}; + access_by_lua_file /tmp/metrics.lua; + } + + location /kube/pods { + access_by_lua_file "/tmp/pod.lua"; + } + + location /kube/delete/pods { + access_by_lua_file "/tmp/pod.lua"; + } + + location /kube/nodes { + access_by_lua_file "/tmp/node.lua"; + } + + location /kube/chaos/nodes { + access_by_lua_file "/tmp/chaos-node.lua"; + } + + location /kube/endpoint { + content_by_lua_block { + ngx.header['Access-Control-Allow-Origin'] = '*' + ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' + ngx.header['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' + ngx.header['Access-Control-Expose-Headers'] = 'Content-Length,Content-Range'; + ngx.say(os.getenv("ENDPOINT")) + } + } + + location /kube/namespaces { + content_by_lua_block { + ngx.header['Access-Control-Allow-Origin'] = '*' + ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' + ngx.header['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' + ngx.header['Access-Control-Expose-Headers'] = 'Content-Length,Content-Range'; + ngx.say(os.getenv("NAMESPACE")) + } + } + location /metrics { default_type text/html; content_by_lua_block { local redis = require "resty.redis" local red = redis:new() + + -- local okredis, errredis = red:connect(os.getenv("REDIS_HOST"), 6379) local okredis, errredis = red:connect("unix:/tmp/redis.sock") + for i, res in ipairs(red:keys("*")) do ngx.log(ngx.ERR, res) @@ -42,34 +97,10 @@ server { content_by_lua_block { local arg = ngx.req.get_uri_args() ngx.req.read_body() - local request_body = ngx.req.get_body_data() + local request_body = ngx.req.get_body_data() local handle = io.popen("/opt/kube-linter-parser.sh " .. arg['k8s_url'] .. " " .. arg['namespace'] .. " " .. arg['pod_name'] .. " " .. arg['token']) local result = handle:read("*a") ngx.say(result) } } - - location /kube/chaos-node { - default_type text/html; - access_by_lua_file /tmp/metrics.lua; - content_by_lua_block { - local arg = ngx.req.get_uri_args() - ngx.req.read_body() - local request_body = ngx.req.get_body_data() - os.execute("/opt/chaos-node.sh " .. arg['k8s_url'] .. " " .. arg['token'] .. " " .. arg['node_name'] .. " " .. arg['namespace'] .. " &") - ngx.say('chaos_node started!') - } - } - - location /kube { - rewrite ^/kube(.*)$ /api$1 break; - proxy_pass https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS}; - access_by_lua_file /tmp/metrics.lua; - } - - location /kube/api { - rewrite ^/kube/api(.*)$ /api$1 break; - proxy_pass https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS}; - access_by_lua_file /tmp/metrics.lua; - } } diff --git a/nginx/KubeInvaders_dev.templ b/nginx/KubeInvaders_dev.templ index 394a1e4..4774a39 100644 --- a/nginx/KubeInvaders_dev.templ +++ b/nginx/KubeInvaders_dev.templ @@ -1,9 +1,15 @@ +# lua_package_path '/usr/share/lua/5.1/?.lua;;'; + server { listen 8080 default_server; root /var/www/html/; index index.html; location / { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; try_files $uri $uri/ =404; add_header Last-Modified $date_gmt; add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; @@ -12,6 +18,54 @@ server { etag off; } + location /kube { + rewrite ^/kube(.*)$ /api$1 break; + proxy_pass ${ENDPOINT}; + access_by_lua_file /tmp/metrics.lua; + } + + location /kube/api { + rewrite ^/kube/api(.*)$ /api$1 break; + proxy_pass ${ENDPOINT}; + access_by_lua_file /tmp/metrics.lua; + } + + location /kube/pods { + access_by_lua_file "/tmp/pod.lua"; + } + + location /kube/delete/pods { + access_by_lua_file "/tmp/pod.lua"; + } + + location /kube/nodes { + access_by_lua_file "/tmp/node.lua"; + } + + location /kube/chaos/nodes { + access_by_lua_file "/tmp/chaos-node.lua"; + } + + location /kube/endpoint { + content_by_lua_block { + ngx.header['Access-Control-Allow-Origin'] = '*' + ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' + ngx.header['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' + ngx.header['Access-Control-Expose-Headers'] = 'Content-Length,Content-Range'; + ngx.say(os.getenv("ENDPOINT")) + } + } + + location /kube/namespaces { + content_by_lua_block { + ngx.header['Access-Control-Allow-Origin'] = '*' + ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' + ngx.header['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' + ngx.header['Access-Control-Expose-Headers'] = 'Content-Length,Content-Range'; + ngx.say(os.getenv("NAMESPACE")) + } + } + location /metrics { default_type text/html; content_by_lua_block { @@ -51,29 +105,4 @@ server { ngx.say(result) } } - - location /kube/chaos-node { - default_type text/html; - access_by_lua_file /tmp/metrics.lua; - - content_by_lua_block { - local arg = ngx.req.get_uri_args() - ngx.req.read_body() - local request_body = ngx.req.get_body_data() - os.execute("/opt/chaos-node.sh " .. arg['k8s_url'] .. " " .. arg['token'] .. " " .. arg['node_name'] .. " " .. arg['namespace'] .. " &") - ngx.say('chaos_node started!') - } - } - - location /kube { - rewrite ^/kube(.*)$ /api$1 break; - proxy_pass ${ENDPOINT}; - access_by_lua_file /tmp/metrics.lua; - } - - location /kube/api { - rewrite ^/kube/api(.*)$ /api$1 break; - proxy_pass ${ENDPOINT}; - access_by_lua_file /tmp/metrics.lua; - } } diff --git a/nginx/nginx.conf b/nginx/nginx.conf index fc039e8..0ddb3e9 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -2,6 +2,9 @@ worker_processes 2; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; env REDIS_HOST; +env TOKEN; +env ENDPOINT; +env NAMESPACE; events { worker_connections 1024; diff --git a/nginx/node.lua b/nginx/node.lua new file mode 100644 index 0000000..277adb8 --- /dev/null +++ b/nginx/node.lua @@ -0,0 +1,56 @@ +local https = require "ssl.https" +local ltn12 = require "ltn12" +local json = require 'lunajson' + +local k8s_url = os.getenv("ENDPOINT") +local token = os.getenv("TOKEN") +local arg = ngx.req.get_uri_args() +local url = k8s_url.. "/api/v1/nodes" +local decoded = nil +local nodes = {} + +ngx.header['Access-Control-Allow-Origin'] = '*' +ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' +ngx.header['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' +ngx.header['Access-Control-Expose-Headers'] = 'Content-Length,Content-Range'; + +ngx.log(ngx.ERR, "token: " .. token) +ngx.log(ngx.ERR, "url: " .. url) + +local headers = { + ["Accept"] = "application/json", + ["Content-Type"] = "application/json", + ["Authorization"] = "Bearer " .. token, +} + +local resp = {} + +local ok, statusCode, headers, statusText = https.request{ + url = url, + headers = headers, + method = "GET", + sink = ltn12.sink.table(resp) +} + +ngx.log(ngx.ERR, "REQUEST LOGS...") +ngx.log(ngx.ERR, ok) +ngx.log(ngx.ERR, statusCode) +ngx.log(ngx.ERR, statusText) + +local i = 0 +nodes["items"] = {} + +for k,v in ipairs(resp) do + decoded = json.decode(v) + if decoded["kind"] == "NodeList" then + for k2,v2 in ipairs(decoded["items"]) do + if not v2["metadata"]["labels"]["node-role.kubernetes.io/master"] then + ngx.log(ngx.ERR, "found node " .. v2["metadata"]["name"]) + nodes["items"][i] = v2["metadata"]["name"] + i = i + 1 + end + end + end +end + +ngx.say(json.encode(nodes)) \ No newline at end of file diff --git a/nginx/pod.lua b/nginx/pod.lua new file mode 100644 index 0000000..9001f77 --- /dev/null +++ b/nginx/pod.lua @@ -0,0 +1,84 @@ +local https = require "ssl.https" +local ltn12 = require "ltn12" +local json = require 'lunajson' + +local k8s_url = os.getenv("ENDPOINT") +local token = os.getenv("TOKEN") +local arg = ngx.req.get_uri_args() +local namespace = arg['namespace'] +local decoded = nil +local action = arg['action'] +local url = '' +local pods = {} +local method = 'GET' +local pods_not_found = true; + +ngx.header['Access-Control-Allow-Origin'] = '*' +ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' +ngx.header['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' +ngx.header['Access-Control-Expose-Headers'] = 'Content-Length,Content-Range'; + +if action == "list" then + url = k8s_url.. "/api/v1/namespaces/" .. namespace .. "/pods" + +elseif action == "delete" then + local pod_name = arg['pod_name'] + url = k8s_url.. "/api/v1/namespaces/" .. namespace .. "/pods/" .. pod_name + method = "DELETE" + +else + ngx.say("Please set the parameter 'action'") + ngx.exit(ngx.OK) +end + +ngx.log(ngx.ERR, "token: " .. token) +ngx.log(ngx.ERR, "url: " .. url) +ngx.log(ngx.ERR, "namespace: " .. namespace) + +local headers = { + ["Accept"] = "application/json", + ["Content-Type"] = "application/json", + ["Authorization"] = "Bearer " .. token, +} + +local resp = {} + +local ok, statusCode, headers, statusText = https.request{ + url = url, + headers = headers, + method = method, + sink = ltn12.sink.table(resp) +} + +ngx.log(ngx.ERR, "REQUEST LOGS...") +ngx.log(ngx.ERR, ok) +ngx.log(ngx.ERR, statusCode) +ngx.log(ngx.ERR, statusText) + +if action == "list" then + local i = 1 + pods["items"] = {} + for k,v in ipairs(resp) do + decoded = json.decode(v) + if decoded["kind"] == "PodList" then + for k2,v2 in ipairs(decoded["items"]) do + if v2["status"]["phase"] == "Running" and v2["metadata"]["labels"]["approle"] ~= "chaosnode" then + ngx.log(ngx.ERR, "found pod " .. v2["metadata"]["name"]) + pods["items"][i] = v2["metadata"]["name"] + i = i + 1 + pods_not_found = false; + end + end + end + end + + if pods_not_found then + ngx.log(ngx.ERR, "No pods found into the namespace" .. namespace) + ngx.say("{\"items\": []}") + else + ngx.say(json.encode(pods)) + end + +elseif action == "delete" then + ngx.say(table.concat(resp)) +end \ No newline at end of file