reload, restart or redeploy
context
Containerized application deployed on Kubernetes, manually or via GitOps, is composed of multiple objects from namespace, deployment, configmaps, secrets, services, and more. Most are static while other will be updated during the application lifecycle.
Most of these object defintions are static while others will be updated with no to significant impact on the application, e.g.:
- changing a service impacts the access but not the application.
- changing a data backend secret impacts the application with crashing, providing corrupted and/or outdated response.
- changing a namespace or a deployment will require a redeployment of the application, meaning full disruption.
How can we address these use cases?
the application
The hello-path-go
code mockup a web service with a third-party credential loop validation. If the flag value of my-secret
is:
- different than 4321 then it fails and retry after 10 secondes.
- 4321 then it "validates" the credentials and start the webservice.
the code
The sources are available here.
package main
import (
"flag"
"fmt"
"log"
"net/http"
"os"
"time"
)
func main() {
var (
listenAddr = flag.String("listen-addr", "0.0.0.0:8080", "Service IP and Port with default 0.0.0.0:8080")
mySecret = flag.String("mysecret", "1234", "Default secret is 1234")
reloadEnable = flag.Bool("reload", false, "Enable the configuration reload capability.")
)
flag.Parse()
// Intanciate a custom logger by function.
hplog := log.New(os.Stdout, "[hello-path-go-main] ", log.LstdFlags)
// Define a handler for pseudo GET requests.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
// conditional message based on the provided path.
if path == "/mysecret" {
// return value to the pseudo GET request
fmt.Fprintf(w, "my little secret is %s", *mySecret)
// log the pseudo GET request
hplog.Println("GET ", path)
} else if path != "/" {
// return value to the pseudo GET request
fmt.Fprintf(w, "hello from %s\n", path)
// log the pseudo GET request
hplog.Println("GET ", path)
} else {
// return value to the pseudo GET request
fmt.Fprintf(w, "try to add /test as path\n")
// log the pseudo GET request
hplog.Println("GET ", path)
}
})
hplog.Println("------------------------------------------------------------")
hplog.Println("hello-path-go - a simple web service returning the URL path.")
hplog.Println("------------------------------------------------------------")
hplog.Println("Web service initialization...")
// reloadEnable help to wait for the appropriate secret to be provided.
if *reloadEnable != false {
reloadCount := 1
hplog.Println("RELOAD: Connection to remote service: nok. Checking every 10 seconds.")
for *mySecret != "4321" {
hplog.Println("RELOAD(", reloadCount, "): mysecret value is", *mySecret, "while expected value is 4321.")
reloadCount += 1
time.Sleep(10 * time.Second)
}
}
// check that the secret is correct to start the service.
// if no, we crash.
if *mySecret != "4321" {
hplog.Println("Note: mysecret value is", *mySecret, "while expected value is 4321.")
hplog.Fatalln("FATAL: connection to remote service failed. Check mysecret parameter.")
}
// if yes, we start the service.
hplog.Println("Connection to remote service: ok.")
hplog.Println("Web service accessible at", *listenAddr)
// Start the service and listen on the given port.
if err := http.ListenAndServe(*listenAddr, nil); err != nil {
// Log error messages
hplog.Fatal(err)
}
}
To follow this article, the repository can clone with the following command:
Output of the code with no parameters results in an incorrect mysecret
value:
[hello-path-go-main] 2024/04/18 11:16:44 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 11:16:44 hello-path-go - a simple web service returning the URL path.
[hello-path-go-main] 2024/04/18 11:16:44 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 11:16:44 Web service initialization...
[hello-path-go-main] 2024/04/18 11:16:44 Note: mysecret value is 1234 while expected value is 4321.
[hello-path-go-main] 2024/04/18 11:16:44 FATAL: connection to remote service failed. Check mysecret parameter.
exit status 1
Output of the code with the correct value set for my-secret
results in a working web service with a security exposure:
[hello-path-go-main] 2024/04/18 11:17:59 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 11:17:59 hello-path-go - a simple web service returning the URL path.
[hello-path-go-main] 2024/04/18 11:17:59 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 11:17:59 Web service initialization...
[hello-path-go-main] 2024/04/18 11:17:59 Connection to remote service: ok.
[hello-path-go-main] 2024/04/18 11:17:59 Web service accessible at 0.0.0.0:8080
the build
The initial image has been built using podman
with the following Containerfile
:
FROM docker.io/library/golang:1.22.0-bullseye AS build
WORKDIR /work
# Copy the source
COPY ./* /work/
WORKDIR /work/
RUN CGO_ENABLED=1 GOOS=linux GO111MODULE=on go build -a -installsuffix cgo main.go
FROM quay.io/centos/centos:stream9-minimal
LABEL org.opencontainers.image.source=https://github.com/beezy-dev/kleidi
LABEL org.opencontainers.image.title="hello-path-go"
LABEL org.opencontainers.image.vendor="beeyz.dev"
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL org.opencontainers.image.documentation="https://beezy.dev/application/envreload/"
COPY --from=build ./work/main ./main
ENTRYPOINT [ "./main" ]
To build the image, the following command can be executed:
NOTE: at this stage, if you plan to use your build, the Deployment files will require some light modifications.
The image is hosted here https://github.com/beezy-dev/verbose-couscous/pkgs/container/hello-path-go
The image tag is ghcr.io/beezy-dev/hello-path-go:v0.1
for any deployment type.
the deployment
no arg
The Deployment
manfiest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-path-go-deployment
labels:
app: hello-path-go
spec:
replicas: 1
selector:
matchLabels:
app: hello-path-go
template:
metadata:
labels:
app: hello-path-go
spec:
containers:
- name: hello-path-go
image: ghcr.io/beezy-dev/hello-path-go:v0.2
imagePullPolicy: Always
ports:
- containerPort: 8080
To deploy hello-path-go
:
Checking the Deployment
status:
NAME READY STATUS RESTARTS AGE
pod/hello-path-go-deployment-5c48979c88-wfndp 0/1 CrashLoopBackOff 1 (12s ago) 14s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hello-path-go-deployment 0/1 1 0 14s
NAME DESIRED CURRENT READY AGE
replicaset.apps/hello-path-go-deployment-5c48979c88 1 1 0 14s
Checking the logs from the Pod
:
[hello-path-go-main] 2024/04/18 18:52:08 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 18:52:08 hello-path-go - a simple web service returning the URL path.
[hello-path-go-main] 2024/04/18 18:52:08 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 18:52:08 Web service initialization...
[hello-path-go-main] 2024/04/18 18:52:08 Note: mysecret value is 1234 while expected value is 4321.
[hello-path-go-main] 2024/04/18 18:52:08 FATAL: connection to remote service failed. Check mysecret parameter.
The default secret doesn't allow the mockup connection resulting in a crash. Let's delete the Deployment
.
mysecret arg
To confirm that our application works, the code includes a flag called mysecret
setting the secret as an argument.
However, this is a clear security exposure that should never be considered for a production-grade environment.
The Deployment
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-path-go-deployment
labels:
app: hello-path-go
spec:
replicas: 1
selector:
matchLabels:
app: hello-path-go
template:
metadata:
labels:
app: hello-path-go
spec:
containers:
- name: hello-path-go
image: ghcr.io/beezy-dev/hello-path-go:v0.2
imagePullPolicy: Always
ports:
- containerPort: 8080
args:
- --mysecret=4321
To deploy hello-path-go
:
Checking the Deployment
status:
NAME READY STATUS RESTARTS AGE
pod/hello-path-go-deployment-54fdf8687b-hq9lh 1/1 Running 0 13s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 40m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hello-path-go-deployment 1/1 1 1 13s
NAME DESIRED CURRENT READY AGE
replicaset.apps/hello-path-go-deployment-54fdf8687b 1 1 1 13s
Checking the logs from the Pod
:
[hello-path-go-main] 2024/04/18 19:19:15 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 19:19:15 hello-path-go - a simple web service returning the URL path.
[hello-path-go-main] 2024/04/18 19:19:15 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 19:19:15 Web service initialization...
[hello-path-go-main] 2024/04/18 19:19:15 Connection to remote service: ok.
[hello-path-go-main] 2024/04/18 19:19:15 Web service accessible at 0.0.0.0:8080
The explicit secret export allow the mockup connection to succeed and start the web service.
However, this is a clear security exposure that should never be considered for a production-grade environment.
Let's delete the Deployment
.