This commit is contained in:
Gilles Mouchet 2025-04-12 11:03:22 +02:00
commit d993995eb3
24 changed files with 629 additions and 0 deletions

17
.env.dist Normal file
View File

@ -0,0 +1,17 @@
# docker image
DOCKER_IMAGE=hello-kube
DOCKER_IMAGE_VERSION=1.0
PORT_LOCAL=8081
PORT=8080 # do not change var name. It using by server.js
# registry docker
DOCKER_REGISTRY=docker.io
DOCKER_REGISTRY_USER=gmouchet
DOCKER_REGISTRY_PASSWORD=TO_BE_COMPLETED
# variable used by the application
KUBERNETES_NAMESPACE=Docker
KUBERNETES_NODE_NAME=${HOSTNAME}
MESSAGE="Welcome on Docker environment"

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.env

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"editor.fontSize": 13,
"terminal.integrated.fontSize": 13,
"window.zoomLevel": 1.4,
}

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# Hello kube application
## Description
This application display a web page with:
* message
* namespace
* pod
* node
* image details
## clone repositopry
```bash
git clone https://gitweb.dyndns.org/helm/hello-kube.git
```
## Helm variables
| Variable name | Value sample | Description |
|:---|:---|:---|
| message | `hello world` | Message display on web page |
| ingress.hostUrl | `hello-kube.gmolab.net`| Site URL. Must be define in DNS or hosts file |
| deployment.replicaCount | `2` | Number of deployment's replica |
| deployment.container.image.repository | `docker.io/gmouchet/hello-kube:1.0` | Docker image repository |
| deployment.container.image.repository.pullPolicy | `IfNotPresent` | [Image Pull Policy](https://kubernetes.io/docs/concepts/containers/images/) |
| deployment.container.port | `8080` | Docker image port. Do not change without rebuild docker image |
## Deploy
```bash
KENV=gmo|vdg
helm upgrade hello-kube . \
--install \
--atomic \
--cleanup-on-fail \
--values=values-$KENV-env.yaml \
--namespace hello-kube \
--create-namespace
```
## Test
Go to http://hello-kube.gmolab.net (<ingress.hostUrl>)
## Building images
If you'd like to build the `hello-kube` container image yourself and reference from your own registry or DockerHub repository, then you can get more details on how to do this in the [Build and push container images](README_BUILD.md) documentation.
## Changelog
### v1.0.0 (2025-04-12)
#### Added
- initial version

64
README_BUILD.md Normal file
View File

@ -0,0 +1,64 @@
# Hello Kube demo app
A demo app that can be deployd to a Kubernetes cluster (k8s, kind, etc.). It displays a message, and also namespace, pod, node and image details.
This is a fork from [paulbouwer/hello-kubernetes](https://github.com/paulbouwer/hello-kubernetes)
## Configuration
| Variables | Required | Default Value | Description |
| --- | -------- | ------------- | ----------- |
| DOCKER_IMAGE | Yes | hello-kube | Docker image name |
| DOCKER_IMAGE_VERSION | Yes | 1.0 | Docker image version |
| DOCKER_REGISTRY | yes | docker.io | Docker registry name |
| DOCKER_REGISTRY_USER | Yes | gmouchet | Docker registry user |
| DOCKER_REGISTRY_PASSWORD| Yes | secret | Docker registry user password |
| PORT_LOCAL| No| 8080 | The port for URL <server_fqdn>:<PORT_LOCAL>|
| PORT | No | 8080 | The port that the app listens on. |
| MESSAGE | No | "Hello world!" | The message displayed by the app. |
| RENDER_PATH_PREFIX | No | "" | The path prefix to use when rendering the urls for the static assets in the handlebar templates. <br/> Must be used when app is deployed with an ingress using a backend path for traffic to app. |
| HANDLER_PATH_PREFIX | No | "" | The path prefix to use by handlers when serving the dynamic and static assets. <br/> Note: Must be used when app is deployed with an ingress that has a backend path for traffic to the app, but which does not rewrite the backend paths to '/'. |
| KUBERNETES_NAMESPACE | Yes | "-" | The Kubernetes namespace that the app has been deployed to. |
| KUBERNETES_NODE_NAME | Yes | "-" | The name of the Kubernetes node that the app is deployed on. |
The application relies on the following files for configuration and operational information.
| File | Required | Information | Description |
| ---- | -------- | ----------- | ----------- |
| package.json | Yes | `.version` | The release version is used when the CONTAINER_IMAGE env is not provided. |
| info.json | Yes | `.containerImageArch` | The container image architecture is used for display. This file will be overwritten in future versions as part of the container image build process when multi-arch images are suppoted. |
## Build image
**Multi architecture**
To create a multi-architecture image, proceed as follows:
```bash
docker buildx create --use --name gmobuilder
docker buildx inspect gmobuilder --bootstrap
```
```bash
docker buildx build --platform linux/amd64,linux/arm64 -t gmouchet/hello-kube:latest . --push
```
or user script `./docker.sh build`
**Variables**
```bash
cp .env.dist .env
vim .env
```
**Build image**
```bash
./docker.sh build-no-cache
```
**Others actions**
```bash
./docker.sh help
```
## Changelog
### v1.0.0 (2025-04-12)
#### Added
- initial version

10
docker-compose.yml Normal file
View File

@ -0,0 +1,10 @@
services:
hello-kube:
container_name: hello-kube
hostname: hello-kube
image: ${DOCKER_REGISTRY_USER}/${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION}
ports:
- '${PORT_LOCAL:-9090}:${PORT:-8080}' #local:docker
env_file:
- .env

101
docker.sh Executable file
View File

@ -0,0 +1,101 @@
#!/bin/bash
progName="./$(/bin/basename $0)"
DOCKER_REGISTRY="$(grep -s 'DOCKER_REGISTRY=' .env | cut -f2 -d=)"
DOCKER_REGISTRY_USER="$(grep -s 'DOCKER_REGISTRY_USER=' .env | cut -f2 -d=)"
DOCKER_REGISTRY_PASSWORD="$(grep -s 'DOCKER_REGISTRY_PASSWORD=' .env | cut -f2 -d=)"
DOCKER_IMAGE="$(grep -s 'DOCKER_IMAGE=' .env | cut -f2 -d=)"
DOCKER_IMAGE_VERSION="$(grep -s 'DOCKER_IMAGE_VERSION=' .env | cut -f2 -d=)"
function print_usage {
# print
/bin/cat << EOF
Usage: $progName [options]
Options:
start Start docker container
stop Stop docker container
down Stop docker container and delete container and image
bash Open a shell bash
bash-root Open a root shell
logs Display container logs
build Build docker images and push on registry
build-no-cache Build docker images without use cache and push on registry
help,-h,--help Display this help
Examples:
To start container
$progName start
To stop container
$progName stop
To create a new functional environment after recovering the sources
$progName build
EOF
}
if [ $# -eq 0 ]; then
echo "${progName}: you must specify an option"
echo -e "Try '$progName help' for more information.\n"
fi
while test $# -gt 0
do
case "$1" in
start)
docker compose up -d;
;;
stop)
docker compose stop
;;
down)
docker compose down --rmi all
;;
bash)
docker exec -it hello-kube /bin/sh
;;
bash-root)
docker exec --user root -it hello-kube /bin/sh
;;
logs)
docker logs hello-kube -f
;;
build-no-cache)
echo $DOCKER_REGISTRY_PASSWORD| docker login -u ${DOCKER_REGISTRY_USER} --password-stdin ${DOCKER_REGISTRY}
if [ $? -eq 0 ]; then
# need for multi architecture
docker buildx create --append --use --name gmobuilder
docker buildx inspect gmobuilder --bootstrap
# build multi architecture image
docker buildx build --no-cache --platform linux/amd64,linux/arm64 \
-t ${DOCKER_REGISTRY_USER}/${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} \
-f ./docker/hello-kube/Dockerfile . --output=type=registry
docker logout ${DOCKER_REGISTRY}
fi
;;
build)
echo $DOCKER_REGISTRY_PASSWORD| docker login -u ${DOCKER_REGISTRY_USER} --password-stdin ${DOCKER_REGISTRY}
if [ $? -eq 0 ]; then
# need for multi architecture
docker buildx create --append --use --name gmobuilder
docker buildx inspect gmobuilder --bootstrap
# build multi architecture image
docker buildx build --platform linux/amd64,linux/arm64 \
-t ${DOCKER_REGISTRY_USER}/${DOCKER_IMAGE}:${DOCKER_IMAGE_VERSION} \
-f ./docker/hello-kube/Dockerfile . --output=type=registry
docker logout ${DOCKER_REGISTRY}
fi
;;
-h|--help|help)
print_usage
exit 0
;;
*)
echo "${progName}: invalid option -- '$1'!"
echo -e "Try '$progName help' for more information.\n"
;;
esac
shift
done
exit 0

