Published: Dec 16, 2023 by Isaac Johnson
In A New Project: Part 1: Moving past the blank canvas we set the plans for the App we wanted to build, drew up some diagrams and planned a few spikes to figure out how we might containerize it.
Today we’ll look at NodeJS and Python as options as we action on the Spikes
NodeJS: React
My first shot is to try ReactJS. I generally find it’s quick to get started
$ mkdir k8sChecker
$ cd k8sChecker/
$ npx create-react-app my-app
$ npm install @kubernetes/client-node
I got a few errors in launching with npm start
till I moved to Node 20.
If I use NodeJS 20 (latest)
$ nvm list
v14.21.3
v16.20.2
v18.18.1
v18.18.2
-> v20.8.0
I create a kubernetes
library that could be used for K8s calls
import { CoreV1Api, KubeConfig } from '@kubernetes/client-node';
const kc = new KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(CoreV1Api);
export async function getServices() {
const res = await k8sApi.listServiceForAllNamespaces();
return res.body.items;
}
I hoped I could them pull it in using the App.js
//import logo from './logo.svg';
//import './App.css';
import { useEffect, useState } from 'react';
import { getServices } from './kubernetes';
function App() {
const [services, setServices] = useState([]);
useEffect(() => {
async function fetchData() {
const data = await getServices();
setServices(data);
}
fetchData();
}, []);
return (
<div>
<h1>Services</h1>
<ul>
{services.map((service) => (
<li key={service.metadata.uid}>{service.metadata.name}</li>
))}
</ul>
</div>
);
}
export default App;
The first errors I get on launch are about pollyfills and query strings
Failed to compile.
Module not found: Error: Can't resolve 'querystring' in '/home/builder/Workspaces/k8sChecker/my-app/node_modules/@kubernetes/client-node/dist'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "querystring": require.resolve("querystring-es3") }'
- install 'querystring-es3'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "querystring": false }
WARNING in ./node_modules/@kubernetes/client-node/dist/api.js
Module Warning (from ./node_modules/source-map-loader/dist/cjs.js):
Failed to parse source map from '/home/builder/Workspaces/k8sChecker/my-app/node_modules/@kubernetes/client-node/dist/api.js.map' file: Error: ENOENT: no such file or directory, open '/home/builder/Workspaces/k8sChecker/my-app/node_modules/@kubernetes/client-node/dist/api.js.map'
I tried all sorts of things. Installing querystring-es3, adding in a resolve block to react-scripts:
fallback: {
"querystring": require.resolve("querystring-es3") ,
"path": require.resolve("path-browserify"),
"buffer": require.resolve("buffer/"),
"crypto": require.resolve("crypto-browserify"),
"http": require.resolve("stream-http"),
"stream": require.resolve("stream-browserify"),
"url": require.resolve("url/"),
"util": require.resolve("util/"),
},
Then installing the missing packages
$ npm install --save buffer crypto-browserify stream-http stream
-browserify url util
but then that dumped
$ npm start
> my-app@0.1.0 start
> react-scripts start
Failed to compile.
Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.resolve.alias should be one of these:
object { alias?, aliasFields?, byDependency?, cache?, cachePredicate?, cacheWithContext?, conditionNames?, descriptionFiles?, enforceExtension?, exportsFields?, extensionAlias?, extensions?, fallback?, fileSystem?, fullySpecified?, importsFields?, mainFields?, mainFiles?, modules?, plugins?, preferAbsolute?, preferRelative?, resolver?, restrictions?, roots?, symlinks?, unsafeCache?, useSyncFileSystemCalls? }
-> Redirect module requests.
Details:
* configuration.resolve.alias['fallback'] should be one of these:
[non-empty string, ...] | false | non-empty string
-> New request.
Details:
* configuration.resolve.alias['fallback'] should be an array:
[non-empty string, ...]
-> Multiple alternative requests.
* configuration.resolve.alias['fallback'] should be false.
-> Ignore request (replace with empty module).
* configuration.resolve.alias['fallback'] should be a non-empty string.
-> New request.
I decided to try it fresh, maybe somewhere in my experimentation, I screwed up a file.
I created a new folder, this time I used Node 18 and tried again
$ cd k8sChecker2/
$ nvm use 18.18.2
$ npx create-react-app my-app
$ cd my-app/
$ npm install --save @kubernetes/client-node
$ npm start
Here I created an App.js I hoped would work
import logo from './logo.svg';
import './App.css';
import { KubeConfig, CoreV1Api } from '@kubernetes/client-node';
function App() {
const kc = new KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(CoreV1Api);
const res = await k8sApi.listServiceForAllNamespaces();
return res.body.items;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
<div>
<h1>Services</h1>
<ul>
{res.body.items.map((service) => (
<li key={service.metadata.uid}>{service.metadata.name}</li>
))}
</ul>
</div>
);
}
export default App;
This is quite literally just slapping in what I think are the relevant bits to fetch K8s details and display them.
Trying to launch indicated it wasn’t keen on the await
Failed to compile.
SyntaxError: /home/builder/Workspaces/k8sChecker2/my-app/src/App.js: Unexpected reserved word 'await'. (10:14)
8 | const k8sApi = kc.makeApiClient(CoreV1Api);
9 |
> 10 | const res = await k8sApi.listServiceForAllNamespaces();
| ^
11 | return res.body.items;
12 |
13 | return (
at parser.next (<anonymous>)
at normalizeFile.next (<anonymous>)
ERROR in ./src/App.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /home/builder/Workspaces/k8sChecker2/my-app/src/App.js: Unexpected reserved word 'await'. (10:14)
8 | const k8sApi = kc.makeApiClient(CoreV1Api);
9 |
> 10 | const res = await k8sApi.listServiceForAllNamespaces();
| ^
11 | return res.body.items;
12 |
13 | return (
at constructor (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:356:19)
at FlowParserMixin.raise (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:3223:19)
at FlowParserMixin.checkReservedWord (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12072:12)
at FlowParserMixin.parseIdentifierName (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12051:12)
at FlowParserMixin.parseIdentifier (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12026:23)
at FlowParserMixin.parseExprAtom (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:11231:27)
at FlowParserMixin.parseExprAtom (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:6932:20)
at FlowParserMixin.parseExprSubscripts (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:10857:23)
at FlowParserMixin.parseUpdate (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:10840:21)
at FlowParserMixin.parseMaybeUnary (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:10816:23)
at FlowParserMixin.parseMaybeUnaryOrPrivate (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:10654:61)
at FlowParserMixin.parseExprOps (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:10659:23)
at FlowParserMixin.parseMaybeConditional (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:10636:23)
at FlowParserMixin.parseMaybeAssign (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:10597:21)
at FlowParserMixin.parseMaybeAssign (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:5755:18)
at /home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:10567:39
at FlowParserMixin.allowInAnd (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12279:16)
at FlowParserMixin.parseMaybeAssignAllowIn (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:10567:17)
at FlowParserMixin.parseVar (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:13259:91)
at FlowParserMixin.parseVarStatement (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:13100:10)
at FlowParserMixin.parseStatementContent (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12683:23)
at FlowParserMixin.parseStatementLike (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12588:17)
at FlowParserMixin.parseStatementLike (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:5088:24)
at FlowParserMixin.parseStatementListItem (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12568:17)
at FlowParserMixin.parseBlockOrModuleBlockBody (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:13189:61)
at FlowParserMixin.parseBlockBody (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:13182:10)
at FlowParserMixin.parseBlock (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:13170:10)
at FlowParserMixin.parseFunctionBody (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:11935:24)
at FlowParserMixin.parseFunctionBody (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:5065:11)
at FlowParserMixin.parseFunctionBodyAndFinish (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:11921:10)
at FlowParserMixin.parseFunctionBodyAndFinish (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:5073:18)
at /home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:13318:12
at FlowParserMixin.withSmartMixTopicForbiddingContext (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12261:14)
at FlowParserMixin.parseFunction (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:13317:10)
at FlowParserMixin.parseFunctionStatement (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12984:17)
at FlowParserMixin.parseStatementContent (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12614:21)
at FlowParserMixin.parseStatementLike (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12588:17)
at FlowParserMixin.parseStatementLike (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:5088:24)
at FlowParserMixin.parseModuleItem (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12565:17)
at FlowParserMixin.parseBlockOrModuleBlockBody (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:13189:36)
at FlowParserMixin.parseBlockBody (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:13182:10)
at FlowParserMixin.parseProgram (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12464:10)
at FlowParserMixin.parseTopLevel (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:12454:25)
at FlowParserMixin.parseTopLevel (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:5893:28)
at FlowParserMixin.parse (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:14376:10)
at parse (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/parser/lib/index.js:14417:38)
at parser (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/core/lib/parser/index.js:41:34)
at parser.next (<anonymous>)
at normalizeFile (/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@babel/core/lib/transformation/normalize-file.js:64:37)
at normalizeFile.next (<anonymous>)
ERROR in [eslint]
src/App.js
Line 10:14: Parsing error: Unexpected reserved word 'await'. (10:14)
webpack compiled with 2 errors
One of your dependencies, babel-preset-react-app, is importing the
"@babel/plugin-proposal-private-property-in-object" package without
declaring it in its dependencies. This is currently working because
"@babel/plugin-proposal-private-property-in-object" is already in your
node_modules folder for unrelated reasons, but it may break at any time.
babel-preset-react-app is part of the create-react-app project, which
is not maintianed anymore. It is thus unlikely that this bug will
ever be fixed. Add "@babel/plugin-proposal-private-property-in-object" to
your devDependencies to work around this error. This will make this message
go away.
^C
When I removed the await
and combined the div’s
import logo from './logo.svg';
import './App.css';
import { KubeConfig, CoreV1Api } from '@kubernetes/client-node';
function App() {
const kc = new KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(CoreV1Api);
const res = k8sApi.listServiceForAllNamespaces();
return res.body.items;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<h1>Services</h1>
<ul>
{res.body.items.map((service) => (
<li key={service.metadata.uid}>{service.metadata.name}</li>
))}
</ul>
</div>
);
}
export default App;
I got the same blow up again
Compiled with problems:
×
ERROR in ./node_modules/@kubernetes/client-node/dist/attach.js 7:20-42
Module not found: Error: Can't resolve 'querystring' in '/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@kubernetes/client-node/dist'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "querystring": require.resolve("querystring-es3") }'
- install 'querystring-es3'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "querystring": false }
ERROR in ./node_modules/@kubernetes/client-node/dist/azure_auth.js 8:34-58
Module not found: Error: Can't resolve 'child_process' in '/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@kubernetes/client-node/dist'
Like before, I installed the package
builder@LuiGi17:~/Workspaces/k8sChecker2/my-app$ npm i querystring-es3
added 1 package, and audited 1594 packages in 3s
248 packages are looking for funding
run `npm fund` for details
11 vulnerabilities (5 moderate, 6 high)
To address all issues possible (including breaking changes), run:
npm audit fix --force
Some issues need review, and may require choosing
a different dependency.
Run `npm audit` for details.
Then, I added the resolve line fallback: { "querystring": require.resolve("querystring-es3") },
This is a game that isn’t fun. More modules to fix
Compiled with problems:
×
ERROR in ./node_modules/@kubernetes/client-node/dist/azure_auth.js 8:34-58
Module not found: Error: Can't resolve 'child_process' in '/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@kubernetes/client-node/dist'
ERROR in ./node_modules/@kubernetes/client-node/dist/config.js 8:22-46
Module not found: Error: Can't resolve 'child_process' in '/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@kubernetes/client-node/dist'
ERROR in ./node_modules/@kubernetes/client-node/dist/config.js 9:11-24
Module not found: Error: Can't resolve 'fs' in '/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@kubernetes/client-node/dist'
ERROR in ./node_modules/@kubernetes/client-node/dist/config.js 11:12-26
Module not found: Error: Can't resolve 'net' in '/home/builder/Workspaces/k8sChecker2/my-app/node_modules/@kubernetes/client-node/dist'
I installed every one it listed as missing
builder@LuiGi17:~/Workspaces/k8sChecker2/my-app$ npm install assert buffer child_process crypto fs http https net os path process stream timers tls url util zlib
npm WARN deprecated crypto@1.0.1: This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.
added 25 packages, and audited 1619 packages in 7s
254 packages are looking for funding
run `npm fund` for details
11 vulnerabilities (5 moderate, 6 high)
To address all issues possible (including breaking changes), run:
npm audit fix --force
Some issues need review, and may require choosing
a different dependency.
Run `npm audit` for details.
and same vomits
I tried
fallback: {
"querystring": require.resolve("querystring-es3"),
"path": require.resolve("path-browserify"),
"buffer": require.resolve("buffer/"),
"crypto": require.resolve("crypto-browserify"),
"http": require.resolve("stream-http"),
"stream": require.resolve("stream-browserify"),
"url": require.resolve("url/"),
"child_process": require.resolve("child_process/"),
"fs": require.resolve("fs/"),
"util": require.resolve("util/"),
},
but it still blew up on child_process and fs
but adding them
fallback: {
"querystring": require.resolve("querystring-es3"),
"path": require.resolve("path-browserify"),
"buffer": require.resolve("buffer/"),
"crypto": require.resolve("crypto-browserify"),
"http": require.resolve("stream-http"),
"stream": require.resolve("stream-browserify"),
"url": require.resolve("url/"),
"child_process": require.resolve("child_process/"),
"fs": require.resolve("fs/"),
"util": require.resolve("util/"),
},
puked. No installs work
builder@LuiGi17:~/Workspaces/k8sChecker2/my-app$ npm install child_process --save
up to date, audited 1750 packages in 3s
256 packages are looking for funding
run `npm fund` for details
11 vulnerabilities (5 moderate, 6 high)
To address all issues possible (including breaking changes), run:
npm audit fix --force
Some issues need review, and may require choosing
a different dependency.
Run `npm audit` for details.
builder@LuiGi17:~/Workspaces/k8sChecker2/my-app$ npm start
> my-app@0.1.0 start
> react-scripts start
Cannot find module '/home/builder/Workspaces/k8sChecker2/my-app/node_modules/child_process/index.js'. Please verify that the package.json has a valid "main" entry
I finally got to a point where I was ready to pivot, or at least take a break
Python
I prefer NodeJS, I do. But at the end of the day, I want a scripting language that easily compiles (or runs in an interpreter) in a container.
We can get a lot done with Python and Flask.
I created a new directory pyK8sService
and started to build out the files by hand
My first (working) app.py:
from flask import Flask, render_template, request
from kubernetes import client, config
import sys
app = Flask(__name__)
@app.route('/')
def index():
ingresses, ingress_count = get_kubernetes_resources()
print(f"Number of Ingress resources found: {ingress_count}", file=sys.stderr)
return render_template('index.html', resources=ingresses, count=ingress_count)
@app.route('/disable', methods=['GET'])
def disable_ingress():
ingress_name = request.args.get('thisIngress')
if ingress_name:
update_ingress_service(ingress_name, 'disabledservice')
return f"Ingress '{ingress_name}' updated to use service 'disabledservice'"
else:
return "Invalid request. Please provide 'thisIngress' as a GET parameter."
def get_kubernetes_resources():
config.load_incluster_config()
v1 = client.NetworkingV1Api()
namespace = open("/var/run/secrets/kubernetes.io/serviceaccount/namespace").read()
ingresses = v1.list_namespaced_ingress(namespace)
ingress_list = [ingress.metadata.name for ingress in ingresses.items]
ingress_count = len(ingress_list)
return ingress_list, ingress_count
def update_ingress_service(ingress_name, new_service_name):
config.load_incluster_config()
v1 = client.NetworkingV1Api()
namespace = open("/var/run/secrets/kubernetes.io/serviceaccount/namespace").read()
try:
ingress = v1.read_namespaced_ingress(name=ingress_name, namespace=namespace)
# Update the Ingress backend service to 'new_service_name'
print(f"Ingress first: {ingress}", file=sys.stderr)
ingress.spec.backend.service_name = new_service_name
print(f"Ingress second: {ingress}", file=sys.stderr)
v1.replace_namespaced_ingress(name=ingress_name, namespace=namespace, body=ingress)
except client.rest.ApiException as e:
return f"Error updating Ingress '{ingress_name}': {e}"
return f"Ingress '{ingress_name}' updated to use service '{new_service_name}'"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
This leverages a basic template in templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kubernetes Ingresses</title>
</head>
<body>
<h1>Kubernetes Ingresses in the Namespace: 8</h1>
<ul>
</ul>
</body>
</html>
And a requirements.txt
Flask==2.1.0
kubernetes==28.1.0
Werkzeug==2.2.2
The next file I need is a Dockerfile to build it. In fact, the only challenge I had was that python:3.8 seemed to crash but 3.8-slim was fine. Maybe I pulled a bad image, who knows.
FROM python:3.8-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Lastly, I wanted a Kubernetes Manfiest to use it. Since I decided, at least while I debugged, to use my private CR, I needed to add an image pull secret.
The RBAC rules were mostly found with trial and error. I can say that I used a bit of ChatGPT and the consequence of the older free-tier GPT 3.5 is it gets APIs wrong (or at least based on dated knowledge).
This meant I got hung up on why the Ingress was absent in the Extensions API, and even when I got the syntax right in the Python code, it kept crashing on zero entries. After a while it dawned on me that Ingresses used to be there, but now live in networking.k8s.io. My cluster (upon which i test) is 1.23 or higher.
apiVersion: v1
kind: ServiceAccount
metadata:
name: list-services-sa
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: list-services-role
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["list"]
- apiGroups: ["extensions"] # Use "extensions" API group for Ingress
resources: ["ingresses"]
verbs: ["list","create","delete"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["list","create","delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: default
name: list-services-rolebinding
subjects:
- kind: ServiceAccount
name: list-services-sa
namespace: default
roleRef:
kind: Role
name: list-services-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: list-services-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: list-services
template:
metadata:
labels:
app: list-services
spec:
serviceAccountName: list-services-sa
containers:
- name: list-services-container
image: harbor.freshbrewed.science/freshbrewedprivate/pytest01:16
ports:
- containerPort: 5000
volumeMounts:
- mountPath: "/var/run/secrets/kubernetes.io/serviceaccount/namespacewrong"
name: namespace-volume
imagePullSecrets:
- name: myharborreg
volumes:
- name: namespace-volume
downwardAPI:
items:
- path: "namespace"
fieldRef:
fieldPath: metadata.namespace
What you will notice above is I am not making an ingress. Until I figure out Auth and controls, this will live either as a NodePort service (where I reach it on a high number port on a Node) OR I’ll live with port-forwarding when I want to engage.
With a quick port-forward I could see my ingresses
I next thought I might get fancy and implement the disable too
Creating an App.py as
from flask import Flask, render_template, request
from kubernetes import client, config
import sys
app = Flask(__name__)
@app.route('/')
def index():
ingresses, ingress_count = get_kubernetes_resources()
print(f"Number of Ingress resources found: {ingress_count}", file=sys.stderr)
return render_template('index.html', resources=ingresses, count=ingress_count)
@app.route('/disable', methods=['GET'])
def disable_ingress():
ingress_name = request.args.get('thisIngress')
if ingress_name:
update_ingress_service(ingress_name, 'disabledservice')
return f"Ingress '{ingress_name}' updated to use service 'disabledservice'"
else:
return "Invalid request. Please provide 'thisIngress' as a GET parameter."
def get_kubernetes_resources():
config.load_incluster_config()
v1 = client.NetworkingV1Api()
namespace = open("/var/run/secrets/kubernetes.io/serviceaccount/namespace").read()
ingresses = v1.list_namespaced_ingress(namespace)
ingress_list = [ingress.metadata.name for ingress in ingresses.items]
ingress_count = len(ingress_list)
return ingress_list, ingress_count
def update_ingress_service(ingress_name, new_service_name):
config.load_incluster_config()
v1 = client.NetworkingV1Api()
namespace = open("/var/run/secrets/kubernetes.io/serviceaccount/namespace").read()
try:
ingress = v1.read_namespaced_ingress(name=ingress_name, namespace=namespace)
# Update the Ingress backend service to 'new_service_name'
print(f"Ingress first: {ingress}", file=sys.stderr)
ingress.spec.backend.service_name = new_service_name
print(f"Ingress second: {ingress}", file=sys.stderr)
v1.replace_namespaced_ingress(name=ingress_name, namespace=namespace, body=ingress)
except client.rest.ApiException as e:
return f"Error updating Ingress '{ingress_name}': {e}"
return f"Ingress '{ingress_name}' updated to use service '{new_service_name}'"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
and the HTML now as
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kubernetes Ingresses</title>
</head>
<body>
<h1>Kubernetes Ingresses in the Namespace: 8</h1>
<ul>
</ul>
</body>
</html>
Now that things are a bit stable, we can see the files in the repo
and it makes more sense to flip the bit and make it public
and sharing it here
I’ll close the issue with a comment
I’m going to try an App I bought on the Play store for Android, GitNex, to create an issue
which, while I cannot set the project, does create
But I can set it in the web interface
I added a couple more tasks to dig into, namely fixing the disable and implementing a full CICD pipeline
Summary
Sometimes things don’t pan out. I probably could, with enough time, energy and research have sorted it out. However, it made more sense to pivot to Python. I have some old colleagues at TR that would get quite a chuckle of me giving up on Node for Python, but regardless, this is the right path.
Next, I have to solve how best to fix my disable issue and build out the pipeline, but we’ll cover all that next time. I’m also going to start thinking on auth and documentation.