View File

@ -0,0 +1,3 @@
node_modules
package-lock.json
README.md

View File

@ -0,0 +1,17 @@
FROM node:18.20.8-alpine3.21
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies
COPY ./docker/hello-kube/package.json /usr/src/app/
#COPY package.json /usr/src/app/
RUN npm install
# Bundle app source
COPY ./docker/hello-kube/ /usr/src/app
#COPY . /usr/src/app
USER node
CMD [ "npm", "start" ]

View File

@ -0,0 +1,3 @@
{
"containerImageArch": "linux/amd64,linux/arm64"
}

View File

@ -0,0 +1,18 @@
{
"name": "hello-kube",
"version": "1.0",
"description": "Hello Kube!",
"author": "Gilles Mouchet",
"license": "MIT",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.17.1",
"express-handlebars": "^5.2.1",
"express-pino-logger": "^6.0.0",
"handlebars": "^4.7.7",
"pino": "^6.11.2"
}
}

View File

@ -0,0 +1,18 @@
{
"name": "hello-kube",
"version": "1.0",
"description": "Hello Kube!",
"author": "Gilles Mouchet",
"license": "MIT",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.17.1",
"express-handlebars": "^5.2.1",
"pino-http": "^10.4.0",
"handlebars": "^4.7.7",
"pino": "^6.11.2"
}
}

View File

@ -0,0 +1,82 @@
const express = require('express');
const exphbs = require('express-handlebars');
const os = require("os");
const fs = require('fs');
const pino = require('pino');
const expressPino = require('express-pino-logger');
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
const expressLogger = expressPino({ logger });
const app = express();
app.use(expressLogger);
app.engine('handlebars', exphbs({defaultLayout: 'main'}));
app.set('view engine', 'handlebars');
// Configuration
var port = process.env.PORT || 8080;
var message = process.env.MESSAGE || 'Hello world!';
var renderPathPrefix = (
process.env.RENDER_PATH_PREFIX ?
'/' + process.env.RENDER_PATH_PREFIX.replace(/^[\\/]+/, '').replace(/[\\/]+$/, '') :
''
);
var handlerPathPrefix = (
process.env.HANDLER_PATH_PREFIX ?
'/' + process.env.HANDLER_PATH_PREFIX.replace(/^[\\/]+/, '').replace(/[\\/]+$/, '') :
''
);
var namespace = process.env.KUBERNETES_NAMESPACE || '-';
var podName = process.env.KUBERNETES_POD_NAME || os.hostname();
var nodeName = process.env.KUBERNETES_NODE_NAME || '-';
var nodeOS = os.type() + ' ' + os.release();
var applicationVersion = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
var containerImage = process.env.CONTAINER_IMAGE || 'gmouchet/hello-kube:' + applicationVersion
var containerImageArch = JSON.parse(fs.readFileSync('info.json', 'utf8')).containerImageArch;
logger.debug();
logger.debug('Configuration');
logger.debug('-----------------------------------------------------');
logger.debug('PORT=' + process.env.PORT);
logger.debug('MESSAGE=' + process.env.MESSAGE);
logger.debug('RENDER_PATH_PREFIX=' + process.env.RENDER_PATH_PREFIX);
logger.debug('HANDLER_PATH_PREFIX=' + process.env.HANDLER_PATH_PREFIX);
logger.debug('KUBERNETES_NAMESPACE=' + process.env.KUBERNETES_NAMESPACE);
logger.debug('KUBERNETES_POD_NAME=' + process.env.KUBERNETES_POD_NAME);
logger.debug('KUBERNETES_NODE_NAME=' + process.env.KUBERNETES_NODE_NAME);
logger.debug('CONTAINER_IMAGE=' + process.env.CONTAINER_IMAGE);
// Handlers
logger.debug();
logger.debug('Handlers');
logger.debug('-----------------------------------------------------');
logger.debug('Handler: static assets');
logger.debug('Serving from base path "' + handlerPathPrefix + '"');
app.use(handlerPathPrefix, express.static('static'))
logger.debug('Handler: /');
logger.debug('Serving from base path "' + handlerPathPrefix + '"');
app.get(handlerPathPrefix + '/', function (req, res) {
res.render('home', {
message: message,
namespace: namespace,
pod: podName,
node: nodeName + ' (' + nodeOS + ')',
container: containerImage + ' (' + containerImageArch + ')',
renderPathPrefix: renderPathPrefix
});
});
// Server
logger.debug();
logger.debug('Server');
logger.debug('-----------------------------------------------------');
app.listen(port, function () {
logger.info("Listening on: http://%s:%s", podName, port);
});

View File

@ -0,0 +1,59 @@
body {
margin:0;
padding:0;
background-color:#333333;
}
div.main {
text-align: center;
}
div.main img {
margin: 40px 0;
}
div.content {
color:#f2f2f2;
}
.content #message {
margin: 10px 0 50px 0;
padding: 30px 0;
font-family: 'Ubuntu', sans-serif;
font-weight: 300;
font-size: 32pt;
background-color: #505050;
border-top: 2px solid #909090;
border-bottom: 2px solid #909090;
}
.content #footer {
margin: 50px 0;
padding: 30px 0;
font-family: 'Ubuntu', sans-serif;
font-weight: 300;
font-size: 12pt;
color: #909090;
border-top: 2px solid #909090;
}
.content #info {
margin: 0 auto;
font-family: 'Ubuntu', sans-serif;
font-weight: 300;
font-size: 12pt;
}
.content #info table {
margin: 10px auto;
}
.content #info table th {
text-align: right;
padding-right: 20px;
}
.content #info table td {
text-align: left;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,22 @@
<div id="message">
{{ message }}
</div>
<div id="info">
<table>
<tr>
<th>namespace:</th>
<td>{{ namespace }}</td>
</tr>
<tr>
<th>pod:</th>
<td>{{ pod }}</td>
</tr>
<tr>
<th>node:</th>
<td>{{ node }}</td>
</tr>
</table>
</div>
<div id="footer">
{{ container }}
</div>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Hello Kube!</title>
<link rel="stylesheet" type="text/css" href="{{ renderPathPrefix }}/css/main.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu:300" >
</head>
<body>
<div class="main">
<img src="{{ renderPathPrefix }}/images/kubernetes.png"/>
<div class="content">
{{{body}}}
</div>
</div>
</body>
</html>

29
helm/Chart.yaml Normal file
View File

@ -0,0 +1,29 @@
apiVersion: v2
name: hello-kube
description: Display a simple web page
maintainers:
- name: GMOLab
email: gilles.mouchet@gmail.com
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.0.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
# Also set the docker image tag
appVersion: "1.0.0"

View File

@ -0,0 +1,58 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-kube
namespace: {{ .Release.Namespace }}
spec:
replicas: {{ required ".Values.deployment.replicaCount entry is required!" .Values.deployment.replicaCount }}
selector:
matchLabels:
app: hello-kube
template:
metadata:
labels:
app: hello-kube
spec:
containers:
- name: hello-kube
image: {{ required ".Values.deployment.container.image.repository entry is required!" .Values.deployment.container.image.repository }}
imagePullPolicy: {{ required ".Values.deployment.container.image.pullPolicy entry is required!" .Values.deployment.container.image.pullPolicy }}
ports:
- name: http
containerPort: {{ required ".Values.deployment.container.port entry is required!" .Values.deployment.container.port }}
env:
- name: PORT
value: '{{ required ".Values.deployment.container.port entry is required!" .Values.deployment.container.port }}'
- name: MESSAGE
value: "{{ .Values.message }}"
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: KUBERNETES_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: KUBERNETES_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
startupProbe:
tcpSocket:
port: {{ required ".Values.deployment.container.port entry is required!" .Values.deployment.container.port }}
failureThreshold: 30
periodSeconds: 10
livenessProbe:
tcpSocket:
port: {{ required ".Values.deployment.container.port entry is required!" .Values.deployment.container.port }}
periodSeconds: 30
failureThreshold: 3 #Default 3
initialDelaySeconds: 0
readinessProbe:
tcpSocket:
port: {{ required ".Values.deployment.container.port entry is required!" .Values.deployment.container.port }}
initialDelaySeconds: 15
periodSeconds: 10

View File

@ -0,0 +1,18 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-hello-kube
namespace: {{ .Release.Namespace }}
spec:
ingressClassName: nginx
rules:
- host: {{ required ".Values.ingress.hostUrl entry is required!" .Values.ingress.hostUrl }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-hello-kube
port:
number: 8080

View File

@ -0,0 +1,15 @@
---
apiVersion: v1
kind: Service
metadata:
name: service-hello-kube
namespace: {{ .Release.Namespace }}
labels:
app: hello-kube
spec:
ports:
- port: {{ required ".Values.deployment.container.port entry is required!" .Values.deployment.container.port }}
protocol: TCP
selector:
app: hello-kube
type: ClusterIP

12
helm/values-gmo-env.yaml Normal file
View File

@ -0,0 +1,12 @@
# provide a custom message
message: "Bienvenue dans le lab de Gilles Mouchet"
ingress:
hostUrl: hello-kube.gmolab.net # to declare on DNS or host file
deployment:
replicaCount: 2
container:
image:
repository: "docker.io/gmouchet/hello-kube:1.0"
pullPolicy: IfNotPresent
port: 8080 # docker image port. Do not change without rebuild docker image
# with another port

12
helm/values-vdg-env.yaml Normal file
View File

@ -0,0 +1,12 @@
# provide a custom message
message: "Bienvenue dans le lab de la Ville de Genèev!"
ingress:
hostUrl: hello-kube.vdglab.net # to declare on DNS or host file
deployment:
replicaCount: 2
container:
image:
repository: "docker.io/gmouchet/hello-kube:1.0"
pullPolicy: IfNotPresent
port: 8080 # docker image port. Do not change without rebuild docker image
# with another port